Skip to main content

Phase 1: Extract

The extractor connects to your PostgreSQL database and queries the system catalogs (pg_catalog) to build a complete schema representation.

What Gets Extracted

  • Tables - Columns, constraints, indexes, partitioning, row security flags and policies
  • Views - Regular and materialized views with definitions
  • Functions - All languages, with full body and attributes
  • Triggers - Table triggers with conditions
  • Types - Enums, composites, domains
  • Sequences - With ownership information
  • Extensions - Installed extensions
  • TimescaleDB - Hypertables, compression/retention policies, continuous aggregates

Excluded Schemas

System schemas are automatically excluded: Use --exclude-schema to exclude additional schemas (e.g., _prisma, supabase_*).

Phase 2: Parse

The parser reads your SQL files and converts them to the same JSON schema format as the extractor.

Supported SQL

pgtofu supports standard PostgreSQL DDL:

Type Normalization

Types are normalized for accurate comparison:

Phase 3: Diff

The differ compares the current schema (from extract) with the desired schema (from parse) to detect all differences.

Change Detection

For each object type, the differ:
  1. Builds maps by qualified name (schema.name)
  2. Detects additions (in desired but not current)
  3. Detects deletions (in current but not desired)
  4. Detects modifications (in both but different)

Change Severities

Safe Type Widening

These type changes are classified as SAFE:

View Comparison

View definitions are normalized before comparison to handle formatting differences (whitespace, case, aliases). When statically identifiable output columns change names at the same ordinal, pgtofu emits explicit ALTER VIEW ... RENAME COLUMN statements before replacing the definition. Changes whose prior definitions depend on other changed objects remain in one migration batch, with tables and views restored before dependent indexes, functions, and triggers in down migrations.

Policy Comparison

Policies are identified by their table and name. pgtofu compares the command, permissive or restrictive mode, role set, both expressions, and comments. Expression comparison handles common PostgreSQL catalog rewrites while preserving literal case, quoted identifiers, and meaningful casts. See row-level security for declarations and migration behavior.

Phase 4: Generate

The generator creates golang-migrate compatible migration files from the detected changes.

Dependency Resolution

Changes are ordered using topological sort (Kahn’s algorithm):
  1. Extensions
  2. Custom types
  3. Sequences
  4. Tables
  5. Columns, constraints
  6. Indexes
  7. Views
  8. Functions
  9. Triggers
  10. TimescaleDB features
Deletions are ordered in reverse.

Generated DDL Features

  • Idempotent - Uses IF EXISTS/IF NOT EXISTS clauses
  • Transaction-wrapped - Grouped in BEGIN/COMMIT blocks when safe
  • Reversible - Both up and down migrations generated
  • Documented - Includes comments describing each change
  • Safe identifiers - All identifiers properly quoted

File Naming

Breaking Change Warnings

Schema File Organization

Recommended directory structure:
The CLI discovers .sql files recursively and reads them in filename order, with files under policies/ read after the other schema files. Keep each table’s policy declarations after its table definition, or put them in policies/.

Best Practices

Include the table definition and its indexes in the same file for easier maintenance.
Use CONSTRAINT fk_orders_user FOREIGN KEY... instead of anonymous constraints for clearer migrations.
Commit schema files to git. They are the source of truth for your database structure.
Always run pgtofu diff before pgtofu generate to review changes.

See Also