Catalog modification checklist
Background: knowledge/idioms/catalog-conventions.md. This skill is the
hands-on procedure; consult it before/after any change to
src/include/catalog/*.h or *.dat.
Before you start
Decide which catalog(s) you touch. Common patterns:
- New builtin function →
pg_proc.dat(+ C function in some backend file). - New operator →
pg_operator.dat+pg_proc.dat. - New cast →
pg_cast.dat+pg_proc.dat. - New type →
pg_type.dat+ I/O funcs inpg_proc.dat. - New column on existing catalog → edit
pg_X.h, decide BKI_DEFAULT, possibly update every existing row inpg_X.dat. - New catalog table entirely → new
pg_X.h+pg_X.dat+ entries insrc/include/catalog/Makefile/meson.build+headerslist insrc/backend/catalog/Makefile.
- New builtin function →
Pick OIDs. From
src/include/catalog/:./unused_oidsPick a random starting OID in 8000-9999 and a contiguous block big enough for your patch. The 8000-9999 range is reserved by project convention for in-progress patches and forks (see
src/include/access/transam.hcomments aroundFirstGenbkiObjectId); keeping new work in that range minimises collisions with concurrent patches. 10000-11999 is reserved for genbki.pl auto-assignment, and the committer renumbers your patch down to a tidy low-OID range viarenumber_oids.plat commit time — don't do that yourself in in-flight work.
Making the edit
Edit the header (
pg_X.h) if the schema changes.- Wrap varlena / nullable trailing columns in
#ifdef CATALOG_VARLEN. - Annotate OID-referencing columns with
BKI_LOOKUP(target_catalog)(orBKI_LOOKUP_OPTif zero is allowed). - Provide
BKI_DEFAULT(val)for any column the.datfiles may omit. - Public constants (relkinds, prokinds, …) belong inside
#ifdef EXPOSE_TO_CLIENT_CODEso frontend code can read them via the generatedpg_X_d.h. - When adding a new fixed-length column to an existing catalog, append
it at the end of the fixed-length section (before any
CATALOG_VARLENblock). This minimises ABI churn for code that readsForm_pg_X->existing_field— offsets of pre-existing fields don't shift.
- Wrap varlena / nullable trailing columns in
Edit the data file (
pg_X.dat).- Group new entries near related existing ones (not at the end).
- Always include a
descr(becomes thepg_descriptionrow). - Use symbolic names for OID references (
prorettype => 'int4'), not numeric OIDs.BKI_LOOKUPresolves them. - Don't write columns that have a
BKI_DEFAULTmatching your value. - Don't write computed columns like
pronargs.
For a typical immutable strict
int4 -> int4function the.datrow is just:{ oid => '8473', descr => 'frobnitz of an int', proname => 'frobnitz', prorettype => 'int4', proargtypes => 'int4', prosrc => 'my_new_func' },Don't write:
pronargs(computed byAddDefaultValues),provolatile(defaulti= immutable),proisstrict(defaultt),proparallel(defaults= safe),prokind(defaultf), and anything else matchingBKI_DEFAULTinpg_proc.h. Only write columns where you DIVERGE from the default (e.g. a stable function needsprovolatile => 's').Write/wire the C implementation.
- For a new function:
PG_FUNCTION_INFO_V1(name); Datum name(PG_FUNCTION_ARGS) { ... }in an appropriatesrc/backend/.../*.c. The C symbol must matchprosrcin the dat entry.
- For a new function:
Cache & index plumbing (only when needed)
- Adding a new lookup pattern? Add a
DECLARE_UNIQUE_INDEXandMAKE_SYSCACHE(NAME, idx, nbuckets)to the header. Use the syscache from C withSearchSysCacheN+ReleaseSysCache.
Using a syscache from C
- Every
SearchSysCache*(non-Copy) call that returns a valid tuple MUST be paired withReleaseSysCache(tup)before return. Unreleased pins log "cache reference leak" at transaction end. - Pointers into the tuple (e.g.
GETSTRUCT(tup),SysCacheGetAttrresults that point into the tuple) are only valid betweenSearchSysCache*andReleaseSysCache. If you need them longer, either copy them out (pstrdup,datumCopy) or useSearchSysCacheCopy1which returns a palloc'd copy; free it withheap_freetupleinstead ofReleaseSysCache. - A miss returns an invalid HeapTuple (
HeapTupleIsValid(tup) == false, i.e. NULL). This is NOT an error from the cache — the caller decides what to do. Idioms:- "Should never happen":
elog(ERROR, "cache lookup failed for function %u", oid) - User-triggerable:
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg(...)))
- "Should never happen":
- Pure existence check:
SearchSysCacheExists1(no release needed). - OID-only fetch:
GetSysCacheOid1. - Cross-backend coherence is automatic via shared invalidation messages — you don't need to invalidate manually after a catalog update done through the normal heap_update path.
- Adding a TOAST-eligible catalog?
DECLARE_TOAST(name, toastoid, indexoid)— pin both OIDs.
Mandatory verifications (run all of these)
./duplicate_oids— exit code 0, no output. Run fromsrc/include/catalog/.cd src/include/catalog && ./duplicate_oidsBump
CATALOG_VERSION_NOinsrc/include/catalog/catversion.h. FormatYYYYMMDDN— today's date with N=1 (or higher if multiple bumps land same day). This is mandatory if you:- Added / removed / renamed any catalog column.
- Added / removed / changed any
.datrow. - Added / removed / renamed any system function or operator.
- Changed
primnodes.h/parsenodes.h(stored parsetrees). - Did anything else that would break a running cluster reading data written by the prior binary.
If you're unsure: bump it. The cost is zero; missing it produces confusing "cluster won't start" reports.
Rebuild fully.
genbki.plruns at build time and regeneratespostgres.bki,pg_X_d.h,syscache_ids.h,syscache_info.h,system_fk_info.h. Forgetting a clean rebuild leaves stale headers. With meson:ninja -C buildLook for
Generating src/backend/catalog/postgres.bkiin the log.Re-initdb. Old data directories won't open after a catversion bump. From the build dir:
rm -rf data && ./tmp_install/.../initdb -D dataOr use the
build-and-runskill.Run catalog-touching regression tests:
meson test -C build --suite regressAdd a new test exercising the new function/operator/column.
make checkworks too.If applicable, update:
src/test/regress/expected/*.out— opr_sanity, type_sanity, psql_crosstab outputs often shift when you add catalog rows.doc/src/sgml/func.sgml(or equivalent) — user-facing docs for new functions/operators.src/bin/psql/tab-complete.in.c— completion for new SQL keywords.src/test/modules/test_oat_hooksetc. if you touched ACL columns.
Pre-commit gate (don't skip)
-
./duplicate_oidsclean -
CATALOG_VERSION_NObumped - Full clean rebuild succeeds
-
meson test -C build(ormake check-world) green -
git grepshows no stale references to renamed columns / removed OIDs - Docs updated for any user-visible addition
Common failure modes (in order of frequency)
- Forgot to bump catversion → reviewer flags it, or worse, lands and then breaks every developer's local cluster on next pull.
- OID collision with a concurrent patch →
duplicate_oidscatches it; pick a new OID and rerun. - Varlena column outside
CATALOG_VARLEN→ silent garbage reads viaForm_pg_X->col. Always wrap. BKI_LOOKUPtarget name not present in the referenced.dat→ genbki.pl errors clearly; check spelling and that the row exists.- New
.datrow passes build but breaksopr_sanity/type_sanityregression checks → those tests exist precisely to enforce catalog invariants; read the failure carefully, it will tell you which invariant. - Forgot to add
ReleaseSysCacheafter a successfulSearchSysCache*in new C code → "cache reference leak" warnings at txn end.
Reference files (in source/)
src/include/catalog/README— pointer to docssrc/include/catalog/genbki.h— macro referencesrc/include/catalog/catversion.h— bump targetsrc/include/catalog/duplicate_oids— uniqueness check scriptsrc/include/catalog/unused_oids— OID pickersrc/include/catalog/renumber_oids.pl— committer-side cleanupsrc/backend/catalog/genbki.pl+Catalog.pm— the generatorsrc/include/access/transam.h:195-197—FirstGenbkiObjectId/FirstUnpinnedObjectId/FirstNormalObjectIdsrc/include/utils/syscache.h+src/backend/utils/cache/syscache.c— syscache API and registry
Upstream docs
- https://www.postgresql.org/docs/current/bki.html
- https://www.postgresql.org/docs/current/system-catalog-declarations.html
- https://www.postgresql.org/docs/current/catalogs.html
Cross-references
.claude/skills/fmgr-and-spi/SKILL.md—pg_proc.datrows for SQL-callable C functions (provolatile / proisstrict / proparallel)..claude/skills/access-method-apis/SKILL.md—pg_am.dat, opclass / strategy / support-function registration..claude/skills/parser-and-nodes/SKILL.md— catversion bump rules when serializedQueryfields change (views / rules)..claude/skills/extension-development/SKILL.md— extensions shipping their ownpg_proc/pg_typeentries via SQL install scripts (not.dat)..claude/skills/testing/SKILL.md— regress test for new catalog entries (OID-portable output, no plain\din expected files)..claude/skills/commit-message-style/SKILL.md— committer convention: catversion bump lives in the same commit as the catalog change.knowledge/idioms/catalog-conventions.md— long-form discussion.