news 2026/9/25 9:53:11

RisingWave SQL 解析器深度指南:从 sqlparser-rs 分支到 YAML 驱动的新增测试用例实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RisingWave SQL 解析器深度指南:从 sqlparser-rs 分支到 YAML 驱动的新增测试用例实战
  • 数据库
  • 流处理
  • 后端
  • 数据工程

【免费下载链接】risingwave

Event streaming platform for agentic AI. Continuously ingest, transform, and serve event streams in real time, at scale.

项目地址:https://gitcode.com/gh_mirrors/ri/risingwave
点击查看免费下载

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 给出了最精炼的两步操作法,这是理解整个测试体系的钥匙:

  1. 复制一个 YAML 测试条目:在对应的 yaml 文件中复制一条现成的用例,把其中的input改成你想要测试的 SQL 语句。
  2. 重新生成期望输出:运行./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 解析器新增语法支持时推荐这样操作:

  1. 根据 SQL 所属语法主题,选择最贴切的 YAML 文件(SELECT 相关去select.yaml,ALTER 相关去alter.yaml,DDL 去create.yaml/drop.yaml等);
  2. 复制一条结构最相似的同类型用例,修改input为你的 SQL;
  3. 运行./risedev update-parser-test,检查终端打印的绿/红差异是否符合预期;
  4. 对成功用例,检查formatted_sql的规范化结果是否符合项目风格(如::是否被规范为CAST、=是否被规范为TO);
  5. 对应当报错的语法,确认error_msg的定位符号(^)和错误文案是否准确;
  6. 重新运行普通测试确认全部通过:
    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.

项目地址:https://gitcode.com/gh_mirrors/ri/risingwave
点击查看免费下载

相关推荐

上一篇:UAssetGUI终极指南:5分钟掌握虚幻引擎资源文件转换
下一篇:vscode-copilot-chat 调试日志全解析:用 JSONL 会话日志定位 Agent 异常行为

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 9:45:22

风险情报驱动的数字供应链安全治理:从SBOM到运行时防护

过去这两年&#xff0c;做应用安全的人应该都有一个共同感受&#xff1a;漏洞已经不只是“修不修”的问题&#xff0c;而是根本来不及修。Log4j2漏洞爆出来的时候&#xff0c;很多企业连夜排查&#xff0c;最后发现内网躺着几百条调用链&#xff1b;XZ-utils后门事件又给整个行…

作者头像 李华
网站建设 2026/9/25 9:37:26

PHP对接EOS区块链:从RPC到签名推送的完整指南

我第一次在搜索框里敲下php <<<eos的时候&#xff0c;搜索结果有点滑稽——左边是 PHP heredoc 语法讲解&#xff0c;右边是 EOS 区块链相关的帖子。这个组合并非巧合&#xff1a;<<<EOS在 PHP 里是合法的 heredoc 定界符&#xff0c;EOS 同时又是一条公链的…

作者头像 李华
网站建设 2026/9/25 9:34:21

SSH Secure Shell Client 从入门到迁移:密钥、隧道与排错全解析

1. 先从背景说起&#xff1a;SSH Secure Shell Client 到底是什么&#xff0c;为什么还有人用它1.1 最初它是给谁用的SSH Secure Shell Client 是早期 Windows 环境下最常见的商业 SSH 客户端之一。现在很多人已经习惯了用 Windows Terminal 敲ssh命令&#xff0c;或者直接用 V…

作者头像 李华
网站建设 2026/9/25 9:32:54

Oracle 存储过程实战:用 TaoToken 统一 Key 打通 Cline 配置与调试链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 9:31:33

Xred木马深度剖析:PHP WebShell的隐蔽驻留与实战排查防御

1. 从一次应急响应说起&#xff1a;Xred木马到底是什么我第一次接触到Xred木马&#xff0c;是在一次内部安全巡检中。当时一台测试服务器的CPU占用率长期飘红&#xff0c;排查了半天也没找到明显的异常进程&#xff0c;直到用netstat看到一条非常可疑的外连连接&#xff0c;顺藤…

作者头像 李华
网站建设 2026/9/25 9:16:35

Go语言实战:从零实现云原生链路诊断工具

写这篇文章之前&#xff0c;我先说个真实经历。上个月在测试环境联调两个微服务&#xff0c;A服务在Node-1上的Pod里怎么都连不上Node-2上的B服务&#xff0c;抓包抓了半天&#xff0c;发现数据包倒是发出去了&#xff0c;但就是没有回包。当时我手里只有现成的ping和telnet&am…

作者头像 李华