pgrust 解析器揭秘:SQL 语法分析如何在 Rust 中实现
pgrust 解析器揭秘SQL 语法分析如何在 Rust 中实现【免费下载链接】pgrustPostgres rewritten in Rust, now faster than Postgres and Clickhouse项目地址: https://gitcode.com/GitHub_Trending/pg/pgrustpgrust 是一个用 Rust 重写 PostgreSQL 的开源项目目标不仅是能用还要比原生 Postgres 更快。而实现这一切的地基就是它那套完整的SQL 解析器。从输入一段 SQL 文本到生成可供优化器使用的查询树pgrust 的解析链路复刻了 PostgreSQL 18.3 的完整逻辑——包括词法扫描、语法归约、以及语义分析三个阶段。这篇文章将带你一步步拆解 pgrust 解析器的内部构造看看 SQL 语法分析在 Rust 中究竟是如何实现的。为什么解析器是重写数据库的第一块基石任何数据库在执行 SQL 前都必须先把文本读懂。PostgreSQL 原生使用 flex 生成词法扫描器、用 bison 生成 LALR 语法分析器。pgrust 要重写 Postgres就必须原样复刻这一整套解析行为——因为后面 46,000 条回归测试都以 Postgres 的输出为准绳。pgrust 解析器的整体数据流可以概括为四步阶段对应 C 源文件pgrust 中的模块职责词法分析scan.lcrates/backend/parser/scan_fgram/把 SQL 文本切成 token词法过滤parser.ccrates/backend/parser/driver/多词合并、Unicode 转义语法分析gram.ycrates/_support/pgrust/gram_c2rust_fgram/按文法归约出语法树安全转换无对应 C 文件crates/backend/parser/gram_core/原始指针树 → 安全的 Rust 所有权树语义分析analyze.ccrates/backend/parser/parser_analyze/生成最终 Query 树接下来我们逐一深入。第一步词法分析把 SQL 文本切成 Token词法分析是解析器的翻译官。它的任务是把SELECT * FROM users WHERE age 18这种纯文本切成SELECT、*、FROM、users等一个个有意义的 token。PostgreSQL 的词法器由 flex 生成包含大量复杂的规则字符串字面量的各种引号状态、$tag$ ... $tag$美元引用、E...转义字符串、注释、多字节字符编码等。pgrust 没有直接调用 C 的 flex 产物而是手写了一个忠实还原 flex 行为的状态机——它逐条复刻 flex 的最长匹配优先、同长则先出现者优先规则连 token 的位置偏移yylloc都做到字节级一致。这个模块位于crates/backend/parser/scan_fgram/其中scan_core.rs是扫描器核心。pgrust 在文档注释里明确承诺token 流与 C 扫描器字节级完全一致。 一个有趣的设计pgrust 用thread_local!的Cell来存放backslash_quote、standard_conforming_strings这类扫描器全局配置替代了 C 里的裸全局变量既保留了行为又避免了数据竞争。第二步base_yylex 过滤器多词 Token 的魔法合并拿到基础 token 后还不能直接喂给语法分析器。PostgreSQL 的文法里有一些多词 token需要提前合并才能正确归约例如NOT LIKE→ 合并为一个NOT_LAtokenWITH TIME→ 合并为WITH_LAFORMAT JSON→ 合并为FORMAT_LA这个合并逻辑在 C 里位于parser.c的base_yylex。pgrust 把它原样移植到了crates/backend/parser/driver/src/lib.rs并且还做了个很聪明的改造C 的扫描器是有状态的、会原地修改缓冲区而 pgrust 把它建模成无状态扫描——每次从字节游标处恢复返回 token 和下一个恢复点过滤逻辑本身一行不改就能跑通。此外Unicode 转义标识符U...的去转义逻辑也在这里对应udeescape.rs。第三步语法分析31 万行的 bison 文法移植这是 pgrust 解析器最硬核的部分。PostgreSQL 的gram.y经过 bison 生成后是一个包含约4800 条归约规则的 LALR 状态机。手工用 Rust 重写这套文法几乎不可能——pgrust 的选择是用 c2rust 工具把 bison 生成的gram.c机械地翻译成 Rust。结果就是crates/_support/pgrust/gram_c2rust_fgram/src/gram.rs这个文件足足31 万行里面是完整的 LR 状态转移表和 4800 多个动作块。这相当于把整个 PostgreSQL 语法规则原封不动搬进了 Rust 世界。这个模块是经过审计的、受限的unsafe代码它在内部用 C 风格#[repr(C)]节点结构构建一棵原始指针树。关键在于不安全代码被严格关在这些fgramFFI/生成crate 里对外绝不泄漏一个裸指针。第四步convert 转换器安全边界的守护者语法分析器产出的是一棵*mut Node原始指针树这显然不符合 Rust 的所有权和安全模型。于是crates/backend/parser/gram_core/承担了安全边界的角色gram_core/src/convert.rs在语法分析返回后一次性遍历整棵原始指针树用统一的 5 条映射规则把它重建为 pgrust 自己的拥有所有权的nodes解析树基于Mcx内存上下文、PgBox、PgVec、PgString转换完成后所有传给上层模块的数据都是安全且自有的裸指针一个都不剩。任何语法错误都会通过错误消息 SQLSTATE 游标位置的方式返回和 C 版本的ereport(ERROR)表现完全一致。有趣的是pgrust 还把 C 里基于setjmp/longjmp的错误跳转改写成了 Rust 的panic catch_unwind机制——不再编译任何 C 代码。第五步语义分析从语法树到查询树语法分析只是读懂结构真正决定 SQL 语义的是分析阶段。crates/backend/parser/parser_analyze/移植了analyze.c把 raw parse tree 转换成可供优化器使用的Query树。目前该模块已完成 SELECT 全链路——从transformStmt分发、transformSelectStmt到 FOR UPDATE/SHARE 锁子句再到查询 IDqueryId的生成用于pg_stat_activity等统计。pgrust 还通过maybe_jumble_query实现了查询文本的 jumble 哈希这与pg_stat_statements的查询归一化机制同源。性能对比Rust 重写后到底快多少根据项目介绍pgrust 的最新版本✅ 100% 通过 Postgres 回归测试套件✅ 事务型工作负载比 Postgres 快50%✅ 分析型工作负载比 Postgres 快约300 倍✅ 在 ClickBench 基准上约为 ClickHouse 的 2 倍慢且还在持续优化采用一线程一连接替代 Postgres 的一进程一连接模型是性能跃升的关键而这一切都建立在解析器稳定输出正确查询树的前提之上。写在最后为什么值得关注 pgrust 的解析器pgrust 的解析器为我们展示了一条极具启发性的重写路径行为优先用 c2rust 机械翻译搬来 31 万行文法先保证 100% 兼容安全隔离把 unsafe 关在笼子里边界处统一转换为安全的所有权模型逐步替换先 SELECT 后 DDL一步步把 C 逻辑替换成手写的 Rust 实现。对想学习数据库内核、或者关注如何用 Rust 重写大型 C 项目的开发者来说pgrust 的解析器是一个绝佳的活教材。下一次当你敲下一条 SQL 时不妨想想这段文本正在经历词法切分、多词合并、语法归约、指针树转换、语义分析五重关卡——而这一切如今都在 Rust 的内存安全保证下高效运行。【免费下载链接】pgrustPostgres rewritten in Rust, now faster than Postgres and Clickhouse项目地址: https://gitcode.com/GitHub_Trending/pg/pgrust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考