Skip to content

Schema change: an existing integer column can become an identity column - #9

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

swsarancodes merged 2 commits into
mainfrom
feat/schema-change-identity

Conversation

@swsarancodes

Copy link
Copy Markdown
Owner

What changed

alter_column with autoIncrement: true on a column that is not an identity column used to be refused ("not compiled: its sequence has to start after the largest existing value"). It now compiles:

  • One step, one transaction (inside the lock retry): ALTER TABLE … ADD GENERATED BY DEFAULT AS IDENTITY, then the sequence is started at the largest value the column holds plus one, so a new row cannot be given a number that is already used. BY DEFAULT, so an insert that supplies its own value still works.
  • The largest value is read through an index on the column. On a table that is not small the step is refused without one ("add an index first"), because finding the maximum would otherwise read the whole table under the lock.
  • The column has to be an integer type and NOT NULL (the same change can make it NOT NULL), a generated column is refused, and a serial column's default has to be removed in the same change (defaultValue: null); the step list then drops the default first.
  • The identity's own sequence is the one that is started. pg_get_serial_sequence is ambiguous for a column that still owns the sequence of an old serial default and started the wrong sequence (found by the serial-to-identity scenario below), so the step looks up the sequence the column depends on internally.
  • On servers older than PostgreSQL 13, a column that owns an old serial sequence and also has an identity makes every INSERT fail ("more than one owned sequence found"). There the old sequence is detached from the column first (it stays, for anything that calls nextval on it by name), and the step says so.

How it was checked

  • 6 new unit tests; the repo suite passes (1,754 tests) and both type-checks are clean.
  • Two scenarios in scripts/schema-change-pg.ts: an integer primary key becomes an identity column, and a serial column becomes one. Each inserts a row afterwards and checks it is numbered after the largest value (and that an explicit id still works). Lock levels and the predicted end state are checked like every scenario. npm run test:schema-pg:versions passes on PostgreSQL 12, 13, 14, 15, 16, 17 and 18; the first run caught both the wrong-sequence bug and the PostgreSQL 12 failure above.
  • The check that a lock retry that gives up stops no longer depends on how fast the machine is (it failed under heavy load); it still checks that it stops.

Reviewer notes

  • Not included: enums, a safe column rename.

alter_column with autoIncrement on a column that is not one yet adds GENERATED BY DEFAULT AS IDENTITY and starts its sequence after the largest value the column holds, in one transaction (inside the lock retry), so a new row cannot be given a number that is already used. The largest value is read through an index; a table that is not small needs one first. The column has to be an integer type and NOT NULL, and a serial default has to be removed in the same change. The identity's own sequence is the one that is started, because pg_get_serial_sequence is ambiguous for a column that still owns an old serial sequence. On servers older than PostgreSQL 13, which cannot have both on one column, the old sequence is detached first.
… does not depend on the machine

Two scenarios: an integer primary key becomes an identity column, and a serial column becomes one (the default is dropped first). Each inserts a row afterwards and checks that it is numbered after the largest value. The check that a lock-retry that gives up stops now only requires that it stops, not how fast, which depended on the load of the machine running it.
@swsarancodes
swsarancodes merged commit cc3ee1b 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