C++中不建议手写SQL解析器,应优先选用lemon或duckdb parser;lemon线程安全、无全局变量,duckdb parser纯C++、MIT协议且覆盖SQL:2016特性,但均不负责语义分析。

直接说结论:C++ 里不建议从零手写完整 SQL 解析器来支撑“复杂 SQL”,除非你明确在做数据库内核研究或教学项目;生产级轻量前端应基于成熟 parser generator(如 lemon、bison)或已验证的开源解析库(如 duckdb 的 parser 模块),否则会卡死在 GROUP BY 与窗口函数嵌套、WITH RECURSIVE、类型推导等边界 case 上。
用 lemon 而不是 bison 写 SQL 语法分析器更稳
lemon 是 SQLite 官方使用的 LALR(1) parser generator,无全局变量、线程安全、错误恢复机制成熟,特别适合嵌入式/轻量场景。相比 bison 默认的 YACC 兼容模式,它天然规避了 yyglobal 冲突和重入问题。
- 必须用
%extra_argument {void* pParser}传上下文,避免解析过程中访问外部状态引发竞态 - 每个语法规则需显式调用
sqlite3ParserARG_FETCH获取参数,不能依赖隐式yyval传递 AST 节点指针 -
lemon不生成.h文件,所有 token 定义(如TOKEN_SELECT)要手动在 C++ 头中声明并保持与.y文件同步 - 遇到
ORDER BY expr NULLS FIRST这类 PostgreSQL 扩展语法时,得在%fallback或自定义 error rule 中拦截,而不是让 parser 直接报错退出
duckdb 的 parser 模块可直接头文件复用
duckdb 的 src/parser/ 是纯 C++ 实现、无运行时依赖、MIT 协议,且已覆盖标准 SQL-92 到 SQL:2016 大部分特性(包括 CTE、窗口函数、VALUES 表达式)。它不绑定执行引擎,只做 parse → unique_ptr<parsedexpression></parsedexpression>。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
- 只需
#include "duckdb/parser/parser.hpp"和链接libduckdb_parser.a(可静态编译进你的二进制) - 调用
Parser::ParseQuery("SELECT * FROM t WHERE x > 1"),返回vector<unique_ptr>></unique_ptr>,每个 statement 可 downcast 成SelectStatement、InsertStatement等 - 注意
ParsedExpression树中字段名是未解析的ColumnRefExpression,不含 schema 信息——词法分析完成,但语义绑定(比如判断t.x是否存在)得你自己做 - 若需支持自定义函数(如
my_func(a, b)),要在ParserOptions里注册function_name_resolver回调,否则解析直接失败
词法分析阶段必须区分关键字与标识符的大小写策略
SQL 标准规定关键字不区分大小写(SELECT 和 select 等价),但标识符(表名、列名)默认大小写敏感(除非用双引号包裹)。手写 lexer 时容易在这里翻车。
立即学习“前端免费学习笔记(深入)”;
- 用
std::tolower统一转换 token 字符串再查关键字表,但注意 locale 影响——必须设为Clocale,否则std::tolower('İ')在 Turkish locale 下行为异常 - 双引号标识符(
"UserTable")要原样保留大小写,并剥离两端引号;反引号(`user_table`)和方括号([user table])需按目标方言单独处理 - 数字字面量要区分
123(整型)、123.0(浮点)、X'ABCD'(十六进制 blob),lexer 返回不同 token type,不能全塞进一个TOKEN_NUMBER - 注释(
--,/* */)必须在 lexer 层吞掉,不能传给 parser——否则SELECT 1 -- comment会被截断成SELECT 1后 parser 报 unexpected EOF
真正难的不是把 SELECT a,b FROM t 拆开,而是当用户写下 SELECT ROW_NUMBER() OVER (PARTITION BY x ORDER BY y DESC) AS rn FROM t WHERE rn > 10 时,你能准确识别 rn 在 WHERE 中是非法引用(窗口函数别名不可在 WHERE 使用),这已经超出词法/语法层,进入语义分析范畴。轻量级前端如果只做到 parse 阶段,就得在文档里白纸黑字写清:“本模块不校验列可见性、不检查函数签名、不展开 CTE”。否则,用户第一眼觉得能用,第二眼就掉坑里。

















