postgres

package
v4.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 19 Imported by: 0

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

View Source
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
)
View Source
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

View Source
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")
)
View Source
var ErrCommandConflict = errors.New("PostgreSQL migration command conflict")

ErrCommandConflict means the exact command, source, base or prefix differs.

View Source
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.

View Source
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.

View Source
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.

View Source
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

func New(database *sql.DB, options ...Option) (*Backend, error)

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

func (backend *Backend) Acquire(ctx context.Context) (migrations.Session, error)

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

func (backend *Backend) ForNamespace(namespace string) (*Backend, error)

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

func (backend *Backend) Inspect(ctx context.Context) (migrations.Checksum, error)

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

func (backend *Backend) ReadLedger(ctx context.Context) (records []migrations.Record, err error)

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

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.

func (*CommandSession) Release

func (s *CommandSession) Release(ctx context.Context) error

Release releases/discards the native connection; use a finite independent cleanup context after cancellation. It does not close the borrowed database.

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

type Option func(*Backend) error

Option configures a PostgreSQL backend during construction only.

func WithLockRetryInterval

func WithLockRetryInterval(interval time.Duration) Option

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

func WithLockTimeout(timeout time.Duration) Option

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

func WithOperationTimeout(timeout time.Duration) Option

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

func WithStatementTimeout(timeout time.Duration) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL