Skip to content

[finding] undefined 比较数在仓内有五种读法 —— #6050 只在 driver-sql/turso 落了拒收,formula 读作「键缺失」、read-scope-sql 编成 = NULL、driver-memory 读作 null #6125

Description

@os-zhuang

出自 #6050 的实施(PR 见下)。#6050 的裁决(2026-08-07,B 案)把「比较数位置的 undefined」定为一律拒收(INVALID_FILTER / 400),实施面按裁决只覆盖 driver-sql + driver-turso 的 remote transport;issue 正文「落点」节要求裁决后按 #5298 的方式逐格实测其余无值语义面。实测做完了,记录在此,按 Prime Directive #10 不指派、不扩大那张 PR 的 diff。

实测(origin/main @ cba7454df,PR 落地前;四行 fixture:d 在 1-2 有值、3 为 SQL NULL、4 为「键不存在」)

同一个形状 { d: undefined },五个求值面五种读法:

求值面 { d: undefined } 读法
driver-sql / turso LOCAL 抛裸 knex Undefined binding(s)(无 code/status)
turso REMOTE(buildWhereSQL) ['3','4'] undefined ≡ nullIS NULL
driver-memory(mingo 活路径) ['3','4'] undefined ≡ null
@objectstack/formula matchesFilterCondition ['4'] undefined ≢ null,只匹配键缺失的行
service-analytics read-scope-sql.ts compileScopedFilterToSql "t"."d" = ? / params [null] d = NULL → UNKNOWN → 匹配零行

对照组(null 比较数)五面一致,{d: null} / {$eq: null} → IS NULL 族,{$ne: null} → IS NOT NULL 族。

其余格(实测同表):

  • read-scope-sql:{$gt: undefined}"t"."d" > ? bound null;{$in:[undefined]}IN (?) bound null;{$not:{d: undefined}}NOT (("t"."d" IS NOT NULL AND "t"."d" = ?)) bound null。全部是「合法 SQL、绑了个 NULL、答一个没人问的问题」,且一条日志都不报
  • formula:{$ne: undefined}['1','2','3'](含 SQL NULL 行),{$in:[undefined]}['4'],{$gt: undefined}[]
  • driver-memory:{$ne: undefined}['1','2'],{$in:[undefined]}[],{$not:{d:{$ne: undefined}}}['3','4']

为什么单独立单而不是搭 #6050 的车

三条,都是语义争议而非实现细节,#6050 的裁决没有覆盖它们,其派工单也明确要求「有语义争议只记录留后续单」:

  1. formula 的读法是第三种语义,不是第三个 bug 拼写。 它把 undefined 读成「这个键在记录里不存在」——{d: undefined} 只匹配 row 4,而 {d: null} 匹配 3 和 4。这是有意义的区分(driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 正在争的就是「键缺失」vs「值为 null」该不该分家),直接照 drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050 改成拒收会顺手替 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 拍板。
  2. read-scope-sql 的信封不同。 它只会抛 READ_SCOPE_COMPILE_FAILED / 500(模块自述「⛔ The only way this module refuses」),而 drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050 判的是 INVALID_FILTER / 400。read scope 是平台自己编译出来的,不是调用方输入,所以「这是谁的错、该报哪个码」是一次独立裁决,不能默认沿用。
  3. driver-memory / driver-mongodb[裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 的冻结面,drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050 的裁决明确写了「照 [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 只 pin 不改」。但请注意后果:PR 落地后 driver-memory 与 driver-sql 在这一格上开始不一致(memory 答 ['3','4'],sql 拒收)。这是裁决接受的代价,不是回归,但它是冻结解冻时要还的账。

触达性(判严重度需要的那一半)

undefined 过不了 JSON,所以只能从进程内代码来。三个面的进料口:

⚠️ 严重度请分诊自己判:立单时判的严重度两个方向都不可靠(#5347 的先例)。这里如实记的是「未证实触达」,不是「不会发生」——read-scope-sql 那格若真被喂到,方向是匹配零行(fail-closed,不是越权),而 formula 那格方向是「少匹配」,两者都不是 #6050 那种越权读。

建议的处置(供裁决方,不是既定结论)

  1. drivers: undefined 比较数被发射器读作 null、却被守卫/校验读作「值」—— turso local 抛裸 knex 错、remote 静默答 IS NULL(实测,origin/main) #6050 的 B 案推平到 formula + read-scope-sql,各自用自己的信封(formula 无信封,需先定;read-scope 用 READ_SCOPE_COMPILE_FAILED/500);同时给 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 的「键缺失 vs null」一并拍板,因为改 formula 绕不过它;或
  2. 只补 read-scope-sql(信封已有、方向单一),formula 挂到 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 上一起裁;或
  3. 全部并入 [finding] 仓内存在 5 个独立的过滤器→谓词编译器,每次语义裁决成本 ×5 —— 值得立「谓词编译收敛」调查程序(#5298 成本清单副产品) #5930 提议的「谓词编译收敛」调查程序 —— 这已经是同一形状第 N 次要 5 份裁决,[finding] 仓内存在 5 个独立的过滤器→谓词编译器,每次语义裁决成本 ×5 —— 值得立「谓词编译收敛」调查程序(#5298 成本清单副产品) #5930 的成本清单说的正是这件事。

关联

Blocked-by: #6050(其 PR 落地后本单的对照表才是当前状态)。同族:#5298(无值语义族的裁决入口 + 「守卫匹配发射器」不变量)、#5299(driver-memory 与 formula 对「字段没有值」的三处分歧)、#5905(objectql having-filter.ts 是第五个求值面)、#5930([finding] 仓内 5 个独立过滤器→谓词编译器,每次裁决 ×5)、#5499(memory/mongodb 投入冻结)、#5347 / #5369(比较数按声明拒收的先例)。

会话:session_01WyvqvKMG6asi9aXjKE6xtx

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions