返回正文目录

Compose Query · 链式 API 手册(Manual B)

状态:8.x 同步版 · 已同步派生 / Join / Union / CTE 复用;链式入口长期策略仍以 Manual A 为准 风格定位:以 Query.from(...) 为入口的 fluent / chained API;面向 SDK 调用方、需要 IDE 补全和静态分析的开发者 镜像手册:DSL 配置式手册(Manual A) 缺口跟踪:docs/8.3.0.beta/compose-query-manuals-gap-tracker.md 优先级:本手册晚于 Manual A 落稿(用户决策:"先对齐 DSL,再考虑链式 API");新能力优先以 DSL 手册作为规范入口

::: tip 关于"🚧 待补"标记 本手册采用骨架先行策略,章节标题已固定,能力随 spec 补齐分批落稿。看到 🚧 表示对应章节有未关闭的 gap,可在 docs/8.3.0.beta/compose-query-manuals-gap-tracker.md 查看上下文与目标版本。 :::

::: info 决策契约

::: warning 关于本手册的存续性 架构验证已完成(2026-04-26):DSL 配置式(Manual A)与链式 API 在 IR 层完全独立,未来如整体移除 Query.from(...) 入口,本手册可安全 deprecate,不影响 DSL 任何能力。

具体而言:

完整结论与证据见 docs/8.3.0.beta/compose-query-manuals-gap-tracker.md 的 G7 closure note;移除级别选择见同文件 G8。 :::


1. 入门:Query.from(...) 链式查询

🚧 待补:等 Manual A §1 落稿后镜像

预览(来源:docs/8.2.0.beta/P0-ComposeQuery-CTE使用参考手册.md):

const salesBase = Query.from("FactSalesQueryModel");
const sales = salesBase
  .where([{ field: "salesDate$id", op: ">=", value: "2025-01-01" }])
  .groupBy(salesBase.product$id)
  .select(
    salesBase.product$id,
    salesBase.product$caption,
    salesBase.salesAmount.sum().as("totalSales")
  );

要覆盖的小节:


2. 列与维度引用约定(Proxy 属性)

🚧 待补:等 Manual A §2 落稿后镜像

要覆盖的:


3. 过滤 / 分组 / 排序 / 分页

🚧 待补

要覆盖的:


4. 计算字段

🚧 待补:参考 docs/8.3.0.beta/compose-query-manuals-gap-tracker.md 的 G6。

要覆盖的:


5. 派生查询:prev.where(...).select(...)

链式 API 中,任何已构造出的 plan 都可以继续 .where() / .select() / .orderBy(),形成派生查询。派生层只能引用上一层已投影出的列。

const base = Query.from("OdooSaleOrderModel").groupBy(...).select(...);
const filtered = base
  .where([{ field: "totalSales", op: ">", value: 50000 }])
  .select(base.teamId$caption, base.totalSales);

约束与 Manual A §5 一致:


6. Join:a.join(b, type, on)

plan-level Join 入口与 DSL 返回的 plan 对象一致:

const joined = premiumCustomers.join(pendingOrders, "inner", [
  { left: "id", op: "=", right: "partnerId" }
]);

const finalPlan = joined.select(
  premiumCustomers.name.as("customer_name"),
  pendingOrders.name.as("order_number"),
  pendingOrders.amountTotal.as("order_amount")
);

约束:


7. Union:a.union(b)

const allOrders = onlineOrders.union(offlineOrders, { all: true });
const allChannels = onlineOrders.union([offlineOrders, partnerOrders], { all: true });

约束:


8. CTE 复用与命名 plans

CTE 复用是编译器内部能力。链式 API 中同一个 plan 被多个下游 plan 引用时,引擎会自动识别公共子计划,在支持 CTE 的方言上编译为 WITH ... AS (...),否则退回派生子查询。

公开边界:


9. 时间窗口语义层(高层快捷方式)

链式入口目前不作为 timeWindow 的首选公开写法。需要稳定的 AI 生成和 MCP 调用时,请使用 Manual A 的 DSL 形态:

dsl({
  model: "FactSalesQueryModel",
  columns: ["salesDate$id", "salesAmount"],
  groupBy: ["salesDate$id"],
  timeWindow: {
    field: "salesDate$id",
    grain: "month",
    comparison: "yoy",
    value: ["2025-01-01", "2026-01-01"],
    targetMetrics: ["salesAmount"]
  }
});

如 SDK 后续保留链式 helper,语义必须与 DSL 完全一致,包含:

候选 helper 仅供 SDK 设计参考:

const yoy = base.timeWindow({
  field: "salesDate$id",
  grain: "month",
  comparison: "yoy",
  value: ["2025-01-01", "2026-01-01"],
  targetMetrics: ["salesAmount"]
});

10. 时间窗口原语层(底层窗口函数)

🚧 待补:参考 docs/8.3.0.beta/compose-query-manuals-gap-tracker.md 的 G3。

形态候选:

const withLag = base.select(
  base.salesDate$id,
  base.salesAmount,
  base.salesAmount.lag(1).over({
    partitionBy: [base.product$id],
    orderBy: [base.salesDate$id]
  }).as("salesAmountLag1")
);

底层 IR 已实现:OverClause / WindowColumn / WindowFrame(v1.5 Java)


11. 输出后缀规约

链式 API 如暴露 timeWindow helper,必须沿用 Manual A §11 的默认后缀:

后缀 含义
__prior 前期值
__diff 差值
__ratio 增长率
__ytd 年初至今累计
__mtd 月初至今累计
__rolling_{N}{unit} 滚动窗口值

12. 错误码与诊断

🚧 待补

与 Manual A §12 共享同一套错误码体系(沙箱 COMPOSE_* + 治理层)。


13. 真值 SQL 编译预览(4 方言)

🚧 待补

与 Manual A §13 共享同一份编译规则(同一份 IR)。


附录 A · 链式 API ↔ DSL 互译表

🚧 待补:两本手册都达到 §1-§11 完整后再写

镜像 Manual A 的附录 A,方向反转。


附录 B · 原 QueryPlan API 映射

🚧 待补:可选,仅在内部 IR 类型暴露给 SDK 调用方时启用

要覆盖的:


维护记录

日期 操作 备注
2026-04-26 创建骨架 初始化 13 节占位,与 Manual A 章节严格对仗
2026-05-03 同步 8.x 已验收能力 补齐派生查询 / Join / Union / CTE 复用,并明确 timeWindow 以 DSL 为稳定公开入口