@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 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 \u307E\u305F\u306F LIKE \u304C\u5FC5\u8981\u3067\u3059",
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
- // src/converter/whereToKintone.ts
2661
- function whereToKintone(expr) {
2662
- switch (expr.type) {
2663
- case "BINARY":
2664
- return convertBinary(expr);
2665
- case "NULL_CHECK":
2666
- return convertNullCheck(expr);
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 collectSelectOutputNames(columns) {
3239
- const names = /* @__PURE__ */ new Set();
3240
- for (const col of columns) {
3241
- if (col.type === "FIELD" && col.alias) {
3242
- names.add(col.alias);
3243
- continue;
3244
- }
3245
- if (col.type === "LITERAL_COL") {
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 names;
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
- function isAggregateSyntheticName(name) {
3297
- return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
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 mainPushDown = extractMainSafePushdown(
5703
- stmt,
5704
- pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
5705
- pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
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 loadTypedInFieldTypes(stmt, client, cacheContext);
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
- return new Parser(tokens).parse();
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 = extractMainSafePushdown(stmt);
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 && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join2.table.alias }) : null;
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
- return new Parser(tokens).parse();
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
- return new Parser(tokens).parseStatements();
7828
+ const statements = new Parser(tokens).parseStatements();
7829
+ statements.forEach(validateKlikeStatement);
7830
+ return statements;
7525
7831
  }
7526
7832
 
7527
7833
  // src/core/displayFormat.ts