agentsclimarketplace

Parser and nodes

Skill matejformanek/postgres-claude/.claude/skills/parser-and-nodes

Turn Claude Code into a long-term collaborator on PostgreSQL internals — cited knowledge corpus, agent skills, slash commands, and task-shaped scenarios for backend hacking.

Install
npx -y skills add matejformanek/postgres-claude --skill parser-and-nodes

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Hack the PostgreSQL parser or add a new Node type — covers scan.l flex tokenizer, gram.y bison grammar, parse_analyze in analyze.c, kwlist.h keywords, and adding/modifying Node types in parsenodes.h / primnodes.h / plannodes.h that flow through copy/equal/out/read funcs auto-generated by gen_node_support.pl. Spans the AST→Query→Plan three-tree pipeline, mutator/walker conventions (expression_tree_walker, query_tree_mutator), the node-type reference table, List/lappend/foreach idioms, and shift-reduce conflict resolution. Use whenever a PG patch edits src/backend/parser/**, edits src/include/nodes/*.h, adds a new SQL keyword to kwlist.h, extends a parse-tree node, writes a tree mutator/walker, or resolves a gram.y shift-reduce conflict. Skip for non-PG parsers (ANTLR, Babel, nom, tree-sitter, LALRPOP, Yacc/Bison on other projects, LLVM IR / Clang AST), JSON / XML / YAML parsing, and executor-node additions (use executor-and-planner instead).

SKILL.md

16.9 KB, ~4.2k tokens by cl100k_base, as published. Nobody here has run it

Parser & Node-types — operational

Companion docs: knowledge/idioms/parser-pipeline.md, knowledge/idioms/node-types-and-lists.md.

0. The three-tree pipeline — AST → Query → Plan

Every SQL statement flows through three distinct tree shapes; nodes in each header live or die at a specific stage. Confuse the stages and your patch will silently fail in a downstream walker.

SQL text
  │
  │  gram.y / scan.l / kwlist.h
  ▼
1. Raw parsetree (AST)         — parsenodes.h utility-stmt nodes
   Examples: SelectStmt,         + A_Expr / A_Const / ColumnRef /
   InsertStmt, A_Expr,             FuncCall (raw expression shapes)
   ColumnRef, FuncCall          → walker: raw_expression_tree_walker
                                  No types resolved, no relations
                                  looked up, no operators resolved.
  │
  │  parser/analyze.c           → transformStmt → transform<Foo>Stmt
  │  parser/parse_*.c           → adds rangetable, resolves names,
  │                               picks operator + cast OIDs,
  │                               applies coercions
  ▼
2. Query tree                  — parsenodes.h Query + analyzed
   Examples: Query, RangeTblEntry, expression nodes (primnodes.h)
   Var, OpExpr, Const, FuncExpr,  Examples: Var, OpExpr, Const,
   TargetEntry, FromExpr, JoinExpr  FuncExpr, Aggref, WindowFunc
                                → walker: expression_tree_walker
                                  + query_tree_walker
                                  + QTW_* flags for rtable/CTE descent
  │
  │  rewrite/                  → rules, views (Query → Query)
  │  optimizer/planner.c        → planner: Query → Plan
  ▼
3. Plan tree                   — plannodes.h
   Examples: SeqScan,            Examples: SeqScan, IndexScan, HashJoin,
   IndexScan, HashJoin,            Sort, Agg, Limit, Gather, GatherMerge
   Sort, Agg, Limit             → walker: plan_tree_walker
                                + planstate_tree_walker (executor-time)
                                  Shipped to parallel workers via
                                  nodeToString / stringToNode.

Operational rules per shape:

ShapeKey invariants
Raw parsetreeNo catalog lookups (parser runs without an open transaction in some paths); A_* and ColumnRef are the pre-resolution placeholders. Don't try to compute types here.
Query treeTypes, collations, operator OIDs, cast OIDs resolved. Var carries varno+varattno into the rangetable. Subqueries are nested Querys in RangeTblEntry. Stored in pg_rewrite.ev_action for views/rules — bump catversion when changing serialized fields.
Plan treeMaterialized from Paths by createplan.c. Plan's expression trees use Vars indexed into targetlist slots, NOT rangetable slots. Shipped to workers via the out/read funcs — bumps to plan-node fields don't need catversion (not stored on disk) but DO need correct out/read coverage.

The executor-and-planner skill covers the Plan-tree side (createplan, add_path costing, ExecInit/ProcNode dispatch). This skill covers raw parsetree and Query tree work, plus the node-machinery infrastructure (gen_node_support.pl, copy/equal/out/read funcs).

Tools for working with an existing tree

  • copyObject(p) — deep copy (preserves argument type via typeof_unqual macro, nodes.h:228-233). Dispatcher copyObjectImpl in copyfuncs.c:177-212, with check_stack_depth() guard (:185). By-ref Datums go through _copyConstdatumCopy; T_List is deep-copied via list_copy_deep, T_IntList/T_OidList/T_XidList shallow via list_copy. Extensible nodes dispatch to a registered nodeCopy callback (_copyExtensibleNode). Allocated in CurrentMemoryContext — caller controls the lifetime.
  • equal(a, b) — deep structural compare (equalfuncs.c).
  • nodeToString(p) / stringToNode(s) — Lisp-ish text serialization; load-bearing for plan cache, rule storage (pg_rewrite.ev_action), parallel-worker plan shipping. The round-trip copyObject(stringToNode(nodeToString(p))) underpins the debug_write_read_parse_plan_trees GUC.
  • expression_tree_walker / _mutator — polymorphic recursion over expression trees (post-analysis: Var, OpExpr, …). raw_expression_tree_walker for the pre-analysis shape (A_Expr, ColumnRef). query_tree_walker / _mutator wrap for Query, with QTW_* flags (nodeFuncs.h:22-34) to control rtable/CTE descent. planstate_tree_walker is the executor-time analogue. Mutator contract: return NULL to delete a subtree; walker contract: callback returns true to short-circuit, false to keep descending. The macro wrappers (nodeFuncs.h:155-183) let callbacks be typed without -Wincompatible-pointer-types warnings.

When you're adding a new SQL statement

Touch four layers, in order. Build between each so you fail fast.

  1. Grammar — src/backend/parser/gram.y

    • Add the keyword(s) to unreserved_keyword / col_name_keyword / etc. AND to src/include/parser/kwlist.h (alphabetical, with category).
    • Add a top-level rule producing your new *Stmt node, wire it into stmt: (search for stmt: in gram.y). Use makeNode(FooStmt) in the action, set the location with @1, fill fields from $N.
    • Match an existing simple statement as template (e.g. DropStmt, VariableSetStmt). Do NOT invent fresh helpers in gram.y if parser/parse_utilcmd.c already has one.
  2. Lexer — src/backend/parser/scan.l (only if you introduced a new token shape; new keywords alone do not need scan.l changes — they go through kwlist.h).

  3. Parse node — src/include/nodes/parsenodes.h (utility stmts) or primnodes.h (expressions) or plannodes.h (plan nodes)

    • First field NodeTag type; (or another node struct, for "inheritance").
    • Last field for top-level statements is typically ParseLoc location;.
    • Annotate fields if needed: pg_node_attr(...) on the struct, per-field attrs like query_jumble_ignore, equal_ignore. See nodes.h:43-125 for the full list.
  4. Analyze / execute

    • Optimizable statement → add a transformFooStmt(ParseState *, FooStmt *) in parser/analyze.c and wire it into the switch (nodeTag(parseTree)) inside transformStmt (analyze.c:334-451, switch at :368). Caution (analyze.c:363-367): any change to that switch must also be reflected in stmt_requires_parse_analysis() (:469-505) and analyze_requires_snapshot() (:513-529) — three sites, one logical change.
    • Utility statement → no transform needed; just dump into a Query with CMD_UTILITY (the default path). Execution lives in src/backend/commands/ via ProcessUtility / standard_ProcessUtility (src/backend/tcop/utility.c).
  5. Run gen_node_support.pl (done automatically by the build) and verify the generated copyfuncs.funcs.c, equalfuncs.funcs.c, outfuncs.funcs.c, readfuncs.funcs.c chunks for your new struct look sane.

  6. Add a regression test under src/test/regress/sql/ (and a matching expected file). See .claude/skills/testing/SKILL.md.

When you're adding a new Node type (no SQL change)

From src/backend/nodes/README "Steps to Add a Node":

  1. Put the struct in the right header. The file must be in gen_node_support.pl's @all_input_files list (parsenodes.h, primnodes.h, plannodes.h, pathnodes.h, execnodes.h, value.h, etc. — see gen_node_support.pl:53-77). The T_Foo tag is generated automatically into nodes/nodetags.h (do NOT hand-edit it).
  2. First field is NodeTag type; — unless you "inherit" by embedding another node struct as the first field (e.g. Plan plan; in a new plan node).
  3. Build. Inspect the generated copyfuncs.funcs.c / equalfuncs.funcs.c / outfuncs.funcs.c / readfuncs.funcs.c / queryjumblefuncs.funcs.c entries for your node. Add pg_node_attr(...) and per-field attrs to fix anything wrong.
  4. If a field needs special treatment, common annotations:
    • pg_node_attr(custom_copy_equal) — write your own bodies in copyfuncs.c / equalfuncs.c and the generator will skip yours.
    • pg_node_attr(no_copy_equal, no_read, no_query_jumble) — opt out entirely (executor state nodes typically use nodetag_only).
    • Per-field: array_size(otherfield), copy_as(VALUE), equal_ignore, read_write_ignore, query_jumble_ignore, query_jumble_location.
  5. If your node is an expression (lives in primnodes.h or has child Expr fields), add cases to every giant switch in nodeFuncs.c:
    • exprType (:42+), exprTypmod (:304+), exprCollation / exprSetCollation / exprInputCollation (:826+, :1140+, :1092+), exprLocation (:1403+) — type/typmod/collation/location introspection.
    • expression_tree_walker_impl (:2111+), expression_tree_mutator_impl (:3018+) — recursion into children.
    • raw_expression_tree_walker_impl (:4115+) — only if the node can appear in raw parsetrees.
    • set_opfuncid family (:1890+) — only if your node holds an operator OID needing resolution. Grep T_<SiblingNode> (e.g. T_OpExpr) to find every site; five-to-eight maintenance points per node, miss one and walkers silently misbehave.
  6. Adding a node type renumbers existing tags. Recompile the whole tree (--enable-depend helps). No initdb needed — node numbers never go to disk. BUT if the node can appear in a stored parse tree (rule actions, view definitions), bump CATALOG_VERSION_NO in src/include/catalog/catversion.h.
  7. Test with debug_copy_parse_plan_trees=on, debug_write_read_parse_plan_trees=on, debug_raw_expression_coverage_test=on (set via PG_TEST_INITDB_EXTRA_OPTS).

When you're modifying an existing Node type

  • Adding a field to a node that appears in stored catalog parse trees (e.g. Query, anything in views / rules) → bump catversion. Add a pg_node_attr if the new field needs special read/write handling (e.g. read_as(...) for backward-compat when reading old serialized trees from extensions — usually not needed because catversion changes invalidate stored trees).
  • Removing or reordering fields → also catversion bump if stored.
  • Changing only Plan/Path internal fields (never serialized to catalogs) → no catversion bump needed. But Plan nodes ARE serialized to parallel workers, so you still need correct out/read funcs.

Pre-submit smoke

cd dev/build-debug && ninja && \
  meson test --suite regress -q

Plus the debug-tree GUCs above on a separate initdb if you touched anything tree-shape related.

Node-type reference table

The major node families and where each lives. When in doubt about which header your new field belongs in, check this table.

HeaderShapeExamplesWhen you'd add here
nodes/parsenodes.hRaw + QuerySelectStmt, InsertStmt, A_Expr, ColumnRef, Query, RangeTblEntry, TargetEntry, FromExpr, JoinExprNew SQL statement; new fields on Query (catversion bump if serialized in pg_rewrite)
nodes/primnodes.hExpression (analyzed)Var, Const, OpExpr, FuncExpr, Aggref, WindowFunc, SubLink, CaseExpr, CoalesceExprNew expression node (must touch every nodeFuncs.c switch — see §"new Node type")
nodes/plannodes.hPlanPlan, SeqScan, IndexScan, HashJoin, Sort, Agg, Limit, Gather, Append, MergeAppendNew plan node (also wire executor under nodeXxx.c — see executor-and-planner)
nodes/pathnodes.hPath + RelOptInfoPath, IndexPath, JoinPath, RelOptInfo, PlannerInfoNew path shape considered by the optimizer
nodes/execnodes.hPlanState (runtime)PlanState, SeqScanState, EState, ExprContext, ProjectionInfoNew runtime state for an executor node
nodes/value.hLeaf valuesInteger, Float, Boolean, String, BitStringAlmost never — the four primitive value carriers
nodes/pg_list.hListsList, IntList, OidList, XidListAlmost never (collection infrastructure)
nodes/extensible.hExtension nodesExtensibleNode, CustomScanCustom AM / custom planner extension

nodes/nodetags.h is auto-generated by gen_node_support.pl; never hand-edit it. A new struct in any of the headers above (with the file listed in gen_node_support.pl @all_input_files) gets its T_Foo tag automatically.

Walker / mutator quick reference

FunctionWhereUse for
raw_expression_tree_walkernodes/nodeFuncs.cPre-analysis (A_Expr, ColumnRef, FuncCall); used by parser/parse_*.c
expression_tree_walkernodes/nodeFuncs.cAnalyzed expressions in a Query tree
expression_tree_mutatornodes/nodeFuncs.cSame shape, but returns new nodes; rewriter / planner use this
query_tree_walkernodes/nodeFuncs.cWraps expression_tree_walker to also walk Query subexpressions; controlled by QTW_* flags
query_tree_mutatornodes/nodeFuncs.cMutator counterpart
range_table_walker / _mutatornodes/nodeFuncs.cJust the rangetable, with per-RTE field control flags
plan_tree_walkernodes/nodeFuncs.cPlan-tree (plannodes.h) recursion
planstate_tree_walkerexecutor/execProcnode.cExecutor-time (PlanState) recursion; used by EXPLAIN

Conventions to remember:

  • Walker callback returns bool. Return true to short-circuit the whole walk; return false to continue descending.
  • Mutator callback returns Node *. Return NULL to delete a subtree; return the input pointer to keep it unchanged; return a new node to replace.
  • QTW_* flags (nodeFuncs.h:22-34) control rtable / CTE descent. The default skips RTE subqueries — opt in with QTW_EXAMINE_RTES_BEFORE / _AFTER when you actually want them.
  • The typed macro wrappers (nodeFuncs.h:155-183) let your callback be typed (bool foo_walker(Node *n, MyCtx *ctx)) without -Wincompatible-pointer-types warnings.

Files-examined rows

filedepthproduced
src/backend/parser/READMEfullSKILL §"new SQL statement"
src/backend/parser/gram.y lines 13625-13706scanned + 1 ruleSKILL §grammar
src/backend/parser/scan.l 1-40topSKILL §lexer
src/backend/parser/parse_node.c 1-50topSKILL §analyze
src/backend/parser/analyze.c 1-80, 334-433transformStmt full switchSKILL §analyze
src/backend/nodes/READMEfullSKILL §"new Node"
src/include/nodes/nodes.hfullSKILL §annotations
src/include/nodes/pg_list.h 1-120, 380-485List API + foreachidioms
src/include/nodes/value.hfullidioms
src/backend/nodes/gen_node_support.pl 1-140top + file listSKILL §generator

Cross-references

  • .claude/skills/executor-and-planner/SKILL.md — Plan-tree side of the three-tree pipeline; createplan.c, ExecInitNode/ExecProcNode for new plan nodes.
  • .claude/skills/catalog-conventions/SKILL.mdCATALOG_VERSION_NO bump rules when serialized parse-tree fields change.
  • .claude/skills/testing/SKILL.mddebug_copy_parse_plan_trees, debug_write_read_parse_plan_trees, debug_raw_expression_coverage_test GUCs for tree-shape regressions.
  • .claude/skills/coding-style/SKILL.md — node-header style, pg_node_attr placement, T_Foo enum convention.
  • .claude/skills/fmgr-and-spi/SKILL.md — function-call node (FuncExpr) interaction with fmgr; SPI's parse-tree consumption.
  • knowledge/idioms/parser-pipeline.md — long-form pipeline narrative.
  • knowledge/idioms/node-types-and-lists.mdList / foreach patterns + node-tag invariants.
  • source/src/backend/nodes/README — canonical "Steps to Add a Node" reference.
  • source/src/backend/parser/README — gram.y conventions, shift/reduce conflict resolution.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 327,069. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.