Skip to content

analytics 自动推断路径:inferCubeFromQuery 的 stripPrefix 把关系穿越 owner.region 铸成基表列 region —— 基表恰好有同名列时静默筛/分组错列(两个策略、两个请求键均如此) #5739

Description

@os-zhuang

实现 #5669(where 源字段闸门)时路过。本单不是 #5669 引入的,现状在 origin/main(3de5ec8)上即如此,#5669 的 PR 也不改它。

位置

packages/services/service-analytics/src/analytics-service.ts,inferCubeFromQuery(没有注册 Cube 时为自由查询即席合成 Cube)。三个铸造循环都用 stripPrefix():

const stripPrefix = (m) => (m.includes('.') ? m.split('.').slice(1).join('.') : m);
...
for (const d of query.dimensions || []) { const key = stripPrefix(d); dimensions[key] = { ..., sql: key }; }

stripPrefix 的本意是剥掉 <cube>.<field> 限定符。但它对关系穿越一视同仁:owner.region 也被剥成 region,并铸出 dimensions.region = { sql: 'region' } —— 一个基表列的 dimension。

下游每个解析器都先查 cube 的 bag:lookupMember 的「plain second-segment lookup (legacy behaviour)」这一档命中 region,于是在到达「synthetic relation traversal」那一档之前就返回了基表列。关系穿越就此被基表列遮蔽。

实测(测试双,crm_account 字段含 id/name/industry/region/owner —— 注意 region基表自己的列)

四个组合全部静默通过,无任何拒收:

① ObjectQL, where: {'owner.region': 'NA'}
   → executeAggregate 收到 filter: {"region":"NA"}          ← 筛的是基表 region

② NativeSQL, where: {'owner.region': 'NA'}
   → SELECT COUNT(*) AS "count" FROM "crm_account" WHERE region = $1     ← 没有 JOIN

③ ObjectQL, dimensions: ['owner.region']
   → groupBy: ["region"]

④ NativeSQL, dimensions: ['owner.region']
   → SELECT region AS "owner.region", COUNT(*) ... GROUP BY region       ← 列名标着关系属性,值来自基表

② 尤其值得注意:同一个 NativeSQLStrategy 在已注册 cube上对同一个 member 是正常 JOIN 的 ——

(authored cube, dimensions: {})
→ SELECT COUNT(*) ... FROM "crm_account" LEFT JOIN "owner" ON "crm_account"."owner" = "owner"."id" WHERE "owner"."region" = $1

差别只在于即席 cube 里被铸进了一个同名 dimension。④ 还多一层:响应列名是 "owner.region",值却是基表 region,读者无法从结果里看出来。

后果分档

  • 基表恰好有同名列 → 静默筛错列 / 分组错列,行数与图表都是错的,没有任何错误可读(①②③④)。这是「静默错行」类,比报错更糟。
  • 基表没有同名列 → 落到 no such column: regionanalytics: where 里点名不存在的字段仍然一路到驱动 —— #4437(measure)/ #5520(dimension)之后,filter 面是同一个缺陷剩下的第三个 param #5669 之后这一支答 400 INVALID_FIELD 且点名 region;消息本身是诚实的(进 SQL 的列确实是 region),但调用方写的是 owner.region,所以指名的字段和他写的不是同一个,读起来仍然费解。
  • 跨对象信封(planCrossObject)在这条路上看不见它:它收到的是已解析出的裸名 region,没有点号可分类,所以既不 JOIN 也不拒收。已注册 cube 上它是会拒的(ObjectQL 答「cannot evaluate a cross-object filter」)。

与既有单子的关系(已查重)

搜过 open issues(inferCube / stripPrefix / analytics cross-object dotted / planCrossObject),无同题单。

建议(需要裁定,不建议顺手改)

stripPrefix 现在同时承担两件语义不同的事:剥 <cube>. 限定符(应该剥)与剥关系路径(不该剥)。分开的判定大致是「首段等于 cube 名 → 剥;否则是关系路径 → 原样铸造,或干脆不铸,交给 JOIN 机制」。但即席 cube 是单表无 join,所以「原样铸造」之后由谁来服务这次穿越、要不要在即席路径上支持关系筛选,是一个产品裁定而不是实现细节 —— 故只立单,不带 PR。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions