Skip to content

Repository files navigation

seqwall logo

Seqwall is a tool for PostgreSQL migrations testing.
Ensure that every migration is reversible, idempotent, compatible with others in sequence, structurally sound and verifiable.

CI status coverage go version license MIT platforms


📦 Installation

Docker images

Package: https://github.com/realkarych/seqwall/pkgs/container/seqwall.

docker run --rm --network=host \
  ghcr.io/realkarych/seqwall:latest staircase --help

Homebrew (macOS & Linux)

brew tap realkarych/tap
brew install seqwall        # first install
brew upgrade seqwall        # later updates

Debian / Ubuntu (APT)

# Import the GPG key
curl -fsSL https://realkarych.github.io/seqwall-apt/public.key \
  | sudo tee /etc/apt/trusted.gpg.d/seqwall.asc

# Add the repository
echo "deb [arch=$(dpkg --print-architecture)] \
  https://realkarych.github.io/seqwall-apt stable main" \
  | sudo tee /etc/apt/sources.list.d/seqwall.list

# Install / update
sudo apt update
sudo apt install seqwall          # first install
sudo apt upgrade seqwall          # later updates

Other distros / Windows

Download the pre‑built archive from the Releases page, unpack, add the binary to your PATH.

On Windows, you may need Unblock-File .\seqwall.exe before first run.

Go install (Go ≥ 1.17)

go install github.com/realkarych/seqwall@latest
# make sure $GOBIN (default ~/go/bin) is on your PATH

✅ Once installed, verify it works

❯ seqwall staircase --help
Launch staircase testing

Usage:
  seqwall staircase [flags]

Flags:
      --postgres-url string           PostgreSQL URL (required or fallback: $DATABASE_URL environment variable)
      --migrations-path string        Path to migrations. Migrations must be in lexicographical order (required)
      --upgrade string                Shell command that applies next migration (required)
      --downgrade string              Shell command that reverts current migration (required)
      --migrations-extension string   Extension of migration files (default: .sql)
      --schema stringArray            Schemas to test (default [public])
      --test-snapshots                Compare schema snapshots. If false, only checks fact that migrations are applied
                                      / reverted with no errors (default true)
      --depth int                     Depth of staircase testing (0 = all)
      --help                          help for staircase

🧬 Methodology & Core Principles

Migrations are contracts

Each migration must be reversible and must not break the schema if applied, reverted, and reapplied.

Snapshots reveal the truth

After each migration, Seqwall captures schema metadata using information_schema views and PostgreSQL system catalogs.

This includes tables, columns, constraints, indexes, views, triggers, functions, enums, sequences, and foreign keys. Constraint snapshots retain complete definitions and enforcement state. Foreign key snapshots retain ordered local and referenced columns with qualified table identities. Column snapshots record effective nullability on every supported PostgreSQL version. On PostgreSQL 18, constraint snapshots also retain native NOT NULL names, definitions, validation state, enforcement, and inheritance behavior. Trigger snapshots retain complete definitions and enabled state for user-defined triggers, including constraint triggers. PostgreSQL-generated internal triggers are excluded, so custom changes to their firing mode are not compared. Sequence snapshots retain their numeric type, start, minimum, maximum, increment, cycle and cache configuration, plus qualified column ownership for explicit, serial and identity sequences. Runtime counters such as the current or last value and is_called are data state and are excluded. Column references to domain, composite, and range types retain their qualified type identity, and enum labels are captured. Snapshot coverage is deliberately bounded: table persistence, row-level security policies, partition and inheritance metadata, relation options and ownership, complete domain/composite/range definitions, extension object definitions, non-table ACLs, and role-dependent table-grant visibility are not comprehensively captured. The snapshots are compared using structured diffs. This comparison covers the captured metadata in the selected schemas; it does not establish universal database equivalence or a transactionally consistent view during concurrent DDL.

Staircase testing guarantees schema consistency

We use a 3-phase strategy:

  1. actualize — applying all migrations and captures etalon schema snapshot for each migration.

  2. down → up → down — starting from the latest migration, step backwards:

    • downgrade one migration,
    • upgrade it again,
    • then downgrade once more (down step).
    • At each step, the schema is compared with previously captured etalon snapshots — both before and after — ensuring reversibility and no drift.
  3. re-actualize — starting from the lower point reached in step 2 (after several rollbacks):

    • re-apply each migration one by one
    • compare each re-applied migration with etalon

This ensures that the migration chain is robust in both directions, even when recovering from mid-chain downgrades.

staircase

Standalone by design

