Skip to content

driver-memory analytics 面:flattenFilterCondition 对所有算子一律摊平数组比较数,{qty: {$eq: [100]}} 变成 $eq: 100(find() 取 0 行,analytics 取 1 行) #5442

Description

@os-zhuang

#5374 时,为了确认新的谓词构造表在「比较数是数组」时的行为,逐个实测了标量算子收到数组比较数会怎样。这一条不属于 #5374 的范围面 —— #5374 裁的是算子映射到了错的目标,这一条在更上游的**降级(lowering)**这一层,#5374 修好之后依然存在。按 Prime Directive #10 单独记在这里,unassigned。

位置

packages/plugins/driver-memory/src/memory-analytics.tsflattenFilterCondition(约 :595):

for (const opKey of opEntries) {
  const cubeOp = this.mongoOperatorToCubeOperator(opKey, key, `${here}.${opKey}`);
  const v = wrapper[opKey];
  out.push({ member: key, operator: cubeOp, values: Array.isArray(v) ? [...v] : [v] });
}

Array.isArray(v) ? [...v] : [v]无差别的:它对 $in / $nin 是对的(那两个算子的比较数本来就是列表),对 $eq / $ne / $gt / $contains 等标量算子则把「一个数组值」摊成了「列表里的第一个元素」。下游取 values[0],数组性就此丢失。

现象(实测,在 #5374 修复之后)

3 行数据,qty 分别为 100 / 200 / 100(id 1/2/3):

where find() analytics 说明
{qty: {$eq: [100]}} [] [1,3] 数组比较数被摊成标量 100,于是匹配上了
{qty: {$eq: []}} [] [] 巧合一致:摊成 undefined 后同样取不到行

find() 的答案是对的:存的是数字 100,[100] 是一个数组,mingo 跨类型比较永不相等,所以 0 行。analytics 面把作者写的数组当成了标量,回答了一个作者没有问的问题。

方向这次是收窄→放大都可能:上表是放大(0 → 2 行),但 {qty: {$eq: [100, 200]}} 会静默丢掉 200,那是收窄。两个方向都是静默的。

为什么是 bug

  1. 它把一个「什么都匹配不上」的查询变成了「匹配上了」。 作者写 $eq: [100] 大概率是笔误(想写 $in),而正确的行为是照实回答 0 行 —— 让笔误可见。摊平之后笔误被修正成了另一个查询,作者永远看不到自己写错了。这是 Prime Directive Add comprehensive test suite for Zod schema validation #12 点名的「消费端宽容」形状:在消费端替生产者猜意图,把错误藏起来。
  2. live query path 不这么做。 normalizeFieldOperatorscase '$eq'result[op] = store(val),数组原样进 mingo。所以又是本包两面对同一个 where 给出不同行集({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240)。
  3. spec 侧允许这个输入。 FilterCondition$eq 是宽类型,所以这个形状能通过 assertFilterConditionShape,不会在门口被拦下。

建议(不代裁决)

两条路,请裁:

倾向 B,因为「0 行」和「作者写错了」在图表上同样看不出区别,而 #5347 / #5328 已经为这一类建立了拒收的惯例;但 B 需要先确认 spec 是否有意允许 $eq: [...] 表达「等于这个数组值」(schemaless 存储里数组是合法列值),若有意,则只能走 A。

未验证的部分

  • 没有测 $contains / $gt 等其余标量算子收到数组时的行为,只测了 $eq / $ne。机制是共用的一行代码,推断其余相同,但未实测。
  • 没有确认是否存在真实调用方依赖当前的摊平行为(例如某个把 $in 写成 $eq 的 widget 元数据)。改之前值得 grep 一遍。

关联:#5374(同文件、同一轮核对里发现;它裁的是算子→谓词层,本条在降级层)、#5347 / #5328(同类「形状不对就拒收」的先例)、#5240(本包两面不得分叉)、#5373

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