Skip to content

[finding] filter-normalizer 的 $not allowNull 守卫在 #5298 第二批之后变成冗余的第二层(谓词等价,SQL 多一层括号) #6005

Description

@os-zhuang

PR #6004(#5977 / #5298 第二批)实施途中的范围外观察,按 Prime Directive #10 记录,不指派。观察类:今天没有任何用户会撞到它,谓词逐行等价,执行计划不变 —— 故挂 finding、不挂 pm:queue,严重度交分诊轮判。

事实

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

#5146 为 $not 引入了 nullSafeNegationOperand → guardFieldEntry → nullGuardForFieldSpec,后者对一个字段 spec 给三种判定:none / requireValue / allowNull。

PR #6004 之后,fieldLeaves 在叶子层自己发守卫,条件恰好就是 nullValueSatisfiesOperator(op, v) && !operatorIsNullTotal(op, v) —— 也就是 nullGuardForFieldSpec 的 allowNull 判定本身。于是 allowNull 这一支变成冗余的第二层:

{$not: {stage: {$ne: 'won'}}}
→ NOT ((stage IS NULL OR (stage IS NULL OR stage != $1)))
              ^^^^^^^^^^^^^^^  guardFieldEntry 的 allowNull 支
                                ^^^^^^^^^^^^^^^  fieldLeaves 的叶子守卫

X OR (X OR Y) ≡ X OR Y,所以行为完全等价(PR #6004 实测钉住:$not of $ne / $nin 仍答 ['1'],极性未被拓宽)。#5146 的 header 本来就把「引擎路径守卫两次、幂等」写成已接受的代价,这条只是把同一笔代价又叠了一层。

requireValue 支不受影响,仍然必需(正向比较、以及多算子 spec 里只要有一个算子不接受 NULL 的情况)。

为什么可能值得清

allowNull 支能到达的算子,恰好只有 $ne(非 null 比较数)/ $nin(非空)/ $notContains —— 其余要么 total($null / $exists / $eq: null / $ne: null / 空 $nin)走 none,要么正向走 requireValue。也就是说这三个算子现在每一个都自带守卫,allowNull 支再没有独占的落点。

去掉它(改为 none)在多算子 spec 上也成立,靠分配律:

  • 现在:(c IS NULL OR (X AND Y))
  • 去掉后:(c IS NULL OR X) AND (c IS NULL OR Y)

两者逐行等价。

为什么 PR #6004 没有顺手做

  1. 它改的是 $not 的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {} 一个是 TRUE 一个是 FALSE #5146 的守卫代码,不在 [#5298 第二批] filter-normalizer(Cube 面)NULL-safe 对齐:先实测既有 NULL 行为,分叉则改写;N1 入表按后落地者规则 #5977 的验收范围内(「scope = the issue」);
  2. 删肢需要按 fixture 三分诊重新逐条判 filter-normalizer-not-null-safe.test.ts 里 $not 的语义在 driver-sql 与 driver-memory / formula 之间分叉:NULL 行的去留相反,$not: {} 一个是 TRUE 一个是 FALSE #5146 的钉子 —— 其中至少一条($not of $ne 的生成 SQL 断言)会跟着变形,值得单独一轮而不是搭车;
  3. 收益是可读性 / SQL 噪音,不是正确性。

关联

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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