Skip to content

{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240

Description

@os-zhuang

修 #5134(driver-sql 的空 $and/$or/$not 单位元)时按纪律核对「编译成空」的全部成因,发现这一处不属于本单范围的分叉。按 Prime Directive #10 单独记在这里,unassigned。

现象

{ a: {} } —— 一个字段,后面跟零个操作符。FilterConditionSchema 的非递归半边是 z.record(z.string(), z.unknown()),所以这个形状是声明合法的。同一个 filter,同仓四条路径给三个答案:

路径 代码位置 对 { a: {} } 的答案
driver-sql,顶层 sql-driver.ts applyFilters 的 plain-map 分支 → assertCompilableComparand 抛 INVALID_FILTER / 400(#5041 加的闸门)
driver-sql,组合子内($or/$and/$not 的分支里) sql-driver.ts applyFilterCondition 的 typeof value === 'object' 分支 遍历零个操作符 → 不产出任何 SQL → 实际语义 TRUE
formula matches-filter.ts evalField:if (keys.length === 0 ¦¦ …) return false FALSE(fail closed,显式写死)
driver-memory memory-matcher.ts checkCondition:落到 JSON.stringify(value) === JSON.stringify(condition) FALSE(结构相等,顺带为假)

driver-sql 自己内部就不自洽:同一个 { a: {} },写在顶层被响亮拒收,包进一层 $or 就变成静默的 TRUE。

为什么这是个 bug 而不是洁癖

在 $or 里,一个「什么都不产出」的分支会被 knex 连同分组一起丢掉,于是

{ $or: [ { a: {} }, { b: 2 } ] }

编译成 (b = 2) —— 而按 {} 是 TRUE 析取项的算法(#5134 刚刚为空节点确立的),它应当匹配全部行;按 formula/driver-memory 的 FALSE 判定,它应当只匹配 b = 2。两种读法结论不同,而当前 SQL 的结果是碰巧和后者一致、却是经由「丢弃子句」而非任何一种语义得到的 —— 和 #5134 修掉的那个成因完全同源。

为什么 #5134 没顺手修

因为两个候选答案各有依据、且互相矛盾,这是语义定调不是实现细节:

#5134 的实现刻意不碰它:归约函数把带字段键的节点一律判为 'clause'(而非 'true'),所以这次的单位元修复既没有把 { a: {} } 悄悄升格为「匹配所有行」,也没有替它做任何裁决 —— 行为与修复前逐字节相同。代码里留了注释指向本单。

建议

三选一,需维护者/spec 车道拍板;拍板后四个后端一起对齐,并把该 case 补进 FILTER_LOGIC_CASES(见 #5239,那一单已经在扩这张表)。个人倾向 拒收:它让 AI 生成的 metadata 在编写期就炸,而不是在某个后端上安静地多返回或少返回几行 —— 与 #5041 已经在 driver-sql 顶层建立的先例一致,只是把同一条闸门补到组合子内部。若嫌收紧契约代价大,次选 FALSE(跟随两个参照实现,且方向 fail-closed)。

未验证的部分:我没有找到今天真的会产出 { field: {} } 的 producer(UI 筛选器里「选了字段还没选操作符」是最可能的来源,未实测)。严重度请 PM 按 triage 定,不代表我判断它低。

关联:#5134(同函数、同「编译成空」家族,已修空组合子)、#5041(顶层那道闸门的由来)、#5239(一致性表扩条)、#3774。

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