Skip to content

analytics/objectql: 跨对象(点号 lookup)维度聚合在 ObjectQL 路径上静默塌成 (null) 桶 / native SQL 报错 #3654

Description

@os-zhuang

调查 #3597/#3602 时,对引擎 aggregate 做独立 trace 发现的功能 bug(与 RLS 无关):跨对象(点号/lookup 遍历)维度的聚合在 ObjectQL 路径上根本不工作 —— native SQL driver 上报错,in-memory 上静默塌进一个 (null) 桶算错

根因:engine.aggregate 没有 join 能力

  • engine.aggregate(packages/objectql/src/engine.ts 的 aggregate 方法)只用 {object, where, groupBy, aggregations} 建 AST,从不解析点号、从不填 joins、从不展开 lookup。对比 findexpandRelatedRecords 会对被引用对象重跑读取(engine.ts:2007-2133),aggregate 一次都不调。
  • SQL driver 的 aggregate(packages/plugins/driver-sql/src/sql-driver.ts:1562-1622)方法里没有任何 .join();joins:true 是广告了但未实现的能力位。把 account.region 丢给 knex → 生成 "account"."region",但 FROM 里只有基表 → SQL 报错(account 不在 FROM)。
  • in-memory 聚合(packages/objectql/src/in-memory-aggregation.ts:81-88projectGroupValue)对 field='account.region'row['account.region'] 平铺取值 → 基对象行里没有这个键 → 恒 '(null)' → 所有行塌进一个 (null)

已复现(in-memory 路径)

applyInMemoryAggregation 直接调用(纯函数,无 mock):

import { applyInMemoryAggregation } from './in-memory-aggregation.js';

const rows = [
  { account: 'acc_west', amount: 10 },
  { account: 'acc_east', amount: 20 },   // 不同 account/region
];
const out = applyInMemoryAggregation(rows, {
  groupBy: ['account.region'],
  aggregations: [{ function: 'sum', field: 'amount', alias: 'revenue' }],
});
// 期望:两个桶(West:10, East:20)
// 实际:
//   [{ 'account.region': '(null)', revenue: 30 }]   ← 全塌成一个 (null) 桶

expect(out).toEqual([{ 'account.region': '(null)', revenue: 30 }]) 通过 —— 坐实塌桶。native SQL 路径的报错未跑真库复现,是代码追查(driver 无 JOIN → 未解析列),但机制清楚。

触发面 / 用户影响

跨对象数据集只在 NativeSQLStrategy(手编 LEFT JOIN)上工作。一旦 NativeSQL 声明失败回退到 ObjectQLStrategy,跨对象查询就坏:

  • 日期分桶的跨对象 dashboard(timeDimensions[].granularity 让 NativeSQL 退场,native-sql-strategy.ts:30)—— 最常见的触发;
  • in-memory driver(dev/demo/测试);
  • external/federated 对象(ADR-0062 D6)。

ObjectQLStrategy 对点号维度走 lookupMember 兜底 return { sql: member },把 account.region 原样当 groupBy 字符串传给引擎(packages/services/service-analytics/src/strategies/objectql-strategy.tslookupMember/resolveFieldName),于是命中上述引擎缺口。

症状:图表要么 500,要么显示一个标着 (null)、把所有组的度量加总到一起的假桶 —— 静默出错,数字看着"有值"却是全表汇总。

与既有工作的关系

可能的方向(未实现,供讨论)

  1. 让 ObjectQL 聚合路径支持 lookup 遍历:engine.aggregate 学会展开点号维度(driver-native join,或 in-memory expand 后再聚合)—— 与 findexpandRelatedRecords 对齐。工作量最大但治本。
  2. fail-closed:ObjectQLStrategy 在检测到点号/跨对象维度且本路径无法 join 时显式拒绝(不论有无 scope),给出清晰错误,而不是塌成 (null) 静默算错。轻量、防止静默错误。
  3. 至少给 in-memory projectGroupValue 的点号平铺取值加护栏 + 告警。

倾向 (2) 作为止血(把静默错误变成响亮错误),(1) 作为后续能力。


🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions