Skip to content

PostgreSQL Node 数据结构详解

摘要

PostgreSQL 使用一种优雅而强大的树形节点系统来表示 SQL 语句、查询计划、表达式等所有内部数据结构。本文基于 PostgreSQL 源码,详细剖析其核心 Node 系统的设计原理、数据结构、操作函数和使用模式,帮助读者深入理解 PostgreSQL 的内部表示机制。


1. Node 系统概述

1.1 设计原则

PostgreSQL 的 Node 系统遵循一个简单而强大的设计原则:

每种节点的第一个字段必定是 NodeTag 类型标识符

c
// src/include/nodes/nodes.h
typedef struct Node
{
    NodeTag     type;           // 节点类型标识符
} Node;

这个设计使得:

  • 任意节点都可以通过强制转换为 Node* 来识别类型
  • IsA() 宏可以安全地检查任意节点类型
  • 节点树可以统一遍历和处理

1.2 Node 类型分类

类别示例说明
表达式节点Const, Var, FuncExpr表示表达式
查询节点Query, SelectStmt表示完整查询
计划节点SeqScan, HashJoin, Sort执行计划
路径节点IndexPath, NestPath优化器内部路径
列表节点List, IntList容器类型
值节点Integer, String, Float原始数据类型

2. NodeTag 枚举详解

2.1 NodeTag 定义

NodeTag 是一个枚举类型,定义了所有可能的节点类型:

c
// src/include/nodes/nodetags.h (自动生成)
typedef enum NodeTag
{
    T_Invalid = 0,
    
    /* 列表和别名节点 */
    T_List = 1,
    T_Alias = 2,
    T_RangeVar = 3,
    
    /* 表达式节点 (5-61) */
    T_Var = 5,
    T_Const = 6,
    T_Param = 7,
    T_Aggref = 8,
    T_WindowFunc = 9,
    T_SubLink = 10,
    T_FuncExpr = 11,
    T_OpExpr = 12,
    T_BoolExpr = 13,
    T_FieldSelect = 14,
    /* ... 更多表达式类型 ... */
    
    /* 解析树节点 (67-140) */
    T_RawStmt = 67,
    T_SelectStmt = 68,
    T_InsertStmt = 69,
    T_UpdateStmt = 70,
    T_DeleteStmt = 71,
    T_CreateStmt = 72,      // CREATE TABLE
    T_Constraint = 73,
    /* ... 更多 DDL 节点 ... */
    
    /* 计划节点 (341-430) */
    T_Result = 341,
    T_SeqScan = 342,
    T_IndexScan = 343,
    T_HashJoin = 344,
    T_Sort = 345,
    T_Agg = 346,
    T_Material = 347,
    /* ... 更多执行计划节点 ... */
    
    /* 值节点 */
    T_Integer = 459,
    T_Float = 460,
    T_Boolean = 461,
    T_String = 462
} NodeTag;

2.2 按功能组织的 NodeTag

T_Invalid (0)

    ├── 基础类型 (1-4)
    │       T_List, T_Alias, T_RangeVar, T_TableFunc

    ├── 表达式节点 (5-61)
    │       ├── 变量/常量: T_Var, T_Const, T_Param
    │       ├── 函数调用: T_Aggref, T_WindowFunc, T_FuncExpr, T_OpExpr
    │       ├── 逻辑表达式: T_BoolExpr (AND/OR/NOT)
    │       ├── 比较表达式: T_SubLink, T_NullIfExpr, T_CoalesceExpr
    │       ├── 类型转换: T_RelabelType, T_CoerceViaIO
    │       └── 其他: T_CaseExpr, T_ArrayExpr, T_RowExpr

    ├── 查询结构节点 (62-66)
    │       T_TargetEntry, T_RangeTblRef, T_JoinExpr, T_FromExpr, T_Query

    ├── 解析树节点 (67-140)
    │       ├── 语句: T_InsertStmt, T_UpdateStmt, T_DeleteStmt
    │       ├── DDL: T_CreateStmt, T_AlterTableStmt, T_DropStmt
    │       └── 其他: T_TypeName, T_ColumnDef, T_IndexElem

    ├── 优化器节点 (262-340)
    │       ├── 关系信息: T_RelOptInfo, T_IndexOptInfo
    │       └── 路径: T_Path, T_IndexPath, T_HashPath, T_NestPath

    ├── 执行计划节点 (341-430)
    │       ├── 扫描: T_SeqScan, T_IndexScan, T_BitmapScan
    │       ├── 连接: T_NestLoop, T_MergeJoin, T_HashJoin
    │       ├── 操作: T_Sort, T_Agg, T_Group, T_Limit
    │       └── 修改: T_ModifyTable, T_Append

    └── 值节点 (459-463)
            T_Integer, T_Float, T_Boolean, T_String

