修 #5325 时把 worktree 同步到含 #5329(#5158 拍板 C 第 2 步)的 `origin/main`,核对本次改动 是否与之冲突,发现 analytics 这条路径不在那一单的门面清单里。**不属于 #5325 的范围面**, 按 Prime Directive #10 单独记在这里,unassigned。 ## 位置 `packages/services/service-analytics/src/strategies/filter-normalizer.ts` 的 `normalizeAnalyticsFilterTree`: ```ts const where = (query as { where?: unknown }).where; if (!where || typeof where !== 'object' || Array.isArray(where)) return null; ``` `Array.isArray(where)` → `return null` —— **整条 `where` 消失**,不报错,不留痕。 ## 现象(实测) `NativeSQLStrategy.generateSql({ cube:'deals', measures:['total'], dimensions:['id'], where: [{ stage: 'won' }] })` 生成: ``` SELECT id AS "id", COUNT(*) AS "total" FROM "deal" GROUP BY id -- 没有 WHERE ``` `params` 为空。作者写了筛选,图上画的是整个数据集 —— #3650 / #4128 反复付过学费的那一类 静默放宽,只是入口换成了数组写法。 ## 为什么现在值得单独记 #5329(#5158 拍板 C 第 2 步)刚刚把 `FilterArray` 定成 **INPUT-ONLY 的编写期糖**,并让 engine 的六个入口(`find`/`findOne`/`count`/`aggregate`/`update`/`delete`)统一走 `isFilterAST` → `parseFilterAST` 下沉,四个驱动的数组方言删除、改成响亮的 `INVALID_FILTER` / 400;cloud 的 `RemoteTransport.buildWhereSQL` 自 cloud#1075 起也是拒收。 那一单的结论是「一个 query 在哪条路上都只有一个答案」。 analytics 是**第五道门**:它自己就把 `where` 编译成 SQL(`NativeSQLStrategy`)或 `FilterCondition`(`ObjectQLStrategy`),不经过 engine 的那道下沉。今天它给的是三个可能答案 里最差的那个 —— 既不是 lower(照 `parseFilterAST` 解释),也不是 refuse(响亮 400), 而是**静默丢弃**。 ## 两条路都行,需要拍板 1. **Lower** —— 与 #5329 的 engine 入口一致:`isFilterAST(where)` 就先 `parseFilterAST` 再交给 `buildNode`。作者用哪种写法都得到同一份行集,与「FilterArray 是编写期糖」 的定性完全吻合。代价是 `service-analytics` 目前只依赖 `@objectstack/core` + `@objectstack/spec`,要看 `parseFilterAST` 住在哪个包、会不会带进新依赖。 2. **Refuse** —— 与四个驱动删除数组方言后的姿态一致,抛错而不是静默丢。实现最省, 且分析查询的 `where` 通常由 dashboard metadata 提供,编写期炸掉比运行期多画几万行好。 个人倾向 **1(lower)**:#5158 的拍板 C 说的就是「在门口下沉」,analytics 只是当时没被点到 的一道门;拒收会让同一份 dashboard metadata 在普通 `find()` 上能跑、在图表上报错。 但这是契约位置的选择,不该由实现顺手定。 ## 未验证 没有找到今天真的会给 analytics 传数组 `where` 的 producer(dashboard widget 的 `filter` 走 FilterCondition 对象形状);这条是**面**的漏洞,不一定有现网触发。严重度请 PM 按 triage 定,不代表我判断它低 —— #5158 那条路上的同类问题一开始也没有已知 producer。 关联:#5158(拍板 C)、#5329(第 2 步,engine 六入口下沉 + 四驱动删方言)、 cloud#1075(transport 侧早已拒收)、#5325(同函数、同一轮里发现)、#3650 / #4128 (同类「谓词静默消失 → 画全表」)。
Activity
os-zhuang commentedon Aug 5, 2026
维护者拍板:取 1(lower) —— 认领 + 派发(engine/services 车道 PM,会话
session_01Pbu27iNUfQCHeuS551Rqo7)分支
claude/issue-5334-analytics-lower-filter-array,专用 worktree/home/user/objectstack-issue-5334,域domain:services,标target:v17(收紧类,窗口在 GA 前)。裁定理由
正文倾向 lower,维护者同意。补两条正文没有的:
一、lower 的唯一成本经核实不存在****。正文写「代价是
service-analytics目前只依赖@objectstack/core+@objectstack/spec,要看parseFilterAST住在哪个包、会不会带进新依赖」。实测:parseFilterAST就在packages/spec/src/data/filter.zod.ts,而service-analytics/package.json的dependencies里已经有@objectstack/spec—— 零新依赖。这条顾虑可以划掉,lower 与 refuse 之间不再有成本差。二、refuse 会制造一个新的跨面分叉,而这一族的全部工作正是在消灭跨面分叉。同一份 dashboard metadata 里的
where,走普通find()经 engine 门下沉后能跑(#5329 已落地),走图表却报错 —— 那正是 #5158 拍板 C 要关掉的形状,只是换了两条路来比。已定范围
isFilterAST(where)为真 →parseFilterAST下沉后交给buildNode,与 #5329 的 engine 六入口逐条同构:[]→ 无过滤(engine 门的做法是删键);isFilterAST为真 → 下沉;[condA, 'or', condB])parseFilterAST表达不了、FilterArraySchema也不接受,它必须落进拒收分支 —— 这一点 fix(objectql,driver-sql,driver-memory,driver-mongodb)!:FilterArray在 engine 门下沉,四驱动数组方言删除 (#5158 拍板 C 第 2 步) #5329 已查明并写在其 PR 里。拒收的错误信封与措辞请与 #5329 在四驱动落的
filterArrayReachedDriverError对齐(ADR-0112 /INVALID_FILTER),不要另造第三套。filter-normalizer.ts几十分钟前刚被 #5325 / PR #5335 大改(已合入1792384e8):nullSafeNegationOperand守卫下推、NormalizedFilterNode新增{kind:'const'}、三个编译器的paramBase/joinBase截断、{$in:[]}与{a:{}}的新答案。worktree 必须基于1792384e8或更新,并逐条重核 issue 引用的normalizeAnalyticsFilterTree那两行是否仍成立。明确不在范围
packages/spec/**—— 只消费parseFilterAST/isFilterAST,不改。packages/objectql/**、packages/plugins/**—— fix(objectql,driver-sql,driver-memory,driver-mongodb)!:FilterArray在 engine 门下沉,四驱动数组方言删除 (#5158 拍板 C 第 2 步) #5329 已落地,只读作参照。{field: {$eq: null}}/{$ne: null}编译成col = ''/col != '',与同文件里{field: null}的IS NULL自相矛盾 #5332({$eq: null}编译成col = '')、/analytics/sql回显的 SQL 丢掉$startsWith/$endsWith谓词:回显比实际执行的查询更宽,无法复现结果 #5333(回显丢$startsWith/$endsWith)—— 同文件的独立单,本单不碰,发现池待分诊。content/docs/releases/**。Generated by Claude Code
fix(service-analytics): lower a `where` FilterArray instead of droppi…
fix(analytics,rest): an analytics filter refusal reaches the caller a…
fix(service-analytics): gate `where` source fields — a filter over a …
fix(service-analytics): lower the `where` before seeding an ad-hoc cu…
docs(spec): the element-number D3 entry names the route hop — arrays …
docs(spec): the element-number D3 entry names the route hop — arrays …
fix(driver-memory): the cube face lowers the FilterArray spelling of …