@rex0220/kintone-sql-tools 2.0.0 → 2.1.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/dist-cli/ksql.js +278 -35
- package/dist-mcp/ksql-mcp.js +279 -36
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -183,6 +183,7 @@ var Lexer = class {
|
|
|
183
183
|
if (opTok) return opTok;
|
|
184
184
|
if (isIdentStart(ch)) return this.readIdentOrKeyword(start);
|
|
185
185
|
if (ch === "#") return this.readHashIdent(start);
|
|
186
|
+
if (ch === "@") return this.readVariable(start);
|
|
186
187
|
throw new LexError(
|
|
187
188
|
`\u4E88\u671F\u3057\u306A\u3044\u6587\u5B57 \u300C${ch}\u300D \u3067\u3059`,
|
|
188
189
|
this.pos,
|
|
@@ -362,6 +363,23 @@ var Lexer = class {
|
|
|
362
363
|
}
|
|
363
364
|
return this.makeToken("IDENT" /* IDENT */, value, start);
|
|
364
365
|
}
|
|
366
|
+
/** バッチ変数: @[A-Za-z_][A-Za-z0-9_]{0,63} */
|
|
367
|
+
readVariable(start) {
|
|
368
|
+
this.pos++;
|
|
369
|
+
const first = this.input[this.pos] ?? "";
|
|
370
|
+
if (!/[A-Za-z_]/.test(first)) {
|
|
371
|
+
throw new LexError("\u300C@\u300D \u306E\u76F4\u5F8C\u306B\u306F\u82F1\u5B57\u307E\u305F\u306F _ \u3067\u59CB\u307E\u308B\u5909\u6570\u540D\u304C\u5FC5\u8981\u3067\u3059", start, this.input);
|
|
372
|
+
}
|
|
373
|
+
this.pos++;
|
|
374
|
+
while (this.pos < this.input.length && /[A-Za-z0-9_]/.test(this.input[this.pos])) {
|
|
375
|
+
this.pos++;
|
|
376
|
+
}
|
|
377
|
+
const nameLength = this.pos - start - 1;
|
|
378
|
+
if (nameLength > 64) {
|
|
379
|
+
throw new LexError("\u5909\u6570\u540D\u306F 64 \u6587\u5B57\u4EE5\u5185\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", start, this.input);
|
|
380
|
+
}
|
|
381
|
+
return this.makeToken("VARIABLE" /* VARIABLE */, this.input.slice(start, this.pos), start);
|
|
382
|
+
}
|
|
365
383
|
// ----------------------------------------------------------
|
|
366
384
|
// 空白・コメントをスキップ
|
|
367
385
|
// ----------------------------------------------------------
|
|
@@ -573,6 +591,8 @@ var Parser = class {
|
|
|
573
591
|
return this.parseDescribe();
|
|
574
592
|
case "EXPLAIN" /* EXPLAIN */:
|
|
575
593
|
return this.parseExplain();
|
|
594
|
+
case "SET" /* SET */:
|
|
595
|
+
return this.parseSetVariable();
|
|
576
596
|
case "ASSERT" /* ASSERT */:
|
|
577
597
|
return this.parseAssert();
|
|
578
598
|
case "IDENT" /* IDENT */: {
|
|
@@ -585,11 +605,65 @@ var Parser = class {
|
|
|
585
605
|
break;
|
|
586
606
|
}
|
|
587
607
|
throw new ParseError(
|
|
588
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
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",
|
|
589
609
|
tok
|
|
590
610
|
);
|
|
591
611
|
}
|
|
592
612
|
// ----------------------------------------------------------
|
|
613
|
+
// SET @name = <ScalarExpr>
|
|
614
|
+
// ----------------------------------------------------------
|
|
615
|
+
parseSetVariable() {
|
|
616
|
+
this.expect("SET" /* SET */);
|
|
617
|
+
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
618
|
+
this.expect("=" /* EQ */);
|
|
619
|
+
const expr = this.parseScalarExpr();
|
|
620
|
+
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
621
|
+
}
|
|
622
|
+
/** SET RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
|
|
623
|
+
parseScalarExpr() {
|
|
624
|
+
const tok = this.peek();
|
|
625
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
626
|
+
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
627
|
+
}
|
|
628
|
+
if (tok.kind === "NULL" /* NULL */) {
|
|
629
|
+
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
630
|
+
}
|
|
631
|
+
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
632
|
+
throw new ParseError("SET \u306E\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u4EE3\u5165\u306F Phase 1b \u3067\u5BFE\u5FDC\u4E88\u5B9A\u3067\u3059", tok);
|
|
633
|
+
}
|
|
634
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
635
|
+
this.advance();
|
|
636
|
+
return { type: "STRING", value: tok.value };
|
|
637
|
+
}
|
|
638
|
+
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
639
|
+
throw new ParseError(
|
|
640
|
+
"SET \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
|
+
tok
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */) {
|
|
645
|
+
return this.parseSqlValue();
|
|
646
|
+
}
|
|
647
|
+
const expr = this.parseArithAddSub();
|
|
648
|
+
this.rejectNonScalarExpr(expr, tok);
|
|
649
|
+
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
650
|
+
throw new ParseError("SET \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
|
+
}
|
|
652
|
+
rejectNonScalarExpr(node, tok) {
|
|
653
|
+
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
654
|
+
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
655
|
+
throw new ParseError("SET \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
|
+
}
|
|
657
|
+
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
658
|
+
this.rejectNonScalarExpr(node.left, tok);
|
|
659
|
+
this.rejectNonScalarExpr(node.right, tok);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (node.type === "STRING_FUNC") {
|
|
663
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// ----------------------------------------------------------
|
|
593
667
|
// CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
|
|
594
668
|
// CREATE / DROP / TEMP / TABLE は予約語にしない(ソフトキーワード)
|
|
595
669
|
// ----------------------------------------------------------
|
|
@@ -733,6 +807,10 @@ var Parser = class {
|
|
|
733
807
|
/** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
|
|
734
808
|
parseAssertOperand() {
|
|
735
809
|
const tok = this.peek();
|
|
810
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
811
|
+
this.advance();
|
|
812
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
813
|
+
}
|
|
736
814
|
if (tok.kind === "STRING" /* STRING */) {
|
|
737
815
|
this.advance();
|
|
738
816
|
return { type: "STRING", value: tok.value };
|
|
@@ -1640,6 +1718,10 @@ var Parser = class {
|
|
|
1640
1718
|
// 右辺の値
|
|
1641
1719
|
parseSqlValue() {
|
|
1642
1720
|
const tok = this.peek();
|
|
1721
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
1722
|
+
this.advance();
|
|
1723
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
1724
|
+
}
|
|
1643
1725
|
if (tok.kind === "STRING" /* STRING */) {
|
|
1644
1726
|
this.advance();
|
|
1645
1727
|
return { type: "STRING", value: tok.value };
|
|
@@ -1702,8 +1784,10 @@ var Parser = class {
|
|
|
1702
1784
|
values.push({ type: "STRING", value: tok.value });
|
|
1703
1785
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
1704
1786
|
values.push({ type: "NUMBER", value: Number(tok.value) });
|
|
1787
|
+
} else if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
1788
|
+
values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
|
|
1705
1789
|
} else {
|
|
1706
|
-
throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u307E\u305F\u306F\u6570\
|
|
1790
|
+
throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u3001\u6570\u5024\u3001\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1707
1791
|
}
|
|
1708
1792
|
} while (this.consume("," /* COMMA */));
|
|
1709
1793
|
return values;
|
|
@@ -1935,6 +2019,7 @@ var Parser = class {
|
|
|
1935
2019
|
*/
|
|
1936
2020
|
parseAssignmentValue() {
|
|
1937
2021
|
const tok = this.peek();
|
|
2022
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) return this.parseSqlValue();
|
|
1938
2023
|
if (tok.kind === "STRING" /* STRING */) return this.parseSqlValue();
|
|
1939
2024
|
if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */ || tok.kind === "LOGINUSER" /* LOGINUSER */) return this.parseSqlValue();
|
|
1940
2025
|
if (tok.kind === "IN" /* IN */) return this.parseSqlValue();
|
|
@@ -2196,7 +2281,7 @@ function isDmlType(type) {
|
|
|
2196
2281
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
2197
2282
|
}
|
|
2198
2283
|
function isReadOnlyType(type) {
|
|
2199
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "ASSERT";
|
|
2284
|
+
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";
|
|
2200
2285
|
}
|
|
2201
2286
|
function hasWhereClause(stmt) {
|
|
2202
2287
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -2229,6 +2314,7 @@ function collectDmlTargetFields(stmt) {
|
|
|
2229
2314
|
|
|
2230
2315
|
// src/core/batch.ts
|
|
2231
2316
|
var MAX_TEMP_TABLES = 16;
|
|
2317
|
+
var MAX_BATCH_VARIABLES = 64;
|
|
2232
2318
|
var BatchAnalysisError = class extends Error {
|
|
2233
2319
|
constructor(message, statementIndex) {
|
|
2234
2320
|
super(message);
|
|
@@ -2249,12 +2335,29 @@ function collectRefs(node, tempRefs, appIds) {
|
|
|
2249
2335
|
for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
|
|
2250
2336
|
}
|
|
2251
2337
|
}
|
|
2338
|
+
function collectVariableRefs(node, refs) {
|
|
2339
|
+
if (Array.isArray(node)) {
|
|
2340
|
+
for (const v of node) collectVariableRefs(v, refs);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
if (node !== null && typeof node === "object") {
|
|
2344
|
+
const obj = node;
|
|
2345
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
2346
|
+
refs.add(obj["name"]);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2252
2352
|
function analyzeBatch(statements) {
|
|
2253
2353
|
if (statements.length === 0) {
|
|
2254
2354
|
throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
|
|
2255
2355
|
}
|
|
2256
2356
|
if (statements.length === 1) {
|
|
2257
2357
|
const t = statements[0].type;
|
|
2358
|
+
if (t === "SET_VARIABLE") {
|
|
2359
|
+
throw new BatchAnalysisError("ArgumentError: SET variable requires a batch.", 0);
|
|
2360
|
+
}
|
|
2258
2361
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
2259
2362
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
2260
2363
|
throw new BatchAnalysisError(
|
|
@@ -2266,6 +2369,8 @@ function analyzeBatch(statements) {
|
|
|
2266
2369
|
const defined = /* @__PURE__ */ new Map();
|
|
2267
2370
|
const createdOrder = [];
|
|
2268
2371
|
const results = [];
|
|
2372
|
+
const variableDefs = /* @__PURE__ */ new Map();
|
|
2373
|
+
const variableOrder = [];
|
|
2269
2374
|
statements.forEach((stmt, index) => {
|
|
2270
2375
|
const statementType = getStatementType(stmt);
|
|
2271
2376
|
const created = [];
|
|
@@ -2273,6 +2378,31 @@ function analyzeBatch(statements) {
|
|
|
2273
2378
|
const refs = /* @__PURE__ */ new Set();
|
|
2274
2379
|
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
2275
2380
|
const dependsOn = /* @__PURE__ */ new Set();
|
|
2381
|
+
const variableRefs = /* @__PURE__ */ new Set();
|
|
2382
|
+
collectVariableRefs(stmt, variableRefs);
|
|
2383
|
+
for (const name of variableRefs) {
|
|
2384
|
+
const def = variableDefs.get(name);
|
|
2385
|
+
if (def === void 0) {
|
|
2386
|
+
throw new BatchAnalysisError(
|
|
2387
|
+
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
2388
|
+
index
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
def.referencedBy.push(index);
|
|
2392
|
+
}
|
|
2393
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
2394
|
+
if (variableDefs.has(stmt.name)) {
|
|
2395
|
+
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
2396
|
+
}
|
|
2397
|
+
variableDefs.set(stmt.name, { index, referencedBy: [] });
|
|
2398
|
+
variableOrder.push(stmt.name);
|
|
2399
|
+
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
2400
|
+
throw new BatchAnalysisError(
|
|
2401
|
+
`ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
|
|
2402
|
+
index
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2276
2406
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
2277
2407
|
collectRefs(stmt.query, refs, stmtAppIds);
|
|
2278
2408
|
} else if (stmt.type === "DROP_TEMP_TABLE") {
|
|
@@ -2342,11 +2472,17 @@ function analyzeBatch(statements) {
|
|
|
2342
2472
|
});
|
|
2343
2473
|
});
|
|
2344
2474
|
const containsDml = results.some((r) => r.isDml);
|
|
2475
|
+
const variables = variableOrder.map((name) => ({
|
|
2476
|
+
name,
|
|
2477
|
+
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
2478
|
+
}));
|
|
2345
2479
|
return {
|
|
2346
2480
|
statementCount: statements.length,
|
|
2347
2481
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
2348
2482
|
containsDml,
|
|
2349
2483
|
tempTables: createdOrder,
|
|
2484
|
+
variables,
|
|
2485
|
+
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
2350
2486
|
statements: results
|
|
2351
2487
|
};
|
|
2352
2488
|
}
|
|
@@ -2517,6 +2653,8 @@ function convertField(field) {
|
|
|
2517
2653
|
}
|
|
2518
2654
|
function convertValue(value, op) {
|
|
2519
2655
|
switch (value.type) {
|
|
2656
|
+
case "VARIABLE":
|
|
2657
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
2520
2658
|
case "STRING":
|
|
2521
2659
|
return convertString(value);
|
|
2522
2660
|
case "NUMBER":
|
|
@@ -2547,11 +2685,18 @@ function convertInList(v, op) {
|
|
|
2547
2685
|
if (op !== "IN" && op !== "NOT_IN") {
|
|
2548
2686
|
throw new KintoneQueryError("IN_LIST \u306F IN / NOT IN \u6F14\u7B97\u5B50\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
|
|
2549
2687
|
}
|
|
2688
|
+
assertResolvedInListValues(v.values);
|
|
2550
2689
|
const values = v.values.map(
|
|
2551
2690
|
(item) => item.type === "STRING" ? convertString(item) : String(item.value)
|
|
2552
2691
|
).join(",");
|
|
2553
2692
|
return `(${values})`;
|
|
2554
2693
|
}
|
|
2694
|
+
function assertResolvedInListValues(values) {
|
|
2695
|
+
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
2696
|
+
if (unresolved?.type === "VARIABLE") {
|
|
2697
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${unresolved.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2555
2700
|
function quoteIdentifier(name) {
|
|
2556
2701
|
if (/^[\w$\u3000-\u9FFF]+$/u.test(name)) {
|
|
2557
2702
|
return name;
|
|
@@ -3295,15 +3440,17 @@ function evalBinary(expr, row) {
|
|
|
3295
3440
|
return evalOp(expr.op, left, expr.right, row);
|
|
3296
3441
|
}
|
|
3297
3442
|
function evalOp(op, leftStr, right, row) {
|
|
3298
|
-
if (op === "IN") {
|
|
3299
|
-
if (right.type === "IN_LIST")
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
if (right.type === "
|
|
3305
|
-
|
|
3306
|
-
|
|
3443
|
+
if (op === "IN" || op === "NOT_IN") {
|
|
3444
|
+
if (right.type === "IN_LIST") {
|
|
3445
|
+
assertResolvedInListValues2(right.values);
|
|
3446
|
+
const contains = right.values.some((v) => leftStr === String(v.value));
|
|
3447
|
+
return op === "IN" ? contains : !contains;
|
|
3448
|
+
}
|
|
3449
|
+
if (right.type === "SUBQUERY_IN_LIST") {
|
|
3450
|
+
const contains = right.resolved.has(leftStr);
|
|
3451
|
+
return op === "IN" ? contains : !contains;
|
|
3452
|
+
}
|
|
3453
|
+
return op === "NOT_IN";
|
|
3307
3454
|
}
|
|
3308
3455
|
if (op === "LIKE") {
|
|
3309
3456
|
const pattern = resolveValue(right, row);
|
|
@@ -3333,6 +3480,12 @@ function evalOp(op, leftStr, right, row) {
|
|
|
3333
3480
|
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
3334
3481
|
}
|
|
3335
3482
|
}
|
|
3483
|
+
function assertResolvedInListValues2(values) {
|
|
3484
|
+
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
3485
|
+
if (unresolved?.type === "VARIABLE") {
|
|
3486
|
+
throw new Error(`ParseError: unresolved batch variable @${unresolved.name}.`);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3336
3489
|
function evalNullCheck(expr, row) {
|
|
3337
3490
|
const val = resolveField(expr.field, row);
|
|
3338
3491
|
return expr.not ? val !== "" : val === "";
|
|
@@ -3352,6 +3505,8 @@ function resolveField(field, row) {
|
|
|
3352
3505
|
}
|
|
3353
3506
|
function resolveValue(value, row) {
|
|
3354
3507
|
switch (value.type) {
|
|
3508
|
+
case "VARIABLE":
|
|
3509
|
+
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
3355
3510
|
case "STRING":
|
|
3356
3511
|
return value.value;
|
|
3357
3512
|
case "NUMBER":
|
|
@@ -3715,6 +3870,8 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
3715
3870
|
}
|
|
3716
3871
|
function toKintoneValue(value, fieldType) {
|
|
3717
3872
|
switch (value.type) {
|
|
3873
|
+
case "VARIABLE":
|
|
3874
|
+
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
3718
3875
|
case "STRING":
|
|
3719
3876
|
return convertString2(value.value, fieldType);
|
|
3720
3877
|
case "NUMBER":
|
|
@@ -4590,6 +4747,10 @@ async function executeStatement(sql, client, options) {
|
|
|
4590
4747
|
return executeParsedStatement(stmt, client, options, cacheContext);
|
|
4591
4748
|
}
|
|
4592
4749
|
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
4750
|
+
const unresolved = findVariableRef(stmt);
|
|
4751
|
+
if (unresolved !== null) {
|
|
4752
|
+
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
4753
|
+
}
|
|
4593
4754
|
switch (stmt.type) {
|
|
4594
4755
|
case "SELECT":
|
|
4595
4756
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -4622,6 +4783,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4622
4783
|
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4623
4784
|
case "DROP_TEMP_TABLE":
|
|
4624
4785
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4786
|
+
case "SET_VARIABLE":
|
|
4787
|
+
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
4625
4788
|
case "ASSERT":
|
|
4626
4789
|
return executeAssert(stmt, client, options, cacheContext);
|
|
4627
4790
|
}
|
|
@@ -4652,6 +4815,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4652
4815
|
const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
|
|
4653
4816
|
const cacheContext = options.cacheContext ?? "default";
|
|
4654
4817
|
const tempTables = /* @__PURE__ */ new Map();
|
|
4818
|
+
const variables = /* @__PURE__ */ new Map();
|
|
4655
4819
|
const results = [];
|
|
4656
4820
|
const failed = /* @__PURE__ */ new Set();
|
|
4657
4821
|
let aborted = null;
|
|
@@ -4689,7 +4853,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4689
4853
|
})
|
|
4690
4854
|
} : options;
|
|
4691
4855
|
const outcome = await runWithDeadline(
|
|
4692
|
-
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
|
|
4856
|
+
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
4693
4857
|
remaining
|
|
4694
4858
|
);
|
|
4695
4859
|
results.push({ ...base, status: "success", ...outcome });
|
|
@@ -4700,6 +4864,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4700
4864
|
aborted = "timeout";
|
|
4701
4865
|
} else if (e instanceof AssertError) {
|
|
4702
4866
|
aborted = "assertion";
|
|
4867
|
+
} else if (info.statementType === "SET_VARIABLE") {
|
|
4868
|
+
aborted = "fail-fast";
|
|
4703
4869
|
} else if (!options.continueOnError) {
|
|
4704
4870
|
aborted = "fail-fast";
|
|
4705
4871
|
}
|
|
@@ -4714,44 +4880,49 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4714
4880
|
metrics
|
|
4715
4881
|
};
|
|
4716
4882
|
}
|
|
4717
|
-
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
|
|
4718
|
-
if (stmt.type === "
|
|
4883
|
+
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
4884
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
4885
|
+
variables.set(stmt.name, evaluateScalarExpr(stmt.expr));
|
|
4886
|
+
return {};
|
|
4887
|
+
}
|
|
4888
|
+
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
4889
|
+
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
4719
4890
|
const materializeOptions = {
|
|
4720
4891
|
...options,
|
|
4721
4892
|
maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
4722
4893
|
onLimitReached: "error"
|
|
4723
4894
|
};
|
|
4724
|
-
const result = await runSelectLike(
|
|
4725
|
-
tempTables.set(
|
|
4726
|
-
return { tempTable:
|
|
4895
|
+
const result = await runSelectLike(resolvedStmt.query, client, materializeOptions, cacheContext, tempTables);
|
|
4896
|
+
tempTables.set(resolvedStmt.name, result.rows);
|
|
4897
|
+
return { tempTable: resolvedStmt.name, rowCount: result.rows.length };
|
|
4727
4898
|
}
|
|
4728
4899
|
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
4729
4900
|
tempTables.delete(stmt.name);
|
|
4730
4901
|
return { tempTable: stmt.name };
|
|
4731
4902
|
}
|
|
4732
4903
|
if (stmt.type === "EXPLAIN") {
|
|
4733
|
-
return { result: await executeParsedStatement(
|
|
4904
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
4734
4905
|
}
|
|
4735
|
-
if (
|
|
4736
|
-
await executeAssert(
|
|
4906
|
+
if (resolvedStmt.type === "ASSERT") {
|
|
4907
|
+
await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
|
|
4737
4908
|
return {};
|
|
4738
4909
|
}
|
|
4739
4910
|
if (info.tempTablesReferenced.length > 0) {
|
|
4740
|
-
if (
|
|
4741
|
-
return { result: await executeQueryWithCte(
|
|
4911
|
+
if (resolvedStmt.type === "SELECT" || resolvedStmt.type === "UNION") {
|
|
4912
|
+
return { result: await executeQueryWithCte(resolvedStmt, client, options, tempTables, cacheContext) };
|
|
4742
4913
|
}
|
|
4743
|
-
if (
|
|
4744
|
-
return { result: await executeWith(
|
|
4914
|
+
if (resolvedStmt.type === "WITH") {
|
|
4915
|
+
return { result: await executeWith(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4745
4916
|
}
|
|
4746
|
-
if (
|
|
4747
|
-
return { result: await executeInsertSelect(
|
|
4917
|
+
if (resolvedStmt.type === "INSERT_SELECT") {
|
|
4918
|
+
return { result: await executeInsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4748
4919
|
}
|
|
4749
|
-
if (
|
|
4750
|
-
return { result: await executeUpsertSelect(
|
|
4920
|
+
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
4921
|
+
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4751
4922
|
}
|
|
4752
4923
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
4753
4924
|
}
|
|
4754
|
-
return { result: await executeParsedStatement(
|
|
4925
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
4755
4926
|
}
|
|
4756
4927
|
async function runSelectLike(query, client, options, cacheContext, tempTables) {
|
|
4757
4928
|
if (query.type === "WITH") {
|
|
@@ -4812,6 +4983,62 @@ function parseSqlBatch(sql) {
|
|
|
4812
4983
|
const tokens = new Lexer(sql).tokenize();
|
|
4813
4984
|
return new Parser(tokens).parseStatements();
|
|
4814
4985
|
}
|
|
4986
|
+
function evaluateScalarExpr(expr) {
|
|
4987
|
+
switch (expr.type) {
|
|
4988
|
+
case "STRING":
|
|
4989
|
+
return { type: "string", value: expr.value };
|
|
4990
|
+
case "NUMBER":
|
|
4991
|
+
return { type: "number", value: expr.value };
|
|
4992
|
+
case "KINTONE_FUNC":
|
|
4993
|
+
return { type: "string", value: resolveKintoneFunc(expr.name) };
|
|
4994
|
+
case "STRING_FUNC":
|
|
4995
|
+
return { type: "string", value: evalStringFunc(expr, {}) };
|
|
4996
|
+
case "ARITH": {
|
|
4997
|
+
const value = evalArithExpr(expr, {});
|
|
4998
|
+
if (!Number.isFinite(value)) {
|
|
4999
|
+
throw new Error("ArgumentError: SET scalar arithmetic produced a non-finite number.");
|
|
5000
|
+
}
|
|
5001
|
+
return { type: "number", value };
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
function resolveVariableRefs(node, variables) {
|
|
5006
|
+
if (Array.isArray(node)) {
|
|
5007
|
+
return node.map((v) => resolveVariableRefs(v, variables));
|
|
5008
|
+
}
|
|
5009
|
+
if (node !== null && typeof node === "object") {
|
|
5010
|
+
const obj = node;
|
|
5011
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
5012
|
+
const value = variables.get(obj["name"]);
|
|
5013
|
+
if (value === void 0) {
|
|
5014
|
+
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
5015
|
+
}
|
|
5016
|
+
return value.type === "number" ? { type: "NUMBER", value: value.value } : { type: "STRING", value: value.value };
|
|
5017
|
+
}
|
|
5018
|
+
return Object.fromEntries(
|
|
5019
|
+
Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
5022
|
+
return node;
|
|
5023
|
+
}
|
|
5024
|
+
function findVariableRef(node) {
|
|
5025
|
+
if (Array.isArray(node)) {
|
|
5026
|
+
for (const value of node) {
|
|
5027
|
+
const found = findVariableRef(value);
|
|
5028
|
+
if (found !== null) return found;
|
|
5029
|
+
}
|
|
5030
|
+
return null;
|
|
5031
|
+
}
|
|
5032
|
+
if (node !== null && typeof node === "object") {
|
|
5033
|
+
const obj = node;
|
|
5034
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
|
|
5035
|
+
for (const value of Object.values(obj)) {
|
|
5036
|
+
const found = findVariableRef(value);
|
|
5037
|
+
if (found !== null) return found;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
return null;
|
|
5041
|
+
}
|
|
4815
5042
|
var AssertError = class extends Error {
|
|
4816
5043
|
constructor(message) {
|
|
4817
5044
|
super(`AssertError: ${message}`);
|
|
@@ -4842,6 +5069,8 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
4842
5069
|
}
|
|
4843
5070
|
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
4844
5071
|
switch (operand.type) {
|
|
5072
|
+
case "VARIABLE":
|
|
5073
|
+
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
4845
5074
|
case "NUMBER":
|
|
4846
5075
|
return String(operand.value);
|
|
4847
5076
|
case "STRING":
|
|
@@ -6343,13 +6572,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
6343
6572
|
function buildBatchExplainPlans(sql) {
|
|
6344
6573
|
const statements = parseSqlBatch(sql);
|
|
6345
6574
|
const analysis = analyzeBatch(statements);
|
|
6575
|
+
const variables = /* @__PURE__ */ new Map();
|
|
6346
6576
|
return {
|
|
6347
6577
|
statementCount: statements.length,
|
|
6348
|
-
statements: statements.map((stmt, i) =>
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6578
|
+
statements: statements.map((stmt, i) => {
|
|
6579
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
6580
|
+
const result = {
|
|
6581
|
+
index: i,
|
|
6582
|
+
type: analysis.statements[i].statementType,
|
|
6583
|
+
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
6584
|
+
};
|
|
6585
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
6586
|
+
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
6587
|
+
}
|
|
6588
|
+
return result;
|
|
6589
|
+
})
|
|
6353
6590
|
};
|
|
6354
6591
|
}
|
|
6355
6592
|
function buildBatchStatementPlan(stmt, info) {
|
|
@@ -6367,6 +6604,12 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
6367
6604
|
" \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
|
|
6368
6605
|
];
|
|
6369
6606
|
}
|
|
6607
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
6608
|
+
return [
|
|
6609
|
+
`SET @${stmt.name} = <scalar expression>`,
|
|
6610
|
+
" 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"
|
|
6611
|
+
];
|
|
6612
|
+
}
|
|
6370
6613
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
6371
6614
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
6372
6615
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -31096,6 +31096,7 @@ var Lexer = class {
|
|
|
31096
31096
|
if (opTok) return opTok;
|
|
31097
31097
|
if (isIdentStart(ch)) return this.readIdentOrKeyword(start);
|
|
31098
31098
|
if (ch === "#") return this.readHashIdent(start);
|
|
31099
|
+
if (ch === "@") return this.readVariable(start);
|
|
31099
31100
|
throw new LexError(
|
|
31100
31101
|
`\u4E88\u671F\u3057\u306A\u3044\u6587\u5B57 \u300C${ch}\u300D \u3067\u3059`,
|
|
31101
31102
|
this.pos,
|
|
@@ -31275,6 +31276,23 @@ var Lexer = class {
|
|
|
31275
31276
|
}
|
|
31276
31277
|
return this.makeToken("IDENT" /* IDENT */, value, start);
|
|
31277
31278
|
}
|
|
31279
|
+
/** バッチ変数: @[A-Za-z_][A-Za-z0-9_]{0,63} */
|
|
31280
|
+
readVariable(start) {
|
|
31281
|
+
this.pos++;
|
|
31282
|
+
const first = this.input[this.pos] ?? "";
|
|
31283
|
+
if (!/[A-Za-z_]/.test(first)) {
|
|
31284
|
+
throw new LexError("\u300C@\u300D \u306E\u76F4\u5F8C\u306B\u306F\u82F1\u5B57\u307E\u305F\u306F _ \u3067\u59CB\u307E\u308B\u5909\u6570\u540D\u304C\u5FC5\u8981\u3067\u3059", start, this.input);
|
|
31285
|
+
}
|
|
31286
|
+
this.pos++;
|
|
31287
|
+
while (this.pos < this.input.length && /[A-Za-z0-9_]/.test(this.input[this.pos])) {
|
|
31288
|
+
this.pos++;
|
|
31289
|
+
}
|
|
31290
|
+
const nameLength = this.pos - start - 1;
|
|
31291
|
+
if (nameLength > 64) {
|
|
31292
|
+
throw new LexError("\u5909\u6570\u540D\u306F 64 \u6587\u5B57\u4EE5\u5185\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", start, this.input);
|
|
31293
|
+
}
|
|
31294
|
+
return this.makeToken("VARIABLE" /* VARIABLE */, this.input.slice(start, this.pos), start);
|
|
31295
|
+
}
|
|
31278
31296
|
// ----------------------------------------------------------
|
|
31279
31297
|
// 空白・コメントをスキップ
|
|
31280
31298
|
// ----------------------------------------------------------
|
|
@@ -31486,6 +31504,8 @@ var Parser = class {
|
|
|
31486
31504
|
return this.parseDescribe();
|
|
31487
31505
|
case "EXPLAIN" /* EXPLAIN */:
|
|
31488
31506
|
return this.parseExplain();
|
|
31507
|
+
case "SET" /* SET */:
|
|
31508
|
+
return this.parseSetVariable();
|
|
31489
31509
|
case "ASSERT" /* ASSERT */:
|
|
31490
31510
|
return this.parseAssert();
|
|
31491
31511
|
case "IDENT" /* IDENT */: {
|
|
@@ -31498,11 +31518,65 @@ var Parser = class {
|
|
|
31498
31518
|
break;
|
|
31499
31519
|
}
|
|
31500
31520
|
throw new ParseError(
|
|
31501
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
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",
|
|
31502
31522
|
tok
|
|
31503
31523
|
);
|
|
31504
31524
|
}
|
|
31505
31525
|
// ----------------------------------------------------------
|
|
31526
|
+
// SET @name = <ScalarExpr>
|
|
31527
|
+
// ----------------------------------------------------------
|
|
31528
|
+
parseSetVariable() {
|
|
31529
|
+
this.expect("SET" /* SET */);
|
|
31530
|
+
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
31531
|
+
this.expect("=" /* EQ */);
|
|
31532
|
+
const expr = this.parseScalarExpr();
|
|
31533
|
+
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
31534
|
+
}
|
|
31535
|
+
/** SET RHS 専用。既存式パーサーで構文を読み、フィールド参照を明示的に拒否する。 */
|
|
31536
|
+
parseScalarExpr() {
|
|
31537
|
+
const tok = this.peek();
|
|
31538
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
31539
|
+
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067\u306F\u4ED6\u306E\u5909\u6570\u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
31540
|
+
}
|
|
31541
|
+
if (tok.kind === "NULL" /* NULL */) {
|
|
31542
|
+
throw new ParseError("SET \u306E\u53F3\u8FBA\u3067 NULL \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08Phase 1a\uFF09", tok);
|
|
31543
|
+
}
|
|
31544
|
+
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
31545
|
+
throw new ParseError("SET \u306E\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u4EE3\u5165\u306F Phase 1b \u3067\u5BFE\u5FDC\u4E88\u5B9A\u3067\u3059", tok);
|
|
31546
|
+
}
|
|
31547
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
31548
|
+
this.advance();
|
|
31549
|
+
return { type: "STRING", value: tok.value };
|
|
31550
|
+
}
|
|
31551
|
+
if (tok.kind === "LOGINUSER" /* LOGINUSER */) {
|
|
31552
|
+
throw new ParseError(
|
|
31553
|
+
"SET \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
|
+
tok
|
|
31555
|
+
);
|
|
31556
|
+
}
|
|
31557
|
+
if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */) {
|
|
31558
|
+
return this.parseSqlValue();
|
|
31559
|
+
}
|
|
31560
|
+
const expr = this.parseArithAddSub();
|
|
31561
|
+
this.rejectNonScalarExpr(expr, tok);
|
|
31562
|
+
if (expr.type === "NUMBER" || expr.type === "STRING_FUNC" || expr.type === "ARITH") return expr;
|
|
31563
|
+
throw new ParseError("SET \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
|
+
}
|
|
31565
|
+
rejectNonScalarExpr(node, tok) {
|
|
31566
|
+
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
31567
|
+
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
31568
|
+
throw new ParseError("SET \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
|
+
}
|
|
31570
|
+
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
31571
|
+
this.rejectNonScalarExpr(node.left, tok);
|
|
31572
|
+
this.rejectNonScalarExpr(node.right, tok);
|
|
31573
|
+
return;
|
|
31574
|
+
}
|
|
31575
|
+
if (node.type === "STRING_FUNC") {
|
|
31576
|
+
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok);
|
|
31577
|
+
}
|
|
31578
|
+
}
|
|
31579
|
+
// ----------------------------------------------------------
|
|
31506
31580
|
// CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
|
|
31507
31581
|
// CREATE / DROP / TEMP / TABLE は予約語にしない(ソフトキーワード)
|
|
31508
31582
|
// ----------------------------------------------------------
|
|
@@ -31646,6 +31720,10 @@ var Parser = class {
|
|
|
31646
31720
|
/** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
|
|
31647
31721
|
parseAssertOperand() {
|
|
31648
31722
|
const tok = this.peek();
|
|
31723
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
31724
|
+
this.advance();
|
|
31725
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
31726
|
+
}
|
|
31649
31727
|
if (tok.kind === "STRING" /* STRING */) {
|
|
31650
31728
|
this.advance();
|
|
31651
31729
|
return { type: "STRING", value: tok.value };
|
|
@@ -32553,6 +32631,10 @@ var Parser = class {
|
|
|
32553
32631
|
// 右辺の値
|
|
32554
32632
|
parseSqlValue() {
|
|
32555
32633
|
const tok = this.peek();
|
|
32634
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
32635
|
+
this.advance();
|
|
32636
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
32637
|
+
}
|
|
32556
32638
|
if (tok.kind === "STRING" /* STRING */) {
|
|
32557
32639
|
this.advance();
|
|
32558
32640
|
return { type: "STRING", value: tok.value };
|
|
@@ -32615,8 +32697,10 @@ var Parser = class {
|
|
|
32615
32697
|
values.push({ type: "STRING", value: tok.value });
|
|
32616
32698
|
} else if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
32617
32699
|
values.push({ type: "NUMBER", value: Number(tok.value) });
|
|
32700
|
+
} else if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
32701
|
+
values.push({ type: "VARIABLE", name: tok.value.slice(1).toLowerCase() });
|
|
32618
32702
|
} else {
|
|
32619
|
-
throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u307E\u305F\u306F\u6570\
|
|
32703
|
+
throw new ParseError("IN \u30EA\u30B9\u30C8\u306B\u306F\u6587\u5B57\u5217\u3001\u6570\u5024\u3001\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
32620
32704
|
}
|
|
32621
32705
|
} while (this.consume("," /* COMMA */));
|
|
32622
32706
|
return values;
|
|
@@ -32848,6 +32932,7 @@ var Parser = class {
|
|
|
32848
32932
|
*/
|
|
32849
32933
|
parseAssignmentValue() {
|
|
32850
32934
|
const tok = this.peek();
|
|
32935
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) return this.parseSqlValue();
|
|
32851
32936
|
if (tok.kind === "STRING" /* STRING */) return this.parseSqlValue();
|
|
32852
32937
|
if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */ || tok.kind === "LOGINUSER" /* LOGINUSER */) return this.parseSqlValue();
|
|
32853
32938
|
if (tok.kind === "IN" /* IN */) return this.parseSqlValue();
|
|
@@ -33109,7 +33194,7 @@ function isDmlType(type) {
|
|
|
33109
33194
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
33110
33195
|
}
|
|
33111
33196
|
function isReadOnlyType(type) {
|
|
33112
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "ASSERT";
|
|
33197
|
+
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";
|
|
33113
33198
|
}
|
|
33114
33199
|
function hasWhereClause(stmt) {
|
|
33115
33200
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -33130,6 +33215,7 @@ function getInsertValuesCount(stmt) {
|
|
|
33130
33215
|
|
|
33131
33216
|
// src/core/batch.ts
|
|
33132
33217
|
var MAX_TEMP_TABLES = 16;
|
|
33218
|
+
var MAX_BATCH_VARIABLES = 64;
|
|
33133
33219
|
var BatchAnalysisError = class extends Error {
|
|
33134
33220
|
constructor(message, statementIndex) {
|
|
33135
33221
|
super(message);
|
|
@@ -33150,12 +33236,29 @@ function collectRefs(node, tempRefs, appIds) {
|
|
|
33150
33236
|
for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
|
|
33151
33237
|
}
|
|
33152
33238
|
}
|
|
33239
|
+
function collectVariableRefs(node, refs) {
|
|
33240
|
+
if (Array.isArray(node)) {
|
|
33241
|
+
for (const v of node) collectVariableRefs(v, refs);
|
|
33242
|
+
return;
|
|
33243
|
+
}
|
|
33244
|
+
if (node !== null && typeof node === "object") {
|
|
33245
|
+
const obj = node;
|
|
33246
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
33247
|
+
refs.add(obj["name"]);
|
|
33248
|
+
return;
|
|
33249
|
+
}
|
|
33250
|
+
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
33251
|
+
}
|
|
33252
|
+
}
|
|
33153
33253
|
function analyzeBatch(statements) {
|
|
33154
33254
|
if (statements.length === 0) {
|
|
33155
33255
|
throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
|
|
33156
33256
|
}
|
|
33157
33257
|
if (statements.length === 1) {
|
|
33158
33258
|
const t = statements[0].type;
|
|
33259
|
+
if (t === "SET_VARIABLE") {
|
|
33260
|
+
throw new BatchAnalysisError("ArgumentError: SET variable requires a batch.", 0);
|
|
33261
|
+
}
|
|
33159
33262
|
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
33160
33263
|
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
33161
33264
|
throw new BatchAnalysisError(
|
|
@@ -33167,6 +33270,8 @@ function analyzeBatch(statements) {
|
|
|
33167
33270
|
const defined = /* @__PURE__ */ new Map();
|
|
33168
33271
|
const createdOrder = [];
|
|
33169
33272
|
const results = [];
|
|
33273
|
+
const variableDefs = /* @__PURE__ */ new Map();
|
|
33274
|
+
const variableOrder = [];
|
|
33170
33275
|
statements.forEach((stmt, index) => {
|
|
33171
33276
|
const statementType = getStatementType(stmt);
|
|
33172
33277
|
const created = [];
|
|
@@ -33174,6 +33279,31 @@ function analyzeBatch(statements) {
|
|
|
33174
33279
|
const refs = /* @__PURE__ */ new Set();
|
|
33175
33280
|
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
33176
33281
|
const dependsOn = /* @__PURE__ */ new Set();
|
|
33282
|
+
const variableRefs = /* @__PURE__ */ new Set();
|
|
33283
|
+
collectVariableRefs(stmt, variableRefs);
|
|
33284
|
+
for (const name of variableRefs) {
|
|
33285
|
+
const def = variableDefs.get(name);
|
|
33286
|
+
if (def === void 0) {
|
|
33287
|
+
throw new BatchAnalysisError(
|
|
33288
|
+
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
33289
|
+
index
|
|
33290
|
+
);
|
|
33291
|
+
}
|
|
33292
|
+
def.referencedBy.push(index);
|
|
33293
|
+
}
|
|
33294
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
33295
|
+
if (variableDefs.has(stmt.name)) {
|
|
33296
|
+
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
33297
|
+
}
|
|
33298
|
+
variableDefs.set(stmt.name, { index, referencedBy: [] });
|
|
33299
|
+
variableOrder.push(stmt.name);
|
|
33300
|
+
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
33301
|
+
throw new BatchAnalysisError(
|
|
33302
|
+
`ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
|
|
33303
|
+
index
|
|
33304
|
+
);
|
|
33305
|
+
}
|
|
33306
|
+
}
|
|
33177
33307
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
33178
33308
|
collectRefs(stmt.query, refs, stmtAppIds);
|
|
33179
33309
|
} else if (stmt.type === "DROP_TEMP_TABLE") {
|
|
@@ -33243,11 +33373,17 @@ function analyzeBatch(statements) {
|
|
|
33243
33373
|
});
|
|
33244
33374
|
});
|
|
33245
33375
|
const containsDml = results.some((r) => r.isDml);
|
|
33376
|
+
const variables = variableOrder.map((name) => ({
|
|
33377
|
+
name,
|
|
33378
|
+
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
33379
|
+
}));
|
|
33246
33380
|
return {
|
|
33247
33381
|
statementCount: statements.length,
|
|
33248
33382
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
33249
33383
|
containsDml,
|
|
33250
33384
|
tempTables: createdOrder,
|
|
33385
|
+
variables,
|
|
33386
|
+
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
33251
33387
|
statements: results
|
|
33252
33388
|
};
|
|
33253
33389
|
}
|
|
@@ -33418,6 +33554,8 @@ function convertField(field) {
|
|
|
33418
33554
|
}
|
|
33419
33555
|
function convertValue(value, op) {
|
|
33420
33556
|
switch (value.type) {
|
|
33557
|
+
case "VARIABLE":
|
|
33558
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
33421
33559
|
case "STRING":
|
|
33422
33560
|
return convertString(value);
|
|
33423
33561
|
case "NUMBER":
|
|
@@ -33448,11 +33586,18 @@ function convertInList(v, op) {
|
|
|
33448
33586
|
if (op !== "IN" && op !== "NOT_IN") {
|
|
33449
33587
|
throw new KintoneQueryError("IN_LIST \u306F IN / NOT IN \u6F14\u7B97\u5B50\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
|
|
33450
33588
|
}
|
|
33589
|
+
assertResolvedInListValues(v.values);
|
|
33451
33590
|
const values = v.values.map(
|
|
33452
33591
|
(item) => item.type === "STRING" ? convertString(item) : String(item.value)
|
|
33453
33592
|
).join(",");
|
|
33454
33593
|
return `(${values})`;
|
|
33455
33594
|
}
|
|
33595
|
+
function assertResolvedInListValues(values) {
|
|
33596
|
+
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
33597
|
+
if (unresolved?.type === "VARIABLE") {
|
|
33598
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${unresolved.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
33599
|
+
}
|
|
33600
|
+
}
|
|
33456
33601
|
function quoteIdentifier(name) {
|
|
33457
33602
|
if (/^[\w$\u3000-\u9FFF]+$/u.test(name)) {
|
|
33458
33603
|
return name;
|
|
@@ -34196,15 +34341,17 @@ function evalBinary(expr, row) {
|
|
|
34196
34341
|
return evalOp(expr.op, left, expr.right, row);
|
|
34197
34342
|
}
|
|
34198
34343
|
function evalOp(op, leftStr, right, row) {
|
|
34199
|
-
if (op === "IN") {
|
|
34200
|
-
if (right.type === "IN_LIST")
|
|
34201
|
-
|
|
34202
|
-
|
|
34203
|
-
|
|
34204
|
-
|
|
34205
|
-
if (right.type === "
|
|
34206
|
-
|
|
34207
|
-
|
|
34344
|
+
if (op === "IN" || op === "NOT_IN") {
|
|
34345
|
+
if (right.type === "IN_LIST") {
|
|
34346
|
+
assertResolvedInListValues2(right.values);
|
|
34347
|
+
const contains = right.values.some((v) => leftStr === String(v.value));
|
|
34348
|
+
return op === "IN" ? contains : !contains;
|
|
34349
|
+
}
|
|
34350
|
+
if (right.type === "SUBQUERY_IN_LIST") {
|
|
34351
|
+
const contains = right.resolved.has(leftStr);
|
|
34352
|
+
return op === "IN" ? contains : !contains;
|
|
34353
|
+
}
|
|
34354
|
+
return op === "NOT_IN";
|
|
34208
34355
|
}
|
|
34209
34356
|
if (op === "LIKE") {
|
|
34210
34357
|
const pattern = resolveValue(right, row);
|
|
@@ -34234,6 +34381,12 @@ function evalOp(op, leftStr, right, row) {
|
|
|
34234
34381
|
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
34235
34382
|
}
|
|
34236
34383
|
}
|
|
34384
|
+
function assertResolvedInListValues2(values) {
|
|
34385
|
+
const unresolved = values.find((item) => item.type === "VARIABLE");
|
|
34386
|
+
if (unresolved?.type === "VARIABLE") {
|
|
34387
|
+
throw new Error(`ParseError: unresolved batch variable @${unresolved.name}.`);
|
|
34388
|
+
}
|
|
34389
|
+
}
|
|
34237
34390
|
function evalNullCheck(expr, row) {
|
|
34238
34391
|
const val = resolveField(expr.field, row);
|
|
34239
34392
|
return expr.not ? val !== "" : val === "";
|
|
@@ -34253,6 +34406,8 @@ function resolveField(field, row) {
|
|
|
34253
34406
|
}
|
|
34254
34407
|
function resolveValue(value, row) {
|
|
34255
34408
|
switch (value.type) {
|
|
34409
|
+
case "VARIABLE":
|
|
34410
|
+
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
34256
34411
|
case "STRING":
|
|
34257
34412
|
return value.value;
|
|
34258
34413
|
case "NUMBER":
|
|
@@ -34616,6 +34771,8 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
34616
34771
|
}
|
|
34617
34772
|
function toKintoneValue(value, fieldType) {
|
|
34618
34773
|
switch (value.type) {
|
|
34774
|
+
case "VARIABLE":
|
|
34775
|
+
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
34619
34776
|
case "STRING":
|
|
34620
34777
|
return convertString2(value.value, fieldType);
|
|
34621
34778
|
case "NUMBER":
|
|
@@ -35491,6 +35648,10 @@ async function executeStatement(sql, client, options) {
|
|
|
35491
35648
|
return executeParsedStatement(stmt, client, options, cacheContext);
|
|
35492
35649
|
}
|
|
35493
35650
|
async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
35651
|
+
const unresolved = findVariableRef(stmt);
|
|
35652
|
+
if (unresolved !== null) {
|
|
35653
|
+
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
35654
|
+
}
|
|
35494
35655
|
switch (stmt.type) {
|
|
35495
35656
|
case "SELECT":
|
|
35496
35657
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -35523,6 +35684,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
35523
35684
|
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35524
35685
|
case "DROP_TEMP_TABLE":
|
|
35525
35686
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35687
|
+
case "SET_VARIABLE":
|
|
35688
|
+
throw new Error("ArgumentError: SET variable requires a batch.");
|
|
35526
35689
|
case "ASSERT":
|
|
35527
35690
|
return executeAssert(stmt, client, options, cacheContext);
|
|
35528
35691
|
}
|
|
@@ -35553,6 +35716,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35553
35716
|
const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
|
|
35554
35717
|
const cacheContext = options.cacheContext ?? "default";
|
|
35555
35718
|
const tempTables = /* @__PURE__ */ new Map();
|
|
35719
|
+
const variables = /* @__PURE__ */ new Map();
|
|
35556
35720
|
const results = [];
|
|
35557
35721
|
const failed = /* @__PURE__ */ new Set();
|
|
35558
35722
|
let aborted2 = null;
|
|
@@ -35590,7 +35754,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35590
35754
|
})
|
|
35591
35755
|
} : options;
|
|
35592
35756
|
const outcome = await runWithDeadline(
|
|
35593
|
-
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
|
|
35757
|
+
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
35594
35758
|
remaining
|
|
35595
35759
|
);
|
|
35596
35760
|
results.push({ ...base, status: "success", ...outcome });
|
|
@@ -35601,6 +35765,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35601
35765
|
aborted2 = "timeout";
|
|
35602
35766
|
} else if (e instanceof AssertError) {
|
|
35603
35767
|
aborted2 = "assertion";
|
|
35768
|
+
} else if (info.statementType === "SET_VARIABLE") {
|
|
35769
|
+
aborted2 = "fail-fast";
|
|
35604
35770
|
} else if (!options.continueOnError) {
|
|
35605
35771
|
aborted2 = "fail-fast";
|
|
35606
35772
|
}
|
|
@@ -35615,44 +35781,49 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35615
35781
|
metrics
|
|
35616
35782
|
};
|
|
35617
35783
|
}
|
|
35618
|
-
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
|
|
35619
|
-
if (stmt.type === "
|
|
35784
|
+
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
35785
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
35786
|
+
variables.set(stmt.name, evaluateScalarExpr(stmt.expr));
|
|
35787
|
+
return {};
|
|
35788
|
+
}
|
|
35789
|
+
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
35790
|
+
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
35620
35791
|
const materializeOptions = {
|
|
35621
35792
|
...options,
|
|
35622
35793
|
maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
35623
35794
|
onLimitReached: "error"
|
|
35624
35795
|
};
|
|
35625
|
-
const result = await runSelectLike(
|
|
35626
|
-
tempTables.set(
|
|
35627
|
-
return { tempTable:
|
|
35796
|
+
const result = await runSelectLike(resolvedStmt.query, client, materializeOptions, cacheContext, tempTables);
|
|
35797
|
+
tempTables.set(resolvedStmt.name, result.rows);
|
|
35798
|
+
return { tempTable: resolvedStmt.name, rowCount: result.rows.length };
|
|
35628
35799
|
}
|
|
35629
35800
|
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
35630
35801
|
tempTables.delete(stmt.name);
|
|
35631
35802
|
return { tempTable: stmt.name };
|
|
35632
35803
|
}
|
|
35633
35804
|
if (stmt.type === "EXPLAIN") {
|
|
35634
|
-
return { result: await executeParsedStatement(
|
|
35805
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
35635
35806
|
}
|
|
35636
|
-
if (
|
|
35637
|
-
await executeAssert(
|
|
35807
|
+
if (resolvedStmt.type === "ASSERT") {
|
|
35808
|
+
await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
|
|
35638
35809
|
return {};
|
|
35639
35810
|
}
|
|
35640
35811
|
if (info.tempTablesReferenced.length > 0) {
|
|
35641
|
-
if (
|
|
35642
|
-
return { result: await executeQueryWithCte(
|
|
35812
|
+
if (resolvedStmt.type === "SELECT" || resolvedStmt.type === "UNION") {
|
|
35813
|
+
return { result: await executeQueryWithCte(resolvedStmt, client, options, tempTables, cacheContext) };
|
|
35643
35814
|
}
|
|
35644
|
-
if (
|
|
35645
|
-
return { result: await executeWith(
|
|
35815
|
+
if (resolvedStmt.type === "WITH") {
|
|
35816
|
+
return { result: await executeWith(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
35646
35817
|
}
|
|
35647
|
-
if (
|
|
35648
|
-
return { result: await executeInsertSelect(
|
|
35818
|
+
if (resolvedStmt.type === "INSERT_SELECT") {
|
|
35819
|
+
return { result: await executeInsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
35649
35820
|
}
|
|
35650
|
-
if (
|
|
35651
|
-
return { result: await executeUpsertSelect(
|
|
35821
|
+
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
35822
|
+
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
35652
35823
|
}
|
|
35653
35824
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
35654
35825
|
}
|
|
35655
|
-
return { result: await executeParsedStatement(
|
|
35826
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
35656
35827
|
}
|
|
35657
35828
|
async function runSelectLike(query, client, options, cacheContext, tempTables) {
|
|
35658
35829
|
if (query.type === "WITH") {
|
|
@@ -35713,6 +35884,62 @@ function parseSqlBatch(sql) {
|
|
|
35713
35884
|
const tokens = new Lexer(sql).tokenize();
|
|
35714
35885
|
return new Parser(tokens).parseStatements();
|
|
35715
35886
|
}
|
|
35887
|
+
function evaluateScalarExpr(expr) {
|
|
35888
|
+
switch (expr.type) {
|
|
35889
|
+
case "STRING":
|
|
35890
|
+
return { type: "string", value: expr.value };
|
|
35891
|
+
case "NUMBER":
|
|
35892
|
+
return { type: "number", value: expr.value };
|
|
35893
|
+
case "KINTONE_FUNC":
|
|
35894
|
+
return { type: "string", value: resolveKintoneFunc(expr.name) };
|
|
35895
|
+
case "STRING_FUNC":
|
|
35896
|
+
return { type: "string", value: evalStringFunc(expr, {}) };
|
|
35897
|
+
case "ARITH": {
|
|
35898
|
+
const value = evalArithExpr(expr, {});
|
|
35899
|
+
if (!Number.isFinite(value)) {
|
|
35900
|
+
throw new Error("ArgumentError: SET scalar arithmetic produced a non-finite number.");
|
|
35901
|
+
}
|
|
35902
|
+
return { type: "number", value };
|
|
35903
|
+
}
|
|
35904
|
+
}
|
|
35905
|
+
}
|
|
35906
|
+
function resolveVariableRefs(node, variables) {
|
|
35907
|
+
if (Array.isArray(node)) {
|
|
35908
|
+
return node.map((v) => resolveVariableRefs(v, variables));
|
|
35909
|
+
}
|
|
35910
|
+
if (node !== null && typeof node === "object") {
|
|
35911
|
+
const obj = node;
|
|
35912
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
35913
|
+
const value = variables.get(obj["name"]);
|
|
35914
|
+
if (value === void 0) {
|
|
35915
|
+
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
35916
|
+
}
|
|
35917
|
+
return value.type === "number" ? { type: "NUMBER", value: value.value } : { type: "STRING", value: value.value };
|
|
35918
|
+
}
|
|
35919
|
+
return Object.fromEntries(
|
|
35920
|
+
Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
|
|
35921
|
+
);
|
|
35922
|
+
}
|
|
35923
|
+
return node;
|
|
35924
|
+
}
|
|
35925
|
+
function findVariableRef(node) {
|
|
35926
|
+
if (Array.isArray(node)) {
|
|
35927
|
+
for (const value of node) {
|
|
35928
|
+
const found = findVariableRef(value);
|
|
35929
|
+
if (found !== null) return found;
|
|
35930
|
+
}
|
|
35931
|
+
return null;
|
|
35932
|
+
}
|
|
35933
|
+
if (node !== null && typeof node === "object") {
|
|
35934
|
+
const obj = node;
|
|
35935
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
|
|
35936
|
+
for (const value of Object.values(obj)) {
|
|
35937
|
+
const found = findVariableRef(value);
|
|
35938
|
+
if (found !== null) return found;
|
|
35939
|
+
}
|
|
35940
|
+
}
|
|
35941
|
+
return null;
|
|
35942
|
+
}
|
|
35716
35943
|
var AssertError = class extends Error {
|
|
35717
35944
|
constructor(message) {
|
|
35718
35945
|
super(`AssertError: ${message}`);
|
|
@@ -35743,6 +35970,8 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
35743
35970
|
}
|
|
35744
35971
|
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
35745
35972
|
switch (operand.type) {
|
|
35973
|
+
case "VARIABLE":
|
|
35974
|
+
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
35746
35975
|
case "NUMBER":
|
|
35747
35976
|
return String(operand.value);
|
|
35748
35977
|
case "STRING":
|
|
@@ -37244,13 +37473,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
37244
37473
|
function buildBatchExplainPlans(sql) {
|
|
37245
37474
|
const statements = parseSqlBatch(sql);
|
|
37246
37475
|
const analysis = analyzeBatch(statements);
|
|
37476
|
+
const variables = /* @__PURE__ */ new Map();
|
|
37247
37477
|
return {
|
|
37248
37478
|
statementCount: statements.length,
|
|
37249
|
-
statements: statements.map((stmt, i) =>
|
|
37250
|
-
|
|
37251
|
-
|
|
37252
|
-
|
|
37253
|
-
|
|
37479
|
+
statements: statements.map((stmt, i) => {
|
|
37480
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt : resolveVariableRefs(stmt, variables);
|
|
37481
|
+
const result = {
|
|
37482
|
+
index: i,
|
|
37483
|
+
type: analysis.statements[i].statementType,
|
|
37484
|
+
plan: buildBatchStatementPlan(planStmt, analysis.statements[i])
|
|
37485
|
+
};
|
|
37486
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
37487
|
+
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
37488
|
+
}
|
|
37489
|
+
return result;
|
|
37490
|
+
})
|
|
37254
37491
|
};
|
|
37255
37492
|
}
|
|
37256
37493
|
function buildBatchStatementPlan(stmt, info) {
|
|
@@ -37268,6 +37505,12 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
37268
37505
|
" \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
|
|
37269
37506
|
];
|
|
37270
37507
|
}
|
|
37508
|
+
if (stmt.type === "SET_VARIABLE") {
|
|
37509
|
+
return [
|
|
37510
|
+
`SET @${stmt.name} = <scalar expression>`,
|
|
37511
|
+
" 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"
|
|
37512
|
+
];
|
|
37513
|
+
}
|
|
37271
37514
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
37272
37515
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
37273
37516
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -39703,7 +39946,7 @@ Options:
|
|
|
39703
39946
|
-h, --help Show help
|
|
39704
39947
|
`);
|
|
39705
39948
|
}
|
|
39706
|
-
var SERVER_VERSION = true ? "2.
|
|
39949
|
+
var SERVER_VERSION = true ? "2.1.0" : "0.0.0-dev";
|
|
39707
39950
|
function createServer(args) {
|
|
39708
39951
|
const server = new McpServer({
|
|
39709
39952
|
name: "ksql-mcp",
|
package/dist-mcpb/ksql-mcp.mcpb
CHANGED
|
Binary file
|