返回正文目录

问题摘要与归属

公开文档第五节推荐的 dsl({model: prevPlan, ...}) 派生写法,在 Launcher 0.1.39 的 Runtime 受限 Compose 中触发 Java ClassCastException。基础计划 BaseModelPlan 与 JOIN 计划 JoinPlan 均复现;相同投影改用 prevPlan.query({...}) 可以通过验证、预览并执行。

归属:Java Runtime Compose 的 dsl 宿主桥接与公开文档合同一致性,不是数据库 SQL 执行错误。校验错误还应明确输入类型 / 参数位置,相关诊断方向见 #160。

环境与版本核验

capabilities 实际响应节选:

{
  "engine": "java",
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "schemaVersion": "2026-06-06",
  "securityMode": "none-dev-test-only",
  "capabilities": {
    "compose.validate": "supported",
    "compose.preview": "supported",
    "compose.execute": "supported",
    "models.describe": "supported"
  }
}

模型前提(已实际 describe 核对)

ChinookInvoiceQueryModel 对应 Chinook 的 Invoice 表,必要公开字段如下:

字段 实际物理映射 / 角色
customerId Invoice.CustomerId,客户键
invoiceDate Invoice.InvoiceDate,账单日期
invoiceAmount Invoice.Total,MONEY、sum;沿用样例原单位

下面只引用已公开字段和前一计划的输出别名。年度基线为 47 位有账单客户、83 张账单、477.53;原始数据与模型在复现前后均未修改。

文档依据

已远端读取核对 DSL 手册第五节,该节标题为“派生查询:dsl({ model: prevPlan, ... })”,并明确 model 可传上一个 QueryPlan。本轮读取文件的 Git blob SHA 为 60ec0347b097cd2887cbcd3c9008c270b700054a。

复现步骤与最小实际请求

  1. 启动上述 0.1.39,加载并 describe 账单 QM。
  2. 保存如下完整脚本为 02-dsl-plan-model.fsscript。它只把上一计划 bills 作为 dsl 的 model,没有 calculatedFields,也没有 JOIN:
const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],
    groupBy: ['customerId']
});
return {plans: dsl({
    model: bills,
    columns: ['customer_id', 'bill_amount'],
    orderBy: ['customer_id'], limit: 100
})};
  1. 校验:
foggy-runtime --output json --base-url http://127.0.0.1:18067 --namespace chinook-composition compose validate --script ./02-dsl-plan-model.fsscript

预期

按公开文档将 bills 视作计划输入,对其已输出的 customer_id、bill_amount 做投影,校验应通过。

如果标准 Runtime 的受限桥接有意不支持该公开形态,至少应在输入校验中返回明确的 model 类型 / 能力限制及路径,并给出 bills.query({...}) 替代写法,同时对齐文档的宿主适用范围;不应暴露内部 Java 强制类型转换异常。

实际响应

CLI exitCode 2,success=false,error.code=COMPOSE_SCRIPT_INVALID、phase=compose.validate、safeToAutoRepair=false。完整实际响应:

{
  "diagnostics": {
    "attributes": {
      "datasourceType": "sqlite",
      "defaultDialect": "sqlite",
      "dialectSource": "namespace-datasource-type",
      "namespace": "chinook-composition",
      "namespaceDatasourceId": "chinook-composition-sqlite",
      "namespaceDatasourceStatus": "active",
      "resolvedDialect": "sqlite"
    },
    "warnings": []
  },
  "engine": "java",
  "error": {
    "code": "COMPOSE_SCRIPT_INVALID",
    "message": "class com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')",
    "phase": "compose.validate",
    "safeToAutoRepair": false,
    "suggestedNextAction": "Inspect diagnostics and runtime logs, then retry."
  },
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "success": false
}

失败发生在脚本宿主构造计划时,尚未执行数据库查询;没有调用 execute。

关键服务端日志

以下为本次复现的 runtime.out.log 增量节选:

java.lang.ClassCastException: class com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.BaseModelPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')

	at com.foggyframework.dataset.model.engine.compose.runtime.ScriptRuntime.lambda$doRunScript$0(ScriptRuntime.java:276)

java.lang.ClassCastException: class com.foggyframework.dataset.model.engine.compose.plan.JoinPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.JoinPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')

	at com.foggyframework.dataset.model.engine.compose.runtime.ScriptRuntime.lambda$doRunScript$0(ScriptRuntime.java:276)

正常对照:只改为计划对象的 query 方法

实际完整请求:

const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01']}],
    groupBy: ['customerId']
});
return {plans: bills.query({
    columns: ['customer_id', 'bill_amount'],
    orderBy: ['customer_id'], limit: 100
})};

这个请求 validate、preview、execute 退出码均为 0,实际得到 47 位客户 / 477.53;没有更换模型、权限、数据源或日期条件。

JOIN 计划也复现(不带 calculatedFields)

为避免与另一单计算字段问题混淆,本轮另用纯列内表达式的 JOIN 请求复测 model: joined。完整实际请求与响应:

JOIN 计划请求及响应
const bills = dsl({
    model: 'ChinookInvoiceQueryModel',
    columns: ['customerId as bill_customer_id', 'sum(invoiceAmount) as bill_amount'],
    slice: [{ field: 'invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],
    groupBy: ['customerId']
});

const lines = dsl({
    model: 'ChinookInvoiceLineQueryModel',
    columns: ['invoice$customerId as line_customer_id', 'sum(lineAmount) as line_amount'],
    slice: [{ field: 'invoice$invoiceDate', op: '[)', value: ['2012-01-01', '2013-01-01'] }],
    groupBy: ['invoice$customerId']
});

const joined = bills.join(lines, 'left', [
    { left: 'bill_customer_id', op: '=', right: 'line_customer_id' }
]);

const compared = dsl({
    model: joined,
    columns: ['bill_customer_id', 'line_customer_id', 'bill_amount', 'line_amount',
        'ROUND(bill_amount - line_amount, 2) as amount_difference'],
    orderBy: ['bill_customer_id'], limit: 100
});

return { plans: compared };
{
  "diagnostics": {
    "attributes": {
      "datasourceType": "sqlite",
      "defaultDialect": "sqlite",
      "dialectSource": "namespace-datasource-type",
      "namespace": "chinook-composition",
      "namespaceDatasourceId": "chinook-composition-sqlite",
      "namespaceDatasourceStatus": "active",
      "resolvedDialect": "sqlite"
    },
    "warnings": []
  },
  "engine": "java",
  "error": {
    "code": "COMPOSE_SCRIPT_INVALID",
    "message": "class com.foggyframework.dataset.model.engine.compose.plan.JoinPlan cannot be cast to class java.lang.String (com.foggyframework.dataset.model.engine.compose.plan.JoinPlan is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @7106e68e; java.lang.String is in module java.base of loader 'bootstrap')",
    "phase": "compose.validate",
    "safeToAutoRepair": false,
    "suggestedNextAction": "Inspect diagnostics and runtime logs, then retry."
  },
  "runtimeApiVersion": "foggy-runtime-api/v1",
  "success": false
}

建议验收

上述失败请求均止于 validate。当前教学主线使用已通过的 plan.query(...),没有用私有 API 或手写 SQL 绕过语义治理。