Skip to content

service-analytics 的第二个 SQL 编译器 filter-normalizer.buildNode 仍带着 #5297 的三条分叉:$not 非 NULL-safe、{$not:{}} 不加 WHERE、$or{} 析取项被丢 #5325

Description

@os-zhuang

#5297(read-scope-sql.tscompileNode,PR 见下)时确认,不在该 PR 的文件面内
—— PM 派发时给定的文件面是 read-scope-sql.ts + 该包 __tests__ + changeset,而这一条的
修法要动 NormalizedFilterNode 类型本身和两个 strategy 的编译器,是另一个形状的改动。
这里单独记录,附实测。

本条最早由 #5297 的评论
(#5297 (comment))
点出;#5297 的正文只覆盖 compileNode,所以那一单合入后这一条仍然在。

位置

packages/services/service-analytics/src/strategies/filter-normalizer.tsbuildNode

这是分析查询里作者自己的 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.tscompileFilterNode(编译成真正执行的 SQL);
  • objectql-strategy.tsfilterNodeToCondition(把 $not / $or 原样交给引擎);
  • objectql-strategy.tsrenderFilterNodeSql(回显给浏览器的展示 SQL)。

两件事因此需要先定,再动手:

  1. 守卫加在哪一层。 objectql 路径把 $not 交给引擎,而引擎背后的 driver-sql(fix(driver-sql): $not 取反前先把操作数编译成全域谓词,NULL 行不再被静默排除 (#5146) #5296
    之后)与 driver-memory 本来就已经是 NULL-safe 的 —— 在 buildNode 里加守卫会让那条
    路径双重加守卫(结果仍正确但 SQL 冗余),只在 native-sql-strategy 加则把「一个
    filter 是什么意思」的知识分到了两处。倾向前者(normalizer 是唯一的语义收敛点,与它
    自己 TSDoc 里的立场一致),但这是个契约位置的选择,不该由实现顺手定。
  2. NormalizedFilterNode 需要一个布尔常量。 该联合类型今天只有
    leaf | and | or | not,没有 FALSE 的表示法,{$not:{}} 就是因此只能编译成
    「什么都不发」。加一个常量 kind 会同时改到上面三个编译器。

关联

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions