实现 #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,读者无法从结果里看出来。
后果分档
与既有单子的关系(已查重)
搜过 open issues(inferCube / stripPrefix / analytics cross-object dotted / planCrossObject),无同题单。
建议(需要裁定,不建议顺手改)
stripPrefix 现在同时承担两件语义不同的事:剥 <cube>. 限定符(应该剥)与剥关系路径(不该剥)。分开的判定大致是「首段等于 cube 名 → 剥;否则是关系路径 → 原样铸造,或干脆不铸,交给 JOIN 机制」。但即席 cube 是单表无 join,所以「原样铸造」之后由谁来服务这次穿越、要不要在即席路径上支持关系筛选,是一个产品裁定而不是实现细节 —— 故只立单,不带 PR。
实现 #5669(
where源字段闸门)时路过。本单不是 #5669 引入的,现状在origin/main(3de5ec8)上即如此,#5669 的 PR 也不改它。位置
packages/services/service-analytics/src/analytics-service.ts,inferCubeFromQuery(没有注册 Cube 时为自由查询即席合成 Cube)。三个铸造循环都用stripPrefix():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是基表自己的列)四个组合全部静默通过,无任何拒收:
② 尤其值得注意:同一个 NativeSQLStrategy 在已注册 cube上对同一个 member 是正常 JOIN 的 ——
差别只在于即席 cube 里被铸进了一个同名 dimension。④ 还多一层:响应列名是
"owner.region",值却是基表region,读者无法从结果里看出来。后果分档
no such column: region。analytics: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),无同题单。inferCube仍把数组where当「不是筛选」跳过 —— #5334 之后这个!Array.isArray守卫已经过时 #5353(open,observation):同一个函数的邻居缺陷 —— 那是「数组where被当成没有筛选跳过」,只影响铸出的 dimension 词汇表;本单是「点号 member 被铸成基表列并遮蔽关系穿越」,影响真正进 SQL 的列。两者都在inferCubeFromQuery,建议同一单里一起收(修stripPrefix的判定时顺手把数组下沉补上)。where里点名不存在的字段仍然一路到驱动 —— #4437(measure)/ #5520(dimension)之后,filter 面是同一个缺陷剩下的第三个 param #5669 / 本 PR:where闸门。闸门与本单共用resolveMemberSource,所以两个请求键对点号 member 的读法是一致的(这一点在where-source-field-gate.test.ts里以「与已发布的 dimension 闸门逐字一致」的用例钉住了)。一旦本单裁定这个读法该改,两个键会一起改 —— 这正是把解析放进一个函数的目的。crm_account.industry) renders raw stored values — the same field as a local dimension renders its option labels #5144(open):dataset 的点号关系 dimension 显示原始值而非 option label —— 相邻但不同层(标签解析,不是列解析)。建议(需要裁定,不建议顺手改)
stripPrefix现在同时承担两件语义不同的事:剥<cube>.限定符(应该剥)与剥关系路径(不该剥)。分开的判定大致是「首段等于 cube 名 → 剥;否则是关系路径 → 原样铸造,或干脆不铸,交给 JOIN 机制」。但即席 cube 是单表无 join,所以「原样铸造」之后由谁来服务这次穿越、要不要在即席路径上支持关系筛选,是一个产品裁定而不是实现细节 —— 故只立单,不带 PR。