PostgreSQL-01-Parser-解析器

模块概览

模块职责

Parser 模块是 PostgreSQL 查询处理管道的第一阶段,负责将 SQL 文本字符串转换为结构化的抽象语法树(Abstract Syntax Tree, AST)。该模块不执行任何数据库访问,即使在中止的事务中也能正常工作,返回的"原始"解析树需要由后续的 Analyzer 模块进行语义分析。

核心能力

  • 词法分析:识别 SQL 关键字、标识符、字面量、运算符等词法单元(Token)
  • 语法分析:根据 PostgreSQL SQL 语法规则构建解析树
  • 多种解析模式:支持标准 SQL、PL/pgSQL 表达式、类型名称等不同上下文的解析
  • 错误定位:记录每个语法元素在原始 SQL 文本中的位置,便于错误报告

输入/输出

  • 输入
    • SQL 文本字符串(const char *)
    • 解析模式(RawParseMode):默认模式、类型名称模式、PL/pgSQL 表达式模式等
  • 输出
    • 解析树列表(List *):包含一个或多个原始语句节点(RawStmt)
    • 失败时返回 NIL(空列表)

上下游依赖

  • 上游调用方
    • Backend 进程主循环(tcop/postgres.c)
    • PL/pgSQL 编译器(pl/plpgsql)
    • 实用工具命令(如 PREPARE)
  • 下游被调方
    • Lexer(scan.l):词法分析器,Flex 生成
    • Grammar(gram.y):语法分析器,Bison 生成
  • 后续处理
    • Analyzer(parser/analyze.c):将解析树转换为查询树(Query)

生命周期

  • 解析器状态为临时对象,仅在单次解析过程中存活
  • 使用栈上分配的扫描器状态(yyscanner)
  • 解析树节点在 CurrentMemoryContext 中分配
  • 解析完成后,扫描器资源立即释放

模块架构图

flowchart TB
    subgraph Parser["Parser 模块 (src/backend/parser)"]
        direction TB

        Entry[raw_parser 入口函数] --> Init[初始化阶段]
        Init --> Scanner[scanner_init<br/>初始化 Lexer]
        Init --> ParserInit[parser_init<br/>初始化 Bison]

        Scanner --> Lex[base_yylex<br/>词法分析器]
        ParserInit --> Gram[base_yyparse<br/>语法分析器]

        Lex -->|Token 流| Gram

        Gram --> Actions[语法动作<br/>构建 AST 节点]
        Actions --> Tree[生成 Parse Tree]

        Tree --> Cleanup[scanner_finish<br/>清理资源]
        Cleanup --> Return[返回 List 结构]
    end

    subgraph External["外部输入"]
        SQL[SQL 文本]
        Mode[RawParseMode]
    end

    subgraph Keywords["关键字表"]
        KW[ScanKeywords<br/>SQL 关键字列表]
        KWTokens[ScanKeywordTokens<br/>关键字到 Token 映射]
    end

    subgraph Output["输出结果"]
        RawStmt[RawStmt 节点]
        SelectStmt[SelectStmt]
        InsertStmt[InsertStmt]
        UpdateStmt[UpdateStmt]
        DeleteStmt[DeleteStmt]
        UtilityStmt[DDL/DCL 语句]
    end

    SQL --> Entry
    Mode --> Entry
    KW --> Scanner
    KWTokens --> Scanner

    Return --> RawStmt
    RawStmt --> SelectStmt
    RawStmt --> InsertStmt
    RawStmt --> UpdateStmt
    RawStmt --> DeleteStmt
    RawStmt --> UtilityStmt

    subgraph NextStage["后续阶段"]
        Analyzer[Analyzer<br/>语义分析]
    end

    Return --> Analyzer

架构说明

分层设计

  1. 接口层(parser.c):

    • raw_parser() 函数是唯一的公开入口点
    • 封装词法和语法分析器的初始化与清理
    • 处理多种解析模式的差异
  2. 词法分析层(scan.l):

    • Flex 生成的状态机,识别 SQL 词法单元
    • 关键字识别:通过完美哈希表(ScanKeywords)快速查找
    • 字符串处理:支持标准 SQL 字符串、转义字符、Unicode 转义
    • 注释过滤:自动跳过单行注释(–)和多行注释(/* */)
  3. 语法分析层(gram.y):

    • Bison 生成的 LALR(1) 解析器
    • 超过 600 条语法规则,覆盖 PostgreSQL SQL 方言
    • 语法动作:嵌入 C 代码构建 AST 节点
    • 错误恢复:检测到语法错误时尝试继续解析

关键设计点

1. Token 前瞻处理

标准 SQL 某些语法需要多个 Token 前瞻(如 NULLS FIRST),但 LALR(1) 仅支持单 Token 前瞻。Parser 通过 base_yylex() 中间层实现 Token 合并,将多词关键字合并为单个 Token(如 NULLS_LA + FIRSTNULLS_FIRST),保持语法 LALR(1) 的约束。

2. 多模式支持

