alitrack

DuckDB v2.0 换掉了用了8年的SQL解析器

DuckDB 在 8 月 17 日预告了 v2.0 的十项新特性(服务器模式、触发器、VARIANT 类型、异步 I/O 等),三天后,团队又发了一篇深度技术博文,专门讲一个听起来很底层的改动——换解析器。

DuckDB 从 2018 年第一个 commit 就在用从 PostgreSQL 继承来的 LALR(1) 解析器,用了八年。v2.0 全部换成自己写的 PEG 解析器。

● ● ●

解析器是干什么的

一条 SQL 查询从输入到执行,经过四个阶段:分词器(Tokenizer)把字符串拆成 token → 解析器(Parser)判断语法是否合法 → 转换器(Transformer)把解析树转成 DuckDB 内部 AST → 绑定器(Binder)检查表和字段是否存在。

解析器只回答一个问题:这句 SQL 的语法对不对。它不关心表是否存在,那是绑定器的事。

DuckDB 原来的解析器是从 PostgreSQL 继承的 Bison 生成的 LALR(1) 解析器。好处是成熟、经过考验,但也带来了一个致命问题:当你想加新语法时,很小的改动都可能和现有规则产生冲突(shift/reduce 或 reduce/reduce 冲突)。随着 DuckSQL(DuckDB 的 SQL 方言)不断增长,修改语法的难度越来越高。

DuckDB 解析器架构对比

DuckDB 解析器架构对比

● ● ●

为什么选 PEG

PEG(Parsing Expression Grammar)是另一种描述语言的方式。和 LALR 不同,PEG 的备选规则按顺序尝试,第一个匹配的胜出,不存在 shift/reduce 冲突。

DuckDB 并不是第一个这么做的——Python 在 3.9 版本也从 LL(1) 解析器换成了 PEG 解析器,理由完全一样:让语言更容易演化。

DuckDB 团队在 2024 年 11 月发过一篇论文,探讨了 PEG 作为可扩展数据库解析器的可行性。当时原型只能解析 SQL 的一个子集。从 v1.2 开始,PEG 解析器先用于 CLI 的自动补全;v1.5 以实验性选项引入;到 v2.0,正式成为默认解析器。

● ● ●

从原型到生产的三个关键问题

1. 覆盖全部 DuckSQL 方言

新解析器必须能解析 DuckDB 接受的所有 SQL 语法,包括所有语句类型、表达式类型、运算符优先级和结合性、关键字分类(哪些是保留关键字不能用作表名),以及正确的错误报告。

2. Packrat 解析:避免指数级回退

PEG 的一个常见问题是回溯时的重复工作。一个简单的例子——大量未匹配的左括号:

SELECT ((((((((((((((((((;

在 v1.5 的实验性 PEG 解析器中,每多一个左括号,解析时间大约翻倍:18 个括号 5.3 秒,19 个 10.6 秒。解决方案是 packrat 解析——一种记忆化技术,缓存在同一 token 位置应用同一匹配器的结果。启用后,同样的查询在 0.001 秒内拒绝。

3. 兼容现有 AST

新解析器产生的 AST 必须和旧解析器完全一致,这样绑定器和后面的执行管线不需要任何改动。PEG 解析器替换的只是解析前端,整个执行管道保持不变。

● ● ●

新语法

解析器换掉之后,DuckDB 立刻加了几项新语法:

  • 表达式语句
    :不需要写 SELECT,直接写 current_date(), current_time() 就能执行
  • CONNECT 语句
    :连接到远程数据库,后续查询路由到该服务器,直到执行 DISCONNECT。配合 Quack 协议使用
  • 外部资源管理
    :CREATE EXTERNAL RESOURCE / REGISTER EXTERNAL RESOURCE / CONNECT TO EXTERNAL RESOURCE——通过扩展管理 DuckDB 外部的资源
  • COPY TO 增强
    :支持 PARTITION BY 和 ORDER BY 子句

这些语法在旧解析器上也能加,但代价会高得多。

● ● ●

真正的大招:扩展可以自己加语法

这是 PEG 解析器最核心的区别。

现有扩展(如 psql、duckpgq)如果要加新语法,只能通过 fallback 机制——DuckDB 先尝试解析,失败后再交给扩展。这有两个问题:扩展自己得解析整个 SQL,而且多个扩展的语法无法组合。

PEG 解析器允许扩展 hook 到 DuckDB 的语法树的任意部分,增加自己的规则,同时继续复用 DuckSQL 的其他部分。扩展只需要定义自己新增的语法规则和转换函数,不需要自己实现表达式、表引用、GROUP BY 等。

举个例子,Google 的 pipe query syntax(|> WHERE、|> AGGREGATE ... GROUP BY)只需要注册一个 PipeSelectAtom 作为 SelectAtom 的额外备选,加上几个 PipeOperator 规则,就能在 DuckDB 里跑 pipe 语法了。

FROM range(6) t(i)
  |> WHERE i % 2 = 0
  |> SELECT i, doubled: i * 2
  |> ORDER BY i DESC;

这个 API 目前还是预览版,可能在 v2.0 正式发布前有变化。

● ● ●

对用户的影响

这次解析器替换对普通用户应该是透明的——所有现有查询继续正常工作就行。如果发现某个查询的行为变了,给 DuckDB 提 issue。

真正的影响在生态上。PEG 解析器让 DuckDB 的语法扩展能力从"几乎不可能"变成了"设计上支持"。v2.0 发布后,应该会出现一批带自定义 SQL 语法的扩展。

● ● ●

参考来源

  1. 01
    DuckDB 官方博文: duckdb.org/2026/08/20/duckdb-20-peg-parser
  2. 02
    DuckDB v2.0 预览: duckdb.org/2026/08/17/duckdb-20-highlights
  3. 03
    运行时可扩展解析器论文: duckdb.org/2024/11/22/runtime-extensible-parsers
  4. 04
    CIDR 2025 论文: vldb.org/cidrdb/papers/2025/p18-muhleisen.pdf
  5. 05
    Python 3.9 PEG 解析器: peps.python.org/pep-0617
  6. 06
    DuckDB v2.0 PEG parser PR: github.com/duckdb/duckdb/pull/22194
  7. 07
    Pipe query syntax PR: github.com/duckdb/duckdb/pull/24919