Skip to content

非否定路径上的 $ne / $nin / $notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了 $not) #5298

Description

@os-zhuang

#5146 时实测到的邻接分叉,不在该单范围内(维护者的拍板明确只覆盖 $not)。
这里按 Prime Directive #10 记录。

事实(实测,main@88b9b2d5c + PR #5296 的分支;这几条路径 PR #5296 未改动)

fixture:1: stage='won'2: stage='lost'3: stage=NULL4: stage=NULL

filter driver-sql 编译 driver-sql 返回 driver-memory formula matchesFilterCondition
{ stage: { $ne: 'won' } } stage <> 'won' 2 2,3,4 2,3,4
{ stage: { $nin: ['won'] } } stage not in ('won') 2 2,3,4 2,3,4
{ stage: { $notContains: 'w' } } stage NOT LIKE '%w%' 2 2(另一处分叉,另单) 2,3,4

成因与 #5146 同源:SQL 里 NULL <> 'won' 是 UNKNOWN 而不是 TRUE,WHERE 只留 TRUE;
JS 家族用两值逻辑求值,undefined !== 'won' 直接为真。区别只在于 #5146 的宿主是
$not,这里是算子本身

为什么单独记

PR #5296 只把 $not 内部的叶子编译成全域谓词(这是 #5146 拍板的范围),$not
之外的比较逐字符未变 —— 刻意的:改动非否定路径会影响每一条普通查询的 SQL 形状与
索引利用,属于另一个量级的决定,需要单独拍板。所以今天的状态是:

  • { $not: { stage: 'won' } } → 三家一致(NULL 行返回);
  • { stage: { $ne: 'won' } } → 仍然分叉(SQL 少三行)。

对使用者而言,这个不一致是可见的:同一条「不是 won」的列表过滤,SQL 数据源上看不到
stage 为空的记录,内存数据源上看得到;RLS 写侧 check(formula)与读侧 SQL 也会对
同一条规则给出不同答案。

需要拍板的

$ne / $nin / $notContains 是否也采用 NULL-safe 语义(col IS NULL OR col <> v)?

建议与 #5146 的 spec 半边、#5239FILTER_LOGIC_CASES 扩表一并考虑:无论选哪个,
跨驱动 case 都应该把 null 行的去留写进那张表(它今天刻意不含 null 处理,所以这类
分叉现在没有任何门禁能发现)。

关联

#5146($not 的同源分叉,已拍板 NULL-safe)、PR #5296(实现,含逐算子极性表可直接
复用)、#5239(conformance 表)、#5240({ field: {} } 的三个答案)、cloud#1076。

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