Check pending database migrations with info and validate

Check pending database migrations with dblift info, then confirm applied history with dblift validate. Real output from DBLift 4.11.1 on SQLite, open source.

Knowing a file is in git is not the same as knowing this database has applied it. dblift info shows which migrations are pending and which have been applied. dblift validate checks migration file consistency and applied-state integrity.

Both commands are open source, and so is dblift validate-sql. None of them needs a licence. This is episode 2 of DBLift features, explained: know what's applied. Episode 1, preview the SQL before it runs, is the step before this one. Install steps are in the five-minute install tutorial.

Everything below was run with DBLift 4.11.1, installed from PyPI (pip install dblift==4.11.1) into a virtual environment, against SQLite 3.45.1 on 19 October 2026, using Python 3.12.3. You need Python 3.11 or newer. Terminal excerpts drop the log banner lines. The confirmation info also drops its closing status box. Everything else is unedited. Installed On shows UTC. This capture ran on a UTC clock.

Step 1: A small project

python3 -m venv .venv && . .venv/bin/activate
pip install dblift==4.11.1
dblift --version
dblift version 4.11.1

Two versioned files. Version 1 creates users. Version 2 adds a column and is the one we want to see as pending.

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);
ALTER TABLE users ADD COLUMN email VARCHAR(255);

With no --config, the CLI reads dblift.yaml from the current directory:

database:
  url: "sqlite:///shop.db"
migrations:
  directory: "./migrations"

Step 2: Apply version 1 only

--target-version 1 applies version 1 and leaves version 2 on disk.

dblift migrate --target-version 1
Found 1 pending migration(s)
Migration lock acquired successfully
Statement executed successfully 
Migration V1__create_users.sql executed successfully in 1ms
Successfully applied migration V1__create_users.sql

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Command MIGRATE completed successfully (Execution time: 29 ms)               ┃
┃ Schema Version: 1                                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

migrate takes a lock on every engine and --dry-run takes none. Migration lock acquired successfully is that lock. The blank line before the success box is in the capture. Schema version is now 1. The command also writes a log file under ./logs/.

Step 3: Check pending database migrations with dblift info

The reference for check pending database migrations is dblift info. It reads the schema history table (dblift_schema_history by default) and resolves those rows against the scripts on disk.

dblift info
=== Migration Summary ===
Total Migrations: 2
Applied Migrations: 1
Pending Migrations: 1
Failed Migrations: 0
=========================

╭────────────┬─────────┬──────────────┬──────┬─────────────────────┬──────────────┬─────────┬───────────┬──────────╮
│ Category   │ Version │ Description  │ Type │ Installed On        │ Installed By │ State   │ Exec Time │ Undoable │
├────────────┼─────────┼──────────────┼──────┼─────────────────────┼──────────────┼─────────┼───────────┼──────────┤
│ Versioned  │ 1       │ create_users │ SQL  │ 2026-10-19 15:53:20 │ ubuntu       │ Success │       1ms │    No    │
│ Versioned  │ 2       │ add_email    │ SQL  │                     │              │ Pending │           │    No    │
╰────────────┴─────────┴──────────────┴──────┴─────────────────────┴──────────────┴─────────┴───────────┴──────────╯
Total migrations: 2
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Command INFO completed successfully (Execution time: 50 ms)                  ┃
┃ Schema Version: 1                                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Version 1 is Success, installed by ubuntu at 2026-10-19 15:53:20. Version 2 is Pending, with an empty Installed On and an empty Installed By. Installed On shows UTC, and this machine's clock was UTC, so 15:53:20 is the UTC time stored for version 1.

Undoable: No means neither file has a paired undo script. See Roll back a migration with undo files.

Step 4: Apply the pending migration

dblift migrate
Found 1 pending migration(s)
Migration lock acquired successfully
Statement executed successfully 
Migration V2__add_email.sql executed successfully in 1ms
Successfully applied migration V2__add_email.sql

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Command MIGRATE completed successfully (Execution time: 18 ms)               ┃
┃ Schema Version: 2                                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Only V2__add_email.sql ran. Version 1 was already applied, so this migrate left it alone. Schema version is now 2.

