@rex0220/kintone-sql-tools 1.13.2 → 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 +329 -44
- package/dist-mcp/ksql-mcp.js +330 -45
- 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
|
}
|
|
@@ -2407,6 +2543,29 @@ function negateOp(op) {
|
|
|
2407
2543
|
}
|
|
2408
2544
|
}
|
|
2409
2545
|
|
|
2546
|
+
// src/core/like.ts
|
|
2547
|
+
function likePatternHasWildcard(pattern) {
|
|
2548
|
+
return pattern.includes("%") || pattern.includes("_");
|
|
2549
|
+
}
|
|
2550
|
+
function isLike(where) {
|
|
2551
|
+
return where.type === "BINARY" && (where.op === "LIKE" || where.op === "NOT_LIKE");
|
|
2552
|
+
}
|
|
2553
|
+
function whereHasLike(where) {
|
|
2554
|
+
if (where === null) return false;
|
|
2555
|
+
if (isLike(where)) return true;
|
|
2556
|
+
switch (where.type) {
|
|
2557
|
+
case "LOGICAL":
|
|
2558
|
+
return whereHasLike(where.left) || whereHasLike(where.right);
|
|
2559
|
+
case "NOT":
|
|
2560
|
+
case "GROUP":
|
|
2561
|
+
return whereHasLike(where.expr);
|
|
2562
|
+
case "BINARY":
|
|
2563
|
+
case "NULL_CHECK":
|
|
2564
|
+
case "EXISTS":
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2410
2569
|
// src/converter/whereToKintone.ts
|
|
2411
2570
|
function whereToKintone(expr) {
|
|
2412
2571
|
switch (expr.type) {
|
|
@@ -2425,6 +2584,11 @@ function whereToKintone(expr) {
|
|
|
2425
2584
|
}
|
|
2426
2585
|
}
|
|
2427
2586
|
function convertBinary(expr) {
|
|
2587
|
+
if (isLike(expr)) {
|
|
2588
|
+
throw new KintoneQueryError(
|
|
2589
|
+
"LIKE / NOT LIKE \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\uFF08\u5E38\u306B JS \u8A55\u4FA1\u304C\u5FC5\u8981\u3067\u3059\uFF09"
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2428
2592
|
const left = convertField(expr.left);
|
|
2429
2593
|
const op = convertOp(expr.op);
|
|
2430
2594
|
const right = convertValue(expr.right, expr.op);
|
|
@@ -2489,6 +2653,8 @@ function convertField(field) {
|
|
|
2489
2653
|
}
|
|
2490
2654
|
function convertValue(value, op) {
|
|
2491
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`);
|
|
2492
2658
|
case "STRING":
|
|
2493
2659
|
return convertString(value);
|
|
2494
2660
|
case "NUMBER":
|
|
@@ -2519,11 +2685,18 @@ function convertInList(v, op) {
|
|
|
2519
2685
|
if (op !== "IN" && op !== "NOT_IN") {
|
|
2520
2686
|
throw new KintoneQueryError("IN_LIST \u306F IN / NOT IN \u6F14\u7B97\u5B50\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
|
|
2521
2687
|
}
|
|
2688
|
+
assertResolvedInListValues(v.values);
|
|
2522
2689
|
const values = v.values.map(
|
|
2523
2690
|
(item) => item.type === "STRING" ? convertString(item) : String(item.value)
|
|
2524
2691
|
).join(",");
|
|
2525
2692
|
return `(${values})`;
|
|
2526
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
|
+
}
|
|
2527
2700
|
function quoteIdentifier(name) {
|
|
2528
2701
|
if (/^[\w$\u3000-\u9FFF]+$/u.test(name)) {
|
|
2529
2702
|
return name;
|
|
@@ -2550,22 +2723,22 @@ function resolveSelectMode(stmt) {
|
|
|
2550
2723
|
if (stmt.columns.some(
|
|
2551
2724
|
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "SCALAR_SUBQUERY_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr(c.expr)
|
|
2552
2725
|
)) return "FULL_SCAN";
|
|
2553
|
-
if (
|
|
2726
|
+
if (whereRequiresJsEval(stmt.where)) return "FULL_SCAN";
|
|
2554
2727
|
if (stmt.orderBy.some((o) => o.key.type !== "FIELD_NAME")) return "FULL_SCAN";
|
|
2555
2728
|
return "SIMPLE";
|
|
2556
2729
|
}
|
|
2557
|
-
function
|
|
2730
|
+
function whereRequiresJsEval(where) {
|
|
2558
2731
|
if (where === null) return false;
|
|
2559
2732
|
switch (where.type) {
|
|
2560
2733
|
case "BINARY":
|
|
2561
|
-
return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY";
|
|
2734
|
+
return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY" || isLike(where);
|
|
2562
2735
|
case "NULL_CHECK":
|
|
2563
2736
|
return isFunc(where.field);
|
|
2564
2737
|
case "LOGICAL":
|
|
2565
|
-
return
|
|
2738
|
+
return whereRequiresJsEval(where.left) || whereRequiresJsEval(where.right);
|
|
2566
2739
|
case "NOT":
|
|
2567
2740
|
case "GROUP":
|
|
2568
|
-
return
|
|
2741
|
+
return whereRequiresJsEval(where.expr);
|
|
2569
2742
|
case "EXISTS":
|
|
2570
2743
|
return true;
|
|
2571
2744
|
}
|
|
@@ -2601,7 +2774,7 @@ function selectToKintoneParams(stmt) {
|
|
|
2601
2774
|
}
|
|
2602
2775
|
function selectToFetchAllParams(stmt, appId) {
|
|
2603
2776
|
const queryParts = [];
|
|
2604
|
-
if (stmt.where !== null && stmt.joins.length === 0 && !
|
|
2777
|
+
if (stmt.where !== null && stmt.joins.length === 0 && !whereRequiresJsEval(stmt.where)) {
|
|
2605
2778
|
queryParts.push(whereToKintone(stmt.where));
|
|
2606
2779
|
}
|
|
2607
2780
|
return {
|
|
@@ -3267,15 +3440,17 @@ function evalBinary(expr, row) {
|
|
|
3267
3440
|
return evalOp(expr.op, left, expr.right, row);
|
|
3268
3441
|
}
|
|
3269
3442
|
function evalOp(op, leftStr, right, row) {
|
|
3270
|
-
if (op === "IN") {
|
|
3271
|
-
if (right.type === "IN_LIST")
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
if (right.type === "
|
|
3277
|
-
|
|
3278
|
-
|
|
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";
|
|
3279
3454
|
}
|
|
3280
3455
|
if (op === "LIKE") {
|
|
3281
3456
|
const pattern = resolveValue(right, row);
|
|
@@ -3305,6 +3480,12 @@ function evalOp(op, leftStr, right, row) {
|
|
|
3305
3480
|
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
3306
3481
|
}
|
|
3307
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
|
+
}
|
|
3308
3489
|
function evalNullCheck(expr, row) {
|
|
3309
3490
|
const val = resolveField(expr.field, row);
|
|
3310
3491
|
return expr.not ? val !== "" : val === "";
|
|
@@ -3324,6 +3505,8 @@ function resolveField(field, row) {
|
|
|
3324
3505
|
}
|
|
3325
3506
|
function resolveValue(value, row) {
|
|
3326
3507
|
switch (value.type) {
|
|
3508
|
+
case "VARIABLE":
|
|
3509
|
+
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
3327
3510
|
case "STRING":
|
|
3328
3511
|
return value.value;
|
|
3329
3512
|
case "NUMBER":
|
|
@@ -3339,6 +3522,8 @@ function resolveValue(value, row) {
|
|
|
3339
3522
|
case "SCALAR_SUBQUERY":
|
|
3340
3523
|
return value.resolved;
|
|
3341
3524
|
case "ARITH_VALUE":
|
|
3525
|
+
if (value.expr.type === "FIELD_REF") return resolveFieldRef(row, value.expr.field);
|
|
3526
|
+
if (value.expr.type === "STRING_FUNC") return evalStringFunc(value.expr, row);
|
|
3342
3527
|
return String(evalArithExpr(value.expr, row));
|
|
3343
3528
|
case "CASE_VALUE":
|
|
3344
3529
|
return evalCaseWhen(value.expr, row);
|
|
@@ -3382,7 +3567,7 @@ function resolveKintoneFunc(name) {
|
|
|
3382
3567
|
var likeRegexCache = /* @__PURE__ */ new Map();
|
|
3383
3568
|
var LIKE_REGEX_CACHE_MAX = 200;
|
|
3384
3569
|
function matchLike(value, pattern) {
|
|
3385
|
-
if (!
|
|
3570
|
+
if (!likePatternHasWildcard(pattern)) {
|
|
3386
3571
|
return value.includes(pattern);
|
|
3387
3572
|
}
|
|
3388
3573
|
let regex = likeRegexCache.get(pattern);
|
|
@@ -3407,6 +3592,12 @@ function matchLike(value, pattern) {
|
|
|
3407
3592
|
}
|
|
3408
3593
|
|
|
3409
3594
|
// src/converter/dmlToKintone.ts
|
|
3595
|
+
function assertDmlWhereIsSafe(where) {
|
|
3596
|
+
if (!whereHasLike(where)) return;
|
|
3597
|
+
throw new DmlConvertError(
|
|
3598
|
+
"UPDATE / DELETE \u306E WHERE \u306B LIKE / NOT LIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002LIKE \u306F kSQL \u306E\u610F\u5473\u8AD6\u306B\u5F93\u3063\u3066 JS \u3067\u8A55\u4FA1\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u89AA\u30EC\u30B3\u30FC\u30C9 DML \u306B\u306F JS \u8A55\u4FA1\u7D4C\u8DEF\u304C\u306A\u3044\u305F\u3081\u3001\u5B89\u5168\u4E0A\u62D2\u5426\u3057\u307E\u3057\u305F\u3002SELECT \u3067\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u3092\u78BA\u8A8D\u3057\u3001IN \u307E\u305F\u306F\u5B8C\u5168\u4E00\u81F4\u3067\u5BFE\u8C61\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3410
3601
|
var USER_TYPES = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
3411
3602
|
var ARRAY_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
3412
3603
|
function insertToPostBatches(stmt, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
@@ -3431,6 +3622,7 @@ function buildInsertRecord(fields, row, fieldTypes) {
|
|
|
3431
3622
|
return record;
|
|
3432
3623
|
}
|
|
3433
3624
|
function updateToGetQuery(stmt) {
|
|
3625
|
+
assertDmlWhereIsSafe(stmt.where);
|
|
3434
3626
|
return {
|
|
3435
3627
|
app: stmt.appId,
|
|
3436
3628
|
query: whereToKintone(stmt.where),
|
|
@@ -3459,6 +3651,7 @@ function hasArithAssignment(stmt) {
|
|
|
3459
3651
|
);
|
|
3460
3652
|
}
|
|
3461
3653
|
function updateToGetQueryForArith(stmt) {
|
|
3654
|
+
assertDmlWhereIsSafe(stmt.where);
|
|
3462
3655
|
const refFields = /* @__PURE__ */ new Set();
|
|
3463
3656
|
for (const { value } of stmt.assignments) {
|
|
3464
3657
|
if (value.type === "ARITH") {
|
|
@@ -3604,6 +3797,7 @@ function resolveArithOperand(operand, raw) {
|
|
|
3604
3797
|
return n;
|
|
3605
3798
|
}
|
|
3606
3799
|
function deleteToGetQuery(stmt) {
|
|
3800
|
+
assertDmlWhereIsSafe(stmt.where);
|
|
3607
3801
|
return {
|
|
3608
3802
|
app: stmt.appId,
|
|
3609
3803
|
query: whereToKintone(stmt.where),
|
|
@@ -3676,6 +3870,8 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
3676
3870
|
}
|
|
3677
3871
|
function toKintoneValue(value, fieldType) {
|
|
3678
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`);
|
|
3679
3875
|
case "STRING":
|
|
3680
3876
|
return convertString2(value.value, fieldType);
|
|
3681
3877
|
case "NUMBER":
|
|
@@ -3888,6 +4084,7 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
3888
4084
|
function extractTableCondition(where, tableAlias) {
|
|
3889
4085
|
switch (where.type) {
|
|
3890
4086
|
case "BINARY":
|
|
4087
|
+
if (isLike(where)) return null;
|
|
3891
4088
|
if (!isSingleTableField(where.left, tableAlias)) return null;
|
|
3892
4089
|
if (!isPushDownableRight(where.right)) return null;
|
|
3893
4090
|
return where;
|
|
@@ -3927,7 +4124,7 @@ function isPushDownableRight(value) {
|
|
|
3927
4124
|
function referencesOnlyTable(expr, tableAlias) {
|
|
3928
4125
|
switch (expr.type) {
|
|
3929
4126
|
case "BINARY":
|
|
3930
|
-
return isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
|
|
4127
|
+
return !isLike(expr) && isSingleTableField(expr.left, tableAlias) && isPushDownableRight(expr.right);
|
|
3931
4128
|
case "NULL_CHECK":
|
|
3932
4129
|
return isSingleTableField(expr.field, tableAlias);
|
|
3933
4130
|
case "LOGICAL":
|
|
@@ -4550,6 +4747,10 @@ async function executeStatement(sql, client, options) {
|
|
|
4550
4747
|
return executeParsedStatement(stmt, client, options, cacheContext);
|
|
4551
4748
|
}
|
|
4552
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
|
+
}
|
|
4553
4754
|
switch (stmt.type) {
|
|
4554
4755
|
case "SELECT":
|
|
4555
4756
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -4582,6 +4783,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4582
4783
|
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4583
4784
|
case "DROP_TEMP_TABLE":
|
|
4584
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.");
|
|
4585
4788
|
case "ASSERT":
|
|
4586
4789
|
return executeAssert(stmt, client, options, cacheContext);
|
|
4587
4790
|
}
|
|
@@ -4612,6 +4815,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4612
4815
|
const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
|
|
4613
4816
|
const cacheContext = options.cacheContext ?? "default";
|
|
4614
4817
|
const tempTables = /* @__PURE__ */ new Map();
|
|
4818
|
+
const variables = /* @__PURE__ */ new Map();
|
|
4615
4819
|
const results = [];
|
|
4616
4820
|
const failed = /* @__PURE__ */ new Set();
|
|
4617
4821
|
let aborted = null;
|
|
@@ -4649,7 +4853,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4649
4853
|
})
|
|
4650
4854
|
} : options;
|
|
4651
4855
|
const outcome = await runWithDeadline(
|
|
4652
|
-
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
|
|
4856
|
+
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
|
|
4653
4857
|
remaining
|
|
4654
4858
|
);
|
|
4655
4859
|
results.push({ ...base, status: "success", ...outcome });
|
|
@@ -4660,6 +4864,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4660
4864
|
aborted = "timeout";
|
|
4661
4865
|
} else if (e instanceof AssertError) {
|
|
4662
4866
|
aborted = "assertion";
|
|
4867
|
+
} else if (info.statementType === "SET_VARIABLE") {
|
|
4868
|
+
aborted = "fail-fast";
|
|
4663
4869
|
} else if (!options.continueOnError) {
|
|
4664
4870
|
aborted = "fail-fast";
|
|
4665
4871
|
}
|
|
@@ -4674,44 +4880,49 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4674
4880
|
metrics
|
|
4675
4881
|
};
|
|
4676
4882
|
}
|
|
4677
|
-
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
|
|
4678
|
-
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") {
|
|
4679
4890
|
const materializeOptions = {
|
|
4680
4891
|
...options,
|
|
4681
4892
|
maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
4682
4893
|
onLimitReached: "error"
|
|
4683
4894
|
};
|
|
4684
|
-
const result = await runSelectLike(
|
|
4685
|
-
tempTables.set(
|
|
4686
|
-
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 };
|
|
4687
4898
|
}
|
|
4688
4899
|
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
4689
4900
|
tempTables.delete(stmt.name);
|
|
4690
4901
|
return { tempTable: stmt.name };
|
|
4691
4902
|
}
|
|
4692
4903
|
if (stmt.type === "EXPLAIN") {
|
|
4693
|
-
return { result: await executeParsedStatement(
|
|
4904
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
4694
4905
|
}
|
|
4695
|
-
if (
|
|
4696
|
-
await executeAssert(
|
|
4906
|
+
if (resolvedStmt.type === "ASSERT") {
|
|
4907
|
+
await executeAssert(resolvedStmt, client, options, cacheContext, tempTables);
|
|
4697
4908
|
return {};
|
|
4698
4909
|
}
|
|
4699
4910
|
if (info.tempTablesReferenced.length > 0) {
|
|
4700
|
-
if (
|
|
4701
|
-
return { result: await executeQueryWithCte(
|
|
4911
|
+
if (resolvedStmt.type === "SELECT" || resolvedStmt.type === "UNION") {
|
|
4912
|
+
return { result: await executeQueryWithCte(resolvedStmt, client, options, tempTables, cacheContext) };
|
|
4702
4913
|
}
|
|
4703
|
-
if (
|
|
4704
|
-
return { result: await executeWith(
|
|
4914
|
+
if (resolvedStmt.type === "WITH") {
|
|
4915
|
+
return { result: await executeWith(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4705
4916
|
}
|
|
4706
|
-
if (
|
|
4707
|
-
return { result: await executeInsertSelect(
|
|
4917
|
+
if (resolvedStmt.type === "INSERT_SELECT") {
|
|
4918
|
+
return { result: await executeInsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4708
4919
|
}
|
|
4709
|
-
if (
|
|
4710
|
-
return { result: await executeUpsertSelect(
|
|
4920
|
+
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
4921
|
+
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
4711
4922
|
}
|
|
4712
4923
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
4713
4924
|
}
|
|
4714
|
-
return { result: await executeParsedStatement(
|
|
4925
|
+
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
4715
4926
|
}
|
|
4716
4927
|
async function runSelectLike(query, client, options, cacheContext, tempTables) {
|
|
4717
4928
|
if (query.type === "WITH") {
|
|
@@ -4772,6 +4983,62 @@ function parseSqlBatch(sql) {
|
|
|
4772
4983
|
const tokens = new Lexer(sql).tokenize();
|
|
4773
4984
|
return new Parser(tokens).parseStatements();
|
|
4774
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
|
+
}
|
|
4775
5042
|
var AssertError = class extends Error {
|
|
4776
5043
|
constructor(message) {
|
|
4777
5044
|
super(`AssertError: ${message}`);
|
|
@@ -4802,6 +5069,8 @@ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
|
4802
5069
|
}
|
|
4803
5070
|
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
4804
5071
|
switch (operand.type) {
|
|
5072
|
+
case "VARIABLE":
|
|
5073
|
+
throw new Error(`ParseError: unresolved batch variable @${operand.name}.`);
|
|
4805
5074
|
case "NUMBER":
|
|
4806
5075
|
return String(operand.value);
|
|
4807
5076
|
case "STRING":
|
|
@@ -6303,13 +6572,21 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
6303
6572
|
function buildBatchExplainPlans(sql) {
|
|
6304
6573
|
const statements = parseSqlBatch(sql);
|
|
6305
6574
|
const analysis = analyzeBatch(statements);
|
|
6575
|
+
const variables = /* @__PURE__ */ new Map();
|
|
6306
6576
|
return {
|
|
6307
6577
|
statementCount: statements.length,
|
|
6308
|
-
statements: statements.map((stmt, i) =>
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
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
|
+
})
|
|
6313
6590
|
};
|
|
6314
6591
|
}
|
|
6315
6592
|
function buildBatchStatementPlan(stmt, info) {
|
|
@@ -6327,6 +6604,12 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
6327
6604
|
" \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
|
|
6328
6605
|
];
|
|
6329
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
|
+
}
|
|
6330
6613
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
6331
6614
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
6332
6615
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
@@ -6486,8 +6769,10 @@ function collectFullScanReasons(stmt) {
|
|
|
6486
6769
|
r.push("\u96C6\u8A08\u95A2\u6570\uFF08COUNT / SUM \u7B49\uFF09\u3042\u308A");
|
|
6487
6770
|
if (stmt.columns.some((c) => c.type === "SCALAR_SUBQUERY_COL"))
|
|
6488
6771
|
r.push("SELECT \u5217\u306B\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA");
|
|
6489
|
-
if (
|
|
6772
|
+
if (whereRequiresJsEval(stmt.where))
|
|
6490
6773
|
r.push("WHERE \u53E5\u306B JS \u8A55\u4FA1\u304C\u5FC5\u8981\u306A\u5F0F");
|
|
6774
|
+
if (whereHasLike(stmt.where))
|
|
6775
|
+
r.push("LIKE \u306F\u5E38\u306B JS \u8A55\u4FA1\u306E\u305F\u3081\u5168\u4EF6\u53D6\u5F97");
|
|
6491
6776
|
if (stmt.orderBy.some((o) => o.key.type !== "FIELD_NAME"))
|
|
6492
6777
|
r.push("ORDER BY \u306B\u5F0F");
|
|
6493
6778
|
return r;
|