Skip to content

Schema change: renaming a table keeps the old name working as a view - #6

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

swsarancodes merged 2 commits into
mainfrom
feat/schema-change-safe-rename

Conversation

@swsarancodes

Copy link
Copy Markdown
Owner

What changed

A table rename used to break every query using the old name the moment it ran (the step was flagged "breaks callers"). For an existing table it now takes one step that renames the table and creates a view under the old name in the same transaction, so applications keep working until they have switched. A second step, not run by default, drops the old name later; the undo drops the view and renames the table back. compatViews (default on) turns it off; a table the plan itself creates is renamed directly.

Things a compatibility view could get wrong, and how each is handled:

  • Permissions. A new view has no grants, so applications would lose access. The table's SELECT, INSERT, UPDATE and DELETE grants are copied onto the view as plain, readable GRANT statements (the owner's own are skipped). Grants a view cannot hold (TRUNCATE and so on) and column-level grants are not copied, and the step's notes say so.
  • Row-level security. Before PostgreSQL 15 a view over such a table runs as its owner, who is not held to the policies, so a view would let applications around them. The rename is refused there, with the reason. From 15 the view is created WITH (security_invoker = true), so the caller's own rights and policies apply as before.
  • Later steps in the same plan. The compiler records that the view now depends on every column, so a later drop or type change of a listed column is refused until the view is gone, and it refuses to create anything else under the old name.
  • What the view can and cannot do is in the step's notes: SELECT, INSERT, UPDATE and DELETE work and the table's defaults still apply; ALTER TABLE, TRUNCATE, COPY and LOCK do not, and columns added later are not in the view.

The catalog now reads each table's privileges and whether row-level security is on. The expected state compared after a run includes the views each table should have.

How it was checked

  • 7 new unit tests (rename.test.ts); the repo suite passes (1,743 tests) and both type-checks are clean.
  • Against real PostgreSQL 12, 13, 14, 15, 16, 17 and 18, a new rename section in scripts/schema-change-pg16.ts runs the rename with real roles: a role with the old grants still reads and writes through the old name (sequence and constant defaults applied), a read-only role still cannot write, a foreign key from another table follows the renamed table, the catalog matches the compiler's expected state, later changes to a listed column are refused, the contract step removes the old name, and the undo puts the table back. From 15 a caller still sees only the rows its policy allows through the old name; before 15 the row-level-security case is refused. All 15 sections of that script pass on every version.

Reviewer notes

  • Not done: a safe column rename. A view cannot help there (applications read the columns directly); it needs a new column, a sync trigger, a backfill and a later drop. The existing column rename is unchanged and still flagged as breaking callers.
  • scripts/schema-change-pg16.ts and scripts/lib/ are still not in the repository, so the scenarios above ran from a local copy.

…ach table

After a run the catalog is compared with the views each table should have, so a compatibility view left by a rename is expected and a view nobody planned is reported.
For an existing table, one step renames it and creates a view under the old name in the same transaction, with the table's SELECT, INSERT, UPDATE and DELETE grants copied onto the view. From PostgreSQL 15 the view runs with the caller's own rights (security_invoker); before that a table with row-level security is refused, because the view would run as its owner. An optional contract step drops the view later, and the undo puts the table's name back. Later steps in the plan know the view depends on the columns. The catalog now reads each table's privileges and whether row-level security is on. compatViews (default on) turns it off.
@swsarancodes
swsarancodes merged commit c45c13e 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