pgdesign v0.26.0 /internal/diff
On this page

Package diff compares two resolved schemas or a schema against a live database, producing a structured diff with risk annotations and partition changes.

#internal/diff

#internal/diff

Package diff compares two resolved schemas or a schema against a live database and produces a structured diff with risk annotations on each change.

#SchemaDiff

Go go
type SchemaDiff struct

SchemaDiff describes the differences between a desired and actual schema.

#RenamePair

Go go
type RenamePair struct

RenamePair is a resolved from->to rename (table or column).

#RenameSpec

Go go
type RenameSpec struct

RenameSpec is the set of declared renames consumed at diff time (parsed from the project config [renames] section). It is a pure, committed, CI-safe migration directive — never part of the schema's canonical identity.

#ColumnRenameSpec

Go go
type ColumnRenameSpec struct

ColumnRenameSpec declares a single column rename within a table.

#SMTransitionDiff

Go go
type SMTransitionDiff struct

SMTransitionDiff describes changes to a state machine type's transitions. Enum value changes (states added/removed) are tracked separately in EnumsChanged.

#SMTransitionRef

Go go
type SMTransitionRef struct

SMTransitionRef identifies a single directed transition edge (from -> to).

#TableDiff

Go go
type TableDiff struct

TableDiff describes the differences within a single table.

#ColumnChange

Go go
type ColumnChange struct

ColumnChange describes a change to a single column, with risk classification.

#EnumDiff

Go go
type EnumDiff struct

EnumDiff describes changes to an enum type.

#EnumValueInsert

Go go
type EnumValueInsert struct

EnumValueInsert describes an enum value inserted in the middle of an existing enum, requiring BEFORE/AFTER syntax in ALTER TYPE.

#ViewDiff

Go go
type ViewDiff struct

ViewDiff describes changes to a view.

#MaterializedViewDiff

Go go
type MaterializedViewDiff struct

MaterializedViewDiff describes changes to a materialized view.

#SequenceDiff

Go go
type SequenceDiff struct

SequenceDiff describes changes to a sequence.

#CompositeTypeDiff

Go go
type CompositeTypeDiff struct

CompositeTypeDiff describes changes to a composite type. Composite type changes are destructive (DROP + CREATE CASCADE).

#CompositeFieldChange

Go go
type CompositeFieldChange struct

CompositeFieldChange describes a change to a composite type field.

#FunctionDiff

Go go
type FunctionDiff struct

FunctionDiff describes changes to a function/procedure.

#DomainDiff

Go go
type DomainDiff struct

DomainDiff describes changes to a domain type.

#FKChange

Go go
type FKChange struct

FKChange describes a changed foreign key constraint.

#IndexChange

Go go
type IndexChange struct

IndexChange describes a changed index.

#TriggerChange

Go go
type TriggerChange struct

TriggerChange describes a changed trigger.

#PolicyDiff

Go go
type PolicyDiff struct

PolicyDiff describes changes to a single RLS policy.

#PartitionDiff

Go go
type PartitionDiff struct

PartitionDiff describes changes to a table's partitioning configuration.

#MaintenanceDiff

Go go
type MaintenanceDiff struct

MaintenanceDiff describes changes to a table's partman maintenance configuration.

#LiveNormalizer

Go go
type LiveNormalizer interface

LiveNormalizer resolves the ≈_pg RESIDUE that pure N cannot reach: catalog-dependent cast materialization (e.g. status = 'active' vs the PG-stored status = 'active'::text). It round-trips a desired-side expression through the target database — PG computes its own canonical form — so the desired side matches the introspected (already PG-canonical) side.

It is used ONLY on the live diff path (diff --live). Identity NEVER consumes its output: the pure/encoding path has no database. Implementations are best-effort total — an expression a round-trip cannot reach (referencing an absent table/column) falls to the minimal forward-simulation rule set, which is N itself.

#Diff

Go go
func Diff(desired, actual *model.Schema) *SchemaDiff