3. 类型检查与转换

3.1 IsA 宏

c
// src/include/nodes/nodes.h
#define nodeTag(nodeptr)     (((const Node*)(nodeptr))->type)

#define IsA(nodeptr, _type_) (nodeTag(nodeptr) == T_##_type_)

使用示例:

c
Node *node;

if (IsA(node, Query))
{
    // node 是 Query 类型
    Query *query = (Query *) node;
}

if (IsA(node, Const))
{
    // node 是 Const 类型
    Const *con = (Const *) node;
}

// 检查多个类型
if (IsA(node, SelectStmt) || IsA(node, InsertStmt))
{
    // 处理的 DML 语句
}

3.2 castNode 宏 (类型安全转换)

c
// src/include/nodes/nodes.h
#ifdef USE_ASSERT_CHECKING
static inline Node *
castNodeImpl(NodeTag type, void *ptr)
{
    // 仅在断言模式下检查类型
    Assert(ptr == NULL || nodeTag(ptr) == type);
    return (Node *) ptr;
}
#define castNode(_type_, nodeptr) \
    ((_type_ *) castNodeImpl(T_##_type_, nodeptr))
#else
#define castNode(_type_, nodeptr) ((_type_ *) (nodeptr))
#endif

使用示例:

c
// 类型安全的节点转换,带断言检查
Query *query = castNode(Query, rawStmt->stmt);

// 如果类型不匹配,会触发断言失败
// 仅在 debug 模式下启用检查,生产环境零开销

3.3 is_node_like Macro

c
// 检查节点类型是否匹配模式
#define is_node_like(nodeptr, pattern) \
    (nodeTag(nodeptr) == T_##pattern || \
     nodeTag(nodeptr) == T_##pattern##2 || ...)

4. 节点创建与内存管理

4.1 makeNode 宏

c
// src/include/nodes/nodes.h

// 内部分配器 - 使用 palloc0 分配并清零内存
static inline Node *
newNode(Size size, NodeTag tag)
{
    Node *result;
    Assert(size >= sizeof(Node));      // 确保足够容纳 Node 结构
    result = (Node *) palloc0(size);   // 分配并清零
    result->type = tag;
    return result;
}

// 推荐使用的节点创建宏
#define makeNode(_type_) \
    ((_type_ *) newNode(sizeof(_type_), T_##_type_))

使用示例:

c
// 创建一个 Query 节点
Query *query = makeNode(Query);

// 创建一个常量表达式
Const *c = makeNode(Const);

// 创建一个 RangeVar (表引用)
RangeVar *rv = makeNode(RangeVar);
rv->catalogname = NULL;
rv->schemaname = "public";
rv->relname = "users";

4.2 makeNode 示例函数

c
// src/backend/nodes/makefuncs.c

// 创建 Var 节点 (变量引用)
Var *
makeVar(int varno, AttrNumber varattno, Oid vartype, 
        int32 vartypmod, Oid varcollid, Index varlevelsup)
{
    Var *var = makeNode(Var);
    var->varno = varno;
    var->varattno = varattno;
    var->vartype = vartype;
    var->vartypmod = vartypmod;
    var->varcollid = varcollid;
    var->varlevelsup = varlevelsup;
    var->varnosyn = (Index) varno;
    var->varattnosyn = varattno;
    var->location = -1;
    var->varnullingrels = NULL;
    return var;
}

// 创建常量节点
Const *
makeConst(Oid consttype, int32 consttypmod, Oid constcollid,
          int constlen, Datum constvalue, 
          bool constisnull, bool constbyval)
{
    Const *cnst = makeNode(Const);
    cnst->consttype = consttype;
    cnst->consttypmod = consttypmod;
    cnst->constcollid = constcollid;
    cnst->constlen = constlen;
    cnst->constvalue = constvalue;
    cnst->constisnull = constisnull;
    cnst->constbyval = constbyval;
    cnst->location = -1;
    return cnst;
}

// 创建目标项 (SELECT 列)
TargetEntry *
makeTargetEntry(Expr *expr, AttrNumber resno, 
                char *resname, bool resjunk)
{
    TargetEntry *tle = makeNode(TargetEntry);
    tle->expr = expr;
    tle->resno = resno;
    tle->resname = resname;
    tle->ressortgroupref = 0;
    tle->resorigtbl = InvalidOid;
    tle->resorigcol = 0;
    tle->resjunk = resjunk;
    return tle;
}

4.3 节点内存上下文

节点分配在当前内存上下文中,通常使用 CurrentMemoryContext:

c
// 节点在当前上下文中分配
Node *node = makeNode(Query);

// 切换到特定上下文创建节点
MemoryContext old = MemoryContextSwitchTo(QueryContext);
Node *node = makeNode(Query);
MemoryContextSwitchTo(old);

5. List 结构详解

5.1 List 数据结构

PostgreSQL 的 List 是可扩展数组,不是传统的 Lisp 链表:

c
// src/include/nodes/list.h
typedef struct List
{
    NodeTag     type;           // T_List, T_IntList, T_OidList, T_XidList
    int         length;        // 当前元素数量
    int         max_length;    // 已分配的元素数组长度
    ListCell   *elements;      // 可重分配的数组指针
    ListCell    initial_elements[];  // 内联存储
} List;

// 空列表表示
#define NIL ((List *) NULL)

5.2 ListCell 联合体

c
typedef union ListCell
{
    void       *ptr_value;         // T_List 使用
    int         int_value;        // T_IntList 使用
    Oid         oid_value;        // T_OidList 使用
    TransactionId xid_value;      // T_XidList 使用
} ListCell;

5.3 List 类型

类型NodeTag元素类型示例用途
T_List通用列表void *节点树、语句列表
T_IntList整数列表int列号、偏移量
T_OidListOID 列表Oid表 OID、类型 OID
T_XidList事务 ID 列表TransactionId事务列表

5.4 ListCell 访问宏

c
// 获取元素值
#define lfirst(lc)              ((lc)->ptr_value)
#define lfirst_int(lc)          ((lc)->int_value)
#define lfirst_oid(lc)          ((lc)->oid_value)
#define lfirst_node(type, lc)   castNode(type, lfirst(lc))

// 按索引访问
#define linitial(l)             lfirst(list_nth_cell(l, 0))
#define lsecond(l)              lfirst(list_nth_cell(l, 1))
#define lthird(l)               lfirst(list_nth_cell(l, 2))
#define llast(l)                lfirst(list_last_cell(l))

// 安全的整数/OID 访问
#define linitial_int(l)         lfirst_int(list_nth_cell(l, 0))
#define llast_int(l)            lfirst_int(list_last_cell(l))

5.5 List 创建宏

c
// 创建指针列表 (T_List)
#define list_make1(x1)          list_make1_impl(T_List, list_make_ptr_cell(x1))
#define list_make2(x1, x2)      ...
#define list_make3(x1, x2, x3)  ...
#define list_make4(x1, x2, x3, x4) ...
#define list_make5(x1, x2, x3, x4, x5) ...

// 创建整数列表 (T_IntList)
#define list_make1_int(x1)      list_make1_impl(T_IntList, list_make_int_cell(x1))
#define list_make2_int(x1, x2)  ...

// 创建 OID 列表 (T_OidList)
#define list_make1_oid(x1)      list_make1_impl(T_OidList, list_make_oid_cell(x1))

// 便捷创建函数
List *list_make1_impl(NodeTag type, ListCell c1);
List *list_make2_impl(NodeTag type, ListCell c1, ListCell c2);

5.6 List 遍历

c
// 基础遍历
ListCell *cell;
foreach (cell, myList)
{
    Node *node = lfirst(cell);
    // 处理 node
}

// 使用指针遍历 (更简洁)
Node *node;
foreach_ptr (Node, node, myList)
{
    // 处理 node
}

// 带索引遍历
int i = 0;
for (ListCell *cell = list_head(myList); 
     cell != NULL; 
     cell = lnext(myList, cell), i++)
{
    Node *node = lfirst(cell);
    // 处理 node, i 是索引
}

5.7 List 操作函数

c
// 追加元素
List *lappend(List *list, void *datum);
List *lappend_int(List *list, int datum);
List *lappend_oid(List *list, Oid datum);

// 前置元素
List *lcons(void *datum, List *list);
List *lcons_int(int datum, List *list);

// 连接列表
List *list_concat(List *list1, const List *list2);

// 删除元素
List *list_delete(List *list, const void *datum);
List *list_delete_first(List *list);

// 成员检查
bool list_member(const List *list, const void *datum);
bool list_member_int(const List *list, int datum);
bool list_member_oid(const List *list, Oid datum);

// 列表长度
int list_length(const List *list);

// 列表复制
List *list_copy(const List *oldlist);          // 浅复制
List *list_copy_deep(const List *oldlist);    // 深复制

// 列表切片
List *list_truncate(List *list, int n);       // 保留前 n 个元素

5.8 List 使用示例

c
// 构建整数列表
List *colNums = list_make2_int(1, 2);
colNums = lappend_int(colNums, 3);
// 结果: [1, 2, 3]

// 构建节点列表
List *exprList = list_make1(makeConst(INT4OID, -1, 0, 
                                       sizeof(int32), Int32GetDatum(1),
                                       false, true));
exprList = lappend(exprList, makeConst(INT4OID, -1, 0,
                                       sizeof(int32), Int32GetDatum(2),
                                       false, true));

// 遍历处理
foreach (cell, exprList)
{
    Const *c = lfirst_node(Const, cell);
    Datum val = c->constvalue;
    // 处理常量值
}

// OID 列表 (表引用)
List *tableOids = list_make1_oid(MyTableRelationId);
tableOids = list_concat(tableOids, list_make1_oid(OtherTableId));

6. 值节点 (Value Nodes)

6.1 原始值类型定义

c
// src/include/nodes/value.h

typedef struct Integer
{
    NodeTag     type;           // T_Integer = 459
    int         ival;          // 整数值
} Integer;

typedef struct Float
{
    NodeTag     type;           // T_Float = 460
    char       *fval;          // 存储为字符串以保持精度
} Float;

typedef struct Boolean
{
    NodeTag     type;           // T_Boolean = 461
    bool        boolval;
} Boolean;

typedef struct String
{
    NodeTag     type;           // T_String = 462
    char       *sval;          // 字符串值
} String;

6.2 值访问宏

c
#define intVal(v)      (castNode(Integer, v)->ival)
#define floatVal(v)    (atof(castNode(Float, v)->fval))
#define boolVal(v)    (castNode(Boolean, v)->boolval)
#define strVal(v)     (castNode(String, v)->sval)

6.3 值节点使用示例

c
// 在表达式中使用值节点
Node *make_int_node(int i)
{
    Integer *n = makeNode(Integer);
    n->ival = i;
    return (Node *) n;
}

Node *make_string_node(const char *s)
{
    String *n = makeNode(String);
    n->sval = pstrdup(s);
    return (Node *) n;
}

// 在解析中使用
Node *parse_value = make_int_node(42);
if (IsA(parse_value, Integer))
{
    int val = intVal(parse_value);
    printf("Integer value: %d\n", val);
}

7. 表达式节点详解

7.1 Const 节点 (常量)

c
// src/include/nodes/primnodes.h
typedef struct Const
{
    Expr        xpr;
    Oid         consttype;      // 数据类型 OID
    int32       consttypmod;   // 类型修饰符
    Oid         constcollid;   // 排序规则 OID
    int         constlen;      // 常量长度 (-1 表示变长)
    Datum       constvalue;     // 实际值
    bool        constisnull;   // 是否为 NULL
    bool        constbyval;    // 是否按值传递
    bool        constisarray;  // 是否为数组类型
    int         location;      // 解析位置
} Const;

7.2 Var 节点 (变量引用)

c
// src/include/nodes/primnodes.h
typedef struct Var
{
    Expr        xpr;
    Index       varno;         // 范围表索引
    AttrNumber  varattno;      // 属性编号 (1-based)
    Oid         vartype;       // 数据类型
    int32       vartypmod;     // 类型修饰符
    Oid         varcollid;     // 排序规则
    Index       varlevelsup;   // 上层嵌套级别 (用于子查询)
    Index       varnosyn;      // 同步索引
    AttrNumber  varattnosyn;   // 同步属性编号
    int         location;      // 位置
    Bitmapset   *varnullingrels;  // 可为空的列
} Var;

7.3 FuncExpr 节点 (函数调用)

c
// src/include/nodes/primnodes.h
typedef struct FuncExpr
{
    Expr        xpr;
    Oid         funcid;         // 函数 OID
    Oid         funcresulttype; // 返回类型
    bool        funcretset;    // 是否返回集合
    bool        funcvariadic;  // 是否有可变参数
    Oid        *funccoltypes;  // 返回列类型
    int32      *funccoltypmods; // 返回列类型修饰符
    int         funcformat;    // 格式 (刚量/IO 转换)
    List       *args;          // 参数列表
    int         location;      // 位置
} FuncExpr;

7.4 表达式节点示例

c
// 构建表达式: users.age + 1 > 18
FuncExpr *
build_age_check_expr(void)
{
    // Var: users.age
    Var *age = makeVar(1, 2, INT4OID, -1, InvalidOid, 0);
    
    // Const: 1
    Const *one = makeConst(INT4OID, -1, InvalidOid, 
                           sizeof(int32), Int32GetDatum(1),
                           false, true);
    
    // Const: 18
    Const *eighteen = makeConst(INT4OID, -1, InvalidOid,
                                 sizeof(int32), Int32GetDatum(18),
                                 false, true);
    
    // age + 1
    OpExpr *plus = makeNode(OpExpr);
    plus->opno = Int4EqualOperator;  // 或实际的操作符 OID
    plus->args = list_make2(age, one);
    plus->opfuncid = F_INT4PL;
    
    // (age + 1) > 18
    OpExpr *gt = makeNode(OpExpr);
    gt->opno = Int4GreaterOperator;
    gt->args = list_make2(plus, eighteen);
    gt->opfuncid = F_INT4GT;
    
    return gt;
}

8. 查询计划节点

8.1 Plan 基结构

c
// src/include/nodes/plannodes.h
typedef struct Plan
{
    NodeTag     type;
    
    // 成本估算
    Cost        startup_cost;   // 开始获取元组前的成本
    Cost        total_cost;     // 总成本
    Cardinality plan_rows;      // 估算行数
    int         plan_width;     // 平均行宽 (字节)
    
    // 并行相关
    bool        parallel_aware;
    bool        parallel_safe;
    bool        async_capable;
    
    // 通用字段
    int         plan_node_id;   // 计划树中的唯一 ID
    List       *targetlist;     // 输出列 (TargetEntry 列表)
    List       *qual;           // 过滤条件 (AND 列表)
    Plan       *lefttree;       // 左子节点 (外层)
    Plan       *righttree;      // 右子节点 (内层)
    List       *initPlan;       // InitPlan 节点
    
    // 参数追踪
    Bitmapset  *extParam;       // 影响此节点的外部参数
    Bitmapset  *allParam;       // 影响此节点的所有参数
} Plan;

// 子节点访问宏
#define outerPlan(node)   (((Plan *)(node))->lefttree)
#define innerPlan(node)   (((Plan *)(node))->righttree)

8.2 TargetEntry 结构

c
// src/include/nodes/primnodes.h
typedef struct TargetEntry
{
    Expr        xpr;            // 内嵌的 Expr (用于内存管理)
    Expr       *expr;          // 要计算的表达式
    AttrNumber  resno;         // 属性编号 (输出列号)
    char       *resname;       // 列名 (用于显示)
    Index       ressortgroupref; // 排序/分组引用
    Oid         resorigtbl;    // 源表 OID
    AttrNumber  resorigcol;    // 源表中的列号
    bool        resjunk;       // true = 从最终输出中移除
} TargetEntry;

8.3 常见计划节点

shell
PlannedStmt (根节点)

    └── Plan (基类)

          ├── Scan (扫描)
     ├── SeqScan      - 顺序扫描
     ├── IndexScan    - 索引扫描
     ├── IndexOnlyScan - 仅索引扫描
     ├── BitmapScan   - 位图扫描
     └── TidScan      - CTID 扫描

          ├── Join (连接)
     ├── NestLoop     - 嵌套循环连接
     ├── MergeJoin    - 归并连接
     └── HashJoin     - 哈希连接

          ├── 操作符
     ├── Sort         - 排序
     ├── Agg          - 聚合
     ├── WindowAgg    - 窗口函数
     ├── Group        - 分组
     ├── Unique       - 去重
     ├── Limit        - 限制
     └── Material     - 物化

          └── 修改
                └── ModifyTable - INSERT/UPDATE/DELETE

8.4 计划树构建示例

c
// 构建简单查询: SELECT name FROM users WHERE id = 1 ORDER BY name

// 1. 创建扫描计划 (叶子节点)
SeqScan *scan = makeNode(SeqScan);
scan->plan.plan_node_id = 1;
scan->scanrelid = 1;  // 范围表索引

// 2. 创建目标列表
TargetEntry *tle1 = makeTargetEntry(
    makeVar(1, 2, TEXTOID, -1, InvalidOid, 0),  // name 列
    1,                                           // resno = 1
    "name",                                      // 列名
    false                                        // 不是 junk
);
scan->plan.targetlist = list_make1(tle1);

// 3. 创建过滤条件: id = 1
Var *id_var = makeVar(1, 1, INT4OID, -1, InvalidOid, 0);
Const *one = makeConst(INT4OID, -1, InvalidOid, 
                       sizeof(int32), Int32GetDatum(1), false, true);
OpExpr *id_eq = makeNode(OpExpr);
id_eq->opno = Int4EqualOperator;
id_eq->args = list_make2(id_var, one);
scan->plan.qual = list_make1(id_eq);

// 4. 创建排序
Sort *sort = makeNode(Sort);
sort->plan.plan_node_id = 2;
sort->plan.lefttree = (Plan *)scan;
sort->plan.targetlist = scan->plan.targetlist;
sort->numCols = 1;
sort->sortColIdx = list_make1_int(1);  // 按第1列排序
sort->sortOperators = list_make1_oid(bttextcmp);

// 5. 包装为 PlannedStmt
PlannedStmt *stmt = makeNode(PlannedStmt);
stmt->commandType = CMD_SELECT;
stmt->planTree = (Plan *)sort;
stmt->rtable = list_of_range_table_entries;

9. 节点树操作

9.1 节点复制 (copyfuncs.c)

c
// 深度复制整个节点树
void *copyObjectImpl(const void *from);

#define copyObject(obj) \
    ((typeof(obj)) copyObjectImpl(obj))

// 使用示例
Query *original = parseQuery();
Query *copy = copyObject(original);
// 复制整个节点树,包括所有子节点

9.2 节点比较 (equalfuncs.c)

c
// 比较两个节点树是否结构相等
bool equal(const void *a, const void *b);

// 使用示例
Query *q1 = ...;
Query *q2 = ...;
if (equal(q1, q2))
{
    // 两个查询结构相同
}

9.3 节点序列化 (outfuncs.c)

c
// 将节点树转换为字符串表示
char *nodeToString(const void *obj);

// 使用示例
char *str = nodeToString(myQuery);
elog(DEBUG1, "Query plan: %s", str);
pfree(str);

9.4 节点反序列化 (readfuncs.c)

c
// 从字符串恢复节点树
void *stringToNode(const char *str);

// 使用示例
Node *restored = stringToNode(str);

9.5 表达式类型信息 (nodeFuncs.c)

c
// 获取表达式类型
Oid exprType(const Node *expr);
int32 exprTypmod(const Node *expr);
Oid exprCollation(const Node *expr);

// 检查表达式属性
bool exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod);
bool exprIsLengthCoercion(const Node *expr, ...);

// 剥离隐式强制转换
Node *strip_implicit_coercions(Node *node);

// 获取位置信息
int exprLocation(const Node *expr);

10. 节点系统使用模式

10.1 遍历节点树

c
// 递归遍历节点树
void
walk_node_tree(Node *node, void *context)
{
    if (node == NULL)
        return;
    
    switch (nodeTag(node))
    {
        case T_Query:
            walk_query((Query *) node, context);
            break;
        case T_SelectStmt:
            walk_select((SelectStmt *) node, context);
            break;
        case T_Const:
            // 处理常量
            break;
        case T_List:
            {
                List *list = (List *) node;
                ListCell *cell;
                foreach (cell, list)
                    walk_node_tree(lfirst(cell), context);
            }
            break;
        default:
            // 递归处理所有子节点
            if (IsA(node, Expr))
                walk_expr((Expr *) node, context);
            break;
    }
}

10.2 统计节点数量

c
// 统计计划树中的节点数
int
count_plan_nodes(Plan *plan)
{
    int count = 1;  // 当前节点
    ListCell *lc;
    
    // 遍历目标列表
    foreach (lc, plan->targetlist)
    {
        TargetEntry *tle = lfirst_node(TargetEntry, lc);
        count += count_expr_nodes((Node *) tle->expr);
    }
    
    // 遍历过滤条件
    foreach (lc, plan->qual)
        count += count_expr_nodes(lfirst(lc));
    
    // 递归处理子节点
    if (outerPlan(plan))
        count += count_plan_nodes(outerPlan(plan));
    if (innerPlan(plan))
        count += count_plan_nodes(innerPlan(plan));
    
    return count;
}

10.3 修改节点树

c
// 替换节点树中的子节点
Node *
replace_var_in_targetlist(Plan *plan, Var *old_var, Var *new_var)
{
    List *new_tlist = NULL;
    ListCell *lc;
    
    foreach (lc, plan->targetlist)
    {
        TargetEntry *tle = lfirst_node(TargetEntry, lc);
        Expr *new_expr = (Expr *) replace_var_in_expr(tle->expr, 
                                                      (Node *) old_var, 
                                                      (Node *) new_var);
        TargetEntry *new_tle = makeTargetEntry(new_expr, tle->resno,
                                               tle->resname, tle->resjunk);
        new_tlist = lappend(new_tlist, new_tle);
    }
    
    plan->targetlist = new_tlist;
    return (Node *) plan;
}

11. 源码文件索引

文件路径功能描述
src/include/nodes/nodes.hNode 基结构、IsA 宏、makeNode
src/include/nodes/nodetags.hNodeTag 枚举定义
src/include/nodes/list.hList 数据结构、ListCell
src/include/nodes/value.hInteger、Float、String、Boolean 值节点
src/include/nodes/primnodes.hVar、Const、FuncExpr、TargetEntry
src/include/nodes/parsenodes.hCreateStmt、SelectStmt 等解析节点
src/include/nodes/plannodes.hPlan、SeqScan、HashJoin 等执行计划节点
src/include/nodes/pathnodes.hPath、IndexPath 等优化器路径节点
src/backend/nodes/makefuncs.cmakeVar、makeConst、makeTargetEntry 等创建函数
src/backend/nodes/copyfuncs.ccopyObject 深度复制实现
src/backend/nodes/equalfuncs.cequal 节点比较实现
src/backend/nodes/outfuncs.cnodeToString 序列化实现
src/backend/nodes/readfuncs.cstringToNode 反序列化实现
src/backend/nodes/list.cList 操作函数实现

12. 总结

核心要点

  1. 统一标识: 所有节点都以 NodeTag 开头,实现统一的类型识别

  2. IsA 模式: 通过简单的标签比较实现类型检查,零运行时开销

  3. makeNode 模式: 使用 palloc0 分配并清零内存的节点创建模式

  4. List 容器: 使用可扩展数组实现的通用容器,支持指针、整数、OID 等类型

  5. 节点树: SQL 语句和查询计划都表示为树形节点结构

  6. 函数式操作: 通过 copyObjectequalnodeToString 等函数实现通用的节点树操作

设计优势

  • 类型安全: 通过 IsAcastNode 实现类型检查
  • 内存管理: 与 MemoryContext 集成,便于统一释放
  • 树形表示: 自然地表示 SQL 语句和执行计划的层次结构
  • 可扩展性: 新增节点类型只需添加新的结构体和 NodeTag 值
  • 通用操作: 复制、比较、序列化等操作适用于所有节点类型

本文档基于 PostgreSQL 源码分析编写,详细阐述了 PostgreSQL 核心 Node 数据结构的设计、实现和使用方法。