@rex0220/kintone-sql-tools 2.2.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 +196 -42
- package/dist-mcp/ksql-mcp.js +187 -45
- 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,20 +617,48 @@ 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 */) {
|
|
632
|
-
|
|
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
|
+
}
|
|
646
|
+
this.advance();
|
|
647
|
+
const query = this.parseSelect();
|
|
648
|
+
this.expect(")" /* RPAREN */);
|
|
649
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
650
|
+
throw new ParseError(
|
|
651
|
+
"\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306E\u5F8C\u306B\u7B97\u8853\u6F14\u7B97\u5B50\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
652
|
+
this.peek()
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
const hasWildcard = query.columns.some(
|
|
656
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
657
|
+
);
|
|
658
|
+
if (!hasWildcard && query.columns.length !== 1) {
|
|
659
|
+
throw new ParseError("scalar subquery in SET must return exactly 1 column.", tok);
|
|
660
|
+
}
|
|
661
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
633
662
|
}
|
|
634
663
|
if (tok.kind === "STRING" /* STRING */) {
|
|
635
664
|
this.advance();
|
|
@@ -637,7 +666,7 @@ var Parser = class {
|
|
|
637
666
|
}
|
|
638
667
|
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
639
668
|
throw new ParseError(
|
|
640
|
-
|
|
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`,
|
|
641
670
|
tok
|
|
642
671
|
);
|
|
643
672
|
}
|
|
@@ -645,22 +674,22 @@ var Parser = class {
|
|
|
645
674
|
return this.parseSqlValue();
|
|
646
675
|
}
|
|
647
676
|
const expr = this.parseArithAddSub();
|
|
648
|
-
this.rejectNonScalarExpr(expr, tok);
|
|
677
|
+
this.rejectNonScalarExpr(expr, tok, context);
|
|
649
678
|
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
650
|
-
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);
|
|
651
680
|
}
|
|
652
|
-
rejectNonScalarExpr(node, tok) {
|
|
681
|
+
rejectNonScalarExpr(node, tok, context) {
|
|
653
682
|
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
654
683
|
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
655
|
-
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);
|
|
656
685
|
}
|
|
657
686
|
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
658
|
-
this.rejectNonScalarExpr(node.left, tok);
|
|
659
|
-
this.rejectNonScalarExpr(node.right, tok);
|
|
687
|
+
this.rejectNonScalarExpr(node.left, tok, context);
|
|
688
|
+
this.rejectNonScalarExpr(node.right, tok, context);
|
|
660
689
|
return;
|
|
661
690
|
}
|
|
662
691
|
if (node.type === "STRING_FUNC") {
|
|
663
|
-
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
692
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
|
|
664
693
|
}
|
|
665
694
|
}
|
|
666
695
|
// ----------------------------------------------------------
|
|
@@ -2286,7 +2315,7 @@ function isDmlType(type) {
|
|
|
2286
2315
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
2287
2316
|
}
|
|
2288
2317
|
function isReadOnlyType(type) {
|
|
2289
|
-
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";
|
|
2290
2319
|
}
|
|
2291
2320
|
function hasWhereClause(stmt) {
|
|
2292
2321
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -2360,8 +2389,9 @@ function analyzeBatch(statements) {
|
|
|
2360
2389
|
}
|
|
2361
2390
|
if (statements.length === 1) {
|
|
2362
2391
|
const t = statements[0].type;
|
|
2363
|
-
if (t === "SET_VARIABLE") {
|
|
2364
|
-
|
|
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);
|
|
2365
2395
|
}
|
|
2366
2396
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
2367
2397
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
@@ -2395,7 +2425,7 @@ function analyzeBatch(statements) {
|
|
|
2395
2425
|
}
|
|
2396
2426
|
def.referencedBy.push(index);
|
|
2397
2427
|
}
|
|
2398
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
2428
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
2399
2429
|
if (variableDefs.has(stmt.name)) {
|
|
2400
2430
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
2401
2431
|
}
|
|
@@ -2492,6 +2522,40 @@ function analyzeBatch(statements) {
|
|
|
2492
2522
|
};
|
|
2493
2523
|
}
|
|
2494
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
|
+
|
|
2495
2559
|
// src/core/scalarCompare.ts
|
|
2496
2560
|
function compareScalarValues(op, leftStr, rightStr) {
|
|
2497
2561
|
if (op === "=") return leftStr === rightStr;
|
|
@@ -4823,6 +4887,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4823
4887
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4824
4888
|
case "SET_VARIABLE":
|
|
4825
4889
|
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
4890
|
+
case "DECLARE_VARIABLE":
|
|
4891
|
+
throw new Error("ArgumentError: DECLARE variable requires a batch.");
|
|
4826
4892
|
case "ASSERT":
|
|
4827
4893
|
return executeAssert(stmt, client, options, cacheContext);
|
|
4828
4894
|
}
|
|
@@ -4836,6 +4902,8 @@ var BatchTimeoutError = class extends Error {
|
|
|
4836
4902
|
async function executeBatch(sql, client, options = {}) {
|
|
4837
4903
|
const statements = parseSqlBatch(sql);
|
|
4838
4904
|
const analysis = analyzeBatch(statements);
|
|
4905
|
+
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
4906
|
+
const batchOptions = { ...options, variables: injectedVariables };
|
|
4839
4907
|
if (options.continueOnError && analysis.containsDml) {
|
|
4840
4908
|
throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
|
|
4841
4909
|
}
|
|
@@ -4880,16 +4948,16 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4880
4948
|
}
|
|
4881
4949
|
try {
|
|
4882
4950
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
4883
|
-
const userConfirm =
|
|
4951
|
+
const userConfirm = batchOptions.confirm;
|
|
4884
4952
|
const stmtOptions = userConfirm ? {
|
|
4885
|
-
...
|
|
4953
|
+
...batchOptions,
|
|
4886
4954
|
confirm: (count, operation) => userConfirm(count, operation, {
|
|
4887
4955
|
statementIndex: i,
|
|
4888
4956
|
statementCount: statements.length,
|
|
4889
4957
|
statementType: info.statementType,
|
|
4890
4958
|
targetAppId: info.targetAppId
|
|
4891
4959
|
})
|
|
4892
|
-
} :
|
|
4960
|
+
} : batchOptions;
|
|
4893
4961
|
const outcome = await runWithDeadline(
|
|
4894
4962
|
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
4895
4963
|
remaining
|
|
@@ -4902,7 +4970,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4902
4970
|
aborted = "timeout";
|
|
4903
4971
|
} else if (e instanceof AssertError) {
|
|
4904
4972
|
aborted = "assertion";
|
|
4905
|
-
} else if (info.statementType === "SET_VARIABLE") {
|
|
4973
|
+
} else if (info.statementType === "SET_VARIABLE" || info.statementType === "DECLARE_VARIABLE") {
|
|
4906
4974
|
aborted = "fail-fast";
|
|
4907
4975
|
} else if (!options.continueOnError) {
|
|
4908
4976
|
aborted = "fail-fast";
|
|
@@ -4920,7 +4988,36 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4920
4988
|
}
|
|
4921
4989
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
4922
4990
|
if (stmt.type === "SET_VARIABLE") {
|
|
4923
|
-
|
|
4991
|
+
const resolvedStmt2 = resolveVariableRefs(stmt, variables);
|
|
4992
|
+
if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
4993
|
+
try {
|
|
4994
|
+
const value = await evaluateScalarSubquery(
|
|
4995
|
+
resolvedStmt2.expr.query,
|
|
4996
|
+
client,
|
|
4997
|
+
options,
|
|
4998
|
+
cacheContext,
|
|
4999
|
+
tempTables
|
|
5000
|
+
);
|
|
5001
|
+
variables.set(stmt.name, { type: "string", value });
|
|
5002
|
+
} catch (e) {
|
|
5003
|
+
if (e instanceof ScalarSubqueryError) {
|
|
5004
|
+
throw new Error(`ArgumentError: ${e.message}`);
|
|
5005
|
+
}
|
|
5006
|
+
throw e;
|
|
5007
|
+
}
|
|
5008
|
+
} else {
|
|
5009
|
+
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr));
|
|
5010
|
+
}
|
|
5011
|
+
return {};
|
|
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
|
+
}
|
|
4924
5021
|
return {};
|
|
4925
5022
|
}
|
|
4926
5023
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
@@ -5083,6 +5180,12 @@ var AssertError = class extends Error {
|
|
|
5083
5180
|
this.name = "AssertError";
|
|
5084
5181
|
}
|
|
5085
5182
|
};
|
|
5183
|
+
var ScalarSubqueryError = class extends Error {
|
|
5184
|
+
constructor(message) {
|
|
5185
|
+
super(message);
|
|
5186
|
+
this.name = "ScalarSubqueryError";
|
|
5187
|
+
}
|
|
5188
|
+
};
|
|
5086
5189
|
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
5087
5190
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
5088
5191
|
if (stmt.op === "BETWEEN") {
|
|
@@ -5116,25 +5219,38 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
5116
5219
|
case "ARITH":
|
|
5117
5220
|
return String(evalAssertArith(operand));
|
|
5118
5221
|
case "SCALAR_SUBQUERY": {
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5222
|
+
try {
|
|
5223
|
+
return await evaluateScalarSubquery(
|
|
5224
|
+
operand.query,
|
|
5225
|
+
client,
|
|
5226
|
+
options,
|
|
5227
|
+
cacheContext,
|
|
5228
|
+
tempTables
|
|
5124
5229
|
);
|
|
5230
|
+
} catch (e) {
|
|
5231
|
+
if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
|
|
5232
|
+
throw e;
|
|
5125
5233
|
}
|
|
5126
|
-
if (result.rowCount === 0) {
|
|
5127
|
-
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
5128
|
-
}
|
|
5129
|
-
if (result.rowCount > 1) {
|
|
5130
|
-
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
5131
|
-
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
5132
|
-
}
|
|
5133
|
-
const col = result.columns[0] ?? "";
|
|
5134
|
-
return result.rows[0]?.[col] ?? "";
|
|
5135
5234
|
}
|
|
5136
5235
|
}
|
|
5137
5236
|
}
|
|
5237
|
+
async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
|
|
5238
|
+
const { query, probed } = withScalarProbeLimit(sourceQuery);
|
|
5239
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
5240
|
+
if (result.columns.length !== 1) {
|
|
5241
|
+
throw new ScalarSubqueryError(
|
|
5242
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
if (result.rowCount === 0) {
|
|
5246
|
+
throw new ScalarSubqueryError("scalar subquery returned no rows (expected 1 row).");
|
|
5247
|
+
}
|
|
5248
|
+
if (result.rowCount > 1) {
|
|
5249
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
5250
|
+
throw new ScalarSubqueryError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
5251
|
+
}
|
|
5252
|
+
return result.rows[0]?.[result.columns[0]] ?? "";
|
|
5253
|
+
}
|
|
5138
5254
|
function withScalarProbeLimit(query) {
|
|
5139
5255
|
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
5140
5256
|
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
@@ -6629,20 +6745,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
6629
6745
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
6630
6746
|
return cache;
|
|
6631
6747
|
}
|
|
6632
|
-
function buildBatchExplainPlans(sql) {
|
|
6748
|
+
function buildBatchExplainPlans(sql, injectedVariables) {
|
|
6633
6749
|
const statements = parseSqlBatch(sql);
|
|
6634
6750
|
const analysis = analyzeBatch(statements);
|
|
6751
|
+
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
6635
6752
|
const variables = /* @__PURE__ */ new Map();
|
|
6636
6753
|
return {
|
|
6637
6754
|
statementCount: statements.length,
|
|
6638
6755
|
statements: statements.map((stmt, i) => {
|
|
6639
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
6756
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
|
|
6640
6757
|
const result = {
|
|
6641
6758
|
index: i,
|
|
6642
6759
|
type: analysis.statements[i].statementType,
|
|
6643
6760
|
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
6644
6761
|
};
|
|
6645
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
6762
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
6646
6763
|
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
6647
6764
|
}
|
|
6648
6765
|
return result;
|
|
@@ -6665,11 +6782,26 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
6665
6782
|
];
|
|
6666
6783
|
}
|
|
6667
6784
|
if (stmt.type === "SET_VARIABLE") {
|
|
6785
|
+
if (stmt.expr.type === "SCALAR_SUBQUERY") {
|
|
6786
|
+
const subInfo = hasTempTableRef(stmt.expr.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
6787
|
+
return [
|
|
6788
|
+
`SET @${stmt.name} = (SELECT ...)`,
|
|
6789
|
+
" value: \u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF081\u884C1\u5217\u30FB\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09",
|
|
6790
|
+
" subquery:",
|
|
6791
|
+
...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
|
|
6792
|
+
];
|
|
6793
|
+
}
|
|
6668
6794
|
return [
|
|
6669
6795
|
`SET @${stmt.name} = <scalar expression>`,
|
|
6670
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"
|
|
6671
6797
|
];
|
|
6672
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
|
+
}
|
|
6673
6805
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
6674
6806
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
6675
6807
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -8131,6 +8263,7 @@ Options:
|
|
|
8131
8263
|
-f, --file <path> Execute SQL file
|
|
8132
8264
|
--console Start interactive console mode
|
|
8133
8265
|
--dry-run Parse and show execution plan only
|
|
8266
|
+
--var <name=value> Override a DECLARE variable (repeatable; not for secrets)
|
|
8134
8267
|
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
8135
8268
|
(batch + json: prints one JSON envelope for the whole batch)
|
|
8136
8269
|
--max-records <n> Max records to fetch (default: 500)
|
|
@@ -8200,6 +8333,8 @@ function parseArgs(argv) {
|
|
|
8200
8333
|
password: null,
|
|
8201
8334
|
token: null,
|
|
8202
8335
|
tokenMap: {},
|
|
8336
|
+
// `__proto__` も有効な変数名なので prototype のない辞書で保持する。
|
|
8337
|
+
variables: /* @__PURE__ */ Object.create(null),
|
|
8203
8338
|
tokenFile: null,
|
|
8204
8339
|
app: null,
|
|
8205
8340
|
diagRecordId: null,
|
|
@@ -8294,6 +8429,19 @@ function parseArgs(argv) {
|
|
|
8294
8429
|
continue;
|
|
8295
8430
|
}
|
|
8296
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
|
+
}
|
|
8297
8445
|
if (a === "-e" || a === "--execute") {
|
|
8298
8446
|
out.executeSql = v ?? "";
|
|
8299
8447
|
i++;
|
|
@@ -8850,6 +8998,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
8850
8998
|
const argv = ["-e", sql];
|
|
8851
8999
|
if (dryRun) argv.push("--dry-run");
|
|
8852
9000
|
if (format) argv.push("--format", format);
|
|
9001
|
+
for (const [name, value] of Object.entries(base.variables)) argv.push("--var", `${name}=${value}`);
|
|
8853
9002
|
pushOpt(argv, "--config", base.configPath);
|
|
8854
9003
|
pushOpt(argv, "--profile", base.profile);
|
|
8855
9004
|
pushOpt(argv, "--base-url", base.baseUrl);
|
|
@@ -9473,6 +9622,10 @@ async function run() {
|
|
|
9473
9622
|
attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
|
|
9474
9623
|
};
|
|
9475
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
|
+
}
|
|
9476
9629
|
const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
|
|
9477
9630
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
9478
9631
|
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
@@ -9488,7 +9641,7 @@ async function run() {
|
|
|
9488
9641
|
if (args.dryRun) {
|
|
9489
9642
|
let plans;
|
|
9490
9643
|
try {
|
|
9491
|
-
plans = buildBatchExplainPlans(sql);
|
|
9644
|
+
plans = buildBatchExplainPlans(sql, args.variables);
|
|
9492
9645
|
} catch (err) {
|
|
9493
9646
|
const restored = sourceSql && sqlDiagnosticContext ? restoreSqlContextError(err, sourceSql, {
|
|
9494
9647
|
bindings: sqlDiagnosticContext.appBindingByMappedApp,
|
|
@@ -9805,6 +9958,7 @@ query=${label}`);
|
|
|
9805
9958
|
continueOnError: args.continueOnError,
|
|
9806
9959
|
tempTableMaxRows,
|
|
9807
9960
|
timeoutMs: timeout,
|
|
9961
|
+
variables: args.variables,
|
|
9808
9962
|
confirm: batchContainsDml ? async (count, operation) => {
|
|
9809
9963
|
if (count > dmlMaxRows) {
|
|
9810
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,20 +31530,48 @@ 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 */) {
|
|
31545
|
-
|
|
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
|
+
}
|
|
31559
|
+
this.advance();
|
|
31560
|
+
const query = this.parseSelect();
|
|
31561
|
+
this.expect(")" /* RPAREN */);
|
|
31562
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
31563
|
+
throw new ParseError(
|
|
31564
|
+
"\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306E\u5F8C\u306B\u7B97\u8853\u6F14\u7B97\u5B50\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
31565
|
+
this.peek()
|
|
31566
|
+
);
|
|
31567
|
+
}
|
|
31568
|
+
const hasWildcard = query.columns.some(
|
|
31569
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
31570
|
+
);
|
|
31571
|
+
if (!hasWildcard && query.columns.length !== 1) {
|
|
31572
|
+
throw new ParseError("scalar subquery in SET must return exactly 1 column.", tok);
|
|
31573
|
+
}
|
|
31574
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
31546
31575
|
}
|
|
31547
31576
|
if (tok.kind === "STRING" /* STRING */) {
|
|
31548
31577
|
this.advance();
|
|
@@ -31550,7 +31579,7 @@ var Parser = class {
|
|
|
31550
31579
|
}
|
|
31551
31580
|
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
31552
31581
|
throw new ParseError(
|
|
31553
|
-
|
|
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`,
|
|
31554
31583
|
tok
|
|
31555
31584
|
);
|
|
31556
31585
|
}
|
|
@@ -31558,22 +31587,22 @@ var Parser = class {
|
|
|
31558
31587
|
return this.parseSqlValue();
|
|
31559
31588
|
}
|
|
31560
31589
|
const expr = this.parseArithAddSub();
|
|
31561
|
-
this.rejectNonScalarExpr(expr, tok);
|
|
31590
|
+
this.rejectNonScalarExpr(expr, tok, context);
|
|
31562
31591
|
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
31563
|
-
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);
|
|
31564
31593
|
}
|
|
31565
|
-
rejectNonScalarExpr(node, tok) {
|
|
31594
|
+
rejectNonScalarExpr(node, tok, context) {
|
|
31566
31595
|
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
31567
31596
|
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
31568
|
-
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);
|
|
31569
31598
|
}
|
|
31570
31599
|
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
31571
|
-
this.rejectNonScalarExpr(node.left, tok);
|
|
31572
|
-
this.rejectNonScalarExpr(node.right, tok);
|
|
31600
|
+
this.rejectNonScalarExpr(node.left, tok, context);
|
|
31601
|
+
this.rejectNonScalarExpr(node.right, tok, context);
|
|
31573
31602
|
return;
|
|
31574
31603
|
}
|
|
31575
31604
|
if (node.type === "STRING_FUNC") {
|
|
31576
|
-
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
31605
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
|
|
31577
31606
|
}
|
|
31578
31607
|
}
|
|
31579
31608
|
// ----------------------------------------------------------
|
|
@@ -33199,7 +33228,7 @@ function isDmlType(type) {
|
|
|
33199
33228
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
33200
33229
|
}
|
|
33201
33230
|
function isReadOnlyType(type) {
|
|
33202
|
-
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";
|
|
33203
33232
|
}
|
|
33204
33233
|
function hasWhereClause(stmt) {
|
|
33205
33234
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -33261,8 +33290,9 @@ function analyzeBatch(statements) {
|
|
|
33261
33290
|
}
|
|
33262
33291
|
if (statements.length === 1) {
|
|
33263
33292
|
const t = statements[0].type;
|
|
33264
|
-
if (t === "SET_VARIABLE") {
|
|
33265
|
-
|
|
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);
|
|
33266
33296
|
}
|
|
33267
33297
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
33268
33298
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
@@ -33296,7 +33326,7 @@ function analyzeBatch(statements) {
|
|
|
33296
33326
|
}
|
|
33297
33327
|
def.referencedBy.push(index);
|
|
33298
33328
|
}
|
|
33299
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
33329
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
33300
33330
|
if (variableDefs.has(stmt.name)) {
|
|
33301
33331
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
33302
33332
|
}
|
|
@@ -33393,6 +33423,40 @@ function analyzeBatch(statements) {
|
|
|
33393
33423
|
};
|
|
33394
33424
|
}
|
|
33395
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
|
+
|
|
33396
33460
|
// src/core/scalarCompare.ts
|
|
33397
33461
|
function compareScalarValues(op, leftStr, rightStr) {
|
|
33398
33462
|
if (op === "=") return leftStr === rightStr;
|
|
@@ -35724,6 +35788,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
35724
35788
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35725
35789
|
case "SET_VARIABLE":
|
|
35726
35790
|
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
35791
|
+
case "DECLARE_VARIABLE":
|
|
35792
|
+
throw new Error("ArgumentError: DECLARE variable requires a batch.");
|
|
35727
35793
|
case "ASSERT":
|
|
35728
35794
|
return executeAssert(stmt, client, options, cacheContext);
|
|
35729
35795
|
}
|
|
@@ -35737,6 +35803,8 @@ var BatchTimeoutError = class extends Error {
|
|
|
35737
35803
|
async function executeBatch(sql, client, options = {}) {
|
|
35738
35804
|
const statements = parseSqlBatch(sql);
|
|
35739
35805
|
const analysis = analyzeBatch(statements);
|
|
35806
|
+
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
35807
|
+
const batchOptions = { ...options, variables: injectedVariables };
|
|
35740
35808
|
if (options.continueOnError && analysis.containsDml) {
|
|
35741
35809
|
throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
|
|
35742
35810
|
}
|
|
@@ -35781,16 +35849,16 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35781
35849
|
}
|
|
35782
35850
|
try {
|
|
35783
35851
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
35784
|
-
const userConfirm =
|
|
35852
|
+
const userConfirm = batchOptions.confirm;
|
|
35785
35853
|
const stmtOptions = userConfirm ? {
|
|
35786
|
-
...
|
|
35854
|
+
...batchOptions,
|
|
35787
35855
|
confirm: (count, operation) => userConfirm(count, operation, {
|
|
35788
35856
|
statementIndex: i,
|
|
35789
35857
|
statementCount: statements.length,
|
|
35790
35858
|
statementType: info.statementType,
|
|
35791
35859
|
targetAppId: info.targetAppId
|
|
35792
35860
|
})
|
|
35793
|
-
} :
|
|
35861
|
+
} : batchOptions;
|
|
35794
35862
|
const outcome = await runWithDeadline(
|
|
35795
35863
|
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
35796
35864
|
remaining
|
|
@@ -35803,7 +35871,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35803
35871
|
aborted2 = "timeout";
|
|
35804
35872
|
} else if (e instanceof AssertError) {
|
|
35805
35873
|
aborted2 = "assertion";
|
|
35806
|
-
} else if (info.statementType === "SET_VARIABLE") {
|
|
35874
|
+
} else if (info.statementType === "SET_VARIABLE" || info.statementType === "DECLARE_VARIABLE") {
|
|
35807
35875
|
aborted2 = "fail-fast";
|
|
35808
35876
|
} else if (!options.continueOnError) {
|
|
35809
35877
|
aborted2 = "fail-fast";
|
|
@@ -35821,7 +35889,36 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35821
35889
|
}
|
|
35822
35890
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
35823
35891
|
if (stmt.type === "SET_VARIABLE") {
|
|
35824
|
-
|
|
35892
|
+
const resolvedStmt2 = resolveVariableRefs(stmt, variables);
|
|
35893
|
+
if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
35894
|
+
try {
|
|
35895
|
+
const value = await evaluateScalarSubquery(
|
|
35896
|
+
resolvedStmt2.expr.query,
|
|
35897
|
+
client,
|
|
35898
|
+
options,
|
|
35899
|
+
cacheContext,
|
|
35900
|
+
tempTables
|
|
35901
|
+
);
|
|
35902
|
+
variables.set(stmt.name, { type: "string", value });
|
|
35903
|
+
} catch (e) {
|
|
35904
|
+
if (e instanceof ScalarSubqueryError) {
|
|
35905
|
+
throw new Error(`ArgumentError: ${e.message}`);
|
|
35906
|
+
}
|
|
35907
|
+
throw e;
|
|
35908
|
+
}
|
|
35909
|
+
} else {
|
|
35910
|
+
variables.set(stmt.name, evaluateScalarExpr(resolvedStmt2.expr));
|
|
35911
|
+
}
|
|
35912
|
+
return {};
|
|
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
|
+
}
|
|
35825
35922
|
return {};
|
|
35826
35923
|
}
|
|
35827
35924
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
@@ -35984,6 +36081,12 @@ var AssertError = class extends Error {
|
|
|
35984
36081
|
this.name = "AssertError";
|
|
35985
36082
|
}
|
|
35986
36083
|
};
|
|
36084
|
+
var ScalarSubqueryError = class extends Error {
|
|
36085
|
+
constructor(message) {
|
|
36086
|
+
super(message);
|
|
36087
|
+
this.name = "ScalarSubqueryError";
|
|
36088
|
+
}
|
|
36089
|
+
};
|
|
35987
36090
|
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
35988
36091
|
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
35989
36092
|
if (stmt.op === "BETWEEN") {
|
|
@@ -36017,25 +36120,38 @@ async function evalAssertOperand(operand, client, options, cacheContext, tempTab
|
|
|
36017
36120
|
case "ARITH":
|
|
36018
36121
|
return String(evalAssertArith(operand));
|
|
36019
36122
|
case "SCALAR_SUBQUERY": {
|
|
36020
|
-
|
|
36021
|
-
|
|
36022
|
-
|
|
36023
|
-
|
|
36024
|
-
|
|
36123
|
+
try {
|
|
36124
|
+
return await evaluateScalarSubquery(
|
|
36125
|
+
operand.query,
|
|
36126
|
+
client,
|
|
36127
|
+
options,
|
|
36128
|
+
cacheContext,
|
|
36129
|
+
tempTables
|
|
36025
36130
|
);
|
|
36131
|
+
} catch (e) {
|
|
36132
|
+
if (e instanceof ScalarSubqueryError) throw new AssertError(e.message);
|
|
36133
|
+
throw e;
|
|
36026
36134
|
}
|
|
36027
|
-
if (result.rowCount === 0) {
|
|
36028
|
-
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
36029
|
-
}
|
|
36030
|
-
if (result.rowCount > 1) {
|
|
36031
|
-
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
36032
|
-
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
36033
|
-
}
|
|
36034
|
-
const col = result.columns[0] ?? "";
|
|
36035
|
-
return result.rows[0]?.[col] ?? "";
|
|
36036
36135
|
}
|
|
36037
36136
|
}
|
|
36038
36137
|
}
|
|
36138
|
+
async function evaluateScalarSubquery(sourceQuery, client, options, cacheContext, tempTables) {
|
|
36139
|
+
const { query, probed } = withScalarProbeLimit(sourceQuery);
|
|
36140
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
36141
|
+
if (result.columns.length !== 1) {
|
|
36142
|
+
throw new ScalarSubqueryError(
|
|
36143
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
36144
|
+
);
|
|
36145
|
+
}
|
|
36146
|
+
if (result.rowCount === 0) {
|
|
36147
|
+
throw new ScalarSubqueryError("scalar subquery returned no rows (expected 1 row).");
|
|
36148
|
+
}
|
|
36149
|
+
if (result.rowCount > 1) {
|
|
36150
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
36151
|
+
throw new ScalarSubqueryError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
36152
|
+
}
|
|
36153
|
+
return result.rows[0]?.[result.columns[0]] ?? "";
|
|
36154
|
+
}
|
|
36039
36155
|
function withScalarProbeLimit(query) {
|
|
36040
36156
|
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
36041
36157
|
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
@@ -37530,20 +37646,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
37530
37646
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
37531
37647
|
return cache;
|
|
37532
37648
|
}
|
|
37533
|
-
function buildBatchExplainPlans(sql) {
|
|
37649
|
+
function buildBatchExplainPlans(sql, injectedVariables) {
|
|
37534
37650
|
const statements = parseSqlBatch(sql);
|
|
37535
37651
|
const analysis = analyzeBatch(statements);
|
|
37652
|
+
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
37536
37653
|
const variables = /* @__PURE__ */ new Map();
|
|
37537
37654
|
return {
|
|
37538
37655
|
statementCount: statements.length,
|
|
37539
37656
|
statements: statements.map((stmt, i) => {
|
|
37540
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
37657
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
|
|
37541
37658
|
const result = {
|
|
37542
37659
|
index: i,
|
|
37543
37660
|
type: analysis.statements[i].statementType,
|
|
37544
37661
|
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
37545
37662
|
};
|
|
37546
|
-
if (stmt.type === "SET_VARIABLE") {
|
|
37663
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
37547
37664
|
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
37548
37665
|
}
|
|
37549
37666
|
return result;
|
|
@@ -37566,11 +37683,26 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
37566
37683
|
];
|
|
37567
37684
|
}
|
|
37568
37685
|
if (stmt.type === "SET_VARIABLE") {
|
|
37686
|
+
if (stmt.expr.type === "SCALAR_SUBQUERY") {
|
|
37687
|
+
const subInfo = hasTempTableRef(stmt.expr.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
37688
|
+
return [
|
|
37689
|
+
`SET @${stmt.name} = (SELECT ...)`,
|
|
37690
|
+
" value: \u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u6642\u306B1\u56DE\u8A55\u4FA1\uFF081\u884C1\u5217\u30FB\u30D0\u30C3\u30C1\u5185\u5B9A\u6570\u30FB\u7D50\u679C\u30E1\u30BF\u30C7\u30FC\u30BF\u306B\u306F\u975E\u516C\u958B\uFF09",
|
|
37691
|
+
" subquery:",
|
|
37692
|
+
...buildPlanForBatchQuery(stmt.expr.query, subInfo).map((l) => ` ${l}`)
|
|
37693
|
+
];
|
|
37694
|
+
}
|
|
37569
37695
|
return [
|
|
37570
37696
|
`SET @${stmt.name} = <scalar expression>`,
|
|
37571
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"
|
|
37572
37698
|
];
|
|
37573
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
|
+
}
|
|
37574
37706
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
37575
37707
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
37576
37708
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -39549,6 +39681,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39549
39681
|
}
|
|
39550
39682
|
async function query(input, validated) {
|
|
39551
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
|
+
}
|
|
39552
39687
|
if (validation.batch) {
|
|
39553
39688
|
if (validation.containsDml) {
|
|
39554
39689
|
throw new Error("ArgumentError: batch contains DML statements. Use ksql_mutate.");
|
|
@@ -39575,7 +39710,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39575
39710
|
// バッチでは timeout を合計タイムアウトとして扱う(仕様 §5.7)。
|
|
39576
39711
|
// runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
|
|
39577
39712
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
39578
|
-
timeoutMs: runtime2.timeout
|
|
39713
|
+
timeoutMs: runtime2.timeout,
|
|
39714
|
+
variables: input.variables
|
|
39579
39715
|
});
|
|
39580
39716
|
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
39581
39717
|
}
|
|
@@ -39664,6 +39800,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39664
39800
|
tempTableMaxRows: runtime.tempTableMaxRows,
|
|
39665
39801
|
// 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
|
|
39666
39802
|
timeoutMs: runtime.timeout,
|
|
39803
|
+
variables: input.variables,
|
|
39667
39804
|
confirm: async (count, operation) => {
|
|
39668
39805
|
if (count > dmlMaxRows) {
|
|
39669
39806
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
@@ -39692,6 +39829,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
39692
39829
|
async function mutate(input, validated) {
|
|
39693
39830
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
39694
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
|
+
}
|
|
39695
39835
|
if (validation.batch) {
|
|
39696
39836
|
return mutateBatch(input, validation, dmlMaxRows);
|
|
39697
39837
|
}
|
|
@@ -39914,7 +40054,8 @@ var queryInputSchema = external_exports.object({
|
|
|
39914
40054
|
tempTableMaxRows,
|
|
39915
40055
|
timeout,
|
|
39916
40056
|
continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
|
|
39917
|
-
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()
|
|
39918
40059
|
});
|
|
39919
40060
|
var mutateInputSchema = external_exports.object({
|
|
39920
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;"),
|
|
@@ -39925,7 +40066,8 @@ var mutateInputSchema = external_exports.object({
|
|
|
39925
40066
|
fetchParallel,
|
|
39926
40067
|
tempTableMaxRows,
|
|
39927
40068
|
timeout,
|
|
39928
|
-
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()
|
|
39929
40071
|
});
|
|
39930
40072
|
var describeAppInputSchema = external_exports.object({
|
|
39931
40073
|
app: external_exports.number().int().positive().describe("kintone app ID to describe."),
|
|
@@ -40014,7 +40156,7 @@ Options:
|
|
|
40014
40156
|
-h, --help Show help
|
|
40015
40157
|
`);
|
|
40016
40158
|
}
|
|
40017
|
-
var SERVER_VERSION = true ? "2.
|
|
40159
|
+
var SERVER_VERSION = true ? "2.4.0" : "0.0.0-dev";
|
|
40018
40160
|
function createServer(args) {
|
|
40019
40161
|
const server = new McpServer({
|
|
40020
40162
|
name: "ksql-mcp",
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|