Diff compares two registry-present models (desired vs actual) and returns a structured diff. Items in desired but not in actual are "added"; items in actual but not in desired are "removed". Both sides are the SAME model class (L7): semantic type names are compared. Use DiffLive when actual is an introspected (registry-absent) schema.

#DiffLive

Go go
func DiffLive(desired, actual *model.Schema, ln LiveNormalizer) *SchemaDiff

DiffLive compares a registry-present desired model against an INTROSPECTED (registry-absent) actual schema, with an optional LiveNormalizer. Because the introspected side carries no semantic type information (L7 — a different model class), class-aware fields such as Column.SemanticTypeName are NOT compared, so they never false-drift. When ln is non-nil (the diff --live path), the DESIRED side's table-scoped expressions are round-tripped through the target DB before comparison, resolving the catalog-dependent cast residue. The caller's desired schema is never mutated — a normalized copy is built.

#IsWidening

Go go
func IsWidening(oldType, newType string) bool

IsWidening returns true if oldType -> newType is a safe widening conversion. Arguments are SQL type strings (e.g., from typeinfo.Reconstruct output).

#ChangedObjectKeys

Go go
func ChangedObjectKeys(desired, actual *model.Schema) ([]enc.Key, error)

ChangedObjectKeys is the per-object-id DIFF FAST PATH (roadmap kernel 1.4, Part I). It builds both models' revision manifests (kind-qualified key -> object-id) and returns the keys whose canonical bytes differ: objects present on one side only, or present on both with a different content id. A caller can then DEEP-diff only these objects and skip every object whose id is unchanged, turning a whole-schema comparison into O(changed objects).

When the result is empty the two models are byte-identical object-for-object, hence ≈_syn-equal, hence diff-empty (the forward conformance direction).

WIRING CHOICE (reported): this is the "kernel utility diff CONSUMES" option, not a short-circuit injected into Diff. Diff remains the AUTHORITATIVE full comparison for two reasons: (1) building two manifests on every Diff call would add an encode pass to the common (unequal) path; (2) more importantly, a manifest-equality short-circuit inside Diff would MASK Diff's own object-by-object logic from the forward-conformance test (which compares a model against its round-trip) — the test would then exercise the short-circuit rather than Diff. Keeping the fast path as a consumed utility preserves both Diff's semantics and the test's teeth. The on-disk chain (roadmap 5.2), where both manifests are already materialized, is the natural caller that prunes its O(objects) work through this function.

#FormatTerminal

Go go
func FormatTerminal(d *SchemaDiff) string

FormatTerminal renders the diff as human-readable colored terminal output.

#FormatJSON

Go go
func FormatJSON(d *SchemaDiff) string

FormatJSON renders the diff as a JSON string.

#CheckTruncationCollisions

Go go
func CheckTruncationCollisions(s *model.Schema) error

CheckTruncationCollisions is the NAMEDATALEN collision guard for the diff path. Content-derived constraint/index names can exceed 63 bytes; when two DISTINCT such names in one table collection truncate to the same 63 bytes, PostgreSQL stores them identically and matchObjectsTrunc's truncation-aware fallback can no longer tell them apart — it would silently pair both desired names against the one truncated actual. This guard runs on the DESIRED schema before any diff and returns a HARD ERROR naming the truncation and every colliding name, per-collection and per-table, so the ambiguity is surfaced loudly instead of producing a wrong migration. It is a pure function of the desired schema (the collision is a property of desired names alone).

#ResolveRenames

Go go
func ResolveRenames(d *SchemaDiff, desired, actual *model.Schema, spec RenameSpec, actualIntrospected bool) error

ResolveRenames applies the rename gate to a computed diff. actual is the base model (the reconstructed head, or the introspected live schema); it may be nil at genesis, where no removals and therefore no renames are possible. actualIntrospected suppresses class-aware column comparison against an introspected base (matching diffColumn's own contract).

#SchemaDiff.IsEmpty

Go go
func (d *SchemaDiff) IsEmpty() bool

IsEmpty returns true if the diff contains no changes.

#SchemaDiff.Summary

Go go
func (d *SchemaDiff) Summary() string

Summary returns a human-readable summary of the diff.

Search