Documentation
¶
Overview ¶
Package pg provides peng's PostgreSQL dialect. Its Provider is backed by a pgxpool.Pool; seed functions receive pgx.Tx; the SessionLocker uses pg_advisory_lock for cross-process serialization.
This is one of several dialect packages under peng. Users pick the one matching their database and use it directly:
import (
"github.com/jackc/pgx/v5/pgxpool"
"github.com/2kims/peng"
"github.com/2kims/peng/pg"
)
pool, _ := pgxpool.New(ctx, dsn)
p, _ := pg.NewProvider(peng.Config{Dialect: peng.DialectPostgres}, pool)
p.Up(ctx)
Seed registration is per-dialect because the SeedFunc signature depends on the dialect's transaction type. pg.RegisterSeed adds to a pg-specific global registry; the auto-generated registry.go in peng/seeds/ wires those calls up at import time.
Index ¶
- Constants
- Variables
- func HashName(name string) int64
- func SchemaModelFromPool(ctx context.Context, pool *pgxpool.Pool) (*peng.Schema, error)
- func WithAutoScratch(ctx context.Context, liveDSN string, fn func(scratchDSN string) error) error
- type DBTX
- type Option
- func WithIsolationLevel(level pgx.TxIsoLevel) Option
- func WithLocker(l SessionLocker) Option
- func WithLogger(l *slog.Logger) Option
- func WithOutOfOrder(policy peng.OutOfOrderPolicy) Option
- func WithRetry(rp RetryPolicy) Option
- func WithSeedRunner(r SeedRunner) Option
- func WithStore(s Store) Option
- func WithoutLocker() Option
- type PostgresSessionLocker
- type Provider
- func (p *Provider) ApplyAllSeeds(ctx context.Context, opts ...peng.ApplySeedOption) ([]peng.SeedRunResult, error)
- func (p *Provider) ApplySeed(ctx context.Context, name string, opts ...peng.ApplySeedOption) (*peng.SeedRunResult, error)
- func (p *Provider) Close()
- func (p *Provider) Config() peng.Config
- func (p *Provider) Down(ctx context.Context) (*peng.Migration, error)
- func (p *Provider) Drift(ctx context.Context) (peng.DriftReport, error)
- func (p *Provider) Logger() *slog.Logger
- func (p *Provider) MarkApplied(ctx context.Context, migs []peng.Migration, force bool) error
- func (p *Provider) Pool() *pgxpool.Pool
- func (p *Provider) ReplaceAppliedMigrations(ctx context.Context, migs []peng.Migration) error
- func (p *Provider) RunQuery(ctx context.Context, sql string, args []any) (*peng.QueryResult, error)
- func (p *Provider) SchemaApply(ctx context.Context, snap *peng.Snapshot) error
- func (p *Provider) SchemaDiff(ctx context.Context, liveDSN, scratchDSN string) (string, error)
- func (p *Provider) SchemaDump(ctx context.Context, dsn string) (*peng.Snapshot, error)
- func (p *Provider) SchemaModel(ctx context.Context) (*peng.Schema, error)
- func (p *Provider) SchemaPreview(ctx context.Context, liveDSN, scratchDSN string) (string, error)
- func (p *Provider) SchemaPreviewModels(ctx context.Context, liveDSN, scratchDSN string) (diff string, before, after *peng.Schema, err error)
- func (p *Provider) SeedStatus(ctx context.Context) ([]peng.SeedStatus, error)
- func (p *Provider) Status(ctx context.Context) ([]peng.MigrationStatus, error)
- func (p *Provider) Store() Store
- func (p *Provider) Up(ctx context.Context) ([]peng.Migration, error)
- type RetryPolicy
- type Seed
- type SeedKind
- type SeedRunner
- type SessionLocker
- type Store
Constants ¶
const DefaultLockName = "peng"
DefaultLockName is the lock identifier used by NewSessionLocker when no name is given.
Variables ¶
var ErrNoSeedRunner = errors.New("peng/pg: no SeedRunner configured for Go seeds (run `peng build` and pass pg.WithSeedRunner)")
ErrNoSeedRunner is returned by ApplySeed / ApplyAllSeeds when a Go seed is encountered but the Provider has no SeedRunner configured.
Functions ¶
func SchemaModelFromPool ¶
SchemaModelFromPool fetches the typed schema model from any pgxpool — used by callers that need a model from a database the Provider doesn't own (e.g. an auto-scratch DB during /schema/preview).
No advisory lock is taken; the caller is presumed to be the only user of the pool. This is fine for scratch databases (transient, single-purpose) but would be wrong for the live DB — that's why the Provider's [SchemaModel] takes the lock and this exported helper doesn't.
func WithAutoScratch ¶
WithAutoScratch creates a temporary database on the same Postgres server as liveDSN, calls fn with the new database's DSN, then drops the database on return — even if fn errors. The "maintenance" connection (used to run CREATE DATABASE and DROP DATABASE) is opened against the conventional "postgres" database on the same server using the same credentials as liveDSN.
Requires the role authenticated by liveDSN to have CREATEDB privilege. Postgres on Docker / testcontainers / homebrew gives this by default; managed services like RDS / Cloud SQL may need an explicit GRANT.
The scratch database is named peng_diff_<unix>_<rand>, easy to identify and clean up manually if a peng run crashes before the defer fires.
liveDSN must be a URL-style DSN (postgres://...). Keyword-style DSNs (host=... dbname=...) are not supported by this helper — pass --scratch-dsn explicitly when using one.
Types ¶
type DBTX ¶
DBTX is the subset of pgx types that the postgres Store implementation uses. Defined as a type alias to the sqlc-generated interface so the *postgres.Store adapter satisfies pg.Store without method-signature gymnastics.
Satisfied by *pgxpool.Pool, *pgxpool.Conn, *pgx.Conn, pgx.Tx.
type Option ¶
Option configures a Provider during construction.
func WithIsolationLevel ¶
func WithIsolationLevel(level pgx.TxIsoLevel) Option
WithIsolationLevel sets the transaction isolation level used by every per-migration and per-seed transaction.
func WithLocker ¶
func WithLocker(l SessionLocker) Option
WithLocker injects a SessionLocker, overriding the default.
func WithLogger ¶
WithLogger overrides the default discard logger.
func WithOutOfOrder ¶
func WithOutOfOrder(policy peng.OutOfOrderPolicy) Option
WithOutOfOrder selects what Up does when it discovers a pending migration whose version is older than the current head. The default is peng.OutOfOrderError — Up refuses to apply such migrations until they're rebased. peng.OutOfOrderWarn relaxes that to "apply with a warning" for teams that don't want to rebase on every conflict.
func WithRetry ¶
func WithRetry(rp RetryPolicy) Option
WithRetry configures retries for migrations and seeds that fail with a transient Postgres error (serialization failure / deadlock).
func WithSeedRunner ¶
func WithSeedRunner(r SeedRunner) Option
WithSeedRunner injects a SeedRunner for executing Go seeds.
type PostgresSessionLocker ¶
type PostgresSessionLocker struct {
// contains filtered or unexported fields
}
PostgresSessionLocker uses pg_advisory_lock to serialize peng operations against the same database. The lock is held for the lifetime of the *pgxpool.Conn used to acquire it.
func NewSessionLocker ¶
func NewSessionLocker(name string) *PostgresSessionLocker
NewSessionLocker returns a locker keyed on name.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is the postgres-specific implementation of peng operations. It satisfies cli.Provider but the CLI is the only place that uses the interface — direct users construct *Provider and call its methods.
func NewProvider ¶
NewProvider constructs a postgres Provider.
func (*Provider) ApplyAllSeeds ¶
func (p *Provider) ApplyAllSeeds(ctx context.Context, opts ...peng.ApplySeedOption) ([]peng.SeedRunResult, error)
ApplyAllSeeds applies every discovered seed in version order, stopping at the first failure (the failing seed is left unrecorded; earlier successful seeds remain applied, mirroring migration semantics).
func (*Provider) ApplySeed ¶
func (p *Provider) ApplySeed(ctx context.Context, name string, opts ...peng.ApplySeedOption) (*peng.SeedRunResult, error)
ApplySeed applies a single seed by name from SeedsDir.
func (*Provider) Close ¶
func (p *Provider) Close()
Close closes the underlying pgxpool. Implements cli.Provider.
func (*Provider) Drift ¶
Drift collects every drift condition: checksum mismatches, orphaned applied rows, and out-of-order pendings.
func (*Provider) MarkApplied ¶
MarkApplied records migs in peng_migrations as already-applied without executing their SQL. It is the backfill step of `peng adopt`: the migrations have already run against this database under another tool (or are a baseline of the live schema), so peng must only learn that they are done.
Each row is stored with the real checksum hashSQL(m.UpSQL) — the same hash Up records — so adopted migrations are drift-clean: a later `peng drift` compares the file against this checksum and finds no change. (This is deliberately unlike SchemaApply's empty-checksum sentinel, which marks "applied via snapshot, file not authoritative.")
Unless force is set, MarkApplied refuses a database whose peng_migrations table already has rows, mirroring SchemaApply's freshness guard — adopting twice would double-insert.
func (*Provider) ReplaceAppliedMigrations ¶ added in v0.2.0
ReplaceAppliedMigrations replaces peng_migrations with migs without executing their SQL. Squash uses this after old migration files have been archived so the database bookkeeping matches the new baseline and does not leave orphaned rows behind.
func (*Provider) RunQuery ¶
RunQuery executes an arbitrary SQL statement against the live pool and returns the results as a QueryResult. Parameters must already be substituted to $N positional form (use QueryDef.BuildSQL first).
func (*Provider) SchemaApply ¶
SchemaApply runs a snapshot against a fresh database. Refuses if peng_migrations already has any rows (use `peng up` to advance a non-fresh database). On success, every migration on disk with version <= snapshot.HeadVersion is recorded with an empty checksum — a sentinel meaning "applied via snapshot, not via Up." Drift detection ignores empty checksums, so backfilled rows don't trigger false positives.
func (*Provider) SchemaDiff ¶
SchemaDiff compares the live database to what peng's migrations declare. It applies every migration against scratchDSN (which must point at an empty database peng does NOT create or drop — caller's responsibility), pg_dumps both databases, normalizes pg_dump's noise out, and returns a unified diff.
Returns an empty string when the two schemas match after normalization. Otherwise the returned string is a unified diff where `-` lines are in live but not migrations (suspicious; possible manual edits to the live DB) and `+` lines are in migrations but not live (unapplied migrations or manual deletions from live).
Requires pg_dump on PATH. The peng bookkeeping tables (peng_migrations, peng_seeds) are excluded from both dumps because their differences are noise — they exist on both sides with different contents (apply order, durations).
func (*Provider) SchemaDump ¶
SchemaDump shells out to pg_dump to produce a schema-only snapshot of the target database. The bookkeeping tables (peng_migrations, peng_seeds) are excluded; the snapshot represents only the user schema.
Requires pg_dump on PATH. dsn must be a Postgres connection string that pg_dump understands (the same one used to build the pgxpool is fine).
func (*Provider) SchemaModel ¶
SchemaModel introspects the live database and returns a structured peng.Schema suitable for visualization. Distinct from SchemaDump (which produces a pg_dump SQL string) and SchemaDiff (which compares two databases) — this method builds a typed model from pg_catalog and information_schema queries.
Per-Index details still aren't populated (the basic INDEX list is available via pg_index but adding it inflates the model with noise for small schemas; deferred until peng-tui has somewhere useful to render them).
func (*Provider) SchemaPreview ¶
SchemaPreview shows what running `peng up` would do to the live schema, without touching live. It mirrors live (schema + peng bookkeeping rows) to scratchDSN, captures that pre-state, applies the pending migrations on scratch, captures the post-state, and returns a unified diff.
Empty return means there are no pending migrations or applying them produces no schema change (rare but possible — e.g. a migration that only inserts data via a seed-like ALTER... DEFAULT).
scratchDSN must point at an empty database peng does NOT create or drop — caller's responsibility.
func (*Provider) SchemaPreviewModels ¶
func (p *Provider) SchemaPreviewModels(ctx context.Context, liveDSN, scratchDSN string) (diff string, before, after *peng.Schema, err error)
SchemaPreviewModels is [SchemaPreview] plus the typed schema models from both sides. Used by peng-tui's diagram-diff modal so the client can render a colored "after" diagram + per-table delta drawer without re-issuing introspection requests.
Returns the same unified-diff text as [SchemaPreview]; `before` is the live schema model; `after` is the scratch schema model after pending migrations have been applied. Auto-scratch handling stays with the caller (handlers wrap this in WithAutoScratch when no scratch DSN is configured).
func (*Provider) SeedStatus ¶
SeedStatus returns the state of every discovered seed paired with its applied record (if any).
func (*Provider) Status ¶
Status returns the state of every migration in the migrations directory paired with its applied record (if any), plus drift flags (ChecksumChanged, OutOfOrder). Orphaned applied rows do not appear here — use Drift to surface them.
func (*Provider) Up ¶
Up applies every pending migration in version order. When an out-of-order pending is detected (version older than the current head), behavior depends on the Provider's OutOfOrderPolicy: OutOfOrderError (default) returns an error before applying any pendings; OutOfOrderWarn logs a warning and applies anyway.
type RetryPolicy ¶
RetryPolicy configures how the Provider retries transactions that fail with a transient Postgres error (serialization failure 40001 or deadlock 40P01). The default zero value means "no retry".
type Seed ¶
type Seed struct {
Version string
Name string
Kind SeedKind
// Path is the absolute filesystem path. For SeedSQL it points at
// the .sql file; for SeedGo it points at the package directory.
Path string
}
Seed is one discovered seed under the Provider's SeedsDir.
func DiscoverSeeds ¶
DiscoverSeeds returns the seeds under dir, sorted by version ascending. SQL seeds are top-level <version>_<name>.sql files; Go seeds are <version>_<name>/ subdirectories. Entries that don't match either shape are skipped silently. A non-existent dir returns an empty slice with no error so a freshly-init'd project doesn't trip.
type SeedKind ¶
type SeedKind int
SeedKind distinguishes how a discovered seed is executed.
SQL seeds are single .sql files at the top of SeedsDir; the Provider runs them inline in a single transaction.
Go seeds are subdirectories of SeedsDir whose contents are compiled into a seed-runner binary by `peng build`. Provider.ApplySeed delegates execution to a SeedRunner configured via WithSeedRunner — without one, applying a Go seed fails with a clear error.
type SeedRunner ¶
SeedRunner executes a Go seed out-of-process. M3 of the v1.0.0 refactor wires the default implementation that exec()s .peng/bin/seed-runner against the Provider's DSN; until then, Go seeds require an injected runner via WithSeedRunner or applying them returns ErrNoSeedRunner.
type SessionLocker ¶
type SessionLocker interface {
Lock(ctx context.Context, conn *pgxpool.Conn) error
Unlock(ctx context.Context, conn *pgxpool.Conn) error
}
SessionLocker holds a lock for the lifetime of a single *pgxpool.Conn. Both Lock and Unlock must be called on the same connection, and the connection must remain checked out from the pool while the lock is held.
type Store ¶
type Store interface {
EnsureTables(ctx context.Context, db DBTX) error
InsertMigration(ctx context.Context, db DBTX, version, name string, durationMs int64, checksum string) error
DeleteMigration(ctx context.Context, db DBTX, version string) error
GetMigration(ctx context.Context, db DBTX, version string) (*peng.MigrationRecord, error)
ListMigrations(ctx context.Context, db DBTX) ([]peng.MigrationRecord, error)
InsertSeed(ctx context.Context, db DBTX, version, name string, durationMs int64) error
DeleteSeed(ctx context.Context, db DBTX, version string) error
GetSeed(ctx context.Context, db DBTX, version string) (*peng.SeedRecord, error)
ListSeeds(ctx context.Context, db DBTX) ([]peng.SeedRecord, error)
}
Store reads and writes peng's bookkeeping tables for the postgres dialect.