MP043warning

ban-domain-constraint

Domain constraints validate against ALL rows in ALL columns using that domain.

operation
Constraints
lock taken
ACCESS EXCLUSIVE
remediation
Manual rewrite
category
Constraints & keys

What triggers it

Two branches. First, a CreateDomainStmt whose constraints[].Constraint.contype is CONSTR_CHECK. Second, an AlterDomainStmt whose subtype is the single character C, which is what the grammar uses for ADD CONSTRAINT — AlterDomainStmt.subtype is a bare char, not one of the AT_* enum names AlterTableCmd carries, and libpg-query passes it through as written. Both branches were probed against the bundled parser. Adding a NOT NULL constraint to a domain goes through the same C path and is flagged too, since it validates existing values the same way.

What does not

ALTER DOMAIN ... ADD CONSTRAINT ... NOT VALID, which arrives as C with skip_validation: true — nothing is scanned until a later VALIDATE CONSTRAINT, and that form is what this rule recommends. CREATE DOMAIN carrying only NOT NULL (CONSTR_NOTNULL) or only a DEFAULT, since neither is CONSTR_CHECK. ALTER DOMAIN ... SET DEFAULT / DROP DEFAULT (subtype "T"), DROP CONSTRAINT (X), and SET NOT NULL / DROP NOT NULL (O / N) — none of them validate existing values. Ordinary table-level CHECK constraints, which are MP030.

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

ALTER DOMAIN ... ADD CONSTRAINT takes ACCESS EXCLUSIVE on every table that has a column of that domain and validates the predicate against all of them, so a domain used by twenty tables produces twenty simultaneous blocking full scans. CREATE DOMAIN is the cheaper half of the rule: it touches no table and takes no table lock, and is flagged because it fixes the constraint every future column of that domain inherits, and because changing it later is the expensive statement above.

Why it matters

Adding or modifying a domain constraint triggers validation against every row in every table that uses the domain type. This can be extremely slow and lock-intensive.

Unsafe, and safe

Flagged

CREATE DOMAIN positive_int AS INTEGER CHECK (VALUE > 0);
ALTER DOMAIN positive_int ADD CONSTRAINT min_val CHECK (VALUE >= 1);

Safe alternative

-- Use column-level CHECK constraints instead
ALTER TABLE orders ADD CONSTRAINT chk_qty CHECK (quantity > 0) NOT VALID;

What it assumes

With no catalog access the rule cannot know how many columns use the domain, so it cannot distinguish a brand-new domain nothing references from one wired through half the schema — and it warns identically either way. Nothing in the code reads ctx.cluster, ctx.tableStats, or any other production field, so --database-url does not change its behaviour. Earlier releases read subtype "T" as ADD CONSTRAINT, which inverted the rule into a false positive on default changes and a false negative on the scan it is named for; the trigger set above is the corrected one.

What the CLI prints

migrationpilot analyze migration.sql
⚠ [MP043] WARNING (line 1)
  CREATE DOMAIN "positive_int" with CHECK constraint. Future columns using this domain will be validated against this constraint, and modifying it later requires scanning all tables using the domain.

  Safe alternative:
  -- Consider using CHECK constraints on individual columns instead of domain constraints.
  -- This gives you more control over validation and avoids cross-table scans when modifying constraints.

  Why: Domain constraints are validated against every column in every table that uses the domain type. Adding a CHECK constraint to a domain can trigger full table scans across many tables simultaneously. Use CHECK constraints on individual columns instead.
  Docs: https://migrationpilot.dev/rules/mp043

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 MP043
CREATE DOMAIN positive_int AS INTEGER CHECK (VALUE > 0);

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

.migrationpilotrc.yml
rules:
  MP043: false

# or keep it, and downgrade it
rules:
  MP043:
    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 MP043 in the playground

Related rules