Skip to main content

differ Package

The differ package compares two schemas and detects all differences, classifying each change by type and severity.
import "github.com/accented-ai/pgtofu/internal/differ"

Differ

Main type for schema comparison.
type Differ struct {
    options Options
}

func New(options Options) *Differ

Options

type Options struct {
    // IgnoreComments - don't detect comment changes
    IgnoreComments bool

    // IgnoreOwners - don't detect owner changes
    IgnoreOwners bool

    // IgnoreTablespaces - don't detect tablespace changes
    IgnoreTablespaces bool
}

func DefaultOptions() Options

Compare

Compare two schemas and return detected changes:
func (d *Differ) Compare(current, desired *schema.Database) (*DiffResult, error)

Example Usage

import "github.com/accented-ai/pgtofu/internal/differ"

func compareSchemas(current, desired *schema.Database) (*differ.DiffResult, error) {
    opts := differ.DefaultOptions()
    d := differ.New(opts)

    result, err := d.Compare(current, desired)
    if err != nil {
        return nil, fmt.Errorf("comparing schemas: %w", err)
    }

    // Print summary
    fmt.Printf("Changes: %d\n", len(result.Changes))
    fmt.Printf("Safe: %d\n", result.Stats.Safe)
    fmt.Printf("Breaking: %d\n", result.Stats.Breaking)

    return result, nil
}

DiffResult

Contains the comparison results:
type DiffResult struct {
    Current  *schema.Database
    Desired  *schema.Database
    Changes  []Change
    Warnings []string
    Stats    DiffStats
}

type DiffStats struct {
    Total                  int
    Safe                   int
    PotentiallyBreaking    int
    Breaking               int
    DataMigrationRequired  int
}

Change

Represents a single detected difference:
type Change struct {
    Type        ChangeType
    Severity    ChangeSeverity
    Description string
    ObjectType  string         // "table", "column", "index", etc.
    ObjectName  string         // Qualified name
    Details     map[string]any // Additional metadata
    DependsOn   []string       // Dependencies for ordering
    Order       int            // Execution order (after sorting)
}

ChangeType

Over 40 change types are defined:
type ChangeType string

const (
    // Tables
    ChangeTypeAddTable    ChangeType = "ADD_TABLE"
    ChangeTypeDropTable   ChangeType = "DROP_TABLE"
    ChangeTypeModifyTable ChangeType = "MODIFY_TABLE"

    // Columns
    ChangeTypeAddColumn             ChangeType = "ADD_COLUMN"
    ChangeTypeDropColumn            ChangeType = "DROP_COLUMN"
    ChangeTypeModifyColumnType      ChangeType = "MODIFY_COLUMN_TYPE"
    ChangeTypeModifyColumnNullability ChangeType = "MODIFY_COLUMN_NULLABILITY"
    ChangeTypeModifyColumnDefault   ChangeType = "MODIFY_COLUMN_DEFAULT"

    // Constraints
    ChangeTypeAddConstraint    ChangeType = "ADD_CONSTRAINT"
    ChangeTypeDropConstraint   ChangeType = "DROP_CONSTRAINT"
    ChangeTypeModifyConstraint ChangeType = "MODIFY_CONSTRAINT"

    // Indexes
    ChangeTypeAddIndex    ChangeType = "ADD_INDEX"
    ChangeTypeDropIndex   ChangeType = "DROP_INDEX"
    ChangeTypeModifyIndex ChangeType = "MODIFY_INDEX"

    // Views
    ChangeTypeAddView    ChangeType = "ADD_VIEW"
    ChangeTypeDropView   ChangeType = "DROP_VIEW"
    ChangeTypeModifyView ChangeType = "MODIFY_VIEW"

    // Functions
    ChangeTypeAddFunction    ChangeType = "ADD_FUNCTION"
    ChangeTypeDropFunction   ChangeType = "DROP_FUNCTION"
    ChangeTypeModifyFunction ChangeType = "MODIFY_FUNCTION"

    // Triggers
    ChangeTypeAddTrigger  ChangeType = "ADD_TRIGGER"
    ChangeTypeDropTrigger ChangeType = "DROP_TRIGGER"

    // Extensions
    ChangeTypeAddExtension  ChangeType = "ADD_EXTENSION"
    ChangeTypeDropExtension ChangeType = "DROP_EXTENSION"

    // Types
    ChangeTypeAddType  ChangeType = "ADD_TYPE"
    ChangeTypeDropType ChangeType = "DROP_TYPE"

    // TimescaleDB
    ChangeTypeAddHypertable           ChangeType = "ADD_HYPERTABLE"
    ChangeTypeAddCompressionPolicy    ChangeType = "ADD_COMPRESSION_POLICY"
    ChangeTypeModifyCompressionPolicy ChangeType = "MODIFY_COMPRESSION_POLICY"
    ChangeTypeAddRetentionPolicy      ChangeType = "ADD_RETENTION_POLICY"
    ChangeTypeAddContinuousAggregate  ChangeType = "ADD_CONTINUOUS_AGGREGATE"
    // ... more types
)

ChangeSeverity

type ChangeSeverity string

const (
    SeveritySafe                  ChangeSeverity = "SAFE"
    SeverityPotentiallyBreaking   ChangeSeverity = "POTENTIALLY_BREAKING"
    SeverityBreaking              ChangeSeverity = "BREAKING"
    SeverityDataMigrationRequired ChangeSeverity = "DATA_MIGRATION_REQUIRED"
)
Severity Guidelines:
SeverityChanges
SAFEAdd table, add column (nullable), add index, add function
POTENTIALLY_BREAKINGDrop index, modify default, modify view
BREAKINGDrop table, drop column, drop extension
DATA_MIGRATION_REQUIREDIncompatible type change, add NOT NULL

Comparators

Internal comparator types for specific object types:

TableComparator

type TableComparator struct {
    options Options
}

func (c *TableComparator) Compare(current, desired []schema.Table) []Change

ColumnComparator

type ColumnComparator struct {
    options Options
}

func (c *ColumnComparator) Compare(
    table string,
    current, desired []schema.Column,
) []Change

ConstraintComparator

type ConstraintComparator struct {
    options Options
}

func (c *ConstraintComparator) Compare(
    table string,
    current, desired []schema.Constraint,
) []Change

IndexComparator

type IndexComparator struct {
    options Options
}

func (c *IndexComparator) Compare(current, desired []schema.Index) []Change

Type Compatibility

The differ includes type compatibility checking:
func isCompatibleTypeChange(from, to string) (bool, ChangeSeverity)
Safe Widening:
  • smallintintegerbigint
  • VARCHAR(n)VARCHAR(m) where m > n
  • NUMERIC(p,s)NUMERIC(p',s') where p' >= p and s' >= s
Incompatible:
  • Any narrowing (larger to smaller)
  • Cross-type changes (integer to text)

Dependency Resolution

Changes are ordered using topological sort:
func OrderChanges(changes []Change) ([]Change, error)
Ordering Rules:
  1. Extensions before types
  2. Types before tables
  3. Tables before constraints
  4. Tables before indexes
  5. Tables before views
  6. Functions before triggers
  7. Drops in reverse order

See Also