Skip to content

driver-sql 把**文档级**未声明 $op({$where:…}、{$nor:[…]})当成列名编译,静默返回空结果集 —— #5324 的文档级一半在 SQL 侧还在 #5348

Description

@os-zhuang

修 #5324(driver-memory 的未知 $op 透传)时,把同一批输入喂给 driver-sql 做信封对照,发现 SQL 侧在文档级位置有同一条缝,且症状比 memory 侧更隐蔽。按 Prime Directive #10 单独记在这里,unassigned。

实测原文

SqlDriver + better-sqlite3 :memory:,单行 { id: '1', stage: 'won', score: 10 }:

WHERE {"$where":"return true"}        =>  RESOLVED []      // 期望:INVALID_FILTER / 400
WHERE {"$nor":[{"stage":"won"}]}      =>  RESOLVED []      // 期望:INVALID_FILTER / 400

对照组 —— 字段级同类输入在 SQL 侧是正确的,一直都是:

WHERE {"stage":{"$sounds_like":"won"}}  =>  THROW code=INVALID_FILTER status=400
        Unsupported filter operator "$sounds_like" on field "stage". Supported operators: …
WHERE {"score":{"$between":5}}          =>  THROW code=INVALID_FILTER status=400
        Operator "$between" on field "score" requires a [min, max] value array.

机制

FilterConditionSchema 在节点位置只声明三个 $ 键(LOGICAL_OPERATORS:$and / $or / $not),其余键都是字段名。applyFilterCondition 的分支结构正是按这个假设写的,但没有任何一处检查这个假设:

结果不是报错,而是一个匹配不到任何行的谓词 —— 静默的空结果集,和 #5328 在 driver-memory 上的 $between 是同一个症状。

{$nor:[…]} 更值得看一眼:它的值是数组,连 typeof value === 'object' && !Array.isArray(value) 这一支都进不去,同样落到 else,同样静默。

为什么是 bug

  1. 同一个驱动,两个位置两种答案。 字段级未知算子响亮拒收(A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 / data: unsupported-filter-operator refusal ships without error.code and leaks the [sql-driver] prefix #4436 的成果),文档级未知算子静默返回空 —— 而调用方分辨不出「真的没有匹配」和「过滤器根本没编译」。这与 [spec] $field 跨字段比较:spec 声明 + cel-to-filter 产出,但无任何 SQL 执行层实现 —— enforce-or-remove 裁决位 #5041 记录的形状完全一致:那一条也是「查询编译了、跑了、返回零行」,并且当时的结论是「A silent wrong answer is what A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 / fix(data): a filter the server cannot apply is rejected, not silently ignored (#4181) #4209 settled is strictly worse than an error on a permission-scoped read」。
  2. $where 这个名字不是随便举的例子。 driver-mongodb 的 default 分支注释专门点名 $where / $function / $expr 是 P0 —— 一个未声明的 $op 被后端当真求值会绕过查询意图。SQL 侧目前不会求值它(它变成了列名),但「未声明的键悄悄改变了 WHERE 的含义」这件事本身就该拒收。
  3. 四家后端现在只剩它。 driver-memory 在本轮(driver-memory 的实时查询路径根本不支持 $not —— mingo 抛无 code 的 MingoError,CEL !expr 降下来的 RLS scope 在该驱动上直接 500 #5324)已经对文档级未声明 $op 抛 INVALID_FILTER / 400;driver-mongodb 的 translateCondition 至少不会把它编成列(落到 default 的字段分支);SQL 是唯一把它编成列名并静默的。

建议(不代裁决)

在 reduceFilterKey 里(而不是发射器里)补一条:节点位置上以 $ 开头且不在 LOGICAL_OPERATORS 中的键,抛 unsupportedFilterError。理由与 #5240 把 {field:{}} 的拒收放在这条验证走查上完全相同 —— 这条走查是穷尽的、不短路的,放在发射器里会让「refused or ignored depending on its SIBLINGS」(该函数自己的注释)重演。

driver-memory 侧的实现可以逐字参照:packages/plugins/driver-memory/src/filter-refusal.ts 的 unknownLogicalOperatorError + assertFilterConditionShape。

未验证的部分

我只实测了 better-sqlite3 一个方言(其他方言把 "$where" 当列名的行为可能是报错而不是返回空 —— 那样症状不同但病因相同)。也没有清点仓内是否真有产出文档级未声明 $op 的生产者;这更像是 AI 生成的元数据或手写 scope 的失误路径。严重度请 PM 按 triage 定,不代表我判断它低。

关联:#5324(driver-memory 的同一条缝,文档级 + 字段级都已修)、#5328、#5134(reduceFilterNode 形状门)、#5240(为什么拒收要放在走查上)、#5041(「编译了、跑了、返回零行」的先例)、#4436(信封)。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions