The Keycloak Upgrade Ledger

Every upgrade we have rehearsed, with the environment stated and the clock running.

2026-09-11 · run 2026-09-11-postgres-schema-rename-semantics

what `ALTER SCHEMA … RENAME TO` carries, and the one thing it drops

Scale
one schema, one table, one row, one view, two functions, one role
Database
PostgreSQL 14.10 (postgres:14.10-alpine), single node, container
Topology
single container, --rm

Environment

FieldValue
Keycloak from → ton/a — no Keycloak in this run
Distribution / start moden/a
DatabasePostgreSQL 14.10 (postgres:14.10-alpine), single node, container
Why 14BEGIN ATOMIC function bodies arrived in 14, and the contrast with string-constant bodies is the whole test. 14 is the floor, not a preference
Postgres tuningstock image defaults, untouched
Dataset scaleone schema, one table, one row, one view, two functions, one role
Seeding methoddirect SQL (psql -f)
Topologysingle container, --rm
Host classcarbon (Ben's workstation)
JVM heapn/a
Wall-clock elapsedseconds; duration is not what this run measures

Method

Build a schema old holding a table, a view over it, and two functions that differ only in how their bodies are stored — one LANGUAGE sql AS $$ … $$ (string constant), one LANGUAGE sql BEGIN ATOMIC … END (SQL-standard body). Grant a role USAGE on the schema and SELECT on the table, and set an ALTER DEFAULT PRIVILEGES entry. Then ALTER SCHEMA old RENAME TO newsch and read back what each object says about itself.

Results

ObjectAfter the rename
View definitionfollowspg_get_viewdef reads FROM newsch.t
BEGIN ATOMIC function bodyfollows — rewritten to newsch.t, still returns 1
String-constant function bodydoes not follow — still reads old.t
Calling the string-constant functionERROR: relation "old.t" does not exist
Role's schema USAGEsurviveshas_schema_privilege true
Role's table SELECTsurviveshas_table_privilege true
ALTER DEFAULT PRIVILEGES entrysurvives — the pg_default_acl row now points at newsch

The mechanism, in one sentence from the docs

CREATE FUNCTION says the standard body is "parsed at function definition time, the string constant form is parsed at execution time." Everything stored as a parse tree references the schema by OID and does not care what it is called. Everything stored as text is text. Privileges behave like the first group: ACLs hang off the object, not off the name.

Outcome

Verification

ClaimPrimary sourceChecked
Views follow a schema renamepg_get_viewdef after rename → newsch.t
BEGIN ATOMIC bodies follow itpg_get_functiondef after rename → newsch.t; call returns 1
String-constant bodies do notpg_get_functiondef still old.t; call errors
Grants survivehas_schema_privilege / has_table_privilege both true
Default privileges survivepg_default_acl.defaclnamespace = newsch
pg_restore has no REMAP_SCHEMApostgresql.org/docs/17/app-pgrestore.html — -n filters only
The parse-time difference is the causepostgresql.org/docs/17/sql-createfunction.html
A search_path on the role masking the broken functionnot tested — and this is the one that matters, because it would turn the loud error into a quiet wrong answer
plpgsql bodiesnot tested; they are string constants too and should behave identically
Anything above 14.10not tested. The docs sentence is unchanged through 17, but this run is 14.10 only

← Back to the Ledger