fix(objectql,service-analytics): 报告对象「实际所在」的 datasource,而非它声明的那个 (#5288) - #6820
Conversation
analytics 的 `getObjectDatasource` 探针读的是 `getObject(name).datasource`
—— 对象**声明**的值,也就是 `ObjectQL.getDriver` 五步解析顺序里的第 1 步。
`ObjectSchema.datasource` 带 `.default('default')`,而 `'default'` 在引擎里
的含义是「没有显式绑定,继续往下查」,所以凡是被 `datasourceMapping` 规则、
ADR-0057 §3.6 生命周期分流、或所属 package 的 `defaultDatasource` 路由过去
的对象,一律回答 `'default'`,在探针的读者那里又被当成「主库」。
`sys_audit_log` 就是活标本:`lifecycle.class: 'audit'` 把它放到 `telemetry`,
全程没有任何声明可读。于是 #5033 那条查询期诊断 —— 它存在的唯一意义就是
点名「表不在哪个库」—— 点错了库。
引擎侧新增 `ObjectQL.resolveEffectiveDatasource(objectName)`:`getDriver`
既有解析顺序的公开、只算名字的那一面,抽出来是为了让这个顺序只存在一份
(与 #4462 抽出 `resolveMappedDatasource` 是同一个理由:第二份更短的路由
实现必然少一步,而且是悄悄地少)。`getDriver` 改为消费同一个解析器,行为
逐条不变 —— 优先级、声明/映射的 datasource 无驱动时拒绝回落到默认库、
两条诊断文案都原样保留。
对象什么都没被绑定、纯粹骑着部署默认驱动时,访问器答 `undefined`:默认驱动
保留自己的自然名(#3826),那是驱动名而不是谁把这个对象绑上去的 datasource;
而这也正是消费方早已写在文档里的口径。
analytics 侧只做一件事:探针改问引擎。路由规则**没有**在 analytics 侧重算。
#5115 的编译期闸门判据一字未改,变化的是它的输入现在能对隐式路由的对象作答。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USNUyHEr7uaU6MoEWXitei
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31297526579 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
Queue-failure triage (services-lane PM, Verdict: not this PR. Checklist item 3 (batch-mate conflict) or flakiness — re-queued exactly once, not repeatedly. Item 1 — is the failing test in a package this PR changed? No. Item 2 — broken on main for everyone? No. The test arrived on main with So: item 3, or intermittent. Either way the bot's own guidance applies — a blind re-queue rebuilds everything behind it, and repeated re-queues burn the queue for nothing. Auto-merge re-armed once. If the identical signature comes back on the next attempt, that is flakiness confirmed by repetition and it gets its own card (fix or quarantine that test) rather than a third attempt. Local verification note, so nobody repeats a dead end: I could not reproduce against the shared checkout — its HEAD sits at Generated by Claude Code |
Closes #5288
问题
plugin.ts把 analytics 的探针接成了对象声明的值:这是
ObjectQL.getDriver五步解析顺序里的第 1 步。而ObjectSchema.datasource带
.default('default'),'default'在引擎里的含义恰恰是「没有显式绑定,继续往下查」,不是「主库」。于是被
datasourceMapping规则、ADR-0057 §3.6 生命周期分流、或所属package 的
defaultDatasource路由走的对象,探针一律回答'default'。sys_audit_log就是活标本:靠lifecycle.class: 'audit'落到telemetry,全程没有任何声明可读。#5033 那条查询期诊断存在的唯一意义就是点名「表不在哪个库」,而它点错了:
(两条都是本 PR 新增测试里实测抓下来的完整文案,不是手写示意。)
改动
1.
packages/objectql/src/engine.ts—— 新增resolveEffectiveDatasource(objectName)。getDriver的解析顺序抽成一份私有的resolveDatasourceBinding()(返回「名字 + 是哪一步定的」),
getDriver改为消费它并负责「名字 → driver」以及驱动缺失时的两条诊断;公开的resolveEffectiveDatasource()只取名字。顺序只存在一份,不是两份 —— 这正是 #4462抽出
resolveMappedDatasource的理由,本 PR 在同一个文件里照着同一个形状写。getDriver行为逐条不变:优先级不变;声明 / 映射到的 datasource 没有活驱动时仍然拒绝回落默认库(#4462),两条文案一字未改;第 3/4/5 步仍然只在驱动已注册时才成立;两行
debug 日志留在「它描述的那个决定」上,但改由
getDriver发出,好让解析器保持无副作用—— 探针问一次名字不应该在日志里留下一条从未发生的路由事件。
什么都没绑定、纯粹骑着部署默认驱动的对象,访问器答
undefined,这是刻意的:默认驱动保留自己的自然名(#3826,
drivers.has('default')按构造为 false),那是驱动名,不是谁把这个对象绑上去的 datasource;而
undefined也正是消费方早就写在文档里的口径(
analytics-service.ts: "bound to, orundefinedwhen it rides the default one")。需要默认驱动名字的调用方仍然有
getDefaultDriverName()。顺带把「探针答案取决于对象有没有被 Zod parse 过」这个隐患也消掉了:parse 过的对象带
'default',没 parse 的带undefined,现在两者都归一到
undefined。2.
packages/services/service-analytics/src/plugin.ts—— 探针改问引擎。 只此一行。路由规则没有在 analytics 侧重算 —— 那正是本单存在的意义。
3. 注释订正(dataset-compiler.ts / analytics-service.ts)。 #5115 的闸门注释原文写着
「判据只认显式的
object.datasource……那些规则在这里看不见」,输入换了之后这句话就不成立了。判据一字未改,注释改成如实描述新的输入,并写清楚还是看不见什么。
测试
packages/objectql/src/engine-effective-datasource.test.ts(新增,12 例):第 2/3/4 步各一例(mapping 规则 / 生命周期分流 / package 默认值)—— 都是探针以前答错的;显式声明这一
例保持不变;每一例都同时断言
getDriverForObject挑的就是同名驱动(一份顺序,两种形状的答案);「什么都没绑定」答
undefined;未注册对象答undefined;Zod parse 过的对象声明值是
'default'而访问器答'telemetry';绑定的 datasource 没有驱动时报出名字而不抛(
getDriver该抛还是抛)。packages/services/service-analytics/src/__tests__/effective-datasource-probe.test.ts(新增,6 例):走真实的 plugin 接线,engine double 把「声明了什么」和「引擎路由到哪」
分开作答 —— 缺陷就活在这个混同里,任何让 double 直接声明自己路由的测试都看不见它。含 (a)
的钉子、显式绑定的不变性、以及引擎不实现该访问器时退回 pre-analytics 的 getObjectDatasource 报告的是「声明值」而非「有效 datasource」:#5033 的诊断会点错库,#5115 的编译期闸门看不见隐式路由的对象 #5288 文案的分级。
raw-sql-object-routing.test.ts的 engine double 只实现了getObject(),改动后它答不上新问题 —— 按「补声明」处置(它的
routingmap 本来就是有效路由,execute就是按它选库的),而不是改断言。这一条是 CI 之前本地就跑红并修好的。
反向验证(先写预测,再跑)
把探针从访问器上摘掉(恢复成读声明值),预测:analytics 那 6 例里红 4 绿 2 ——
两条「点名 telemetry」的钉子红;「引擎答不上时退回默认文案」那条也红,但方向是反的
(旧探针答
'default'而新探针答undefined,所以它红在文案变成了datasource "default",不是红在没退回);编译期拒绝那条红。绿的两条:显式绑定的不变性(第 1 步本来就对),以及
「一侧只是骑默认驱动就不判」(这条是为了错的理由而绿——旧探针整个答不上)。引擎那 12 例
不动,因为摘的是探针不是访问器。
实测:
Tests 4 failed | 2 passed,红的正是预测的那 4 条,断言消息逐条对上。恢复后 6/6 绿。关于 #5115 的闸门(报告,不动手)
判据没改。变的是它的输入:现在两侧都被绑定(无论是显式、mapping、生命周期还是 package
默认值)且不同名时,编译期就能拒绝 —— 以前只有双方都显式声明才拒,这类 join 照样执行
不了,只是晚一点炸。而催生 #5115 的那个场景(被绑定的对象 join 一个只是骑默认驱动的对象)
仍然看不见:后者答
undefined,而未作答的一侧永远不拒。要覆盖它,闸门得把「骑部署默认」当成一个答案,那是 #5115 后续单的事,本 PR 只把边界钉成了测试。
停止条件(18:29Z 分诊 re-anchor)未触发:访问器是
getDriver既有解析步骤之上的薄公开包装,只算名字、不取 driver、无缓存、无新语义、顺序未变;
packages/spec未触碰。engine.ts冲突面复核:#5272 已关闭;当前 5 个 open PR 无一触及packages/objectql/src/engine.ts;main 上该文件最近一次提交是 4fedb11,已在本分支基线内。
🤖 Generated with Claude Code
Generated by Claude Code