Catalog conventions
Skill matejformanek/postgres-claude/.claude/skills/catalog-conventions
Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.
npx -y skills add matejformanek/postgres-claude --skill catalog-conventionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Add or modify a PostgreSQL system-catalog entry — covers adding a pg_proc.dat builtin function, pg_operator.dat operator, pg_type.dat type, pg_cast.dat cast, pg_opclass.dat opclass, adding a new column on pg_class / pg_aggregate / pg_attribute / etc., BKI bootstrap entries, OID assignment policy (genbki.pl, unused_oids), catversion (CATALOG_VERSION_NO) bumping, and regenerating postgres.bki. Use whenever a PG patch edits anything under src/include/catalog/ (.h or .dat), adds a SQL-visible builtin (function/operator/type/cast/opclass), assigns or recycles an OID, or bumps the catversion. Skip for user-level information_schema queries on a running server, Django / Alembic / Rails / Flyway / Liquibase migrations, Oracle DBA_* / MySQL information_schema / Snowflake INFORMATION_SCHEMA catalog questions, schema design and normalization advice, ER-diagram tooling, and adding constraints to user-application tables.
SKILL.md
11.2 KB, as published. Nobody here has run it
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_oids -
Bump
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.