com.worxbend.persistence
Members list
Packages
Type members
Classlikes
JDBC codecs for the two Postgres types the domain refuses to know about.
JDBC codecs for the two Postgres types the domain refuses to know about.
The point of both is negative: no org.postgresql.util.PGobject and no java.sql.Array ever escapes this module. A repository returns io.circe.Json, which is the same type modules/kernel already speaks, so the jsonb-ness of the storage stops at the edge of persistence and swapping Magnum out (ADR §12.4 budgets for it) does not ripple into the domain.
Attributes
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
Codecs.type
A source of a DataSource.
A source of a DataSource.
Structurally identical to jakarta.inject.Provider[DataSource] — one method, get(), same erasure — so ferrite binds it in a Guice module with a one-line adapter and nothing about this module leaks into the injection container. It is declared here rather than being jakarta.inject.Provider because jakarta.inject-api is not on this module's classpath, and adding a DI annotation library to an infrastructure library to express "a thing you can ask for a DataSource" would invert the dependency this trait exists to keep pointing inward: cobalt has no Guice at all and must be able to use the same pool.
Attributes
- Supertypes
-
class Objecttrait Matchableclass Any
- Known subtypes
-
class HikariPool
The two pools of ADR §5, opened and closed together.
The two pools of ADR §5, opened and closed together.
They are a pair rather than two independent objects because their failure modes are shared: if the write pool cannot be opened, a service holding only a working read pool is not degraded, it is wrong. Closing is likewise all-or-nothing, and close closes the second pool even when the first throws — a half-closed pair leaks exactly the sessions the explicit shutdown path exists to reclaim.
Attributes
- Companion
- object
- Supertypes
-
trait AutoCloseableclass Objecttrait Matchableclass Any
Attributes
- Companion
- class
- Supertypes
-
trait Producttrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
DatabaseConfig.type
Everything needed to reach PostgreSQL, plus the two pools of ADR §5.
Everything needed to reach PostgreSQL, plus the two pools of ADR §5.
The credentials live here rather than being baked into the JDBC URL so that a deployment can supply them from a secret store without string-splicing a URL, and so that a password never appears in the pool name, the metric tags or a connection-error message that quotes the URL.
Attributes
- Companion
- object
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
One bounded HikariCP pool, with an explicit shutdown.
One bounded HikariCP pool, with an explicit shutdown.
AutoCloseable and not a finaliser or a shutdown hook: a pool holds server-side sessions, and leaking them is how a restart loop exhausts max_connections and takes down the other services too. The owner — a Guice @Singleton's stop hook in ferrite, CoordinatedShutdown in cobalt — is responsible for calling close, and making that explicit is the point.
Attributes
- Companion
- object
- Supertypes
Attributes
- Companion
- class
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
HikariPool.type
What a migration run did.
What a migration run did.
Returned rather than logged so the caller can assert on it: ADR §9.3 makes "the applied-version list matches a committed baseline" a CI gate, which is the mechanism that catches a migration edited in place — the single most common way a schema silently diverges between a developer's database and production.
targetVersion is the version the schema is at when the run finishes, which is not what Flyway's MigrateResult.targetSchemaVersion holds: Flyway leaves that field null whenever nothing was pending, because to Flyway it means "the version this run migrated towards". Reported verbatim it makes the field say None on every boot after the first — so a replica logs "schema at " against a fully migrated database, and any caller that reads it as "is the schema applied" is right exactly once and wrong forever after. The version after the run is what every caller in this repo actually asks for, so that is what this field means and it is never None for a database that has any migration applied.
Attributes
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
Programmatic Flyway, callable from a service's startup and from a test.
Programmatic Flyway, callable from a service's startup and from a test.
ferrite owns the migrations (ADR §1) — cobalt and wolfram never run them, so there is exactly one writer of the schema and no ordering question at deploy time. The same entry point is reused by the db-migrate one-shot in compose, which runs the ferrite image in migrate mode rather than pulling the 380 MB flyway/flyway image for what is one JDBC call.
Two configuration choices are load-bearing:
validateOnMigratestays on. It is what turns "someone editedV1__events.sqlafter it shipped" into a failed startup instead of a database whose schema does not match the file that claims to describe it.cleanDisabledis on.Flyway.clean()drops every object in the schema; there is no operational reason this codebase ever needs it, and leaving it available means a mis-typed command can destroy the event store.
The history table deliberately lands in the connection's default schema (public) rather than in events: V1__events.sql is what creates events, and a history table living inside the schema its own migrations create makes the first run a chicken-and-egg problem that Flyway papers over with createSchemas.
Attributes
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
Migrations.type
Attributes
- Companion
- class
- Supertypes
-
trait Producttrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
PoolConfig.type
One connection pool's worth of settings.
One connection pool's worth of settings.
ADR §5 mandates two pools per service — a read-only search pool and an ingest write pool — so that a runaway search cannot starve persistence and a burst of ingest cannot starve the UI. Two pools rather than one large one is a load-shedding decision: a bounded pool is a queue with a visible depth and a timeout, and the whole point of ADR §0's rejection of a virtual-thread executor is that unbounded concurrency moves the queue somewhere you cannot see it or shed from it.
Value parameters
- connectionInitSql
-
run once per physical connection. This is where the search pool sets
statement_timeout, which is the only mechanism that reliably kills a runaway query — a client-sidequeryTimeoutleaves the backend running. - maximumPoolSize
-
also the size of the matching
CustomExecutionContext. They must be equal: more threads than connections means threads blocked ingetConnection(a timeout you did not choose), fewer means idle connections. - readOnly
-
sets the session default. Cheap defence in depth: a repository accidentally wired to the search pool fails on the write rather than performing it.
Attributes
- Companion
- object
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
The fragment algebra every query in this module is built from — and the one place SQL text is produced.
The fragment algebra every query in this module is built from — and the one place SQL text is produced.
The security property is structural, not a convention. lit is an inline method that rejects, at compile time, any argument that is not a literal constant: it asks constValueOpt for the argument's value and calls compiletime.error when there is none. A String computed at runtime — which is what user input is — has no constant type, so it cannot become SQL text. The set of characters that can ever reach a statement is therefore fixed when this module compiles, and every value the user supplied travels through bind into a JDBC placeholder.
That is stronger than ADR §6.2's "lit is private to FilterSql.scala", which is a file-scope rule a reviewer has to notice being broken, and it is why the fragment builders live here rather than being duplicated per query: there is exactly one function in the module whose argument becomes SQL, and the compiler guards it.
A String & Singleton bound alone was tried first and is not sufficient: a stable val holding a request parameter has a singleton type and would have been accepted. The bound is still present — it is what stops the literal type being widened away before constValueOpt sees it — but the constant check is what enforces the rule. The only remaining escape hatch is asInstanceOf, which the ADR already bans by review.
++ is associative in the parameter positions as well as in the text, which is what makes arbitrary AND/OR/NOT nesting work. Magnum's FragWriter.write(ps, pos): Int returns the next free position, so composing two writers is function composition on that position — no fragment needs to know its own offset, and a deeply nested filter binds its parameters in exactly the order its text mentions them.
Attributes
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
Sql.type