data_sets

A data correction is a SQL file that changes rows rather than structure. Each statement declares how many rows it should touch, and the correction is refused if reality disagrees. This page covers the file, its directives, and the policy that governs a set of them.

A data correction is a SQL file that changes rows rather than structure. Each statement declares how many rows it should touch, and the correction is refused if reality disagrees. This page covers the file, its directives, and the policy that governs a set of them.

A correction file

Every directive appears here at least once. Read the comments as instructions to DBLift, not notes to the next person.

corrections/2026-08-11__restore_lapsed_trials.sql

-- dblift:formatted
-- Ticket OPS-4417. Trials cancelled by the 08-09 billing retry.

-- dblift: expect=138
UPDATE subscriptions
   SET status = 'trialing',
       cancelled_at = NULL
 WHERE status = 'cancelled'
   AND cancellation_reason = 'billing_retry_exhausted'
   AND cancelled_at >= '2026-08-09';

-- dblift: expect=>=1 onfail=warn
UPDATE billing_attempts
   SET retry_state = 'pending'
 WHERE subscription_id IN (
         SELECT id FROM subscriptions WHERE status = 'trialing'
       );

-- dblift: expect=any onfail=skip
DELETE FROM dunning_queue
 WHERE reason = 'billing_retry_exhausted';

-- dblift: no-undo
INSERT INTO ops_audit (ticket, note, recorded_at)
VALUES ('OPS-4417', 'Restored trials cancelled in error', CURRENT_TIMESTAMP);

The directives

DirectiveGoesWhat it does
-- dblift:formattedFile headerActivates directive parsing for the file. Without it, no directive in the file is read — the correction still runs, just with defaults everywhere.
expect=1Before a statementThe exact row count the statement must affect. Also accepts >=1, <=5 and any. When the count does not match, onfail decides what happens.
onfail=haltBefore a statementThe default. An unmet expect aborts the correction and rolls the whole thing back.
onfail=warnBefore a statementRecords a warning and carries on. The statement stays applied.
onfail=skipBefore a statementRecords a skipped note and carries on. Because the correction runs in one transaction, the statement has already executed — skip does not undo it.
no-undoBefore a statementMarks the statement as not reversible. No inverse DML is generated for it, so data undo will not restore what it changed.

[!WARNING] The header is not optional decoration

A file without -- dblift:formatted is never directive-parsed. Every expect in it is ignored silently and the statements run unguarded. The header can appear anywhere in the file. If a correction matters enough to assert row counts, check the header is present.

Configuring a set

Corrections are grouped into named sets. A set says where its files live, which columns identify a row, and what it will not allow.

data_sets:
  billing:
    directories:
      - "./corrections/billing"
    keys:
      subscriptions: [id]
      billing_attempts: [id]
    policy:
      require_expect: true
      allow_full_table: false
      max_affected_rows: 5000

environments:
  prod:
    data_sets:
      billing:
        policy:
          undo: required
          max_affected_rows: 500
          denied_tables: [ledger_entries]

The environment block overlays the root set, so production can be stricter than staging without a second set of files. Here production halves the row ceiling, insists every correction be reversible, and puts the ledger out of reach entirely.

Set keys

KeyDefaultWhat it does
directories[]Where this set's correction files live. Scanned recursively unless the entry says otherwise.
keys{}Per-table identifying columns. Used to build the inverse statement that undo replays.
apply_modererunStored on the set; not consumed at runtime today.
authoringimperativeStored on the set; not consumed at runtime today.
history_tabledblift_data_history_{name}Audit ledger for this set. Empty still resolves to dblift_data_history_<set>.

Policy keys

The policy is enforced when the plan is applied. A correction that breaks it is refused at apply, not at plan construction.

KeyDefaultWhat it does
enabledtrueTurns the data set off without deleting its files.
require_expectfalseEvery statement must carry an expect directive, or the plan is refused.
allow_uncheckedtrueSet to false to reject statements whose row count nobody asserted.
undooptionalSet to required and a correction without a reversible path will not be planned.
allow_full_tabletrueSet to false to reject any statement with no WHERE clause.
max_affected_rows—Ceiling on rows a single correction may touch. Nothing above it is planned.
allowed_tables—Allowlist. When set, only these tables may be corrected.
denied_tables—Denylist, applied after the allowlist.
max_statement_bytes2000000Size ceiling on a single statement.
restore_key_columnsid, pk, uuid, keyColumns used to identify a row when building the inverse statement for undo.

Not available on document stores

Corrections are SQL from end to end — the file is split into statements, each gated by a row count, inverted to build the undo, and applied in one transaction. A document store offers none of those, so DBLift refuses rather than half-emulating them. On Azure Cosmos DB or MongoDB, express the change as a Python migration against the provider SDK instead.

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