MP020warningauto-fix

require-statement-timeout

Long-running DDL without a preceding SET statement_timeout.

operation
Session settings
lock taken
ACCESS EXCLUSIVE
remediation
Fixed by --fix
category
Lock safety

What triggers it

Fires when isLongRunningCandidate matches: VACUUM (FULL), any ClusterStmt, any ReindexStmt, a non-concurrent CREATE INDEX, or the AT_ValidateConstraint, AT_SetNotNull, or AT_AlterColumnType subcommand of ALTER TABLE — and no earlier statement in the file already sets statement_timeout.

What does not

Statements not classified as long-running (for example CREATE INDEX CONCURRENTLY, a plain ADD COLUMN) never reach the check, and the rule returns as soon as it finds statement_timeout — as a VariableSetStmt or just the raw substring — in any earlier statement in the file, even one unrelated to this operation.

Where it applies

Applies to every PostgreSQL version MigrationPilot targets. It works on the SQL text alone — no database connection needed.

The lock, and what it blocks

Lock type depends on the underlying statement: ACCESS EXCLUSIVE for CLUSTER, VACUUM FULL, non-concurrent CREATE INDEX, and column type changes or SET NOT NULL; SHARE or ACCESS EXCLUSIVE for REINDEX. Without a timeout, a stalled operation holds that lock indefinitely instead of failing fast, queuing everything behind it.

Why it matters

Without statement_timeout, a DDL operation that encounters unexpected conditions (bloated table, heavy WAL, slow I/O) can hold locks for hours, turning a routine migration into a full outage.

Unsafe, and safe

Flagged

ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_user;
-- Full table scan with no bound on how long it may run

Safe alternative

SET statement_timeout = '30s';
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_user;
RESET statement_timeout;

What it assumes

Detection is purely textual: it looks for the string statement_timeout anywhere in an earlier statement in the file, so it cannot confirm the timeout actually applies before this statement runs or is set to a sane value; an unrelated SET statement_timeout earlier in the file is enough to silence the warning.

What backs this rule

Every rule is a claim about PostgreSQL, so it carries what the claim rests on: a handbook chapter that cites the manual, the incidents that put it there, and the version it was last checked against.

What the CLI prints

migrationpilot analyze migration.sql
⚠ [MP020] WARNING (line 1)
  Long-running DDL without a preceding SET statement_timeout. This operation could hold locks for an extended time if it runs longer than expected.

  Safe alternative:
  -- Set a timeout so the operation is killed if it runs too long
  SET statement_timeout = '30s';
  ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_user
  RESET statement_timeout;

  Why: Without statement_timeout, a DDL operation that encounters unexpected conditions (bloated table, heavy WAL, slow I/O) can hold locks for hours, turning a routine migration into a full outage.
  Docs: https://migrationpilot.dev/rules/mp020

Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here.

Turning it off

For one statement, put a comment on the line before it:

-- migrationpilot-disable MP020
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_user;

For the whole project, in .migrationpilotrc.yml — by name or by id:

.migrationpilotrc.yml
rules:
  MP020: false

# or keep it, and downgrade it
rules:
  MP020:
    severity: warning

Try it

Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.

Run MP020 in the playground

Related rules