Step 5: Confirm with info

dblift info
=== Migration Summary ===
Total Migrations: 2
Applied Migrations: 2
Pending Migrations: 0
Failed Migrations: 0
=========================

╭────────────┬─────────┬──────────────┬──────┬─────────────────────┬──────────────┬─────────┬───────────┬──────────╮
│ Category   │ Version │ Description  │ Type │ Installed On        │ Installed By │ State   │ Exec Time │ Undoable │
├────────────┼─────────┼──────────────┼──────┼─────────────────────┼──────────────┼─────────┼───────────┼──────────┤
│ Versioned  │ 1       │ create_users │ SQL  │ 2026-10-19 15:53:20 │ ubuntu       │ Success │       1ms │    No    │
│ Versioned  │ 2       │ add_email    │ SQL  │ 2026-10-19 15:53:21 │ ubuntu       │ Success │       1ms │    No    │
╰────────────┴─────────┴──────────────┴──────┴─────────────────────┴──────────────┴─────────┴───────────┴──────────╯
Total migrations: 2

Pending migrations: 0. Version 2 was installed at 2026-10-19 15:53:21 UTC. The closing status box is the part this confirmation info drops.

Step 6: A clean dblift validate

dblift validate
Migration validation passed (SQL not parsed)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Command VALIDATE completed successfully (Execution time: 3 ms)               ┃
┃ Schema Version: 2                                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

The command exited 0. Migration validation passed (SQL not parsed) is the success line from this run. validate checks the files against applied history. It does not parse or lint the SQL. Offline file checks are `dblift validate-sql`, which is open source and does not read the history table. Episode 1 is where the [SAFE] tags in a dry run come from.

Step 7: A checksum mismatch

The database stayed as it was. The applied file on disk gained one SQL comment:

printf '\n-- edited after apply\n' >> migrations/V1__create_users.sql
dblift validate

The file then read:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- edited after apply
ERROR: Migration script V1__create_users.sql has been modified since it was applied. Database checksum: 1781645818, Filesystem checksum: -922064047
ERROR: Validation failed. Detected modified migration scripts.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ FAILED ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Command VALIDATE failed (Execution time: 3 ms)                               ┃
┃ Error: Migration script V1__create_users.sql has been modified since it was  ┃
┃ Schema Version: 2                                                            ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

The command exited 1. That is a command failure on the exit codes page (code 1), alongside 0, 2, 4, and 130. Branch on the code. The message does not quote a DBLIFT-* diagnostic code. The codes that do exist are on the error codes page.

The ERROR line is the full text: database checksum 1781645818, filesystem checksum -922064047. A negative checksum is a signed 32-bit CRC32, so the minus sign is a normal value. See Migrations and versioning. In this capture the FAILED box stops the same sentence at "was". It does not repeat the checksums. Read the ERROR line for those.

The checksum that no longer matches is the one stored on the applied row in the schema history table. Put the original file back if the edit was a mistake. dblift repair realigns history checksums and does not run the migration SQL. Use it when you intend the new file contents to become the recorded checksum.

This validate did not pass --strict. Without --strict, a missing applied migration file is only a warning, and pending versions lower than the highest applied version are allowed. --strict turns both into failures.

Make it a habit: check pending database migrations

dblift info      # which versions are pending, which are applied
dblift validate  # applied files still match the history table
dblift migrate   # apply what info listed as pending

Run info before migrate whenever you check pending database migrations. Run validate when you need to know an applied file was not edited after the fact.

FAQ

How do I check pending database migrations?

Run dblift info. Pending rows have an empty Installed On and state Pending. Applied rows show Success, who ran them, and when. Installed On shows UTC.

Does validate read the SQL?

It compares files with applied history. The passing line in this capture is Migration validation passed (SQL not parsed). validate also fails when there is a failed history row, not only on a checksum mismatch. dblift validate-sql is the open-source command that reads the files without connecting.

What exit code is a checksum mismatch?

1, from the run above. A clean dblift validate in the previous step exited 0. Codes are listed on the exit codes page.

Read next

DBLift is information technology / developer tools software. Contact: contact@dblift.com.