通过 RawParseMode 枚举控制解析行为:

  • RAW_PARSE_DEFAULT:标准 SQL 语句列表(分号分隔)
  • RAW_PARSE_TYPE_NAME:仅解析类型名称(如 integer[]
  • RAW_PARSE_PLPGSQL_EXPR:PL/pgSQL 表达式(无需分号)
  • RAW_PARSE_PLPGSQL_ASSIGNn:PL/pgSQL 赋值语句(n 表示目标变量的点号层数)

实现机制:通过 lookahead_token 向 Lexer 注入模式标识 Token,语法文件根据该 Token 选择不同的起始规则。

3. 位置跟踪

每个 AST 节点包含 location 字段,记录其在原始 SQL 文本中的字节偏移(非字符偏移)。错误报告时,通过该偏移在 SQL 文本中标注错误位置(^ 符号)。

4. 内存管理

  • 解析树节点通过 palloc()CurrentMemoryContext 中分配
  • 扫描器状态(yyscanner)在栈上分配,仅持有指向共享资源的指针
  • 关键字表(ScanKeywords)为全局只读数据,编译时静态初始化

边界条件

并发性

  • 无全局状态:每次调用 raw_parser() 使用独立的扫描器状态
  • 线程不安全:依赖 PostgreSQL 单线程后端模型,但不同后端进程可并发调用
  • 关键字表为只读共享资源,无竞争问题

输入限制

  • SQL 文本长度无硬编码限制,受内存大小约束
  • Token 长度限制:标识符最大 NAMEDATALEN - 1 字节(默认 63)
  • 嵌套深度:语法树深度无限制,但深度过大会导致栈溢出(递归解析)

错误处理

  • 语法错误:Bison 通过 yyerror() 报告错误,返回非零值,raw_parser() 返回 NIL
  • 词法错误:如非法字符、未终止的字符串,Lexer 调用 ereport(ERROR)
  • 内存不足:palloc() 失败触发 elog(ERROR),通过 PostgreSQL 异常机制传播

性能要点

热路径优化

  1. 关键字查找:使用完美哈希表(编译时生成),O(1) 时间复杂度
  2. Token 缓冲:Flex 使用内部缓冲区,减少系统调用
  3. 字符串复制:仅在必要时复制字符串(如去除引号、处理转义)

瓶颈识别

  • CPU 密集:词法分析和语法分析均为 CPU 密集操作
  • 无 I/O:不访问磁盘或网络
  • 内存分配:频繁的 palloc() 调用,可能导致内存碎片

优化策略

  • 准备语句(Prepared Statement):解析一次,执行多次,缓存解析结果
  • 简单查询协议优化:短查询(如 SELECT 1)通过快速路径处理
  • 并行解析:不支持,单个 SQL 文本的解析是串行的

版本兼容性

  • SQL 语法向后兼容:新版本 PostgreSQL 支持旧版本 SQL 语法
  • 新关键字引入:可能导致旧代码使用的标识符成为保留字(需加引号)
  • 解析树节点结构:跨版本不兼容,不可序列化持久化(每次查询重新解析)

数据结构与 UML 图

核心数据结构

1. RawStmt(原始语句节点)

/* src/include/nodes/parsenodes.h */
typedef struct RawStmt
{
    NodeTag     type;           /* 节点类型标识:T_RawStmt */
    Node       *stmt;           /* 实际的语句节点(SelectStmt/InsertStmt 等) */
    int         stmt_location;  /* 语句在原始文本中的起始位置(字节偏移) */
    int         stmt_len;       /* 语句长度(字节数),-1 表示未知 */
} RawStmt;

字段说明

字段 类型 必填 说明
type NodeTag 节点类型标识,固定为 T_RawStmt
stmt Node * 指向实际的语句节点(多态指针,可指向 SelectStmt、InsertStmt、CreateStmt 等)
stmt_location int 语句在原始 SQL 文本中的起始字节偏移,用于错误定位
stmt_len int 语句长度(字节数),仅在多语句文本时有效;单语句时为 -1

设计意义

RawStmt 是解析器返回的顶层节点,封装了实际的语句节点及其元数据。通过统一的包装结构,后续阶段可以获取语句的源码位置信息,用于错误报告和查询日志。

2. SelectStmt(SELECT 语句节点)

/* src/include/nodes/parsenodes.h */
typedef struct SelectStmt
{
    NodeTag     type;           /* 节点类型:T_SelectStmt */

    List       *distinctClause; /* DISTINCT 子句(NIL 表示无 DISTINCT) */
    IntoClause *intoClause;     /* INTO 子句(用于 SELECT INTO) */
    List       *targetList;     /* 选择列表(ResTarget 节点列表) */
    List       *fromClause;     /* FROM 子句(RangeVar 等节点列表) */
    Node       *whereClause;    /* WHERE 条件表达式 */
    List       *groupClause;    /* GROUP BY 子句(GroupingClause 节点列表) */
    bool        groupDistinct;  /* GROUP BY DISTINCT */
    Node       *havingClause;   /* HAVING 条件表达式 */
    List       *windowClause;   /* WINDOW 定义列表 */

    List       *valuesLists;    /* VALUES 子句(嵌套 List,用于 VALUES 语法) */

    List       *sortClause;     /* ORDER BY 子句(SortBy 节点列表) */
    Node       *limitOffset;    /* OFFSET 表达式 */
    Node       *limitCount;     /* LIMIT 表达式 */
    LimitOption limitOption;    /* LIMIT 选项(WITH TIES 等) */

    List       *lockingClause;  /* FOR UPDATE/SHARE 子句 */
    WithClause *withClause;     /* WITH 子句(CTE) */

    /* 集合操作(UNION/INTERSECT/EXCEPT) */
    SetOperation op;            /* 集合操作类型 */
    bool        all;            /* ALL 标志(如 UNION ALL) */
    struct SelectStmt *larg;    /* 左操作数(用于集合操作) */
    struct SelectStmt *rarg;    /* 右操作数(用于集合操作) */
} SelectStmt;

字段说明(核心字段)

字段 类型 必填 说明
targetList List * 选择列表,包含 ResTarget 节点(如 SELECT a, b+1 AS c
fromClause List * FROM 子句,包含 RangeVar(表)、JoinExpr(连接)等节点
whereClause Node * WHERE 条件表达式,为 Expr 类型节点(如 A_ExprBoolExpr
groupClause List * GROUP BY 子句,包含分组表达式或 GroupingSet 节点
havingClause Node * HAVING 条件表达式
sortClause List * ORDER BY 子句,包含 SortBy 节点(指定排序列和排序方向)
limitCount Node * LIMIT 表达式,可以是常量或参数占位符
limitOffset Node * OFFSET 表达式
op SetOperation 集合操作类型(SETOP_UNIONSETOP_INTERSECTSETOP_EXCEPT
larg / rarg SelectStmt * 集合操作的左右操作数(递归结构)

使用场景

  • 简单 SELECT:仅 targetListfromClause 非空
  • 聚合查询:包含 groupClausehavingClause
  • 集合操作:op 非空,largrarg 指向子查询
  • VALUES 语法:valuesLists 非空(如 VALUES (1, 2), (3, 4)

3. InsertStmt(INSERT 语句节点)

/* src/include/nodes/parsenodes.h */
typedef struct InsertStmt
{
    NodeTag     type;           /* 节点类型:T_InsertStmt */

    RangeVar   *relation;       /* 目标表 */
    List       *cols;           /* 列名列表(ResTarget 节点,仅包含列名) */
    Node       *selectStmt;     /* 数据源(SelectStmt 或 VALUES) */
    OnConflictClause *onConflictClause; /* ON CONFLICT 子句 */
    List       *returningList;  /* RETURNING 子句 */
    WithClause *withClause;     /* WITH 子句(CTE) */
    OverridingKind override;    /* OVERRIDING SYSTEM/USER VALUE */
} InsertStmt;

字段说明

字段 类型 必填 说明
relation RangeVar * 目标表名(包含模式名、表名)
cols List * 插入的列名列表;如为 NIL,表示所有列(按表定义顺序)
selectStmt Node * 数据源,可以是 SelectStmt(子查询)或直接 VALUES 列表
onConflictClause OnConflictClause * UPSERT 语法的冲突处理子句(ON CONFLICT DO NOTHING/UPDATE
returningList List * RETURNING 子句,返回插入后的列值(如 RETURNING id

语义约束

  • cols 长度必须与 selectStmt 返回的列数一致(Analyzer 阶段检查)
  • onConflictClause 仅在指定冲突目标(唯一索引)或无冲突目标(任意冲突)时有效

4. UpdateStmt(UPDATE 语句节点)

/* src/include/nodes/parsenodes.h */
typedef struct UpdateStmt
{
    NodeTag     type;           /* 节点类型:T_UpdateStmt */

    RangeVar   *relation;       /* 目标表 */
    List       *targetList;     /* SET 子句(ResTarget 节点列表) */
    Node       *whereClause;    /* WHERE 条件表达式 */
    List       *fromClause;     /* FROM 子句(用于多表 UPDATE) */
    List       *returningList;  /* RETURNING 子句 */
    WithClause *withClause;     /* WITH 子句(CTE) */
} UpdateStmt;

字段说明

字段 类型 必填 说明
relation RangeVar * 目标表名
targetList List * SET 子句,每个 ResTarget 节点表示一个赋值(如 name = 'Alice'
whereClause Node * WHERE 条件表达式,限定更新的行
fromClause List * FROM 子句,允许 UPDATE 时连接其他表
returningList List * RETURNING 子句,返回更新后的列值

PostgreSQL 特性

PostgreSQL 支持 UPDATE 的 FROM 子句,允许基于其他表的数据更新目标表:

UPDATE users SET status = orders.status
FROM orders
WHERE users.id = orders.user_id;

5. DeleteStmt(DELETE 语句节点)

/* src/include/nodes/parsenodes.h */
typedef struct DeleteStmt
{
    NodeTag     type;           /* 节点类型:T_DeleteStmt */

    RangeVar   *relation;       /* 目标表 */
    List       *usingClause;    /* USING 子句(用于多表 DELETE) */
    Node       *whereClause;    /* WHERE 条件表达式 */
    List       *returningList;  /* RETURNING 子句 */
    WithClause *withClause;     /* WITH 子句(CTE) */
} DeleteStmt;

字段说明

字段 类型 必填 说明
relation RangeVar * 目标表名
usingClause List * USING 子句,允许 DELETE 时连接其他表
whereClause Node * WHERE 条件表达式,限定删除的行
returningList List * RETURNING 子句,返回删除前的列值

PostgreSQL 特性

类似 UPDATE,PostgreSQL 支持 DELETE 的 USING 子句:

DELETE FROM users
USING orders
WHERE users.id = orders.user_id AND orders.status = 'cancelled';

类图(Mermaid ClassDiagram)

classDiagram
    class Node {
        <<abstract>>
        +NodeTag type
    }

    class RawStmt {
        +NodeTag type
        +Node *stmt
        +int stmt_location
        +int stmt_len
    }

    class SelectStmt {
        +NodeTag type
        +List *distinctClause
        +List *targetList
        +List *fromClause
        +Node *whereClause
        +List *groupClause
        +Node *havingClause
        +List *sortClause
        +Node *limitCount
        +Node *limitOffset
        +SetOperation op
        +SelectStmt *larg
        +SelectStmt *rarg
    }

    class InsertStmt {
        +NodeTag type
        +RangeVar *relation
        +List *cols
        +Node *selectStmt
        +OnConflictClause *onConflictClause
        +List *returningList
    }

    class UpdateStmt {
        +NodeTag type
        +RangeVar *relation
        +List *targetList
        +Node *whereClause
        +List *fromClause
        +List *returningList
    }

    class DeleteStmt {
        +NodeTag type
        +RangeVar *relation
        +List *usingClause
        +Node *whereClause
        +List *returningList
    }

    class CreateStmt {
        +NodeTag type
        +RangeVar *relation
        +List *tableElts
        +List *inhRelations
        +PartitionSpec *partspec
        +OnCommitAction oncommit
    }

    class List {
        +int length
        +ListCell *head
    }

    class RangeVar {
        +char *catalogname
        +char *schemaname
        +char *relname
        +int location
    }

    class ResTarget {
        +char *name
        +Node *val
        +int location
    }

    Node <|-- RawStmt
    Node <|-- SelectStmt
    Node <|-- InsertStmt
    Node <|-- UpdateStmt
    Node <|-- DeleteStmt
    Node <|-- CreateStmt

    RawStmt --> Node : stmt
    SelectStmt --> List : targetList
    SelectStmt --> List : fromClause
    SelectStmt --> SelectStmt : larg, rarg
    InsertStmt --> RangeVar : relation
    InsertStmt --> Node : selectStmt
    UpdateStmt --> RangeVar : relation
    UpdateStmt --> List : targetList
    DeleteStmt --> RangeVar : relation
    List --> ResTarget : element
    List --> RangeVar : element

类图说明

继承关系

  • 所有解析树节点继承自抽象基类 Node
  • Node 仅包含 type 字段(NodeTag 枚举),用于运行时类型识别(RTTI)

组合关系

  • RawStmt 包含多态的 stmt 指针,指向具体的语句节点
  • SelectStmt 支持递归结构(集合操作时 largrarg 指向子查询)
  • 所有列表使用 PostgreSQL 通用 List 结构(双向链表)

多态机制

PostgreSQL 使用 C 语言的"手工多态":

  1. 基类 Node 包含类型标识 type
  2. 所有节点类型的第一个字段必须是 NodeTag type
  3. 通过 nodeTag(node) 宏获取节点类型,再强制类型转换

示例:

Node *node = (Node *) stmt;
switch (nodeTag(node)) {
    case T_SelectStmt:
        SelectStmt *select = (SelectStmt *) node;
        /* 处理 SELECT 语句 */
        break;
    case T_InsertStmt:
        InsertStmt *insert = (InsertStmt *) node;
        /* 处理 INSERT 语句 */
        break;
    /* ... */
}

API 详细规格与调用链路

API 1: raw_parser() - SQL 文本解析

基本信息

  • 函数名称raw_parser
  • 函数签名List *raw_parser(const char *str, RawParseMode mode)
  • 所属文件src/backend/parser/parser.c
  • 协议/方法:C 函数调用(内部 API)
  • 幂等性:是(相同输入产生相同输出,无副作用)

请求参数结构

/* 函数参数说明 */
const char *str;        /* SQL 文本字符串(以 \0 结尾) */
RawParseMode mode;      /* 解析模式(枚举) */

参数表

参数 类型 必填 默认 约束 说明
str const char * - 非 NULL,以 \0 结尾 SQL 文本字符串,可以包含多条语句(分号分隔)
mode RawParseMode RAW_PARSE_DEFAULT 枚举值之一 解析模式,控制语法规则和返回结果格式

RawParseMode 枚举值

枚举值 说明 返回结果
RAW_PARSE_DEFAULT 标准 SQL 语句列表 List,每个 RawStmt 包含一条语句
RAW_PARSE_TYPE_NAME 类型名称(如 integer[] List,单元素列表
RAW_PARSE_PLPGSQL_EXPR PL/pgSQL 表达式 List,单元素列表,stmt 为表达式节点
RAW_PARSE_PLPGSQL_ASSIGN1 PL/pgSQL 赋值语句(单级变量) List,单元素列表,stmt 为 PLAssignStmt
RAW_PARSE_PLPGSQL_ASSIGN2 PL/pgSQL 赋值语句(两级变量,如 a.b List,单元素列表,stmt 为 PLAssignStmt
RAW_PARSE_PLPGSQL_ASSIGN3 PL/pgSQL 赋值语句(三级变量,如 a.b.c List,单元素列表,stmt 为 PLAssignStmt

响应结构

/* 返回值说明 */
List *result;  /* 解析树列表,失败时返回 NIL */

返回值表

字段 类型 必有 说明
result List * 解析树节点列表;成功时非 NIL,失败时为 NIL

成功场景

  • 返回 List 结构,包含一个或多个 RawStmt 节点(或其他节点,取决于 mode
  • 每个 RawStmt 节点的 stmt 字段指向实际的语句节点(如 SelectStmt

失败场景

  • 返回 NIL(空列表指针)
  • 错误信息通过 PostgreSQL 错误报告机制(ereport(ERROR))输出
  • 常见错误:
    • 语法错误(如 SELECT FROM 缺少列名)
    • 词法错误(如未终止的字符串 'abc
    • 非法字符(如控制字符)

入口函数核心代码

/* src/backend/parser/parser.c */
List *
raw_parser(const char *str, RawParseMode mode)
{
    core_yyscan_t yyscanner;
    base_yy_extra_type yyextra;
    int yyresult;

    /* 1. 初始化词法分析器(Flex 扫描器) */
    yyscanner = scanner_init(str, &yyextra.core_yy_extra,
                             &ScanKeywords, ScanKeywordTokens);

    /* 2. 根据解析模式设置前瞻 Token */
    if (mode == RAW_PARSE_DEFAULT)
        yyextra.have_lookahead = false;  /* 默认模式:无前瞻 */
    else
    {
        /* 其他模式:注入模式 Token 作为前瞻,引导语法选择特定规则 */
        static const int mode_token[] = {
            [RAW_PARSE_DEFAULT] = 0,
            [RAW_PARSE_TYPE_NAME] = MODE_TYPE_NAME,
            [RAW_PARSE_PLPGSQL_EXPR] = MODE_PLPGSQL_EXPR,
            /* ... 其他模式 ... */
        };
        yyextra.have_lookahead = true;
        yyextra.lookahead_token = mode_token[mode];
        yyextra.lookahead_yylloc = 0;
        yyextra.lookahead_end = NULL;
    }

    /* 3. 初始化语法分析器(Bison 解析器) */
    parser_init(&yyextra);

    /* 4. 执行语法分析 */
    yyresult = base_yyparse(yyscanner);

    /* 5. 清理扫描器资源 */
    scanner_finish(yyscanner);

    /* 6. 检查解析结果 */
    if (yyresult)  /* 非零表示错误 */
        return NIL;

    /* 7. 返回解析树列表 */
    return yyextra.parsetree;
}

逐步解释

步骤 1:初始化词法分析器

  • 调用 scanner_init() 创建 Flex 扫描器状态
  • 传入 SQL 文本指针、关键字表(ScanKeywords)和 Token 映射表(ScanKeywordTokens
  • yyscanner 为不透明句柄,指向扫描器内部状态

步骤 2:设置前瞻 Token

  • 默认模式(RAW_PARSE_DEFAULT):无前瞻,语法从 stmtblock 规则开始
  • 其他模式:设置 lookahead_token 为特定值(如 MODE_TYPE_NAME),语法从对应的规则开始
  • 前瞻机制:Lexer 在第一次调用 yylex() 时直接返回 lookahead_token,无需扫描输入

步骤 3:初始化语法分析器

  • 调用 parser_init() 初始化 Bison 解析器状态
  • 清空解析树列表(yyextra.parsetree = NIL

步骤 4:执行语法分析

  • 调用 base_yyparse(),Bison 生成的解析函数
  • 内部循环调用 base_yylex() 获取 Token,根据语法规则构建解析树
  • 解析树节点通过语法动作(嵌入的 C 代码)创建并添加到 yyextra.parsetree

步骤 5:清理扫描器资源

  • 调用 scanner_finish() 释放 Flex 扫描器占用的内存
  • 解析树节点在 CurrentMemoryContext 中,不被清理

步骤 6:检查解析结果

  • yyresult 为 0 表示成功,非 0 表示语法错误
  • 错误时,Bison 已通过 yyerror() 调用 ereport(ERROR) 报告错误(但错误可能被捕获)

步骤 7:返回解析树

  • 成功:返回 yyextra.parsetree(非 NIL 的 List 指针)
  • 失败:返回 NIL

调用链路与上层函数

调用链路 1:Backend 主循环处理 SQL 命令

PostgresMain (postgres.c)
  → exec_simple_query (postgres.c)
    → pg_parse_query (postgres.c)
      → raw_parser (parser.c)  ← 当前函数

上层函数:pg_parse_query()

/* src/backend/tcop/postgres.c */
List *
pg_parse_query(const char *query_string)
{
    List *raw_parsetree_list;

    /* 调用解析器,使用默认模式 */
    raw_parsetree_list = raw_parser(query_string, RAW_PARSE_DEFAULT);

    /* (此处省略查询重写和规范化处理) */

    return raw_parsetree_list;
}

功能说明

  • pg_parse_query() 是 Backend 进程处理 SQL 命令的第一步
  • 输入:客户端发送的原始 SQL 文本(可能包含多条语句)
  • 输出:解析树列表,每个元素对应一条语句
  • 后续处理:逐个语句调用 Analyzer、Planner、Executor

上层函数:exec_simple_query()

/* src/backend/tcop/postgres.c */
static void
exec_simple_query(const char *query_string)
{
    List *parsetree_list;
    ListCell *parsetree_item;

    /* 1. 解析 SQL 文本 */
    parsetree_list = pg_parse_query(query_string);

    /* 2. 遍历每条语句,逐个执行 */
    foreach(parsetree_item, parsetree_list)
    {
        RawStmt *parsetree = lfirst_node(RawStmt, parsetree_item);

        /* 3. 语义分析(Analyzer) */
        Query *query = parse_analyze_fixedparams(parsetree, query_string, NULL, 0, NULL);

        /* 4. 查询重写(Rewriter) */
        List *querytree_list = pg_rewrite_query(query);

        /* 5. 生成执行计划(Planner) */
        /* 6. 执行计划(Executor) */
        /* (此处省略后续步骤) */
    }
}

功能说明

  • exec_simple_query() 实现"简单查询协议"(Simple Query Protocol)
  • 流程:Parse → Analyze → Rewrite → Plan → Execute → Return
  • 错误处理:任一阶段失败,通过 PG_TRY/PG_CATCH 捕获,回滚事务,返回错误给客户端

调用链路 2:PL/pgSQL 编译器解析表达式

plpgsql_compile (pl_comp.c)
  → plpgsql_parse_word (pl_comp.c)
    → raw_parser (parser.c, mode = RAW_PARSE_PLPGSQL_EXPR)

上层函数:plpgsql_parse_word()

/* src/pl/plpgsql/src/pl_comp.c */
PLpgSQL_expr *
plpgsql_parse_word(const char *word)
{
    List *parsetree;
    RawStmt *rawstmt;

    /* 使用 PL/pgSQL 表达式模式解析 */
    parsetree = raw_parser(word, RAW_PARSE_PLPGSQL_EXPR);

    /* 提取唯一的 RawStmt 节点 */
    rawstmt = linitial_node(RawStmt, parsetree);

    /* 构造 PLpgSQL_expr 结构 */
    /* (此处省略表达式包装代码) */

    return expr;
}

功能说明

  • PL/pgSQL 变量名或表达式(如 my_vara + b)需要解析为表达式树
  • 使用 RAW_PARSE_PLPGSQL_EXPR 模式,语法规则仅接受单个表达式(无分号)
  • 返回的解析树后续转换为可执行的表达式对象

时序图

sequenceDiagram
    autonumber
    participant Client as 客户端
    participant Backend as Backend 进程
    participant PG as PostgresMain
    participant ESQ as exec_simple_query
    participant PPQ as pg_parse_query
    participant RP as raw_parser
    participant SI as scanner_init
    participant LEX as base_yylex (Lexer)
    participant PARSE as base_yyparse (Parser)
    participant SF as scanner_finish

    Client->>Backend: 发送 SQL 文本
    Backend->>PG: PostgresMain() 主循环
    PG->>ESQ: exec_simple_query(sql)
    ESQ->>PPQ: pg_parse_query(sql)

    PPQ->>RP: raw_parser(sql, RAW_PARSE_DEFAULT)
    RP->>SI: scanner_init(sql, keywords)
    SI-->>RP: yyscanner 句柄

    RP->>RP: 设置解析模式(前瞻 Token)
    RP->>RP: parser_init(&yyextra)

    RP->>PARSE: base_yyparse(yyscanner)

    loop 逐 Token 解析
        PARSE->>LEX: base_yylex() 获取下一个 Token
        LEX->>LEX: 扫描输入,识别关键字/标识符/字面量
        LEX-->>PARSE: 返回 Token 类型和值

        PARSE->>PARSE: 根据语法规则移进/规约
        PARSE->>PARSE: 语法动作:构建 AST 节点
    end

    PARSE-->>RP: 返回 yyresult(0=成功,非0=失败)

    RP->>SF: scanner_finish(yyscanner)
    SF-->>RP: 清理完成

    alt 解析成功
        RP-->>PPQ: 返回 List<RawStmt>
        PPQ-->>ESQ: 返回解析树列表
        ESQ->>ESQ: 遍历语句,调用 Analyzer
    else 解析失败
        RP-->>PPQ: 返回 NIL
        PPQ->>PPQ: ereport(ERROR) 报告错误
        PPQ-->>Client: 返回错误消息
    end

时序图说明

关键步骤

  1. SQL 文本接收(步骤 1-5):客户端通过 libpq 协议发送 SQL,Backend 进程主循环接收
  2. 解析器初始化(步骤 6-9):初始化 Lexer 和 Parser 状态,设置解析模式
  3. 词法/语法分析循环(步骤 10-15):Parser 驱动 Lexer 逐 Token 扫描,构建解析树
  4. 资源清理与返回(步骤 16-20):释放扫描器资源,返回解析树或错误

并发性

  • 每个 Backend 进程独立处理一个客户端连接,无跨进程共享状态
  • 扫描器和解析器状态在栈或进程私有内存中,无锁竞争
  • 关键字表为全局只读数据,可安全并发访问

超时

  • 解析器本身无超时机制(纯 CPU 操作)
  • 外层通过 statement_timeout 全局变量控制,信号处理器中断长时间操作

错误恢复

  • Bison 语法包含错误规则(error Token),尝试在错误点后恢复解析
  • 但 PostgreSQL 实践中,首次错误即中止解析,返回 NIL
  • 错误信息通过 ereport(ERROR) 输出,包含错误位置(errlocation()

关键功能与实现细节

关键功能 1:词法分析(Lexer)

功能概述

词法分析器(Lexer)负责将 SQL 文本字符串分解为词法单元(Token)序列。Lexer 由 Flex(快速词法分析器生成器)从源文件 scan.l 生成,输出为 scan.c

核心任务

  1. Token 识别

    • SQL 关键字(如 SELECTFROMWHERE
    • 标识符(表名、列名、变量名)
    • 字面量(整数、浮点数、字符串、布尔值)
    • 运算符(+-=|| 等)
    • 分隔符(逗号、分号、括号)
  2. 关键字查找

    • 通过完美哈希表(Perfect Hash Table)快速判断标识符是否为关键字
    • 关键字表 ScanKeywords 在编译时由 src/tools/gen_keywordlist.pl 生成
  3. 字符串处理

    • 标准 SQL 字符串:单引号包围('text'),内部单引号转义(''
    • 转义字符串:E'text\n'(支持 C 风格转义)
    • Unicode 转义:U&'data\0041'(Unicode 码点)
  4. 注释过滤

    • 单行注释:-- comment
    • 多行注释:/* comment */(支持嵌套)
  5. 位置跟踪

    • 记录每个 Token 在原始文本中的字节偏移(yylloc
    • 错误报告时高亮错误位置

关键代码:scanner_init()

/* src/backend/parser/scan.l */
core_yyscan_t
scanner_init(const char *str,
             core_yy_extra_type *yyext,
             const ScanKeywordList *keywordlist,
             const uint16 *keyword_tokens)
{
    Size slen = strlen(str);

    /* 1. 分配扫描器状态结构 */
    yyscan_t scanner;
    yylex_init(&scanner);

    /* 2. 设置输入缓冲区 */
    yy_scan_string(str, scanner);

    /* 3. 关联扩展数据 */
    yyext->keywords = keywordlist;
    yyext->keyword_tokens = keyword_tokens;
    yyext->backslash_quote = backslash_quote;
    yyext->escape_string_warning = escape_string_warning;
    yyext->standard_conforming_strings = standard_conforming_strings;
    yyset_extra(yyext, scanner);

    /* 4. 初始化位置跟踪 */
    yyext->scanbuflen = slen;
    yyext->scanbuf = str;

    return scanner;
}

逐步解释

  1. 分配扫描器状态

    • yylex_init() 创建 Flex 生成的扫描器状态结构
    • 状态为可重入(reentrant),支持多个独立实例
  2. 设置输入缓冲区

    • yy_scan_string() 将 SQL 文本字符串设置为输入源
    • Flex 内部复制字符串到缓冲区(双缓冲机制)
  3. 关联关键字表

    • keywords 指向 ScanKeywords 全局数组(所有 SQL 关键字)
    • keyword_tokens 指向关键字到 Token 类型的映射表
  4. 配置字符串处理行为

    • backslash_quote:控制反斜杠转义单引号的行为(GUC 参数)
    • standard_conforming_strings:是否符合 SQL 标准字符串语义

关键代码:base_yylex() Token 前瞻处理

/* src/backend/parser/parser.c */
int
base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
{
    base_yy_extra_type *yyextra = pg_yyget_extra(yyscanner);
    int cur_token;
    int next_token;

    /* 1. 处理前瞻 Token(用于模式切换) */
    if (yyextra->have_lookahead)
    {
        yyextra->have_lookahead = false;
        cur_token = yyextra->lookahead_token;
        *llocp = yyextra->lookahead_yylloc;
        if (yyextra->lookahead_end)
            *(yyextra->lookahead_end) = yyextra->lookahead_yyleng;
        return cur_token;
    }

    /* 2. 调用 Flex 生成的 Lexer 获取下一个 Token */
    cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);

    /* 3. Token 合并逻辑:处理多词关键字 */
    /*
     * 例如:NULLS FIRST 需要合并为 NULLS_FIRST,因为 LALR(1) 仅支持单 Token 前瞻
     */
    if (cur_token == NULLS_LA)  /* NULLS 后可能跟 FIRST/LAST */
    {
        next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
        if (next_token == FIRST_P)
            cur_token = NULLS_FIRST;
        else if (next_token == LAST_P)
            cur_token = NULLS_LAST;
        else
        {
            /* 不匹配,回退 next_token */
            yyextra->have_lookahead = true;
            yyextra->lookahead_token = next_token;
            yyextra->lookahead_yylloc = *llocp;
        }
    }
    /* (此处省略其他多词关键字处理) */

    return cur_token;
}

逐步解释

  1. 前瞻 Token 处理

    • 如果 have_lookahead 标志为真,直接返回 lookahead_token
    • 用于解析模式切换(如 RAW_PARSE_TYPE_NAME 模式注入 MODE_TYPE_NAME Token)
  2. 调用核心 Lexer

    • core_yylex() 是 Flex 生成的词法分析函数
    • 返回 Token 类型(整数),Token 值通过 lvalp 传递(联合体)
  3. 多词关键字合并

    • PostgreSQL SQL 语法包含多词关键字(如 NULLS FIRSTWITH TIME ZONE
    • LALR(1) 语法仅支持单 Token 前瞻,无法直接识别多词关键字
    • 解决方案:在 Lexer 层合并多词关键字为单个 Token
    • 示例:
      • 扫描到 NULLS,返回 NULLS_LA(Lookahead 标记)
      • 扫描下一个 Token,如果是 FIRST,合并为 NULLS_FIRST
      • 如果不是 FIRSTLAST,回退该 Token(设置前瞻标志)

Token 合并列表(部分)

多词关键字 合并后的 Token
NULLS FIRST NULLS_FIRST
NULLS LAST NULLS_LAST
WITH TIME ZONE WITH_TIME
WITHOUT TIME ZONE WITHOUT_TIME
GROUP BY 无需合并(BY 已是关键字)

关键功能 2:语法分析(Parser)

功能概述

语法分析器(Parser)根据 PostgreSQL SQL 语法规则,将 Token 序列构建为抽象语法树(AST)。Parser 由 Bison(GNU 语法分析器生成器)从源文件 gram.y 生成,输出为 gram.c

核心任务

  1. 语法规则匹配

    • 定义超过 600 条语法规则,覆盖 SQL 标准与 PostgreSQL 扩展
    • LALR(1) 解析算法,单 Token 前瞻
  2. AST 节点构造

    • 每条语法规则的语义动作(嵌入 C 代码)构造对应的 AST 节点
    • 节点通过 palloc() 在当前内存上下文中分配
  3. 错误恢复

    • 语法错误时,Bison 调用 yyerror() 函数
    • PostgreSQL 实现中,首次错误即中止解析

语法规则示例:SELECT 语句

/* src/backend/parser/gram.y */

/* 简化的 SELECT 语句规则 */
SelectStmt:
    simple_select
    | select_with_parens
    | select_clause sort_clause opt_for_locking_clause opt_select_limit
    /* ... 其他变体 ... */
    ;

simple_select:
    SELECT opt_all_clause opt_target_list
    into_clause from_clause where_clause
    group_clause having_clause window_clause
    {
        /* 语义动作:构造 SelectStmt 节点 */
        SelectStmt *n = makeNode(SelectStmt);
        n->targetList = $3;          /* 选择列表 */
        n->intoClause = $4;          /* INTO 子句 */
        n->fromClause = $5;          /* FROM 子句 */
        n->whereClause = $6;         /* WHERE 条件 */
        n->groupClause = $7;         /* GROUP BY */
        n->havingClause = $8;        /* HAVING */
        n->windowClause = $9;        /* WINDOW */
        $$ = (Node *) n;
    }
    ;

opt_target_list:
    target_list                      { $$ = $1; }
    | /* EMPTY */                    { $$ = NIL; }
    ;

target_list:
    target_el                        { $$ = list_make1($1); }
    | target_list ',' target_el      { $$ = lappend($1, $3); }
    ;

target_el:
    a_expr AS ColLabel
    {
        /* 构造 ResTarget 节点(带别名) */
        $$ = makeNode(ResTarget);
        $$->name = $3;
        $$->val = (Node *) $1;
        $$->location = @1;
    }
    | a_expr
    {
        /* 构造 ResTarget 节点(无别名) */
        $$ = makeNode(ResTarget);
        $$->name = NULL;
        $$->val = (Node *) $1;
        $$->location = @1;
    }
    | '*'
    {
        /* 通配符 SELECT * */
        ColumnRef *cr = makeNode(ColumnRef);
        cr->fields = list_make1(makeNode(A_Star));
        cr->location = @1;

        $$ = makeNode(ResTarget);
        $$->name = NULL;
        $$->val = (Node *) cr;
        $$->location = @1;
    }
    ;

语法规则说明

规则结构

  • 规则头(如 SelectStmt:)定义非终结符
  • 规则体(如 simple_select)定义该非终结符的可能展开
  • 语义动作({ ... } 中的 C 代码)在规则匹配时执行

符号引用

  • $n:引用规则中第 n 个符号的语义值(联合体 YYSTYPE
  • $$:当前规则的语义值(返回值)
  • @n:引用第 n 个符号的位置(YYLTYPE,字节偏移)

示例解析

SQL:SELECT id, name AS user_name FROM users WHERE id = 1

解析过程:

  1. 匹配 simple_select 规则
  2. opt_target_list 匹配两个 target_el
    • id:表达式为 ColumnRef(fields=['id']),无别名
    • name AS user_name:表达式为 ColumnRef(fields=['name']),别名为 user_name
  3. from_clause 匹配 users 表(RangeVar(relname='users')
  4. where_clause 匹配条件表达式(A_Expr(kind=AEXPR_OP, name='=', lexpr=ColumnRef('id'), rexpr=A_Const(1))
  5. 构造 SelectStmt 节点,包含上述字段

AST 结构示例

SelectStmt
├── targetList = List [
│   ├── ResTarget { name=NULL, val=ColumnRef('id'), location=7 }
│   ├── ResTarget { name='user_name', val=ColumnRef('name'), location=11 }
│   ]
├── fromClause = List [
│   └── RangeVar { relname='users', location=33 }
│   ]
├── whereClause = A_Expr {
│   kind = AEXPR_OP,
│   name = List ['='],
│   lexpr = ColumnRef('id'),
│   rexpr = A_Const { val=1, location=49 }
│   }
├── groupClause = NIL
├── havingClause = NULL
└── windowClause = NIL

关键代码:parser_init()

/* src/backend/parser/gram.y */
void
parser_init(base_yy_extra_type *yyext)
{
    /* 初始化解析树列表为空 */
    yyext->parsetree = NIL;

    /* 初始化错误标志 */
    yyext->have_error = false;
}

功能说明

  • 在调用 base_yyparse() 前初始化解析器状态
  • 清空解析树列表,准备接收新构建的 AST 节点
  • 错误标志用于检测解析过程中的错误(虽然实际未使用,错误通过异常机制传播)

关键代码:base_yyparse() 语法分析主循环

/* src/backend/parser/gram.c(Bison 生成) */
int
base_yyparse(core_yyscan_t yyscanner)
{
    /* (此处省略大量 Bison 生成的状态机代码) */

    /* 简化的逻辑伪代码 */
    int yystate = 0;  /* 初始状态 */
    int yytoken;

    while (true)
    {
        /* 1. 获取下一个 Token */
        yytoken = base_yylex(&yylval, &yylloc, yyscanner);

        /* 2. 查表决定动作(移进/规约/接受/错误) */
        int action = yytable[yystate][yytoken];

        if (action > 0)  /* 移进 */
        {
            /* 将 Token 压栈,切换状态 */
            push_stack(yytoken, yylval, yylloc);
            yystate = action;
        }
        else if (action < 0)  /* 规约 */
        {
            /* 根据规则号执行规约 */
            int rule = -action;
            execute_rule(rule);  /* 调用语义动作 */

            /* 弹出规则右部的符号,压入规则左部 */
            pop_stack(rule_length[rule]);
            push_stack(rule_lhs[rule], rule_result);

            /* 切换状态(goto 表) */
            yystate = yygoto[stack_top_state][rule_lhs[rule]];
        }
        else if (action == ACCEPT)  /* 接受 */
        {
            return 0;  /* 解析成功 */
        }
        else  /* 错误 */
        {
            yyerror("syntax error");
            return 1;  /* 解析失败 */
        }
    }
}

逐步解释

  1. 状态机驱动

    • Bison 生成的 LALR(1) 解析器是基于状态机的
    • 每个状态对应语法分析的某个阶段
    • 状态转移表(yytable)和 goto 表(yygoto)在编译时生成
  2. 移进(Shift)

    • 将当前 Token 压入符号栈
    • 切换到新状态,继续读取下一个 Token
  3. 规约(Reduce)

    • 根据规则号,将栈顶的多个符号替换为规则左部的非终结符
    • 执行语义动作(构造 AST 节点)
    • 更新状态(查 goto 表)
  4. 接受(Accept)

    • 所有 Token 已处理,语法匹配成功
    • 返回 0 表示成功
  5. 错误(Error)

    • Token 无法匹配任何规则
    • 调用 yyerror() 报告错误
    • 返回非零值表示失败

错误处理:yyerror()

/* src/backend/parser/gram.y */
static void
yyerror(core_yyscan_t yyscanner, const char *msg)
{
    parser_yyerror(msg);
}

void
parser_yyerror(const char *msg)
{
    const char *query_text = /* 当前 SQL 文本 */;
    int error_location = /* 错误位置 */;

    /* 报告语法错误 */
    ereport(ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg("%s", msg),
             parser_errposition(error_location)));
}

功能说明

  • Bison 在检测到语法错误时调用 yyerror()
  • ereport(ERROR) 触发 PostgreSQL 异常机制,跳转到错误处理代码
  • parser_errposition() 在错误消息中标注错误位置(在 SQL 文本下方显示 ^

示例错误输出

SELECT FROM users;

错误消息:

ERROR:  syntax error at or near "FROM"
LINE 1: SELECT FROM users;
               ^

典型使用场景与最佳实践

场景 1:简单 SELECT 查询解析

SQL 示例

SELECT id, name FROM users WHERE id = 1;

解析流程

  1. 词法分析

    • Token 序列:SELECT, id, ,, name, FROM, users, WHERE, id, =, 1, ;
  2. 语法分析

    • 匹配 SelectStmtsimple_select 规则
    • 构造 SelectStmt 节点,包含:
      • targetList:两个 ResTarget 节点(idname
      • fromClause:一个 RangeVar 节点(users 表)
      • whereClause:一个 A_Expr 节点(id = 1
  3. 返回结果

    • List 包含一个 RawStmt 节点
    • RawStmt->stmt 指向 SelectStmt 节点

代码示例

const char *sql = "SELECT id, name FROM users WHERE id = 1;";
List *parsetree_list = raw_parser(sql, RAW_PARSE_DEFAULT);

/* 提取第一个语句 */
RawStmt *rawstmt = linitial_node(RawStmt, parsetree_list);
SelectStmt *select = (SelectStmt *) rawstmt->stmt;

/* 访问解析树 */
printf("目标列数: %d\n", list_length(select->targetList));
printf("表名: %s\n", ((RangeVar *) linitial(select->fromClause))->relname);

场景 2:多语句批量解析

SQL 示例

BEGIN;
INSERT INTO users VALUES (1, 'Alice');
COMMIT;

解析流程

  • 返回 List 包含三个 RawStmt 节点:
    1. TransactionStmt(BEGIN)
    2. InsertStmt(INSERT)
    3. TransactionStmt(COMMIT)

代码示例

const char *sql = "BEGIN; INSERT INTO users VALUES (1, 'Alice'); COMMIT;";
List *parsetree_list = raw_parser(sql, RAW_PARSE_DEFAULT);

/* 遍历所有语句 */
ListCell *lc;
foreach(lc, parsetree_list)
{
    RawStmt *rawstmt = lfirst_node(RawStmt, lc);

    switch (nodeTag(rawstmt->stmt))
    {
        case T_TransactionStmt:
            printf("事务控制语句\n");
            break;
        case T_InsertStmt:
            printf("插入语句\n");
            break;
        default:
            printf("其他语句\n");
    }
}

场景 3:错误处理与诊断

SQL 示例(语法错误)

SELECT * FORM users;  /* FROM 拼写错误 */

解析流程

  1. Lexer 识别 FORM 为标识符(非关键字)
  2. Parser 期望 FROM 关键字,但收到标识符,触发语法错误
  3. yyerror() 调用 ereport(ERROR),输出错误消息

错误消息

ERROR:  syntax error at or near "FORM"
LINE 1: SELECT * FORM users;
                 ^

最佳实践

  • 客户端错误处理:捕获 PQresultStatus() 返回 PGRES_FATAL_ERROR
  • 错误码检查:通过 PQresultErrorField(result, PG_DIAG_SQLSTATE) 获取 SQLSTATE
  • 语法错误码42601ERRCODE_SYNTAX_ERROR

场景 4:准备语句优化

原理

准备语句(Prepared Statement)允许解析一次,执行多次,避免重复解析开销。

SQL 示例

PREPARE get_user AS SELECT * FROM users WHERE id = $1;
EXECUTE get_user(1);
EXECUTE get_user(2);

流程

  1. PREPARE 命令:

    • 解析 SQL(SELECT * FROM users WHERE id = $1
    • 生成解析树,缓存在会话内存中
    • 参数占位符 $1 保留为 ParamRef 节点
  2. EXECUTE 命令:

    • 跳过解析阶段,直接使用缓存的解析树
    • 参数值(12)在执行阶段绑定

性能提升

  • 避免重复的词法/语法分析(CPU 密集)
  • 适用于高频短查询(如 OLTP 场景)

最佳实践

  • 使用 libpq API:PQprepare() + PQexecPrepared()
  • ORM 框架(如 SQLAlchemy)自动使用准备语句
  • 注意:准备语句的生命周期为会话级别,会话结束后失效

配置与调优

相关 GUC 参数

参数名称 类型 默认值 说明
standard_conforming_strings bool on 是否符合 SQL 标准字符串语义(关闭时 \ 为转义字符)
escape_string_warning bool on 非标准字符串转义时是否发出警告
backslash_quote enum safe_encoding 控制 \' 的行为(off/on/safe_encoding)

配置建议

  • 生产环境:保持默认值(standard_conforming_strings = on),符合 SQL 标准
  • 迁移旧应用:如应用依赖 \ 转义,设置 standard_conforming_strings = off

性能优化

1. 使用准备语句

  • 适用场景:高频执行的相同结构 SQL
  • 效果:减少 10%-30% CPU 开销(取决于查询复杂度)

2. 减少语句长度

  • 超长 SQL(如大量 INSERT VALUES)会增加解析时间
  • 解决方案:使用 COPY 命令批量导入(绕过解析器)

3. 避免动态 SQL

  • 动态拼接 SQL 字符串无法使用准备语句
  • 解决方案:使用参数化查询

调试与诊断

调试工具

1. 查看解析树

启用调试输出(需重新编译,开启 --enable-debug):

/* 设置调试标志 */
debug_print_parse = true;

/* 执行查询 */
SELECT * FROM users;

输出示例(简化):

{QUERY
   :commandType 1
   :targetList (
      {TARGETENTRY
         :expr {VAR :varno 1 :varattno 1}
         :resname id
      }
      ...
   )
   ...
}

2. GDB 断点调试

gdb --args postgres --single -D /path/to/data postgres
(gdb) break raw_parser
(gdb) run
(gdb) next  # 单步执行
(gdb) print yyextra->parsetree  # 查看解析树

3. 日志记录

SET client_min_messages = DEBUG1;
SELECT * FROM users;

输出包含解析阶段的详细日志(如关键字查找、Token 识别)。

常见问题诊断

问题 1:语法错误难以定位

症状:错误消息指向错误位置不准确

原因:错误恢复机制可能延迟错误报告

解决方案

  • 检查错误位置前后的语法
  • 使用 SQL 格式化工具(如 pgFormatter)规范化 SQL

问题 2:关键字冲突

症状:旧代码使用的标识符成为新版本 PostgreSQL 的保留字

原因:新版本引入新的 SQL 关键字

解决方案

  • 使用双引号包围标识符(如 "user"
  • 查询保留字列表:SELECT * FROM pg_get_keywords();

问题 3:解析性能瓶颈

症状:复杂查询解析耗时过长

诊断

EXPLAIN (ANALYZE, BUFFERS, TIMING ON) SELECT ...;

查看 Planning Time(包含解析时间)

解决方案

  • 简化查询结构(减少嵌套子查询)
  • 使用 CTE(WITH 子句)拆分复杂查询
  • 使用准备语句缓存解析结果

总结

Parser 模块是 PostgreSQL 查询处理的入口,负责将 SQL 文本转换为结构化的解析树。其设计体现了以下核心原则:

  1. 分离关注点:词法、语法、语义三阶段分离,Parser 仅关注语法正确性
  2. 可扩展性:语法规则集中在 gram.y,易于添加新语法
  3. 错误友好:精确的位置跟踪,便于错误诊断
  4. 性能优化:完美哈希表、Token 前瞻、准备语句缓存

理解 Parser 模块的实现细节,有助于:

  • 诊断 SQL 语法错误
  • 扩展 PostgreSQL 语法(自定义 SQL 方言)
  • 优化高频查询的解析性能
  • 开发 SQL 工具(如格式化、静态分析)