postgres/postgres
Executor
The executor walks a plan tree pulling tuples on demand and returns them to the client. Source: src/backend/executor/.
Directory layout
src/backend/executor/
├── README # design notes
├── execAmi.c # parallel/rescan utilities
├── execAsync.c # async execution wrapper
├── execCurrent.c # CURRENT OF cursor support
├── execExpr.c # expression compilation
├── execExprInterp.c # the expression interpreter
├── execIndexing.c # index-aware INSERT/UPDATE
├── execMain.c # top-level driver
├── execParallel.c # parallel-worker setup, DSM-based tuple queues
├── execPartition.c # partition routing for INSERT/UPDATE
├── execProcnode.c # generic dispatch over plan-node types
├── execReplication.c # logical-replication apply path
├── execScan.c # generic scan helpers
├── execTuples.c # TupleTableSlot abstraction
├── execUtils.c # ExprContext, EState, MemoryContext glue
├── functions.c # SQL-language function execution
├── instrument.c # EXPLAIN ANALYZE timing/row counters
├── nodeAgg.c # aggregation
├── nodeAppend.c # UNION ALL, partition append
├── nodeBitmap*.c # bitmap-driven heap scans
├── nodeGather*.c # parallel-query roots
├── nodeHash*.c # hash builds and joins
├── nodeIndex*.c # index scans and index-only scans
├── nodeMaterial.c # buffered output
├── nodeMerge*.c # merge-join / merge append
├── nodeModify*.c # INSERT/UPDATE/DELETE/MERGE
├── nodeNestloop.c # nested-loop join
├── nodeRecursive*.c # WITH RECURSIVE
├── nodeResult.c # constant / one-row plans
├── nodeSeqscan.c # sequential heap scan
├── nodeSort.c # tuplesort wrapper
├── nodeSubplan.c # SubPlan execution
├── nodeWindowAgg.c # window functions
├── ... etc
├── spi.c # Server Programming Interface (SPI)
├── tqueue.c # tuple queues used by parallel workers
└── tstoreReceiver.c # tuple-store destinationExecution model
PostgreSQL uses a pull-based (Volcano-style) executor: each plan node implements ExecProcNode, which returns the next tuple. Children are pulled from when their parent needs another row. The dispatch table is in src/backend/executor/execProcnode.c:
TupleTableSlot *
ExecProcNode(PlanState *node)
{
return node->ExecProcNode(node);
}The function pointer is set when each PlanState is initialized. So ExecProcNode is a single virtual call; the actual scan/join logic lives in the per-node nodeXxx.c file.
Top-level driver
ExecutorRun (in execMain.c) is the entry point called from the wire-protocol layer. It:
- Sets up an
EState(executor state) and per-tuple memory context. - Calls
InitPlanto walk the plan tree and produce a parallelPlanStatetree. - Loops calling
ExecProcNodeon the root, sending each returned slot to the configured destination (DestReceiver). - Calls
ExecEndPlanto release resources.
DestReceiver is the abstraction that lets the same executor target a network protocol (printtup), a tuplestore, COPY output, or a SPI caller.
TupleTableSlot
Tuples flow between plan nodes as TupleTableSlots — a slot is a small object that can hold either a "minimal" tuple (no headers), a heap tuple, a virtual tuple (an array of Datum + null flags), or a buffer-pinned tuple. Source: execTuples.c. Each operator advertises which slot ops it produces (TTSOpsHeapTuple, TTSOpsVirtual, etc.) and which it expects to consume.
The point is to avoid copies. A scan can return a buffer-pinned heap tuple; an aggregate can read the columns it needs without copying; a sort can transform the slot into a minimal tuple to compress storage.
Expression evaluation
Every WHERE clause, target-list expression, GROUP BY key, and projection is an expression evaluated by the expression machinery. Source: execExpr.c (compilation) + execExprInterp.c (the interpreter).
execExpr.c walks an Expr tree and emits a vector of ExprEvalStep opcodes:
EEOP_DONE, EEOP_INNER_FETCHSOME, EEOP_INNER_VAR, EEOP_FUNCEXPR_STRICT,
EEOP_BOOL_AND_STEP, EEOP_AGG_PLAIN_TRANS_BYREF, ...ExecInterpExpr is a giant goto-threaded dispatch loop (using GCC's computed gotos for speed) that advances through the steps. This was a major rewrite from the recursive interpreter that came before.
When the JIT layer is enabled, the same step list is also used to emit LLVM IR (src/backend/jit/llvm/llvmjit_expr.c); the JITed code replaces ExecInterpExpr for the hot expressions of long-running queries.
Plan node families
| Family | Files | What they do |
|---|---|---|
| Scan | nodeSeqscan, nodeIndexscan, nodeIndexonlyscan, nodeBitmap*, nodeFunctionscan, nodeValuesscan, nodeCtescan, nodeWorktablescan, nodeNamedtuplestorescan, nodeForeignscan, nodeCustomscan, nodeTidscan, nodeSamplescan |
Produce tuples from a relation, function, values list, etc. |
| Join | nodeNestloop, nodeHashjoin, nodeMergejoin |
Combine two child tuples into one. |
| Materialization | nodeMaterial, nodeSort, nodeIncrementalsort, nodeUnique, nodeMemoize |
Buffer / sort / dedupe child output. |
| Aggregation | nodeAgg, nodeWindowAgg, nodeGroup |
Compute aggregates and window functions. |
| Set ops | nodeSetOp, nodeAppend, nodeMergeAppend, nodeRecursiveunion |
UNION/INTERSECT/EXCEPT, append over partitions. |
| Modification | nodeModifyTable, nodeMerge |
INSERT/UPDATE/DELETE/MERGE; handle returning, triggers, partition routing. |
| Parallel | nodeGather, nodeGatherMerge |
Parallel-query roots. |
| Subplans | nodeSubplan, nodeSubqueryscan |
Execute or scan a sub-plan. |
| Limit / control | nodeLimit, nodeResult, nodeProjectSet |
LIMIT, constant rows, set-returning functions. |
Each family exposes ExecInit*, Exec*, and ExecEnd* functions plus per-node parallel-aware variants.
Executor lifecycle of a single plan
sequenceDiagram
participant Caller as PortalRun
participant Main as execMain.c
participant Init as InitPlan
participant ProcNode as Plan tree
Caller->>Main: ExecutorStart(queryDesc, eflags)
Main->>Init: InitPlan
Init->>ProcNode: ExecInitNode (recursive)
ProcNode-->>Init: PlanState tree
Caller->>Main: ExecutorRun(queryDesc, dest, count)
loop until count or EOF
Main->>ProcNode: ExecProcNode(root)
ProcNode-->>Main: TupleTableSlot
Main->>Main: dest->receiveSlot(slot)
end
Caller->>Main: ExecutorFinish (after-statement triggers)
Caller->>Main: ExecutorEnd
Main->>ProcNode: ExecEndNodeExecInitNode allocates a PlanState matching the Plan, initializes per-node state, recursively initializes children, and registers the node's ExecProcNode callback. ExecEndNode is the symmetric cleanup.
Modify operations
nodeModifyTable.c is a hub: it takes a single subplan that produces "rows to insert/update/delete" and dispatches them. For each row:
- INSERT: locate the right partition (via
execPartition.c), call the table AM'stuple_insert, run BEFORE/AFTER triggers, fire RLS checks, update indexes. - UPDATE: locate old tuple (TID from junk column), evaluate new column values, call
tuple_update, follow HOT chains as needed, update indexes for changed indexed columns. - DELETE: TID from junk column →
tuple_delete. - MERGE: per-row, evaluate which
WHEN MATCHED/WHEN NOT MATCHEDbranch applies and execute the corresponding action.
execIndexing.c handles the index side: for each affected index, generate index keys and call the index AM's aminsert (or sometimes amunique_check for deferred unique constraints).
Triggers
Trigger machinery is mostly in src/backend/commands/trigger.c but the executor calls it: BEFORE-row triggers from ExecBRInsertTriggers (and friends), AFTER triggers queued via AfterTriggerSaveEvent and fired at end-of-statement / end-of-transaction. Statement-level triggers fire from ExecutorFinish. Foreign-key checks are themselves trigger-driven.
Parallel query
Gather/GatherMerge plan nodes are roots of parallel sub-trees. At runtime:
- The leader allocates a dynamic shared memory (DSM) segment.
- The leader serializes the plan into the segment.
- The postmaster forks
parallel_workersworker backends. - Each worker re-initializes a sub-
EStatefrom the DSM, including its share of any HashJoin hash table or BitmapHeapScan bitmap. - Workers and the leader run the subtree, pushing tuples through DSM-backed
TupleQueues back to the leader. - The leader interleaves its own work with consuming the queues.
Source: nodeGather.c, nodeGatherMerge.c, execParallel.c, tqueue.c. Each parallel-aware plan node has additional callbacks for shared-state coordination (Init*ParallelDSM, Reinit*ParallelDSM, *ParallelFinish).
SPI
The Server Programming Interface (spi.c) is the C-level API that PL/pgSQL and other procedural languages use to run SQL from inside a backend. SPI takes care of building a query plan, preparing a portal, fetching results, and managing memory contexts. Custom C extensions also use SPI when they need to execute SQL.
Tuplestores and tuplesorts
Two general-purpose buffering layers used by the executor:
- Tuplestore (
src/backend/utils/sort/tuplestore.c) — buffered list of tuples, optionally rewindable. Used byMaterial,WindowAgg, set-returning functions,WITH HOLDcursors. - Tuplesort (
src/backend/utils/sort/tuplesort.c) — external sort with multiple algorithms (quicksort, "polyphase" merge, replacement selection in older versions). Used bySort, hash-aggregate spill, btree builds, CLUSTER, parallel sort.
Both spill to disk when they exceed work_mem.
EXPLAIN
EXPLAIN, EXPLAIN ANALYZE, and EXPLAIN (FORMAT JSON) all share the same backbone in src/backend/commands/explain.c. ANALYZE actually runs the query and uses instrument.c to record per-node timing and row counts; everything else just walks the PlannedStmt and PlanState trees and prints them.
Entry points for modification
- New executor node: define in
nodes/execnodes.h(state) andnodes/plannodes.h(plan), implementExecInitX,ExecX,ExecEndXinnodeX.c, and add to the dispatch table inexecProcnode.c. - New expression operator: add a step opcode to
execExprInterp.c, emit it fromexecExpr.c. JIT support optional but expected for hot operators. - Parallel-aware existing node: implement the parallel-state callbacks. The
Appendnode is a good reference for partition-aware parallel execution.
For how plans get here, see Planner. For how triggers and DDL hook into execution, see src/backend/commands/.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.