Skip to content

dataset 跨 datasource JOIN 应在编译/发布期就被拒绝,而不是留到查询期才响亮失败(#5033 的后续) #5115

Description

@xuyushun441-sys

背景

#5033 修好了 service-analytics 的 executeRawSql 自动桥接 —— 它现在把 object 透传给 ObjectQL.execute(),于是 dataset 的原始 SQL 落在该对象自己的 datasource 上,而不是默认库。

该修复带来一个已被 PM 接受的行为变更:NativeSQLStrategy 为点号维度(account.region)生成的 LEFT JOIN,如果 join 两端落在不同的 datasource 上,现在会在基对象自己的 datasource 上响亮失败,而不是像以前那样静默地读错库。PR 里已经把这个失败的措辞修正为指名真实原因(表 X 不在 datasource Y 上),并留了修复建议;degradation(真正缺表 → 空结果 + WARN)对基对象缺失的情形保持不变。

这个 issue 要解决的

响亮失败发生在查询期,太晚了。 这是一个纯粹的元数据错误:dataset 声明了一个物理上不可能在一条 SQL 里执行的 join,这件事在编译/发布期就完全可判定 —— 只需要问每个参与对象绑定在哪个 datasource 上。按 Prime Directive #12(contract-first,在 producer 端修、在 authoring/publish 期响亮拒绝),它应该在作者(尤其是 AI 作者)按下保存的那一刻就被拒绝,而不是等到某个看板 widget 在生产上炸掉。

现状的成本:

  • 一个跨 datasource 的 dataset 可以被正常保存、发布、出现在看板里,只有真正查询时才失败 —— 而查询往往发生在别的环境、别的时间、别的人面前;
  • AI 生成的 dataset 元数据是这个错误最容易繁殖的地方:include 一个 lookup 看起来永远是合法的,除非有东西在 authoring 期告诉作者「这两个对象不在同一个库里」;
  • 「声明了但运行不了」正是 Prime Directive chore: version packages #10 点名的 declared ≠ enforced 形状。

建议方向

compileDataset(packages/services/service-analytics/src/dataset-compiler.ts)已经在校验 include 里的关系是否存在于基对象上,并且已经通过 relationshipResolver 拿到 join 目标对象名。再加一个可选的 datasource 解析器(#5033 已经为诊断文案在 AnalyticsServiceConfig 上引入了 getObjectDatasource(objectName),plugin 侧从 engine.getObject(name).datasource 接出),就足以在编译期判定:

需要一并想清楚的两点:

  1. 落点:只在 compileDataset 里拒绝,还是也进入 metadata 发布闸门(publish-time lint),让保存 dataset 的那一刻就红?后者才真正满足「在 authoring 期拒绝」,前者只是把失败从查询期提前到注册期。
  2. 是否要给一条合法出路:跨 datasource 的维度理论上可以由 analytics 层做两段读 + 内存 join(ObjectQL 路径已经有 FK-expand 的先例,见 objectql-crossobj-expand.test.ts)。如果决定支持,那就不是「拒绝」而是「换策略」,NativeSQLStrategy.canHandle 应当在 join 跨库时 decline(与它对 external 对象的既有姿态一致),把查询让给能跨库的路径。这条比单纯拒绝更贵,但它是唯一让跨库看板真正能工作的方案 —— 需要维护者定方向。

出处

从 #5033 的实现中分出。PM 在 #5033 的派发裁定里明确:本轮不做编译期拒绝,如分析认为需要则单独记账。这就是那笔账。

Observed while implementing #5033 on main @ c272e48be。

Activity

  1. os-zhuang commented on Aug 4, 2026

    @os-zhuang
    Contributor

    维护者拍板:取 两步走

    不在本轮把「跨库 dataset」做成能工作的功能,也不把它永久钉死为非法:

    • 第 1 步(现在做,本单):compileDataset 在编译期拒绝跨 datasource 的 join —— 纯元数据错误,在只问「每个参与对象绑定在哪个 datasource」就能判定的时刻判定,而不是等某个看板 widget 在生产上炸。解析器缺席或答不上来(无 data engine 的宿主、外部 datasource)按仓里既有的 "cannot answer, do not block" 分级放行,由 service-analytics 的 executeRawSql 自动桥接丢弃 objectName —— dataset 原始 SQL 永远打在默认 datasource 上,凡被路由到非默认 datasource 的对象一律读成 0 #5033 的查询期防线兜底。
    • 第 2 步(单独立单,不在本轮):若要让跨库看板真正能工作,那不是「拒绝」而是「换策略」—— NativeSQLStrategy.canHandle 在 join 跨库时 decline(与它对 external 对象的既有姿态一致),把查询让给两段读 + 内存 join 的路径。这条明显更贵,且第 1 步落地后它是纯增量(拒绝改为降级换策略),不需要现在决定形状。

    正文两个待定点的答复:落点先只做 compileDataset(注册期拒绝);进 metadata 发布闸门才是真正的 authoring 期拒绝,但发布闸门在 lint/spec 面,属别的车道 —— 第 1 步落地后另立单路由,不在本单混做。needs-user-decision 摘除。

    认领

    认领(PM 循环,会话 session_01Pbu27iNUfQCHeuS551Rqo7):

    • 分支:claude/issue-5115-dataset-cross-datasource-compile-gate
    • worktree:/home/user/objectstack-issue-5115
    • 域:domain:services
    • 文件面:packages/services/service-analytics/src/dataset-compiler.ts 及其测试、AnalyticsServiceConfig 的 getObjectDatasource 接线
    • ⛔ 不触碰 packages/spec/**、不触碰 packages/lint/**(发布闸门 = 第 2 步之外的独立路由)

    Generated by Claude Code

  2. self-assigned this
    on Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions