修 #5297 (read-scope-sql.ts 的 compileNode,PR 见下)时确认,不在该 PR 的文件面内
—— PM 派发时给定的文件面是 read-scope-sql.ts + 该包 __tests__ + changeset,而这一条的
修法要动 NormalizedFilterNode 类型本身和两个 strategy 的编译器,是另一个形状的改动。
这里单独记录,附实测。
本条最早由 #5297 的评论
(#5297 (comment) )
点出;#5297 的正文只覆盖 compileNode,所以那一单合入后这一条仍然在。
位置
packages/services/service-analytics/src/strategies/filter-normalizer.ts 的 buildNode。
这是分析查询里作者自己的 where 的编译路径(compileNode 走的是 RLS read scope),
两者是各自独立的函数。filter-normalizer.ts 自己的 TSDoc 写明是照着 compileNode 写的:
The combinator handling deliberately mirrors read-scope-sql.ts's compileNode,
including its fail-closed empty-array rejection, so the two SQL-producing paths in this
package cannot drift apart about what a filter MEANS.
—— 所以两者不会互相漂移,但在 #5297 修掉 compileNode 之后,这一份同向偏离 了另外
五个后端。
实测
origin/main @ 26e1029f5 + #5297 的分支,fixture 与 driver-sql 的
sql-driver-not-null-safe.test.ts 逐行相同(行 3、4 的 stage 为 NULL,行 3 的 amount
为 NULL,行 4 的 owner 为 NULL);经 NativeSQLStrategy.generateSql 生成 SQL 后在
sql.js 上取行:
where
生成的 WHERE
实测行
应为(JS 家族 / #5296 后的 driver-sql)
{ $not: { stage: 'won' } }
NOT (stage = $1)
2
2,3,4
{ $not: { stage: { $in: ['won'] } } }
NOT (stage IN ($1))
2
2,3,4
{ $not: {} }
没有 WHERE
1,2,3,4
零行
{ $or: [{ stage: 'won' }, {}] }
stage = $1
1
1,2,3,4
{ $not: { $or: [{stage:'won'},{owner:'u1'}] } }
NOT ((stage = $1 OR owner = $2))
2
2,4
三条与 #5297 逐条同向:$not 发的是裸 NOT (…)(三值逻辑,NULL 行被丢);
buildNode({}) 返回 null 于是 if (inner) 为假、整条 $not 消失;$or 的 {} 分支
在 .filter((n) => n !== null) 里被丢掉,整条 $or 收紧成剩余分支。
宿主与 #5297 不同:这里是作者写的 where,不是权限边界。所以 {$not:{}} 的后果是
「图表画了整个数据集」而不是越权 —— 但那正是 #3650 / #4128 反复付过学费的那一类静默放宽,
而 $not 非 NULL-safe 的后果是同一条 widget filter 在分析查询与普通查询上给出不同的行集。
为什么不是照抄 #5297 的补丁
buildNode 的产物是 NormalizedFilterNode 树,两个 strategy 各自编译它,一共三处
NOT (${inner}):
native-sql-strategy.ts 的 compileFilterNode(编译成真正执行的 SQL);
objectql-strategy.ts 的 filterNodeToCondition(把 $not / $or 原样交给引擎 );
objectql-strategy.ts 的 renderFilterNodeSql(回显给浏览器的展示 SQL)。
两件事因此需要先定,再动手:
守卫加在哪一层。 objectql 路径把 $not 交给引擎,而引擎背后的 driver-sql(fix(driver-sql): $not 取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296
之后)与 driver-memory 本来就已经是 NULL-safe 的 —— 在 buildNode 里加守卫会让那条
路径双重加守卫 (结果仍正确但 SQL 冗余),只在 native-sql-strategy 加则把「一个
filter 是什么意思」的知识分到了两处。倾向前者(normalizer 是唯一的语义收敛点,与它
自己 TSDoc 里的立场一致),但这是个契约位置的选择,不该由实现顺手定。
NormalizedFilterNode 需要一个布尔常量。 该联合类型今天只有
leaf | and | or | not,没有 FALSE 的表示法,{$not:{}} 就是因此只能编译成
「什么都不发」。加一个常量 kind 会同时改到上面三个编译器。
关联
read-scope-sql 的 $not 有两处与 SQL 驱动分叉:非 NULL-safe(#5146 后的最后一个异类),且 { $not: {} } 编译成空 → RLS 整表放行 #5297 / 其 PR —— read-scope-sql.ts 的同三条,已修;本条是同包内的第二份拷贝。
$not 的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {} 一个是 TRUE 一个是 FALSE #5146 / PR fix(driver-sql): $not 取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296 —— $not 的 NULL 语义已由维护者拍板,不需要重新裁定 ;
sql-driver.ts 的 nullSafeNegationOperand 是参照实现。
SqlDriver.applyFilterCondition 丢弃编译成空的 $and/$or 子过滤器,而不是套用布尔单位元 —— 与同仓 matchesFilterCondition / driver-memory 相反 #5134 / PR fix(driver-sql): 空 $and/$or/$not 按布尔单位元编译,$or: [] 不再返回全表 (#5134) #5243 —— 布尔单位元。
空组合子在同仓有两个对立答案:五个后端归约成布尔单位元,service-analytics 的两个编译器 fail-closed 抛错 —— #5239 的一致性表四条因此进不了表 #5322 —— $and: [] / $or: [] 空组合子的独立裁定。buildNode 的空数组同样是
fail-closed 抛错,不属于本条 ,按 空组合子在同仓有两个对立答案:五个后端归约成布尔单位元,service-analytics 的两个编译器 fail-closed 抛错 —— #5239 的一致性表四条因此进不了表 #5322 走。
native-sql-filter-logic-conformance.test.ts 同样遍历 FILTER_LOGIC_CASES;那张表
刻意不含 null 处理,所以这三条现有门禁都发现不了 —— 但 spec 车道([spec] FILTER_LOGIC_CASES 补空组合子的布尔单位元四条 —— 需与 driver-mongodb 的单位元归约同时落地 #5239 )给该表补
null case 时,这个文件会单独变红。
修 #5297(
read-scope-sql.ts的compileNode,PR 见下)时确认,不在该 PR 的文件面内—— PM 派发时给定的文件面是
read-scope-sql.ts+ 该包__tests__+ changeset,而这一条的修法要动
NormalizedFilterNode类型本身和两个 strategy 的编译器,是另一个形状的改动。这里单独记录,附实测。
本条最早由 #5297 的评论
(#5297 (comment))
点出;#5297 的正文只覆盖
compileNode,所以那一单合入后这一条仍然在。位置
packages/services/service-analytics/src/strategies/filter-normalizer.ts的buildNode。这是分析查询里作者自己的
where的编译路径(compileNode走的是 RLS read scope),两者是各自独立的函数。
filter-normalizer.ts自己的 TSDoc 写明是照着compileNode写的:—— 所以两者不会互相漂移,但在 #5297 修掉
compileNode之后,这一份同向偏离了另外五个后端。
实测
origin/main@26e1029f5+ #5297 的分支,fixture 与driver-sql的sql-driver-not-null-safe.test.ts逐行相同(行 3、4 的stage为 NULL,行 3 的amount为 NULL,行 4 的
owner为 NULL);经NativeSQLStrategy.generateSql生成 SQL 后在sql.js 上取行:
where{ $not: { stage: 'won' } }NOT (stage = $1)22,3,4{ $not: { stage: { $in: ['won'] } } }NOT (stage IN ($1))22,3,4{ $not: {} }1,2,3,4{ $or: [{ stage: 'won' }, {}] }stage = $111,2,3,4{ $not: { $or: [{stage:'won'},{owner:'u1'}] } }NOT ((stage = $1 OR owner = $2))22,4三条与 #5297 逐条同向:
$not发的是裸NOT (…)(三值逻辑,NULL 行被丢);buildNode({})返回null于是if (inner)为假、整条$not消失;$or的{}分支在
.filter((n) => n !== null)里被丢掉,整条$or收紧成剩余分支。宿主与 #5297 不同:这里是作者写的
where,不是权限边界。所以{$not:{}}的后果是「图表画了整个数据集」而不是越权 —— 但那正是 #3650 / #4128 反复付过学费的那一类静默放宽,
而
$not非 NULL-safe 的后果是同一条 widget filter 在分析查询与普通查询上给出不同的行集。为什么不是照抄 #5297 的补丁
buildNode的产物是NormalizedFilterNode树,两个 strategy 各自编译它,一共三处NOT (${inner}):native-sql-strategy.ts的compileFilterNode(编译成真正执行的 SQL);objectql-strategy.ts的filterNodeToCondition(把$not/$or原样交给引擎);objectql-strategy.ts的renderFilterNodeSql(回显给浏览器的展示 SQL)。两件事因此需要先定,再动手:
$not交给引擎,而引擎背后的 driver-sql(fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296之后)与 driver-memory 本来就已经是 NULL-safe 的 —— 在
buildNode里加守卫会让那条路径双重加守卫(结果仍正确但 SQL 冗余),只在
native-sql-strategy加则把「一个filter 是什么意思」的知识分到了两处。倾向前者(normalizer 是唯一的语义收敛点,与它
自己 TSDoc 里的立场一致),但这是个契约位置的选择,不该由实现顺手定。
NormalizedFilterNode需要一个布尔常量。 该联合类型今天只有leaf | and | or | not,没有 FALSE 的表示法,{$not:{}}就是因此只能编译成「什么都不发」。加一个常量 kind 会同时改到上面三个编译器。
关联
$not有两处与 SQL 驱动分叉:非 NULL-safe(#5146 后的最后一个异类),且{ $not: {} }编译成空 → RLS 整表放行 #5297 / 其 PR ——read-scope-sql.ts的同三条,已修;本条是同包内的第二份拷贝。$not的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {}一个是 TRUE 一个是 FALSE #5146 / PR fix(driver-sql):$not取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296 ——$not的 NULL 语义已由维护者拍板,不需要重新裁定;sql-driver.ts的nullSafeNegationOperand是参照实现。$and/$or/$not按布尔单位元编译,$or: []不再返回全表 (#5134) #5243 —— 布尔单位元。$and: []/$or: []空组合子的独立裁定。buildNode的空数组同样是fail-closed 抛错,不属于本条,按 空组合子在同仓有两个对立答案:五个后端归约成布尔单位元,service-analytics 的两个编译器 fail-closed 抛错 —— #5239 的一致性表四条因此进不了表 #5322 走。
native-sql-filter-logic-conformance.test.ts同样遍历FILTER_LOGIC_CASES;那张表刻意不含 null 处理,所以这三条现有门禁都发现不了 —— 但 spec 车道([spec] FILTER_LOGIC_CASES 补空组合子的布尔单位元四条 —— 需与 driver-mongodb 的单位元归约同时落地 #5239)给该表补
null case 时,这个文件会单独变红。