Version
DataProviderMigrate 0.9.12-beta (current latest release).
Summary
Adding a column that declares checkConstraint to an existing table plans two
operations that both create the same constraint. The second one fails and the whole
migration rolls back, so the change can never be applied.
Phase: all — applying 4 of 4 operation(s):
AddColumnOperation
AddCheckConstraintOperation
CreateRlsPolicyOperation
CreateRlsPolicyOperation
Error: migration apply failed: 42710: constraint "tool_calls_execution_target_chk"
for relation "tool_calls" already exists
AddColumnOperation already emits the CHECK inline as part of the ADD COLUMN, and
the diff engine independently notices the constraint is absent and schedules
AddCheckConstraintOperation for it too.
Proof that AddColumn is the one creating it
After the failed run, the database has neither the column nor the constraint —
the apply is transactional and rolled back cleanly:
execution_target column : None | nullable: None | default: None
constraints : []
So the constraint did not pre-exist. It existed by the time operation 2 ran, and the
only thing between those points is operation 1.
Schema fragment
- name: execution_target
type: Text
isNullable: false
defaultValue: "'client'::text"
checkConstraint: "execution_target IN ('client', 'workspace')"
Why this is easy to miss
It only occurs on the ALTER path. On a fresh database the table is created with the
column inline, so AddColumnOperation never runs and no duplicate is planned. Our
test suite migrates a throwaway Postgres from scratch on every run and is completely
green; only the migration against the long-lived database fails. Any project whose
tests build the schema fresh will pass CI and then fail in production.
Expected
Adding a column with checkConstraint to an existing table applies once. Either
AddColumnOperation emits the bare column and leaves the CHECK to
AddCheckConstraintOperation, or the diff engine does not schedule a constraint that
the column operation already carries.
Impact
There is no way to express this without working around the bug — checkConstraint on
a column is the only form the schema language offers (we use it 19 times), and
--phase structural still contains both operations. The migration is simply
unappliable, which blocks the deploy that depends on it.
Version
DataProviderMigrate0.9.12-beta (current latest release).Summary
Adding a column that declares
checkConstraintto an existing table plans twooperations that both create the same constraint. The second one fails and the whole
migration rolls back, so the change can never be applied.
AddColumnOperationalready emits the CHECK inline as part of theADD COLUMN, andthe diff engine independently notices the constraint is absent and schedules
AddCheckConstraintOperationfor it too.Proof that AddColumn is the one creating it
After the failed run, the database has neither the column nor the constraint —
the apply is transactional and rolled back cleanly:
So the constraint did not pre-exist. It existed by the time operation 2 ran, and the
only thing between those points is operation 1.
Schema fragment
Why this is easy to miss
It only occurs on the ALTER path. On a fresh database the table is created with the
column inline, so
AddColumnOperationnever runs and no duplicate is planned. Ourtest suite migrates a throwaway Postgres from scratch on every run and is completely
green; only the migration against the long-lived database fails. Any project whose
tests build the schema fresh will pass CI and then fail in production.
Expected
Adding a column with
checkConstraintto an existing table applies once. EitherAddColumnOperationemits the bare column and leaves the CHECK toAddCheckConstraintOperation, or the diff engine does not schedule a constraint thatthe column operation already carries.
Impact
There is no way to express this without working around the bug —
checkConstraintona column is the only form the schema language offers (we use it 19 times), and
--phase structuralstill contains both operations. The migration is simplyunappliable, which blocks the deploy that depends on it.