Documentation
¶
Overview ¶
Package database owns the backend's Postgres access plumbing: the pgx connection pool, the transaction helper every service writes through, and the advisory-lock-guarded migration runner that gates startup.
It deliberately contains no queries. All SQL is sqlc-generated and parameterized by construction (CLAUDE.md rule 3, docs/architecture.md §14.1); this package only supplies the connection and transaction machinery those generated queries run on.
Index ¶
- Constants
- func AfterCommit(ctx context.Context, fn func(ctx context.Context))
- func Migrate(ctx context.Context, opts MigrateOptions) error
- func New(ctx context.Context, opts PoolOptions) (*pgxpool.Pool, error)
- func RunInTx(ctx context.Context, pool *pgxpool.Pool, ...) (err error)
- type MigrateOptions
- type PoolOptions
Constants ¶
const MigrationAdvisoryLockKey int64 = 0x4E4F524954450001
MigrationAdvisoryLockKey is the Postgres advisory lock ID this package serializes migrations on.
The value is arbitrary but must never change: it is the whole coordination mechanism between processes. The bytes spell "NORITE" followed by a slot number, so it is recognizable in pg_locks output when an operator is trying to work out what is holding things up.
This is *our* lock, distinct from the one golang-migrate's own Postgres driver takes internally (keyed off the database and schema name). Both exist and they do not conflict: ours is always acquired first and covers the entire run — version check included — where the driver's covers only its own statements.
Variables ¶
This section is empty.
Functions ¶
func AfterCommit ¶
AfterCommit registers fn to run once the transaction ctx belongs to has committed, in registration order.
It panics when ctx carries no transaction. The two quiet alternatives are both wrong in a way no test would notice: running fn at once is exactly the before-commit dispatch rule 5 forbids, and dropping it loses an event or leaves a revoked session's connection open. A panic is found the first time the path runs, which is always in a test.
func Migrate ¶
func Migrate(ctx context.Context, opts MigrateOptions) error
Migrate applies all pending migrations, serialized across processes by a Postgres advisory lock.
It is deliberately blocking and is called before the server reports itself ready: a self-hosted instance must never serve against a not-yet-migrated schema (docs/architecture.md §2, "Cross-cutting"). The advisory lock is what makes that safe when two processes start at once — a self-hoster briefly running two copies of the binary, or a restart racing its predecessor — since without it both could try to apply the same migration concurrently.
The lock lives on a dedicated connection, so a crashed process releases it automatically when its session ends rather than wedging every future startup.
Canceling ctx aborts the wait for the lock, and stops a multi-migration run at the next boundary. It does NOT interrupt a statement already executing: golang-migrate's pgx driver runs each migration on its own context.Background(). A stop mid-run is reported as an error, never as success, so the caller cannot mark an instance ready against a partially-migrated schema.
The flagship reaches the same guarantee through a Helm pre-upgrade Job running this same code path (docs/architecture.md §12); only the trigger differs.
func New ¶
New opens a pgx connection pool and verifies it can actually reach Postgres.
Verifying up front is the point: pgxpool connects lazily, so without the ping a misconfigured DSN would surface as a confusing failure on the first real request instead of as a clear startup error.
func RunInTx ¶
func RunInTx(ctx context.Context, pool *pgxpool.Pool, fn func(ctx context.Context, tx pgx.Tx) error) (err error)
RunInTx runs fn inside a transaction, committing on success and rolling back on any error or panic.
Every mutating service method goes through this. It is what makes the project's mutation-and-audit-log-in-one-transaction rule (CLAUDE.md rule 2) expressible as a single fn, and what gives the gateway its "dispatch only after commit" ordering (rule 5): whatever must happen only once the change is durable is registered with AfterCommit on the ctx fn receives, and runs after Commit returns nil — never if the transaction rolls back.
fn's ctx is the caller's with the transaction's hook list added. Closures conventionally name it ctx and shadow the outer one, so every call made inside fn carries the list without being edited; a call that reaches past the shadow to the outer ctx and registers a hook panics rather than losing it (see AfterCommit).
Types ¶
type MigrateOptions ¶
type MigrateOptions struct {
// DatabaseURL is the Postgres connection string.
DatabaseURL string
// Source holds the migration files, normally the go:embed'd backend/migrations FS.
Source fs.FS
// SourceDir is the path within Source to read migrations from ("." for the FS root).
SourceDir string
// LockTimeout bounds how long to wait for the advisory lock before giving up. Waiting is normal —
// another process mid-migration — so this only needs to be long enough to outlast a real migration.
LockTimeout time.Duration
// Logger receives progress lines. Optional.
Logger *zerolog.Logger
}
MigrateOptions configures Migrate.
type PoolOptions ¶
type PoolOptions struct {
// DatabaseURL is the Postgres connection string.
DatabaseURL string
// MaxConns / MinConns size the pool. Kept deliberately small per replica — see
// docs/architecture.md §11 "Database connection management" and config.Config's own commentary.
MaxConns int32
MinConns int32
// ConnectTimeout bounds how long New waits for the first successful connection.
ConnectTimeout time.Duration
}
PoolOptions configures New.