@rex0220/kintone-sql-tools 2.3.0 → 2.4.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 +47 -46
- package/dist-cli/ksql.js +117 -25
- package/dist-mcp/ksql-mcp.js +108 -28
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,13 +16,13 @@ kintone アプリを SQL 風の構文で操作するツールセットです。
|
|
|
16
16
|
- プラグイン: read-only バッチのみ(最終結果を表示)
|
|
17
17
|
- **`ASSERT`(実行時ゲート。DML 前の件数ガード / CLI ヘルスチェック)**(v1.10.0)
|
|
18
18
|
- サブテーブル仮想テーブル(`APP100$明細`)
|
|
19
|
-
- CLI 拡張 `APP@profile`
|
|
20
|
-
- 同一 SQL 内で同一 APP の profile 混在を許可
|
|
21
|
-
- `INSERT/UPDATE/UPSERT` 対応、`DELETE` は未対応
|
|
22
|
-
- CLI / MCP の論理アプリ参照 `LAPP_<NAME>`
|
|
23
|
-
- profile ごとの `logicalApps` で、同じ SQL を異なる物理アプリ ID へ安全に解決
|
|
24
|
-
- `APP100` は常に物理 ID 100 のまま(暗黙変換なし)
|
|
25
|
-
- `allowPhysicalAppRefs: false` で、その profile の物理 `APPxxx` 直接参照を禁止可能
|
|
19
|
+
- CLI 拡張 `APP@profile`
|
|
20
|
+
- 同一 SQL 内で同一 APP の profile 混在を許可
|
|
21
|
+
- `INSERT/UPDATE/UPSERT` 対応、`DELETE` は未対応
|
|
22
|
+
- CLI / MCP の論理アプリ参照 `LAPP_<NAME>`
|
|
23
|
+
- profile ごとの `logicalApps` で、同じ SQL を異なる物理アプリ ID へ安全に解決
|
|
24
|
+
- `APP100` は常に物理 ID 100 のまま(暗黙変換なし)
|
|
25
|
+
- `allowPhysicalAppRefs: false` で、その profile の物理 `APPxxx` 直接参照を禁止可能
|
|
26
26
|
- `FROM` 省略 SELECT(例: `SELECT 'xxx' AS a`)
|
|
27
27
|
|
|
28
28
|
## インストール
|
|
@@ -50,11 +50,11 @@ npm run build:plugin
|
|
|
50
50
|
|
|
51
51
|
## 使い分け(CLI / Plugin)
|
|
52
52
|
|
|
53
|
-
- CLI を使うケース:
|
|
53
|
+
- CLI を使うケース:
|
|
54
54
|
- DML を含むバッチ実行・SQL ファイル実行(`-f`)
|
|
55
55
|
- CI/CD 連携
|
|
56
|
-
- `APP@profile` を使った環境切替
|
|
57
|
-
- `LAPP_<NAME>` を使った配置非依存 SQL
|
|
56
|
+
- `APP@profile` を使った環境切替
|
|
57
|
+
- `LAPP_<NAME>` を使った配置非依存 SQL
|
|
58
58
|
- `--dry-run` / `EXPLAIN` による安全確認
|
|
59
59
|
|
|
60
60
|
- Plugin を使うケース:
|
|
@@ -62,14 +62,14 @@ npm run build:plugin
|
|
|
62
62
|
- 非エンジニア向けの運用
|
|
63
63
|
- UI で結果確認したい場合
|
|
64
64
|
|
|
65
|
-
- MCP を使うケース:
|
|
65
|
+
- MCP を使うケース:
|
|
66
66
|
- Claude 等の AI クライアントから kintone を照会・更新
|
|
67
|
-
- 一時テーブルで中間結果をサーバー内に保持し、AI のコンテキスト消費を抑えたい場合
|
|
68
|
-
- validation / EXPLAIN で論理名から最終的な物理アプリ ID への解決を確認したい場合
|
|
67
|
+
- 一時テーブルで中間結果をサーバー内に保持し、AI のコンテキスト消費を抑えたい場合
|
|
68
|
+
- validation / EXPLAIN で論理名から最終的な物理アプリ ID への解決を確認したい場合
|
|
69
69
|
|
|
70
70
|
注意:
|
|
71
71
|
|
|
72
|
-
- `APP@profile` と `LAPP_<NAME>[@profile]` は Node.js runtime(CLI / MCP)の拡張です。plugin 側では非対応です。
|
|
72
|
+
- `APP@profile` と `LAPP_<NAME>[@profile]` は Node.js runtime(CLI / MCP)の拡張です。plugin 側では非対応です。
|
|
73
73
|
|
|
74
74
|
## 最短実行例(CLI)
|
|
75
75
|
|
|
@@ -104,39 +104,39 @@ node dist-cli/ksql.js --console --base-url https://example.cybozu.com --token xx
|
|
|
104
104
|
- 既定: `./ksql.config.json`
|
|
105
105
|
- profile 切替: `--profile <name>`
|
|
106
106
|
|
|
107
|
-
例:
|
|
107
|
+
例:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
node dist-cli/ksql.js --config ./ksql.config.json --profile dev -e "SELECT * FROM APP100"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
論理アプリ参照を使う場合、profile ごとに論理名と物理 ID を定義します。
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"defaultProfile": "dev",
|
|
118
|
-
"profiles": {
|
|
119
|
-
"dev": {
|
|
120
|
-
"baseUrl": "https://dev.example.cybozu.com",
|
|
121
|
-
"logicalApps": { "ORDERS": 100 },
|
|
122
|
-
"tokenMap": { "APP100": "env:DEV_ORDERS_TOKEN" }
|
|
123
|
-
},
|
|
124
|
-
"prod": {
|
|
125
|
-
"baseUrl": "https://prod.example.cybozu.com",
|
|
126
|
-
"allowPhysicalAppRefs": false,
|
|
127
|
-
"logicalApps": { "ORDERS": 1200 },
|
|
128
|
-
"tokenMap": { "APP1200": "env:PROD_ORDERS_TOKEN" }
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
node dist-cli/ksql.js --config ./ksql.config.json --profile dev -e "SELECT * FROM LAPP_ORDERS"
|
|
136
|
-
node dist-cli/ksql.js --config ./ksql.config.json --profile prod -e "SELECT * FROM LAPP_ORDERS"
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
どちらも同じ SQL ですが、前者は `APP100`、後者は `APP1200` に解決されます。`logicalApps` のキーは `LAPP_` を付けない ASCII 論理名です。`APP100`、`100`、`LAPP_ORDERS` は設定キーとして拒否されます。
|
|
110
|
+
node dist-cli/ksql.js --config ./ksql.config.json --profile dev -e "SELECT * FROM APP100"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
論理アプリ参照を使う場合、profile ごとに論理名と物理 ID を定義します。
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"defaultProfile": "dev",
|
|
118
|
+
"profiles": {
|
|
119
|
+
"dev": {
|
|
120
|
+
"baseUrl": "https://dev.example.cybozu.com",
|
|
121
|
+
"logicalApps": { "ORDERS": 100 },
|
|
122
|
+
"tokenMap": { "APP100": "env:DEV_ORDERS_TOKEN" }
|
|
123
|
+
},
|
|
124
|
+
"prod": {
|
|
125
|
+
"baseUrl": "https://prod.example.cybozu.com",
|
|
126
|
+
"allowPhysicalAppRefs": false,
|
|
127
|
+
"logicalApps": { "ORDERS": 1200 },
|
|
128
|
+
"tokenMap": { "APP1200": "env:PROD_ORDERS_TOKEN" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
node dist-cli/ksql.js --config ./ksql.config.json --profile dev -e "SELECT * FROM LAPP_ORDERS"
|
|
136
|
+
node dist-cli/ksql.js --config ./ksql.config.json --profile prod -e "SELECT * FROM LAPP_ORDERS"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
どちらも同じ SQL ですが、前者は `APP100`、後者は `APP1200` に解決されます。`logicalApps` のキーは `LAPP_` を付けない ASCII 論理名です。`APP100`、`100`、`LAPP_ORDERS` は設定キーとして拒否されます。
|
|
140
140
|
|
|
141
141
|
## CLI オプション
|
|
142
142
|
|
|
@@ -154,6 +154,7 @@ Options:
|
|
|
154
154
|
-f, --file <path> Execute SQL file
|
|
155
155
|
--console Start interactive console mode
|
|
156
156
|
--dry-run Parse and show execution plan only
|
|
157
|
+
--var <name=value> Override a DECLARE variable (repeatable; not for secrets)
|
|
157
158
|
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
158
159
|
(batch + json: prints one JSON envelope for the whole batch)
|
|
159
160
|
--max-records <n> Max records to fetch (default: 500)
|
|
@@ -236,7 +237,7 @@ Options:
|
|
|
236
237
|
- [CLI / Console 仕様](docs/ksql_cli_console_spec.md)
|
|
237
238
|
- [バッチ実行・一時テーブル仕様](docs/ksql_batch_temp_table_spec.md)
|
|
238
239
|
- [MCP サーバー仕様](docs/ksql_mcp_server_spec.md) / [Claude Desktop への導入(MCPB)](docs/ksql_mcpb_claude_desktop_install.md)
|
|
239
|
-
- [APP@profile 仕様](docs/cli_app_profile_spec.md)
|
|
240
|
+
- [APP@profile 仕様](docs/cli_app_profile_spec.md)
|
|
240
241
|
- [公開前チェックリスト](docs/internal/public_release_checklist.md)
|
|
241
242
|
|
|
242
243
|
## ライセンス
|
package/dist-cli/ksql.js
CHANGED
|
@@ -599,13 +599,14 @@ var Parser = class {
|
|
|
599
599
|
const upper = tok.value.toUpperCase();
|
|
600
600
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
601
601
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
602
|
+
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
602
603
|
break;
|
|
603
604
|
}
|
|
604
605
|
default:
|
|
605
606
|
break;
|
|
606
607
|
}
|
|
607
608
|
throw new ParseError(
|
|
608
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
609
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
609
610
|
tok
|
|
610
611
|
);
|
|
611
612
|
}
|
|
@@ -616,19 +617,32 @@ var Parser = class {
|
|
|
616
617
|
this.expect("SET" /* SET */);
|
|
617
618
|
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
618
619
|
this.expect("=" /* EQ */);
|
|
619
|
-
const expr = this.parseScalarExpr();
|
|
620
|
+
const expr = this.parseScalarExpr("SET", true);
|
|
620
621
|
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
621
622
|
}
|
|
622
|
-
|
|
623
|
-
|
|
623
|
+
parseDeclareVariable() {
|
|
624
|
+
this.advance();
|
|
625
|
+
const variable = this.expect("VARIABLE" /* VARIABLE */, "DECLARE \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
626
|
+
this.expect("=" /* EQ */);
|
|
627
|
+
const expr = this.parseScalarExpr("DECLARE", false);
|
|
628
|
+
if (expr.type === "SCALAR_SUBQUERY") {
|
|
629
|
+
throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
|
|
630
|
+
}
|
|
631
|
+
return { type: "DECLARE_VARIABLE", name: variable.value.slice(1).toLowerCase(), default: expr };
|
|
632
|
+
}
|
|
633
|
+
/** SET / DECLARE RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
|
|
634
|
+
parseScalarExpr(context, allowScalarSubquery) {
|
|
624
635
|
const tok = this.peek();
|
|
625
636
|
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
626
|
-
throw new ParseError(
|
|
637
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
627
638
|
}
|
|
628
639
|
if (tok.kind === "NULL" /* NULL */) {
|
|
629
|
-
throw new ParseError(
|
|
640
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
630
641
|
}
|
|
631
642
|
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
643
|
+
if (!allowScalarSubquery) {
|
|
644
|
+
throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
645
|
+
}
|
|
632
646
|
this.advance();
|
|
633
647
|
const query = this.parseSelect();
|
|
634
648
|
this.expect(")" /* RPAREN */);
|
|
@@ -652,7 +666,7 @@ var Parser = class {
|
|
|
652
666
|
}
|
|
653
667
|
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
654
668
|
throw new ParseError(
|
|
655
|
-
|
|
669
|
+
`${context} \u306E\u53F3\u8FBA\u3067 LOGINUSER() \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08\u5B9F\u884C\u74B0\u5883\u5171\u901A\u306E\u30ED\u30B0\u30A4\u30F3\u30E6\u30FC\u30B6\u30FC\u89E3\u6C7A\u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF09`,
|
|
656
670
|
tok
|
|
657
671
|
);
|
|
658
672
|
}
|
|
@@ -660,22 +674,22 @@ var Parser = class {
|
|
|
660
674
|
return this.parseSqlValue();
|
|
661
675
|
}
|
|
662
676
|
const expr = this.parseArithAddSub();
|
|
663
|
-
this.rejectNonScalarExpr(expr, tok);
|
|
677
|
+
this.rejectNonScalarExpr(expr, tok, context);
|
|
664
678
|
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
665
|
-
throw new ParseError(
|
|
679
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u306B\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u542B\u307E\u306A\u3044\u30B9\u30AB\u30E9\u30FC\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
666
680
|
}
|
|
667
|
-
rejectNonScalarExpr(node, tok) {
|
|
681
|
+
rejectNonScalarExpr(node, tok, context) {
|
|
668
682
|
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
669
683
|
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
670
|
-
throw new ParseError(
|
|
684
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u30FB\u96C6\u8A08\u95A2\u6570\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
671
685
|
}
|
|
672
686
|
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
673
|
-
this.rejectNonScalarExpr(node.left, tok);
|
|
674
|
-
this.rejectNonScalarExpr(node.right, tok);
|
|
687
|
+
this.rejectNonScalarExpr(node.left, tok, context);
|
|
688
|
+
this.rejectNonScalarExpr(node.right, tok, context);
|
|
675
689
|
return;
|
|
676
690
|
}
|
|
677
691
|
if (node.type === "STRING_FUNC") {
|
|
678
|
-
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
692
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
|
|
679
693
|
}
|
|
680
694
|
}
|
|
681
695
|
// ----------------------------------------------------------
|
|
@@ -2301,7 +2315,7 @@ function isDmlType(type) {
|
|
|
2301
2315
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
2302
2316
|
}
|
|
2303
2317
|
function isReadOnlyType(type) {
|
|
2304
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "ASSERT";
|
|
2318
|
+
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
2305
2319
|
}
|
|
2306
2320
|
function hasWhereClause(stmt) {
|
|
2307
2321
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -2375,8 +2389,9 @@ function analyzeBatch(statements) {
|
|
|
2375
2389
|
}
|
|
2376
2390
|
if (statements.length === 1) {
|
|
2377
2391
|
const t = statements[0].type;
|
|
2378
|
-
if (t === "SET_VARIABLE") {
|
|
2379
|
-
|
|
2392
|
+
if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
|
|
2393
|
+
const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
|
|
2394
|
+
throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
|
|
2380
2395
|
}
|
|
2381
2396
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
2382
2397
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
@@ -2410,7 +2425,7 @@ function analyzeBatch(statements) {
|
|
|
2410
2425
|
}
|
|
2411
2426
|
def.referencedBy.push(index);
|
|
2412
2427
|
}
|
|
2413
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
2428
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
2414
2429
|
if (variableDefs.has(stmt.name)) {
|
|
2415
2430
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
2416
2431
|
}
|
|
@@ -2507,6 +2522,40 @@ function analyzeBatch(statements) {
|
|
|
2507
2522
|
};
|
|
2508
2523
|
}
|
|
2509
2524
|
|
|
2525
|
+
// src/core/batchVariables.ts
|
|
2526
|
+
var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
2527
|
+
function normalizeBatchVariableName(name) {
|
|
2528
|
+
if (!VARIABLE_NAME_RE.test(name)) {
|
|
2529
|
+
throw new Error(
|
|
2530
|
+
`ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
return name.toLowerCase();
|
|
2534
|
+
}
|
|
2535
|
+
function normalizeBatchVariables(input) {
|
|
2536
|
+
const normalized = /* @__PURE__ */ Object.create(null);
|
|
2537
|
+
for (const [rawName, value] of Object.entries(input ?? {})) {
|
|
2538
|
+
const name = normalizeBatchVariableName(rawName);
|
|
2539
|
+
if (Object.prototype.hasOwnProperty.call(normalized, name)) {
|
|
2540
|
+
throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
|
|
2541
|
+
}
|
|
2542
|
+
normalized[name] = value;
|
|
2543
|
+
}
|
|
2544
|
+
return normalized;
|
|
2545
|
+
}
|
|
2546
|
+
function validateDeclaredBatchVariables(statements, input) {
|
|
2547
|
+
const normalized = normalizeBatchVariables(input);
|
|
2548
|
+
const declared = new Set(
|
|
2549
|
+
statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
|
|
2550
|
+
);
|
|
2551
|
+
for (const name of Object.keys(normalized)) {
|
|
2552
|
+
if (!declared.has(name)) {
|
|
2553
|
+
throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return normalized;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2510
2559
|
// src/core/scalarCompare.ts
|
|
2511
2560
|
function compareScalarValues(op, leftStr, rightStr) {
|
|
2512
2561
|
if (op === "=") return leftStr === rightStr;
|
|
@@ -4838,6 +4887,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4838
4887
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4839
4888
|
case "SET_VARIABLE":
|
|
4840
4889
|
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
4890
|
+
case "DECLARE_VARIABLE":
|
|
4891
|
+
throw new Error("ArgumentError: DECLARE variable requires a batch.");
|
|
4841
4892
|
case "ASSERT":
|
|
4842
4893
|
return executeAssert(stmt, client, options, cacheContext);
|
|
4843
4894
|
}
|
|
@@ -4851,6 +4902,8 @@ var BatchTimeoutError = class extends Error {
|
|
|
4851
4902
|
async function executeBatch(sql, client, options = {}) {
|
|
4852
4903
|
const statements = parseSqlBatch(sql);
|
|
4853
4904
|
const analysis = analyzeBatch(statements);
|
|
4905
|
+
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
4906
|
+
const batchOptions = { ...options, variables: injectedVariables };
|
|
4854
4907
|
if (options.continueOnError && analysis.containsDml) {
|
|
4855
4908
|
throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
|
|
4856
4909
|
}
|
|
@@ -4895,16 +4948,16 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4895
4948
|
}
|
|
4896
4949
|
try {
|
|
4897
4950
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
4898
|
-
const userConfirm =
|
|
4951
|
+
const userConfirm = batchOptions.confirm;
|
|
4899
4952
|
const stmtOptions = userConfirm ? {
|
|
4900
|
-
...
|
|
4953
|
+
...batchOptions,
|
|
4901
4954
|
confirm: (count, operation) => userConfirm(count, operation, {
|
|
4902
4955
|
statementIndex: i,
|
|
4903
4956
|
statementCount: statements.length,
|
|
4904
4957
|
statementType: info.statementType,
|
|
4905
4958
|
targetAppId: info.targetAppId
|
|
4906
4959
|
})
|
|
4907
|
-
} :
|
|
4960
|
+
} : batchOptions;
|
|
4908
4961
|
const outcome = await runWithDeadline(
|
|
4909
4962
|
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
4910
4963
|
remaining
|
|
@@ -4917,7 +4970,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4917
4970
|
aborted = "timeout";
|
|
4918
4971
|
} else if (e instanceof AssertError) {
|
|
4919
4972
|
aborted = "assertion";
|
|
4920
|
-
} else if (info.statementType === "SET_VARIABLE") {
|
|
4973
|
+
} else if (info.statementType === "SET_VARIABLE" || info.statementType === "DECLARE_VARIABLE") {
|
|
4921
4974
|
aborted = "fail-fast";
|
|
4922
4975
|
} else if (!options.continueOnError) {
|
|
4923
4976
|
aborted = "fail-fast";
|
|
@@ -4957,6 +5010,16 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
4957
5010
|
}
|
|
4958
5011
|
return {};
|
|
4959
5012
|
}
|
|
5013
|
+
if (stmt.type === "DECLARE_VARIABLE") {
|
|
5014
|
+
const injected = options.variables ?? {};
|
|
5015
|
+
if (Object.prototype.hasOwnProperty.call(injected, stmt.name)) {
|
|
5016
|
+
variables.set(stmt.name, { type: "string", value: injected[stmt.name] });
|
|
5017
|
+
} else {
|
|
5018
|
+
const value = evaluateScalarExpr(stmt.default);
|
|
5019
|
+
variables.set(stmt.name, { type: "string", value: String(value.value) });
|
|
5020
|
+
}
|
|
5021
|
+
return {};
|
|
5022
|
+
}
|
|
4960
5023
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
4961
5024
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
4962
5025
|
const materializeOptions = {
|
|
@@ -6682,9 +6745,10 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
6682
6745
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
6683
6746
|
return cache;
|
|
6684
6747
|
}
|
|
6685
|
-
function buildBatchExplainPlans(sql) {
|
|
6748
|
+
function buildBatchExplainPlans(sql, injectedVariables) {
|
|
6686
6749
|
const statements = parseSqlBatch(sql);
|
|
6687
6750
|
const analysis = analyzeBatch(statements);
|
|
6751
|
+
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
6688
6752
|
const variables = /* @__PURE__ */ new Map();
|
|
6689
6753
|
return {
|
|
6690
6754
|
statementCount: statements.length,
|
|
@@ -6695,7 +6759,7 @@ function buildBatchExplainPlans(sql) {
|
|
|
6695
6759
|
type: analysis.statements[i].statementType,
|
|
6696
6760
|
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
6697
6761
|
};
|
|
6698
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
6762
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
6699
6763
|
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
6700
6764
|
}
|
|
6701
6765
|
return result;
|
|
@@ -6732,6 +6796,12 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
6732
6796
|
" value: \u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09"
|
|
6733
6797
|
];
|
|
6734
6798
|
}
|
|
6799
|
+
if (stmt.type === "DECLARE_VARIABLE") {
|
|
6800
|
+
return [
|
|
6801
|
+
`DECLARE @${stmt.name} = <default scalar expression>`,
|
|
6802
|
+
" value: \u5916\u90E8\u6CE8\u5165\u304C\u3042\u308C\u3070\u63A1\u7528\u3001\u306A\u3051\u308C\u3070\u65E2\u5B9A\u5024\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u5024\u306F\u975E\u516C\u958B\uFF09"
|
|
6803
|
+
];
|
|
6804
|
+
}
|
|
6735
6805
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
6736
6806
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
6737
6807
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -8193,6 +8263,7 @@ Options:
|
|
|
8193
8263
|
-f, --file <path> Execute SQL file
|
|
8194
8264
|
--console Start interactive console mode
|
|
8195
8265
|
--dry-run Parse and show execution plan only
|
|
8266
|
+
--var <name=value> Override a DECLARE variable (repeatable; not for secrets)
|
|
8196
8267
|
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
8197
8268
|
(batch + json: prints one JSON envelope for the whole batch)
|
|
8198
8269
|
--max-records <n> Max records to fetch (default: 500)
|
|
@@ -8262,6 +8333,8 @@ function parseArgs(argv) {
|
|
|
8262
8333
|
password: null,
|
|
8263
8334
|
token: null,
|
|
8264
8335
|
tokenMap: {},
|
|
8336
|
+
// `__proto__` も有効な変数名なので prototype のない辞書で保持する。
|
|
8337
|
+
variables: /* @__PURE__ */ Object.create(null),
|
|
8265
8338
|
tokenFile: null,
|
|
8266
8339
|
app: null,
|
|
8267
8340
|
diagRecordId: null,
|
|
@@ -8356,6 +8429,19 @@ function parseArgs(argv) {
|
|
|
8356
8429
|
continue;
|
|
8357
8430
|
}
|
|
8358
8431
|
const v = argv[i + 1];
|
|
8432
|
+
if (a === "--var") {
|
|
8433
|
+
const raw = v ?? "";
|
|
8434
|
+
const eq = raw.indexOf("=");
|
|
8435
|
+
if (eq < 0) throw new Error("ArgumentError: --var must use name=value.");
|
|
8436
|
+
const rawName = raw.slice(0, eq);
|
|
8437
|
+
const name = normalizeBatchVariableName(rawName);
|
|
8438
|
+
if (Object.prototype.hasOwnProperty.call(out.variables, name)) {
|
|
8439
|
+
throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
|
|
8440
|
+
}
|
|
8441
|
+
out.variables[name] = raw.slice(eq + 1);
|
|
8442
|
+
i++;
|
|
8443
|
+
continue;
|
|
8444
|
+
}
|
|
8359
8445
|
if (a === "-e" || a === "--execute") {
|
|
8360
8446
|
out.executeSql = v ?? "";
|
|
8361
8447
|
i++;
|
|
@@ -8912,6 +8998,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
8912
8998
|
const argv = ["-e", sql];
|
|
8913
8999
|
if (dryRun) argv.push("--dry-run");
|
|
8914
9000
|
if (format) argv.push("--format", format);
|
|
9001
|
+
for (const [name, value] of Object.entries(base.variables)) argv.push("--var", `${name}=${value}`);
|
|
8915
9002
|
pushOpt(argv, "--config", base.configPath);
|
|
8916
9003
|
pushOpt(argv, "--profile", base.profile);
|
|
8917
9004
|
pushOpt(argv, "--base-url", base.baseUrl);
|
|
@@ -9535,6 +9622,10 @@ async function run() {
|
|
|
9535
9622
|
attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
|
|
9536
9623
|
};
|
|
9537
9624
|
const appIds = sql ? extractAppIds(sql) : [];
|
|
9625
|
+
if (!isBatchSql && Object.keys(args.variables).length > 0) {
|
|
9626
|
+
process.stderr.write("ArgumentError: --var requires a batch containing DECLARE.\n");
|
|
9627
|
+
return 2;
|
|
9628
|
+
}
|
|
9538
9629
|
const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
|
|
9539
9630
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
9540
9631
|
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
@@ -9550,7 +9641,7 @@ async function run() {
|
|
|
9550
9641
|
if (args.dryRun) {
|
|
9551
9642
|
let plans;
|
|
9552
9643
|
try {
|
|
9553
|
-
plans = buildBatchExplainPlans(sql);
|
|
9644
|
+
plans = buildBatchExplainPlans(sql, args.variables);
|
|
9554
9645
|
} catch (err) {
|
|
9555
9646
|
const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
|
|
9556
9647
|
bindings: sqlDiagnosticContext.appBindingByMappedApp,
|
|
@@ -9867,6 +9958,7 @@ query=${label}`);
|
|
|
9867
9958
|
continueOnError: args.continueOnError,
|
|
9868
9959
|
tempTableMaxRows,
|
|
9869
9960
|
timeoutMs: timeout,
|
|
9961
|
+
variables: args.variables,
|
|
9870
9962
|
confirm: batchContainsDml ? async (count, operation) => {
|
|
9871
9963
|
if (count > dmlMaxRows) {
|
|
9872
9964
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
|
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -31512,13 +31512,14 @@ var Parser = class {
|
|
|
31512
31512
|
const upper = tok.value.toUpperCase();
|
|
31513
31513
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
31514
31514
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
31515
|
+
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
31515
31516
|
break;
|
|
31516
31517
|
}
|
|
31517
31518
|
default:
|
|
31518
31519
|
break;
|
|
31519
31520
|
}
|
|
31520
31521
|
throw new ParseError(
|
|
31521
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31522
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31522
31523
|
tok
|
|
31523
31524
|
);
|
|
31524
31525
|
}
|
|
@@ -31529,19 +31530,32 @@ var Parser = class {
|
|
|
31529
31530
|
this.expect("SET" /* SET */);
|
|
31530
31531
|
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
31531
31532
|
this.expect("=" /* EQ */);
|
|
31532
|
-
const expr = this.parseScalarExpr();
|
|
31533
|
+
const expr = this.parseScalarExpr("SET", true);
|
|
31533
31534
|
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
31534
31535
|
}
|
|
31535
|
-
|
|
31536
|
-
|
|
31536
|
+
parseDeclareVariable() {
|
|
31537
|
+
this.advance();
|
|
31538
|
+
const variable = this.expect("VARIABLE" /* VARIABLE */, "DECLARE \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
31539
|
+
this.expect("=" /* EQ */);
|
|
31540
|
+
const expr = this.parseScalarExpr("DECLARE", false);
|
|
31541
|
+
if (expr.type === "SCALAR_SUBQUERY") {
|
|
31542
|
+
throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", this.peek());
|
|
31543
|
+
}
|
|
31544
|
+
return { type: "DECLARE_VARIABLE", name: variable.value.slice(1).toLowerCase(), default: expr };
|
|
31545
|
+
}
|
|
31546
|
+
/** SET / DECLARE RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
|
|
31547
|
+
parseScalarExpr(context, allowScalarSubquery) {
|
|
31537
31548
|
const tok = this.peek();
|
|
31538
31549
|
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
31539
|
-
throw new ParseError(
|
|
31550
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
31540
31551
|
}
|
|
31541
31552
|
if (tok.kind === "NULL" /* NULL */) {
|
|
31542
|
-
throw new ParseError(
|
|
31553
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
31543
31554
|
}
|
|
31544
31555
|
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
31556
|
+
if (!allowScalarSubquery) {
|
|
31557
|
+
throw new ParseError("DECLARE \u306E\u65E2\u5B9A\u5024\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
31558
|
+
}
|
|
31545
31559
|
this.advance();
|
|
31546
31560
|
const query = this.parseSelect();
|
|
31547
31561
|
this.expect(")" /* RPAREN */);
|
|
@@ -31565,7 +31579,7 @@ var Parser = class {
|
|
|
31565
31579
|
}
|
|
31566
31580
|
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
31567
31581
|
throw new ParseError(
|
|
31568
|
-
|
|
31582
|
+
`${context} \u306E\u53F3\u8FBA\u3067 LOGINUSER() \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08\u5B9F\u884C\u74B0\u5883\u5171\u901A\u306E\u30ED\u30B0\u30A4\u30F3\u30E6\u30FC\u30B6\u30FC\u89E3\u6C7A\u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF09`,
|
|
31569
31583
|
tok
|
|
31570
31584
|
);
|
|
31571
31585
|
}
|
|
@@ -31573,22 +31587,22 @@ var Parser = class {
|
|
|
31573
31587
|
return this.parseSqlValue();
|
|
31574
31588
|
}
|
|
31575
31589
|
const expr = this.parseArithAddSub();
|
|
31576
|
-
this.rejectNonScalarExpr(expr, tok);
|
|
31590
|
+
this.rejectNonScalarExpr(expr, tok, context);
|
|
31577
31591
|
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
31578
|
-
throw new ParseError(
|
|
31592
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u306B\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u542B\u307E\u306A\u3044\u30B9\u30AB\u30E9\u30FC\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
31579
31593
|
}
|
|
31580
|
-
rejectNonScalarExpr(node, tok) {
|
|
31594
|
+
rejectNonScalarExpr(node, tok, context) {
|
|
31581
31595
|
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
31582
31596
|
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
31583
|
-
throw new ParseError(
|
|
31597
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u30FB\u96C6\u8A08\u95A2\u6570\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
31584
31598
|
}
|
|
31585
31599
|
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
31586
|
-
this.rejectNonScalarExpr(node.left, tok);
|
|
31587
|
-
this.rejectNonScalarExpr(node.right, tok);
|
|
31600
|
+
this.rejectNonScalarExpr(node.left, tok, context);
|
|
31601
|
+
this.rejectNonScalarExpr(node.right, tok, context);
|
|
31588
31602
|
return;
|
|
31589
31603
|
}
|
|
31590
31604
|
if (node.type === "STRING_FUNC") {
|
|
31591
|
-
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
31605
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
|
|
31592
31606
|
}
|
|
31593
31607
|
}
|
|
31594
31608
|
// ----------------------------------------------------------
|
|
@@ -33214,7 +33228,7 @@ function isDmlType(type) {
|
|
|
33214
33228
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
33215
33229
|
}
|
|
33216
33230
|
function isReadOnlyType(type) {
|
|
33217
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "ASSERT";
|
|
33231
|
+
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
33218
33232
|
}
|
|
33219
33233
|
function hasWhereClause(stmt) {
|
|
33220
33234
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -33276,8 +33290,9 @@ function analyzeBatch(statements) {
|
|
|
33276
33290
|
}
|
|
33277
33291
|
if (statements.length === 1) {
|
|
33278
33292
|
const t = statements[0].type;
|
|
33279
|
-
if (t === "SET_VARIABLE") {
|
|
33280
|
-
|
|
33293
|
+
if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
|
|
33294
|
+
const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
|
|
33295
|
+
throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
|
|
33281
33296
|
}
|
|
33282
33297
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
33283
33298
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
@@ -33311,7 +33326,7 @@ function analyzeBatch(statements) {
|
|
|
33311
33326
|
}
|
|
33312
33327
|
def.referencedBy.push(index);
|
|
33313
33328
|
}
|
|
33314
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
33329
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
33315
33330
|
if (variableDefs.has(stmt.name)) {
|
|
33316
33331
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
33317
33332
|
}
|
|
@@ -33408,6 +33423,40 @@ function analyzeBatch(statements) {
|
|
|
33408
33423
|
};
|
|
33409
33424
|
}
|
|
33410
33425
|
|
|
33426
|
+
// src/core/batchVariables.ts
|
|
33427
|
+
var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
33428
|
+
function normalizeBatchVariableName(name) {
|
|
33429
|
+
if (!VARIABLE_NAME_RE.test(name)) {
|
|
33430
|
+
throw new Error(
|
|
33431
|
+
`ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
|
|
33432
|
+
);
|
|
33433
|
+
}
|
|
33434
|
+
return name.toLowerCase();
|
|
33435
|
+
}
|
|
33436
|
+
function normalizeBatchVariables(input) {
|
|
33437
|
+
const normalized = /* @__PURE__ */ Object.create(null);
|
|
33438
|
+
for (const [rawName, value] of Object.entries(input ?? {})) {
|
|
33439
|
+
const name = normalizeBatchVariableName(rawName);
|
|
33440
|
+
if (Object.prototype.hasOwnProperty.call(normalized, name)) {
|
|
33441
|
+
throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
|
|
33442
|
+
}
|
|
33443
|
+
normalized[name] = value;
|
|
33444
|
+
}
|
|
33445
|
+
return normalized;
|
|
33446
|
+
}
|
|
33447
|
+
function validateDeclaredBatchVariables(statements, input) {
|
|
33448
|
+
const normalized = normalizeBatchVariables(input);
|
|
33449
|
+
const declared = new Set(
|
|
33450
|
+
statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
|
|
33451
|
+
);
|
|
33452
|
+
for (const name of Object.keys(normalized)) {
|
|
33453
|
+
if (!declared.has(name)) {
|
|
33454
|
+
throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
|
|
33455
|
+
}
|
|
33456
|
+
}
|
|
33457
|
+
return normalized;
|
|
33458
|
+
}
|
|
33459
|
+
|
|
33411
33460
|
// src/core/scalarCompare.ts
|
|
33412
33461
|
function compareScalarValues(op, leftStr, rightStr) {
|
|
33413
33462
|
if (op === "=") return leftStr === rightStr;
|
|
@@ -35739,6 +35788,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
35739
35788
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35740
35789
|
case "SET_VARIABLE":
|
|
35741
35790
|
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
35791
|
+
case "DECLARE_VARIABLE":
|
|
35792
|
+
throw new Error("ArgumentError: DECLARE variable requires a batch.");
|
|
35742
35793
|
case "ASSERT":
|
|
35743
35794
|
return executeAssert(stmt, client, options, cacheContext);
|
|
35744
35795
|
}
|
|
@@ -35752,6 +35803,8 @@ var BatchTimeoutError = class extends Error {
|
|
|
35752
35803
|
async function executeBatch(sql, client, options = {}) {
|
|
35753
35804
|
const statements = parseSqlBatch(sql);
|
|
35754
35805
|
const analysis = analyzeBatch(statements);
|
|
35806
|
+
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
35807
|
+
const batchOptions = { ...options, variables: injectedVariables };
|
|
35755
35808
|
if (options.continueOnError && analysis.containsDml) {
|
|
35756
35809
|
throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
|
|
35757
35810
|
}
|
|
@@ -35796,16 +35849,16 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35796
35849
|
}
|
|
35797
35850
|
try {
|
|
35798
35851
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
35799
|
-
const userConfirm =
|
|
35852
|
+
const userConfirm = batchOptions.confirm;
|
|
35800
35853
|
const stmtOptions = userConfirm ? {
|
|
35801
|
-
...
|
|
35854
|
+
...batchOptions,
|
|
35802
35855
|
confirm: (count, operation) => userConfirm(count, operation, {
|
|
35803
35856
|
statementIndex: i,
|
|
35804
35857
|
statementCount: statements.length,
|
|
35805
35858
|
statementType: info.statementType,
|
|
35806
35859
|
targetAppId: info.targetAppId
|
|
35807
35860
|
})
|
|
35808
|
-
} :
|
|
35861
|
+
} : batchOptions;
|
|
35809
35862
|
const outcome = await runWithDeadline(
|
|
35810
35863
|
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
35811
35864
|
remaining
|
|
@@ -35818,7 +35871,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35818
35871
|
aborted2 = "timeout";
|
|
35819
35872
|
} else if (e instanceof AssertError) {
|
|
35820
35873
|
aborted2 = "assertion";
|
|
35821
|
-
} else if (info.statementType === "SET_VARIABLE") {
|
|
35874
|
+
} else if (info.statementType === "SET_VARIABLE" || info.statementType === "DECLARE_VARIABLE") {
|
|
35822
35875
|
aborted2 = "fail-fast";
|
|
35823
35876
|
} else if (!options.continueOnError) {
|
|
35824
35877
|
aborted2 = "fail-fast";
|
|
@@ -35858,6 +35911,16 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
35858
35911
|
}
|
|
35859
35912
|
return {};
|
|
35860
35913
|
}
|
|
35914
|
+
if (stmt.type === "DECLARE_VARIABLE") {
|
|
35915
|
+
const injected = options.variables ?? {};
|
|
35916
|
+
if (Object.prototype.hasOwnProperty.call(injected, stmt.name)) {
|
|
35917
|
+
variables.set(stmt.name, { type: "string", value: injected[stmt.name] });
|
|
35918
|
+
} else {
|
|
35919
|
+
const value = evaluateScalarExpr(stmt.default);
|
|
35920
|
+
variables.set(stmt.name, { type: "string", value: String(value.value) });
|
|
35921
|
+
}
|
|
35922
|
+
return {};
|
|
35923
|
+
}
|
|
35861
35924
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
35862
35925
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
35863
35926
|
const materializeOptions = {
|
|
@@ -37583,9 +37646,10 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
37583
37646
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
37584
37647
|
return cache;
|
|
37585
37648
|
}
|
|
37586
|
-
function buildBatchExplainPlans(sql) {
|
|
37649
|
+
function buildBatchExplainPlans(sql, injectedVariables) {
|
|
37587
37650
|
const statements = parseSqlBatch(sql);
|
|
37588
37651
|
const analysis = analyzeBatch(statements);
|
|
37652
|
+
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
37589
37653
|
const variables = /* @__PURE__ */ new Map();
|
|
37590
37654
|
return {
|
|
37591
37655
|
statementCount: statements.length,
|
|
@@ -37596,7 +37660,7 @@ function buildBatchExplainPlans(sql) {
|
|
|
37596
37660
|
type: analysis.statements[i].statementType,
|
|
37597
37661
|
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
37598
37662
|
};
|
|
37599
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
37663
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
37600
37664
|
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
37601
37665
|
}
|
|
37602
37666
|
return result;
|
|
@@ -37633,6 +37697,12 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
37633
37697
|
" value: \u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09"
|
|
37634
37698
|
];
|
|
37635
37699
|
}
|
|
37700
|
+
if (stmt.type === "DECLARE_VARIABLE") {
|
|
37701
|
+
return [
|
|
37702
|
+
`DECLARE @${stmt.name} = <default scalar expression>`,
|
|
37703
|
+
" value: \u5916\u90E8\u6CE8\u5165\u304C\u3042\u308C\u3070\u63A1\u7528\u3001\u306A\u3051\u308C\u3070\u65E2\u5B9A\u5024\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF08\u5024\u306F\u975E\u516C\u958B\uFF09"
|
|
37704
|
+
];
|
|
37705
|
+
}
|
|
37636
37706
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
37637
37707
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
37638
37708
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -39611,6 +39681,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39611
39681
|
}
|
|
39612
39682
|
async function query(input, validated) {
|
|
39613
39683
|
const validation = validated ?? await validate(input);
|
|
39684
|
+
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
39685
|
+
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
39686
|
+
}
|
|
39614
39687
|
if (validation.batch) {
|
|
39615
39688
|
if (validation.containsDml) {
|
|
39616
39689
|
throw new Error("ArgumentError: batch contains DML statements. Use ksql_mutate.");
|
|
@@ -39637,7 +39710,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39637
39710
|
// バッチでは timeout を合計タイムアウトとして扱う(仕様 §5.7)。
|
|
39638
39711
|
// runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
|
|
39639
39712
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
39640
|
-
timeoutMs: runtime2.timeout
|
|
39713
|
+
timeoutMs: runtime2.timeout,
|
|
39714
|
+
variables: input.variables
|
|
39641
39715
|
});
|
|
39642
39716
|
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
39643
39717
|
}
|
|
@@ -39726,6 +39800,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39726
39800
|
tempTableMaxRows: runtime.tempTableMaxRows,
|
|
39727
39801
|
// 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
|
|
39728
39802
|
timeoutMs: runtime.timeout,
|
|
39803
|
+
variables: input.variables,
|
|
39729
39804
|
confirm: async (count, operation) => {
|
|
39730
39805
|
if (count > dmlMaxRows) {
|
|
39731
39806
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
@@ -39754,6 +39829,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39754
39829
|
async function mutate(input, validated) {
|
|
39755
39830
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
39756
39831
|
const validation = validated ?? await validate(input);
|
|
39832
|
+
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
39833
|
+
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
39834
|
+
}
|
|
39757
39835
|
if (validation.batch) {
|
|
39758
39836
|
return mutateBatch(input, validation, dmlMaxRows);
|
|
39759
39837
|
}
|
|
@@ -39976,7 +40054,8 @@ var queryInputSchema = external_exports.object({
|
|
|
39976
40054
|
tempTableMaxRows,
|
|
39977
40055
|
timeout,
|
|
39978
40056
|
continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
|
|
39979
|
-
maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional()
|
|
40057
|
+
maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional(),
|
|
40058
|
+
variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
|
|
39980
40059
|
});
|
|
39981
40060
|
var mutateInputSchema = external_exports.object({
|
|
39982
40061
|
sql: external_exports.string().min(1).describe("DML kSQL text. May contain multiple ;-separated statements (batch) with temp tables, e.g. CREATE TEMP TABLE #t AS SELECT ...; INSERT INTO APPx (...) SELECT ... FROM #t;"),
|
|
@@ -39987,7 +40066,8 @@ var mutateInputSchema = external_exports.object({
|
|
|
39987
40066
|
fetchParallel,
|
|
39988
40067
|
tempTableMaxRows,
|
|
39989
40068
|
timeout,
|
|
39990
|
-
dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional()
|
|
40069
|
+
dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional(),
|
|
40070
|
+
variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
|
|
39991
40071
|
});
|
|
39992
40072
|
var describeAppInputSchema = external_exports.object({
|
|
39993
40073
|
app: external_exports.number().int().positive().describe("kintone app ID to describe."),
|
|
@@ -40076,7 +40156,7 @@ Options:
|
|
|
40076
40156
|
-h, --help Show help
|
|
40077
40157
|
`);
|
|
40078
40158
|
}
|
|
40079
|
-
var SERVER_VERSION = true ? "2.
|
|
40159
|
+
var SERVER_VERSION = true ? "2.4.0" : "0.0.0-dev";
|
|
40080
40160
|
function createServer(args) {
|
|
40081
40161
|
const server = new McpServer({
|
|
40082
40162
|
name: "ksql-mcp",
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|