Open-Source Wikis

/

PostgreSQL

/

Systems

/

Executor

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 destination

Execution 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:

  1. Sets up an EState (executor state) and per-tuple memory context.
  2. Calls InitPlan to walk the plan tree and produce a parallel PlanState tree.
  3. Loops calling ExecProcNode on the root, sending each returned slot to the configured destination (DestReceiver).
  4. Calls ExecEndPlan to 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: ExecEndNode

ExecInitNode 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's tuple_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 MATCHED branch 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:

  1. The leader allocates a dynamic shared memory (DSM) segment.
  2. The leader serializes the plan into the segment.
  3. The postmaster forks parallel_workers worker backends.
  4. Each worker re-initializes a sub-EState from the DSM, including its share of any HashJoin hash table or BitmapHeapScan bitmap.
  5. Workers and the leader run the subtree, pushing tuples through DSM-backed TupleQueues back to the leader.
  6. 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 by Material, WindowAgg, set-returning functions, WITH HOLD cursors.
  • Tuplesort (src/backend/utils/sort/tuplesort.c) — external sort with multiple algorithms (quicksort, "polyphase" merge, replacement selection in older versions). Used by Sort, 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) and nodes/plannodes.h (plan), implement ExecInitX, ExecX, ExecEndX in nodeX.c, and add to the dispatch table in execProcnode.c.
  • New expression operator: add a step opcode to execExprInterp.c, emit it from execExpr.c. JIT support optional but expected for hot operators.
  • Parallel-aware existing node: implement the parallel-state callbacks. The Append node 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.

Executor – PostgreSQL wiki | Factory