一种受SQL启发、专为事件溯源设计的查询语言(2025年)
摘要
EventQL是一种受SQL启发、专为事件溯源设计的查询语言,它提供对事件属性、主题层级结构的一流支持,并具备类似SQL的表达能力,以便高效查询事件流。
暂无内容
查看缓存全文
缓存时间: 2026/05/16 00:35
# EventQL: 一种专为事件溯源设计的类SQL查询语言
来源:https://yoeight.github.io/blog/2025/12/21/EventQL_A_SQL_Inspired_Query_Language_Designed_For_Event_Sourcing.html
## 诺曼底的代码
我的个人博客
---
项目由YoEight(https://github.com/YoEight)维护
托管于GitHub Pages — 主题来自mattgraham(https://twitter.com/mattgraham)
事件溯源已逐渐成为一种流行的架构模式,但高效查询事件流仍然是个挑战。尽管事件是只追加且不可变的,但要在成千上万的事件中定位特定事件或分析模式,仍需精心设计查询。这正是EventQL的优势所在。
## 事件查询的核心难题
在处理事件溯源系统时,你所面对的数据结构与传统数据库截然不同:
- 事件拥有丰富的元数据:类型、主体、时间戳、数据负载
- 事件通过主体层次结构组织(例如,`/books/42`、`/users/123/orders/456`)
- 你需要对事件流进行过滤、聚合和转换
- 性能在很大程度上依赖于合理利用索引
传统的NoSQL查询接口往往力不从心,因为它们并非针对这类事件特有特性而设计。
## EventQL 登场
EventQL 是一种查询语言,最初由 The Native Web 为其 EventSourcingDB(https://www.thenativeweb.io/products/eventsourcingdb)设计。它之所以特别,是因为在保留 SQL 熟悉的表达力的同时,完美捕捉了事件查询的精髓。
下面是一个简单示例:
``
FROM e IN events
WHERE e.type == "io.eventsourcingdb.library.book-acquired"
AND e.data.price > 20
PROJECT INTO {
id: e.id,
title: e.data.title,
price: e.data.price
}
``
如果你写过 SQL,会觉得这个语法非常亲切。但请注意,它是为事件量身定制的:我们按事件类型过滤,访问嵌套的数据负载,并重塑输出结构。
## 为何 EventQL 的设计至关重要
### 1. **事件属性作为一等公民**
EventQL 将事件元数据视为查询语言中的一等公民:
- `e.type` — 按事件类型过滤
- `e.subject` — 按主体层次结构查询
- `e.id` — 引用特定事件
- `e.time` — 基于时间的排序和过滤
- `e.data.*` — 深层访问事件负载
这些属性中的每一个都代表着一个**索引优化机会**。一个设计良好的事件存储可以为类型、主体和时间戳创建索引,使这些查询速度飞快。
### 2. **主体层次结构实现智能范围限定**
EventQL 最强大的特性之一是主体模式匹配:
``
FROM e IN events
WHERE e.subject == "/books/42"
ORDER BY e.time DESC
TOP 100
PROJECT INTO e
``
像 `/books/42` 或 `/users/123/orders/456` 这样的主体层次结构在事件溯源中很自然。它们代表了聚合边界,允许你:
- 将查询范围限定到特定聚合
- 创建基于主体的索引以实现快速查找
- 构建感知层次结构的查询(虽然在基本示例中未展示)
这使得浏览事件数据变得直观:“显示这本书的所有事件”或“这个用户的订单发生了什么?”
### 3. **类SQL的表达力**
EventQL 借鉴了 SQL 成熟的模式:
- **WHERE 子句**,支持完整的布尔逻辑(AND、OR、NOT)
- **ORDER BY**,支持升序/降序排序
- **GROUP BY**,用于聚合
- **TOP/SKIP**,用于分页
- **嵌套子查询**,用于复杂转换
这意味着你可以精确地表达复杂的查询:
``
FROM e IN (
FROM e IN events
WHERE e.type == "order-placed"
PROJECT INTO {
orderId: e.id,
total: e.data.total
}
)
WHERE e.total > 100
ORDER BY e.total DESC
PROJECT INTO e
``
### 4. **投影作为一等概念**
与 SQL 中可选的 SELECT 不同,EventQL 要求使用 `PROJECT INTO` 进行显式投影。这一设计选择对于事件查询来说很有道理,因为通常你需要:
- 重塑嵌套的事件数据
- 从负载中提取特定字段
- 构建聚合或计算值
``
FROM e IN events
WHERE e.type == "book-acquired"
PROJECT INTO {
year: YEAR(e.time),
revenue: SUM(e.data.price)
}
``
投影语法支持任意对象构造,使得构建你所需的输出形状变得轻而易举。
## 天生对索引友好
我特别欣赏 EventQL 的一点是,它自然地引导你编写可索引的查询。考虑 WHERE 子句中常用的属性:
- **事件类型** — 几乎总是被索引
- **主体** — 自然的分区键
- **时间戳** — 时间范围查询必不可少
- **数据字段** — 可以有选择地索引
像这样的查询:
``
FROM e IN events
WHERE e.type == "user-registered"
AND e.time > "2025-01-01"
AND e.subject == "/users"
ORDER BY e.time DESC
TOP 1000
PROJECT INTO e
``
完美地映射到 (type, time, subject) 的复合索引上。语言的结构清晰地指明了索引将如何发挥作用。
## 让解析器达到生产就绪
我最初在 GethDB 数据库项目中编写了一个 EventQL 解析器。它能工作,但与那个具体用例紧密耦合。最近,我决定将其打造成一个独立的库,达到生产就绪。
最终成果是一个健壮的 Rust 解析器,它:
- 提供详细的错误信息,包括行号和列号
- 构建适合查询优化的强类型抽象语法树(AST)
- 支持完整的 EventQL 语法
- 包含全面的测试覆盖
- 可嵌入任何基于 Rust 的事件存储中
### 类型推断:即将推出
GethDB 版本包含一个类型推断系统,我计划也将它移植到这个库中。类型推断器会尽可能多地从查询中收集类型信息,并尽早捕获不一致之处。
例如,它会拒绝像这样的查询:
``
FROM e IN events
WHERE e.data.price == "expensive" -- price 被当作字符串
AND e.data.price > 100 -- price 被当作数字
PROJECT INTO e
``
通过追踪字段在查询中的使用方式,类型推断器可以在查询到达数据库之前就排除无意义的查询。这能提供更好的错误消息,并防止因类型不匹配导致的运行时故障。
你可以在 GitHub 上找到这个解析器:eventql-parser(https://github.com/YoEight/eventql-parser)
## 这为什么重要
事件溯源功能强大,但需要好的工具。一个设计良好的查询语言决定了事件存储是令人痛苦还是愉悦易用。
EventQL 把握住了基本原则:
- **熟悉**(类 SQL 语法)
- **表达力强**(可以编写复杂查询)
- **事件感知**(围绕事件属性设计)
- **索引友好**(自然的优化机会)
如果你正在构建一个事件溯源系统,你需要一种有效查询事件的方法。EventQL 展示了如何做得正确。
## 亲自尝试
该解析器以 Rust crate 的形式提供。下面是入门示例:
``
use event_query_lang::parse_query;
let query = r#"
FROM e IN events
WHERE e.type == "order-placed"
ORDER BY e.time DESC
TOP 100
PROJECT INTO e
"#;
match parse_query(query) {
Ok(ast) => {
// 使用 AST 执行查询
println!("解析成功!");
}
Err(e) => {
eprintln!("解析错误:{}", e);
}
}
``
## 结论
好的语言设计在于深入理解领域,并创建自然的抽象。EventQL 在事件溯源方面做到了这一点。
它证明了你不需要重新发明轮子——SQL 已有的模式,在经过对事件流的深思熟虑的改造后,效果惊人。最终形成的是一种既强大又好用的查询语言。
如果你正在使用事件溯源,不妨看看 EventQL。如果你需要一个生产就绪的解析器,Rust 实现已经可以用了。
---
*这个解析器是为了支持我的 GethDB 项目和其他事件溯源系统而构建的。如果你有反馈或想贡献代码,欢迎在 GitHub 上提交 issue 或 PR。*
相似文章
QO-Bench:诊断类型化事件元组上的查询算子保留检索
QO-Bench 是一个针对类型化事件元组上查询算子问答的诊断性基准测试,涵盖 22,984 篇新闻文章和 614 个企业事件,涉及 18 种查询模板。该基准对 RAG、ReAct RAG、GraphRAG 以及抽取转 SQL 系统进行评估,发现算子执行——而非仅仅是检索——才是核心瓶颈,单纯使用更强的模型并不能解决这一问题。
UniQL:面向文本到SQL的方言通用基准测试
介绍了UniQL,这是一个经过人工验证的可执行基准测试,用于跨方言文本到SQL评估,解决了像Spider和BIRD等现有基准测试中缺乏方言多样性的问题。
CQL:范畴数据库
CQL是一个开源工具,利用范畴论执行数据库操作,如查询、迁移和集成数据,并通过定理证明提供内置的正确性保证。
@thomasp85:我激动万分,终于可以揭开我们2026年所做工作的神秘面纱:请认识 ggsql!一个全新的扩展……
Posit 宣布推出 ggsql 的 alpha 版本。ggsql 是一种新的 SQL 语言扩展,将图形语法风格的数据可视化引入 SQL,兼容 Quarto、Jupyter notebooks、Positron 和 VS Code。用户可以使用受 ggplot2 启发的熟悉 SQL 语法,创建分层、结构化的可视化图表。
ggsql:面向 SQL 的图形语法
ggsql 是一款 Alpha 版本工具,它将图形语法的可视化能力引入 SQL,允许用户在 Quarto、Jupyter、Positron 和 VS Code 中利用 SQL 语法构建结构化、模块化的可视化图表。