Repository navigation
Schema change: renaming a column keeps the old name working until the last step - #11
Merged
Merged
Conversation
… last step For an existing table, an alter_column that renames a column adds a column with the new name and the same type, keeps the two in step in both directions with a BEFORE INSERT OR UPDATE trigger (an insert that gives both keeps the new name's value, an update that changes both keeps the old name's), copies the data in batches, checks the copy, builds copies of the plain btree indexes concurrently and makes the new column NOT NULL if the old one is. Dropping the old column and the trigger, and giving the new column the old default, is a last step that is not run by default. A column in a key, a foreign key or a check, one a view uses, a generated or identity column, a partitioned table, and a rename combined with other changes are refused, with the way out: compatColumns (default on) turns it off and the column is renamed directly.
A scenario runs the rename on a table of a few hundred thousand rows (lock levels and the predicted end state are checked like every scenario), and a section exercises the trigger: existing rows copied, the index copied and valid, the new column NOT NULL, inserts and updates through either name reaching the other, the old default applied to both, an insert that gives both, then the last step removing the old column, the trigger and its function and leaving the new column with the old default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
A column rename used to break every query using the old name the moment it ran. For a column of an existing table it now adds a second column and keeps the two in step, so both names work until applications have switched:
ADD COLUMN <new> <same type>, then aBEFORE INSERT OR UPDATEtrigger that keeps the two in step in both directions (an insert that gives both keeps the new name's value; an update that changes both keeps the old name's; an insert that gives neither gets the old column's default in both), then the data copied in batches, a check that nothing differs, copies of the plain btree indexes builtCONCURRENTLY, and NOT NULL on the new column (through the validated CHECK) if the old one is.Refused, with the way out (
compatColumns, default on, turns the whole behaviour off and renames directly, where constraints and views follow the column): a column in a key, a unique constraint, a foreign key or a check; one a view, policy or trigger depends on; a generated or identity column; a partitioned table; and a rename combined with other changes in the same operation. Partial, expression and non-btree indexes are not copied (a warning says so). A table the plan itself creates is renamed directly. The notes say what is not copied (collation, storage settings, statistics targets, comments, column-level grants).How it was checked
scripts/schema-change-pg.tsruns the rename on a table of a few hundred thousand rows (lock levels of every step againstpg_locksand the predicted end state against the catalog, like every scenario), and a newcolumnrenamesection exercises the trigger on a real server: existing rows copied, the index copied and valid, the new column NOT NULL, inserts and updates through either name reaching the other, the old default applied to both, an insert that gives both, and then the last step removing the old column, the trigger and its function and leaving the new column with the old default.npm run test:schema-pg:versions(16 sections) passes on PostgreSQL 12, 13, 14, 15, 16, 17 and 18.Reviewer notes