Repository navigation
Description
修 #5146(driver-sql 的 $not 改 NULL-safe,PR #5296)时扫到,不在该 PR 范围内
(packages/services/service-analytics/** 是 #5115 的在飞文件面)。这里只记录。
位置
packages/services/service-analytics/src/read-scope-sql.ts 的 compileNode:
} else if (key === '$not') {
const inner = compileNode(value, qAlias, params);
if (inner) clauses.push(`NOT (${inner})`);
}它是 RLS read scope 的独立 SQL 降解器(与 driver-sql 的 applyFilterCondition
不是同一段代码),被 native-sql-strategy.applyReadScope 与 objectql-strategy 用来
给分析查询加可见性约束。
分叉一:$not 非 NULL-safe
发的是裸 NOT (…)。SQL 三值逻辑下,被比较列为 NULL 的行落进 UNKNOWN 而被排除;
driver-memory / formula 返回这些行。#5146 已由维护者拍板取 NULL-safe,
PR #5296 把 driver-sql 对齐到了 JS 家族(NOT (col IS NOT NULL AND …),守卫按算子
极性下推到叶子)。该 PR 合入后,本文件是仓内最后一个仍按三值逻辑回答 $not 的
SQL 家族实现 —— 也就是说:同一条 read scope,普通查询与分析查询会给出不同的可见
集合。
修的方向已经有现成参照:PR #5296 的 nullSafeNegationOperand(纯函数,逐算子极性
表),照抄到这个编译器上即可,不需要重新裁定语义。
分叉二:{ $not: {} } 编译成空字符串 → 整表
compileNode({}) 返回 '',于是 if (inner) 为假,整条 $not 不产出任何子句;
applyReadScope 又有 if (!sql) return;,所以一条 { $not: {} } 的 read scope
完全不加 WHERE —— 本该零行(NOT TRUE ≡ FALSE),实际整表。
这正是 #5134 / PR #5243 在 driver-sql 上修掉的那条(那边现在编译成 1 = 0),
方向是静默放松,而这里的宿主是 RLS。同一段循环里 $and/$or 的空数组是
fail-closed 抛错的(第 54-56 行),所以缺的只有 $not 这一格。
顺带(方向相反、危害小得多):$or: [{}, {a: 1}] 里的 {} 编译成 '' 后被
.filter((s) => s.length > 0) 丢掉,结果是 (a = 1),而布尔代数上 {} 是 TRUE
析取项、整个 $or 为 TRUE —— 静默收紧。
建议
两处都在同一个函数里,建议一次 PR 修完(与 #5115 的在飞改动错开或合并排期)。
packages/services/service-analytics/src/__tests__/read-scope-sql-conformance.test.ts
已经在跑 FILTER_LOGIC_CASES,但那张表刻意不含 null 处理,所以两条都不会被现有
门禁发现 —— 跨驱动的 null case 归 spec 车道(#5239 / #5146 的 spec 半边)。
关联
#5146($not NULL 语义拍板)、PR #5296(driver-sql 侧实现,可直接参照)、
#5134 / PR #5243(布尔单位元)、#5115(该包在飞)。
Activity
os-zhuang commentedon Aug 4, 2026
实测补一条:同包内还有第二份同缺陷的拷贝,本单正文只点了
read-scope-sql.ts的compileNode。packages/services/service-analytics/src/strategies/filter-normalizer.ts的buildNode是分析查询里作者自己的where的编译路径(compileNode走的是 RLS read scope),两者是各自独立的函数,而三处分叉逐条同向。实测(origin/main@175d789,fixture 的stage列在行 3、4 为 NULL):driver-sql(PR #5296 后)read-scope-sqlfilter-normalizer{ $not: { stage: 'won' } }{ $not: { stage: { $in: ['won'] } } }{ $not: {} }{ $or: [{ a: 'x' }, {}] }filter-normalizer.ts自己的注释也写明是照着compileNode写的(「The combinator handling deliberately mirrorsread-scope-sql.ts'scompileNode, including its fail-closed empty-array rejection, so the two SQL-producing paths in this package cannot drift apart about what a filter MEANS」)—— 所以两者不会互相漂移,但同向偏离了另外五个后端。修本单时请一并处理,否则native-sql-filter-logic-conformance.test.ts(同样遍历全部FILTER_LOGIC_CASES)会在 spec 车道补 null case 时单独变红。另外分一条出去:本单正文提到「同一段循环里
$and/$or的空数组是 fail-closed 抛错的」并把它当现状记录,但那一格其实与 #5134 / #5239 裁定的布尔单位元方向相反且有 pin 测试钉着(read-scope-sql.test.ts:86),不是照抄 PR #5296 能解决的跟进型缺陷 —— 已按需拍板另立 #5322,阻塞 #5239 的一致性表扩条。本单的两条(NULL-safe +{$not:{}})方向已定,不受其影响。Generated by Claude Code
os-zhuang commentedon Aug 4, 2026
认领 + 派发(engine/services 车道 PM,会话
session_01Pbu27iNUfQCHeuS551Rqo7)分支
claude/issue-5297-read-scope-not-null-safe-and-empty,专用 worktree/home/user/objectstack-issue-5297,域domain:services。文件面:
packages/services/service-analytics/src/read-scope-sql.ts+ 该包__tests__,外加一份 changeset。优先级上调,插在 #5240 之前派。 理由是分叉二的宿主:
compileNode({})返回空串 →if (inner)为假 → 整条$not不产出子句 →applyReadScope的if (!sql) return;接手 → 一条{ $not: {} }的 RLS read scope 完全不加 WHERE。本该零行(NOT TRUE ≡ FALSE),实际整表放行。同一段循环里$and/$or的空数组是 fail-closed 抛错的(第 54-56 行),只漏了$not这一格。这与 #5134/PR #5243 在 driver-sql 上修掉的是同一条,但那边的宿主是普通查询,这边的宿主是权限边界 —— 严重度不是一个量级。已定范围(两处同函数,一次 PR 修完):
{ $not: {} }按 fix(driver-sql): 空$and/$or/$not按布尔单位元编译,$or: []不再返回全表 (#5134) #5243 的口径编译为恒假子句。$or里的{}空析取项被.filter(s => s.length > 0)丢掉(方向相反、危害小得多,但同源)一并按布尔单位元修正。$not改 NULL-safe。不需要重新裁定语义 ——$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 已由维护者拍板,PR fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296 已合入origin/main(5aae79096),sql-driver.ts里的nullSafeNegationOperand是纯函数 + 逐算子极性表,照它的口径实现即可。合入后本文件是仓内最后一个按三值逻辑回答$not的 SQL 家族实现。⛔ 不碰
packages/spec/**(跨驱动 null case 归 spec 车道,#5239 / #5146 spec 半边)。⛔ 不碰
packages/plugins/driver-sql/**—— 那是 #5240 的在飞文件面。⛔ 不碰
content/docs/releases/**。read-scope-sql-conformance.test.ts已在跑FILTER_LOGIC_CASES,但那张表刻意不含 null 处理,所以两条缺陷现有门禁都发现不了 —— 本单必须自带 pin,不能指望那张表。Generated by Claude Code
docs(spec): 同步轮散文校订 —— 对 main@cdfbee2f0 实测后落笔 (#5239)
fix(service-analytics): read scope 的 `{ $not: {} }` 不再整表放行,`$not` 改为 …
fix(service-analytics)!: 作者的 `where` 也 NULL-safe —— `$not` 下推守卫、`{$no…
fix(driver-mongodb): 空 `$and`/`$or`/`$not` 归约成布尔单位元,非 filter 节点先响亮拒收 (o…
fix(driver-memory): render the analytics echo as the query it describ…
fix(spec): conformance-case notes and names state each decision in wo…