@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 +8 -5
- package/dist-cli/ksql.js +56 -25
- package/dist-engine/index.cjs +13 -13
- package/dist-engine/index.mjs +13 -13
- package/dist-engine/ksql-engine.umd.js +13 -13
- package/dist-engine/meta/bundle-baseline.json +7 -7
- package/dist-engine/meta/cjs.json +7 -7
- package/dist-engine/meta/esm.json +7 -7
- package/dist-engine/meta/umd.json +7 -7
- package/dist-flow/index.cjs +10 -10
- package/dist-flow/index.mjs +10 -10
- package/dist-flow/meta/cjs.json +7 -7
- package/dist-flow/meta/esm.json +7 -7
- package/dist-flow/types/ast.d.ts +6 -4
- package/dist-mcp/ksql-mcp.js +58 -27
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19274
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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, {});
|