Skip to content

driver-memory analytics 面的 cube 值往返是有损的:布尔比较数变成数字({is_active: true} 取到 0 行),null 比较数被整条丢掉(取到全表) #5373

Description

@os-zhuang

#5345(analytics 面两处静默 continue)时,为了给「哪些算子这个面真的能编译」写断言,必须逐个实测编译出来的谓词。在这一步发现的两个缺陷不属于 #5345 的范围面(那一单裁定的是 $or/$not 与无 cube 映射的算子 —— 都是算子层面),按 Prime Directive #10 单独记在这里,unassigned。

这两条都在比较数(值)层面,#5345 的修法(拒收无法编译的算子)碰不到它们:下面这些算子全部在 #5345 之后依然被声明为「本面支持」,只是它们的值编译错了。

位置

packages/plugins/driver-memory/src/memory-analytics.ts:

  • stringifyForCube / coerceFilterValue(约 :640 / :660)—— 本文件私有的一对,和 service-analytics/src/strategies/filter-normalizer.ts 里的同名函数是两份独立实现;
  • flattenFilterCondition 开头的 if (raw == null) continue;

cube 规格把过滤值序列化成 string[],所以每个比较数都要 JS 值 → 字符串 → JS 值往返一次。这个往返对非字符串类型是有损的。

现象(实测)

固定数据 3 行,分别用 analytics 面(MemoryAnalyticsService.query,measures: ['t.count'])和同一个 driver 的 find() 跑同一个 where:

{ id: 1, is_active: true,  name: 'alpha', closed_at: null }
{ id: 2, is_active: false, name: 'beta',  closed_at: '2026-01-01' }
{ id: 3, is_active: true,  name: 'gamma', closed_at: null }
where analytics count find() 行数 应为 方向
{ is_active: true } 0 2 2 缩小
{ is_active: false } 0 1 1 缩小
{ closed_at: null } 3 2 2 放大

布尔:'1' 出去,数字 1 回来

stringifyForCube(true)   // → '1'
coerceFilterValue('1')   // → 1   ← /^-?\d+$/ 先命中,回不到 true

于是 matchStage{ is_active: { $eq: 1 } },而记录里存的是 true。mingo 跨类型比较恒不等,所以一行都不匹配stringifyForCube 的注释说布尔转 '1'/'0' 是「so that downstream consumers expecting SQLite-style numeric booleans match correctly」—— 这个理由对生成 SQL 的那一半成立,对 in-memory 那一半不成立,而两半共用同一个编码。

值得单独指出:{ where: { is_active: true, stage: { $nin: ['lost'] } } } 正是 AnalyticsQuerySchema.wheredocstring 示例(packages/spec/src/data/analytics.zod.ts)。规格自己举的例子在这个面上取到零行。

null:整条谓词消失

flattenFilterCondition 第一行就是 if (raw == null) continue;,所以 { closed_at: null } 连一条 cube 条目都不产生 —— 少一个约束 = 多返回行,就是 #3948 立规矩针对的放大方向。

这条和 #5332 是同一个形状的不同包:#5332 记的是 service-analyticsfieldLeaves,而那一半恰恰是对的 —— 它有 raw === nullnotSet(IS NULL)分支,只有 {$eq: null} 走算子映射时才退化成 = ''。driver-memory 这一半连 {field: null} 都直接丢,比 #5332 描述的状态更差一档。

为什么是 bug

  1. 两个方向都错,且都静默。 布尔那条是「图表空了」,null 那条是「图表算了全表」。前者作者至少看得见异常;后者看不见 —— 一个「结案日期为空」的部件统计了包括已结案在内的所有记录,渲染出来和正常部件没有区别。
  2. 同一 driver 的两个面对同一个 where 给出不同行集。 find() 全部答对,analytics 面三条全错。这正是 { field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 为本包立下的不变量(「一个后端的两半对一个过滤器是什么意思意见不一致,就是那条裁决要关掉的分叉」)所禁止的形状。
  3. driver-memory 的 analytics 面静默丢弃大半个 filter:$or/$not 整条丢,$between/$startsWith/$null/$regex 因无 cube 映射而丢 —— 聚合结果被放大 #5345 关不掉它。 driver-memory 的 analytics 面静默丢弃大半个 filter:$or/$not 整条丢,$between/$startsWith/$null/$regex 因无 cube 映射而丢 —— 聚合结果被放大 #5345 之后,$eq / 隐式相等 / 这些算子都在 ANALYTICS_FILTER_CAPABILITIES 里被声明为支持。声明支持而编译错,是 Prime Directive chore: version packages #10 的 declared ≠ enforced,只是错在值而不是算子。

建议(不代裁决)

两条共一个根因(string[] 编码对非字符串类型有损),建议一起裁,大致三条路:

我倾向 B(值不该在内部表示里被降级成字符串),但这是本面数据表示的公开形状,请 PM/维护者裁。

未验证的部分

只测了上表三个形状。没有测数字比较数经 '100'100 往返后与存储为字符串 '100' 的列比较会怎样(可能是同一个根因的第三个症状),也没有统计现网有多少部件的 where 带布尔或 null 比较数。严重度请 PM 按 triage 定。

关联:#5345(同函数、同一轮核对里发现;它裁的是算子层)、#5332(service-analytics 里同形状的一半,那半还更好)、#3948(no-silent-drop)、#5240(本包两面不得分叉)。

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