postgres/postgres
Patterns and conventions
PostgreSQL's C is idiosyncratic. The codebase has settled on a particular style and a small set of cross-cutting patterns that newcomers must absorb before touching the backend. This page is a tour of the load-bearing ones.
Coding style
- Tabs, 4-column. Configure your editor accordingly. The
.editorconfigfile in the repo root encodes this. - Brace placement: Allman/BSD for functions, K&R for control blocks.
pgindentenforces both. - Naming:
- Functions and variables:
snake_case_lowercase. - Types and Node tags:
CamelCase. - Macros:
UPPER_SNAKE. - File-static helpers commonly start with a lowercase letter even when they correspond to a CamelCase type.
- Functions and variables:
- Headers: every
.cfile starts with the canonical PostgreSQL block comment: a brief description, the dual copyright lines, and anIDENTIFICATION:line giving the file's path. New files must follow this template. - Comments: function-level doc-comments above non-trivial functions are expected. Use plain
/* ... */, not//.
Run src/tools/pgindent/pgindent on changed files before posting a patch. The formatter reads src/tools/pgindent/typedefs.list for project-defined typedef names.
Error handling: ereport and elog
PostgreSQL has its own error-reporting machinery. Source: src/backend/utils/error/elog.c. Every error that should be reachable by users goes through ereport:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for parameter \"%s\": \"%s\"", name, value)));- Severity levels:
DEBUG5..DEBUG1,LOG,INFO,NOTICE,WARNING,ERROR,FATAL,PANIC. ERRORaborts the current transaction via alongjmpto the nearest exception block. The transaction's memory context is freed.FATALaborts the whole backend. The postmaster will log it and the connection drops.PANICaborts and tells the postmaster to restart everything (typically used for unrecoverable shared-memory corruption).elog(LEVEL, "fmt", ...)is the simpler form; use it for messages that are not user-facing or do not need an error code.
Do not use printf, fprintf(stderr, ...), or puts from backend code. They bypass the logging machinery and mess up the protocol with the client.
Memory: contexts and palloc
PostgreSQL never calls malloc directly from the backend. Instead, it allocates from a tree of MemoryContext objects. Source: src/backend/utils/mmgr/.
char *buf = palloc(1024); /* allocates in CurrentMemoryContext */
char *zbuf = palloc0(1024); /* same, zeroed */
char *bigger = repalloc(buf, 4096);
pfree(buf); /* optional; the context's reset frees everything */Each query, transaction, and portal has its own context. The fundamental rule:
When the context is reset or deleted, every allocation in it goes away.
This means most "memory leaks" inside a query are harmless — they go away at end of statement. But long-running operations should explicitly MemoryContextSwitchTo(...) into a context with the right lifetime. MemoryContextReset and MemoryContextDelete are the workhorses.
Common contexts you will encounter:
| Context | Lifetime |
|---|---|
TopMemoryContext |
Backend lifetime. |
MessageContext |
One protocol message. |
CacheMemoryContext |
The relation cache and other long-lived caches. |
CurTransactionContext |
Current transaction. |
PortalContext |
Current portal/cursor. |
ExecutorState |
Executor for one query. |
ExprContext->ecxt_per_tuple_memory |
Reset between input tuples in a plan node. Crucial for SRFs and expression eval. |
When writing a function-returning-set, allocate the per-row results in per_query_memory (the long context) and ephemeral computation in per_tuple_memory so that each tuple's scratch space gets reset.
Resource owners
Memory is just one resource the backend tracks. Buffer pins, file descriptors, dynamic shared memory segments, and snapshots are all tracked through resource owners (src/backend/utils/resowner/). When a transaction aborts, its resource owner releases everything still held. The pattern:
ResourceOwner oldowner = CurrentResourceOwner;
ResourceOwner my = ResourceOwnerCreate(oldowner, "my work");
PG_TRY();
{
CurrentResourceOwner = my;
/* do work; pin buffers, open files */
}
PG_FINALLY();
{
CurrentResourceOwner = oldowner;
ResourceOwnerRelease(my, RESOURCE_RELEASE_BEFORE_LOCKS, true, false);
/* etc */
ResourceOwnerDelete(my);
}
PG_END_TRY();Most backend code does not write ResourceOwnerCreate directly — it inherits the surrounding owner. But understand that if you LockBuffer, ReadBuffer, or RegisterSnapshot, something needs to release it on cleanup.
Locking patterns
There are two kinds of locks:
- Lightweight locks (LWLocks). Shared-memory latches, taken with
LWLockAcquire/LWLockRelease. No deadlock detection; cycles are bugs. Used to protect data structures (e.g.,BufFreelistLock,ProcArrayLock). - Heavyweight locks. Hash-table-backed; deadlock detection runs after a timeout.
LockRelation,LockTuple,LockAcquire. Used at the SQL level for relations, tuples, advisory locks.
A common pattern when modifying a relation:
Relation rel = table_open(relid, RowExclusiveLock);
/* ... */
table_close(rel, NoLock); /* keep the lock until end of transaction */table_open takes a lock; table_close with NoLock releases the relcache pin but keeps the heavy lock. When the transaction ends, all heavy locks are released automatically.
Catalog access patterns
Catalog tables are just tables but accessed through specialized helpers. Two main interfaces:
Sys cache. A dictionary of cached catalog tuples keyed by common keys (e.g.,
pg_class.oid).SearchSysCache1/SearchSysCache2etc. Always paired withReleaseSysCache(orheap_freetupleif you copied).HeapTuple tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); if (HeapTupleIsValid(tup)) { Form_pg_class form = (Form_pg_class) GETSTRUCT(tup); /* read form->... */ ReleaseSysCache(tup); }Direct heap scan. When sys cache doesn't fit. Open the relation, build a
ScanKey,systable_beginscan/systable_getnext/systable_endscan.
Source: src/backend/utils/cache/syscache.c registers all sys caches. src/include/catalog/pg_*.h defines the on-disk row layout for each catalog (the Form_pg_* structs).
Node trees
Almost everything that flows through the parser/planner/executor is a tree of Node structs. Source: src/backend/nodes/. Each node has a NodeTag. The pattern:
if (IsA(node, FuncExpr))
{
FuncExpr *fe = (FuncExpr *) node;
/* ... */
}Helpers to copy, compare, serialize, and walk node trees live alongside (copyfuncs.c, equalfuncs.c, outfuncs.c, readfuncs.c). Most are auto-generated from annotated header files (src/include/nodes/*.h) by the gen_node_support.pl script. If you add a field to a Node, you usually only need to declare it in the header — the generator updates the support functions on rebuild.
fmgr: calling functions
User-defined and built-in functions are called through the function manager (src/backend/utils/fmgr/). The V1 calling convention:
PG_FUNCTION_INFO_V1(my_func);
Datum
my_func(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
if (PG_ARGISNULL(1))
PG_RETURN_NULL();
/* ... */
PG_RETURN_TEXT_P(cstring_to_text(result));
}PG_FUNCTION_ARGS expands to a FunctionCallInfo parameter; the PG_GETARG_* and PG_RETURN_* macros wrap unpacking and packing Datums. Almost all C-level functions written for the backend follow this pattern.
Spinlocks, atomics, and barriers
For very small critical sections in shared memory, use SpinLockAcquire/SpinLockRelease (src/backend/storage/lmgr/s_lock.c). For lock-free algorithms, use the atomics abstraction (src/include/port/atomics.h) — pg_atomic_uint32, pg_atomic_compare_exchange_u32, pg_atomic_fetch_add_u64. Memory barriers come from src/include/port/atomics.h as well: pg_read_barrier, pg_write_barrier, pg_memory_barrier.
Do not write inline assembly for synchronization. The portable wrappers exist for a reason.
Frontend vs. backend
Code under src/include/, src/common/, and src/port/ is shared between the frontend tools and the backend, so it must avoid backend-only facilities (no palloc, ereport, elog). Use pg_log_* for logging in frontend code. Use malloc/free (or, in pg_dump etc., the wrapping macros that exit on OOM).
When in doubt, check whether the file is compiled into both a libpq-based tool and the backend — if so, it must be portable.
When in Rome
The single best thing a new contributor can do is read code in the area they want to modify and copy its conventions exactly. PostgreSQL is not the place for stylistic flourishes; consistency is valued above cleverness.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.