执行、结果与错误

公开执行入口:

const result = await Logicform.execute(logicform, options);

它返回 ExecuteSuccessResult | ExecuteFailureResult。公开入口会把执行错误转换为失败结果,而不是要求调用方依赖异常捕获判断业务执行是否成功。

成功结果

interface ExecuteSuccessResult {
  result: DataRow[];
  schema: ResolvedSemanticSchema;
  normedLogicform: LogicformType;
  sqls: ExecutedSql[];
  lineage: QueryLineage;
  provider: 'mysql' | 'doris' | 'starrocks' | 'postgresql'
    | 'clickhouse' | 'snowflake' | 'oracle';
}

result

最终数据行数组。每行都包含执行器拥有的 _id

  • 有 groupby 时,由所有分组输出值构成稳定行身份;
  • 明细查询可使用允许输出的 ID Property 形成身份;
  • 单聚合行固定为 '0'
  • 无可用稳定身份的明细行使用随机 UUID。

_id 不会加入 schema.properties,因为它不是业务结果列。

object/entity 输出已经被填充为实体对象;boolean、外部 Semantic Type 等结果 hook 也已执行完成。

schema

描述本次动态结果列,而不是原始输入 Schema。它包含每个输出的:

interface ResultProperty {
  name: string;
  type: string;
  primal_type: 'string' | 'number' | 'boolean' | 'date' | 'object';
  isArray: boolean;
  ref?: string;
  granularity?: TimeGranularity;
  comparable?: ComparableCategoryDefinition;
  temporal?: ResultPropertyTemporalMetadata;
}

Operator 产生的计算列也会出现在这里。周期相关结果可能包含:

interface ResultPropertyTemporalMetadata {
  kind: 'period-boundary' | 'period-shift' | 'date-bucket' | 'lifecycle';
  boundary?: 'start' | 'end';
  sourceProperty?: string;
  requestedRange?: { gte?: string; lte?: string };
  effectiveRange?: { gte?: string; lte?: string };
  resolvedAt?: string;
  shiftedBy?: { granularity: TimeGranularity; offset: number };
  resolvedAtByGroup?: Array<{
    group: Record<string, unknown>;
    date: string;
  }>;
}

normedLogicform

系统实际采用的规范化 LogicForm。它可能与输入有以下可预期差异:

  • Property ID 转换为规范引用;
  • 补充缺失的 PredItem name
  • 日期值展开为明确上下界;
  • entity_id 转换为身份 Query;
  • 关系嵌套 Query 转换为路径条件;
  • 权限 Query 与用户 Query 合并;
  • 分页默认 page: 1
  • limit: -1 被移除。

需要审计查询口径时,应保存这个字段。

sqls

interface ExecutedSql {
  type: 'computation' | 'semantic-resolution';
  sql: string;
  parameters: readonly unknown[];
  displaySql: string;
}
  • computation:计算 LogicForm 结果的 SQL;
  • semantic-resolution:解析语义输入/输出的辅助 SQL,例如最新数据日期和实体填充;
  • sql + parameters:Provider 实际执行的参数化请求;
  • displaySql:把参数按当前方言渲染后的诊断文本,永远不会被 SemanticDB 执行。

displaySql 可能包含敏感值,只能写入受控的诊断或审计渠道。命中 Query Cache 时,对应 SQL 仍会保留在 sqls 中。

lineage

interface QueryLineage {
  source: {
    schemaId: string;
    schemaName: string;
    physicalTable: string;
    provider: string;
  };
  outputs: Array<{
    name: string;
    sources: Array<{
      schemaId: string;
      propertyId: string;
      propertyName: string;
      physicalColumn: string;
    }>;
    transformations: string[];
  }>;
  filters: LineageColumnSource[];
}

它说明每个结果列和过滤条件来自哪些 Schema/Property/物理列,以及经历了哪些 Operator 或组合变换。

provider

本次执行实际使用的数据库 Provider kind。

失败结果

interface ExecuteFailureResult {
  errorCode: '100';
  error: string;
  result: [];
  schema?: ResolvedSemanticSchema;
  normedLogicform: LogicformType;
  sqls: ExecutedSql[];
  provider?: string;
  lineage?: QueryLineage;
}

已经产生的规范化结果、动态 Schema、SQL、Provider 和 Lineage 会尽量保留,便于定位失败阶段。error 当前包含错误消息和 stack,可能暴露内部路径,不应原样展示给终端用户。

判断方式:

const response = await Logicform.execute(logicform, options);

if ('errorCode' in response) {
  logger.error({
    error: response.error,
    normedLogicform: response.normedLogicform,
    sqls: response.sqls,
    lineage: response.lineage,
  });
  return;
}

render(response.result, response.schema);

SemanticDBError

扩展 Hook 和底层 API 可以抛出:

class SemanticDBError extends Error {
  code: string;
  details?: Record<string, unknown>;
}

code 是稳定的机器可读分类,details 提供字段、Schema、Mapping 或 Operator 等上下文。公开 Logicform.execute() 最终仍按上述失败结果返回;onProgressfailed 事件可以收到原始 Error 实例。

常见错误类别:

类别典型原因
版本缺少 version、版本低于 2.0、格式无效
SchemaSchema/Property 未注册、类型无效、关系目标缺少 ID
Mapping没有 Mapping 完整覆盖 Query、候选歧义、连接缺失
Query未知 Operator、值形状错误、递归配置无效
Pred/Operator输入数量错误、args 无效、输出重名、非法嵌套
规划跨 Schema 分组无法对齐、输出列冲突、Provider 无法覆盖请求
权限Schema 不在 allowlist、字段不在 whitelist、权限配置无效
Provider方言不支持、数据库执行失败、跨连接查询
取消AbortSignal 已触发

进度回调

onProgress(event) {
  console.log(event.stage, event.partialResult);
}

事件顺序:

stage新增信息
started原始 LogicForm
validated已解析的基础 Schema
normalizednormedLogicform
planned动态结果 Schema;事件本身也包含逻辑计划供高级诊断
lineagelineage
compiledsqls
executingProvider kind
hydrating开始进行语义类型结果处理和实体填充
result最终数据行
completeddurationMs
failed原始 Error

每个事件都有累计的 partialResult,因此观察者不需要自己合并前序事件。回调可返回 Promise,执行会等待它完成;回调抛错也会使本次执行失败。

取消

const controller = new AbortController();

const pending = Logicform.execute(logicform, {
  ...options,
  signal: controller.signal,
});

controller.abort();

系统会在关键阶段检查 signal,并将取消作为失败结果返回。Provider 能否立即中断正在进行的网络请求取决于具体数据库驱动。

数据权限契约

type Permissions = Record<string, {
  rowPermissions?: QueryType;
  columnPermissions?: { whitelist: string[] };
}>;
  • 传入 Permissions 后,它的 key 是可访问 Schema allowlist。
  • rowPermissions 与用户 Query 使用 AND 合并。
  • columnPermissions.whitelist 限制直接或间接引用的字段。
  • 关系查询、子查询、周期组件和实体填充都会继续应用相应 Schema 的权限。
  • 权限不能被 LogicForm 中的同名字段覆盖。

输出安全建议

  • normedLogicformlineage 用于审计与复现。
  • sqlparameters 分开保存;仅在可信环境查看 displaySql
  • 不向终端用户原样返回 stack、连接信息或物理表名。
  • result.schema 驱动展示和格式化,不根据某个 Operator 名硬编码列类型。
  • 业务调用方应显式提供输出 name,避免自动名称变化影响下游字段绑定。