fix(service-analytics): lower the where before seeding an ad-hoc cube's dimensions (#5353) - #5764
Merged
Merged
Conversation
…be's dimensions (#5353) `inferCubeFromQuery` guarded its `where` arm with `!Array.isArray(query.where)`, written when an array `where` was not a filter. #5334 made it one, so one filter minted two different cubes depending on its spelling: where: {stage: 'won'} -> dimensions: {stage} where: [['stage','=','won']] -> dimensions: {} The `where` is now lowered to its canonical FilterCondition first, so the spelling stops mattering. The lowering is #5334's own, extracted from `normalizeAnalyticsFilterTree` as `lowerAnalyticsWhere` so exactly one of it survives; keys are read via `conjunctFieldKeys`, which descends `$and` because the lowering introduces `$and` where the object spelling has none. `$or` / `$not` contribute no key on either spelling, as before. No compiled statement, bound value or gate verdict changes: both spellings already compiled a byte-identical predicate, and an inferred cube declares no `joins`, so `qualifyAndRegisterJoin` leaves the newly-declared members' columns bare. The rejection suggestion lists and `getMeta` now read alike for both. A DOTTED `where` key stays spelling-dependent, deliberately: unifying it means either propagating #5739's base-column mis-cast to the array spelling (measured: a working traversal becomes a different-rows base-column filter, or a 400) or splitting a verdict #5740 shares with the `dimensions` request key. Left to #5739 and pinned by tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 6, 2026 04:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5353
前提重验(对 origin/main,fce4c7385)
立单前提仍然成立,逐条核过:
analytics-service.ts:1577上!Array.isArray(query.where)守卫在位,一字未改。where源字段闸门 —— 点名不存在字段的筛选答 400 INVALID_FIELD 而非驱动 500 (#5669) #5740 写下的说明,明确记着这个 skip 属于 observation:inferCube仍把数组where当「不是筛选」跳过 —— #5334 之后这个!Array.isArray守卫已经过时 #5353、本次不动。filter-normalizer.ts的normalizeAnalyticsFilterTree自 analytics 是 #5158 拍板 C 漏掉的第五道门:where为数组(FilterArray 糖)时被normalizeAnalyticsFilterTree静默丢弃,图表画全表 #5334 起对数组where走isFilterAST→parseFilterAST下沉,所以「数组where是真筛选」也成立。改了什么
inferCubeFromQuery的where臂先下沉再取键。下沉复用 #5334 那一次,不是新接一份:normalizeAnalyticsFilterTree里的数组分支抽成lowerAnalyticsWhere(返回下沉后的 canonicalFilterCondition),normalizeAnalyticsFilterTree变成「这次下沉 +buildNode」。全仓只有一处isFilterAST/parseFilterAST调用,两处读者(闸门读树的叶子,铸造读条件的键)看同一次下沉的两种视图。取键走新的
conjunctFieldKeys,它下钻$and、不下钻$or/$not。下钻$and不是顺手加的:下沉本身会引入$and—— 平铺数组[[a,…],[b,…]]是对象{a…, b…}的数组写法,parseFilterAST把它降成{$and: [{a…},{b…}]}。只读下沉后对象自己的顶层键的话,答案是「只有$and」即什么都没有,两种写法照旧铸出两个 cube。连带效果:对象写法的显式{$and: […]}现在也会铸出其 conjunct 的键(以前不会),这与{a…, b…}拉平,属同一个缺陷类。$or/$not两种写法今天都不贡献键,保持不变。刻意不用
collectFilterLeaves:叶子回答「编出的谓词绑了哪些 member」,这个 bag 回答「调用方可以命名什么」,两者真的不同 ——{stage: {$in: []}}降成布尔常量 FALSE,不绑任何 member,却仍然命名了stage。判定不变性(实测,不是推断)
两种写法修前就编出逐字相同的谓词,这也是本单属 observation 而非缺陷的原因:
resolveFieldSql对未声明 member 回落裸列名,而qualifyAndRegisterJoin只在 cube 声明了joins时才加限定 —— 即席 cube 从不声明。所以新铸的 dimension 只是把这些 member 从「未声明裸列」分支挪到「已声明 dimension」分支,两分支得出同一列。WHERE stage =且 不含"deal"."stage")。where里点名不存在的字段仍然一路到驱动 —— #4437(measure)/ #5520(dimension)之后,filter 面是同一个缺陷剩下的第三个 param #5669 闸门判定:不变。它读的是筛选叶子而非cube.dimensions,所以本单改不动它 —— 实测确认。变的是拒收消息里的Valid filter members:建议表,方向正是本闸门家族要消除的分叉:同一份筛选,两种写法,现在读到同一条消息。一处没有统一的:点号
where键{'owner.region': 'NA'}铸出剥尾后的基表列region;数组写法[['owner.region','=','NA']]什么都不铸,编出关系穿越。两个方向都不是本单能选的,实测:stripPrefix)是回归:② 从能跑的穿越变成WHERE region = $1(基表有同名列时静默筛错列),基表无该列时变成 400。where源字段闸门 —— 点名不存在字段的筛选答 400 INVALID_FIELD 而非驱动 500 (#5669) #5740 刻意钉住的不变量:点号 member 在where与dimensions两个请求键上共用resolveMemberSource、逐字同答,且「一旦这个读法被判定为错,两个键必须同时改」。只在where一侧撤会让两键分叉。所以点号键原样保留各自写法今天的答案(残留循环逐字复刻 origin/main 的对象顶层键 +
stripPrefix),归 #5739 裁定 —— 该单已带needs-user-decision,实测与两个子选项的代价已评论上去。#5353 自己的范围(过时的!Array.isArray守卫)是完整交付的:守卫已删,数组走下沉。测试
新增
infer-cube-where-spelling-parity.test.ts(23 例)。平价表直接取 #5334 自己的EQUIVALENT_SPELLINGS,让两个文件不会对「同一份筛选的两种写法」各说各话 —— #5334 断言两者选出同样的行,这里断言两者铸出同样的cube。每例都断言「精确期望集合」而不只是「两边相等」:只比两边的表会在两边都空时同样通过,而那正是缺陷本身。pnpm --filter @objectstack/service-analytics test→ 56 files / 1008 tests 全绿(含 fix(service-analytics):where源字段闸门 —— 点名不存在字段的筛选答 400 INVALID_FIELD 而非驱动 500 (#5669) #5740 的 31 例、analytics 是 #5158 拍板 C 漏掉的第五道门:where为数组(FilterArray 糖)时被normalizeAnalyticsFilterTree静默丢弃,图表画全表 #5334 等价性用例零回归)。pnpm --filter @objectstack/rest test→ 55 files / 778 tests 全绿。tsc --noEmit:本包在 type-check 覆盖率账本里带 DEBT 条目(无typecheck脚本)。直接跑:改动前后同为 7 条既有报错,我的三个文件零报错。check:nul-bytesOK(5648 文件);对三个改动文件另做了越过门盲区的自查grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'→ 无命中;eslint三个文件零输出。改动只在packages/services/service-analytics/src/,不碰 spec/docs/i18n,故 spec 生成物与 i18n 门无关。normalizeAnalyticsFilterTree等导出在本包外无消费者;依赖本包的rest/cli/verify/qa-dogfood中无数组where、无点号where的 analytics fixture。反向验证(先预测方向,再跑)
恢复
!Array.isArray(query.where)守卫。预测 13 红 / 10 绿,并预先点名红的集合:平价表 13 例中降成合取且至少命名一个字段的 11 例、seeds the where keys ALONGSIDE …、以及bare keys reach parity even when a dotted key rides along。实测恰好这 13 例。两处绿是刻意的,写在用例头里而不留给下一位读者去踩:
$or根的两例(prefix OR group、nested group — OR of an AND)修前修后都绿 —— 两种写法经由析取都不贡献键,那里本来就没有不对称可修。把表读成「13 例全红」是错的。collectFilterLeaves的重构(会把{owner: {region: 'NA'}}拉平成叶子owner.region并重新铸出region)响亮地失败,而不是悄悄改掉一条 fix(service-analytics):where源字段闸门 —— 点名不存在字段的筛选答 400 INVALID_FIELD 而非驱动 500 (#5669) #5740 与dimensions请求键共享的判定。Generated by Claude Code