Documentation
¶
Overview ¶
Package postgres implements the public.migrations ledger and connection-bound advisory-lock backend for migrations.Runner. New borrows a caller-owned *sql.DB; it does not open, close or migrate the database at construction. Acquire owns one leased connection until Release. Cooperating jobs in the same database share a stable lock; unrelated schema writers are not excluded.
Transactional SQL and ledger changes use one transaction per migration. Nontransactional execution persists dirty state first; unresolved dirty outcomes require explicit operator review/recovery. An error during commit or timeout cleanup can follow durable changes; inspect state before retrying. Finite contexts bound cooperative work, not context-free database/sql finalizers.
Default PostgreSQL operation formatting hides driver diagnostics, but error traversal may expose the original cause. Schema inspection returns sensitive definitions and covers only selected catalog properties, not a security audit. See docs/operations.md and docs/threat-model.md (risks 003, 006 and 007).
Index ¶
- Constants
- Variables
- func Fingerprint(objects []SchemaObject) (migrations.Checksum, error)
- type Backend
- func (backend *Backend) Acquire(ctx context.Context) (migrations.Session, error)
- func (b *Backend) AcquireCommand(ctx context.Context) (*CommandSession, error)
- func (backend *Backend) ForNamespace(namespace string) (*Backend, error)
- func (backend *Backend) Inspect(ctx context.Context) (migrations.Checksum, error)
- func (backend *Backend) InspectObjects(ctx context.Context) ([]SchemaObject, error)
- func (backend *Backend) InstallFresh(ctx context.Context, history []migrations.Migration, preflightSQL string) (records []migrations.Record, err error)
- func (backend *Backend) ReadLedger(ctx context.Context) (records []migrations.Record, err error)
- type CatalogIdentity
- type CatalogKind
- type CommandBatch
- type CommandHistory
- type CommandProgress
- func (p CommandProgress) Base() []LedgerSnapshot
- func (p CommandProgress) Complete() bool
- func (p CommandProgress) Fingerprint() [32]byte
- func (CommandProgress) Format(s fmt.State, _ rune)
- func (p CommandProgress) ID() string
- func (p CommandProgress) IdempotencyDigest() [32]byte
- func (CommandProgress) LogValue() slog.Value
- func (CommandProgress) MarshalJSON() ([]byte, error)
- func (CommandProgress) MarshalText() ([]byte, error)
- func (p CommandProgress) Receipts() []CommandReceipt
- type CommandReceipt
- type CommandRequest
- type CommandSession
- func (s *CommandSession) CatalogPresence(ctx context.Context, objects []CatalogIdentity) ([]bool, error)
- func (s *CommandSession) Execute(ctx context.Context, req CommandRequest) (CommandProgress, error)
- func (s *CommandSession) ReadLedger(ctx context.Context, namespace string) ([]migrations.Record, error)
- func (s *CommandSession) ReadProgress(ctx context.Context, id string) (CommandProgress, bool, error)
- func (s *CommandSession) Release(ctx context.Context) error
- type LedgerSnapshot
- type Option
- type SchemaObject
Constants ¶
const ( // MaxLedgerRecords permits one baseline plus the complete bounded migration // source while preventing a hostile ledger from growing retained state. MaxLedgerRecords = migrations.MaxMigrationFiles + 1 // MaxLedgerBytes bounds aggregate kind/name/checksum text retained from one // ledger read, not allocations made by the driver before each scan. MaxLedgerBytes = 16 << 20 )
const ( // MaxSchemaObjects bounds the number of catalog entries in one snapshot. MaxSchemaObjects = 10_000 // MaxSchemaObjectIdentityBytes bounds one canonical catalog identity. MaxSchemaObjectIdentityBytes = 4 << 10 // MaxSchemaObjectDefinitionBytes bounds one canonical catalog definition. MaxSchemaObjectDefinitionBytes = 1 << 20 // MaxSchemaSnapshotBytes bounds aggregate identities and definitions in one // retained snapshot. MaxSchemaSnapshotBytes = 16 << 20 )
Variables ¶
var ( // ErrInvalidConfig indicates unusable PostgreSQL backend configuration. ErrInvalidConfig = errors.New("invalid PostgreSQL migration configuration") // ErrLockNotHeld indicates that advisory ownership was lost or duplicated. ErrLockNotHeld = errors.New("PostgreSQL migration advisory lock not held") // ErrSessionReleased indicates use after lock-session release. ErrSessionReleased = errors.New("PostgreSQL migration session released") // ErrLedgerConflict indicates that owned-ledger state changed unexpectedly. ErrLedgerConflict = errors.New("PostgreSQL migration ledger conflict") // ErrDatabaseOperationFailed identifies a PostgreSQL driver failure whose // diagnostic is available only through errors.Is or errors.As. ErrDatabaseOperationFailed = errors.New("PostgreSQL migration database operation failed") // ErrResourceLimit indicates that PostgreSQL returned more migration or // schema state than the package can safely retain. ErrResourceLimit = errors.New("PostgreSQL migration resource limit exceeded") )
var ErrCommandConflict = errors.New("PostgreSQL migration command conflict")
ErrCommandConflict means the exact command, source, base or prefix differs.
var ErrCommandOutcomeUnknown = errors.New("PostgreSQL migration command outcome unknown")
ErrCommandOutcomeUnknown requires authoritative discovery under ownership; cancellation or a failed commit acknowledgement is never rollback evidence.
var ErrCommandSerialization = errors.New("migration command serialization forbidden")
ErrCommandSerialization denies diagnostic serialization of execution inputs and retained keyed bindings. Native durable encoding is private and SQL-free.
var ErrInstallOutcomeUnknown = errors.New("PostgreSQL fresh installation outcome unknown")
ErrInstallOutcomeUnknown means fresh installation may have committed. Read the authoritative ledger under migration ownership before deciding whether installation succeeded; never blindly retry setup or infer rollback.
var ErrInvalidSchemaSnapshot = errors.New("invalid PostgreSQL schema snapshot")
ErrInvalidSchemaSnapshot indicates ambiguous catalog data that cannot form a reviewed baseline contract.
Functions ¶
func Fingerprint ¶
func Fingerprint(objects []SchemaObject) (migrations.Checksum, error)
Fingerprint hashes supplied catalog objects using the v1 schema framing: sorted unique identities and byte-length-prefixed, unnormalized definitions. It bounds object count/field/aggregate bytes and rejects empty or duplicate identities and empty definitions. It copies the slice before sorting and accepts an empty inventory. It does not query a database, verify completeness, validate SQL/encoding, authenticate the input or check security properties.
Types ¶
type Backend ¶
type Backend struct {
// contains filtered or unexported fields
}
Backend owns PostgreSQL preparation and creates connection-bound sessions.
func New ¶
New constructs the backend without invoking database itself or taking its ownership. Caller options run left-to-right until a nil/failing option; repeated settings use the last value. Defaults are 30-second acquisition, five-minute statement and ten-minute primary-operation timeouts. The caller closes the database after all sessions finish and must not mutate options during use.
func (*Backend) Acquire ¶
Acquire waits for the stable package advisory lock on one physical connection and returns all subsequent session operations bound to it. Its lock timeout includes pool acquisition and polling. Database failures are not retried; uncertain lock-query failures discard the physical connection. A successful caller must Release the session even after operation failure.
func (*Backend) AcquireCommand ¶
func (b *Backend) AcquireCommand(ctx context.Context) (*CommandSession, error)
AcquireCommand acquires native ownership without preparing any schema. It preserves existing Runner and Session contracts and their global lock.
func (*Backend) ForNamespace ¶
ForNamespace derives an IO-free backend for one independent migration owner. It borrows the same database and copies timeout policy without changing the receiver. Namespace is canonical lowercase ASCII snake case, starts with a letter, and contains 1..46 bytes. Empty, uppercase, ambiguous or oversized names return ErrInvalidConfig; values are never normalized or truncated.
The exact ledger is public.golib_migrations_<namespace>. No other history is discovered, adopted, renamed or merged. Retain the same namespace throughout an owner's lifetime; changing it selects empty/different authority, not an upgrade. Selection does not alter migration SQL, versions or checksums and is not an authorization boundary. The default backend still owns public.migrations.
Every operation, including inspection/baseline and atomic installation, uses the selected ledger. Schema inspection remains database-wide: only this ledger's objects are excluded; nonselected ledgers remain visible. All owners retain the same native advisory lock so schema mutation stays serialized. Derived backends are safe for concurrent use with immutable configuration; the caller retains database ownership. Nil/zero receivers deny without IO.
func (*Backend) Inspect ¶
Inspect returns a fingerprint of the package-selected catalog query using a separate leased connection, without taking the migration advisory lock or changing the ledger. It does not freeze concurrent DDL for a later Baseline.
func (*Backend) InspectObjects ¶
func (backend *Backend) InspectObjects(ctx context.Context) ([]SchemaObject, error)
InspectObjects returns selected catalog identities/definitions for review, excluding the package-owned ledger. The caller owns the returned slice; text can contain sensitive SQL/literals and should not enter routine diagnostics. It does not take the advisory lock. The query includes relations, columns, constraints, indexes, triggers, selected policy expressions, enum/domain types, functions and extension versions, but not role grants/ownership, policy role targets, RLS enabled/forced flags, row contents or all PostgreSQL properties. A matching fingerprint therefore does not certify the whole schema/security state; review those omitted properties separately before adoption.
func (*Backend) InstallFresh ¶
func (backend *Backend) InstallFresh(ctx context.Context, history []migrations.Migration, preflightSQL string) (records []migrations.Record, err error)
InstallFresh installs a complete ordered history and optional package-owned preflight SQL in one transaction, retaining each original version/checksum. Ledger creation, preflight effects and every migration roll back together on execution failure. Existing nonempty history returns ErrLedgerConflict before preflight execution; this is not an upgrade, baseline or automatic retry API.
Only reviewed transactional SQL is accepted: preflight and migration SQL must not contain transaction-control statements or nontransactional effects. Empty histories, invalid ordering, no-transaction steps and combined SQL exceeding MaxMigrationSourceBytes deny before database IO. Input is copied for this call; callers must not mutate the slice concurrently with entry.
The stable native advisory lock covers discovery, execution and finalization. Work and lock acquisition are bounded by backend policy/context; independent cleanup uses a finite deadline. The database is borrowed and stays open. Error formatting redacts SQL/driver diagnostics, but traversal may expose underlying driver causes; do not log unwrapped diagnostics. A commit or post-commit unlock failure returns ErrInstallOutcomeUnknown and no success records. Nil/canceled contexts deny before IO. Concurrent calls serialize through native migration ownership. This API does not change Runner's individual-migration transaction semantics.
func (*Backend) ReadLedger ¶
ReadLedger discovers the existing selected ledger history without preparing schema or acquiring the migration advisory lock. A missing ledger yields empty history; a non-table relation at that name returns ErrLedgerConflict. It never discovers or adopts another ledger name.
Discovery uses one bounded repeatable-read, read-only transaction and the same row validation and resource limits as Session.Records. Returned records are caller-owned; dirty records remain dirty. The borrowed database stays open. Error formatting is redacted; errors.Is preserves classification and error traversal may expose driver causes. Do not log unwrapped diagnostics. Concurrent calls are safe. Nil/canceled contexts deny before database IO.
The result is a snapshot, not migration ownership or permission to apply. Compose BuildStatus or PlanUp with the exact source to inspect compatibility; execution must rediscover and validate history under acquired ownership. Runner.Status/Plan retain their existing locked preparation behavior.
type CatalogIdentity ¶
type CatalogIdentity struct {
// Kind selects the identifier tuple grammar and the catalog query.
Kind CatalogKind
// Schema names the exact PostgreSQL namespace; it is empty for roles.
Schema string
// Name names the object within Schema, or the cluster role for CatalogRole.
Name string
// Member names the column for CatalogColumn and is otherwise empty.
Member string
}
CatalogIdentity is one exact PostgreSQL identifier tuple. Each populated identifier is 1..63 valid UTF-8 bytes without NUL; no normalization occurs. Schema uses Schema alone; role uses Name alone; relation/function use Schema+Name; column additionally requires Member. Other fields must be empty.
type CatalogKind ¶
type CatalogKind uint8
CatalogKind identifies bounded PostgreSQL ownership metadata, not SQL. Zero is invalid. Relations include indexes and other pg_class entries.
const ( // CatalogSchema identifies a namespace, including an empty owned schema. CatalogSchema CatalogKind = iota + 1 // CatalogRelation identifies one named relation/index in its exact schema. CatalogRelation // CatalogRole identifies one cluster role, with Schema and Member empty. CatalogRole // CatalogColumn identifies one non-dropped relation column in Member. CatalogColumn // CatalogFunction identifies any overload with the exact schema/name. CatalogFunction )
type CommandBatch ¶
type CommandBatch struct {
// Namespace must match exactly one original CommandHistory owner.
Namespace string
// Migrations is the next contiguous pending prefix for that owner.
Migrations []migrations.Migration
// Preflight is reviewed transactional setup for a complete fresh owner only.
Preflight string
}
CommandBatch is one atomic execution unit. Original native steps and optional reviewed owner preflight commit together with their command attribution. Only transactional migrations are supported. A fresh preflight requires an empty original history and the owner's complete source in a single batch. SQL is a reviewed deployment artifact: it must not contain transaction control or nontransactional effects, or mutate migration ownership state.
func (CommandBatch) Format ¶
func (CommandBatch) Format(s fmt.State, _ rune)
Format redacts preflight and migration SQL for every formatting verb.
func (CommandBatch) LogValue ¶
func (CommandBatch) LogValue() slog.Value
LogValue excludes original executable SQL.
func (CommandBatch) MarshalJSON ¶
func (CommandBatch) MarshalJSON() ([]byte, error)
MarshalJSON denies accidental executable SQL serialization.
func (CommandBatch) MarshalText ¶
func (CommandBatch) MarshalText() ([]byte, error)
MarshalText denies accidental executable SQL serialization.
type CommandHistory ¶
type CommandHistory struct {
// Namespace selects the original physical ledger; empty selects the default.
Namespace string
// Source is the complete immutable history, not just the pending suffix.
Source []migrations.Migration
// Base is the original acknowledged history, never advanced during resume.
Base []migrations.Record
}
CommandHistory describes one original native source and its original base. Namespace empty selects public.migrations; other values use ForNamespace's exact grammar. Base is never advanced by the caller during same-command resume.
func (CommandHistory) Format ¶
func (CommandHistory) Format(s fmt.State, _ rune)
Format redacts original source SQL for every formatting verb.
func (CommandHistory) LogValue ¶
func (CommandHistory) LogValue() slog.Value
LogValue excludes original source SQL and base metadata.
func (CommandHistory) MarshalJSON ¶
func (CommandHistory) MarshalJSON() ([]byte, error)
MarshalJSON denies accidental source serialization.
func (CommandHistory) MarshalText ¶
func (CommandHistory) MarshalText() ([]byte, error)
MarshalText denies accidental source serialization.
type CommandProgress ¶
type CommandProgress struct {
// contains filtered or unexported fields
}
CommandProgress is an immutable native checkpoint. Its zero value is invalid. Command records must be retained while resume or reconciliation is possible.
func (CommandProgress) Base ¶
func (p CommandProgress) Base() []LedgerSnapshot
Base returns detached original physical-ledger snapshots, not current state.
func (CommandProgress) Complete ¶
func (p CommandProgress) Complete() bool
Complete reports that every selected batch has durable native attribution. It does not revalidate current schema or ledger state; Execute does that.
func (CommandProgress) Fingerprint ¶
func (p CommandProgress) Fingerprint() [32]byte
Fingerprint returns the exact original caller policy binding.
func (CommandProgress) Format ¶
func (CommandProgress) Format(s fmt.State, _ rune)
Format redacts retained command bindings for every formatting verb.
func (CommandProgress) ID ¶
func (p CommandProgress) ID() string
ID returns the original stable command identity, or empty for zero.
func (CommandProgress) IdempotencyDigest ¶
func (p CommandProgress) IdempotencyDigest() [32]byte
IdempotencyDigest returns the original retained keyed binding for recovery. It is not a credential or permission to renew/replace the original key.
func (CommandProgress) LogValue ¶
func (CommandProgress) LogValue() slog.Value
LogValue excludes retained keyed bindings and prefix metadata.
func (CommandProgress) MarshalJSON ¶
func (CommandProgress) MarshalJSON() ([]byte, error)
MarshalJSON denies accidental retained-binding serialization.
func (CommandProgress) MarshalText ¶
func (CommandProgress) MarshalText() ([]byte, error)
MarshalText denies accidental retained-binding serialization.
func (CommandProgress) Receipts ¶
func (p CommandProgress) Receipts() []CommandReceipt
Receipts returns a detached exact durable prefix in execution order.
type CommandReceipt ¶
type CommandReceipt struct {
// Namespace identifies the owner whose transaction produced Record.
Namespace string
// Record is the canonical native ledger entry committed with the SQL effect.
Record migrations.Record
}
CommandReceipt atomically attributes one canonical native record to a command. Ordered receipts are an exact prefix, never inferred from schema presence.
type CommandRequest ¶
type CommandRequest struct {
// ID is a stable globally unique ASCII command identifier of 16..128 bytes.
ID string
// Fingerprint binds caller policy; it must be nonzero and remain unchanged.
Fingerprint [32]byte
// IdempotencyDigest is a purpose-bound keyed digest, never a plaintext key.
IdempotencyDigest [32]byte
// Histories is the complete original owner/source/base selection.
Histories []CommandHistory
// Batches is the original ordered execution plan, including committed steps.
Batches []CommandBatch
}
CommandRequest binds one globally unique canonical command to a keyed caller fingerprint and complete original sources/base/ordered execution units. Fingerprint is caller-owned policy binding, never a raw idempotency key. Calls copy collections; callers must not mutate concurrently with entry.
func (CommandRequest) Format ¶
func (CommandRequest) Format(s fmt.State, _ rune)
Format redacts source SQL and policy bindings for every formatting verb.
func (CommandRequest) LogValue ¶
func (CommandRequest) LogValue() slog.Value
LogValue excludes SQL, keyed bindings and retained execution state.
func (CommandRequest) MarshalJSON ¶
func (CommandRequest) MarshalJSON() ([]byte, error)
MarshalJSON denies accidental execution-input serialization.
func (CommandRequest) MarshalText ¶
func (CommandRequest) MarshalText() ([]byte, error)
MarshalText denies accidental execution-input serialization.
type CommandSession ¶
type CommandSession struct {
// contains filtered or unexported fields
}
CommandSession retains one native global ownership interval and physical connection across independent namespaces. Methods serialize with Release; each has a bounded context. Release is required; the database stays borrowed.
func (*CommandSession) CatalogPresence ¶
func (s *CommandSession) CatalogPresence(ctx context.Context, objects []CatalogIdentity) ([]bool, error)
CatalogPresence reads 1..4096 exact identities in one statement through the retained native ownership connection, without preparing or reacquiring any ledger. Results preserve every input position, including duplicates, and belong to the caller. This is presence evidence, not schema adoption proof. Cancellation or failure returns no partial results. PostgreSQL is borrowed; method calls serialize with Execute/Release and obey the native timeout.
func (*CommandSession) Execute ¶
func (s *CommandSession) Execute(ctx context.Context, req CommandRequest) (CommandProgress, error)
Execute validates every participating history before any write, then commits original transactional SQL and native command-prefix attribution atomically. Only the exact original request resumes; external matching progress conflicts.
func (*CommandSession) ReadLedger ¶
func (s *CommandSession) ReadLedger(ctx context.Context, namespace string) ([]migrations.Record, error)
ReadLedger discovers the exact namespace through the retained connection. Missing history stays empty; no schema is prepared or ownership reacquired.
func (*CommandSession) ReadProgress ¶
func (s *CommandSession) ReadProgress(ctx context.Context, id string) (CommandProgress, bool, error)
ReadProgress discovers only native attributed progress, without preparing command state. Missing commands return exists=false and an invalid zero value.
type LedgerSnapshot ¶
type LedgerSnapshot struct {
// Namespace identifies the physical ledger from the original command.
Namespace string
// Records retains detached original metadata, not renewed/current authority.
Records []migrations.Record
}
LedgerSnapshot contains detached original records for one physical owner.
type Option ¶
Option configures a PostgreSQL backend during construction only.
func WithLockRetryInterval ¶
WithLockRetryInterval controls polling when another job owns the advisory lock. Interval must be positive; the default is 100 milliseconds. Context cancellation interrupts the timer between unsuccessful contention attempts.
func WithLockTimeout ¶
WithLockTimeout replaces the finite default advisory-lock polling timeout. The override must remain positive, so callers cannot disable the bound. Lock attempts repeat only while another session owns the lock; database errors are returned without retry.
func WithOperationTimeout ¶
WithOperationTimeout replaces the finite default deadline covering each complete PostgreSQL session or schema-inspection operation. The override must be at least one millisecond, so callers cannot disable the bound.
func WithStatementTimeout ¶
WithStatementTimeout replaces the finite default PostgreSQL statement_timeout applied to each migration transaction or explicit no-transaction execution session. The override must be at least one millisecond, so callers cannot silently select PostgreSQL's unbounded zero value.
type SchemaObject ¶
type SchemaObject struct {
// Identity is a unique, nonempty catalog key of at most
// MaxSchemaObjectIdentityBytes; fingerprinting does not normalize its bytes.
Identity string
// Definition is nonempty catalog text of at most
// MaxSchemaObjectDefinitionBytes. It can contain sensitive SQL or literals.
Definition string
}
SchemaObject is one canonical PostgreSQL catalog identity and definition. Objects are sorted during fingerprinting, so database row order is irrelevant.