@rex0220/kintone-sql-tools 3.71.0 → 3.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -269,6 +269,8 @@ npm パッケージは 2 つのサブパスを **semver 対象の公開 API**
269
269
  | `@rex0220/kintone-sql-tools/engine` | **read-only** のクエリ実行(ダッシュボード等)。書込 API は構造的に遮断 | `runQuery` / `runBatch` / `explainQuery` / `createReadonlyKintoneClient` / `KsqlEngineError` / `version` |
270
270
  | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript`(`asOf`/`timezone` 注入可) / `createExecutionContext`(`onChunkWritten` 書込チャンク通知) / `executeStatement` / `previewStatement`(dry-run 差分プレビュー・書込 0 回) / `disposeExecutionContext` / `createKintoneClient` / `isDmlResult`(`FlowDmlResult` 型ガード) / `version` |
271
271
 
272
+ バッチ実行ランナー **kSQL Flow**(`/flow` API を使った公式ランナー・別リポジトリ): https://github.com/rex0220/ksql-flow
273
+
272
274
  `/flow` の典型的な使い方(1 文ずつ実行して結果で継続判断する):
273
275
 
274
276
  ```ts
@@ -289,11 +291,12 @@ try {
289
291
 
290
292
  ### エンジンバージョン × dialect 対応表
291
293
 
292
- | エンジン | dialect 0(既定・宣言なし) | dialect 1(`-- @ksql dialect: 1`) |
293
- |---|---|---|
294
- | 〜 v3.67.0 | ✅ | —(未実装) |
295
- | v3.68.0 | ✅ | 解析のみ(エンジン内部 API。実行できる出荷面なし・実験的) |
296
- | v3.69.0 〜 | ✅ | ✅ CLI / MCP / プラグイン / `/flow` で実行可 |
294
+ | エンジン | dialect 0(既定・宣言なし) | dialect 1(`-- @ksql dialect: 1`) | ksql-flow |
295
+ |---|---|---|---|
296
+ | 〜 v3.67.0 | ✅ | —(未実装) | — |
297
+ | v3.68.0 | ✅ | 解析のみ(エンジン内部 API。実行できる出荷面なし・実験的) | — |
298
+ | v3.69.0 〜 v3.70.0 | ✅ | ✅ CLI / MCP / プラグイン / `/flow` で実行可 | — |
299
+ | v3.71.0 〜 | ✅ | ✅ 同上 + `previewStatement` | **0.1.0**(要求: `^3.71.0`) |
297
300
 
298
301
  dialect は後方互換で管理します: dialect 0 のスクリプトはどのエンジン版でも挙動不変・破壊的変更は dialect 番号の繰り上げでのみ導入します。変更履歴は [CHANGELOG.md](CHANGELOG.md) を参照してください。
299
302
 
package/dist-cli/ksql.js CHANGED
@@ -4161,7 +4161,7 @@ var Parser = class {
4161
4161
  const values = [];
4162
4162
  do {
4163
4163
  this.expect("(" /* LPAREN */);
4164
- const row = this.parseInsertRow(fields.length);
4164
+ const row = this.parseInsertRow(fields.length, true);
4165
4165
  this.expect(")" /* RPAREN */);
4166
4166
  values.push(row);
4167
4167
  } while (this.consume("," /* COMMA */));
@@ -4222,7 +4222,7 @@ var Parser = class {
4222
4222
  const values = [];
4223
4223
  do {
4224
4224
  this.expect("(" /* LPAREN */);
4225
- values.push(this.parseInsertRow(fields.length));
4225
+ values.push(this.parseInsertRow(fields.length, false));
4226
4226
  this.expect(")" /* RPAREN */);
4227
4227
  } while (this.consume("," /* COMMA */));
4228
4228
  const keyFields = this.parseOnDuplicate();
@@ -4546,7 +4546,7 @@ var Parser = class {
4546
4546
  this.expect("]" /* RBRACKET */);
4547
4547
  return { type: "ARRAY", elements };
4548
4548
  }
4549
- parseInsertRow(expectedLen) {
4549
+ parseInsertRow(expectedLen, allowAsOf) {
4550
4550
  const row = [];
4551
4551
  do {
4552
4552
  if (this.peek().kind === "[" /* LBRACKET */) {
@@ -4567,8 +4567,11 @@ var Parser = class {
4567
4567
  row.push({ type: "STRING", value: tok.value });
4568
4568
  } else if (tok.kind === "NUMBER" /* NUMBER */) {
4569
4569
  row.push(makeNumberLiteral(tok.value));
4570
+ } else if (allowAsOf && this.isDialect1AsOfCall(tok)) {
4571
+ row.push(this.finishVariableReference(tok));
4570
4572
  } else {
4571
- throw new ParseError("INSERT \u306E\u5024\u306B\u306F\u6587\u5B57\u5217\u30FB\u6570\u5024\u30FB\u914D\u5217\u30EA\u30C6\u30E9\u30EB\u30FBCASE WHEN \u304C\u5FC5\u8981\u3067\u3059", tok);
4573
+ const hint = this.isDialect1AsOfCall(tok) ? " INSERT ... VALUES \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u3053\u306E\u6587\u578B\u3067\u306F INSERT ... SELECT \u3067\u6CE8\u5165\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "";
4574
+ throw new ParseError(`INSERT \u306E\u5024\u306B\u306F\u6587\u5B57\u5217\u30FB\u6570\u5024\u30FB\u914D\u5217\u30EA\u30C6\u30E9\u30EB\u30FBCASE WHEN \u304C\u5FC5\u8981\u3067\u3059${hint}`, tok);
4572
4575
  }
4573
4576
  }
4574
4577
  } while (this.consume("," /* COMMA */));
@@ -4580,6 +4583,9 @@ var Parser = class {
4580
4583
  }
4581
4584
  return row;
4582
4585
  }
4586
+ isDialect1AsOfCall(tok) {
4587
+ return this.capabilities.dialect1 === true && tok.kind === "VARIABLE" /* VARIABLE */ && isAsOfFunctionName(tok.value.slice(1).toUpperCase()) && this.peek().kind === "(" /* LPAREN */ && this.peekAt(1).kind === ")" /* RPAREN */;
4588
+ }
4583
4589
  // ----------------------------------------------------------
4584
4590
  // UPDATE
4585
4591
  // ----------------------------------------------------------
@@ -4742,7 +4748,7 @@ var Parser = class {
4742
4748
  const values = [];
4743
4749
  do {
4744
4750
  this.expect("(" /* LPAREN */, "APPEND VALUES \u306E\u5404\u884C\u306F ( \u3067\u59CB\u3081\u3066\u304F\u3060\u3055\u3044");
4745
- values.push(this.parseInsertRow(fields.length));
4751
+ values.push(this.parseInsertRow(fields.length, false));
4746
4752
  this.expect(")" /* RPAREN */);
4747
4753
  } while (this.consume("," /* COMMA */));
4748
4754
  return { kind: "APPEND", fields, values };
@@ -19266,18 +19272,12 @@ async function executeBatchStatement(context) {
19266
19272
  cacheContext,
19267
19273
  tempTables
19268
19274
  );
19269
- const first = resolvedStmt2.expr.query.columns[0];
19270
- let numeric = first?.type === "ARITH_COL" || first?.type === "ARITH_AGG_COL" || first?.type === "WINDOW_COL" && (first.windowKind === void 0 || first.windowKind === "RANKING" || first.windowKind === "AGGREGATE" && (first.aggFunc === "COUNT" || first.aggFunc === "SUM" || first.aggFunc === "AVG")) || first?.type === "AGGREGATE" && (first.func === "COUNT" || first.func === "SUM" || first.func === "AVG" || first.func === "STDDEV_POP" || first.func === "STDDEV_SAMP" || first.func === "VAR_POP" || first.func === "VAR_SAMP" || first.func === "MEDIAN");
19271
- if (first?.type === "AGGREGATE" && first.func === "MODE" || first?.type === "WINDOW_COL" && first.windowKind === "VALUE" || first?.type === "WINDOW_COL" && first.windowKind === "AGGREGATE" && (first.aggFunc === "MIN" || first.aggFunc === "MAX")) {
19272
- const meta = (await inferSelectColumnMeta(
19273
- resolvedStmt2.expr.query,
19274
- ["__scalar__"],
19275
- client,
19276
- cacheContext,
19277
- tempTables
19278
- )).get("__scalar__");
19279
- numeric = meta?.semantics?.compareMode === "number" || meta?.sortKind === "number";
19280
- }
19275
+ const numeric = await scalarSubqueryHasNumericResult(
19276
+ resolvedStmt2.expr.query,
19277
+ client,
19278
+ cacheContext,
19279
+ tempTables
19280
+ );
19281
19281
  const numberValue = numeric ? Number(value) : Number.NaN;
19282
19282
  variables.set(stmt.name, numeric && Number.isFinite(numberValue) ? { type: "number", value: numberValue, raw: value } : { type: "string", value });
19283
19283
  } catch (e) {
@@ -19762,7 +19762,6 @@ async function executeExit(stmt, client, options, cacheContext, tempTables) {
19762
19762
  }
19763
19763
  async function evaluateAssertCondition(stmt, client, options, cacheContext, tempTables) {
19764
19764
  const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
19765
- const semantics = stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string");
19766
19765
  if (stmt.op === "BETWEEN") {
19767
19766
  if (stmt.low === null || stmt.high === null) {
19768
19767
  throw new Error("ArgumentError: malformed ASSERT statement.");
@@ -19770,35 +19769,49 @@ async function evaluateAssertCondition(stmt, client, options, cacheContext, temp
19770
19769
  const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
19771
19770
  const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
19772
19771
  return {
19773
- passed: compareScalarValues(">=", left, low, semantics) && compareScalarValues("<=", left, high, semantics),
19774
- actual: left
19772
+ passed: compareScalarValues(">=", left.value, low.value, assertComparisonSemantics(left, low)) && compareScalarValues("<=", left.value, high.value, assertComparisonSemantics(left, high)),
19773
+ actual: left.value
19775
19774
  };
19776
19775
  }
19777
19776
  if (stmt.right === null) {
19778
19777
  throw new Error("ArgumentError: malformed ASSERT statement.");
19779
19778
  }
19780
19779
  const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
19781
- return { passed: compareScalarValues(stmt.op, left, right, semantics), actual: left };
19780
+ const semantics = stmt.op === "=" || stmt.op === "!=" || stmt.op === "<>" ? stmt.left.type === "NUMBER" || stmt.left.type === "ARITH" ? syntheticSemantics("number") : syntheticSemantics("string") : assertComparisonSemantics(left, right);
19781
+ return {
19782
+ passed: compareScalarValues(stmt.op, left.value, right.value, semantics),
19783
+ actual: left.value
19784
+ };
19785
+ }
19786
+ function assertComparisonSemantics(left, right) {
19787
+ return syntheticSemantics(left.semantics === "number" && right.semantics === "number" ? "number" : "string");
19782
19788
  }
19783
19789
  async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
19784
19790
  switch (operand.type) {
19785
19791
  case "VARIABLE":
19786
19792
  throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
19787
19793
  case "NUMBER":
19788
- return numberLiteralText(operand);
19794
+ return { value: numberLiteralText(operand), semantics: "number" };
19789
19795
  case "STRING":
19790
- return operand.value;
19796
+ return { value: operand.value, semantics: "string" };
19791
19797
  case "ARITH":
19792
- return String(evalAssertArith(operand));
19798
+ return { value: String(evalAssertArith(operand)), semantics: "number" };
19793
19799
  case "SCALAR_SUBQUERY": {
19794
19800
  try {
19795
- return await evaluateScalarSubquery(
19801
+ const value = await evaluateScalarSubquery(
19796
19802
  operand.query,
19797
19803
  client,
19798
19804
  options,
19799
19805
  cacheContext,
19800
19806
  tempTables
19801
19807
  );
19808
+ const numeric = await scalarSubqueryHasNumericResult(
19809
+ operand.query,
19810
+ client,
19811
+ cacheContext,
19812
+ tempTables
19813
+ );
19814
+ return { value, semantics: numeric ? "number" : "string" };
19802
19815
  } catch (e) {
19803
19816
  if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
19804
19817
  throw e;
@@ -19806,6 +19819,21 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
19806
19819
  }
19807
19820
  }
19808
19821
  }
19822
+ async function scalarSubqueryHasNumericResult(query, client, cacheContext, tempTables) {
19823
+ const first = query.columns[0];
19824
+ let numeric = first?.type === "ARITH_COL" || first?.type === "ARITH_AGG_COL" || first?.type === "WINDOW_COL" && (first.windowKind === void 0 || first.windowKind === "RANKING" || first.windowKind === "AGGREGATE" && (first.aggFunc === "COUNT" || first.aggFunc === "SUM" || first.aggFunc === "AVG")) || first?.type === "AGGREGATE" && (first.func === "COUNT" || first.func === "SUM" || first.func === "AVG" || first.func === "STDDEV_POP" || first.func === "STDDEV_SAMP" || first.func === "VAR_POP" || first.func === "VAR_SAMP" || first.func === "MEDIAN");
19825
+ if (first?.type === "AGGREGATE" && first.func === "MODE" || first?.type === "WINDOW_COL" && first.windowKind === "VALUE" || first?.type === "WINDOW_COL" && first.windowKind === "AGGREGATE" && (first.aggFunc === "MIN" || first.aggFunc === "MAX")) {
19826
+ const meta = (await inferSelectColumnMeta(
19827
+ query,
19828
+ ["__scalar__"],
19829
+ client,
19830
+ cacheContext,
19831
+ tempTables
19832
+ )).get("__scalar__");
19833
+ numeric = meta?.semantics?.compareMode === "number" || meta?.sortKind === "number";
19834
+ }
19835
+ return numeric;
19836
+ }
19809
19837
  async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
19810
19838
  const { query, probed } = withScalarProbeLimit(sourceQuery);
19811
19839
  const result = await runSubquery(query, client, options, cacheContext, tempTables);
@@ -25905,6 +25933,9 @@ function buildSubtableReorderPutParams(appId, parentId, revision, subtableCode,
25905
25933
  };
25906
25934
  }
25907
25935
  function valueToString(value) {
25936
+ if (value.type === "VARIABLE") {
25937
+ throw new Error(`InternalError: unresolved variable @${value.name} reached INSERT evaluation.`);
25938
+ }
25908
25939
  if (value.type === "STRING") return value.value;
25909
25940
  if (value.type === "NUMBER") return numberLiteralText(value);
25910
25941
  if (value.type === "CASE_VALUE") return evalCaseWhen(value.expr, {});