Repository navigation
$exists 的比较值不是布尔时,三个后端面给出三个答案,driver-memory 自己的两个面在 'yes' 上就已分叉 —— 实测,$null(#5347)的同族另一轴 #5369
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 5, 2026 分诊 + 范围注记(2026-08-05,锚点 #5499):挂
finding持有。理由:本单是跨后端语义分歧($exists非布尔比较值),与 #5298 / #5299 同族——需要一次语义裁决才有可派发的验收判据;且 driver-memory 投入已冻结(维护者 2026-08-05 裁决),其两个面的分叉随之冻结,driver-sql / formula 侧的答案分歧仍是活问题。下次发现分诊轮与 #5298 / #5299 并案处理(同一次裁决覆盖$ne/$nin/$notContains/$exists/$null一族最经济)。不构成认领。会话:session_01N3uGFF8teXbpgtbEJ1aYXu
Generated by Claude Code
发现分诊轮判级:持有(
finding留),已并案入 #5298(该单已转needs-user-decision,一次裁决覆盖$ne/$nin/$notContains/$exists/$null一族)。本单在裁决书里的份额:$exists非布尔比较值是否照 #5347 裁定 A 一律拒收(预期为同一句话的重复适用)。裁决后:driver-sql 半边可先行入队;driver-memory / driver-mongodb 半边按 #5499 冻结;cloudRemoteTransport半边归 cloud#1116 配套。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
NULL/无值语义族裁决已落在 #5298(2026-08-06,方案 A:NULL-safe)。本单(
$exists非布尔比较值)按预期一句话套用 #5347-A 口径,随 #5298 的交付批次处置,详见该单裁决评论。经办:PM 会话
session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。
Generated by Claude Code
按 #5298 裁决闭环关闭(completed):
- 语义已裁(维护者 2026-08-06,非否定路径上的
$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298「四项确认」③):$exists取「有值」,与$null严格互补——本单实测的「三个后端三个答案」(实为四个,非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 成本清单补测 formula 第四答案)收敛为一个; - 拒收已实现(PR fix(drivers,analytics,formula): $ne / $nin / $notContains 在 $not 之外也 NULL-safe (#5298) #5962,已 MERGED):driver-sql 的
$exists非布尔比较值拒收闸照$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347-A 落地;formula 语义一行收敛;⚠️ 本单建议句「nullValueSatisfiesOperator收紧为value === true」经实测方向写反,未采纳(论证见 非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 成本清单 §7); - 残余面各有账:turso remote 闸归 drivers(turso): remote 模式没有继承 #5146 的
$notNULL-safe —— 同一个 TursoDriver,local 返回 NULL 行、remote 不返回(实测,origin/main) #5903,driver-memory / mongodb 闸随 [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 解冻批次(其行为在冻结期不变)。
Generated by Claude Code
- 语义已裁(维护者 2026-08-06,非否定路径上的
- added a commit that references this issue
on Aug 8, 2026
修 #5347 / #5348(PR #5368)时,按 PM 要求核对 driver-sql 的守卫表
nullValueSatisfiesOperator能否随$null的拒收一并收紧,清点到紧邻的$existsarm 带着同款的=== false二分。实测后确认它也分叉,而且比$null更散。不在 #5347 范围内(PM 裁定只覆盖$null),按 Prime Directive #10 单独记在这里,unassigned。实测原文
fixture 两行 ——
{id:'1', stage:'won'}、{id:'2', stage:null}(注意第二行:键在,值为 null)。三个求值面:对照组(布尔值,方向一致):
机制:三处按三种规则读同一个比较值
driver-sql(
sql-driver.ts,applyFilterCondition的发射器 switch,$nullarm 下一条):——
=== false恒等。除字面false外一律IS NOT NULL,所以0和''都读作「存在」。driver-memory 的 mingo 面(
memory-driver.tsnormalizeFieldOperators):—— 原样交给 mingo,mingo 按 truthy 读。所以
0/''读作「不存在」,'yes'读作「存在」。driver-memory 的参考匹配器(
memory-matcher.ts):—— 也按 truthy(
!!target),但它的exists定义是「有值」(!== undefined && !== null),而 mingo 的是「键存在」。所以在stage: null那一行上两者必然相反,$exists: 'yes'就已经分开:mingo 说["1","2"],matcher 说["1"]。三个规则:
=== false恒等 / truthy + 键存在 / truthy + 有值。为什么这是独立的一条,而不是 #5299 或 #5347 的一部分
$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 不同轴。 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 问的是「$exists的语义是『键存在』还是『有值』」—— 那是布尔比较值下的语义分歧,需要一次语义裁决。本条问的是「比较值根本不是布尔时怎么办」,是形状问题,答案不依赖 driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 怎么裁:无论$exists最终定义成哪一种,$exists: 0都没有一个不靠猜的读法。两条可以独立修,也可以一起修。$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 同族但被明确排除。$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 的 PM 裁定只覆盖$null;PR fix(driver-sql,driver-memory,driver-mongodb): 越界过滤输入在门口拒收,而不是每个后端各答一个 (#5347, #5348) #5368 因此刻意没有收紧$exists,并在nullValueSatisfiesOperator里留了注释说明理由:该守卫表的case '$exists': return value === false必须继续与发射器的opValue === false读法一致,单独收紧守卫表而不加配套的比较值闸门,是制造分叉而不是消除分叉。PR 里也有一条用例把今天的$exists行为钉住,证明它没被顺手改动。为什么值得修
$null的理由逐字适用。 spec 的FieldOperatorsSchema声明$exists: z.boolean().optional()(与$null同一处),非布尔是越界输入;而where走到驱动时没有任何一层按FieldOperatorsSchema校验过。AI 生成的元数据把$exists: "true"/$exists: 1写出来完全可能,字符串"false"是 truthy —— 与$null的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 记录的陷阱一模一样。$exists落在权限面上。 与$null同级:RLS scope 里判断「字段有没有值」是常规写法。FILTER_LOGIC_CASES刻意不含 null 处理(driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 已记录这一点),所以三个面的分歧不会被任何 conformance 顶出来。建议(不代裁决)
与 #5347 取同一条路 —— 非布尔比较值一律拒收(
INVALID_FILTER/ 400),落点与 #5368 的$null闸门完全对称:reduceFilterKey的校验遍历上(不是发射器 —— 发射器会被布尔单位元短路,fix(driver-sql,driver-memory,formula)!:{ field: {} }四个后端一律拒收 —— 零个操作符的字段约束不再有三个答案 (#5240) #5327 / fix(driver-sql,driver-memory,driver-mongodb): 越界过滤输入在门口拒收,而不是每个后端各答一个 (#5347, #5348) #5368 的理由);拒收之后nullValueSatisfiesOperator的case '$exists': return value === false可随之收紧为value === true(方向注意:$exists与$null相反 —— 一个 NULL 列满足$exists当且仅当调用方问的是false)。filter-refusal.ts的assertFieldConstraintShape,与$null那条并排一行。两个过滤面自动同时生效。translateFieldOperators的$existsarm,与$null那条并排。RemoteTransport:同款=== false二分($nullarm 紧邻),归 cloud#1116 那条的配套,建议同时做。代价与 #5347 相同:这是收紧,今天靠 truthy/falsy 巧合工作的调用方会拿到 400。#5347 的裁定已经为这一类代价定了调,所以本条大概率是同一句话的重复适用,而不是一次新裁决 —— 但仍请 PM 明确裁一次,不要由实现方代拍。
注意先后:若 #5299 先裁定了
$exists的语义(键存在 vs 有值),本条的修复会顺带把两个 JS 面的实现对齐;若本条先修,#5299 的裁决面会缩小到只剩布尔输入。两条不冲突,任意顺序都可以。未验证的部分
SqlDriver的同一段发射器代码,预期与 driver-sql 一致(fix(driver-sql,driver-memory,driver-mongodb): 越界过滤输入在门口拒收,而不是每个后端各答一个 (#5347, #5348) #5368 已对$null/ 节点位置越界$op两条实测确认过这条继承链确实传递行为)。=== false二分,未对$exists单独跑数。@objectstack/formula的matchesFilterCondition:未测。driver-memory 与 formula 对「字段没有值」给出三处不同答案:$notContains(null 值)、$exists(键在值为 null)、$nin(缺键) #5299 已记录它与 driver-memory 在布尔$exists上就不一致,非布尔下大概率是第四个答案。$exists的频率。严重度请按 triage 定,不代表我判断它低 —— 我只是按范围没有在 #5368 里顺手改。
关联
#5347 / PR #5368(
$null的同款,已裁定 A 并落地四家后端;本条被明确排除在外)、#5299($exists在布尔比较值下的语义分歧,另一轴,未决)、#5240({field:{}}四后端拒收)、#5324 / #5328(拒收你无法求值的东西)、#5296(nullValueSatisfiesOperator守卫表)、objectstack-ai/cloud#1116(remote Turso 的$null,同族)。Generated by Claude Code