PostgreSQL DDL & Schema — Section Overview
Contents:
- What this section covers
- The pipeline: ProcessUtility and its two escapees
- Reading order
- Detail-doc summaries
- Adjacent sections
What this section covers
Section titled “What this section covers”This subcategory is the schema-change machinery: the code that runs when
a statement defines or alters database objects rather than reading or
writing rows. The organizing fact is the dispatch boundary. A query that the
planner can optimize (SELECT, INSERT, UPDATE, DELETE, MERGE) goes
down the query pipeline; everything else — CREATE TABLE, ALTER TABLE,
CREATE INDEX, CREATE TRIGGER, CREATE POLICY, COPY, REFRESH MATERIALIZED VIEW, and the rest of the utility-statement set — is handed to
ProcessUtility (tcop/utility.c), which is where this section begins.
Concretely, the docs here cover:
- DDL execution and dispatch —
ProcessUtility→standard_ProcessUtility→ProcessUtilitySlow, the table-creation path (DefineRelation→heap_create_with_catalog), and thedefine.chelpers. This is the router doc for the whole section. ALTER TABLE— the multi-pass rewrite engine intablecmds.c(AlterTable→ theAT*phase walk), the single largest command file in the tree.- Index creation —
DefineIndex(indexcmds.c) and the catalog writerindex_create(catalog/index.c), including theCONCURRENTLYmulti-transaction build. - Constraints — how
CHECK,NOT NULL, primary-key, unique, foreign-key, and exclusion constraints are stored (pg_constraint,heap.c’sStoreConstraints/AddRelationNewConstraints) and enforced. - Triggers and event triggers — row/statement triggers
(
commands/trigger.c, fired from the executor) and the separate command-level event-trigger mechanism (commands/event_trigger.c). - Declarative partitioning — partition bounds and descriptors
(
partitioning/,catalog/partition.c) plus the executor-side tuple routing and pruning seams. COPY— utility-dispatched bulk load/unload (copyfrom.c/copyto.c).- Materialized views —
CREATE/REFRESH MATERIALIZED VIEW(matview.c,createas.c). - Row-level security —
CREATE POLICYstorage (commands/policy.c) and the query-rewrite-time enforcement (rewrite/rowsecurity.c).
The sharp boundaries — what is not here and who owns it:
- The catalog as data structure belongs to system-catalog:
pg_class/pg_attribute/pg_indexlayout, the relcache/catcache, and thesinvalinvalidation a committed DDL emits all live inpostgres-overview-system-catalog.md. This section writes catalog rows; that section explains what they are and how every backend stays coherent. - The rewriter and planner belong to query-processing. RLS appears
in both: this section owns
CREATE POLICYand the policy semantics, but the rewrite-phase injection point (fireRIRrules→get_row_security_policies) lives at the rewriter seam documented inpostgres-overview-query-processing.md. - Trigger and partition-routing execution is shared with
query-processing: this section defines triggers and partitions;
the executor fires triggers (
AfterTriggerBeginQuery) and routes tuples (ExecFindPartition) at DML time. VACUUM/CLUSTER/ autovacuum are maintenance, not schema change — they live in txn-recovery, even though they share thecommands/directory.CREATE EXTENSION, foreign tables, and PLs are extensibility; publications/subscriptions are replication-ha. They are utility statements dispatched by the sameProcessUtility, but their mechanism belongs to those sections — this section only notes the shared front door.
The pipeline: ProcessUtility and its two escapees
Section titled “The pipeline: ProcessUtility and its two escapees”The structural through-line is one dispatch function with two members that
deliberately do not go through it. standard_ProcessUtility handles the
self-contained statements inline (a big switch) and forwards the
catalog-heavy ones to ProcessUtilitySlow, which calls the per-command
commands/*.c modules; those in turn call the catalog writers under
catalog/ and the bound machinery under partitioning/. Row-level security
enforcement and partition tuple routing are the two escapees — they fire in
the rewriter and the executor respectively, not at utility dispatch.
flowchart TB
PORTAL["portal / PortalRunUtility<br/>(query-processing)"] --> PU["ProcessUtility / standard_ProcessUtility<br/>postgres-ddl-execution.md"]
PU -->|simple, inline| SIMPLE["DROP, RENAME, COPY dispatch,<br/>REFRESH dispatch, ..."]
PU -->|catalog-heavy| SLOW["ProcessUtilitySlow"]
SLOW --> CRT["DefineRelation (CREATE TABLE)<br/>postgres-ddl-execution.md"]
SLOW --> ALT["AlterTable / AT-phase walk<br/>postgres-alter-table.md"]
SLOW --> IDX["DefineIndex<br/>postgres-index-creation.md"]
SLOW --> CON["constraint definition<br/>postgres-constraints.md"]
SLOW --> TRG["CreateTrigger<br/>postgres-triggers.md"]
SLOW --> EVT["event triggers<br/>postgres-event-triggers.md"]
SLOW --> POL["CREATE POLICY storage<br/>postgres-row-level-security.md"]
SLOW --> MV["CREATE MATERIALIZED VIEW<br/>postgres-matview.md"]
SIMPLE --> CP["DoCopy<br/>postgres-copy.md"]
SIMPLE --> MVR["REFRESH MATERIALIZED VIEW<br/>postgres-matview.md"]
CRT --> CAT["catalog writers<br/>heap.c / index.c / partition.c"]
ALT --> CAT
IDX --> CAT
CON --> CAT
POL --> CAT
CAT --> PART["partition bounds / descriptors<br/>postgres-partitioning.md"]
subgraph ESCAPE["the two escapees — NOT via ProcessUtility"]
direction TB
RLS["RLS enforcement<br/>get_row_security_policies @ rewrite time<br/>postgres-row-level-security.md"]
ROUTE["partition tuple routing + pruning<br/>ExecFindPartition @ execute time<br/>postgres-partitioning.md"]
end
REWRITE["rewriter (query-processing)"] --> RLS
EXECDML["executor on INSERT/UPDATE (query-processing)"] --> ROUTE
EXECDML --> TRGFIRE["trigger firing<br/>postgres-triggers.md"]
CAT -. "emits sinval on commit" .-> SYSCAT["system-catalog section"]
Three things to read off the diagram:
ProcessUtilitySlowis the real DDL workhorse. The split exists because the catalog-mutating commands need aParseState, table-open ordering, and event-trigger fences that the trivial statements do not.postgres-ddl-execution.mdowns that split.- The catalog writers are shared infrastructure.
heap.c(heap_create_with_catalog,StoreConstraints),index.c(index_create), andpartition.care called by many of the command modules. The module docs name the entry they call; the row layouts those writers produce are explained in the system-catalog section. - The escapees are the section’s headline subtlety. A reader who assumes “all schema features run at DDL time” will be wrong twice: RLS policies are defined by DDL but applied by the rewriter on every query against the table, and partition membership is declared by DDL but resolved per-row by the executor. Both module docs lead with that.
Reading order
Section titled “Reading order”Cross-referenced-first — read the dispatch spine before the leaves:
postgres-ddl-execution.md— theProcessUtilitydispatch model andCREATE TABLE. Every other doc in the section assumes this entry point.postgres-alter-table.md— the AT-phase rewrite engine; the most intricate command and the one that exercises the most catalog surface.postgres-index-creation.mdandpostgres-constraints.md— read as a pair; constraints are frequently backed by indexes, and both write into the same catalog path.postgres-triggers.mdthenpostgres-event-triggers.md— the row/statement model first, then the command-level model that reuses the name but not the mechanism.postgres-partitioning.md— pulls together catalog (bounds), planner (pruning), and executor (routing); easier once triggers and constraints are understood, since partitions interact with both.postgres-row-level-security.md— read after the query-processing rewriter overview, since RLS is a rewrite-time mechanism.postgres-copy.mdandpostgres-matview.md— standalone leaves; read last, in any order.
Detail-doc summaries
Section titled “Detail-doc summaries”Forward references; these module docs may not exist yet.
| Doc | One-line scope |
|---|---|
postgres-ddl-execution.md | The ProcessUtility → standard_ProcessUtility → ProcessUtilitySlow dispatch, the simple/slow split, and the CREATE TABLE path (DefineRelation → heap_create_with_catalog); the router for the whole section. |
postgres-alter-table.md | The multi-phase ALTER TABLE engine in tablecmds.c — the AlterTable prep/exec/rewrite passes (AT* subcommands), table rewrites, and recursion to children. |
postgres-index-creation.md | DefineIndex and index_create, opclass/collation resolution, the CONCURRENTLY multi-transaction build, and REINDEX. |
postgres-constraints.md | How CHECK, NOT NULL, primary-key, unique, foreign-key, and exclusion constraints are stored in pg_constraint and enforced (triggers for FKs, indexes for PK/unique). |
postgres-triggers.md | Row- and statement-level trigger definition (CreateTrigger) and the executor-time firing machinery (AfterTrigger* queue, transition tables). |
postgres-event-triggers.md | Command-level event triggers (ddl_command_start/end, sql_drop, table_rewrite) — a distinct mechanism from row triggers that fences around ProcessUtility. |
postgres-partitioning.md | Declarative partitioning end-to-end: bounds (partbounds.c), descriptors (partdesc.c), planner pruning (partprune.c), and executor tuple routing (ExecFindPartition). |
postgres-row-level-security.md | CREATE POLICY storage (commands/policy.c) and the query-rewrite-time enforcement (get_row_security_policies, called from rewriteHandler.c) — a rewriter mechanism, not a DDL-time one. |
postgres-copy.md | COPY bulk load/unload: DoCopy dispatch, the copyfrom.c/copyto.c paths, multi-insert batching, and partition routing during COPY ... FROM. |
postgres-matview.md | Materialized views: CREATE MATERIALIZED VIEW (shared with createas.c) and REFRESH MATERIALIZED VIEW (ExecRefreshMatView), including the concurrent-refresh diff. |
Adjacent sections
Section titled “Adjacent sections”postgres-overview-system-catalog.md— the closest neighbor. This section writes catalog rows; that section explains thepg_*layout the writers target, the relcache/catcache that read them, and thesinvalinvalidation a committed DDL emits. Read them as a write-side / read-side pair.postgres-overview-query-processing.md— owns the rewriter seam where RLS injects (get_row_security_policies), the planner pruning that partitioning relies on, and the executor that fires triggers and routes partition tuples. Three of this section’s docs hand mechanism across this border.postgres-overview-txn-recovery.md—VACUUM/CLUSTER/autovacuum share thecommands/directory but are maintenance, not schema change, and live there; also the owner of the transactional guarantees every DDL statement runs under (DDL in PostgreSQL is transactional and WAL-logged).postgres-overview-extensibility.mdandpostgres-overview-replication-ha.md— also reached throughProcessUtility(CREATE EXTENSION, foreign tables, PLs; publications/ subscriptions), but their mechanism lives there. This section only owns the shared dispatch front door.