很多后台接口最后都会卡在同一个问题上: 前端、管理台或者内部脚本希望传入灵活筛选条件,但后端又不想手写一大坨 if/else 来拼 SQL,更不想把原始 SQL 直接暴露出去。你想要的是一种既足够灵活、又有类型边界、还能跨数据库复用的过滤表达方式。

今天想推荐的 SPANDigital/cel2sql,正好是在解决这个问题。它把 CEL(Common Expression Language) 表达式转换成 SQL 条件,支持 PostgreSQL、MySQL、SQLite、DuckDB、BigQuery 和 Apache Spark SQL。也就是说,你可以让上层系统先用更安全、更结构化的表达式描述筛选逻辑,再由这个库生成目标数据库能执行的 where 条件。

发布时 GitHub 页面显示项目约 37 stars3 forks,主要语言是 Go,采用 Apache-2.0 许可。仓库创建于 2025-07-10,发布时最新 release 是 v3.8.6,发布时间为 2026-06-15

项目概览

属性详情
仓库SPANDigital/cel2sql
定位把 CEL 表达式转换成多种 SQL 方言条件
Stars约 37
Forks3
主要语言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 里给了 NewTypeProviderWithConnectionLoadTableSchema 的方式,可以直接从数据库连接里加载表结构。这对真实项目很关键,因为手工维护 schema 映射很容易漂移,而自动读取更适合长期使用。

如果你在做 API 网关、数据服务、内部报表平台,或者任何需要“让别人写过滤条件,但又不能乱来”的系统,这个 type-aware 设计会比自定义字符串 DSL 靠谱得多。

默认安全边界写得很清楚

这类工具最怕的不是功能不够,而是宣传“更安全”,但安全边界实际很模糊。

cel2sql 在文档里把这部分写得相当明白。默认内建的防护包括:

  • 字段名校验,限制长度和允许字符,阻断常见注入模式;
  • JSON 字段名转义;
  • 正则 ReDoS 防护,限制危险模式、长度、捕获组和嵌套层级;
  • 表达式递归深度限制,默认最大深度为 100;
  • 支持 context timeout,避免复杂表达式拖太久。

这意味着它不是简单把 AST 递归打印成 SQL 字符串,而是认真考虑了“用户可输入表达式”这件事在生产里会碰到的问题。

当然,文档也没有夸大。它仍然建议你:

  • 先校验输入;
  • 执行 SQL 时继续使用 prepared statements;
  • 只暴露必要字段;
  • 对自己的边界条件做测试。

这种写法我会更信一些,因为它是在补安全层,而不是声称自己替你解决一切安全问题。

JSON、数组、时间和日志能力让它更像真实项目组件

如果一个表达式转 SQL 的库只能处理最基础的 a > b,实际用途会很有限。

cel2sql 文档里已经覆盖了不少更贴近业务的场景,例如:

  • startsWithcontains 这类字符串操作;
  • 时间戳比较;
  • 数组 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 不高的小项目很值得放进工具箱。