Skip to content

fix(service-analytics): executeRawSql 桥接透传 objectName —— dataset 原始 SQL 打回对象自己的 datasource (#5033) - #5119

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-5033-rawsql-object-routing
Aug 4, 2026
Merged

fix(service-analytics): executeRawSql 桥接透传 objectName —— dataset 原始 SQL 打回对象自己的 datasource (#5033)#5119
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-5033-rawsql-object-routing

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #5033

结论

AnalyticsServicePluginexecuteRawSql 自动桥接收到了对象名却把它丢掉了 —— engine.execute(knexSql, { args: params })ObjectQL.execute() 的驱动选择顺序是 options.objectgetDriver(object),其次 options.datasource,最后默认驱动;第 1 条永远走不到,于是每一次 dataset 原始 SQL 读都打在默认 datasource 上。凡被路由到别处的对象(ADR-0057 §3.6 telemetry 拆分、显式 object.datasourcedatasourceMapping 规则)都读到 no such table,再被 widget 层的优雅降级换成一个自信的 0 —— 而底下是真实存在的行。

桥接现在传 { args: params, object: objectName },与同文件里一直路由正确的 executeAggregate(engine.aggregate(objectName, …))同姿态。两条 dataset 执行路径从此对「这个对象在哪个库里」给同一个答案,选中哪条策略不再决定你读到的是数据还是零。

顺带把 DataEngineLike.execute 的 options 从 Record< string, unknown > 展开成具名的 { args?, object? }:这个键是承重的,应该在调用点就看得见,而不是藏在一个什么都收的索引签名里。

packages/spec/** 与生成物零改动 —— engine.execute 的契约本来就声明了 object 优先级(见 packages/objectql/src/engine.tsexecute() 注释),本单只是把丢掉路由键的那个消费者修回声明,不需要动契约。packages/objectql/** 只读未改;metadata-protocol/src/protocol.ts 未触碰;content/docs/releases/ 未触碰。

已接受的行为变更:跨 datasource 的 dataset JOIN 现在响亮失败

NativeSQLStrategy 为点号维度(account.region)生成 LEFT JOIN。语句现在跑在基对象自己的 datasource 上,join 目标要是不在那个库里,就会在那里失败 —— 而不再像以前那样静默读错库。这是对的(fail-loud),但它必须以自己的身份失败:如果继续报「backing object … is unavailable」并返回空结果,那就是换了个原因让同一个自信的 0 活下来,而且基表明明就在那儿。

所以缺表分诊现在问的是「驱动指名的是哪张表」:

驱动指名的关系 处理
dataset 自己的 object 降级成空结果 + WARN(不变,这正是降级存在的理由)
某张 join 表,但该 object 在本 kernel 根本没注册 降级(真实缺席,形状不变)
某张 join 表,且该 object 已注册 抛错,指名表 X、基对象所在的 datasource、X 实际所在的 datasource,以及修法

第三行的实际文案:

[Analytics] dataset "audit_by_actor" cannot be executed as one statement:
table "account" is not on datasource "telemetry", which is where its base object
"sys_audit_log" lives — "account" is registered on the default datasource.
A dataset JOIN cannot cross datasources. Fix it by binding both objects to the
same datasource, or by dropping the cross-datasource relationship from the
dataset's `include`/dimensions.

AnalyticsServiceConfig 为此新增一个仅用于诊断的可选钩子 getObjectDatasource(objectName),由 plugin.ts 从 engine 的 schema registry 接出。它不参与任何驱动选择。

派发裁定里的方向 3(widget 在底层表不可用时渲染 "not available" 而非 0)不在本单内 —— 那是跨 objectui 的 UI/契约语义问题。本单只保证:服务端对真正缺表的降级日志继续存在,而被误路由的那批场景修完后根本不会再走到降级路径。

测试

新增 packages/services/service-analytics/src/__tests__/raw-sql-object-routing.test.ts,沿用 execution-context-bridge.test.ts 立下的 fakePluginContext 惯例,驱动真实的 plugin 装配,对着一个「按 execute() 文档的顺序解析驱动」的 engine double 跑 —— 这个缺陷对每一个直接 stub executeRawSql 的测试都是不可见的,这也是它活到现在的原因。

9 个用例:

  • 桥接把 object 交给 engine.execute(驱动选择的第一把钥匙);
  • issue 的原样:同一个 telemetry 路由对象,原始 SQL 路径与对象路由的 aggregate 路径返回同一批行;
  • 该场景不再触达降级路径(有行、无 is unavailable WARN);
  • 默认 datasource 的对象原地不动(指名对象后仍解析到同一个默认驱动);
  • 跨 datasource JOIN:拒绝而非降级成空结果;
  • 错误文案指名表 X + datasource Y + 修法,且不是误导性的旧形状;
  • 拓扑错误不打 widget 降级 WARN;
  • dataset 自己的 object 缺表 → 空结果 + WARN(降级存活);
  • join 目标 object 在本 kernel 未注册 → 降级而非抛错。

本地闸门

pnpm --filter @objectstack/service-analytics test
  Test Files  40 passed (40)
       Tests  529 passed (529)

vitest run src/__tests__/raw-sql-object-routing.test.ts --reporter=verbose
  Test Files  1 passed (1)
       Tests  9 passed (9)

tsc --noEmit -p packages/services/service-analytics/tsconfig.json
  8 errors, 全部在本 PR 未触碰的文件里
  (analytics-service.test.ts / measure-source-field-gate.test.ts /
   objectql-timedimension-projection.test.ts) —— 本 PR 新增 0 个

eslint --no-inline-config <三个改动文件>   → clean

23 个仓级 check 脚本:22 绿。唯一红的 check:i18n 与本 diff 无关 ——
它经 CLI 跑 extract,而本 worktree 没有 packages/cli/dist,报的是
"command i18n:extract:… not found"。已用 git stash 把工作树退回
origin/main 复验:同样 EXIT=1。CI 全新 checkout + build,不受影响。

@objectstack/service-analytics 的 patch 级 changeset 已随本 PR 提交(.changeset/analytics-raw-sql-object-routing.md),内含上面那条行为变更的说明与迁移指引。

范围外发现

🤖 Generated with Claude Code

https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX


Generated by Claude Code

… the default datasource (#5033)

`AnalyticsServicePlugin`'s `executeRawSql` auto-bridge received the object name
and dropped it: `engine.execute(knexSql, { args: params })`. `ObjectQL.execute()`
selects its driver in the order `options.object` -> `getDriver(object)`, then
`options.datasource`, then the default driver, so rule 1 could never fire and
every dataset raw-SQL read landed on the DEFAULT datasource. Any object routed
elsewhere (ADR-0057 3.6 telemetry split, an explicit `object.datasource`, a
`datasourceMapping` rule) raised `no such table`, which the widget-level
graceful degradation turned into a confident `0` over live rows. Measured on the
showcase: `sys_audit_log` returned 49 records object-routed and `{"rows":[]}`
through the dataset raw-SQL path, on one running kernel.

The bridge now passes `{ args: params, object: objectName }`, matching the
`executeAggregate` bridge beside it, so both dataset execution paths give one
answer to "which datasource is this object in". `DataEngineLike.execute`'s
options bag is spelled out instead of `Record<string, unknown>` so the
load-bearing key is visible at the call site.

Accepted behaviour change: a dataset whose SQL joins across datasources now runs
on the base object's own datasource and fails there rather than silently reading
the wrong database. It must fail as ITSELF -- reporting it as "backing object is
unavailable" would keep the confident `0` alive under a new cause -- so the
missing-source triage now asks WHICH relation the driver named:

  - the dataset's own object      -> degrade to an empty result + WARN (unchanged)
  - a joined table whose object   -> degrade (genuine absence, unchanged shape)
    is not registered here
  - a joined table whose object   -> throw, naming table X, the datasource its
    IS registered                    base object lives on, where X actually is,
                                     and the remedy

`AnalyticsServiceConfig` gains one optional diagnostics-only hook,
`getObjectDatasource(objectName)`, wired in `plugin.ts` from the engine's schema
registry. It never selects a driver.

Tests: `src/__tests__/raw-sql-object-routing.test.ts` drives the real plugin
wiring against an engine double that resolves its driver the way `execute()`
documents -- the defect was invisible to every test that stubbed `executeRawSql`
directly. Covers the issue's exact shape (raw SQL == object-routed rows, no
degradation WARN), default-datasource objects unchanged, the cross-datasource
loud failure and its wording, and both surviving degradation paths.

Compile-time rejection of cross-datasource dataset joins is deliberately not in
this PR; filed as #5115.

Fixes #5033

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 5:24am

Request Review

@github-actions github-actions Bot added the size/l label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics.

8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/data-api.mdx (via @objectstack/service-analytics)
  • content/docs/api/index.mdx (via @objectstack/service-analytics)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-analytics)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/service-analytics)
  • content/docs/plugins/packages.mdx (via @objectstack/service-analytics)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-analytics)
  • content/docs/releases/v17.mdx (via @objectstack/service-analytics)
  • content/docs/releases/v9.mdx (via @objectstack/service-analytics)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 05:29
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit f522e95 Aug 4, 2026
24 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-5033-rawsql-object-routing branch August 4, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants