check on delete (expr) fires on INSERT instead of being scoped to DELETE
Affected versions: confirmed in 2.9.0 and 3.1.1 (latest as of 2026-05-22)
Summary
A CHECK constraint declared with the on delete modifier — e.g.,
constraint X check on delete (false) — is evaluated against INSERT
rows, not just DELETE rows as the syntax declares.
Reproduction
declare schema main
{
table T (
Id int,
primary key (),
constraint NoDeleteEver check on delete (false)
);
}
apply schema main;
insert into T (Id) values (1);
Expected: INSERT succeeds ((false) is scoped to DELETE ops only).
Actual: ConstraintError: CHECK constraint failed: NoDeleteEver.
Confirmation that the literal false is not special
constraint NoDeleteEither check on delete (1 = 0)
(semantically identical to (false)) reproduces the same failure on
INSERT, ruling out a special case in literal-false handling.
Root cause hypothesis
The parser correctly captures operations: ['delete'] in the AST
(dist/src/parser/parser.js:3261 / :3361). The schema manager converts
to bitmask RowOpFlag.DELETE = 4. The bug is downstream — INSERT-path
evaluation is not filtering by op mask. Likely candidates:
shouldCheckConstraint or its callers in the constraint-builder layer.
Test reference
stage-6-check-on-delete.spec.ts
check on delete (expr)fires on INSERT instead of being scoped to DELETEAffected versions: confirmed in 2.9.0 and 3.1.1 (latest as of 2026-05-22)
Summary
A CHECK constraint declared with the
on deletemodifier — e.g.,constraint X check on delete (false)— is evaluated against INSERTrows, not just DELETE rows as the syntax declares.
Reproduction
declare schema main { table T ( Id int, primary key (), constraint NoDeleteEver check on delete (false) ); } apply schema main; insert into T (Id) values (1);Expected: INSERT succeeds (
(false)is scoped to DELETE ops only).Actual:
ConstraintError: CHECK constraint failed: NoDeleteEver.Confirmation that the literal
falseis not special(semantically identical to
(false)) reproduces the same failure onINSERT, ruling out a special case in literal-
falsehandling.Root cause hypothesis
The parser correctly captures
operations: ['delete']in the AST(
dist/src/parser/parser.js:3261/:3361). The schema manager convertsto bitmask
RowOpFlag.DELETE = 4. The bug is downstream — INSERT-pathevaluation is not filtering by op mask. Likely candidates:
shouldCheckConstraintor its callers in the constraint-builder layer.Test reference
stage-6-check-on-delete.spec.ts