cel2sql - 用 CEL 安全生成多数据库 SQL 过滤条件
很多后台接口最后都会卡在同一个问题上: 前端、管理台或者内部脚本希望传入灵活筛选条件,但后端又不想手写一大坨 if/else 来拼 SQL,更不想把原始 SQL 直接暴露出去。你想要的是一种既足够灵活、又有类型边界、还能跨数据库复用的过滤表达方式。
今天想推荐的 SPANDigital/cel2sql,正好是在解决这个问题。它把 CEL(Common Expression Language) 表达式转换成 SQL 条件,支持 PostgreSQL、MySQL、SQLite、DuckDB、BigQuery 和 Apache Spark SQL。也就是说,你可以让上层系统先用更安全、更结构化的表达式描述筛选逻辑,再由这个库生成目标数据库能执行的 where 条件。
发布时 GitHub 页面显示项目约 37 stars、3 forks,主要语言是 Go,采用 Apache-2.0 许可。仓库创建于 2025-07-10,发布时最新 release 是 v3.8.6,发布时间为 2026-06-15。
项目概览
| 属性 | 详情 |
|---|---|
| 仓库 | SPANDigital/cel2sql |
| 定位 | 把 CEL 表达式转换成多种 SQL 方言条件 |
| Stars | 约 37 |
| Forks | 3 |
| 主要语言 | Go |
| 许可 | Apache-2.0 |
| 支持方言 | PostgreSQL、MySQL、SQLite、DuckDB、BigQuery、Spark SQL |
| 最新版本 | v3.8.6 |
它适合的不是“写 SQL”,而是“开放受控筛选能力”
很多业务系统其实不缺 SQL 能力,缺的是一个对用户、脚本或上层服务开放筛选逻辑的安全接口。
最常见的几种做法都不太理想:
- 前端传一堆零散字段,后端维护越来越长的筛选分支;
- 直接接受某种自定义 DSL,但类型系统和工具生态很弱;
- 甚至让用户提交原始 SQL 片段,最后把安全和维护一起丢掉。
cel2sql 提供的路线更稳。它把条件表达交给 CEL,也就是 Google 推的通用表达式语言,然后把已经编译过的 AST 转成 SQL。这样你得到的不是一段随意字符串,而是一套可以先做语法和类型检查、再做方言输出的中间层。
README 里的最小示例就很直接:先定义表 schema,再创建 CEL environment,编译类似 user.age >= 18 && user.active 这样的表达式,最后输出 user.age >= 18 AND user.active IS TRUE。这种工作流非常适合管理后台筛选器、可配置报表、策略引擎和内部查询 API。
一个表达式,多个数据库方言,是它最实用的地方
我觉得这个项目最有价值的点,不只是“把表达式变成 SQL”,而是它把这个能力做成了 多方言。
同一个 API 下,当前已经支持:
- PostgreSQL
- MySQL
- SQLite
- DuckDB
- BigQuery
- Apache Spark SQL
如果你的系统存在多种存储后端,这会很有用。很多团队表面上只有一个主库,但旁边还会挂分析型数据库、本地 SQLite、或者测试阶段用 DuckDB 做离线验证。通常一旦方言变多,查询层就开始复制粘贴和分叉。
cel2sql 的好处是把“条件表达”先固定住,让后面的差异留在方言实现里。最近的 v3.8.6 版本还新增了完整的 Spark SQL dialect,包括正则、JSON、数组字面量和部分 comprehension 场景的适配,这说明它不是只停在最常见的 PostgreSQL。
schema 和 type provider 让它比字符串模板更靠谱
很多“表达式转查询”的方案,最后还是会退化成字符串替换,因为它们并不知道数据库里的字段类型,也不知道哪些字段真的存在。
cel2sql 这里做得更工程化一些。文档里的典型流程是先定义 schema,再通过 type provider 把这些 schema 暴露给 CEL。这样在编译阶段就能发现:
- 字段不存在;
- 类型不匹配;
- 表达式语法不合法。
这意味着很多错误能在进数据库之前暴露出来,而不是留到运行时再炸。
更实用的一点是,它不要求所有 schema 都手写。docs/getting-started.md 里给了 NewTypeProviderWithConnection 和 LoadTableSchema 的方式,可以直接从数据库连接里加载表结构。这对真实项目很关键,因为手工维护 schema 映射很容易漂移,而自动读取更适合长期使用。
如果你在做 API 网关、数据服务、内部报表平台,或者任何需要“让别人写过滤条件,但又不能乱来”的系统,这个 type-aware 设计会比自定义字符串 DSL 靠谱得多。
默认安全边界写得很清楚
这类工具最怕的不是功能不够,而是宣传“更安全”,但安全边界实际很模糊。
cel2sql 在文档里把这部分写得相当明白。默认内建的防护包括:
- 字段名校验,限制长度和允许字符,阻断常见注入模式;
- JSON 字段名转义;
- 正则 ReDoS 防护,限制危险模式、长度、捕获组和嵌套层级;
- 表达式递归深度限制,默认最大深度为 100;
- 支持
contexttimeout,避免复杂表达式拖太久。
这意味着它不是简单把 AST 递归打印成 SQL 字符串,而是认真考虑了“用户可输入表达式”这件事在生产里会碰到的问题。
当然,文档也没有夸大。它仍然建议你:
- 先校验输入;
- 执行 SQL 时继续使用 prepared statements;
- 只暴露必要字段;
- 对自己的边界条件做测试。
这种写法我会更信一些,因为它是在补安全层,而不是声称自己替你解决一切安全问题。
JSON、数组、时间和日志能力让它更像真实项目组件
如果一个表达式转 SQL 的库只能处理最基础的 a > b,实际用途会很有限。
cel2sql 文档里已经覆盖了不少更贴近业务的场景,例如:
startsWith、contains这类字符串操作;- 时间戳比较;
- 数组 membership 和长度判断;
- JSON/JSONB 支持;
- 可选 structured logging;
- 可选性能和诊断输出。
这些细节说明作者把它当成真实接口层组件,而不是 demo 级别的 parser。
尤其 logging 和 context 支持值得一提。很多库只做功能转换,不考虑 observability;而这里已经在考虑 schema lookup、JSON path 判断和性能指标这类信息如何接入日志。对需要排查复杂筛选规则的服务来说,这很有帮助。
它特别适合哪些场景
我觉得 cel2sql 很适合几类系统。
第一类,是给管理后台、BI 内页或内部平台开放“自定义筛选”的后端接口。你不想让产品经理或内部用户直接碰 SQL,但又不想每加一个筛选项就改一次接口。
第二类,是多租户 SaaS 里的规则或过滤系统。比如搜索、事件订阅、告警条件、数据分发规则,都会天然需要表达式层。
第三类,是同一套服务需要兼顾 OLTP 和分析场景的系统。你可能在线上打 PostgreSQL,在测试或分析任务里打 DuckDB、BigQuery 或 Spark,那么“同一表达式、多方言输出”会明显减少重复工作。
需要注意的边界
当然,cel2sql 也不是完整查询构建器。
它主要解决的是 条件表达式到 SQL 条件 的转换,不是替你生成整条查询、join 策略或访问控制模型。你还是需要自己决定哪些表可以暴露、哪些字段允许使用、最终查询怎么执行,以及哪些逻辑要放在业务层而不是交给表达式。
另外,CEL 本身虽然比原始 SQL 友好,但也不是给普通终端用户直接裸写就一定舒服。真正在产品里落地时,通常还是要配合表单构建器、规则编辑器或预设模板。
小结
SPANDigital/cel2sql 真正值得看的地方,不只是“把 CEL 变成 SQL”,而是它把这件事做成了一个足够工程化的中间层:多方言、类型感知、可从数据库加载 schema、默认带安全防护、还能配日志和 timeout。
如果你最近正好在做“开放灵活筛选,但不想把 SQL 暴露出去”的接口,这个 stars 不高的小项目很值得放进工具箱。