Docs/Concepts/Data management
ProEnterpriseAlpha

Data management

Schema migrations change structure. Data sets change rows — the one-off UPDATE, INSERT and DELETE that support and operations apply against live data because the application does not expose them.

The path is the same idea as a migration, pointed at DML: a file is planned, reviewed, applied under row-count assertions, and written to an audit ledger. With Enterprise, apply also captures before/after row images so the correction can be reversed. Plans and command output never contain row data, so nothing sensitive lands in git, CI logs, or review tooling.

Not a backup or disaster-recovery mechanism

Undo is a targeted, point-in-time reversal of a correction DBLift itself made. The before-images live inside the same database as your data, so they share its blast radius: they cannot recover from DROP TABLE, disk loss, ransomware, infrastructure failure, or any change DBLift did not make. Keep your normal backups and PITR — undo complements them, it does not replace them.

Versus schema migrations

Schema (V / U)Data (D)
What it changesTables, columns, indexes, viewsRows
IdentityVersionTimestamp id (D20260618143022)
Review artifactThe SQL file, plus plan/preflight on EnterpriseA static data plan JSON with no row data
UndoA matching U file you writeEnterprise restores captured before-images
HistorySchema history tablePer-set audit ledger

A data correction is not a substitute for a migration. If the change is structural, use a V file. If it is a row fix that should be reviewed, asserted, and auditable, use a data set.

What each licence unlocks

CapabilityTier
data new, data plan, data apply, data statusPro
data undo plus before/after image capture during apply, including a direct child rowEnterprise
Governance policy on a set (policy:). Capture-dependent rules need Enterprise.Pro / Enterprise

A Pro apply still writes the ledger. Without Enterprise there is simply nothing to reverse. Running data undo without an Enterprise licence fails with a clear message, never a traceback.

See dblift data for the CLI split.

Create a file (Pro)

dblift data new writes a correction file. It does not open a database, and it never overwrites a file that is already there.

dblift data new "activate user" --dataset corrections

The file lands in the data set's first configured directory, named D<YYYYMMDDHHMMSS>__<slug>.sql, with the -- dblift:formatted header, a default expect=>=1 onfail=halt directive, and a placeholder UPDATE. --table fills the table name. JSON output is {"success": true, "path": "...", "id": "D<timestamp>"}. An unconfigured --dataset is an error, not an empty plan.

The loop

dblift data plan --dataset billing --output plan.json
# review plan.json in the PR — no row data in it
dblift data apply plan.json
dblift data status --dataset billing
# Enterprise:
dblift data undo D20260618143022 --dataset billing

plan is static. It reports, per correction, the enabled triggers on the affected tables, the foreign-key children the statement reaches, an undo verdict, and the policy violations that can be checked without running the DML. apply re-runs the selection, checks each expect, and appends a hash-chained ledger row. status lists each correction and verifies the chain. undo restores the before-image captured when that correction was applied.

An unconfigured --dataset is refused by plan, apply, status, and undo. The error names the dataset and the ones that are configured. Nothing is written, and apply does not fall back to a made-up history table.

Undo verdict

When data plan is connected, each correction carries impact.undo_verdict. With no connection, impact is null. The verdict describes undo completeness. It does not stop data apply by itself.

VerdictMeaning
completeNothing outside the imaged rows would change.
partialA direct child is affected by a cascade that undo does not restore. The same verdict covers a computed or server-maintained column (for example a SQL Server rowversion) and a direct cascade-update child.
blockedAn enabled trigger fires, a cascade reaches depth 2 or more, the catalogue could not be read, or undo: required cannot be satisfied on this licence. An INSERT with no column list, an INSERT with non-literal values, or an INSERT…SELECT is blocked too.

Triggers and cascades

policy.triggers and policy.cascades each take allow, warn, or reject. When the key is absent it follows undo: required becomes reject, and optional becomes warn. Under warn the correction still applies, and the finding is stored with the history row. cascades: reject lets a depth-1 cascade through on Enterprise, where that child is imaged, and refuses the same cascade on Pro.

policy.triggers and policy.cascades are applied by data apply (and --dry-run). data plan reports the impact and the undo verdict but does not reject on those two keys. Other policy violations are recorded on the correction and set the plan's success to false. max_affected_rows stays an apply-time check, because the row count is not known until the statement runs.

What Enterprise undo restores

Enterprise data undo restores the rows the correction changed, and the child rows a direct one-level cascade changed: a delete, or a foreign key set to null or to a default. It does not restore a cascade two or more levels deep, and it does not restore a child whose key changed because the parent key was updated. Those stay unsupported. With undo: required, an unrecoverable cascade is rejected unless policy.cascades is warn or allow.

Prefer a new compensating correction (fix-forward) over undo for anything beyond a recent, isolated mistake. Undo is best-effort and point-in-time: it is sensitive to later drift, capture size, and whether identifying keys were configured.

Author a correction

Files live under one of your data_sets.<name>.directories. The D token is the correction id — the handle data undo takes — and corrections run in ascending timestamp order.

D20260618143022__promote_account_tier.sql

sql
-- dblift:formatted -- dblift: expect=1 UPDATE "app"."accounts" SET tier = 'gold' WHERE id = 7; -- dblift: expect=>=1 onfail=warn UPDATE "app"."audit_log" SET note = 'tier change' WHERE account_id = 7;

-- dblift:formatted anywhere in the file turns on directive parsing. Without it the file still runs, but every expect is ignored and statements use the defaults (expect >=1).

DirectiveValuesMeaning
-- dblift:formattedheaderRequired. Activates directive parsing for the file.
-- dblift: expect=1 · >=1 · <=5 · anyRow-count assertion for the next statement.
onfail=halt · skip · warnWhat to do when the assertion is not met. Defaults to halt.
no-undobare flagExcludes the statement from before-image capture. It cannot be undone.

See data_sets for the set, its identifying keys, and the policy that data plan checks before apply.

See it in actionCompliance and the data ledger on dblift.com
On this page