fix(driver-mongodb): 空 $and/$or/$not 归约成布尔单位元,非 filter 节点先响亮拒收 (#5239) - #5323
Draft
os-zhuang wants to merge 1 commit into
Draft
fix(driver-mongodb): 空 $and/$or/$not 归约成布尔单位元,非 filter 节点先响亮拒收 (#5239)#5323os-zhuang wants to merge 1 commit into
$and/$or/$not 归约成布尔单位元,非 filter 节点先响亮拒收 (#5239)#5323os-zhuang wants to merge 1 commit into
Conversation
…tity, refusing non-nodes first (#5239) `translateFilter` passed combinator arrays through verbatim, and MongoDB answers an empty one with neither TRUE nor FALSE but a third behaviour: it refuses the query (`$and/$or/$nor must be a nonempty array`). So `{$and: []}` and `{$or: []}` reached find/count/updateMany/deleteMany as a server error carrying no ADR-0112 code, while driver-sql (#5134), driver-memory and formula all answered them as identities. Replaced with the same STRUCTURAL three-valued reduction: reduce the whole tree to true/false/clause first, then emit. Empty `$and` becomes TRUE (no condition); empty `$or` becomes FALSE and emits a real zero-row condition (`{_id: {$in: []}}`) — emitting nothing would be `{}`, which find/updateMany/ deleteMany read as EVERY document, the opposite answer. Every `$and`/`$or` array emitted is therefore guaranteed non-empty. Shape rejection lands in the same change and runs BEFORE any identity: measured on main, `{$or: [new Date()]}` translated to `{$or: [{}]}` (every document) and `{$or: 'x'}` / `{$not: null}` translated to `{}` (every document). updateMany and deleteMany translate the same `where`, where that is data loss rather than a wrong row count. Non-nodes now raise INVALID_FILTER / 400 naming the position; the gate judges by PROTOTYPE, since Date/RegExp/class instances satisfy `typeof x === 'object'` while enumerating empty. spec is documentation only: FilterConditionSchema's contract TSDoc now states the NULL-safe `$not` semantics ruled in #5146, and filter-logic-conformance.ts records the measured matrix for the three ruled-but-not-yet-enrolled case families. The four FILTER_LOGIC_CASES rows #5239 asks for are deliberately NOT added: read-scope-sql and the analytics filter-normalizer, both enrolled backends, refuse empty combinators fail-closed by design and pinned test, which contradicts the identity ruling — escalated as #5322. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Contributor
Author
|
PM 验收(session_01ErbEDVAg1No9gdg1pgDAGB)——通过。
进入串行落地链链尾(当前序:#5304 → #5306 → #5308 → #5318 → #5319 → #5321 → #5314 → #5312 → 本 PR)。轮到时由 PM 通知重建 worktree 同步 + 全量重生成 + 兄弟断言,再翻 ready 并 arm auto-merge。 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5239
同批携带 #5146 spec 半边(已落地)与 #5240 spec 半边(受限携带 → 实测后不进表)。
1. 前提复核(先做的事)
按仓规对
origin/main(175d789)逐后端实测四条 case,而不是照抄 issue 正文。实测方法本身有阳性对照:我的 driver-sql 探针复现了 PR #5296 / #5243 已落地 pin 的逐字结果({$not:{stage:'won'}}→ 行 2,3,4;{$or:[]}→ 零行),说明测的是真行为不是我的假设。{$and:[]}{$or:[]}{$or:[{a},{}]}{$not:{}}formuladriver-memorydriver-sql(#5134/PR #5243)driver-sqlite-wasmdriver-mongodb(改前)read-scope-sqlfilter-normalizerissue 正文点名的「四个后端」里就含
read-scope-sql,而它和同包的filter-normalizer各红四格。两者都在册跑全量FILTER_LOGIC_CASES(read-scope-sql-conformance.test.ts、native-sql-filter-logic-conformance.test.ts),所以四条一进表就是八格红。2. 为什么四条没进表
那两处不是「没跟上」,是成文的相反立场:
filter-normalizer.ts的错误消息逐字写着「An empty combinator has no defensible reading — dropping it widens the query, and treating it as "match nothing" silently empties a chart」,而「match nothing」正是 #5134 给$or: []定的答案;read-scope-sql.test.ts:86还用toThrowError(/non-empty array/)把这个抛错钉住了。「响亮拒收」本身是有先例的答案 —— #5240 对
{ field: {} }取的就是它。所以这是两条已落地立场必须有一条让步,属公开契约定调,不是实现细节。已立 #5322(含两轴分析与推荐:取单位元 + 可选地在 publish/lint 面对字面量空组合子拒收,这样运行期语义单一而作者错误仍在编写期爆炸)。派发词的硬要求是「批次 PR 必须对自己的内容全绿」,所以红行不进表,实测矩阵原样留在filter-logic-conformance.ts里,下一位不必重测。同理另两族也没进表:
$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 NULL-safe$not:driver-sql(PR fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296)、driver-sqlite-wasm、driver-memory、formula、driver-mongodb五家已一致(实测{$not:{stage:'won'}}→ 行 2,3,4),但read-scope-sql/filter-normalizer仍发裸NOT (…),只返回行 2 —— 即 read-scope-sql 的$not有两处与 SQL 驱动分叉:非 NULL-safe(#5146 后的最后一个异类),且{ $not: {} }编译成空 → RLS 整表放行 #5297,已在该单补注「filter-normalizer.ts是同缺陷的第二份拷贝」。另外这一族还要改 fixture:FILTER_LOGIC_ROWS每一列都非空,得先加一个可空列并在七个 harness 里同步声明。{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240{ field: {} }:四后端闸门未实现,今天实测有四个不同答案。更根本的是FilterLogicCase没有办法表达「期望拒收」 ——expected是行 id 列表,空列表的意思是「匹配到零行」,恰恰是裁定没取的 FALSE 答案。要进表得先扩表结构(加expectRejection判别式或另立姊妹表),按派发词不擅自发明,只留注记。3. 本 PR 实际改了什么
driver-mongodb(#5239 的硬约束半边)translateFilter原样透传组合子数组,而 MongoDB 对空数组既不答 TRUE 也不答 FALSE,是第三种行为:拒绝整条查询。改成与 #5134 同一套结构性三值归约:先把整棵树判成true/false/clause,再据此产出 —— 而不是「编译完再问有没有产出条件」,后者分不清「本来就是空」和「有东西没编译出来」。$and→ TRUE,不产出条件。$or→ FALSE,产出真实的零行条件{ _id: { $in: [] } }。这一格是关键:「什么都不产出」等于{},而find/updateMany/deleteMany把{}读作全部文档,方向正好相反。{}作$or分支仍是 TRUE 析取项,{$not: {}}仍是零行 —— 这两条 MongoDB 本来就与布尔代数一致,所以归约按结构做而不是只判length === 0(只判长度会有一半是蒙对的)。$and/$or数组因此都保证非空(丢掉单位元成员 ≠ 发一个空数组)。形状拒收在同一改动里,且先于归约。单位元把「这个节点没有谓词」读作「匹配全部文档」,所以空节点必须只有一个成因。改前实测,这一格比 driver-sql 当年更糟 —— 不是「被静默忽略」,是已经在放宽:
{ $or: [new Date()] }{ $or: [{}] }{ $or: 'x' }{}{ $not: null }{}{ $or: ['x'] }{ $or: [{ '0': 'x' }] }updateMany/deleteMany走同一个 translate 层,在那里「放宽到全部文档」不是行数不对而是数据丢失。现按 ADR-0112 以INVALID_FILTER/status: 400拒收并点出位置(filter.$or[0]),消息不带[mongodb]前缀(#3867)。判定按原型而非typeof——Date/RegExp/ class 实例都满足typeof x === 'object'却枚举为空。一处刻意不动:
{ field: {} }仍归为'clause',译文逐字节不变,不替 #5240 做任何裁决。packages/spec(仅文档,零运行时改动)FilterConditionSchema的契约 TSDoc 写明$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 拍板的 NULL-safe$not:被比较列为 NULL 的行不满足被否定的条件、应当被返回,即NOT (…) OR col IS NULL;并说明守卫必须下推到叶子(hoist 到顶会重新放行经由另一分支满足内层$or的行)。按 PD chore: version packages #10 如实写明read-scope-sql/filter-normalizer尚未合规并指向 read-scope-sql 的$not有两处与 SQL 驱动分叉:非 NULL-safe(#5146 后的最后一个异类),且{ $not: {} }编译成空 → RLS 整表放行 #5297 —— 声明出来正是让这个缺口从隐形变成在册。{field:{}}),避免制造新的 declared ≠ enforced。filter-logic-conformance.ts记下三族「已裁定但未进表」的 case、实测矩阵与阻塞单号。FilterArray为仅输入的授权糖(#5158 拍板 C 第 1 步) #5306 的FilterArray声明块(该 PR 仍未合入,且落点在parseFilterAST之后,与本 PR 无重叠)。4. 验证
消费半径全跑(表钉住的每一个后端 + 本次改动包):
skip 不是 pass:
driver-mongodb的 128 skipped 是需要真 mongod 的那半边 —— 本容器取不到mongodb-memory-server的二进制。所以新 pin 分两半,与本包既有惯例一致:译文断言永远跑(对这四条,发出的 document 就是语义:{}= 全部,{_id:{$in:[]}}= 零),真 mongod 那半边回答「服务端是否同意」,在 CI 能取到二进制时才跑。反向验证 —— 方向先预测,再跑
预测:把
mongodb-filter.ts还原成origin/main,新 pin 里除「existing translation is untouched」那组控制项外全红。实测 28 红 / 9 绿,方向与预测一致。唯一偏差是我预测绿的数量写成 8 而实际 9 —— 差的那一条是
a field constrained by zero operators is still not ruled on (#5240),它本就该在还原后保持绿(它钉的正是「{stage:{}}前后逐字节不变」),是我数自己的控制项时漏数了一条,不是方向反了。红的 28 条覆盖三组单位元、嵌套组合、以及全部 15 条形状拒收。Generated by Claude Code