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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions