postgres/postgres
Parser
The parser turns SQL text into a Query tree. It runs in two stages: a bison/flex grammar produces a raw parse tree, and parse analysis resolves names against the catalog and assigns types. Source: src/backend/parser/.
Directory layout
src/backend/parser/
├── analyze.c # parse analysis: top-level dispatch
├── gram.y # bison grammar (~18 KLOC, the largest file in the tree)
├── kwlist.h # keyword list
├── parse_agg.c # aggregate function checks
├── parse_clause.c # FROM, WHERE, GROUP BY, ORDER BY, LIMIT
├── parse_coerce.c # type coercion rules
├── parse_collate.c # collation assignment
├── parse_cte.c # WITH (Common Table Expressions)
├── parse_expr.c # expression analysis (the bulk of the work)
├── parse_func.c # function call resolution
├── parse_jsontable.c # JSON_TABLE
├── parse_merge.c # MERGE
├── parse_node.c # node-construction helpers
├── parse_oper.c # operator resolution
├── parse_param.c # bind parameters ($1, $2, ...)
├── parse_relation.c # range table, schema-qualified names
├── parse_target.c # SELECT target list, INSERT VALUES
├── parse_type.c # type-name resolution
├── parse_utilcmd.c # DDL: CREATE TABLE, CREATE INDEX, etc.
├── parser.c # entry: raw_parser, parse_analyze
├── scan.l # flex scanner
├── scansup.c # string handling helpers
└── parse_*.c # ... a few moreStage 1: raw parsing
raw_parser (in parser.c) calls into the bison-generated parser. The grammar is gram.y and the scanner is scan.l.
graph LR
SQL[SQL string] --> Scanner["scan.l (flex)"]
Scanner --> Grammar["gram.y (bison)"]
Grammar --> RawTree[RawStmt list of Node*]The output is a list of RawStmt nodes, each wrapping a SelectStmt, InsertStmt, CreateStmt, etc. Nothing has been semantically analyzed yet — names like foo.bar could be a column reference, a table-qualified column, or a record field; the raw tree just records the syntactic shape.
The grammar handles all SQL PostgreSQL recognizes: SELECT (with the full SQL standard plus extensions), INSERT/UPDATE/DELETE/MERGE, every DDL command, transactional commands, EXPLAIN, etc. The grammar file is by far the largest single source file in the tree at about 18 KLOC; it is regenerated by bison on build.
Adding a new keyword:
- Add a token in
gram.y(%token). - Add it to
kwlist.hwith the appropriate category. - Use it in production rules.
- Run
src/tools/check_keywords.plto verify the keyword list and grammar agree.
Adding a new statement:
- Define a new
Nodetype insrc/include/nodes/parsenodes.h. - Add grammar rules producing the new node.
- Add a parse-analysis function (often in
parse_utilcmd.cfor DDL). - Add executor or utility-command code to actually run it.
Keywords
Keywords are partitioned into four reservation classes (in kwlist.h):
UNRESERVED_KEYWORD— usable as identifiers in most contexts.COL_NAME_KEYWORD— usable as column names but not table aliases.TYPE_FUNC_NAME_KEYWORD— usable as type or function names.RESERVED_KEYWORD— never usable as an identifier.
Adding a reserved keyword breaks user code. The community is conservative — most new names go in as unreserved or column-name keywords.
Stage 2: parse analysis
parse_analyze (in analyze.c) takes a RawStmt and produces a fully typed Query. It dispatches based on statement type:
Query *
parse_analyze(RawStmt *parseTree, const char *sourceText, ...)
{
ParseState *pstate = make_parsestate(NULL);
/* configure pstate->p_sourcetext, etc. */
Query *query = transformTopLevelStmt(pstate, parseTree);
free_parsestate(pstate);
return query;
}Subroutines do the real work:
| Function | Purpose |
|---|---|
transformSelectStmt |
Analyze a SELECT: range table, target list, WHERE, GROUP BY, etc. |
transformInsertStmt |
Analyze INSERT, including VALUES and INSERT...SELECT. |
transformUpdateStmt, transformDeleteStmt, transformMergeStmt |
Mutating statements. |
transformCreateStmt |
CREATE TABLE; resolves types, expands LIKE. |
transformExpr |
Recursive expression analysis (in parse_expr.c). |
transformFromClause |
FROM list: tables, joins, subqueries, lateral. |
ParseState carries the surrounding context: the range table being built, parent parse states (for subqueries), LATERAL flag, hooks for plug-in resolution.
Range table
The output Query carries a range table (rtable) — a list of RangeTblEntry (RTE) structs, one per relation, subquery, function, values list, or CTE referenced in FROM. Range table entries are 1-indexed in Query nodes; the index is called the RTE index and shows up everywhere downstream.
Type coercion
The most subtle part of parse analysis is resolving the types of expressions. PostgreSQL has a complex but principled coercion system:
- Implicit casts between binary-coercible types (e.g.,
textandvarchar). - Assignment casts in INSERT/UPDATE target lists.
- Explicit casts via
CAST(x AS type)orx::type.
Functions in parse_coerce.c and parse_type.c make these decisions. They consult pg_cast and pg_type for cast and type information.
Function and operator resolution
Overloading: PostgreSQL allows multiple functions and operators with the same name distinguished by argument types. Resolving a call goes through func_select_candidate (in parse_func.c) and oper_select_candidate (in parse_oper.c), which apply a documented multi-step disambiguation procedure (exact match → preferred-type match → ...).
Aggregates and window functions
parse_agg.c walks the post-analysis query and verifies that aggregate and window function calls obey their structural rules: aggregates can't appear in WHERE, GROUP BY columns must cover all non-aggregate target list columns, etc. It also classifies the query as a "grouping query" if any aggregate is present.
CTEs and subqueries
WITH clauses are handled in parse_cte.c. CTEs add to the parse state's CTE list; references in subsequent parts of the query resolve to those entries. CTEs can be recursive; parse_cte.c validates the recursion structure.
Subqueries are recursively analyzed via transformSelectStmt with a child ParseState whose parentParseState is the outer one — that's what enables outer-query references and LATERAL.
Output
The result of parse analysis is a Query (src/include/nodes/parsenodes.h):
typedef struct Query
{
NodeTag type;
CmdType commandType; // CMD_SELECT, CMD_INSERT, ...
QuerySource querySource; // QSRC_ORIGINAL, QSRC_PARSER, QSRC_INSTEAD_RULE, ...
List *rtable; // range table
FromExpr *jointree; // FROM clause as a tree
List *targetList; // SELECT target list / SET clauses
Node *havingQual;
List *groupClause;
List *windowClause;
List *sortClause;
Node *limitOffset;
Node *limitCount;
/* ... many more fields ... */
} Query;This Query flows next into the rewriter (src/backend/rewrite/), which applies view rules and INSTEAD OF triggers, then into the planner (Planner).
Utility statements
Statements that aren't queries — DDL, transaction control, COPY, EXPLAIN — return a Query of command type CMD_UTILITY whose utilityStmt field carries the original parse-tree node. The planner is essentially a pass-through for these; the executor's ProcessUtility path dispatches them.
Entry points for modification
- New SQL syntax: edit
gram.y, parsenodes, parse-analysis transforms; update keyword list if you added a keyword. - New built-in function or operator: usually no parser change needed — register it in
pg_proc.dat/pg_operator.datand the existing resolution code finds it. - New cast:
pg_cast.datplus an implementation function. Implicit casts are powerful — they affect resolution everywhere — and the project is conservative about adding new ones.
For what happens after parse analysis, see Planner. For the rule-based view rewriter, look at src/backend/rewrite/rewriteHandler.c.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.