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

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