Documentation
¶
Overview ¶
Package schemadiff builds the canonical table model both front-ends share and diffs two models into an ordered statement list. The model always comes from a real PostgreSQL catalog — the live table is introspected directly, and a desired-state file is executed on a transaction-scoped scratch schema and introspected the same way, then the transaction is rolled back (execute-and-introspect; semantics are never derived from the AST). Canonical text (types, defaults, constraint and index definitions) is whatever the server's own decompilers print, so cosmetic differences (type aliases, default formatting, implicit names) never show up as diffs.
This is a periphery package (see SAFETY.md): its output is a plan request, and the core executors re-verify their own preconditions.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDifferentTables = errors.New("models describe different tables")
ErrDifferentTables is returned when the two models describe different tables — a caller bug, refused rather than diffed.
var ErrNotTable = errors.New("not an ordinary or partitioned table")
ErrNotTable is returned when the target exists but is not an ordinary or partitioned table (e.g. a view or foreign table).
var ErrTableNotFound = errors.New("table not found")
ErrTableNotFound is returned when the target table does not exist in the requested schema.
var ErrUnsupportedChange = errors.New("unsupported schema change")
ErrUnsupportedChange is returned when converging live onto desired would need a change the engine does not derive (identity or generation changes on an existing column). The caller surfaces it; nothing is guessed.
Functions ¶
This section is empty.
Types ¶
type Change ¶
type Change struct {
// SQL is the literal statement, without a trailing semicolon.
SQL string `json:"sql"`
// Kind classifies the statement for consumers that gate by class.
Kind ChangeKind `json:"kind"`
// Destructive marks statements that discard data, constraints, or
// indexes (column, constraint, and index drops — dropping a unique
// index discards the same guarantee as dropping a unique constraint).
// Destructive changes are gated by the caller, never executed
// silently.
Destructive bool `json:"destructive,omitempty"`
}
Change is one derived statement of the ordered plan.
func Diff ¶
Diff derives the ordered statement list that converges live onto desired. Order is dependency-correct: drops first (indexes, then constraints, then columns), then column adds and alters, then constraint adds, then index creates — so an added column exists before an index or constraint that references it. Within each bucket the order is deterministic: attribute order for columns, name order for constraints and indexes. schema qualifies the emitted statements' table references. Columns are compared by name only: attribute order carries no semantics in PostgreSQL and is deliberately out of scope for convergence.
type ChangeKind ¶
type ChangeKind string
ChangeKind classifies a derived statement so a consumer can gate whole classes of change (destructive, rewriting, index-building) without parsing SQL.
const ( // ChangeCreateTable creates the table (missing-table plans only). ChangeCreateTable ChangeKind = "create-table" // ChangeDropIndex drops an index. ChangeDropIndex ChangeKind = "drop-index" // ChangeDropConstraint drops a table constraint. ChangeDropConstraint ChangeKind = "drop-constraint" // ChangeDropColumn drops a column. ChangeDropColumn ChangeKind = "drop-column" // ChangeAddColumn adds a column. ChangeAddColumn ChangeKind = "add-column" // ChangeAlterType changes a column's type. ChangeAlterType ChangeKind = "alter-type" // ChangeSetDefault sets or replaces a column default. ChangeSetDefault ChangeKind = "set-default" // ChangeDropDefault drops a column default. ChangeDropDefault ChangeKind = "drop-default" // ChangeSetNotNull adds the NOT NULL attribute. ChangeSetNotNull ChangeKind = "set-not-null" // ChangeDropNotNull removes the NOT NULL attribute. ChangeDropNotNull ChangeKind = "drop-not-null" // ChangeAddConstraint adds a table constraint. ChangeAddConstraint ChangeKind = "add-constraint" // ChangeCreateIndex creates an index. ChangeCreateIndex ChangeKind = "create-index" )
The change kinds a plan can contain.
func ChangeKinds ¶
func ChangeKinds() []ChangeKind
ChangeKinds returns the closed set of ChangeKind values. It is part of the plan-report contract (docs/plan-report.md): the set changes only with a format_version bump, and a consumer that meets an unrecognized value must treat the statement as unknown and refuse it.
type Column ¶
type Column struct {
// Name is the column name.
Name string
// Type is the canonical type text (format_type), e.g. "character
// varying(50)" — never an alias like varchar(50).
Type string
// NotNull reports the NOT NULL attribute.
NotNull bool
// Default is the canonical default expression (pg_get_expr), empty when
// none. For a generated column it is the generation expression.
Default string
// SequenceDefault reports that the default expression depends on a
// sequence (per pg_depend) — a serial column or a hand-written nextval
// default. In a desired-state model that sequence exists only inside
// the rolled-back scratch transaction, so no derived plan can
// reference it.
SequenceDefault bool
// Identity is the identity kind, IdentityNone for plain columns.
Identity Identity
// Generated reports GENERATED ALWAYS AS (...) STORED.
Generated bool
}
Column is one column of the canonical model.
type Constraint ¶
type Constraint struct {
// Name is the constraint name.
Name string
// Def is the canonical definition text.
Def string
}
Constraint is one table constraint: its name plus the server-decompiled definition (pg_get_constraintdef), e.g. "PRIMARY KEY (id)".
type Identity ¶
type Identity string
Identity is a column's identity kind, as pg_attribute.attidentity spells it.
type Index ¶
type Index struct {
// Name is the index name.
Name string
// Def is the canonical CREATE INDEX statement.
Def string
}
Index is one non-constraint index: its name plus the server-decompiled CREATE INDEX statement (pg_get_indexdef), unqualified under the introspection search_path.
type Model ¶
type Model struct {
// Table is the unqualified table name.
Table string
// Columns are the table's columns in attribute order.
Columns []Column
// Constraints are the table constraints, name-sorted.
Constraints []Constraint
// Indexes are the non-constraint indexes, name-sorted.
Indexes []Index
}
Model is the canonical, comparison-ready description of one table. It carries no schema qualification: the live and desired sides are introspected under matching search_path settings so their definitions compare textually.
func Introspect ¶
Introspect reads the live table schema.table into the canonical model. It runs inside a read-only transaction whose search_path is set to the target schema (then public), so the server's decompilers print definitions unqualified — directly comparable with a desired-state model introspected the same way.
func IntrospectDesired ¶
func IntrospectDesired(ctx context.Context, db *pgxpool.Pool, desired statement.DesiredSchema) (Model, error)
IntrospectDesired materializes a desired-state schema on a scratch schema and introspects it into the canonical model: execute-and-introspect, the decided way the engine understands DDL semantics. The scratch schema is created inside a single transaction that is always rolled back — nothing the desired file defines ever persists, no CREATEDB privilege is needed, and server-version and extension parity with the live table hold by construction because it runs on the same database.