Documentation
¶
Overview ¶
Package postgres is the PostgreSQL migration dialect: it reads a live database into a schema.Schema through catalog queries.
Index ¶
- type Dialect
- func (d Dialect) Canonicalize(ctx context.Context, c momo.PinnedConn, s *schema.Schema) error
- func (d Dialect) CanonicalizeTables(ctx context.Context, c momo.PinnedConn, s *schema.Schema) (skipped []migrate.ObjectRef, err error)
- func (Dialect) ClassifyError(err error) error
- func (Dialect) Dialect() *dialect.Dialect
- func (d Dialect) HistoryDDL() []string
- func (Dialect) HistoryDDLFor(table string) []string
- func (Dialect) HistoryLockKey(table string) string
- func (d Dialect) HistoryTable(ctx context.Context, c momo.PinnedConn) (string, error)
- func (d Dialect) Inspect(ctx context.Context, c momo.PinnedConn, opts migrate.InspectOptions) (*schema.Schema, error)
- func (Dialect) InspectWithInvalid(ctx context.Context, c momo.PinnedConn, opts migrate.InspectOptions) (*schema.Schema, []string, error)
- func (Dialect) Lock(ctx context.Context, c momo.PinnedConn, key string) (unlock func() error, holder func(context.Context) (string, error), err error)
- func (Dialect) Render(plan []migrate.Change) ([]migrate.Statement, error)
- func (Dialect) Setup(ctx context.Context, c momo.PinnedConn, s migrate.SessionOptions) (reset func() error, err error)
- type Option
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Dialect ¶
type Dialect struct {
// contains filtered or unexported fields
}
Dialect is the PostgreSQL implementation of migrate.Dialect. It holds only its options; every method works on the pinned connection it is given.
func (Dialect) Canonicalize ¶
Canonicalize rewrites the declared views, functions, policies and triggers of s to the text PostgreSQL itself reports: pg_get_viewdef qualifies and re-parenthesizes a view query, pg_get_function_arguments and pg_get_function_result respell a signature, and pg_get_expr and pg_get_triggerdef respell policy and trigger conditions. Without this a declared object never compares equal to its inspection, and the first Diff after a baseline would drop and recreate every view and its dependents, which loses the data of a materialized view.
It creates the declared objects in a scratch schema inside a transaction (tables first, dependencies ordered as a migration would order them), reads them back with the inspector and replaces the declared text in s, then rolls the transaction back, so the scratch schema and everything in it vanish and the application schema is untouched. The scratch schema comes first on the search_path and the connection's own path follows, so unqualified names in the declared text resolve as they do in the live database. A schema without views, functions, triggers or policies is left alone and no statement runs.
The same round trip rewrites the text of tables: check expressions, column defaults and generation expressions (pg_get_constraintdef and pg_get_expr add parentheses and casts, turn IN lists into = ANY (ARRAY[...]) and BETWEEN into two comparisons), and the predicate and expression parts of indexes. Without it a freshly applied migration that declares CHECK (length(title) > 0) reads back as ((length((title)::text) > 0)) and drifts at once.
Foreign keys, indexes without text and the schema qualifiers of the declared objects are not reproduced in the scratch schema, because none of them affects the text read back. Only the tables that carry text are created (all of them when the schema also declares views, functions, triggers or policies, which may refer to any table). Tables of one name in different schemas are canonicalized in separate scratch schemas, one per round, so none of them is refused; two views or functions of one name in different schemas still are.
Limit: each round's scratch schema leads the search_path alone, so a table, trigger or policy in a later round (one whose table or index name is taken in an earlier round) does not see a function created in an earlier round's scratch schema. It resolves the live function of that name when there is one; otherwise Canonicalize fails.
func (Dialect) CanonicalizeTables ¶
func (d Dialect) CanonicalizeTables(ctx context.Context, c momo.PinnedConn, s *schema.Schema) (skipped []migrate.ObjectRef, err error)
CanonicalizeTables is the best-effort form Drift uses: it rewrites only the table text of s (check expressions, defaults, generation expressions, index predicates and expressions) and leaves views, functions, triggers and policies as declared. A table whose scratch copy cannot be created or read back keeps its declared text and is returned in skipped (each creation runs in a savepoint, so one failing table does not abort the others). When the whole round trip fails (no CREATE privilege, a read-only transaction, a failing read-back) it returns the error together with every table that carries text in skipped; s is then left as declared. The search_path limit of Canonicalize applies: a function created in an earlier round's scratch schema is not visible to a later round.
func (Dialect) ClassifyError ¶
ClassifyError turns the failures that a missing extension causes into a momo.ErrUnsupported error that says which extension is not installed and keeps the driver error as its cause: CREATE EXTENSION on a server without the package or by a role that may not create it, and a pgvector type used without the extension. Every other error is returned unchanged. The migration engine applies it to the errors of Apply and AutoMigrate.
func (Dialect) HistoryDDL ¶
HistoryDDL returns the momo_migrations DDL for the bare table name, which lives in the first schema of the session's search_path, or, with WithHistorySchema, for the table qualified with that schema. The engine uses HistoryDDLFor with the qualified reference from HistoryTable instead.
func (Dialect) HistoryDDLFor ¶ added in v0.4.0
HistoryDDLFor returns the history table DDL for table, a SQL-ready reference such as the one HistoryTable returns.
func (Dialect) HistoryLockKey ¶ added in v0.4.0
HistoryLockKey returns the advisory-lock key for the history table reference that HistoryTable returned: "momo:<schema>.momo_migrations" with the unquoted schema name, the key earlier versions used for the same table.
func (Dialect) HistoryTable ¶ added in v0.4.0
HistoryTable returns the quoted, schema-qualified reference of the momo_migrations table, resolved once against the pinned connection. An existing table is found the way earlier momo versions found it, through the connection's search_path, so installs made with the unqualified name keep working and no second table is created. The resolved object must be a regular table in a permanent schema, owned by the connecting role or by a role whose privileges it holds (pg_has_role USAGE; a superuser connecting role also requires the owner to be itself or a superuser): a view, a foreign table, a temporary table or a table owned by an unrelated role is refused with an error wrapping momo.ErrValidation, because writing history through it would run with the migrator's privileges against an object someone else controls. When no table exists yet, the reference names current_schema, the schema the CREATE TABLE uses; when there is no current schema either, the error wraps migrate.ErrNoHistoryTable. Every later statement uses the returned reference, so a migration that changes search_path cannot redirect the history writes. With WithHistorySchema the lookup and the creation schema are that schema and the search_path plays no part.
func (Dialect) Inspect ¶
func (d Dialect) Inspect(ctx context.Context, c momo.PinnedConn, opts migrate.InspectOptions) (*schema.Schema, error)
Inspect reads the objects in opts.Schemas (default: the connection's current schema) into a schema. A table in the current schema has an empty Table.Schema; Schema.Schemas lists only the other managed namespaces. opts.Exclude is applied again by the engine; the dialect also uses it to skip a matching table, view, function or trigger before reading it, so one the schema model cannot represent does not fail the whole inspection.
func (Dialect) InspectWithInvalid ¶
func (Dialect) InspectWithInvalid(ctx context.Context, c momo.PinnedConn, opts migrate.InspectOptions) (*schema.Schema, []string, error)
InspectWithInvalid is Inspect plus the qualified names (relative to the current schema: bare, or "schema.index" elsewhere) of the indexes PostgreSQL marks invalid, which a failed CREATE INDEX CONCURRENTLY leaves behind. The schema model has no slot for them and they stay in Table.Indexes, so a plain Inspect hides the failure. Drift detection (momo-04 Task 18) must call this and report each name as Changed with Detail "invalid index (failed CONCURRENTLY build)". Names follow table order, then index name.
func (Dialect) Lock ¶
func (Dialect) Lock(ctx context.Context, c momo.PinnedConn, key string) (unlock func() error, holder func(context.Context) (string, error), err error)
Lock takes a session-level advisory lock on the pinned connection with the non-blocking pg_try_advisory_lock; the engine owns the wait loop. The key is hashed with fnv64a. Advisory locks are database-wide, so the engine passes "momo:<history schema>.<history table>" to keep schemas independent.
When another session holds the lock, Lock returns migrate.ErrLocked with the holder named ("held by pid=N application_name=X") and a holder function that repeats that query. Otherwise it returns unlock, which releases the lock on the same connection and is safe to call from a defer on any path: it ignores cancellation of ctx. The lock is session-scoped, so it is useless behind a transaction-mode pooler such as PgBouncer; run migrations on a direct or session-pooled connection.
When the session is broken (for example the apply was cancelled while a statement ran), unlock cannot release the lock itself: the connection is discarded, and PostgreSQL frees the session lock when that backend exits, which happens shortly after unlock returns, not before. A caller that must take the lock again at once retries on ErrLocked for a short, bounded time.
Order: run the reset returned by Setup before unlock, so a small statement_timeout cannot cancel the unlock statement.
func (Dialect) Render ¶
Render turns an ordered plan into PostgreSQL statements. The plan must already be dependency-ordered by migrate.OrderErr or migrate.OrderWithViews, whose cycle errors the caller surfaces; Render never reorders. Every identifier is quoted, every literal comes through quoteLiteral, and the output depends only on the plan, never on map order.
func (Dialect) Setup ¶
func (Dialect) Setup(ctx context.Context, c momo.PinnedConn, s migrate.SessionOptions) (reset func() error, err error)
Setup applies lock_timeout and statement_timeout at session scope on the pinned connection. A zero duration leaves that setting untouched unless SessionOptions says it was set explicitly, which sends SET ... = 0 (off). The returned reset restores both to the session defaults (RESET), so nothing leaks into the pool when the connection is released. reset is idempotent and ignores cancellation of ctx. When RESET fails, reset discards the connection (momo.Discarder) so the timeouts cannot leak into the pool. Run reset before the unlock returned by Lock. If Setup itself fails part-way, the settings it already applied are reset before it returns.
type Option ¶ added in v0.10.0
type Option func(*Dialect)
Option configures New.
func WithHistorySchema ¶ added in v0.10.0
WithHistorySchema pins the momo_migrations history table to schema. The table is then looked up as "schema".momo_migrations and not through the search_path, and a new one is created in schema, not in current_schema(). Without it a table of that name in any schema on the search_path, such as public, is found and used, and a test schema that only sits in front of public never gets its own history or its own tables (momotest sets this option). The schema must exist when the history table is created. An empty schema keeps the default behaviour.