schemadiff

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

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

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

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

View Source
var ErrTableNotFound = errors.New("table not found")

ErrTableNotFound is returned when the target table does not exist in the requested schema.

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

func Diff(schema string, live, desired Model) ([]Change, error)

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.

const (
	// IdentityNone means the column is not an identity column.
	IdentityNone Identity = ""
	// IdentityAlways is GENERATED ALWAYS AS IDENTITY.
	IdentityAlways Identity = "a"
	// IdentityByDefault is GENERATED BY DEFAULT AS IDENTITY.
	IdentityByDefault Identity = "d"
)

The identity kinds.

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

func Introspect(ctx context.Context, db *pgxpool.Pool, schema, table string) (Model, error)

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.

Jump to

Keyboard shortcuts

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