PostgreSQL Function Manager (fmgr) — The V1 Call Convention, Lookup Pipeline, and Extension ABI
Contents:
- Theoretical Background
- Common DBMS Design
- PostgreSQL’s Approach
- Source Walkthrough
- Source verification (as of 2026-06-05)
- Beyond PostgreSQL — Comparative Designs & Research Frontiers
- Sources
Theoretical Background
Section titled “Theoretical Background”Every relational engine that supports user-defined functions (UDFs) must answer two questions at call time: where is the code and how are arguments and results exchanged? The answers form the engine’s function-call interface (FCI): the contract between the query executor and any callable unit — built-in, SQL-expression, extension C function, or PL/pgsql procedure.
Two design tensions drive every FCI:
-
Lookup cost vs. call cost. Resolving a function OID to a callable address requires catalog access. In a SQL query that applies the same function to millions of rows, paying lookup cost per row is ruinous. The canonical resolution is to split the path: a one-time lookup phase produces a cached descriptor; the call phase uses only that descriptor, never touching the catalog again. This is the same separation that motivates prepared statements and plan caches in the query layer.
-
Type safety vs. uniformity. A direct C call expresses argument types precisely but ties the caller to one callee signature — every new type would need a new entry point. Engines instead unify all arguments and return values into a single wide type (PostgreSQL uses
Datum, a pointer-or-scalar union), letting every function have the same C signature. Type-specific conversion macros (PG_GETARG_INT32,PG_RETURN_FLOAT8) restore safety at the edges.
Three additional features emerge from the practical demands of a production SQL engine and are present in some form in every major RDBMS:
- Strict-null short-circuit. A function marked strict should not be called when any argument is NULL — the result is implicitly NULL. Enforcing this in the manager rather than in every function body eliminates a pervasive class of defensive code.
- Set-returning functions (SRFs). SQL allows a function to produce a set of rows (equivalent to a table-valued function in SQL Server, a pipelined function in Oracle). The call convention must support either value-per-call (iterator) or batch-materialize semantics.
- Extension ABI stability. Dynamically loaded modules compiled against one minor version must refuse to load against an incompatible major version. A magic-struct pattern — a well-known symbol the loader checks before calling anything else — is the standard solution.
PostgreSQL’s answers to all three live in fmgr.h, fmgr.c, and the
src/backend/utils/fmgr/README, which is the primary design document for
this subsystem.
Common DBMS Design
Section titled “Common DBMS Design”The patterns below recur across PostgreSQL, Oracle, DB2, SQL Server, and MySQL. PostgreSQL’s specific choices in the next section are one calibration of these shared dials.
Two-phase lookup + call descriptor
Section titled “Two-phase lookup + call descriptor”Almost every engine separates function resolution (find the code address,
read catalog metadata) from function invocation (pass arguments, collect
result). The resolved metadata is stored in a per-function descriptor struct
that the executor caches for the lifetime of a query (or plan). The descriptor
holds: the code pointer, the number of expected arguments, the strictness flag,
and a per-call scratch slot (fn_extra) that language handlers use to cache
their own parsed-function state.
Datum-uniform calling convention
Section titled “Datum-uniform calling convention”Rather than separate C entry points per type (add_int32, add_float8, …),
engines use a single scalar wide enough to hold any value. Oracle uses a
dvoid * with type codes; SQL Server uses its own variant-type struct; MySQL
uses the Item hierarchy. PostgreSQL uses Datum — on 64-bit platforms a
uintptr_t large enough to hold either a scalar value directly or a pointer to
a heap-allocated value. All arguments arrive as a NullableDatum[] array; all
results leave as a Datum plus an isnull flag.
Language-dispatcher indirection
Section titled “Language-dispatcher indirection”A function’s call handler is itself a C function registered in pg_language.
When the manager resolves a PL/pgSQL function, it does not call the PL/pgSQL
bytecode directly; it calls the PL/pgSQL call handler, which then interprets
the bytecode. The call handler receives the same FunctionCallInfo the manager
would have passed to a C function, so from the manager’s perspective all
languages look identical after lookup.
Per-session shared-library cache
Section titled “Per-session shared-library cache”Loading a .so / .dll on every function call is prohibitively expensive.
Engines keep a session-level cache of open library handles keyed by the file
path. On the first call to any function in a library the file is dlopen’d and
the address is resolved; subsequent calls reuse the cached handle. PostgreSQL
adds a second layer: a hash table keyed by (fn_oid, xmin, ctid) that stores
the resolved C function pointer so that even dlsym is skipped on repeat calls.
PG_MODULE_MAGIC / ABI guard
Section titled “PG_MODULE_MAGIC / ABI guard”Any engine that supports dynamically loaded extensions needs a guard against
loading a module compiled for a different ABI (different struct layouts,
different FUNC_MAX_ARGS, etc.). The pattern: the loader looks for a well-known
symbol (e.g., Pg_magic_func in PostgreSQL) in the loaded library before
calling any user function. The symbol returns a struct of ABI-critical constants
that the loader compares against its own compile-time values. A mismatch aborts
the load with a useful error.
Theory ↔ PostgreSQL mapping
Section titled “Theory ↔ PostgreSQL mapping”| Concept / pattern | PostgreSQL name |
|---|---|
| Function lookup descriptor | FmgrInfo (fmgr.h:56) |
| Per-call argument/result container | FunctionCallInfoBaseData / FunctionCallInfo (fmgr.h:85) |
| Uniform value type | Datum (postgres.h) |
| Code pointer in descriptor | FmgrInfo.fn_addr — PGFunction typedef |
| Language dispatcher (call handler) | fn_addr = language’s lanplcallfoid for PL functions |
| Per-session library cache | CFuncHash hash table in fmgr.c |
| Built-in function table | fmgr_builtins[] + fmgr_builtin_oid_index[] (fmgrtab.h) |
| ABI guard | Pg_magic_struct / PG_MODULE_MAGIC macro (fmgr.h) |
| Strict-null short-circuit | FmgrInfo.fn_strict; enforced by caller before FunctionCallInvoke |
| Set-returning function support | ReturnSetInfo node in fcinfo->resultinfo |
| Per-call handler scratch slot | FmgrInfo.fn_extra |
| Security-definer / proconfig wrapper | fmgr_security_definer interposer function |
PostgreSQL’s Approach
Section titled “PostgreSQL’s Approach”PostgreSQL’s function manager, universally called fmgr, is the single
gateway through which the executor calls every SQL-callable unit. The in-tree
src/backend/utils/fmgr/README is the design authority; this section distills
it against the REL_18_STABLE source (commit 273fe94).
The architecture has three layers:
- The descriptor layer —
FmgrInfoand its population viafmgr_info. - The call layer —
FunctionCallInfoBaseData,FunctionCallInvoke, and theDirectFunctionCall/FunctionCallNcoll/OidFunctionCallNcollfamilies. - The extension ABI layer —
PG_FUNCTION_INFO_V1,PG_MODULE_MAGIC, and thedfmgr.clibrary loader.
Layer 1 — the FmgrInfo descriptor
Section titled “Layer 1 — the FmgrInfo descriptor”FmgrInfo is the result of resolving a function OID once per query (or plan):
// FmgrInfo — src/include/fmgr.htypedef struct FmgrInfo{ PGFunction fn_addr; /* pointer to function or handler to be called */ Oid fn_oid; /* OID of function (NOT of handler, if any) */ short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */ bool fn_strict; /* function is "strict" (NULL in => NULL out) */ bool fn_retset; /* function returns a set */ unsigned char fn_stats; /* collect stats if track_functions > this */ void *fn_extra; /* extra space for use by handler */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */ fmNodePtr fn_expr; /* expression parse tree for call, or NULL */} FmgrInfo;fn_addr is the only field the call layer uses at invocation time. For a
built-in, it is the C function’s address directly. For a C extension, it is the
dlopen-resolved symbol. For any procedural language (PL/pgSQL, PL/Python, …),
it is the language’s call handler address — the handler then uses fn_oid to
locate the actual function body. fn_extra is the handler’s per-call cache
slot: the PL/pgSQL handler, for example, stores its compiled function parse tree
there after the first call so subsequent calls skip re-parsing.
fmgr_info (the public entry point) delegates to fmgr_info_cxt_security,
which is where the dispatch logic lives:
// fmgr_info_cxt_security — src/backend/utils/fmgr/fmgr.cstatic voidfmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, bool ignore_security){ const FmgrBuiltin *fbp; /* ... */ if ((fbp = fmgr_isbuiltin(functionId)) != NULL) { /* Fast path: built-in, skip pg_proc lookup */ finfo->fn_nargs = fbp->nargs; finfo->fn_strict = fbp->strict; finfo->fn_addr = fbp->func; finfo->fn_oid = functionId; return; } /* Otherwise look up pg_proc via syscache */ procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); /* ... */ if (!ignore_security && (procedureStruct->prosecdef || ... || FmgrHookIsNeeded(functionId))) { finfo->fn_addr = fmgr_security_definer; /* wrap in security layer */ return; } switch (procedureStruct->prolang) { case INTERNALlanguageId: /* alias for a built-in */ fbp = fmgr_lookupByName(prosrc); finfo->fn_addr = fbp->func; break; case ClanguageId: fmgr_info_C_lang(functionId, finfo, procedureTuple); break; case SQLlanguageId: finfo->fn_addr = fmgr_sql; break; default: fmgr_info_other_lang(functionId, finfo, procedureTuple); break; } finfo->fn_oid = functionId; ReleaseSysCache(procedureTuple);}The dispatch has four leaves, each matching a prolang value:
prolang value | fn_addr set to | Notes |
|---|---|---|
| built-in (fast path) | fbp->func (no syscache hit needed) | fmgr_isbuiltin uses fmgr_builtin_oid_index[] — O(1) array lookup |
INTERNALlanguageId | fbp->func via fmgr_lookupByName | User-created alias for a built-in; slower path |
ClanguageId | dlopen-resolved symbol via CFuncHash | fmgr_info_C_lang handles dlopen + CFuncHash |
SQLlanguageId | fmgr_sql (the SQL-function evaluator) | Interprets SQL function body inline |
| anything else | language’s lanplcallfoid | fmgr_info_other_lang looks up pg_language |
The built-in fast path. fmgr_isbuiltin avoids a syscache lookup entirely:
// fmgr_isbuiltin — src/backend/utils/fmgr/fmgr.cstatic const FmgrBuiltin *fmgr_isbuiltin(Oid id){ uint16 index; if (id > fmgr_last_builtin_oid) return NULL; index = fmgr_builtin_oid_index[id]; if (index == InvalidOidBuiltinMapping) return NULL; return &fmgr_builtins[index];}fmgr_builtins[] and fmgr_builtin_oid_index[] are code-generated arrays
(generated from pg_proc.dat by the build system into fmgrtab.c). The index
array maps OID → slot in O(1) with a simple array dereference, making the
built-in path a handful of instructions with no locking.
The C-extension cache. fmgr_info_C_lang reads prosrc (symbol name) and
probin (library file path) from pg_proc, calls load_external_function
(which opens the .so via dfmgr.c and caches the dlopen handle), then
calls fetch_finfo_record to verify the PG_FUNCTION_INFO_V1 descriptor, and
finally stores the resolved address in CFuncHash keyed by (fn_oid, xmin, ctid). Staleness is detected by comparing the cached xmin/ctid against the
current pg_proc tuple — if they differ the cached entry is replaced.
The security-definer interposer. When prosecdef is true, proconfig is
set, or a plugin hook (fmgr_hook) is active, fn_addr is set to
fmgr_security_definer instead of the actual function. At call time,
fmgr_security_definer switches user ID and/or applies GUC overrides, then
invokes the real function through a private cached FmgrInfo stored in
fn_extra. This makes security-definer transparent to callers.
Layer 2 — the FunctionCallInfo call layer
Section titled “Layer 2 — the FunctionCallInfo call layer”FunctionCallInfoBaseData (typedef’d as FunctionCallInfo) is the per-call
container passed to every function:
// FunctionCallInfoBaseData — src/include/fmgr.htypedef struct FunctionCallInfoBaseData{ FmgrInfo *flinfo; /* ptr to lookup info used for this call */ fmNodePtr context; /* pass info about context of call */ fmNodePtr resultinfo; /* pass or return extra info about result */ Oid fncollation; /* collation for function to use */ bool isnull; /* function must set true if result is NULL */ short nargs; /* # arguments actually passed */ NullableDatum args[]; /* flexible array of (Datum value, bool isnull) */} FunctionCallInfoBaseData;context is a Node * that carries call-context information: TriggerData
for trigger functions, AggState / WindowAggState for aggregate/window
functions, CallContext for stored procedures, and ErrorSaveContext for
soft-error callers. The IsA(context, X) pattern lets a function detect its
calling context. resultinfo carries ReturnSetInfo for set-returning
functions.
Stack allocation with LOCAL_FCINFO. Since FunctionCallInfoBaseData has a
flexible array member for arguments, callers use the LOCAL_FCINFO macro to
allocate a correctly-sized struct on the stack:
// LOCAL_FCINFO — src/include/fmgr.h#define LOCAL_FCINFO(name, nargs) \ union { \ FunctionCallInfoBaseData fcinfo; \ char fcinfo_data[SizeForFunctionCallInfo(nargs)]; \ } name##data; \ FunctionCallInfo name = &name##data.fcinfoThe union guarantees alignment. SizeForFunctionCallInfo(nargs) computes
offsetof(args) + sizeof(NullableDatum) * nargs. Heap allocation uses
palloc(SizeForFunctionCallInfo(nargs)).
Invocation macro. The actual call is a single indirect function call:
// FunctionCallInvoke — src/include/fmgr.h#define FunctionCallInvoke(fcinfo) ((* (fcinfo)->flinfo->fn_addr) (fcinfo))Three call-site patterns. Callers choose one of three families depending on how much context they have:
// DirectFunctionCall1Coll — src/backend/utils/fmgr/fmgr.c// For calling a known PGFunction pointer directly; no FmgrInfo needed.DatumDirectFunctionCall1Coll(PGFunction func, Oid collation, Datum arg1){ LOCAL_FCINFO(fcinfo, 1); InitFunctionCallInfoData(*fcinfo, NULL, 1, collation, NULL, NULL); fcinfo->args[0].value = arg1; fcinfo->args[0].isnull = false; result = (*func) (fcinfo); if (fcinfo->isnull) elog(ERROR, "function %p returned NULL", (void *) func); return result;}The three families and their use cases:
| Family | When to use | Notes |
|---|---|---|
DirectFunctionCallNcoll | Caller holds PGFunction*; no NULL args/result | No FmgrInfo; asserts non-NULL result |
FunctionCallNcoll | Caller has FmgrInfo * (from prior fmgr_info) | Tracks pgstat_function_usage; allows NULL |
OidFunctionCallNcoll | Caller has only an OID | Calls fmgr_info + FunctionCallNcoll inline |
Coding conventions for C functions. Every fmgr-callable C function has the
signature Datum func(PG_FUNCTION_ARGS) where PG_FUNCTION_ARGS expands to
FunctionCallInfo fcinfo. Arguments are fetched with type-specific macros and
results are returned the same way:
// Example fmgr-callable function using V1 conventionDatumint4add(PG_FUNCTION_ARGS){ int32 arg1 = PG_GETARG_INT32(0); int32 arg2 = PG_GETARG_INT32(1); PG_RETURN_INT32(arg1 + arg2);}PG_GETARG_INT32(n) expands to DatumGetInt32(fcinfo->args[n].value).
PG_RETURN_INT32(x) expands to return Int32GetDatum(x). For varlena types
like text, PG_GETARG_TEXT_PP(n) de-TOASTs the argument (fetching the
decompressed, detoasted value) and PG_RETURN_TEXT_P(x) returns a pointer as
Datum. A non-strict function checks PG_ARGISNULL(n) before fetching; strict
functions never see NULL arguments because the caller’s null-check fires first.
Layer 3 — the extension ABI
Section titled “Layer 3 — the extension ABI”PG_FUNCTION_INFO_V1. Every C extension function must declare its calling
convention with the PG_FUNCTION_INFO_V1 macro:
// PG_FUNCTION_INFO_V1 — src/include/fmgr.h#define PG_FUNCTION_INFO_V1(funcname) \extern PGDLLEXPORT Datum funcname(PG_FUNCTION_ARGS); \extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \const Pg_finfo_record * \CppConcat(pg_finfo_,funcname) (void) \{ \ static const Pg_finfo_record my_finfo = { 1 }; \ return &my_finfo; \} \extern int no_such_variableThis generates a companion pg_finfo_<funcname> function that returns a
Pg_finfo_record with api_version = 1. fetch_finfo_record looks up this
companion symbol by name (psprintf("pg_finfo_%s", funcname)) and calls it to
verify the version. If no such symbol is found, fmgr raises an error with the
hint message “SQL-callable functions need an accompanying
PG_FUNCTION_INFO_V1(funcname).” Version-0 (the “old style”) has been removed;
V1 is the only supported convention.
PG_MODULE_MAGIC. Beyond per-function version checks, a shared library must
pass a whole-module ABI check. The PG_MODULE_MAGIC macro (or the newer
PG_MODULE_MAGIC_EXT(...)) emits a Pg_magic_struct under the well-known
symbol Pg_magic_func:
// Pg_magic_struct and Pg_abi_values — src/include/fmgr.htypedef struct{ int version; /* PostgreSQL major version */ int funcmaxargs; /* FUNC_MAX_ARGS */ int indexmaxkeys; /* INDEX_MAX_KEYS */ int namedatalen; /* NAMEDATALEN */ int float8byval; /* FLOAT8PASSBYVAL */ char abi_extra[32]; /* see pg_config_manual.h */} Pg_abi_values;dfmgr.c’s load_external_function calls Pg_magic_func() on the freshly
loaded library and compares the returned struct against the server’s own values
field-by-field. Any mismatch triggers incompatible_module_error() with a
message naming the mismatched field. This prevents obscure crashes from struct
layout mismatches when, for example, a library was compiled with a different
NAMEDATALEN.
Soft errors (ErrorSaveContext)
Section titled “Soft errors (ErrorSaveContext)”PostgreSQL’s normal error path (ereport(ERROR, ...)) unwinds via longjmp
and requires a full subtransaction cleanup — expensive for cases like datatype
input functions that simply want to reject malformed input. As of PG14, a
function can report a soft error instead:
// errsave / ereturn pattern — src/backend/utils/fmgr/README// Instead of: ereport(ERROR, (errcode(...), errmsg(...)));// Write: errsave(fcinfo->context, (errcode(...), errmsg(...)));// Or combine: ereturn(fcinfo->context, (Datum) 0, (errcode(...), errmsg(...)));If fcinfo->context is an ErrorSaveContext node, errsave stores the error
information there and returns normally (the function then returns a dummy
Datum). If context is NULL or any other node type, errsave behaves
identically to ereport(ERROR). Callers that want to trap soft errors allocate
an ErrorSaveContext, pass it as fcinfo->context, and inspect
escontext.error_occurred after the call. The README explicitly restricts soft
errors to recoverable conditions (invalid input syntax, out-of-range values);
internal errors and OOM must still use the hard path.
Set-returning functions (SRFs)
Section titled “Set-returning functions (SRFs)”A function marked proretset = true in pg_proc receives a ReturnSetInfo
node in fcinfo->resultinfo. Two return modes are supported:
Value-per-call mode (iterator): the function is called repeatedly; it sets
ReturnSetInfo.isDone to ExprMultipleResult for each row and
ExprEndResult when done. The executor calls it in a loop. Value-per-call
functions must not clean up resources in the final call because the executor
may stop calling them early (e.g., due to LIMIT).
The funcapi.h macros encapsulate the protocol. The first-call detection is
simply “is fn_extra still NULL?”, and the cross-call state lives in a
FuncCallContext hung off fn_extra:
// SRF value-per-call macros — src/include/funcapi.h#define SRF_IS_FIRSTCALL() (fcinfo->flinfo->fn_extra == NULL)#define SRF_FIRSTCALL_INIT() init_MultiFuncCall(fcinfo)#define SRF_PERCALL_SETUP() per_MultiFuncCall(fcinfo)
#define SRF_RETURN_NEXT(_funcctx, _result) \ do { \ ReturnSetInfo *rsi; \ (_funcctx)->call_cntr++; \ rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ rsi->isDone = ExprMultipleResult; \ PG_RETURN_DATUM(_result); \ } while (0)
#define SRF_RETURN_DONE(_funcctx) \ do { \ ReturnSetInfo *rsi; \ end_MultiFuncCall(fcinfo, _funcctx); \ rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ rsi->isDone = ExprEndResult; \ PG_RETURN_NULL(); \ } while (0)init_MultiFuncCall runs exactly once: it asserts the function was called in a
set-accepting context (IsA(fcinfo->resultinfo, ReturnSetInfo)), creates a
dedicated AllocSetContextCreate child of fn_mcxt named “SRF multi-call
context”, allocates a zeroed FuncCallContext there, stashes the pointer into
fn_extra, and — crucially — registers a shutdown_MultiFuncCall callback via
RegisterExprContextCallback so the cross-call context is freed even if the
executor abandons the scan early (the LIMIT case):
// init_MultiFuncCall — src/backend/utils/fmgr/funcapi.cFuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS){ FuncCallContext *retval; if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (fcinfo->flinfo->fn_extra == NULL) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext multi_call_ctx; multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt, "SRF multi-call context", ALLOCSET_SMALL_SIZES); retval = (FuncCallContext *) MemoryContextAllocZero(multi_call_ctx, sizeof(FuncCallContext)); retval->multi_call_memory_ctx = multi_call_ctx; fcinfo->flinfo->fn_extra = retval; RegisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall, PointerGetDatum(fcinfo->flinfo)); } else elog(ERROR, "init_MultiFuncCall cannot be called more than once"); return retval;}per_MultiFuncCall is a one-liner that just casts fn_extra back to
FuncCallContext *. call_cntr (incremented by SRF_RETURN_NEXT) and the
optional max_calls give the function its loop counter; user_fctx is the
per-SRF scratch pointer for whatever cursor state the function needs. Because
the shutdown callback owns teardown, the README’s warning holds: a
value-per-call SRF must not assume SRF_RETURN_DONE will ever run, so any
non-memory resource (file descriptor, open cursor) is unsafe in this mode —
use materialize mode instead.
Materialize mode: the function creates a Tuplestore in
econtext->ecxt_per_query_memory, fills it in one call, stores the pointer and
a TupleDesc into ReturnSetInfo, and sets returnMode = SFRM_Materialize. The InitMaterializedSRF helper function (in funcapi.c)
encapsulates the boilerplate:
// InitMaterializedSRF — src/backend/utils/fmgr/funcapi.cvoidInitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags){ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* sanity checks on rsinfo ... */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; old_context = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(random_access, false, work_mem); MemoryContextSwitchTo(old_context); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = stored_tupdesc;}funcapi.c also provides init_MultiFuncCall / per_MultiFuncCall /
end_MultiFuncCall for value-per-call SRFs that need per-call state (stored in
FuncCallContext, allocated in fn_mcxt).
Call path + SRF value-per-call protocol
Section titled “Call path + SRF value-per-call protocol”The diagram below traces a single value end-to-end — from the pg_proc OID
through the two-phase lookup/call split to a returned Datum — and then shows
how a set-returning function reuses that same FunctionCallInvoke edge once
per row via the fn_extra first-call latch.
flowchart TD
OID["pg_proc OID<br/>(functionId)"]
FI["fmgr_info / fmgr_info_cxt_security<br/>(lookup phase, once per query)"]
FBP["fmgr_isbuiltin<br/>builtin? fn_addr = fbp->func"]
CL["fmgr_info_C_lang<br/>CFuncHash / fetch_finfo_record"]
OL["fmgr_info_other_lang / fmgr_sql<br/>fn_addr = call handler"]
FINFO["FmgrInfo populated<br/>fn_addr, fn_nargs, fn_strict, fn_extra"]
LF["LOCAL_FCINFO(fcinfo, nargs)<br/>+ InitFunctionCallInfoData<br/>+ fill args[].value / .isnull"]
STRICT{"fn_strict &&<br/>any arg isnull?"}
NULLOUT["result = NULL<br/>(skip the call)"]
INV["FunctionCallInvoke(fcinfo)<br/>(* fn_addr)(fcinfo)"]
BODY["function body<br/>Datum f(PG_FUNCTION_ARGS)<br/>PG_GETARG_* / PG_RETURN_*"]
RET["result Datum + fcinfo->isnull"]
OID --> FI
FI -->|"builtin fast path"| FBP
FI -->|"C language"| CL
FI -->|"SQL / PL"| OL
FBP --> FINFO
CL --> FINFO
OL --> FINFO
FINFO --> LF
LF --> STRICT
STRICT -->|"yes"| NULLOUT
STRICT -->|"no"| INV
INV --> BODY
BODY --> RET
RET -->|"fn_retset SRF only"| FIRST{"SRF_IS_FIRSTCALL?<br/>fn_extra == NULL"}
FIRST -->|"yes"| INIT["SRF_FIRSTCALL_INIT<br/>init_MultiFuncCall<br/>alloc FuncCallContext in fn_extra"]
FIRST -->|"no"| PER["SRF_PERCALL_SETUP<br/>per_MultiFuncCall"]
INIT --> PER
PER --> MORE{"call_cntr <<br/>max_calls?"}
MORE -->|"yes"| NEXT["SRF_RETURN_NEXT<br/>isDone = ExprMultipleResult<br/>executor re-invokes via fn_addr"]
MORE -->|"no"| DONE["SRF_RETURN_DONE<br/>end_MultiFuncCall<br/>isDone = ExprEndResult"]
NEXT -->|"loop"| INV
Figure 2 — fmgr call path and the SRF value-per-call protocol. The top half is
the per-value path: fmgr_info populates an FmgrInfo once, then each call
stack-allocates an fcinfo with LOCAL_FCINFO, applies the strict-null
short-circuit, and dispatches through FunctionCallInvoke. The bottom half (the
fn_retset branch) shows the value-per-call loop: the fn_extra == NULL latch
distinguishes the first call (which allocates a FuncCallContext) from every
subsequent call, and SRF_RETURN_NEXT re-enters the same FunctionCallInvoke
edge until SRF_RETURN_DONE sets ExprEndResult.
Component diagram
Section titled “Component diagram”flowchart TD EX["Executor<br/>(ExecMakeTableFunctionResult,<br/>ExprEvalStep, etc.)"] FI["fmgr_info<br/>(lookup phase)"] FCI["FunctionCallInvoke<br/>(call phase)"] BI["fmgr_isbuiltin<br/>O(1) OID index"] CH["CFuncHash<br/>(C extension cache)"] DL["dfmgr.c<br/>load_external_function<br/>+ fetch_finfo_record"] SD["fmgr_security_definer<br/>(interposer)"] PL["fmgr_info_other_lang<br/>→ lanplcallfoid"] SQL["fmgr_sql<br/>(SQL function evaluator)"] FN["fn_addr<br/>(PGFunction*)"] EX -->|"fmgr_info(oid, &flinfo)"| FI FI -->|"builtin OID"| BI FI -->|"C language"| CH CH -->|"cache miss"| DL FI -->|"security / hook"| SD FI -->|"SQL language"| SQL FI -->|"PL language"| PL BI --> FN CH --> FN SD --> FN SQL --> FN PL --> FN FN -->|"FunctionCallInvoke(fcinfo)"| FCI EX -->|"LOCAL_FCINFO + args"| FCI
Figure 1 — fmgr component flow. The executor drives two separate phases: the
lookup phase (left) produces an FmgrInfo with fn_addr set; the call phase
(right) passes FunctionCallInfo through fn_addr. CFuncHash short-circuits
the dfmgr.c library load on repeat calls. fmgr_security_definer interposes
as fn_addr when prosecdef, proconfig, or a plugin hook is active.
Source Walkthrough
Section titled “Source Walkthrough”Lookup pipeline
Section titled “Lookup pipeline”fmgr_info— public entry point; delegates tofmgr_info_cxt_securitywithCurrentMemoryContextandignore_security = false.fmgr_info_cxt— same but takes an explicitMemoryContextfor subsidiary data.fmgr_info_cxt_security— dispatch core: fast-path built-in check, syscache lookup,prolangswitch.fmgr_isbuiltin— O(1) array lookup infmgr_builtin_oid_index; returnsFmgrBuiltin *or NULL.fmgr_lookupByName— linear scan offmgr_builtins[]by name; used only forINTERNALlanguageIdaliases.fmgr_info_C_lang— C-extension path:lookup_C_funcinCFuncHash, thenload_external_function+fetch_finfo_record, thenrecord_C_func.fetch_finfo_record— locatespg_finfo_<name>symbol, calls it, validatesapi_version == 1.lookup_C_func/record_C_func—CFuncHashget/set; staleness check viaxmin+ctid.fmgr_info_other_lang— PL handler path: resolveslanplcallfoidviafmgr_info_cxt_securitywithignore_security = true.fmgr_security_definer— interposer; caches realFmgrInfo+userid+ GUC lists infn_extra; restores after call.fmgr_info_copy— shallow copy ofFmgrInfo; zeroesfn_extraso handler state is not aliased.fmgr_symbol— utility: given OID, returns(mod, fn)strings identifying the C symbol (used bypg_get_function_sqlbodyand JIT).fmgr_internal_function— reverse lookup: name → OID infmgr_builtins[].
Call layer
Section titled “Call layer”FunctionCallInvoke— macro; one indirect call throughfn_addr.InitFunctionCallInfoData— macro; fills all scalar fields ofFunctionCallInfoBaseDataexceptargs[].LOCAL_FCINFO— macro; stack-allocates a correctly-sizedFunctionCallInfoBaseData.DirectFunctionCallNColl(1–9) — call a knownPGFunctionpointer; noFmgrInfo; no NULL args/result allowed.CallerFInfoFunctionCall1/2— likeDirectFunctionCallbut takes anFmgrInfo *(useful when the caller has one and wantsfn_extrato persist).FunctionCallNColl(0–9) — call viaFmgrInfo *; trackspgstat_init_function_usage.OidFunctionCallNColl(0–9) — call by OID; callsfmgr_infoinline.
Extension ABI
Section titled “Extension ABI”PG_FUNCTION_INFO_V1— macro; generatespg_finfo_<name>symbol.PG_MODULE_MAGIC/PG_MODULE_MAGIC_EXT— macro; generatesPg_magic_funcsymbol withPg_magic_struct.load_external_function(indfmgr.c) —dlopen+dlsymwith library cache; callsPg_magic_funcfor ABI check.fetch_finfo_record— locates + validatespg_finfo_<name>.
Set-returning functions
Section titled “Set-returning functions”InitMaterializedSRF— sets upTuplestore+TupleDescinReturnSetInfofor materialize-mode SRFs.init_MultiFuncCall/per_MultiFuncCall/end_MultiFuncCall— value-per-call SRF state management viaFuncCallContext.get_call_result_type— resolves the actual return type of a polymorphic function from the call expression; used by SRFs to build theirTupleDesc.
Position hints (as of 2026-06-05, commit 273fe94)
Section titled “Position hints (as of 2026-06-05, commit 273fe94)”| Symbol | File | Line |
|---|---|---|
FmgrInfo | src/include/fmgr.h | 56 |
FunctionCallInfoBaseData | src/include/fmgr.h | 85 |
PGFunction typedef | src/include/fmgr.h | 40 |
LOCAL_FCINFO | src/include/fmgr.h | 110 |
InitFunctionCallInfoData | src/include/fmgr.h | 150 |
FunctionCallInvoke | src/include/fmgr.h | 172 |
PG_FUNCTION_ARGS | src/include/fmgr.h | 193 |
PG_ARGISNULL | src/include/fmgr.h | 209 |
PG_RETURN_NULL | src/include/fmgr.h | 345 |
PG_FUNCTION_INFO_V1 | src/include/fmgr.h | 415 |
Pg_finfo_record | src/include/fmgr.h | 394 |
Pg_abi_values | src/include/fmgr.h | 466 |
Pg_magic_struct | src/include/fmgr.h | 478 |
FmgrBuiltin | src/include/utils/fmgrtab.h | 25 |
fmgr_isbuiltin | src/backend/utils/fmgr/fmgr.c | 76 |
fmgr_lookupByName | src/backend/utils/fmgr/fmgr.c | 101 |
fmgr_info | src/backend/utils/fmgr/fmgr.c | 127 |
fmgr_info_cxt | src/backend/utils/fmgr/fmgr.c | 137 |
fmgr_info_cxt_security | src/backend/utils/fmgr/fmgr.c | 147 |
fmgr_symbol | src/backend/utils/fmgr/fmgr.c | 281 |
fmgr_info_C_lang | src/backend/utils/fmgr/fmgr.c | 349 |
fmgr_info_other_lang | src/backend/utils/fmgr/fmgr.c | 418 |
fetch_finfo_record | src/backend/utils/fmgr/fmgr.c | 455 |
lookup_C_func | src/backend/utils/fmgr/fmgr.c | 515 |
record_C_func | src/backend/utils/fmgr/fmgr.c | 539 |
fmgr_info_copy | src/backend/utils/fmgr/fmgr.c | 580 |
fmgr_internal_function | src/backend/utils/fmgr/fmgr.c | 595 |
fmgr_security_definer | src/backend/utils/fmgr/fmgr.c | 632 |
DirectFunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 792 |
FunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 1129 |
OidFunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 1411 |
InitMaterializedSRF | src/backend/utils/fmgr/funcapi.c | 76 |
init_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 133 |
per_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 208 |
end_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 220 |
get_call_result_type | src/backend/utils/fmgr/funcapi.c | 276 |
FuncCallContext | src/include/funcapi.h | 57 |
SRF_IS_FIRSTCALL | src/include/funcapi.h | 305 |
SRF_FIRSTCALL_INIT | src/include/funcapi.h | 307 |
SRF_PERCALL_SETUP | src/include/funcapi.h | 309 |
SRF_RETURN_NEXT | src/include/funcapi.h | 311 |
SRF_RETURN_DONE | src/include/funcapi.h | 329 |
Source verification (as of 2026-06-05)
Section titled “Source verification (as of 2026-06-05)”Verified facts
Section titled “Verified facts”-
fmgr_isbuiltinis an O(1) array lookup, not a binary search. Verified infmgr.c:76–93. The function readsfmgr_builtin_oid_index[id](a code-generateduint16array) and returns&fmgr_builtins[index]. No iteration; the comment “fast lookup only possible if original oid still assigned” notes that reassigned OIDs (fromCREATE FUNCTION ... INTERNAL) fall through to thefmgr_lookupByNamelinear scan. -
CFuncHashchecks staleness byxmin+ctid, not just OID. Verified inlookup_C_func(fmgr.c:515–533). The entry is accepted only if bothfn_xmin == HeapTupleHeaderGetRawXmin(t_data)andItemPointerEquals(&fn_tid, &t_self). This catchespg_proctuple updates (e.g.,ALTER FUNCTION) without a session restart. -
V0 calling convention has been fully removed. The
READMEstates “the V0 interface has been removed”. Verified: theapi_versionswitch infmgr_info_C_lang(fmgr.c:399–410) has onlycase 1— any other value falls toelog(ERROR, "unrecognized function API version: %d"). There is nocase 0branch. -
fmgr_security_defineris transparent to the outerfcinfo. Verified infmgr.c:738–777. The function savesfcinfo->flinfo, swaps in the cached innerFmgrInfo, calls viaFunctionCallInvoke, then restoresfcinfo->flinfoin both the success andPG_CATCHpaths. The outerfcinfo(with all the caller’s arguments) is passed unchanged; onlyflinfois swapped for the duration. -
PG_MODULE_MAGIC_EXTis the PG18 addition; plainPG_MODULE_MAGICis equivalent toPG_MODULE_MAGIC_EXT()with no named arguments. Verified infmgr.h:478–495.Pg_magic_structhasnameandversionfields that remain zero (NULL) when the plain macro is used. These fields are informational only and are not compared during the ABI check. -
Soft errors via
errsave/ereturnrequire the caller to pass anErrorSaveContext *asfcinfo->context. Verified inREADME§“Handling Soft Errors”. Ifcontextis NULL or not anErrorSaveContext,errsavecallsereport(ERROR)— no behavioral change for callers that do not opt in.
Open questions
Section titled “Open questions”-
fmgr_hook/needs_fmgr_hookplugin API. Two global function pointers (fmgr_hook,needs_fmgr_hook) are declared infmgr.c:39–40but no in-tree caller sets them (they are extension points). The documentation for what a plugin must do when it sets these globals lives only in the header comment infmgr.c. Investigation path: searchsrc/include/fmgr.hforfmgr_hook_typeand trace call sites; look for third-party extensions (e.g.,pg_hint_plan) that use these hooks for examples. -
CallerFInfoFunctionCall1/2use cases. These functions pass a caller- suppliedFmgrInfo *to a knownPGFunction; the comment says “these functions work like the DirectFunctionCall functions except that” theFmgrInfois available. The only in-tree users are inarray_mapand a few other places inutils/adt/. Whether these have performance advantages overDirectFunctionCallin typical call sites is not documented; the distinction is subtle and the README does not discuss them. -
fn_exprthread.FmgrInfo.fn_expris described as “expression parse tree for call, or NULL” and is “information about the arguments rather than the function.” How consistently this is set by the executor across different plan node types (scan nodes vs. projection vs. aggregate transition) is not verified here. Investigation path: grep forfmgr_info_set_exprinexecutor/.
Beyond PostgreSQL — Comparative Designs & Research Frontiers
Section titled “Beyond PostgreSQL — Comparative Designs & Research Frontiers”-
Oracle’s call-spec and PL/SQL function manager. Oracle’s
CALL_SPECmechanism and thePL/SQL engine → native compilationpipeline address the same lookup-cost/type-safety tensions. Oracle’sDETERMINISTICandRESULT_CACHEfunction attributes are analogs to PostgreSQL’s strict +provolatileflags; a side-by-side would clarify what PostgreSQL’s planner can and cannot cache. -
SQL Server’s CLR integration and function ABI. SQL Server’s CLR integration uses .NET metadata for ABI verification (a richer version of
PG_MODULE_MAGIC). The managed/unmanaged boundary is an interesting comparison point for PostgreSQL’sfmgr_security_definerpattern. -
MySQL’s UDF ABI (xxx_init / xxx / xxx_deinit). MySQL’s three-function UDF API (init, main, deinit) maps directly to PostgreSQL’s
fn_extraper-call cache slot — both solve the “parse once, execute many” problem. MySQL’s ABI has no module-magic analog; version mismatch causes crashes rather than load-time errors. -
DuckDB’s function registration API. DuckDB’s
ScalarFunction/TableFunctionC++ API exposes the same two-phase pattern (register with metadata → invoke withDataChunk) but at a higher abstraction level with vectorized chunks rather than scalarDatum. A comparison with PostgreSQL’s SRF materialize mode would illuminate the per-tuple vs. batch-vectorized trade-off. Relevant to PostgreSQL’s own vectorization research tracks. -
The “INIT function” approach in extension ABI design. PostgreSQL’s
_PG_inithook (declaredPGDLLEXPORTinfmgr.h:434) is called once per library load and is the canonical place to register background workers, hooks, and custom lock managers. The pattern is similar to Linux’smodule_init/module_exit. A comparison with alternative designs (e.g., DuckDB’sLoadInternalor SQLite’ssqlite3_auto_extension) would be useful for thepostgres-extensions.mddoc.
Sources
Section titled “Sources”In-tree design documents
Section titled “In-tree design documents”src/backend/utils/fmgr/README— primary design document for the V1 FCI; the authoritative description ofFmgrInfo,FunctionCallInfo, call contexts, TOAST handling, SRF modes, soft errors, and function handler notes.
Textbook references
Section titled “Textbook references”- Database System Concepts (Silberschatz, Korth & Sudarshan, 7e) — §9.5 “Accessing SQL from a Programming Language” and §27 “PostgreSQL” for UDF integration context.
Source files
Section titled “Source files”src/backend/utils/fmgr/fmgr.c— lookup pipeline, call helpers,CFuncHash,fmgr_security_definer.src/backend/utils/fmgr/funcapi.c— SRF support (InitMaterializedSRF,MultiFuncCall,get_call_result_type).src/backend/utils/fmgr/dfmgr.c— dynamic library loader (dlopencache,load_external_function,Pg_magic_funcABI check).src/include/fmgr.h— all public types, macros, and function declarations for fmgr users.src/include/utils/fmgrtab.h—FmgrBuiltin,fmgr_builtins[],fmgr_builtin_oid_index[].src/include/funcapi.h—ReturnSetInfo,FuncCallContext,TypeFuncClass, SRF helper declarations.