@rex0220/kintone-sql-tools 2.7.0 → 2.9.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 +832 -526
- package/dist-mcp/ksql-mcp.js +765 -459
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -94,6 +94,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
94
94
|
["IS", "IS" /* IS */],
|
|
95
95
|
["NULL", "NULL" /* NULL */],
|
|
96
96
|
["LIKE", "LIKE" /* LIKE */],
|
|
97
|
+
["KLIKE", "KLIKE" /* KLIKE */],
|
|
97
98
|
["IN", "IN" /* IN */],
|
|
98
99
|
["BETWEEN", "BETWEEN" /* BETWEEN */],
|
|
99
100
|
["TODAY", "TODAY" /* TODAY */],
|
|
@@ -1619,7 +1620,7 @@ var Parser = class {
|
|
|
1619
1620
|
}
|
|
1620
1621
|
return false;
|
|
1621
1622
|
}
|
|
1622
|
-
// 比較演算子: =, !=, <>, >, <, >=, <=, LIKE, IN, IS NULL
|
|
1623
|
+
// 比較演算子: =, !=, <>, >, <, >=, <=, LIKE, KLIKE, IN, IS NULL
|
|
1623
1624
|
parseCompareExpr() {
|
|
1624
1625
|
if (this.peek().kind === "(" /* LPAREN */ && !this.isArithParen()) {
|
|
1625
1626
|
this.advance();
|
|
@@ -1655,8 +1656,12 @@ var Parser = class {
|
|
|
1655
1656
|
const pattern = this.parseSqlValue();
|
|
1656
1657
|
return { type: "BINARY", op: "NOT_LIKE", left: field, right: pattern };
|
|
1657
1658
|
}
|
|
1659
|
+
if (this.consume("KLIKE" /* KLIKE */)) {
|
|
1660
|
+
const pattern = this.parseKlikePattern();
|
|
1661
|
+
return { type: "BINARY", op: "NOT_KLIKE", left: field, right: pattern };
|
|
1662
|
+
}
|
|
1658
1663
|
throw new ParseError(
|
|
1659
|
-
"NOT \u306E\u5F8C\u306B\u306F IN \
|
|
1664
|
+
"NOT \u306E\u5F8C\u306B\u306F IN\u3001LIKE\u3001KLIKE \u306E\u3044\u305A\u308C\u304B\u304C\u5FC5\u8981\u3067\u3059",
|
|
1660
1665
|
this.peek()
|
|
1661
1666
|
);
|
|
1662
1667
|
}
|
|
@@ -1666,6 +1671,10 @@ var Parser = class {
|
|
|
1666
1671
|
this.expect(")" /* RPAREN */);
|
|
1667
1672
|
return { type: "BINARY", op: "IN", left: field, right: right2 };
|
|
1668
1673
|
}
|
|
1674
|
+
if (this.consume("KLIKE" /* KLIKE */)) {
|
|
1675
|
+
const pattern = this.parseKlikePattern();
|
|
1676
|
+
return { type: "BINARY", op: "KLIKE", left: field, right: pattern };
|
|
1677
|
+
}
|
|
1669
1678
|
const op = this.parseCompareOp();
|
|
1670
1679
|
const right = this.parseSqlValue();
|
|
1671
1680
|
return { type: "BINARY", op, left: field, right };
|
|
@@ -1691,11 +1700,27 @@ var Parser = class {
|
|
|
1691
1700
|
return "LIKE";
|
|
1692
1701
|
default:
|
|
1693
1702
|
throw new ParseError(
|
|
1694
|
-
"\u6BD4\u8F03\u6F14\u7B97\u5B50\uFF08=, !=, >, <, >=, <=, LIKE, IN, IS\uFF09\u304C\u5FC5\u8981\u3067\u3059",
|
|
1703
|
+
"\u6BD4\u8F03\u6F14\u7B97\u5B50\uFF08=, !=, >, <, >=, <=, LIKE, KLIKE, IN, IS\uFF09\u304C\u5FC5\u8981\u3067\u3059",
|
|
1695
1704
|
tok
|
|
1696
1705
|
);
|
|
1697
1706
|
}
|
|
1698
1707
|
}
|
|
1708
|
+
/** KLIKE / NOT KLIKE の右辺。kintone キーワードは文字列値だけを受け付ける。 */
|
|
1709
|
+
parseKlikePattern() {
|
|
1710
|
+
const tok = this.peek();
|
|
1711
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
1712
|
+
this.advance();
|
|
1713
|
+
return { type: "STRING", value: tok.value };
|
|
1714
|
+
}
|
|
1715
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
1716
|
+
this.advance();
|
|
1717
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
1718
|
+
}
|
|
1719
|
+
throw new ParseError(
|
|
1720
|
+
"KLIKE / NOT KLIKE \u306E\u53F3\u8FBA\u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u307E\u305F\u306F\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059",
|
|
1721
|
+
tok
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1699
1724
|
// WHERE / HAVING の左辺
|
|
1700
1725
|
// - 文字列・数値関数: UPPER(f) / LENGTH(f) / ROUND(f, 2) ...
|
|
1701
1726
|
// - 集計関数(HAVING のみ): COUNT(*) / SUM(f) ...
|
|
@@ -2346,238 +2371,6 @@ function collectDmlTargetFields(stmt) {
|
|
|
2346
2371
|
return [];
|
|
2347
2372
|
}
|
|
2348
2373
|
|
|
2349
|
-
// src/core/batch.ts
|
|
2350
|
-
var MAX_TEMP_TABLES = 16;
|
|
2351
|
-
var MAX_BATCH_VARIABLES = 64;
|
|
2352
|
-
var BatchAnalysisError = class extends Error {
|
|
2353
|
-
constructor(message, statementIndex) {
|
|
2354
|
-
super(message);
|
|
2355
|
-
this.statementIndex = statementIndex;
|
|
2356
|
-
}
|
|
2357
|
-
};
|
|
2358
|
-
function collectRefs(node, tempRefs, appIds) {
|
|
2359
|
-
if (Array.isArray(node)) {
|
|
2360
|
-
for (const v of node) collectRefs(v, tempRefs, appIds);
|
|
2361
|
-
return;
|
|
2362
|
-
}
|
|
2363
|
-
if (node !== null && typeof node === "object") {
|
|
2364
|
-
const obj = node;
|
|
2365
|
-
const cte = obj["cteName"];
|
|
2366
|
-
if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
|
|
2367
|
-
const appId = obj["appId"];
|
|
2368
|
-
if (typeof appId === "number" && appId > 0) appIds.add(appId);
|
|
2369
|
-
for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
function collectVariableRefs(node, refs) {
|
|
2373
|
-
if (Array.isArray(node)) {
|
|
2374
|
-
for (const v of node) collectVariableRefs(v, refs);
|
|
2375
|
-
return;
|
|
2376
|
-
}
|
|
2377
|
-
if (node !== null && typeof node === "object") {
|
|
2378
|
-
const obj = node;
|
|
2379
|
-
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
2380
|
-
refs.add(obj["name"]);
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
function analyzeBatch(statements) {
|
|
2387
|
-
if (statements.length === 0) {
|
|
2388
|
-
throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
|
|
2389
|
-
}
|
|
2390
|
-
if (statements.length === 1) {
|
|
2391
|
-
const t = statements[0].type;
|
|
2392
|
-
if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
|
|
2393
|
-
const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
|
|
2394
|
-
throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
|
|
2395
|
-
}
|
|
2396
|
-
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
2397
|
-
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
2398
|
-
throw new BatchAnalysisError(
|
|
2399
|
-
`ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
|
|
2400
|
-
0
|
|
2401
|
-
);
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
const defined = /* @__PURE__ */ new Map();
|
|
2405
|
-
const createdOrder = [];
|
|
2406
|
-
const results = [];
|
|
2407
|
-
const variableDefs = /* @__PURE__ */ new Map();
|
|
2408
|
-
const variableOrder = [];
|
|
2409
|
-
statements.forEach((stmt, index) => {
|
|
2410
|
-
const statementType = getStatementType(stmt);
|
|
2411
|
-
const created = [];
|
|
2412
|
-
const dropped = [];
|
|
2413
|
-
const refs = /* @__PURE__ */ new Set();
|
|
2414
|
-
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
2415
|
-
const dependsOn = /* @__PURE__ */ new Set();
|
|
2416
|
-
const variableRefs = /* @__PURE__ */ new Set();
|
|
2417
|
-
collectVariableRefs(stmt, variableRefs);
|
|
2418
|
-
for (const name of variableRefs) {
|
|
2419
|
-
const def = variableDefs.get(name);
|
|
2420
|
-
if (def === void 0) {
|
|
2421
|
-
throw new BatchAnalysisError(
|
|
2422
|
-
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
2423
|
-
index
|
|
2424
|
-
);
|
|
2425
|
-
}
|
|
2426
|
-
def.referencedBy.push(index);
|
|
2427
|
-
}
|
|
2428
|
-
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
2429
|
-
if (variableDefs.has(stmt.name)) {
|
|
2430
|
-
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
2431
|
-
}
|
|
2432
|
-
variableDefs.set(stmt.name, { index, referencedBy: [] });
|
|
2433
|
-
variableOrder.push(stmt.name);
|
|
2434
|
-
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
2435
|
-
throw new BatchAnalysisError(
|
|
2436
|
-
`ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
|
|
2437
|
-
index
|
|
2438
|
-
);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
2442
|
-
collectRefs(stmt.query, refs, stmtAppIds);
|
|
2443
|
-
} else if (stmt.type === "DROP_TEMP_TABLE") {
|
|
2444
|
-
} else {
|
|
2445
|
-
collectRefs(stmt, refs, stmtAppIds);
|
|
2446
|
-
}
|
|
2447
|
-
let tempOnlySource = false;
|
|
2448
|
-
if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
|
|
2449
|
-
const srcTemp = /* @__PURE__ */ new Set();
|
|
2450
|
-
const srcApps = /* @__PURE__ */ new Set();
|
|
2451
|
-
collectRefs(stmt.select, srcTemp, srcApps);
|
|
2452
|
-
tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
|
|
2453
|
-
}
|
|
2454
|
-
for (const name of refs) {
|
|
2455
|
-
const at = defined.get(name);
|
|
2456
|
-
if (at === void 0) {
|
|
2457
|
-
throw new BatchAnalysisError(
|
|
2458
|
-
`ParseError: temp table ${name} is not defined in this batch.`,
|
|
2459
|
-
index
|
|
2460
|
-
);
|
|
2461
|
-
}
|
|
2462
|
-
dependsOn.add(at);
|
|
2463
|
-
}
|
|
2464
|
-
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
2465
|
-
if (defined.has(stmt.name)) {
|
|
2466
|
-
throw new BatchAnalysisError(
|
|
2467
|
-
`ParseError: temp table ${stmt.name} is already defined.`,
|
|
2468
|
-
index
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
defined.set(stmt.name, index);
|
|
2472
|
-
createdOrder.push(stmt.name);
|
|
2473
|
-
created.push(stmt.name);
|
|
2474
|
-
if (defined.size > MAX_TEMP_TABLES) {
|
|
2475
|
-
throw new BatchAnalysisError(
|
|
2476
|
-
`ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
|
|
2477
|
-
index
|
|
2478
|
-
);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
2482
|
-
const at = defined.get(stmt.name);
|
|
2483
|
-
if (at === void 0) {
|
|
2484
|
-
throw new BatchAnalysisError(
|
|
2485
|
-
`ParseError: temp table ${stmt.name} is not defined in this batch.`,
|
|
2486
|
-
index
|
|
2487
|
-
);
|
|
2488
|
-
}
|
|
2489
|
-
dependsOn.add(at);
|
|
2490
|
-
dropped.push(stmt.name);
|
|
2491
|
-
defined.delete(stmt.name);
|
|
2492
|
-
}
|
|
2493
|
-
results.push({
|
|
2494
|
-
index,
|
|
2495
|
-
statementType,
|
|
2496
|
-
isDml: isDmlType(statementType),
|
|
2497
|
-
isReadOnly: isReadOnlyType(statementType),
|
|
2498
|
-
hasWhere: hasWhereClause(stmt),
|
|
2499
|
-
insertValuesCount: getInsertValuesCount(stmt),
|
|
2500
|
-
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
2501
|
-
tempTablesCreated: created,
|
|
2502
|
-
tempTablesReferenced: [...refs],
|
|
2503
|
-
tempTablesDropped: dropped,
|
|
2504
|
-
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
2505
|
-
tempOnlySource,
|
|
2506
|
-
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
2507
|
-
});
|
|
2508
|
-
});
|
|
2509
|
-
const containsDml = results.some((r) => r.isDml);
|
|
2510
|
-
const variables = variableOrder.map((name) => ({
|
|
2511
|
-
name,
|
|
2512
|
-
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
2513
|
-
}));
|
|
2514
|
-
return {
|
|
2515
|
-
statementCount: statements.length,
|
|
2516
|
-
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
2517
|
-
containsDml,
|
|
2518
|
-
tempTables: createdOrder,
|
|
2519
|
-
variables,
|
|
2520
|
-
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
2521
|
-
statements: results
|
|
2522
|
-
};
|
|
2523
|
-
}
|
|
2524
|
-
|
|
2525
|
-
// src/core/batchVariables.ts
|
|
2526
|
-
var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
2527
|
-
function normalizeBatchVariableName(name) {
|
|
2528
|
-
if (!VARIABLE_NAME_RE.test(name)) {
|
|
2529
|
-
throw new Error(
|
|
2530
|
-
`ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
2533
|
-
return name.toLowerCase();
|
|
2534
|
-
}
|
|
2535
|
-
function normalizeBatchVariables(input) {
|
|
2536
|
-
const normalized = /* @__PURE__ */ Object.create(null);
|
|
2537
|
-
for (const [rawName, value] of Object.entries(input ?? {})) {
|
|
2538
|
-
const name = normalizeBatchVariableName(rawName);
|
|
2539
|
-
if (Object.prototype.hasOwnProperty.call(normalized, name)) {
|
|
2540
|
-
throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
|
|
2541
|
-
}
|
|
2542
|
-
normalized[name] = value;
|
|
2543
|
-
}
|
|
2544
|
-
return normalized;
|
|
2545
|
-
}
|
|
2546
|
-
function validateDeclaredBatchVariables(statements, input) {
|
|
2547
|
-
const normalized = normalizeBatchVariables(input);
|
|
2548
|
-
const declared = new Set(
|
|
2549
|
-
statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
|
|
2550
|
-
);
|
|
2551
|
-
for (const name of Object.keys(normalized)) {
|
|
2552
|
-
if (!declared.has(name)) {
|
|
2553
|
-
throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
return normalized;
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
// src/core/scalarCompare.ts
|
|
2560
|
-
function compareScalarValues(op, leftStr, rightStr) {
|
|
2561
|
-
if (op === "=") return leftStr === rightStr;
|
|
2562
|
-
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
2563
|
-
const rightNum = Number(rightStr);
|
|
2564
|
-
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
2565
|
-
return op === "<" || op === "<=";
|
|
2566
|
-
}
|
|
2567
|
-
const leftNum = Number(leftStr);
|
|
2568
|
-
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
2569
|
-
switch (op) {
|
|
2570
|
-
case ">":
|
|
2571
|
-
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
2572
|
-
case "<":
|
|
2573
|
-
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
2574
|
-
case ">=":
|
|
2575
|
-
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
2576
|
-
case "<=":
|
|
2577
|
-
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
2374
|
// src/engine/pushDownNot.ts
|
|
2582
2375
|
function pushDownNot(expr) {
|
|
2583
2376
|
switch (expr.type) {
|
|
@@ -2627,6 +2420,10 @@ function negateOp(op) {
|
|
|
2627
2420
|
return "NOT_LIKE";
|
|
2628
2421
|
case "NOT_LIKE":
|
|
2629
2422
|
return "LIKE";
|
|
2423
|
+
case "KLIKE":
|
|
2424
|
+
return "NOT_KLIKE";
|
|
2425
|
+
case "NOT_KLIKE":
|
|
2426
|
+
return "KLIKE";
|
|
2630
2427
|
case "IN":
|
|
2631
2428
|
return "NOT_IN";
|
|
2632
2429
|
case "NOT_IN":
|
|
@@ -2641,6 +2438,9 @@ function likePatternHasWildcard(pattern) {
|
|
|
2641
2438
|
function isLike(where) {
|
|
2642
2439
|
return where.type === "BINARY" && (where.op === "LIKE" || where.op === "NOT_LIKE");
|
|
2643
2440
|
}
|
|
2441
|
+
function isKlike(where) {
|
|
2442
|
+
return where.type === "BINARY" && (where.op === "KLIKE" || where.op === "NOT_KLIKE");
|
|
2443
|
+
}
|
|
2644
2444
|
function whereHasLike(where) {
|
|
2645
2445
|
if (where === null) return false;
|
|
2646
2446
|
if (isLike(where)) return true;
|
|
@@ -2656,14 +2456,29 @@ function whereHasLike(where) {
|
|
|
2656
2456
|
return false;
|
|
2657
2457
|
}
|
|
2658
2458
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
switch (
|
|
2663
|
-
case "
|
|
2664
|
-
return
|
|
2665
|
-
case "
|
|
2666
|
-
|
|
2459
|
+
function whereHasKlike(where) {
|
|
2460
|
+
if (where === null) return false;
|
|
2461
|
+
if (isKlike(where)) return true;
|
|
2462
|
+
switch (where.type) {
|
|
2463
|
+
case "LOGICAL":
|
|
2464
|
+
return whereHasKlike(where.left) || whereHasKlike(where.right);
|
|
2465
|
+
case "NOT":
|
|
2466
|
+
case "GROUP":
|
|
2467
|
+
return whereHasKlike(where.expr);
|
|
2468
|
+
case "BINARY":
|
|
2469
|
+
case "NULL_CHECK":
|
|
2470
|
+
case "EXISTS":
|
|
2471
|
+
return false;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// src/converter/whereToKintone.ts
|
|
2476
|
+
function whereToKintone(expr) {
|
|
2477
|
+
switch (expr.type) {
|
|
2478
|
+
case "BINARY":
|
|
2479
|
+
return convertBinary(expr);
|
|
2480
|
+
case "NULL_CHECK":
|
|
2481
|
+
return convertNullCheck(expr);
|
|
2667
2482
|
case "LOGICAL":
|
|
2668
2483
|
return convertLogical(expr);
|
|
2669
2484
|
case "NOT":
|
|
@@ -2704,6 +2519,10 @@ function convertOp(op) {
|
|
|
2704
2519
|
return "like";
|
|
2705
2520
|
case "NOT_LIKE":
|
|
2706
2521
|
return "not like";
|
|
2522
|
+
case "KLIKE":
|
|
2523
|
+
return "like";
|
|
2524
|
+
case "NOT_KLIKE":
|
|
2525
|
+
return "not like";
|
|
2707
2526
|
case "IN":
|
|
2708
2527
|
return "in";
|
|
2709
2528
|
case "NOT_IN":
|
|
@@ -3229,72 +3048,713 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
3229
3048
|
addFieldRef(join2.on.left.field, join2.on.left.tableAlias, "where");
|
|
3230
3049
|
addFieldRef(join2.on.right.field, join2.on.right.tableAlias, "where");
|
|
3231
3050
|
}
|
|
3232
|
-
walkWhere(stmt.where, "where");
|
|
3233
|
-
for (const gk of stmt.groupBy) walkGroupByKey(gk);
|
|
3234
|
-
walkWhere(stmt.having, "having");
|
|
3235
|
-
for (const ob of stmt.orderBy) walkOrderByKey(ob.key);
|
|
3236
|
-
return states;
|
|
3051
|
+
walkWhere(stmt.where, "where");
|
|
3052
|
+
for (const gk of stmt.groupBy) walkGroupByKey(gk);
|
|
3053
|
+
walkWhere(stmt.having, "having");
|
|
3054
|
+
for (const ob of stmt.orderBy) walkOrderByKey(ob.key);
|
|
3055
|
+
return states;
|
|
3056
|
+
}
|
|
3057
|
+
function collectSelectOutputNames(columns) {
|
|
3058
|
+
const names = /* @__PURE__ */ new Set();
|
|
3059
|
+
for (const col of columns) {
|
|
3060
|
+
if (col.type === "FIELD" && col.alias) {
|
|
3061
|
+
names.add(col.alias);
|
|
3062
|
+
continue;
|
|
3063
|
+
}
|
|
3064
|
+
if (col.type === "LITERAL_COL") {
|
|
3065
|
+
names.add(col.alias ?? `'${col.value}'`);
|
|
3066
|
+
continue;
|
|
3067
|
+
}
|
|
3068
|
+
if (col.type === "AGGREGATE") {
|
|
3069
|
+
if (col.alias) names.add(col.alias);
|
|
3070
|
+
else names.add(aggregateSyntheticName(col.func, col.distinct, col.arg));
|
|
3071
|
+
continue;
|
|
3072
|
+
}
|
|
3073
|
+
if (col.type === "ARITH_AGG_COL") {
|
|
3074
|
+
if (col.alias) names.add(col.alias);
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
if (col.type === "ARITH_COL") {
|
|
3078
|
+
if (col.alias) names.add(col.alias);
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3081
|
+
if (col.type === "CASE_COL") {
|
|
3082
|
+
names.add(col.alias ?? "case");
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
if (col.type === "STRFUNC_COL") {
|
|
3086
|
+
if (col.alias) names.add(col.alias);
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
if (col.type === "SCALAR_SUBQUERY_COL") {
|
|
3090
|
+
names.add(col.alias ?? "(subquery)");
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
return names;
|
|
3094
|
+
}
|
|
3095
|
+
function aggregateSyntheticName(func, distinct, arg) {
|
|
3096
|
+
const argStr = arg.type === "WILDCARD" ? "*" : arithNodeLabel(arg);
|
|
3097
|
+
return distinct ? `${func}(DISTINCT ${argStr})` : `${func}(${argStr})`;
|
|
3098
|
+
}
|
|
3099
|
+
function arithNodeLabel(node) {
|
|
3100
|
+
if (node.type === "FIELD_REF") return node.field;
|
|
3101
|
+
if (node.type === "NUMBER") return String(node.value);
|
|
3102
|
+
if (node.type === "STRING_FUNC") return stringFuncLabel(node);
|
|
3103
|
+
return `(${arithNodeLabel(node.left)}${node.op}${arithNodeLabel(node.right)})`;
|
|
3104
|
+
}
|
|
3105
|
+
function stringFuncLabel(expr) {
|
|
3106
|
+
const args = expr.args.map((a) => {
|
|
3107
|
+
if (a.type === "STRING") return `'${a.value}'`;
|
|
3108
|
+
if (a.type === "STRING_FUNC") return stringFuncLabel(a);
|
|
3109
|
+
if (a.type === "AGG_REF") return aggregateSyntheticName(a.func, a.distinct, a.arg);
|
|
3110
|
+
if (a.type === "AGG_ARITH") return "agg_arith";
|
|
3111
|
+
return arithNodeLabel(a);
|
|
3112
|
+
});
|
|
3113
|
+
return `${expr.func}(${args.join(",")})`;
|
|
3114
|
+
}
|
|
3115
|
+
function isAggregateSyntheticName(name) {
|
|
3116
|
+
return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/core/cteInlining.ts
|
|
3120
|
+
function canInlineSingleCte(stmt) {
|
|
3121
|
+
if (stmt.ctes.length !== 1) return false;
|
|
3122
|
+
const cteDef = stmt.ctes[0];
|
|
3123
|
+
if (cteDef.query.type !== "SELECT" || resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
|
|
3124
|
+
const finalQuery = stmt.query;
|
|
3125
|
+
if (finalQuery.type !== "SELECT") return false;
|
|
3126
|
+
if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
|
|
3127
|
+
if (finalQuery.groupBy.length > 0 || finalQuery.distinct) return false;
|
|
3128
|
+
return !finalQuery.columns.some(
|
|
3129
|
+
(column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
function buildInlinedQuery(stmt) {
|
|
3133
|
+
const cteBody = stmt.ctes[0].query;
|
|
3134
|
+
const final = stmt.query;
|
|
3135
|
+
const finalWhere = stripCteAlias(final.where, final.from.alias);
|
|
3136
|
+
const where = cteBody.where === null ? finalWhere : finalWhere === null ? cteBody.where : { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
|
|
3137
|
+
const columns = final.columns.every((column) => column.type === "WILDCARD") ? cteBody.columns : final.columns;
|
|
3138
|
+
return {
|
|
3139
|
+
type: "SELECT",
|
|
3140
|
+
from: cteBody.from,
|
|
3141
|
+
joins: [],
|
|
3142
|
+
columns,
|
|
3143
|
+
where,
|
|
3144
|
+
groupBy: [],
|
|
3145
|
+
having: null,
|
|
3146
|
+
orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
|
|
3147
|
+
limit: final.limit ?? cteBody.limit,
|
|
3148
|
+
offset: final.offset ?? cteBody.offset,
|
|
3149
|
+
distinct: false
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
function stripCteAlias(where, alias) {
|
|
3153
|
+
if (where === null || alias === null) return where;
|
|
3154
|
+
switch (where.type) {
|
|
3155
|
+
case "BINARY":
|
|
3156
|
+
return { ...where, left: stripCteAliasFromFieldValue(where.left, alias) };
|
|
3157
|
+
case "NULL_CHECK":
|
|
3158
|
+
return { ...where, field: stripCteAliasFromFieldValue(where.field, alias) };
|
|
3159
|
+
case "LOGICAL":
|
|
3160
|
+
return {
|
|
3161
|
+
...where,
|
|
3162
|
+
left: stripCteAlias(where.left, alias),
|
|
3163
|
+
right: stripCteAlias(where.right, alias)
|
|
3164
|
+
};
|
|
3165
|
+
case "NOT":
|
|
3166
|
+
case "GROUP":
|
|
3167
|
+
return { ...where, expr: stripCteAlias(where.expr, alias) };
|
|
3168
|
+
case "EXISTS":
|
|
3169
|
+
return where;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function stripCteAliasFromFieldValue(value, alias) {
|
|
3173
|
+
if (value.type === "FIELD" && value.tableAlias === alias) {
|
|
3174
|
+
return { ...value, tableAlias: null };
|
|
3175
|
+
}
|
|
3176
|
+
return value;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// src/core/optimization/wherePredicatePushdown.ts
|
|
3180
|
+
function extractSafePushdownLeaves(where, options = {}) {
|
|
3181
|
+
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
3182
|
+
}
|
|
3183
|
+
function extractTypedPushdownCandidates(where, options = {}) {
|
|
3184
|
+
return extractAndLeaves(
|
|
3185
|
+
where,
|
|
3186
|
+
(expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
function extractAndLeaves(where, accept) {
|
|
3190
|
+
switch (where.type) {
|
|
3191
|
+
case "BINARY":
|
|
3192
|
+
return accept(where) ? where : null;
|
|
3193
|
+
case "LOGICAL":
|
|
3194
|
+
if (where.op !== "AND") return null;
|
|
3195
|
+
{
|
|
3196
|
+
const left = extractAndLeaves(where.left, accept);
|
|
3197
|
+
const right = extractAndLeaves(where.right, accept);
|
|
3198
|
+
if (left && right) return { ...where, left, right };
|
|
3199
|
+
return left ?? right ?? null;
|
|
3200
|
+
}
|
|
3201
|
+
case "GROUP":
|
|
3202
|
+
return extractAndLeaves(where.expr, accept);
|
|
3203
|
+
case "NULL_CHECK":
|
|
3204
|
+
case "NOT":
|
|
3205
|
+
case "EXISTS":
|
|
3206
|
+
return null;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
function isSafeComparison(expr, options) {
|
|
3210
|
+
if (isKlikeComparison(expr, options)) return true;
|
|
3211
|
+
if (isSafeIdComparison(expr, options)) return true;
|
|
3212
|
+
if (isNumericCandidate(expr, options)) {
|
|
3213
|
+
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
3214
|
+
}
|
|
3215
|
+
return isSelectionInComparison(expr, options);
|
|
3216
|
+
}
|
|
3217
|
+
function isKlikeComparison(expr, options) {
|
|
3218
|
+
if (options.allowKlike === false) return false;
|
|
3219
|
+
if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
|
|
3220
|
+
if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
|
|
3221
|
+
return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
|
|
3222
|
+
}
|
|
3223
|
+
var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
3224
|
+
"DROP_DOWN",
|
|
3225
|
+
"RADIO_BUTTON",
|
|
3226
|
+
"CHECK_BOX",
|
|
3227
|
+
"MULTI_SELECT",
|
|
3228
|
+
"STATUS"
|
|
3229
|
+
]);
|
|
3230
|
+
function isSelectionInComparison(expr, options) {
|
|
3231
|
+
if (!isSelectionInCandidate(expr, options)) return false;
|
|
3232
|
+
if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
|
|
3233
|
+
const fieldType = options.fieldTypes?.get(expr.left.field);
|
|
3234
|
+
if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
|
|
3235
|
+
const validOptions = options.fieldOptions?.get(expr.left.field);
|
|
3236
|
+
if (validOptions === void 0) return false;
|
|
3237
|
+
return expr.right.values.every(
|
|
3238
|
+
(value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
function isSafeIdComparison(expr, options) {
|
|
3242
|
+
if (!isTargetIdField(expr.left, options)) return false;
|
|
3243
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
3244
|
+
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
3245
|
+
}
|
|
3246
|
+
function isTargetIdField(field, options) {
|
|
3247
|
+
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
3248
|
+
const targetAlias = options.tableAlias ?? null;
|
|
3249
|
+
if (field.tableAlias === targetAlias) return true;
|
|
3250
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
3251
|
+
}
|
|
3252
|
+
function isNumericCandidate(expr, options) {
|
|
3253
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
3254
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
3255
|
+
if (expr.right.type !== "NUMBER") return false;
|
|
3256
|
+
if (expr.op === "=") return true;
|
|
3257
|
+
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
3258
|
+
}
|
|
3259
|
+
function isSelectionInCandidate(expr, options) {
|
|
3260
|
+
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
3261
|
+
if (!isTargetField(expr.left, options)) return false;
|
|
3262
|
+
if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
|
|
3263
|
+
if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
|
|
3264
|
+
return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
|
|
3265
|
+
}
|
|
3266
|
+
function isTargetField(field, options) {
|
|
3267
|
+
const targetAlias = options.tableAlias ?? null;
|
|
3268
|
+
if (field.tableAlias === targetAlias) return true;
|
|
3269
|
+
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// src/core/optimization/klikePushdownPlan.ts
|
|
3273
|
+
function buildKlikePushdownPlan(stmt, options = {}) {
|
|
3274
|
+
const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
|
|
3275
|
+
const common = {
|
|
3276
|
+
allowKlike: joinsAreSafeForKlike,
|
|
3277
|
+
allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
|
|
3278
|
+
};
|
|
3279
|
+
let mainCondition = null;
|
|
3280
|
+
if (stmt.where !== null && !stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
3281
|
+
if (stmt.joins.length === 0) {
|
|
3282
|
+
mainCondition = extractSafePushdownLeaves(stmt.where, {
|
|
3283
|
+
...common,
|
|
3284
|
+
tableAlias: stmt.from.alias ?? void 0,
|
|
3285
|
+
allowUnqualifiedFields: true,
|
|
3286
|
+
fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
|
|
3287
|
+
fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
|
|
3288
|
+
});
|
|
3289
|
+
} else if (stmt.from.alias) {
|
|
3290
|
+
mainCondition = extractSafePushdownLeaves(stmt.where, {
|
|
3291
|
+
...common,
|
|
3292
|
+
tableAlias: stmt.from.alias,
|
|
3293
|
+
fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
|
|
3294
|
+
fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
const joinConditions = /* @__PURE__ */ new Map();
|
|
3299
|
+
if (stmt.where !== null) {
|
|
3300
|
+
for (const join2 of stmt.joins) {
|
|
3301
|
+
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
3302
|
+
const condition = extractSafePushdownLeaves(stmt.where, {
|
|
3303
|
+
...common,
|
|
3304
|
+
tableAlias: join2.table.alias,
|
|
3305
|
+
fieldTypes: options.fieldTypesByApp?.get(join2.table.appId),
|
|
3306
|
+
fieldOptions: options.fieldOptionsByApp?.get(join2.table.appId)
|
|
3307
|
+
});
|
|
3308
|
+
if (condition !== null) joinConditions.set(join2.table.alias, condition);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
const appliedKlikes = /* @__PURE__ */ new Set();
|
|
3312
|
+
collectKlikes(mainCondition, appliedKlikes);
|
|
3313
|
+
for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
|
|
3314
|
+
const allKlikes = /* @__PURE__ */ new Set();
|
|
3315
|
+
collectKlikes(stmt.where, allKlikes);
|
|
3316
|
+
return {
|
|
3317
|
+
mainCondition,
|
|
3318
|
+
joinConditions,
|
|
3319
|
+
appliedKlikes,
|
|
3320
|
+
allKlikes: [...allKlikes]
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
function unappliedKlikes(plan) {
|
|
3324
|
+
return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
|
|
3325
|
+
}
|
|
3326
|
+
function collectKlikes(where, out) {
|
|
3327
|
+
if (where === null) return;
|
|
3328
|
+
if (isKlike(where)) {
|
|
3329
|
+
out.add(where);
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
switch (where.type) {
|
|
3333
|
+
case "LOGICAL":
|
|
3334
|
+
collectKlikes(where.left, out);
|
|
3335
|
+
collectKlikes(where.right, out);
|
|
3336
|
+
return;
|
|
3337
|
+
case "NOT":
|
|
3338
|
+
case "GROUP":
|
|
3339
|
+
collectKlikes(where.expr, out);
|
|
3340
|
+
return;
|
|
3341
|
+
case "BINARY":
|
|
3342
|
+
case "NULL_CHECK":
|
|
3343
|
+
case "EXISTS":
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// src/core/klikeValidation.ts
|
|
3349
|
+
var KlikeValidationError = class extends Error {
|
|
3350
|
+
constructor(message) {
|
|
3351
|
+
super(`ArgumentError: ${message}`);
|
|
3352
|
+
this.name = "ArgumentError";
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3355
|
+
function validateKlikeStatement(stmt) {
|
|
3356
|
+
validateStatement(stmt);
|
|
3357
|
+
}
|
|
3358
|
+
function validateKlikePushdownPlan(plan) {
|
|
3359
|
+
if (unappliedKlikes(plan).length > 0) {
|
|
3360
|
+
throw new KlikeValidationError(
|
|
3361
|
+
"FULL_SCAN \u306E KLIKE / NOT KLIKE \u3092\u5B89\u5168\u306B\u62BC\u3057\u4E0B\u3052\u3089\u308C\u307E\u305B\u3093\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function validateStatement(stmt) {
|
|
3366
|
+
switch (stmt.type) {
|
|
3367
|
+
case "SELECT":
|
|
3368
|
+
validateSelect(stmt);
|
|
3369
|
+
return;
|
|
3370
|
+
case "UNION":
|
|
3371
|
+
validateUnion(stmt);
|
|
3372
|
+
return;
|
|
3373
|
+
case "WITH":
|
|
3374
|
+
validateWith(stmt);
|
|
3375
|
+
return;
|
|
3376
|
+
case "EXPLAIN":
|
|
3377
|
+
validateStatement(stmt.query);
|
|
3378
|
+
return;
|
|
3379
|
+
case "CREATE_TEMP_TABLE":
|
|
3380
|
+
validateSelectLike(stmt.query);
|
|
3381
|
+
return;
|
|
3382
|
+
case "SET_VARIABLE":
|
|
3383
|
+
case "DECLARE_VARIABLE":
|
|
3384
|
+
case "ASSERT":
|
|
3385
|
+
validateNestedSelects(stmt);
|
|
3386
|
+
return;
|
|
3387
|
+
case "INSERT":
|
|
3388
|
+
case "INSERT_SELECT":
|
|
3389
|
+
case "UPSERT":
|
|
3390
|
+
case "UPSERT_SELECT":
|
|
3391
|
+
case "UPDATE":
|
|
3392
|
+
case "DELETE":
|
|
3393
|
+
case "REORDER":
|
|
3394
|
+
if (containsKlike(stmt)) {
|
|
3395
|
+
throw new KlikeValidationError(
|
|
3396
|
+
"KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
return;
|
|
3400
|
+
case "SHOW_APPS":
|
|
3401
|
+
case "DESCRIBE":
|
|
3402
|
+
case "DROP_TEMP_TABLE":
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
function validateSelectLike(query) {
|
|
3407
|
+
if (query.type === "SELECT") validateSelect(query);
|
|
3408
|
+
else if (query.type === "UNION") validateUnion(query);
|
|
3409
|
+
else validateWith(query);
|
|
3410
|
+
}
|
|
3411
|
+
function validateUnion(stmt) {
|
|
3412
|
+
validateSelectLike(stmt.left);
|
|
3413
|
+
validateSelect(stmt.right);
|
|
3414
|
+
}
|
|
3415
|
+
function validateWith(stmt) {
|
|
3416
|
+
if (canInlineSingleCte(stmt)) {
|
|
3417
|
+
validateSelect(buildInlinedQuery(stmt));
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
for (const cte of stmt.ctes) {
|
|
3421
|
+
if (cte.query.type === "SELECT") validateSelect(cte.query);
|
|
3422
|
+
else if (cte.query.type === "UNION") validateUnion(cte.query);
|
|
3423
|
+
}
|
|
3424
|
+
validateSelectLike(stmt.query);
|
|
3425
|
+
}
|
|
3426
|
+
function validateSelect(stmt) {
|
|
3427
|
+
validateOwnKlikeExpressions(stmt);
|
|
3428
|
+
if (whereHasKlike(stmt.where)) {
|
|
3429
|
+
const directKintoneSimple = resolveSelectMode(stmt) === "SIMPLE" && stmt.from.cteName === null && stmt.joins.every((join2) => join2.table.cteName === null);
|
|
3430
|
+
if (!directKintoneSimple) {
|
|
3431
|
+
const plan = buildKlikePushdownPlan(stmt, { allowUnresolvedVariables: true });
|
|
3432
|
+
if (unappliedKlikes(plan).length === 0) {
|
|
3433
|
+
validateNestedSelects(stmt);
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
throw new KlikeValidationError(
|
|
3437
|
+
"FULL_SCAN \u306E KLIKE / NOT KLIKE \u306F\u3001\u7269\u7406\u30C6\u30FC\u30D6\u30EB\u306B\u5BFE\u3059\u308B AND \u30EA\u30FC\u30D5\u3068\u3057\u3066\u5FC5\u305A\u62BC\u3057\u4E0B\u3052\u3089\u308C\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
validateNestedSelects(stmt);
|
|
3442
|
+
}
|
|
3443
|
+
function validateOwnKlikeExpressions(stmt) {
|
|
3444
|
+
walkWithoutNestedSelects(stmt, (where) => {
|
|
3445
|
+
if (!isKlike(where)) return;
|
|
3446
|
+
if (!isDescendantOf(stmt.where, where)) {
|
|
3447
|
+
throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F SELECT \u306E WHERE \u53E5\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
|
|
3448
|
+
}
|
|
3449
|
+
const right = where.right;
|
|
3450
|
+
if (right.type !== "STRING" && right.type !== "VARIABLE") {
|
|
3451
|
+
throw new KlikeValidationError(
|
|
3452
|
+
"KLIKE / NOT KLIKE \u306E\u53F3\u8FBA\u306B\u306F\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u307E\u305F\u306F\u6587\u5B57\u5217\u30D0\u30C3\u30C1\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059"
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
if (right.type === "STRING" && right.value.includes("%")) {
|
|
3456
|
+
throw new KlikeValidationError(
|
|
3457
|
+
"KLIKE / NOT KLIKE \u306E\u691C\u7D22\u8A9E\u306B % \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002SQL \u30EF\u30A4\u30EB\u30C9\u30AB\u30FC\u30C9\u691C\u7D22\u306B\u306F LIKE \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
function validateNestedSelects(node) {
|
|
3463
|
+
walkObjects(node, (obj) => {
|
|
3464
|
+
if (obj.type === "SELECT") validateSelect(obj);
|
|
3465
|
+
}, true);
|
|
3466
|
+
}
|
|
3467
|
+
function containsKlike(node) {
|
|
3468
|
+
let found = false;
|
|
3469
|
+
walkObjects(node, (obj) => {
|
|
3470
|
+
if (obj.type === "BINARY" && (obj.op === "KLIKE" || obj.op === "NOT_KLIKE")) found = true;
|
|
3471
|
+
});
|
|
3472
|
+
return found;
|
|
3473
|
+
}
|
|
3474
|
+
function isDescendantOf(root, target) {
|
|
3475
|
+
if (root === null) return false;
|
|
3476
|
+
if (root === target) return true;
|
|
3477
|
+
switch (root.type) {
|
|
3478
|
+
case "LOGICAL":
|
|
3479
|
+
return isDescendantOf(root.left, target) || isDescendantOf(root.right, target);
|
|
3480
|
+
case "NOT":
|
|
3481
|
+
case "GROUP":
|
|
3482
|
+
return isDescendantOf(root.expr, target);
|
|
3483
|
+
case "BINARY":
|
|
3484
|
+
case "NULL_CHECK":
|
|
3485
|
+
case "EXISTS":
|
|
3486
|
+
return false;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
function walkWithoutNestedSelects(node, visitWhere) {
|
|
3490
|
+
if (Array.isArray(node)) {
|
|
3491
|
+
for (const value of node) walkWithoutNestedSelects(value, visitWhere);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (node === null || typeof node !== "object") return;
|
|
3495
|
+
const obj = node;
|
|
3496
|
+
if (obj.type === "BINARY" || obj.type === "NULL_CHECK" || obj.type === "LOGICAL" || obj.type === "NOT" || obj.type === "GROUP" || obj.type === "EXISTS") {
|
|
3497
|
+
visitWhere(obj);
|
|
3498
|
+
}
|
|
3499
|
+
for (const value of Object.values(obj)) {
|
|
3500
|
+
if (value !== node && isSelectObject(value)) continue;
|
|
3501
|
+
walkWithoutNestedSelects(value, visitWhere);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
function walkObjects(node, visit, skipRoot = false) {
|
|
3505
|
+
if (Array.isArray(node)) {
|
|
3506
|
+
for (const value of node) walkObjects(value, visit);
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
if (node === null || typeof node !== "object") return;
|
|
3510
|
+
const obj = node;
|
|
3511
|
+
if (!skipRoot) visit(obj);
|
|
3512
|
+
for (const value of Object.values(obj)) walkObjects(value, visit);
|
|
3513
|
+
}
|
|
3514
|
+
function isSelectObject(value) {
|
|
3515
|
+
return value !== null && typeof value === "object" && value.type === "SELECT";
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
// src/core/batch.ts
|
|
3519
|
+
var MAX_TEMP_TABLES = 16;
|
|
3520
|
+
var MAX_BATCH_VARIABLES = 64;
|
|
3521
|
+
var BatchAnalysisError = class extends Error {
|
|
3522
|
+
constructor(message, statementIndex) {
|
|
3523
|
+
super(message);
|
|
3524
|
+
this.statementIndex = statementIndex;
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
function collectRefs(node, tempRefs, appIds) {
|
|
3528
|
+
if (Array.isArray(node)) {
|
|
3529
|
+
for (const v of node) collectRefs(v, tempRefs, appIds);
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
if (node !== null && typeof node === "object") {
|
|
3533
|
+
const obj = node;
|
|
3534
|
+
const cte = obj["cteName"];
|
|
3535
|
+
if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
|
|
3536
|
+
const appId = obj["appId"];
|
|
3537
|
+
if (typeof appId === "number" && appId > 0) appIds.add(appId);
|
|
3538
|
+
for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
function collectVariableRefs(node, refs) {
|
|
3542
|
+
if (Array.isArray(node)) {
|
|
3543
|
+
for (const v of node) collectVariableRefs(v, refs);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
if (node !== null && typeof node === "object") {
|
|
3547
|
+
const obj = node;
|
|
3548
|
+
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
|
|
3549
|
+
refs.add(obj["name"]);
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
function analyzeBatch(statements) {
|
|
3556
|
+
if (statements.length === 0) {
|
|
3557
|
+
throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
|
|
3558
|
+
}
|
|
3559
|
+
statements.forEach((stmt, index) => {
|
|
3560
|
+
try {
|
|
3561
|
+
validateKlikeStatement(stmt);
|
|
3562
|
+
} catch (error) {
|
|
3563
|
+
if (error instanceof KlikeValidationError) {
|
|
3564
|
+
throw new BatchAnalysisError(error.message, index);
|
|
3565
|
+
}
|
|
3566
|
+
throw error;
|
|
3567
|
+
}
|
|
3568
|
+
});
|
|
3569
|
+
if (statements.length === 1) {
|
|
3570
|
+
const t = statements[0].type;
|
|
3571
|
+
if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
|
|
3572
|
+
const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
|
|
3573
|
+
throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
|
|
3574
|
+
}
|
|
3575
|
+
if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
|
|
3576
|
+
const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
|
|
3577
|
+
throw new BatchAnalysisError(
|
|
3578
|
+
`ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
|
|
3579
|
+
0
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
const defined = /* @__PURE__ */ new Map();
|
|
3584
|
+
const createdOrder = [];
|
|
3585
|
+
const results = [];
|
|
3586
|
+
const variableDefs = /* @__PURE__ */ new Map();
|
|
3587
|
+
const variableOrder = [];
|
|
3588
|
+
statements.forEach((stmt, index) => {
|
|
3589
|
+
const statementType = getStatementType(stmt);
|
|
3590
|
+
const created = [];
|
|
3591
|
+
const dropped = [];
|
|
3592
|
+
const refs = /* @__PURE__ */ new Set();
|
|
3593
|
+
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
3594
|
+
const dependsOn = /* @__PURE__ */ new Set();
|
|
3595
|
+
const variableRefs = /* @__PURE__ */ new Set();
|
|
3596
|
+
collectVariableRefs(stmt, variableRefs);
|
|
3597
|
+
for (const name of variableRefs) {
|
|
3598
|
+
const def = variableDefs.get(name);
|
|
3599
|
+
if (def === void 0) {
|
|
3600
|
+
throw new BatchAnalysisError(
|
|
3601
|
+
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
3602
|
+
index
|
|
3603
|
+
);
|
|
3604
|
+
}
|
|
3605
|
+
def.referencedBy.push(index);
|
|
3606
|
+
}
|
|
3607
|
+
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
3608
|
+
if (variableDefs.has(stmt.name)) {
|
|
3609
|
+
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
3610
|
+
}
|
|
3611
|
+
variableDefs.set(stmt.name, { index, referencedBy: [] });
|
|
3612
|
+
variableOrder.push(stmt.name);
|
|
3613
|
+
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
3614
|
+
throw new BatchAnalysisError(
|
|
3615
|
+
`ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
|
|
3616
|
+
index
|
|
3617
|
+
);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
3621
|
+
collectRefs(stmt.query, refs, stmtAppIds);
|
|
3622
|
+
} else if (stmt.type === "DROP_TEMP_TABLE") {
|
|
3623
|
+
} else {
|
|
3624
|
+
collectRefs(stmt, refs, stmtAppIds);
|
|
3625
|
+
}
|
|
3626
|
+
let tempOnlySource = false;
|
|
3627
|
+
if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
|
|
3628
|
+
const srcTemp = /* @__PURE__ */ new Set();
|
|
3629
|
+
const srcApps = /* @__PURE__ */ new Set();
|
|
3630
|
+
collectRefs(stmt.select, srcTemp, srcApps);
|
|
3631
|
+
tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
|
|
3632
|
+
}
|
|
3633
|
+
for (const name of refs) {
|
|
3634
|
+
const at = defined.get(name);
|
|
3635
|
+
if (at === void 0) {
|
|
3636
|
+
throw new BatchAnalysisError(
|
|
3637
|
+
`ParseError: temp table ${name} is not defined in this batch.`,
|
|
3638
|
+
index
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
dependsOn.add(at);
|
|
3642
|
+
}
|
|
3643
|
+
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
3644
|
+
if (defined.has(stmt.name)) {
|
|
3645
|
+
throw new BatchAnalysisError(
|
|
3646
|
+
`ParseError: temp table ${stmt.name} is already defined.`,
|
|
3647
|
+
index
|
|
3648
|
+
);
|
|
3649
|
+
}
|
|
3650
|
+
defined.set(stmt.name, index);
|
|
3651
|
+
createdOrder.push(stmt.name);
|
|
3652
|
+
created.push(stmt.name);
|
|
3653
|
+
if (defined.size > MAX_TEMP_TABLES) {
|
|
3654
|
+
throw new BatchAnalysisError(
|
|
3655
|
+
`ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
|
|
3656
|
+
index
|
|
3657
|
+
);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
if (stmt.type === "DROP_TEMP_TABLE") {
|
|
3661
|
+
const at = defined.get(stmt.name);
|
|
3662
|
+
if (at === void 0) {
|
|
3663
|
+
throw new BatchAnalysisError(
|
|
3664
|
+
`ParseError: temp table ${stmt.name} is not defined in this batch.`,
|
|
3665
|
+
index
|
|
3666
|
+
);
|
|
3667
|
+
}
|
|
3668
|
+
dependsOn.add(at);
|
|
3669
|
+
dropped.push(stmt.name);
|
|
3670
|
+
defined.delete(stmt.name);
|
|
3671
|
+
}
|
|
3672
|
+
results.push({
|
|
3673
|
+
index,
|
|
3674
|
+
statementType,
|
|
3675
|
+
isDml: isDmlType(statementType),
|
|
3676
|
+
isReadOnly: isReadOnlyType(statementType),
|
|
3677
|
+
hasWhere: hasWhereClause(stmt),
|
|
3678
|
+
insertValuesCount: getInsertValuesCount(stmt),
|
|
3679
|
+
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
3680
|
+
tempTablesCreated: created,
|
|
3681
|
+
tempTablesReferenced: [...refs],
|
|
3682
|
+
tempTablesDropped: dropped,
|
|
3683
|
+
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
3684
|
+
tempOnlySource,
|
|
3685
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
3686
|
+
});
|
|
3687
|
+
});
|
|
3688
|
+
const containsDml = results.some((r) => r.isDml);
|
|
3689
|
+
const variables = variableOrder.map((name) => ({
|
|
3690
|
+
name,
|
|
3691
|
+
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
3692
|
+
}));
|
|
3693
|
+
return {
|
|
3694
|
+
statementCount: statements.length,
|
|
3695
|
+
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
3696
|
+
containsDml,
|
|
3697
|
+
tempTables: createdOrder,
|
|
3698
|
+
variables,
|
|
3699
|
+
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
3700
|
+
statements: results
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// src/core/batchVariables.ts
|
|
3705
|
+
var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
3706
|
+
function normalizeBatchVariableName(name) {
|
|
3707
|
+
if (!VARIABLE_NAME_RE.test(name)) {
|
|
3708
|
+
throw new Error(
|
|
3709
|
+
`ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
return name.toLowerCase();
|
|
3713
|
+
}
|
|
3714
|
+
function normalizeBatchVariables(input) {
|
|
3715
|
+
const normalized = /* @__PURE__ */ Object.create(null);
|
|
3716
|
+
for (const [rawName, value] of Object.entries(input ?? {})) {
|
|
3717
|
+
const name = normalizeBatchVariableName(rawName);
|
|
3718
|
+
if (Object.prototype.hasOwnProperty.call(normalized, name)) {
|
|
3719
|
+
throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
|
|
3720
|
+
}
|
|
3721
|
+
normalized[name] = value;
|
|
3722
|
+
}
|
|
3723
|
+
return normalized;
|
|
3237
3724
|
}
|
|
3238
|
-
function
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
names.add(col.alias ?? `'${col.value}'`);
|
|
3247
|
-
continue;
|
|
3248
|
-
}
|
|
3249
|
-
if (col.type === "AGGREGATE") {
|
|
3250
|
-
if (col.alias) names.add(col.alias);
|
|
3251
|
-
else names.add(aggregateSyntheticName(col.func, col.distinct, col.arg));
|
|
3252
|
-
continue;
|
|
3253
|
-
}
|
|
3254
|
-
if (col.type === "ARITH_AGG_COL") {
|
|
3255
|
-
if (col.alias) names.add(col.alias);
|
|
3256
|
-
continue;
|
|
3257
|
-
}
|
|
3258
|
-
if (col.type === "ARITH_COL") {
|
|
3259
|
-
if (col.alias) names.add(col.alias);
|
|
3260
|
-
continue;
|
|
3261
|
-
}
|
|
3262
|
-
if (col.type === "CASE_COL") {
|
|
3263
|
-
names.add(col.alias ?? "case");
|
|
3264
|
-
continue;
|
|
3265
|
-
}
|
|
3266
|
-
if (col.type === "STRFUNC_COL") {
|
|
3267
|
-
if (col.alias) names.add(col.alias);
|
|
3268
|
-
continue;
|
|
3269
|
-
}
|
|
3270
|
-
if (col.type === "SCALAR_SUBQUERY_COL") {
|
|
3271
|
-
names.add(col.alias ?? "(subquery)");
|
|
3725
|
+
function validateDeclaredBatchVariables(statements, input) {
|
|
3726
|
+
const normalized = normalizeBatchVariables(input);
|
|
3727
|
+
const declared = new Set(
|
|
3728
|
+
statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
|
|
3729
|
+
);
|
|
3730
|
+
for (const name of Object.keys(normalized)) {
|
|
3731
|
+
if (!declared.has(name)) {
|
|
3732
|
+
throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
|
|
3272
3733
|
}
|
|
3273
3734
|
}
|
|
3274
|
-
return
|
|
3275
|
-
}
|
|
3276
|
-
function aggregateSyntheticName(func, distinct, arg) {
|
|
3277
|
-
const argStr = arg.type === "WILDCARD" ? "*" : arithNodeLabel(arg);
|
|
3278
|
-
return distinct ? `${func}(DISTINCT ${argStr})` : `${func}(${argStr})`;
|
|
3279
|
-
}
|
|
3280
|
-
function arithNodeLabel(node) {
|
|
3281
|
-
if (node.type === "FIELD_REF") return node.field;
|
|
3282
|
-
if (node.type === "NUMBER") return String(node.value);
|
|
3283
|
-
if (node.type === "STRING_FUNC") return stringFuncLabel(node);
|
|
3284
|
-
return `(${arithNodeLabel(node.left)}${node.op}${arithNodeLabel(node.right)})`;
|
|
3285
|
-
}
|
|
3286
|
-
function stringFuncLabel(expr) {
|
|
3287
|
-
const args = expr.args.map((a) => {
|
|
3288
|
-
if (a.type === "STRING") return `'${a.value}'`;
|
|
3289
|
-
if (a.type === "STRING_FUNC") return stringFuncLabel(a);
|
|
3290
|
-
if (a.type === "AGG_REF") return aggregateSyntheticName(a.func, a.distinct, a.arg);
|
|
3291
|
-
if (a.type === "AGG_ARITH") return "agg_arith";
|
|
3292
|
-
return arithNodeLabel(a);
|
|
3293
|
-
});
|
|
3294
|
-
return `${expr.func}(${args.join(",")})`;
|
|
3735
|
+
return normalized;
|
|
3295
3736
|
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3737
|
+
|
|
3738
|
+
// src/core/scalarCompare.ts
|
|
3739
|
+
function compareScalarValues(op, leftStr, rightStr) {
|
|
3740
|
+
if (op === "=") return leftStr === rightStr;
|
|
3741
|
+
if (op === "!=" || op === "<>") return leftStr !== rightStr;
|
|
3742
|
+
const rightNum = Number(rightStr);
|
|
3743
|
+
if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
|
|
3744
|
+
return op === "<" || op === "<=";
|
|
3745
|
+
}
|
|
3746
|
+
const leftNum = Number(leftStr);
|
|
3747
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
3748
|
+
switch (op) {
|
|
3749
|
+
case ">":
|
|
3750
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
3751
|
+
case "<":
|
|
3752
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
3753
|
+
case ">=":
|
|
3754
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
3755
|
+
case "<=":
|
|
3756
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
3757
|
+
}
|
|
3298
3758
|
}
|
|
3299
3759
|
|
|
3300
3760
|
// src/engine/evalFunc.ts
|
|
@@ -3508,25 +3968,29 @@ function resolveFieldRef(row, field) {
|
|
|
3508
3968
|
}
|
|
3509
3969
|
|
|
3510
3970
|
// src/engine/evalWhere.ts
|
|
3511
|
-
function evalWhere(expr, row, resolveFieldType) {
|
|
3971
|
+
function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
|
|
3512
3972
|
switch (expr.type) {
|
|
3513
3973
|
case "BINARY":
|
|
3514
|
-
return evalBinary(expr, row, resolveFieldType);
|
|
3974
|
+
return evalBinary(expr, row, resolveFieldType, appliedKlikes);
|
|
3515
3975
|
case "NULL_CHECK":
|
|
3516
3976
|
return evalNullCheck(expr, row);
|
|
3517
3977
|
case "LOGICAL":
|
|
3518
|
-
return evalLogical(expr, row, resolveFieldType);
|
|
3978
|
+
return evalLogical(expr, row, resolveFieldType, appliedKlikes);
|
|
3519
3979
|
case "NOT":
|
|
3520
|
-
return !evalWhere(expr.expr, row, resolveFieldType);
|
|
3980
|
+
return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
|
|
3521
3981
|
case "GROUP":
|
|
3522
|
-
return evalWhere(expr.expr, row, resolveFieldType);
|
|
3982
|
+
return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
|
|
3523
3983
|
case "EXISTS": {
|
|
3524
3984
|
const exists = expr.resolved;
|
|
3525
3985
|
return expr.not ? !exists : exists;
|
|
3526
3986
|
}
|
|
3527
3987
|
}
|
|
3528
3988
|
}
|
|
3529
|
-
function evalBinary(expr, row, resolveFieldType) {
|
|
3989
|
+
function evalBinary(expr, row, resolveFieldType, appliedKlikes) {
|
|
3990
|
+
if (expr.op === "KLIKE" || expr.op === "NOT_KLIKE") {
|
|
3991
|
+
if (appliedKlikes?.has(expr)) return true;
|
|
3992
|
+
throw new Error("KLIKE / NOT KLIKE \u306F\u62BC\u3057\u4E0B\u3052\u6E08\u307F\u96C6\u5408\u306B\u542B\u307E\u308C\u306A\u3044\u305F\u3081 JavaScript \u5074\u3067\u306F\u8A55\u4FA1\u3067\u304D\u307E\u305B\u3093");
|
|
3993
|
+
}
|
|
3530
3994
|
const left = resolveField(expr.left, row, resolveFieldType);
|
|
3531
3995
|
const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
|
|
3532
3996
|
return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
|
|
@@ -3553,6 +4017,9 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType) {
|
|
|
3553
4017
|
const pattern = resolveValue(right, row, resolveFieldType);
|
|
3554
4018
|
return !matchLike(leftStr, pattern);
|
|
3555
4019
|
}
|
|
4020
|
+
if (op === "KLIKE" || op === "NOT_KLIKE") {
|
|
4021
|
+
throw new Error("KLIKE / NOT KLIKE \u306F JavaScript \u5074\u3067\u306F\u8A55\u4FA1\u3067\u304D\u307E\u305B\u3093\uFF08SIMPLE SELECT \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\uFF09");
|
|
4022
|
+
}
|
|
3556
4023
|
const rightStr = resolveValue(right, row, resolveFieldType);
|
|
3557
4024
|
return compareScalarValues(op, leftStr, rightStr);
|
|
3558
4025
|
}
|
|
@@ -3605,11 +4072,11 @@ function evalNullCheck(expr, row) {
|
|
|
3605
4072
|
const val = resolveField(expr.field, row);
|
|
3606
4073
|
return expr.not ? val !== "" : val === "";
|
|
3607
4074
|
}
|
|
3608
|
-
function evalLogical(expr, row, resolveFieldType) {
|
|
4075
|
+
function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
|
|
3609
4076
|
if (expr.op === "AND") {
|
|
3610
|
-
return evalWhere(expr.left, row, resolveFieldType) && evalWhere(expr.right, row, resolveFieldType);
|
|
4077
|
+
return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
|
|
3611
4078
|
}
|
|
3612
|
-
return evalWhere(expr.left, row, resolveFieldType) || evalWhere(expr.right, row, resolveFieldType);
|
|
4079
|
+
return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
|
|
3613
4080
|
}
|
|
3614
4081
|
function resolveField(field, row, resolveFieldType) {
|
|
3615
4082
|
if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
|
|
@@ -3708,6 +4175,11 @@ function matchLike(value, pattern) {
|
|
|
3708
4175
|
|
|
3709
4176
|
// src/converter/dmlToKintone.ts
|
|
3710
4177
|
function assertDmlWhereIsSafe(where) {
|
|
4178
|
+
if (whereHasKlike(where)) {
|
|
4179
|
+
throw new DmlConvertError(
|
|
4180
|
+
"UPDATE / DELETE \u306E WHERE \u306B KLIKE / NOT KLIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002kintone \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u306E\u6253\u3061\u5207\u308A\u3092\u691C\u51FA\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u5168 DML \u3067\u5B89\u5168\u4E0A\u62D2\u5426\u3057\u3066\u3044\u307E\u3059\u3002"
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
3711
4183
|
if (!whereHasLike(where)) return;
|
|
3712
4184
|
throw new DmlConvertError(
|
|
3713
4185
|
"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"
|
|
@@ -4195,92 +4667,6 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
|
|
|
4195
4667
|
};
|
|
4196
4668
|
}
|
|
4197
4669
|
|
|
4198
|
-
// src/core/optimization/wherePredicatePushdown.ts
|
|
4199
|
-
function extractSafePushdownLeaves(where, options = {}) {
|
|
4200
|
-
return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
|
|
4201
|
-
}
|
|
4202
|
-
function extractTypedPushdownCandidates(where, options = {}) {
|
|
4203
|
-
return extractAndLeaves(
|
|
4204
|
-
where,
|
|
4205
|
-
(expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
|
|
4206
|
-
);
|
|
4207
|
-
}
|
|
4208
|
-
function extractAndLeaves(where, accept) {
|
|
4209
|
-
switch (where.type) {
|
|
4210
|
-
case "BINARY":
|
|
4211
|
-
return accept(where) ? where : null;
|
|
4212
|
-
case "LOGICAL":
|
|
4213
|
-
if (where.op !== "AND") return null;
|
|
4214
|
-
{
|
|
4215
|
-
const left = extractAndLeaves(where.left, accept);
|
|
4216
|
-
const right = extractAndLeaves(where.right, accept);
|
|
4217
|
-
if (left && right) return { ...where, left, right };
|
|
4218
|
-
return left ?? right ?? null;
|
|
4219
|
-
}
|
|
4220
|
-
case "GROUP":
|
|
4221
|
-
return extractAndLeaves(where.expr, accept);
|
|
4222
|
-
case "NULL_CHECK":
|
|
4223
|
-
case "NOT":
|
|
4224
|
-
case "EXISTS":
|
|
4225
|
-
return null;
|
|
4226
|
-
}
|
|
4227
|
-
}
|
|
4228
|
-
function isSafeComparison(expr, options) {
|
|
4229
|
-
if (isSafeIdComparison(expr, options)) return true;
|
|
4230
|
-
if (isNumericCandidate(expr, options)) {
|
|
4231
|
-
return options.fieldTypes?.get(expr.left.field) === "NUMBER";
|
|
4232
|
-
}
|
|
4233
|
-
return isSelectionInComparison(expr, options);
|
|
4234
|
-
}
|
|
4235
|
-
var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
4236
|
-
"DROP_DOWN",
|
|
4237
|
-
"RADIO_BUTTON",
|
|
4238
|
-
"CHECK_BOX",
|
|
4239
|
-
"MULTI_SELECT",
|
|
4240
|
-
"STATUS"
|
|
4241
|
-
]);
|
|
4242
|
-
function isSelectionInComparison(expr, options) {
|
|
4243
|
-
if (!isSelectionInCandidate(expr, options)) return false;
|
|
4244
|
-
if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
|
|
4245
|
-
const fieldType = options.fieldTypes?.get(expr.left.field);
|
|
4246
|
-
if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
|
|
4247
|
-
const validOptions = options.fieldOptions?.get(expr.left.field);
|
|
4248
|
-
if (validOptions === void 0) return false;
|
|
4249
|
-
return expr.right.values.every(
|
|
4250
|
-
(value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
|
|
4251
|
-
);
|
|
4252
|
-
}
|
|
4253
|
-
function isSafeIdComparison(expr, options) {
|
|
4254
|
-
if (!isTargetIdField(expr.left, options)) return false;
|
|
4255
|
-
if (expr.right.type !== "NUMBER") return false;
|
|
4256
|
-
return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
|
|
4257
|
-
}
|
|
4258
|
-
function isTargetIdField(field, options) {
|
|
4259
|
-
if (field.type !== "FIELD" || field.field !== "$id") return false;
|
|
4260
|
-
const targetAlias = options.tableAlias ?? null;
|
|
4261
|
-
if (field.tableAlias === targetAlias) return true;
|
|
4262
|
-
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4263
|
-
}
|
|
4264
|
-
function isNumericCandidate(expr, options) {
|
|
4265
|
-
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4266
|
-
if (!isTargetField(expr.left, options)) return false;
|
|
4267
|
-
if (expr.right.type !== "NUMBER") return false;
|
|
4268
|
-
if (expr.op === "=") return true;
|
|
4269
|
-
return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
|
|
4270
|
-
}
|
|
4271
|
-
function isSelectionInCandidate(expr, options) {
|
|
4272
|
-
if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
|
|
4273
|
-
if (!isTargetField(expr.left, options)) return false;
|
|
4274
|
-
if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
|
|
4275
|
-
if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
|
|
4276
|
-
return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
|
|
4277
|
-
}
|
|
4278
|
-
function isTargetField(field, options) {
|
|
4279
|
-
const targetAlias = options.tableAlias ?? null;
|
|
4280
|
-
if (field.tableAlias === targetAlias) return true;
|
|
4281
|
-
return options.allowUnqualifiedFields === true && field.tableAlias === null;
|
|
4282
|
-
}
|
|
4283
|
-
|
|
4284
4670
|
// src/engine/process.ts
|
|
4285
4671
|
function flatten(record, alias) {
|
|
4286
4672
|
const row = {};
|
|
@@ -4345,9 +4731,9 @@ function applyJoin(leftRows, rightRows, join2) {
|
|
|
4345
4731
|
}
|
|
4346
4732
|
return result;
|
|
4347
4733
|
}
|
|
4348
|
-
function applyFilter(rows, where, resolveFieldType) {
|
|
4734
|
+
function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
|
|
4349
4735
|
if (where === null) return rows;
|
|
4350
|
-
return rows.filter((row) => evalWhere(where, row, resolveFieldType));
|
|
4736
|
+
return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
|
|
4351
4737
|
}
|
|
4352
4738
|
function hasAggregateColumns(columns) {
|
|
4353
4739
|
return columns.some(
|
|
@@ -4804,7 +5190,8 @@ function runFullScan(input) {
|
|
|
4804
5190
|
optionOrders,
|
|
4805
5191
|
sortKinds,
|
|
4806
5192
|
fieldTypeResolver,
|
|
4807
|
-
havingFieldTypeResolver
|
|
5193
|
+
havingFieldTypeResolver,
|
|
5194
|
+
appliedKlikes
|
|
4808
5195
|
} = input;
|
|
4809
5196
|
let rows = [];
|
|
4810
5197
|
const mainAlias = stmt.from.alias;
|
|
@@ -4816,7 +5203,7 @@ function runFullScan(input) {
|
|
|
4816
5203
|
const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
|
|
4817
5204
|
rows = applyJoin(rows, rightRows, join2);
|
|
4818
5205
|
}
|
|
4819
|
-
rows = applyFilter(rows, stmt.where, fieldTypeResolver);
|
|
5206
|
+
rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
|
|
4820
5207
|
if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
|
|
4821
5208
|
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
|
|
4822
5209
|
}
|
|
@@ -4937,6 +5324,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4937
5324
|
if (unresolved !== null) {
|
|
4938
5325
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
4939
5326
|
}
|
|
5327
|
+
validateKlikeStatement(stmt);
|
|
4940
5328
|
switch (stmt.type) {
|
|
4941
5329
|
case "SELECT":
|
|
4942
5330
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -5073,6 +5461,7 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5073
5461
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
5074
5462
|
if (stmt.type === "SET_VARIABLE") {
|
|
5075
5463
|
const resolvedStmt2 = resolveVariableRefs(stmt, variables);
|
|
5464
|
+
validateKlikeStatement(resolvedStmt2);
|
|
5076
5465
|
if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
5077
5466
|
try {
|
|
5078
5467
|
const value = await evaluateScalarSubquery(
|
|
@@ -5105,6 +5494,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
5105
5494
|
return {};
|
|
5106
5495
|
}
|
|
5107
5496
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
5497
|
+
validateKlikeStatement(resolvedStmt);
|
|
5108
5498
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
5109
5499
|
const materializeOptions = {
|
|
5110
5500
|
...options,
|
|
@@ -5501,23 +5891,6 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
|
|
|
5501
5891
|
}
|
|
5502
5892
|
}
|
|
5503
5893
|
}
|
|
5504
|
-
function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
|
|
5505
|
-
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5506
|
-
if (stmt.joins.length === 0) {
|
|
5507
|
-
return extractSafePushdownLeaves(stmt.where, {
|
|
5508
|
-
tableAlias: stmt.from.alias ?? void 0,
|
|
5509
|
-
allowUnqualifiedFields: true,
|
|
5510
|
-
fieldTypes,
|
|
5511
|
-
fieldOptions
|
|
5512
|
-
});
|
|
5513
|
-
}
|
|
5514
|
-
if (!stmt.from.alias) return null;
|
|
5515
|
-
return extractSafePushdownLeaves(stmt.where, {
|
|
5516
|
-
tableAlias: stmt.from.alias,
|
|
5517
|
-
fieldTypes,
|
|
5518
|
-
fieldOptions
|
|
5519
|
-
});
|
|
5520
|
-
}
|
|
5521
5894
|
function extractMainTypedPushdownCandidate(stmt) {
|
|
5522
5895
|
if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
|
|
5523
5896
|
if (stmt.joins.length === 0) {
|
|
@@ -5699,23 +6072,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
5699
6072
|
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
5700
6073
|
]);
|
|
5701
6074
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
5702
|
-
const
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
);
|
|
5707
|
-
const tableConditions = /* @__PURE__ */ new Map();
|
|
5708
|
-
if (stmt.where !== null) {
|
|
5709
|
-
for (const join2 of stmt.joins) {
|
|
5710
|
-
if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
|
|
5711
|
-
const cond = extractSafePushdownLeaves(stmt.where, {
|
|
5712
|
-
tableAlias: join2.table.alias,
|
|
5713
|
-
fieldTypes: pushdownMeta.fieldTypesByApp.get(join2.table.appId),
|
|
5714
|
-
fieldOptions: pushdownMeta.fieldOptionsByApp.get(join2.table.appId)
|
|
5715
|
-
});
|
|
5716
|
-
if (cond) tableConditions.set(join2.table.alias, cond);
|
|
5717
|
-
}
|
|
5718
|
-
}
|
|
6075
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
6076
|
+
validateKlikePushdownPlan(pushdownPlan);
|
|
6077
|
+
const mainPushDown = pushdownPlan.mainCondition;
|
|
6078
|
+
const tableConditions = pushdownPlan.joinConditions;
|
|
5719
6079
|
const mainFetch = fetchTableRecordsForFullScan(
|
|
5720
6080
|
stmt,
|
|
5721
6081
|
stmt.from,
|
|
@@ -5796,7 +6156,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
5796
6156
|
optionOrders,
|
|
5797
6157
|
sortKinds,
|
|
5798
6158
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
5799
|
-
havingFieldTypeResolver: fieldTypeResolvers.having
|
|
6159
|
+
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
6160
|
+
appliedKlikes: pushdownPlan.appliedKlikes
|
|
5800
6161
|
});
|
|
5801
6162
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
5802
6163
|
}
|
|
@@ -5845,83 +6206,6 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
|
|
|
5845
6206
|
}
|
|
5846
6207
|
return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
|
|
5847
6208
|
}
|
|
5848
|
-
function canInlineSingleCte(stmt) {
|
|
5849
|
-
if (stmt.ctes.length !== 1) return false;
|
|
5850
|
-
const cteDef = stmt.ctes[0];
|
|
5851
|
-
if (cteDef.query.type !== "SELECT") return false;
|
|
5852
|
-
if (resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
|
|
5853
|
-
const finalQuery = stmt.query;
|
|
5854
|
-
if (finalQuery.type !== "SELECT") return false;
|
|
5855
|
-
if (finalQuery.from.cteName !== cteDef.name) return false;
|
|
5856
|
-
if (finalQuery.joins.length > 0) return false;
|
|
5857
|
-
if (finalQuery.groupBy.length > 0) return false;
|
|
5858
|
-
if (finalQuery.distinct) return false;
|
|
5859
|
-
if (finalQuery.columns.some(
|
|
5860
|
-
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"
|
|
5861
|
-
)) return false;
|
|
5862
|
-
return true;
|
|
5863
|
-
}
|
|
5864
|
-
function buildInlinedQuery(stmt) {
|
|
5865
|
-
const cteBody = stmt.ctes[0].query;
|
|
5866
|
-
const final = stmt.query;
|
|
5867
|
-
const cteAlias = final.from.alias;
|
|
5868
|
-
const finalWhere = stripCteAlias(final.where, cteAlias);
|
|
5869
|
-
let mergedWhere;
|
|
5870
|
-
if (cteBody.where === null) mergedWhere = finalWhere;
|
|
5871
|
-
else if (finalWhere === null) mergedWhere = cteBody.where;
|
|
5872
|
-
else mergedWhere = { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
|
|
5873
|
-
const columns = final.columns.every((c) => c.type === "WILDCARD") ? cteBody.columns : final.columns;
|
|
5874
|
-
return {
|
|
5875
|
-
type: "SELECT",
|
|
5876
|
-
from: cteBody.from,
|
|
5877
|
-
joins: [],
|
|
5878
|
-
columns,
|
|
5879
|
-
where: mergedWhere,
|
|
5880
|
-
groupBy: [],
|
|
5881
|
-
having: null,
|
|
5882
|
-
orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
|
|
5883
|
-
limit: final.limit ?? cteBody.limit,
|
|
5884
|
-
offset: final.offset ?? cteBody.offset,
|
|
5885
|
-
distinct: false
|
|
5886
|
-
};
|
|
5887
|
-
}
|
|
5888
|
-
function stripCteAlias(where, alias) {
|
|
5889
|
-
if (where === null || alias === null) return where;
|
|
5890
|
-
switch (where.type) {
|
|
5891
|
-
case "BINARY":
|
|
5892
|
-
return {
|
|
5893
|
-
type: "BINARY",
|
|
5894
|
-
op: where.op,
|
|
5895
|
-
left: stripCteAliasFromFieldValue(where.left, alias),
|
|
5896
|
-
right: where.right
|
|
5897
|
-
};
|
|
5898
|
-
case "NULL_CHECK":
|
|
5899
|
-
return {
|
|
5900
|
-
type: "NULL_CHECK",
|
|
5901
|
-
not: where.not,
|
|
5902
|
-
field: stripCteAliasFromFieldValue(where.field, alias)
|
|
5903
|
-
};
|
|
5904
|
-
case "LOGICAL":
|
|
5905
|
-
return {
|
|
5906
|
-
type: "LOGICAL",
|
|
5907
|
-
op: where.op,
|
|
5908
|
-
left: stripCteAlias(where.left, alias),
|
|
5909
|
-
right: stripCteAlias(where.right, alias)
|
|
5910
|
-
};
|
|
5911
|
-
case "NOT":
|
|
5912
|
-
return { type: "NOT", expr: stripCteAlias(where.expr, alias) };
|
|
5913
|
-
case "GROUP":
|
|
5914
|
-
return { type: "GROUP", expr: stripCteAlias(where.expr, alias) };
|
|
5915
|
-
case "EXISTS":
|
|
5916
|
-
return where;
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
function stripCteAliasFromFieldValue(fv, alias) {
|
|
5920
|
-
if (fv.type === "FIELD" && fv.tableAlias === alias) {
|
|
5921
|
-
return { type: "FIELD", field: fv.field, tableAlias: null };
|
|
5922
|
-
}
|
|
5923
|
-
return fv;
|
|
5924
|
-
}
|
|
5925
6209
|
async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
|
|
5926
6210
|
if (query.type === "UNION") {
|
|
5927
6211
|
const [leftResult, rightResult] = await Promise.all([
|
|
@@ -5956,8 +6240,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
5956
6240
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
|
|
5957
6241
|
resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
|
|
5958
6242
|
]);
|
|
5959
|
-
const typedInFieldTypes = await
|
|
6243
|
+
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
6244
|
+
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
6245
|
+
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
6246
|
+
]);
|
|
5960
6247
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
6248
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
6249
|
+
validateKlikePushdownPlan(pushdownPlan);
|
|
5961
6250
|
const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
|
|
5962
6251
|
const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
|
|
5963
6252
|
scalarCachePromise.catch(() => {
|
|
@@ -5977,7 +6266,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
5977
6266
|
parallel,
|
|
5978
6267
|
true,
|
|
5979
6268
|
options.onLimitReached ?? "error",
|
|
5980
|
-
warnings
|
|
6269
|
+
warnings,
|
|
6270
|
+
pushdownPlan.mainCondition
|
|
5981
6271
|
);
|
|
5982
6272
|
tables.set(stmt.from.alias, mainRecords);
|
|
5983
6273
|
}
|
|
@@ -5986,6 +6276,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
5986
6276
|
const rows2 = cteCache.get(join2.table.cteName) ?? [];
|
|
5987
6277
|
tables.set(join2.table.alias, rows2.map(processRowToKintoneRecord));
|
|
5988
6278
|
} else {
|
|
6279
|
+
const pushDownCond = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
|
|
5989
6280
|
const optimized = await tryFetchJoinRecordsBySourceKeys(
|
|
5990
6281
|
stmt,
|
|
5991
6282
|
join2,
|
|
@@ -5994,7 +6285,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
5994
6285
|
maxRecords,
|
|
5995
6286
|
parallel,
|
|
5996
6287
|
options.onLimitReached ?? "error",
|
|
5997
|
-
warnings
|
|
6288
|
+
warnings,
|
|
6289
|
+
pushDownCond
|
|
5998
6290
|
);
|
|
5999
6291
|
const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
|
|
6000
6292
|
stmt,
|
|
@@ -6004,7 +6296,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6004
6296
|
parallel,
|
|
6005
6297
|
false,
|
|
6006
6298
|
options.onLimitReached ?? "error",
|
|
6007
|
-
warnings
|
|
6299
|
+
warnings,
|
|
6300
|
+
pushDownCond
|
|
6008
6301
|
);
|
|
6009
6302
|
tables.set(join2.table.alias, joinRecords);
|
|
6010
6303
|
}
|
|
@@ -6019,7 +6312,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6019
6312
|
optionOrders,
|
|
6020
6313
|
sortKinds,
|
|
6021
6314
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
6022
|
-
havingFieldTypeResolver: fieldTypeResolvers.having
|
|
6315
|
+
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
6316
|
+
appliedKlikes: pushdownPlan.appliedKlikes
|
|
6023
6317
|
});
|
|
6024
6318
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
6025
6319
|
}
|
|
@@ -6973,7 +7267,9 @@ async function executeDescribe(stmt, client, cacheContext) {
|
|
|
6973
7267
|
function parseSql(sql) {
|
|
6974
7268
|
try {
|
|
6975
7269
|
const tokens = new Lexer(sql).tokenize();
|
|
6976
|
-
|
|
7270
|
+
const stmt = new Parser(tokens).parse();
|
|
7271
|
+
validateKlikeStatement(stmt);
|
|
7272
|
+
return stmt;
|
|
6977
7273
|
} catch (e) {
|
|
6978
7274
|
if (e instanceof LexError || e instanceof ParseError) {
|
|
6979
7275
|
throw e;
|
|
@@ -7084,6 +7380,7 @@ function buildBatchExplainPlans(sql, injectedVariables) {
|
|
|
7084
7380
|
statementCount: statements.length,
|
|
7085
7381
|
statements: statements.map((stmt, i) => {
|
|
7086
7382
|
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
|
|
7383
|
+
validateKlikeStatement(planStmt);
|
|
7087
7384
|
const result = {
|
|
7088
7385
|
index: i,
|
|
7089
7386
|
type: analysis.statements[i].statementType,
|
|
@@ -7225,9 +7522,10 @@ function buildSelectPlan(stmt, label) {
|
|
|
7225
7522
|
lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
|
|
7226
7523
|
lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
|
|
7227
7524
|
} else {
|
|
7525
|
+
const pushdownPlan = buildKlikePushdownPlan(stmt);
|
|
7228
7526
|
const mainFields = selectToFetchAllFields(stmt, stmt.from);
|
|
7229
7527
|
const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
|
|
7230
|
-
const mainPushDown =
|
|
7528
|
+
const mainPushDown = pushdownPlan.mainCondition;
|
|
7231
7529
|
const mainCandidate = extractMainTypedPushdownCandidate(stmt);
|
|
7232
7530
|
const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
7233
7531
|
lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
|
|
@@ -7240,7 +7538,7 @@ function buildSelectPlan(stmt, label) {
|
|
|
7240
7538
|
const joinFields = selectToFetchAllFields(stmt, join2.table);
|
|
7241
7539
|
const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
|
|
7242
7540
|
const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
|
|
7243
|
-
const joinPushDown = join2.table.alias
|
|
7541
|
+
const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
|
|
7244
7542
|
const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
|
|
7245
7543
|
const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
|
|
7246
7544
|
lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
|
|
@@ -7283,6 +7581,10 @@ function buildWithPlan(stmt) {
|
|
|
7283
7581
|
if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
|
|
7284
7582
|
lines.push(...buildExplainPlan(stmt.query, "[main]"));
|
|
7285
7583
|
}
|
|
7584
|
+
if (canInlineSingleCte(stmt)) {
|
|
7585
|
+
lines.push("");
|
|
7586
|
+
lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
|
|
7587
|
+
}
|
|
7286
7588
|
return lines;
|
|
7287
7589
|
}
|
|
7288
7590
|
function collectFullScanReasons(stmt) {
|
|
@@ -7517,11 +7819,15 @@ var OperationCancelledError = class extends Error {
|
|
|
7517
7819
|
// src/core/sql.ts
|
|
7518
7820
|
function parseSqlStatement(sql) {
|
|
7519
7821
|
const tokens = new Lexer(sql).tokenize();
|
|
7520
|
-
|
|
7822
|
+
const stmt = new Parser(tokens).parse();
|
|
7823
|
+
validateKlikeStatement(stmt);
|
|
7824
|
+
return stmt;
|
|
7521
7825
|
}
|
|
7522
7826
|
function parseSqlStatements(sql) {
|
|
7523
7827
|
const tokens = new Lexer(sql).tokenize();
|
|
7524
|
-
|
|
7828
|
+
const statements = new Parser(tokens).parseStatements();
|
|
7829
|
+
statements.forEach(validateKlikeStatement);
|
|
7830
|
+
return statements;
|
|
7525
7831
|
}
|
|
7526
7832
|
|
|
7527
7833
|
// src/core/displayFormat.ts
|