Seqwall is a single-purpose CLI tool — it requires no server, no daemon, no embedded framework, and no special runtime.

You can run it locally or in CI/CD (recommended), with just your migrations and a database connection string. No vendor lock-in, no config-files, no dependencies beyond PostgreSQL.

Test migrations as they really run

Seqwall runs your actual migration scripts and commands — no wrapper DSLs, no abstractions, no mocks.

You bring your own migration runner (dbmate, alembic, goose, sqlx, atlas, etc.). Seqwall just executes shell commands.

Seqwall captures the database state before the first migration and expects the first rollback to restore that state. Initialize any tables or other objects that your migration runner needs before starting Seqwall.

For dbmate 2.27.0, the following command creates its schema_migrations table without applying a migration:

export DATABASE_URL='postgres://postgres@localhost:5432/postgres?sslmode=disable'
dbmate --schema-file /dev/null dump

This requires pg_dump on PATH. The schema dump is discarded through /dev/null, while the bookkeeping table remains in the database and becomes part of Seqwall's initial snapshot.

Passing the current migration to your runner

Every upgrade and downgrade command receives SEQWALL_CURRENT_MIGRATION in its environment, even when the command contains no placeholder. Its value is the exact path discovered by Seqwall, without quoting or normalization. Each command must apply or revert exactly one migration. An unrestricted up that applies all pending migrations violates the staircase algorithm.

For a POSIX-compatible shell, pass the value as a double-quoted argument:

seqwall staircase --postgres-url "$DATABASE_URL" --migrations-path ./migrations \
  --upgrade './migrate-one up "$SEQWALL_CURRENT_MIGRATION"' \
  --downgrade './migrate-one down "$SEQWALL_CURRENT_MIGRATION"'

The outer single quotes defer expansion until Seqwall runs the command. Inside a shell script, use "$SEQWALL_CURRENT_MIGRATION" in the same way. Repeat the quoted variable to pass the path twice. Do not embed the value in source passed to eval or another sh -c. Seqwall uses $SHELL, falling back to sh when unset or empty; a non-POSIX shell requires its own safe variable syntax, or a helper that reads the environment directly.

On Windows, use a native helper that reads SEQWALL_CURRENT_MIGRATION, or invoke PowerShell scripts without inserting the filename into the command string:

--upgrade "powershell.exe -NoProfile -File .\apply-one.ps1"
--downgrade "powershell.exe -NoProfile -File .\revert-one.ps1"

The scripts read $env:SEQWALL_CURRENT_MIGRATION as a string; for example, Get-Content -LiteralPath $env:SEQWALL_CURRENT_MIGRATION reads that exact file. A native helper can pass the value to its runner using an argument list. Expanding %SEQWALL_CURRENT_MIGRATION% in cmd is not a universal literal-data contract: delayed expansion, CALL, nested parsing, and command construction can reinterpret the filename.

The legacy {current_migration} placeholder retains raw source substitution for filenames containing only ASCII letters, digits, _, -, ., and /; Windows also permits \ and :. An empty value retains the previous empty substitution behavior. Every other filename is rejected before the shell starts if the command contains a placeholder, including quoted, embedded, or repeated placeholders. This deliberately restricts previously accepted templates with spaces or punctuation; use the environment contract for those names. Legacy substitution preserves simple filename behavior and does not promise literal arguments in arbitrary shell evaluation contexts.

Limitations & Scope

Does this mean Seqwall is the only tool you need for testing migrations?

No — databases involve a spectrum of concerns, and a complete testing strategy should include:

  • Load testing — to observe performance & regressions
  • Lock behavior analysis — to catch deadlocks and blocking issues
  • Data state testing — to ensure data survives or transforms as expected
  • Static analysis — to catch anti-patterns or unsafe operations before runtime
  • Integration tests — to validate application logic against migrated schemas
  • ...

Seqwall focuses on schema-level structural correctness — nothing more, nothing less.

🙏 Contribution

Found a bug?

  • Please open an issue with a clear description, reproduction steps (if possible), and expected vs. actual behavior.

Have a question?

Want to suggest a feature?

Ready to contribute code?

  • Look for issues marked with help wanted or good first issue. In fact, you can pick any issue without Assignees 😊️️️️️️.
  • Fork the repo, create a branch, and open a pull request when ready (and tag @realkarych for review).

Your feedback and contributions are always welcome 💙.

About

🧪 Testing tool for PostgreSQL migrations — schema-snapshots based, framework‑agnostic.

Topics

Resources

Stars

32 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages