- 数据库
- 流处理
- 后端
- 数据工程
【免费下载链接】risingwave
Event streaming platform for agentic AI. Continuously ingest, transform, and serve event streams in real time, at scale.
RisingWave 内置了一个基于 Rust 编写的 SQL 解析器 crate(risingwave_sqlparser),它是知名开源项目 sqlparser-rs 的一个深度定制分支,负责将用户提交的 SQL 文本解析为抽象语法树(AST),是前端(Frontend)执行计划生成流程的第一步。本文将以该 crate 的官方 README 为核心骨架,结合仓库源码,系统讲解解析器的整体架构、YAML 驱动的测试体系,以及如何通过两个步骤快速为新增 SQL 语法注册测试用例。
RisingWave 的 SQL 解析器:定位与背景
RisingWave 是流式事件处理平台,用户通过 SQL 定义数据源(Source)、物化视图(Materialized View)和下游 Sink。这些 SQL 语句在进入查询规划与执行阶段之前,必须先被转换成结构化的内部表示。承担这一职责的就是 src/sqlparser 目录下的risingwave_sqlparsercrate。
根据 src/sqlparser/README.md 的说明,该解析器是sqlparser-rs的一个 fork。sqlparser-rs 是一个纯 Rust 实现的 ANSI SQL:2011 词法分析器与解析器,能直接将 SQL 字符串解析为 AST;RisingWave 在其基础上:
- 扩展了流处理场景特有的语法,例如
CREATE SOURCE、ALTER FRAGMENT ... SET BACKFILL_RATE_LIMIT、ALTER SOURCE ... CONNECTOR WITH (...)、WEBHOOK连接器等(这些都能在 tests/testdata/alter.yaml 等测试文件中看到对应用例); - 将底层解析引擎迁移到winnow解析组合子库(见 src/sqlparser/Cargo.toml 中的
winnow = "1.0.1"依赖); - 建立了基于 YAML 快照文件的自动化测试体系,用于锁定每条 SQL 的「格式化输出」与「错误信息」。
从 crate 的文档注释(src/sqlparser/src/lib.rs)可以看到其核心能力描述:提供 ANSI:SQL 2011 的词法分析与解析,输出 AST,并附带了最简单的使用示例。该 crate 在仓库内被命名为risingwave_sqlparser,通过 workspace 管理版本号,遵循 Apache-2.0 许可证。
核心流程:如何为解析器新增一个测试用例
README 给出了最精炼的两步操作法,这是理解整个测试体系的钥匙:
- 复制一个 YAML 测试条目:在对应的 yaml 文件中复制一条现成的用例,把其中的
input改成你想要测试的 SQL 语句。 - 重新生成期望输出:运行
./risedev update-parser-test,让工具自动重新生成formatted_sql(期望的格式化输出)。
这两步背后对应着一条完整的「快照测试」工作流,下面结合源码逐层拆解。
第一步:理解 YAML 测试文件的结构
测试数据存放在 src/sqlparser/tests/testdata 目录下,每个文件对应一类 SQL 语法主题,例如:
select.yaml:SELECT 查询及投影、函数、字段访问、EXCEPT、LIMIT/FETCH等alter.yaml:ALTER 相关语句(ALTER USER、ALTER SOURCE、ALTER FRAGMENT等)create.yaml、drop.yaml、insert.yaml、set.yaml、show.yaml、union.yaml、as_of.yaml、match_recognize.yaml、lambda.yaml、copy.yaml、vacuum.yaml等 20 余个主题文件
每个文件由若干条TestCase组成。测试用例的数据结构定义在 src/sqlparser/tests/parser_test.rs:
pub struct TestCase { pub input: String, // 待解析的 SQL pub formatted_sql: Option<String>, // 期望的格式化 SQL 输出(成功用例) pub error_msg: Option<String>, // 期望的解析错误信息(失败用例) pub formatted_ast: Option<String>, // 期望的 AST Debug 输出(可选,用于锁定 AST 形态) }以 tests/testdata/select.yaml 中的一条实际用例为例:
- input: SELECT * FROM generate_series('2'::INT,'10'::INT,'2'::INT) formatted_sql: SELECT * FROM generate_series(CAST('2' AS INT), CAST('10' AS INT), CAST('2' AS INT)) formatted_ast: 'Query(Query { with: None, body: Select(...) })'注意观察:input中用户写的'2'::INT简写,在formatted_sql中被规范化为CAST('2' AS INT)。这正是「格式化输出」的价值——它不仅验证 SQL 能被成功解析,还验证解析器产出的 AST 在反向打印(unparse)时与预期完全一致,从而锁定语法规范和规范化规则。
失败的用例则记录error_msg,例如:
- input: SELECT * FROM t LIMIT 1 FETCH FIRST ROWS ONLY error_msg: |- sql parser error: Cannot specify both LIMIT and FETCH LINE 1: SELECT * FROM t LIMIT 1 FETCH FIRST ROWS ONLY ^这类用例不仅断言「解析必须失败」,还逐字符比对错误消息文本,保证错误提示的稳定性。
第二步:运行./risedev update-parser-test重新生成期望输出
在新增或修改input之后,需要让期望输出与解析器的实际行为对齐。执行:
./risedev update-parser-test这个命令定义在 src/sqlparser/sqlparser_test.toml 中,其本质是:
UPDATE_PARSER_TEST=1 cargo test --test parser_test即设置环境变量UPDATE_PARSER_TEST=1后运行集成测试。测试入口 src/sqlparser/tests/parser_test.rs 会遍历tests/testdata目录下的所有.yaml/.yml文件;当检测到UPDATE_PARSER_TEST环境变量时,走更新模式(update_test_file),否则走校验模式(run_test_file)。
更新模式(UPDATE_PARSER_TEST=1)做了什么
update_test_file(parser_test.rs)会对文件中每一条用例重新执行解析:
- 解析成功:用当前解析器实际产出的
formatted_sql(以及可选的formatted_ast)覆盖旧值,error_msg置空; - 解析失败:用当前实际错误文本覆盖
error_msg,formatted_sql/formatted_ast置空; - 文件头会重写为
# This file is automatically generated by src/sqlparser/tests/parser_test.rs.的自动生成标记。
同时会在终端用红/绿颜色打印发生变化的条目(红色为旧值、绿色为新值),方便你审查本次更新的差异。
校验模式(普通cargo test --test parser_test)做了什么
run_test_case(parser_test.rs)则严格比对:
- 若用例声明了
error_msg但解析成功,报「Expected failure」; - 若解析失败但实际错误文本与
error_msg不一致,报「Expected error message / Actual error message」差异; - 若用例没有
error_msg却缺少formatted_sql,报「Illegal test case without given the formatted sql」; - 成功用例会比对
formatted_sql与format!("{}", ast)是否一致(Display实现负责 AST 反格式化),若声明了formatted_ast还会比对format!("{:?}", ast)。
该测试使用libtest-mimic作为自定义测试驱动器(harness = false,见 Cargo.toml),每个 YAML 文件被包装成一个Trial,因此可以像标准测试一样按文件过滤执行。
新增测试用例的最佳实践
结合上述机制,为 RisingWave 解析器新增语法支持时推荐这样操作:
- 根据 SQL 所属语法主题,选择最贴切的 YAML 文件(SELECT 相关去
select.yaml,ALTER 相关去alter.yaml,DDL 去create.yaml/drop.yaml等); - 复制一条结构最相似的同类型用例,修改
input为你的 SQL; - 运行
./risedev update-parser-test,检查终端打印的绿/红差异是否符合预期; - 对成功用例,检查
formatted_sql的规范化结果是否符合项目风格(如::是否被规范为CAST、=是否被规范为TO); - 对应当报错的语法,确认
error_msg的定位符号(^)和错误文案是否准确; - 重新运行普通测试确认全部通过:
cargo test --test parser_test
解析器 API 的使用方式
除测试体系外,该 crate 对外暴露了简洁的解析入口。
库方式:Parser::parse_sql
根据 src/sqlparser/src/lib.rs 的官方示例:
use risingwave_sqlparser::parser::Parser; let sql = "SELECT a, b, 123, myfunc(b) \ FROM table_1 \ WHERE a > b AND b < 100 \ ORDER BY a DESC, b"; let ast = Parser::parse_sql(sql).unwrap(); println!("AST: {:?}", ast);parse_sql返回语句(Statement)的Vec。返回的 AST 类型定义在 src/sqlparser/src/ast 目录下,包含statement.rs、query.rs、data_type.rs、value.rs、operator.rs、ddl.rs等模块,覆盖查询、DDL、数据类型、运算符等各类语法节点。
命令行工具:sqlparserbin
Cargo.toml 中声明了一个名为sqlparser的二进制入口(src/sqlparser/src/bin/sqlparser.rs),它从 stdin 读取一行 SQL 并打印 AST:
echo "SELECT 1;" | cargo run --bin sqlparser示例程序:tokenizer + parser
examples/parse.rs 展示了更底层的用法——先通过Tokenizer将 SQL 切成 token 流,再交给Parser生成 AST,并利用 AST 的Display实现反打印:
use risingwave_sqlparser::parser::*; use risingwave_sqlparser::tokenizer::Tokenizer; let tokens = Tokenizer::new(&sql).tokenize_with_location().unwrap(); println!("tokens: {:?}", tokens); let ast = Parser::parse_sql(&sql).unwrap(); for stmt in ast { println!("unparse: {}", stmt); }这个例子也展示了「格式化 SQL」的来源:formatted_sql正是通过format!("{}", ast)得到的,即 AST 的Display实现。
底层实现:winnow 驱动的解析引擎
从源码结构看,该解析器采用了两层解析架构:
- 词法层:src/sqlparser/src/tokenizer.rs 负责把 SQL 文本切成 token(标识符、关键字、字符串、数字、标点等),提供
tokenize_with_location()这类带位置信息的 API,这正是错误消息中LINE 1: ... ^定位信息的来源; - 语法层:src/sqlparser/src/parser.rs(约 6900 行)基于 winnow 解析组合子实现,通过
alt、dispatch、repeat、separated_pair等组合子递归下降地构建 AST。关键的错误类型定义如下:
#[derive(Debug, Clone, PartialEq)] pub enum ParserError { TokenizerError(String), ParserError(String), }- 关键字表:src/sqlparser/src/keywords.rs 定义了完整的 SQL 关键字集合(含 RisingWave 扩展关键字);
- v2 解析辅助模块:src/sqlparser/src/parser_v2 提供
dollar_quoted_string、keyword、literal_u64、single_quoted_string等常用解析原语。
从 crate 文档看,解析器覆盖 ANSI:SQL 2011 语法面;结合测试文件(如as_of.yaml、asof_join.yaml、match_recognize.yaml、lambda.yaml)可以看出,RisingWave 还在此基础上扩展了AS OF时间旅行语法、ASOF JOIN、MATCH_RECOGNIZE、lambda 表达式等面向流处理与高级分析场景的语法能力。错误消息统一以sql parser error: ...为前缀,并附带行号与^定位。
小结
RisingWave 的 SQL 解析器是一个典型的「上游开源 + 深度定制」案例:以 sqlparser-rs 为起点,配合 winnow 解析框架实现了 ANSI SQL:2011 及其流处理扩展语法,并用一套 YAML 快照测试体系将「新增语法」这件事简化为「改 input + 跑一条命令」两个动作。无论你是想为 RisingWave 贡献新语法,还是想理解 SQL 解析器测试的最佳工程实践,都可以从 tests/testdata 下的用例与 tests/parser_test.rs 的测试驱动逻辑入手,快速上手。
- 数据库
- 流处理
- 后端
- 数据工程
【免费下载链接】risingwave
Event streaming platform for agentic AI. Continuously ingest, transform, and serve event streams in real time, at scale.
相关推荐
SQL解析器Rust版:sqlparser-rs完全指南
SQL解析器Rust版:sqlparser rs完全指南 项目介绍 sqlparser rs 是一个专为 Rust 编程语言设计的可扩展 SQL词法分析器和解析
开发工具终极指南:如何在PC上免费畅玩任天堂Switch游戏
终极指南:如何在PC上免费畅玩任天堂Switch游戏 想在电脑大屏幕上体验《塞尔达传说:旷野之息》的壮丽世界,或是与朋友联机畅玩《任天堂明星大乱斗》?Ryuji
数据库流处理后端数据工程RIOT OS RTC 外设驱动测试指南:从测试用例到 alarm 中断机制深度解析
RIOT OS RTC 外设驱动测试指南:从测试用例到 alarm 中断机制深度解析 本篇技术指南以 RIOT OS 仓库中的 tests/periph/rtc
物联网嵌入式操作系统实时系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考