Skip to content

Schema change: add_primary_key - #8

Merged
swsarancodes merged 2 commits into
mainfrom
feat/schema-change-primary-key
Oct 25, 2026
Merged

swsarancodes merged 2 commits into
mainfrom
feat/schema-change-primary-key

Conversation

@swsarancodes

Copy link
Copy Markdown
Owner

What changed

A new plan operation, add_primary_key (table, optional constraint name, columns), for a table that has no primary key (the health report already lists these).

  • The key columns are made NOT NULL first, through the validated CHECK path the compiler already uses (so ADD PRIMARY KEY does not have to read the table under its lock).
  • A unique index is built with CREATE UNIQUE INDEX CONCURRENTLY, then attached with ADD CONSTRAINT … PRIMARY KEY USING INDEX, which only changes the catalog. On a table the plan itself creates it is a plain ADD PRIMARY KEY.
  • Refused, with the reason: a table that already has a key, a partitioned table (needs the partition key and per-partition builds; not compiled), unknown or repeated columns, a constraint name that is taken.
  • Later steps in the same plan see the key (a foreign key to it compiles, backfills batch on it). The planner prompt, the plan normalizer, the structured-output schema and the plan list in the UI know the operation.

How it was checked

  • 5 new unit tests; the repo suite passes (1,748 tests) and both type-checks are clean. OpenAI accepts the schema (npm run check:openai).
  • A new scenario in scripts/schema-change-pg.ts (scenarios can now carry their own setup SQL) runs the whole runbook on a table without a key: the key exists on a NOT NULL column, its index is the primary key's, a duplicate is refused and the helper CHECK is gone. Like every scenario it also has the lock level of each step compared with pg_locks and the predicted end state compared with the catalog. npm run test:schema-pg:versions passes on PostgreSQL 12, 13, 14, 15, 16, 17 and 18.

Reviewer notes

  • Not included: dropping a primary key (drop_constraint refuses it), a key on a partitioned table, enums, identity on an existing column, a safe column rename.

For a table with no primary key: the columns are made NOT NULL first through the validated CHECK, a unique index is built without blocking writes, and it is attached with ADD CONSTRAINT ... PRIMARY KEY USING INDEX, which then only changes the catalog. A table the plan creates gets a plain ADD PRIMARY KEY. Refused: a table that already has a key, a partitioned table, unknown or repeated columns, a name that is taken. Later steps in the same plan, such as a foreign key to the new key, see it.
…set up their own tables

The scenario runs the whole runbook (NOT NULL path, unique index, USING INDEX), checks the key, the index, that a duplicate is refused and that the helper CHECK is gone; the lock levels and the predicted end state are checked like every other scenario. A scenario can now carry SQL that runs before it is compiled.
@swsarancodes
swsarancodes merged commit edffe11 into main Oct 25, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant