@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.
@@ -1811,7 +1811,7 @@ var require_code2 = __commonJS({
1811
1811
  }
1812
1812
  }
1813
1813
  exports2.validateArray = validateArray;
1814
- function validateUnion(cxt) {
1814
+ function validateUnion2(cxt) {
1815
1815
  const { gen, schema, keyword, it } = cxt;
1816
1816
  if (!Array.isArray(schema))
1817
1817
  throw new Error("ajv implementation error");
@@ -1833,7 +1833,7 @@ var require_code2 = __commonJS({
1833
1833
  }));
1834
1834
  cxt.result(valid, () => cxt.reset(), () => cxt.error(true));
1835
1835
  }
1836
- exports2.validateUnion = validateUnion;
1836
+ exports2.validateUnion = validateUnion2;
1837
1837
  }
1838
1838
  });
1839
1839
 
@@ -31007,6 +31007,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
31007
31007
  ["IS", "IS" /* IS */],
31008
31008
  ["NULL", "NULL" /* NULL */],
31009
31009
  ["LIKE", "LIKE" /* LIKE */],
31010
+ ["KLIKE", "KLIKE" /* KLIKE */],
31010
31011
  ["IN", "IN" /* IN */],
31011
31012
  ["BETWEEN", "BETWEEN" /* BETWEEN */],
31012
31013
  ["TODAY", "TODAY" /* TODAY */],
@@ -32532,7 +32533,7 @@ var Parser = class {
32532
32533
  }
32533
32534
  return false;
32534
32535
  }
32535
- // 比較演算子: =, !=, <>, >, <, >=, <=, LIKE, IN, IS NULL
32536
+ // 比較演算子: =, !=, <>, >, <, >=, <=, LIKE, KLIKE, IN, IS NULL
32536
32537
  parseCompareExpr() {
32537
32538
  if (this.peek().kind === "(" /* LPAREN */ && !this.isArithParen()) {
32538
32539
  this.advance();
@@ -32568,8 +32569,12 @@ var Parser = class {
32568
32569
  const pattern = this.parseSqlValue();
32569
32570
  return { type: "BINARY", op: "NOT_LIKE", left: field, right: pattern };
32570
32571
  }
32572
+ if (this.consume("KLIKE" /* KLIKE */)) {
32573
+ const pattern = this.parseKlikePattern();
32574
+ return { type: "BINARY", op: "NOT_KLIKE", left: field, right: pattern };
32575
+ }
32571
32576
  throw new ParseError(
32572
- "NOT \u306E\u5F8C\u306B\u306F IN \u307E\u305F\u306F LIKE \u304C\u5FC5\u8981\u3067\u3059",
32577
+ "NOT \u306E\u5F8C\u306B\u306F IN\u3001LIKE\u3001KLIKE \u306E\u3044\u305A\u308C\u304B\u304C\u5FC5\u8981\u3067\u3059",
32573
32578
  this.peek()
32574
32579
  );
32575
32580
  }
@@ -32579,6 +32584,10 @@ var Parser = class {
32579
32584
  this.expect(")" /* RPAREN */);
32580
32585
  return { type: "BINARY", op: "IN", left: field, right: right2 };
32581
32586
  }
32587
+ if (this.consume("KLIKE" /* KLIKE */)) {
32588
+ const pattern = this.parseKlikePattern();
32589
+ return { type: "BINARY", op: "KLIKE", left: field, right: pattern };
32590
+ }
32582
32591
  const op = this.parseCompareOp();
32583
32592
  const right = this.parseSqlValue();
32584
32593
  return { type: "BINARY", op, left: field, right };
@@ -32604,11 +32613,27 @@ var Parser = class {
32604
32613
  return "LIKE";
32605
32614
  default:
32606
32615
  throw new ParseError(
32607
- "\u6BD4\u8F03\u6F14\u7B97\u5B50\uFF08=, !=, >, <, >=, <=, LIKE, IN, IS\uFF09\u304C\u5FC5\u8981\u3067\u3059",
32616
+ "\u6BD4\u8F03\u6F14\u7B97\u5B50\uFF08=, !=, >, <, >=, <=, LIKE, KLIKE, IN, IS\uFF09\u304C\u5FC5\u8981\u3067\u3059",
32608
32617
  tok
32609
32618
  );
32610
32619
  }
32611
32620
  }
32621
+ /** KLIKE / NOT KLIKE の右辺。kintone キーワードは文字列値だけを受け付ける。 */
32622
+ parseKlikePattern() {
32623
+ const tok = this.peek();
32624
+ if (tok.kind === "STRING" /* STRING */) {
32625
+ this.advance();
32626
+ return { type: "STRING", value: tok.value };
32627
+ }
32628
+ if (tok.kind === "VARIABLE" /* VARIABLE */) {
32629
+ this.advance();
32630
+ return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
32631
+ }
32632
+ throw new ParseError(
32633
+ "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",
32634
+ tok
32635
+ );
32636
+ }
32612
32637
  // WHERE / HAVING の左辺
32613
32638
  // - 文字列・数値関数: UPPER(f) / LENGTH(f) / ROUND(f, 2) ...
32614
32639
  // - 集計関数(HAVING のみ): COUNT(*) / SUM(f) ...
@@ -33247,238 +33272,6 @@ function getInsertValuesCount(stmt) {
33247
33272
  return Array.isArray(obj.values) ? obj.values.length : null;
33248
33273
  }
33249
33274
 
33250
- // src/core/batch.ts
33251
- var MAX_TEMP_TABLES = 16;
33252
- var MAX_BATCH_VARIABLES = 64;
33253
- var BatchAnalysisError = class extends Error {
33254
- constructor(message, statementIndex) {
33255
- super(message);
33256
- this.statementIndex = statementIndex;
33257
- }
33258
- };
33259
- function collectRefs(node, tempRefs, appIds) {
33260
- if (Array.isArray(node)) {
33261
- for (const v of node) collectRefs(v, tempRefs, appIds);
33262
- return;
33263
- }
33264
- if (node !== null && typeof node === "object") {
33265
- const obj = node;
33266
- const cte = obj["cteName"];
33267
- if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
33268
- const appId = obj["appId"];
33269
- if (typeof appId === "number" && appId > 0) appIds.add(appId);
33270
- for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
33271
- }
33272
- }
33273
- function collectVariableRefs(node, refs) {
33274
- if (Array.isArray(node)) {
33275
- for (const v of node) collectVariableRefs(v, refs);
33276
- return;
33277
- }
33278
- if (node !== null && typeof node === "object") {
33279
- const obj = node;
33280
- if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
33281
- refs.add(obj["name"]);
33282
- return;
33283
- }
33284
- for (const v of Object.values(obj)) collectVariableRefs(v, refs);
33285
- }
33286
- }
33287
- function analyzeBatch(statements) {
33288
- if (statements.length === 0) {
33289
- throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
33290
- }
33291
- if (statements.length === 1) {
33292
- const t = statements[0].type;
33293
- if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
33294
- const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
33295
- throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
33296
- }
33297
- if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
33298
- const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
33299
- throw new BatchAnalysisError(
33300
- `ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
33301
- 0
33302
- );
33303
- }
33304
- }
33305
- const defined = /* @__PURE__ */ new Map();
33306
- const createdOrder = [];
33307
- const results = [];
33308
- const variableDefs = /* @__PURE__ */ new Map();
33309
- const variableOrder = [];
33310
- statements.forEach((stmt, index) => {
33311
- const statementType = getStatementType(stmt);
33312
- const created = [];
33313
- const dropped = [];
33314
- const refs = /* @__PURE__ */ new Set();
33315
- const stmtAppIds = /* @__PURE__ */ new Set();
33316
- const dependsOn = /* @__PURE__ */ new Set();
33317
- const variableRefs = /* @__PURE__ */ new Set();
33318
- collectVariableRefs(stmt, variableRefs);
33319
- for (const name of variableRefs) {
33320
- const def = variableDefs.get(name);
33321
- if (def === void 0) {
33322
- throw new BatchAnalysisError(
33323
- `ParseError: variable @${name} is not defined before statement ${index + 1}.`,
33324
- index
33325
- );
33326
- }
33327
- def.referencedBy.push(index);
33328
- }
33329
- if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
33330
- if (variableDefs.has(stmt.name)) {
33331
- throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
33332
- }
33333
- variableDefs.set(stmt.name, { index, referencedBy: [] });
33334
- variableOrder.push(stmt.name);
33335
- if (variableOrder.length > MAX_BATCH_VARIABLES) {
33336
- throw new BatchAnalysisError(
33337
- `ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
33338
- index
33339
- );
33340
- }
33341
- }
33342
- if (stmt.type === "CREATE_TEMP_TABLE") {
33343
- collectRefs(stmt.query, refs, stmtAppIds);
33344
- } else if (stmt.type === "DROP_TEMP_TABLE") {
33345
- } else {
33346
- collectRefs(stmt, refs, stmtAppIds);
33347
- }
33348
- let tempOnlySource = false;
33349
- if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
33350
- const srcTemp = /* @__PURE__ */ new Set();
33351
- const srcApps = /* @__PURE__ */ new Set();
33352
- collectRefs(stmt.select, srcTemp, srcApps);
33353
- tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
33354
- }
33355
- for (const name of refs) {
33356
- const at = defined.get(name);
33357
- if (at === void 0) {
33358
- throw new BatchAnalysisError(
33359
- `ParseError: temp table ${name} is not defined in this batch.`,
33360
- index
33361
- );
33362
- }
33363
- dependsOn.add(at);
33364
- }
33365
- if (stmt.type === "CREATE_TEMP_TABLE") {
33366
- if (defined.has(stmt.name)) {
33367
- throw new BatchAnalysisError(
33368
- `ParseError: temp table ${stmt.name} is already defined.`,
33369
- index
33370
- );
33371
- }
33372
- defined.set(stmt.name, index);
33373
- createdOrder.push(stmt.name);
33374
- created.push(stmt.name);
33375
- if (defined.size > MAX_TEMP_TABLES) {
33376
- throw new BatchAnalysisError(
33377
- `ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
33378
- index
33379
- );
33380
- }
33381
- }
33382
- if (stmt.type === "DROP_TEMP_TABLE") {
33383
- const at = defined.get(stmt.name);
33384
- if (at === void 0) {
33385
- throw new BatchAnalysisError(
33386
- `ParseError: temp table ${stmt.name} is not defined in this batch.`,
33387
- index
33388
- );
33389
- }
33390
- dependsOn.add(at);
33391
- dropped.push(stmt.name);
33392
- defined.delete(stmt.name);
33393
- }
33394
- results.push({
33395
- index,
33396
- statementType,
33397
- isDml: isDmlType(statementType),
33398
- isReadOnly: isReadOnlyType(statementType),
33399
- hasWhere: hasWhereClause(stmt),
33400
- insertValuesCount: getInsertValuesCount(stmt),
33401
- appIds: [...stmtAppIds].sort((a, b) => a - b),
33402
- tempTablesCreated: created,
33403
- tempTablesReferenced: [...refs],
33404
- tempTablesDropped: dropped,
33405
- dependsOn: [...dependsOn].sort((a, b) => a - b),
33406
- tempOnlySource,
33407
- targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
33408
- });
33409
- });
33410
- const containsDml = results.some((r) => r.isDml);
33411
- const variables = variableOrder.map((name) => ({
33412
- name,
33413
- referencedBy: [...variableDefs.get(name).referencedBy]
33414
- }));
33415
- return {
33416
- statementCount: statements.length,
33417
- isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
33418
- containsDml,
33419
- tempTables: createdOrder,
33420
- variables,
33421
- warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
33422
- statements: results
33423
- };
33424
- }
33425
-
33426
- // src/core/batchVariables.ts
33427
- var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
33428
- function normalizeBatchVariableName(name) {
33429
- if (!VARIABLE_NAME_RE.test(name)) {
33430
- throw new Error(
33431
- `ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
33432
- );
33433
- }
33434
- return name.toLowerCase();
33435
- }
33436
- function normalizeBatchVariables(input) {
33437
- const normalized = /* @__PURE__ */ Object.create(null);
33438
- for (const [rawName, value] of Object.entries(input ?? {})) {
33439
- const name = normalizeBatchVariableName(rawName);
33440
- if (Object.prototype.hasOwnProperty.call(normalized, name)) {
33441
- throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
33442
- }
33443
- normalized[name] = value;
33444
- }
33445
- return normalized;
33446
- }
33447
- function validateDeclaredBatchVariables(statements, input) {
33448
- const normalized = normalizeBatchVariables(input);
33449
- const declared = new Set(
33450
- statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
33451
- );
33452
- for (const name of Object.keys(normalized)) {
33453
- if (!declared.has(name)) {
33454
- throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
33455
- }
33456
- }
33457
- return normalized;
33458
- }
33459
-
33460
- // src/core/scalarCompare.ts
33461
- function compareScalarValues(op, leftStr, rightStr) {
33462
- if (op === "=") return leftStr === rightStr;
33463
- if (op === "!=" || op === "<>") return leftStr !== rightStr;
33464
- const rightNum = Number(rightStr);
33465
- if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
33466
- return op === "<" || op === "<=";
33467
- }
33468
- const leftNum = Number(leftStr);
33469
- const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
33470
- switch (op) {
33471
- case ">":
33472
- return numeric ? leftNum > rightNum : leftStr > rightStr;
33473
- case "<":
33474
- return numeric ? leftNum < rightNum : leftStr < rightStr;
33475
- case ">=":
33476
- return numeric ? leftNum >= rightNum : leftStr >= rightStr;
33477
- case "<=":
33478
- return numeric ? leftNum <= rightNum : leftStr <= rightStr;
33479
- }
33480
- }
33481
-
33482
33275
  // src/engine/pushDownNot.ts
33483
33276
  function pushDownNot(expr) {
33484
33277
  switch (expr.type) {
@@ -33528,6 +33321,10 @@ function negateOp(op) {
33528
33321
  return "NOT_LIKE";
33529
33322
  case "NOT_LIKE":
33530
33323
  return "LIKE";
33324
+ case "KLIKE":
33325
+ return "NOT_KLIKE";
33326
+ case "NOT_KLIKE":
33327
+ return "KLIKE";
33531
33328
  case "IN":
33532
33329
  return "NOT_IN";
33533
33330
  case "NOT_IN":
@@ -33542,6 +33339,9 @@ function likePatternHasWildcard(pattern) {
33542
33339
  function isLike(where) {
33543
33340
  return where.type === "BINARY" && (where.op === "LIKE" || where.op === "NOT_LIKE");
33544
33341
  }
33342
+ function isKlike(where) {
33343
+ return where.type === "BINARY" && (where.op === "KLIKE" || where.op === "NOT_KLIKE");
33344
+ }
33545
33345
  function whereHasLike(where) {
33546
33346
  if (where === null) return false;
33547
33347
  if (isLike(where)) return true;
@@ -33557,6 +33357,21 @@ function whereHasLike(where) {
33557
33357
  return false;
33558
33358
  }
33559
33359
  }
33360
+ function whereHasKlike(where) {
33361
+ if (where === null) return false;
33362
+ if (isKlike(where)) return true;
33363
+ switch (where.type) {
33364
+ case "LOGICAL":
33365
+ return whereHasKlike(where.left) || whereHasKlike(where.right);
33366
+ case "NOT":
33367
+ case "GROUP":
33368
+ return whereHasKlike(where.expr);
33369
+ case "BINARY":
33370
+ case "NULL_CHECK":
33371
+ case "EXISTS":
33372
+ return false;
33373
+ }
33374
+ }
33560
33375
 
33561
33376
  // src/converter/whereToKintone.ts
33562
33377
  function whereToKintone(expr) {
@@ -33605,6 +33420,10 @@ function convertOp(op) {
33605
33420
  return "like";
33606
33421
  case "NOT_LIKE":
33607
33422
  return "not like";
33423
+ case "KLIKE":
33424
+ return "like";
33425
+ case "NOT_KLIKE":
33426
+ return "not like";
33608
33427
  case "IN":
33609
33428
  return "in";
33610
33429
  case "NOT_IN":
@@ -34198,6 +34017,647 @@ function isAggregateSyntheticName(name) {
34198
34017
  return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
34199
34018
  }
34200
34019
 
34020
+ // src/core/cteInlining.ts
34021
+ function canInlineSingleCte(stmt) {
34022
+ if (stmt.ctes.length !== 1) return false;
34023
+ const cteDef = stmt.ctes[0];
34024
+ if (cteDef.query.type !== "SELECT" || resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
34025
+ const finalQuery = stmt.query;
34026
+ if (finalQuery.type !== "SELECT") return false;
34027
+ if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
34028
+ if (finalQuery.groupBy.length > 0 || finalQuery.distinct) return false;
34029
+ return !finalQuery.columns.some(
34030
+ (column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
34031
+ );
34032
+ }
34033
+ function buildInlinedQuery(stmt) {
34034
+ const cteBody = stmt.ctes[0].query;
34035
+ const final = stmt.query;
34036
+ const finalWhere = stripCteAlias(final.where, final.from.alias);
34037
+ const where = cteBody.where === null ? finalWhere : finalWhere === null ? cteBody.where : { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
34038
+ const columns = final.columns.every((column) => column.type === "WILDCARD") ? cteBody.columns : final.columns;
34039
+ return {
34040
+ type: "SELECT",
34041
+ from: cteBody.from,
34042
+ joins: [],
34043
+ columns,
34044
+ where,
34045
+ groupBy: [],
34046
+ having: null,
34047
+ orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
34048
+ limit: final.limit ?? cteBody.limit,
34049
+ offset: final.offset ?? cteBody.offset,
34050
+ distinct: false
34051
+ };
34052
+ }
34053
+ function stripCteAlias(where, alias) {
34054
+ if (where === null || alias === null) return where;
34055
+ switch (where.type) {
34056
+ case "BINARY":
34057
+ return { ...where, left: stripCteAliasFromFieldValue(where.left, alias) };
34058
+ case "NULL_CHECK":
34059
+ return { ...where, field: stripCteAliasFromFieldValue(where.field, alias) };
34060
+ case "LOGICAL":
34061
+ return {
34062
+ ...where,
34063
+ left: stripCteAlias(where.left, alias),
34064
+ right: stripCteAlias(where.right, alias)
34065
+ };
34066
+ case "NOT":
34067
+ case "GROUP":
34068
+ return { ...where, expr: stripCteAlias(where.expr, alias) };
34069
+ case "EXISTS":
34070
+ return where;
34071
+ }
34072
+ }
34073
+ function stripCteAliasFromFieldValue(value, alias) {
34074
+ if (value.type === "FIELD" && value.tableAlias === alias) {
34075
+ return { ...value, tableAlias: null };
34076
+ }
34077
+ return value;
34078
+ }
34079
+
34080
+ // src/core/optimization/wherePredicatePushdown.ts
34081
+ function extractSafePushdownLeaves(where, options = {}) {
34082
+ return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
34083
+ }
34084
+ function extractTypedPushdownCandidates(where, options = {}) {
34085
+ return extractAndLeaves(
34086
+ where,
34087
+ (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
34088
+ );
34089
+ }
34090
+ function extractAndLeaves(where, accept) {
34091
+ switch (where.type) {
34092
+ case "BINARY":
34093
+ return accept(where) ? where : null;
34094
+ case "LOGICAL":
34095
+ if (where.op !== "AND") return null;
34096
+ {
34097
+ const left = extractAndLeaves(where.left, accept);
34098
+ const right = extractAndLeaves(where.right, accept);
34099
+ if (left && right) return { ...where, left, right };
34100
+ return left ?? right ?? null;
34101
+ }
34102
+ case "GROUP":
34103
+ return extractAndLeaves(where.expr, accept);
34104
+ case "NULL_CHECK":
34105
+ case "NOT":
34106
+ case "EXISTS":
34107
+ return null;
34108
+ }
34109
+ }
34110
+ function isSafeComparison(expr, options) {
34111
+ if (isKlikeComparison(expr, options)) return true;
34112
+ if (isSafeIdComparison(expr, options)) return true;
34113
+ if (isNumericCandidate(expr, options)) {
34114
+ return options.fieldTypes?.get(expr.left.field) === "NUMBER";
34115
+ }
34116
+ return isSelectionInComparison(expr, options);
34117
+ }
34118
+ function isKlikeComparison(expr, options) {
34119
+ if (options.allowKlike === false) return false;
34120
+ if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
34121
+ if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
34122
+ return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
34123
+ }
34124
+ var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
34125
+ "DROP_DOWN",
34126
+ "RADIO_BUTTON",
34127
+ "CHECK_BOX",
34128
+ "MULTI_SELECT",
34129
+ "STATUS"
34130
+ ]);
34131
+ function isSelectionInComparison(expr, options) {
34132
+ if (!isSelectionInCandidate(expr, options)) return false;
34133
+ if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
34134
+ const fieldType = options.fieldTypes?.get(expr.left.field);
34135
+ if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
34136
+ const validOptions = options.fieldOptions?.get(expr.left.field);
34137
+ if (validOptions === void 0) return false;
34138
+ return expr.right.values.every(
34139
+ (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
34140
+ );
34141
+ }
34142
+ function isSafeIdComparison(expr, options) {
34143
+ if (!isTargetIdField(expr.left, options)) return false;
34144
+ if (expr.right.type !== "NUMBER") return false;
34145
+ return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
34146
+ }
34147
+ function isTargetIdField(field, options) {
34148
+ if (field.type !== "FIELD" || field.field !== "$id") return false;
34149
+ const targetAlias = options.tableAlias ?? null;
34150
+ if (field.tableAlias === targetAlias) return true;
34151
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
34152
+ }
34153
+ function isNumericCandidate(expr, options) {
34154
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
34155
+ if (!isTargetField(expr.left, options)) return false;
34156
+ if (expr.right.type !== "NUMBER") return false;
34157
+ if (expr.op === "=") return true;
34158
+ return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
34159
+ }
34160
+ function isSelectionInCandidate(expr, options) {
34161
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
34162
+ if (!isTargetField(expr.left, options)) return false;
34163
+ if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
34164
+ if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
34165
+ return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
34166
+ }
34167
+ function isTargetField(field, options) {
34168
+ const targetAlias = options.tableAlias ?? null;
34169
+ if (field.tableAlias === targetAlias) return true;
34170
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
34171
+ }
34172
+
34173
+ // src/core/optimization/klikePushdownPlan.ts
34174
+ function buildKlikePushdownPlan(stmt, options = {}) {
34175
+ const joinsAreSafeForKlike = stmt.joins.every((join) => join.type === "INNER");
34176
+ const common = {
34177
+ allowKlike: joinsAreSafeForKlike,
34178
+ allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
34179
+ };
34180
+ let mainCondition = null;
34181
+ if (stmt.where !== null && !stmt.from.subtableCode && stmt.from.cteName === null) {
34182
+ if (stmt.joins.length === 0) {
34183
+ mainCondition = extractSafePushdownLeaves(stmt.where, {
34184
+ ...common,
34185
+ tableAlias: stmt.from.alias ?? void 0,
34186
+ allowUnqualifiedFields: true,
34187
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
34188
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
34189
+ });
34190
+ } else if (stmt.from.alias) {
34191
+ mainCondition = extractSafePushdownLeaves(stmt.where, {
34192
+ ...common,
34193
+ tableAlias: stmt.from.alias,
34194
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
34195
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
34196
+ });
34197
+ }
34198
+ }
34199
+ const joinConditions = /* @__PURE__ */ new Map();
34200
+ if (stmt.where !== null) {
34201
+ for (const join of stmt.joins) {
34202
+ if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
34203
+ const condition = extractSafePushdownLeaves(stmt.where, {
34204
+ ...common,
34205
+ tableAlias: join.table.alias,
34206
+ fieldTypes: options.fieldTypesByApp?.get(join.table.appId),
34207
+ fieldOptions: options.fieldOptionsByApp?.get(join.table.appId)
34208
+ });
34209
+ if (condition !== null) joinConditions.set(join.table.alias, condition);
34210
+ }
34211
+ }
34212
+ const appliedKlikes = /* @__PURE__ */ new Set();
34213
+ collectKlikes(mainCondition, appliedKlikes);
34214
+ for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
34215
+ const allKlikes = /* @__PURE__ */ new Set();
34216
+ collectKlikes(stmt.where, allKlikes);
34217
+ return {
34218
+ mainCondition,
34219
+ joinConditions,
34220
+ appliedKlikes,
34221
+ allKlikes: [...allKlikes]
34222
+ };
34223
+ }
34224
+ function unappliedKlikes(plan) {
34225
+ return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
34226
+ }
34227
+ function collectKlikes(where, out) {
34228
+ if (where === null) return;
34229
+ if (isKlike(where)) {
34230
+ out.add(where);
34231
+ return;
34232
+ }
34233
+ switch (where.type) {
34234
+ case "LOGICAL":
34235
+ collectKlikes(where.left, out);
34236
+ collectKlikes(where.right, out);
34237
+ return;
34238
+ case "NOT":
34239
+ case "GROUP":
34240
+ collectKlikes(where.expr, out);
34241
+ return;
34242
+ case "BINARY":
34243
+ case "NULL_CHECK":
34244
+ case "EXISTS":
34245
+ return;
34246
+ }
34247
+ }
34248
+
34249
+ // src/core/klikeValidation.ts
34250
+ var KlikeValidationError = class extends Error {
34251
+ constructor(message) {
34252
+ super(`ArgumentError: ${message}`);
34253
+ this.name = "ArgumentError";
34254
+ }
34255
+ };
34256
+ function validateKlikeStatement(stmt) {
34257
+ validateStatement(stmt);
34258
+ }
34259
+ function validateKlikePushdownPlan(plan) {
34260
+ if (unappliedKlikes(plan).length > 0) {
34261
+ throw new KlikeValidationError(
34262
+ "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"
34263
+ );
34264
+ }
34265
+ }
34266
+ function validateStatement(stmt) {
34267
+ switch (stmt.type) {
34268
+ case "SELECT":
34269
+ validateSelect(stmt);
34270
+ return;
34271
+ case "UNION":
34272
+ validateUnion(stmt);
34273
+ return;
34274
+ case "WITH":
34275
+ validateWith(stmt);
34276
+ return;
34277
+ case "EXPLAIN":
34278
+ validateStatement(stmt.query);
34279
+ return;
34280
+ case "CREATE_TEMP_TABLE":
34281
+ validateSelectLike(stmt.query);
34282
+ return;
34283
+ case "SET_VARIABLE":
34284
+ case "DECLARE_VARIABLE":
34285
+ case "ASSERT":
34286
+ validateNestedSelects(stmt);
34287
+ return;
34288
+ case "INSERT":
34289
+ case "INSERT_SELECT":
34290
+ case "UPSERT":
34291
+ case "UPSERT_SELECT":
34292
+ case "UPDATE":
34293
+ case "DELETE":
34294
+ case "REORDER":
34295
+ if (containsKlike(stmt)) {
34296
+ throw new KlikeValidationError(
34297
+ "KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
34298
+ );
34299
+ }
34300
+ return;
34301
+ case "SHOW_APPS":
34302
+ case "DESCRIBE":
34303
+ case "DROP_TEMP_TABLE":
34304
+ return;
34305
+ }
34306
+ }
34307
+ function validateSelectLike(query) {
34308
+ if (query.type === "SELECT") validateSelect(query);
34309
+ else if (query.type === "UNION") validateUnion(query);
34310
+ else validateWith(query);
34311
+ }
34312
+ function validateUnion(stmt) {
34313
+ validateSelectLike(stmt.left);
34314
+ validateSelect(stmt.right);
34315
+ }
34316
+ function validateWith(stmt) {
34317
+ if (canInlineSingleCte(stmt)) {
34318
+ validateSelect(buildInlinedQuery(stmt));
34319
+ return;
34320
+ }
34321
+ for (const cte of stmt.ctes) {
34322
+ if (cte.query.type === "SELECT") validateSelect(cte.query);
34323
+ else if (cte.query.type === "UNION") validateUnion(cte.query);
34324
+ }
34325
+ validateSelectLike(stmt.query);
34326
+ }
34327
+ function validateSelect(stmt) {
34328
+ validateOwnKlikeExpressions(stmt);
34329
+ if (whereHasKlike(stmt.where)) {
34330
+ const directKintoneSimple = resolveSelectMode(stmt) === "SIMPLE" && stmt.from.cteName === null && stmt.joins.every((join) => join.table.cteName === null);
34331
+ if (!directKintoneSimple) {
34332
+ const plan = buildKlikePushdownPlan(stmt, { allowUnresolvedVariables: true });
34333
+ if (unappliedKlikes(plan).length === 0) {
34334
+ validateNestedSelects(stmt);
34335
+ return;
34336
+ }
34337
+ throw new KlikeValidationError(
34338
+ "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"
34339
+ );
34340
+ }
34341
+ }
34342
+ validateNestedSelects(stmt);
34343
+ }
34344
+ function validateOwnKlikeExpressions(stmt) {
34345
+ walkWithoutNestedSelects(stmt, (where) => {
34346
+ if (!isKlike(where)) return;
34347
+ if (!isDescendantOf(stmt.where, where)) {
34348
+ throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F SELECT \u306E WHERE \u53E5\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059");
34349
+ }
34350
+ const right = where.right;
34351
+ if (right.type !== "STRING" && right.type !== "VARIABLE") {
34352
+ throw new KlikeValidationError(
34353
+ "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"
34354
+ );
34355
+ }
34356
+ if (right.type === "STRING" && right.value.includes("%")) {
34357
+ throw new KlikeValidationError(
34358
+ "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"
34359
+ );
34360
+ }
34361
+ });
34362
+ }
34363
+ function validateNestedSelects(node) {
34364
+ walkObjects(node, (obj) => {
34365
+ if (obj.type === "SELECT") validateSelect(obj);
34366
+ }, true);
34367
+ }
34368
+ function containsKlike(node) {
34369
+ let found = false;
34370
+ walkObjects(node, (obj) => {
34371
+ if (obj.type === "BINARY" && (obj.op === "KLIKE" || obj.op === "NOT_KLIKE")) found = true;
34372
+ });
34373
+ return found;
34374
+ }
34375
+ function isDescendantOf(root, target) {
34376
+ if (root === null) return false;
34377
+ if (root === target) return true;
34378
+ switch (root.type) {
34379
+ case "LOGICAL":
34380
+ return isDescendantOf(root.left, target) || isDescendantOf(root.right, target);
34381
+ case "NOT":
34382
+ case "GROUP":
34383
+ return isDescendantOf(root.expr, target);
34384
+ case "BINARY":
34385
+ case "NULL_CHECK":
34386
+ case "EXISTS":
34387
+ return false;
34388
+ }
34389
+ }
34390
+ function walkWithoutNestedSelects(node, visitWhere) {
34391
+ if (Array.isArray(node)) {
34392
+ for (const value of node) walkWithoutNestedSelects(value, visitWhere);
34393
+ return;
34394
+ }
34395
+ if (node === null || typeof node !== "object") return;
34396
+ const obj = node;
34397
+ if (obj.type === "BINARY" || obj.type === "NULL_CHECK" || obj.type === "LOGICAL" || obj.type === "NOT" || obj.type === "GROUP" || obj.type === "EXISTS") {
34398
+ visitWhere(obj);
34399
+ }
34400
+ for (const value of Object.values(obj)) {
34401
+ if (value !== node && isSelectObject(value)) continue;
34402
+ walkWithoutNestedSelects(value, visitWhere);
34403
+ }
34404
+ }
34405
+ function walkObjects(node, visit, skipRoot = false) {
34406
+ if (Array.isArray(node)) {
34407
+ for (const value of node) walkObjects(value, visit);
34408
+ return;
34409
+ }
34410
+ if (node === null || typeof node !== "object") return;
34411
+ const obj = node;
34412
+ if (!skipRoot) visit(obj);
34413
+ for (const value of Object.values(obj)) walkObjects(value, visit);
34414
+ }
34415
+ function isSelectObject(value) {
34416
+ return value !== null && typeof value === "object" && value.type === "SELECT";
34417
+ }
34418
+
34419
+ // src/core/batch.ts
34420
+ var MAX_TEMP_TABLES = 16;
34421
+ var MAX_BATCH_VARIABLES = 64;
34422
+ var BatchAnalysisError = class extends Error {
34423
+ constructor(message, statementIndex) {
34424
+ super(message);
34425
+ this.statementIndex = statementIndex;
34426
+ }
34427
+ };
34428
+ function collectRefs(node, tempRefs, appIds) {
34429
+ if (Array.isArray(node)) {
34430
+ for (const v of node) collectRefs(v, tempRefs, appIds);
34431
+ return;
34432
+ }
34433
+ if (node !== null && typeof node === "object") {
34434
+ const obj = node;
34435
+ const cte = obj["cteName"];
34436
+ if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
34437
+ const appId = obj["appId"];
34438
+ if (typeof appId === "number" && appId > 0) appIds.add(appId);
34439
+ for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
34440
+ }
34441
+ }
34442
+ function collectVariableRefs(node, refs) {
34443
+ if (Array.isArray(node)) {
34444
+ for (const v of node) collectVariableRefs(v, refs);
34445
+ return;
34446
+ }
34447
+ if (node !== null && typeof node === "object") {
34448
+ const obj = node;
34449
+ if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
34450
+ refs.add(obj["name"]);
34451
+ return;
34452
+ }
34453
+ for (const v of Object.values(obj)) collectVariableRefs(v, refs);
34454
+ }
34455
+ }
34456
+ function analyzeBatch(statements) {
34457
+ if (statements.length === 0) {
34458
+ throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
34459
+ }
34460
+ statements.forEach((stmt, index) => {
34461
+ try {
34462
+ validateKlikeStatement(stmt);
34463
+ } catch (error51) {
34464
+ if (error51 instanceof KlikeValidationError) {
34465
+ throw new BatchAnalysisError(error51.message, index);
34466
+ }
34467
+ throw error51;
34468
+ }
34469
+ });
34470
+ if (statements.length === 1) {
34471
+ const t = statements[0].type;
34472
+ if (t === "SET_VARIABLE" || t === "DECLARE_VARIABLE") {
34473
+ const verb = t === "SET_VARIABLE" ? "SET" : "DECLARE";
34474
+ throw new BatchAnalysisError(`ArgumentError: ${verb} variable requires a batch.`, 0);
34475
+ }
34476
+ if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
34477
+ const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
34478
+ throw new BatchAnalysisError(
34479
+ `ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
34480
+ 0
34481
+ );
34482
+ }
34483
+ }
34484
+ const defined = /* @__PURE__ */ new Map();
34485
+ const createdOrder = [];
34486
+ const results = [];
34487
+ const variableDefs = /* @__PURE__ */ new Map();
34488
+ const variableOrder = [];
34489
+ statements.forEach((stmt, index) => {
34490
+ const statementType = getStatementType(stmt);
34491
+ const created = [];
34492
+ const dropped = [];
34493
+ const refs = /* @__PURE__ */ new Set();
34494
+ const stmtAppIds = /* @__PURE__ */ new Set();
34495
+ const dependsOn = /* @__PURE__ */ new Set();
34496
+ const variableRefs = /* @__PURE__ */ new Set();
34497
+ collectVariableRefs(stmt, variableRefs);
34498
+ for (const name of variableRefs) {
34499
+ const def = variableDefs.get(name);
34500
+ if (def === void 0) {
34501
+ throw new BatchAnalysisError(
34502
+ `ParseError: variable @${name} is not defined before statement ${index + 1}.`,
34503
+ index
34504
+ );
34505
+ }
34506
+ def.referencedBy.push(index);
34507
+ }
34508
+ if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
34509
+ if (variableDefs.has(stmt.name)) {
34510
+ throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
34511
+ }
34512
+ variableDefs.set(stmt.name, { index, referencedBy: [] });
34513
+ variableOrder.push(stmt.name);
34514
+ if (variableOrder.length > MAX_BATCH_VARIABLES) {
34515
+ throw new BatchAnalysisError(
34516
+ `ParseError: batch exceeds ${MAX_BATCH_VARIABLES} variables.`,
34517
+ index
34518
+ );
34519
+ }
34520
+ }
34521
+ if (stmt.type === "CREATE_TEMP_TABLE") {
34522
+ collectRefs(stmt.query, refs, stmtAppIds);
34523
+ } else if (stmt.type === "DROP_TEMP_TABLE") {
34524
+ } else {
34525
+ collectRefs(stmt, refs, stmtAppIds);
34526
+ }
34527
+ let tempOnlySource = false;
34528
+ if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
34529
+ const srcTemp = /* @__PURE__ */ new Set();
34530
+ const srcApps = /* @__PURE__ */ new Set();
34531
+ collectRefs(stmt.select, srcTemp, srcApps);
34532
+ tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
34533
+ }
34534
+ for (const name of refs) {
34535
+ const at = defined.get(name);
34536
+ if (at === void 0) {
34537
+ throw new BatchAnalysisError(
34538
+ `ParseError: temp table ${name} is not defined in this batch.`,
34539
+ index
34540
+ );
34541
+ }
34542
+ dependsOn.add(at);
34543
+ }
34544
+ if (stmt.type === "CREATE_TEMP_TABLE") {
34545
+ if (defined.has(stmt.name)) {
34546
+ throw new BatchAnalysisError(
34547
+ `ParseError: temp table ${stmt.name} is already defined.`,
34548
+ index
34549
+ );
34550
+ }
34551
+ defined.set(stmt.name, index);
34552
+ createdOrder.push(stmt.name);
34553
+ created.push(stmt.name);
34554
+ if (defined.size > MAX_TEMP_TABLES) {
34555
+ throw new BatchAnalysisError(
34556
+ `ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
34557
+ index
34558
+ );
34559
+ }
34560
+ }
34561
+ if (stmt.type === "DROP_TEMP_TABLE") {
34562
+ const at = defined.get(stmt.name);
34563
+ if (at === void 0) {
34564
+ throw new BatchAnalysisError(
34565
+ `ParseError: temp table ${stmt.name} is not defined in this batch.`,
34566
+ index
34567
+ );
34568
+ }
34569
+ dependsOn.add(at);
34570
+ dropped.push(stmt.name);
34571
+ defined.delete(stmt.name);
34572
+ }
34573
+ results.push({
34574
+ index,
34575
+ statementType,
34576
+ isDml: isDmlType(statementType),
34577
+ isReadOnly: isReadOnlyType(statementType),
34578
+ hasWhere: hasWhereClause(stmt),
34579
+ insertValuesCount: getInsertValuesCount(stmt),
34580
+ appIds: [...stmtAppIds].sort((a, b) => a - b),
34581
+ tempTablesCreated: created,
34582
+ tempTablesReferenced: [...refs],
34583
+ tempTablesDropped: dropped,
34584
+ dependsOn: [...dependsOn].sort((a, b) => a - b),
34585
+ tempOnlySource,
34586
+ targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
34587
+ });
34588
+ });
34589
+ const containsDml = results.some((r) => r.isDml);
34590
+ const variables = variableOrder.map((name) => ({
34591
+ name,
34592
+ referencedBy: [...variableDefs.get(name).referencedBy]
34593
+ }));
34594
+ return {
34595
+ statementCount: statements.length,
34596
+ isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
34597
+ containsDml,
34598
+ tempTables: createdOrder,
34599
+ variables,
34600
+ warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
34601
+ statements: results
34602
+ };
34603
+ }
34604
+
34605
+ // src/core/batchVariables.ts
34606
+ var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
34607
+ function normalizeBatchVariableName(name) {
34608
+ if (!VARIABLE_NAME_RE.test(name)) {
34609
+ throw new Error(
34610
+ `ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
34611
+ );
34612
+ }
34613
+ return name.toLowerCase();
34614
+ }
34615
+ function normalizeBatchVariables(input) {
34616
+ const normalized = /* @__PURE__ */ Object.create(null);
34617
+ for (const [rawName, value] of Object.entries(input ?? {})) {
34618
+ const name = normalizeBatchVariableName(rawName);
34619
+ if (Object.prototype.hasOwnProperty.call(normalized, name)) {
34620
+ throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
34621
+ }
34622
+ normalized[name] = value;
34623
+ }
34624
+ return normalized;
34625
+ }
34626
+ function validateDeclaredBatchVariables(statements, input) {
34627
+ const normalized = normalizeBatchVariables(input);
34628
+ const declared = new Set(
34629
+ statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
34630
+ );
34631
+ for (const name of Object.keys(normalized)) {
34632
+ if (!declared.has(name)) {
34633
+ throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
34634
+ }
34635
+ }
34636
+ return normalized;
34637
+ }
34638
+
34639
+ // src/core/scalarCompare.ts
34640
+ function compareScalarValues(op, leftStr, rightStr) {
34641
+ if (op === "=") return leftStr === rightStr;
34642
+ if (op === "!=" || op === "<>") return leftStr !== rightStr;
34643
+ const rightNum = Number(rightStr);
34644
+ if (leftStr === "" && rightStr !== "" && Number.isFinite(rightNum)) {
34645
+ return op === "<" || op === "<=";
34646
+ }
34647
+ const leftNum = Number(leftStr);
34648
+ const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
34649
+ switch (op) {
34650
+ case ">":
34651
+ return numeric ? leftNum > rightNum : leftStr > rightStr;
34652
+ case "<":
34653
+ return numeric ? leftNum < rightNum : leftStr < rightStr;
34654
+ case ">=":
34655
+ return numeric ? leftNum >= rightNum : leftStr >= rightStr;
34656
+ case "<=":
34657
+ return numeric ? leftNum <= rightNum : leftStr <= rightStr;
34658
+ }
34659
+ }
34660
+
34201
34661
  // src/engine/evalFunc.ts
34202
34662
  function evalArithExpr(expr, row) {
34203
34663
  if (expr.type === "NUMBER") return expr.value;
@@ -34409,25 +34869,29 @@ function resolveFieldRef(row, field) {
34409
34869
  }
34410
34870
 
34411
34871
  // src/engine/evalWhere.ts
34412
- function evalWhere(expr, row, resolveFieldType) {
34872
+ function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
34413
34873
  switch (expr.type) {
34414
34874
  case "BINARY":
34415
- return evalBinary(expr, row, resolveFieldType);
34875
+ return evalBinary(expr, row, resolveFieldType, appliedKlikes);
34416
34876
  case "NULL_CHECK":
34417
34877
  return evalNullCheck(expr, row);
34418
34878
  case "LOGICAL":
34419
- return evalLogical(expr, row, resolveFieldType);
34879
+ return evalLogical(expr, row, resolveFieldType, appliedKlikes);
34420
34880
  case "NOT":
34421
- return !evalWhere(expr.expr, row, resolveFieldType);
34881
+ return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
34422
34882
  case "GROUP":
34423
- return evalWhere(expr.expr, row, resolveFieldType);
34883
+ return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
34424
34884
  case "EXISTS": {
34425
34885
  const exists = expr.resolved;
34426
34886
  return expr.not ? !exists : exists;
34427
34887
  }
34428
34888
  }
34429
34889
  }
34430
- function evalBinary(expr, row, resolveFieldType) {
34890
+ function evalBinary(expr, row, resolveFieldType, appliedKlikes) {
34891
+ if (expr.op === "KLIKE" || expr.op === "NOT_KLIKE") {
34892
+ if (appliedKlikes?.has(expr)) return true;
34893
+ 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");
34894
+ }
34431
34895
  const left = resolveField(expr.left, row, resolveFieldType);
34432
34896
  const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
34433
34897
  return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
@@ -34454,6 +34918,9 @@ function evalOp(op, leftStr, right, row, fieldType, resolveFieldType) {
34454
34918
  const pattern = resolveValue(right, row, resolveFieldType);
34455
34919
  return !matchLike(leftStr, pattern);
34456
34920
  }
34921
+ if (op === "KLIKE" || op === "NOT_KLIKE") {
34922
+ 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");
34923
+ }
34457
34924
  const rightStr = resolveValue(right, row, resolveFieldType);
34458
34925
  return compareScalarValues(op, leftStr, rightStr);
34459
34926
  }
@@ -34506,11 +34973,11 @@ function evalNullCheck(expr, row) {
34506
34973
  const val = resolveField(expr.field, row);
34507
34974
  return expr.not ? val !== "" : val === "";
34508
34975
  }
34509
- function evalLogical(expr, row, resolveFieldType) {
34976
+ function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
34510
34977
  if (expr.op === "AND") {
34511
- return evalWhere(expr.left, row, resolveFieldType) && evalWhere(expr.right, row, resolveFieldType);
34978
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
34512
34979
  }
34513
- return evalWhere(expr.left, row, resolveFieldType) || evalWhere(expr.right, row, resolveFieldType);
34980
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
34514
34981
  }
34515
34982
  function resolveField(field, row, resolveFieldType) {
34516
34983
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
@@ -34609,6 +35076,11 @@ function matchLike(value, pattern) {
34609
35076
 
34610
35077
  // src/converter/dmlToKintone.ts
34611
35078
  function assertDmlWhereIsSafe(where) {
35079
+ if (whereHasKlike(where)) {
35080
+ throw new DmlConvertError(
35081
+ "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"
35082
+ );
35083
+ }
34612
35084
  if (!whereHasLike(where)) return;
34613
35085
  throw new DmlConvertError(
34614
35086
  "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"
@@ -35096,92 +35568,6 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
35096
35568
  };
35097
35569
  }
35098
35570
 
35099
- // src/core/optimization/wherePredicatePushdown.ts
35100
- function extractSafePushdownLeaves(where, options = {}) {
35101
- return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
35102
- }
35103
- function extractTypedPushdownCandidates(where, options = {}) {
35104
- return extractAndLeaves(
35105
- where,
35106
- (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
35107
- );
35108
- }
35109
- function extractAndLeaves(where, accept) {
35110
- switch (where.type) {
35111
- case "BINARY":
35112
- return accept(where) ? where : null;
35113
- case "LOGICAL":
35114
- if (where.op !== "AND") return null;
35115
- {
35116
- const left = extractAndLeaves(where.left, accept);
35117
- const right = extractAndLeaves(where.right, accept);
35118
- if (left && right) return { ...where, left, right };
35119
- return left ?? right ?? null;
35120
- }
35121
- case "GROUP":
35122
- return extractAndLeaves(where.expr, accept);
35123
- case "NULL_CHECK":
35124
- case "NOT":
35125
- case "EXISTS":
35126
- return null;
35127
- }
35128
- }
35129
- function isSafeComparison(expr, options) {
35130
- if (isSafeIdComparison(expr, options)) return true;
35131
- if (isNumericCandidate(expr, options)) {
35132
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
35133
- }
35134
- return isSelectionInComparison(expr, options);
35135
- }
35136
- var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
35137
- "DROP_DOWN",
35138
- "RADIO_BUTTON",
35139
- "CHECK_BOX",
35140
- "MULTI_SELECT",
35141
- "STATUS"
35142
- ]);
35143
- function isSelectionInComparison(expr, options) {
35144
- if (!isSelectionInCandidate(expr, options)) return false;
35145
- if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
35146
- const fieldType = options.fieldTypes?.get(expr.left.field);
35147
- if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
35148
- const validOptions = options.fieldOptions?.get(expr.left.field);
35149
- if (validOptions === void 0) return false;
35150
- return expr.right.values.every(
35151
- (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
35152
- );
35153
- }
35154
- function isSafeIdComparison(expr, options) {
35155
- if (!isTargetIdField(expr.left, options)) return false;
35156
- if (expr.right.type !== "NUMBER") return false;
35157
- return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
35158
- }
35159
- function isTargetIdField(field, options) {
35160
- if (field.type !== "FIELD" || field.field !== "$id") return false;
35161
- const targetAlias = options.tableAlias ?? null;
35162
- if (field.tableAlias === targetAlias) return true;
35163
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
35164
- }
35165
- function isNumericCandidate(expr, options) {
35166
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
35167
- if (!isTargetField(expr.left, options)) return false;
35168
- if (expr.right.type !== "NUMBER") return false;
35169
- if (expr.op === "=") return true;
35170
- return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
35171
- }
35172
- function isSelectionInCandidate(expr, options) {
35173
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
35174
- if (!isTargetField(expr.left, options)) return false;
35175
- if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
35176
- if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
35177
- return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
35178
- }
35179
- function isTargetField(field, options) {
35180
- const targetAlias = options.tableAlias ?? null;
35181
- if (field.tableAlias === targetAlias) return true;
35182
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
35183
- }
35184
-
35185
35571
  // src/engine/process.ts
35186
35572
  function flatten(record2, alias) {
35187
35573
  const row = {};
@@ -35246,9 +35632,9 @@ function applyJoin(leftRows, rightRows, join) {
35246
35632
  }
35247
35633
  return result;
35248
35634
  }
35249
- function applyFilter(rows, where, resolveFieldType) {
35635
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
35250
35636
  if (where === null) return rows;
35251
- return rows.filter((row) => evalWhere(where, row, resolveFieldType));
35637
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
35252
35638
  }
35253
35639
  function hasAggregateColumns(columns) {
35254
35640
  return columns.some(
@@ -35705,7 +36091,8 @@ function runFullScan(input) {
35705
36091
  optionOrders,
35706
36092
  sortKinds,
35707
36093
  fieldTypeResolver,
35708
- havingFieldTypeResolver
36094
+ havingFieldTypeResolver,
36095
+ appliedKlikes
35709
36096
  } = input;
35710
36097
  let rows = [];
35711
36098
  const mainAlias = stmt.from.alias;
@@ -35717,7 +36104,7 @@ function runFullScan(input) {
35717
36104
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
35718
36105
  rows = applyJoin(rows, rightRows, join);
35719
36106
  }
35720
- rows = applyFilter(rows, stmt.where, fieldTypeResolver);
36107
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
35721
36108
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
35722
36109
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
35723
36110
  }
@@ -35838,6 +36225,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
35838
36225
  if (unresolved !== null) {
35839
36226
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
35840
36227
  }
36228
+ validateKlikeStatement(stmt);
35841
36229
  switch (stmt.type) {
35842
36230
  case "SELECT":
35843
36231
  return executeSelect(stmt, client, options, cacheContext);
@@ -35974,6 +36362,7 @@ async function executeBatch(sql, client, options = {}) {
35974
36362
  async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
35975
36363
  if (stmt.type === "SET_VARIABLE") {
35976
36364
  const resolvedStmt2 = resolveVariableRefs(stmt, variables);
36365
+ validateKlikeStatement(resolvedStmt2);
35977
36366
  if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
35978
36367
  try {
35979
36368
  const value = await evaluateScalarSubquery(
@@ -36006,6 +36395,7 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
36006
36395
  return {};
36007
36396
  }
36008
36397
  const resolvedStmt = resolveVariableRefs(stmt, variables);
36398
+ validateKlikeStatement(resolvedStmt);
36009
36399
  if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
36010
36400
  const materializeOptions = {
36011
36401
  ...options,
@@ -36402,23 +36792,6 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
36402
36792
  }
36403
36793
  }
36404
36794
  }
36405
- function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
36406
- if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36407
- if (stmt.joins.length === 0) {
36408
- return extractSafePushdownLeaves(stmt.where, {
36409
- tableAlias: stmt.from.alias ?? void 0,
36410
- allowUnqualifiedFields: true,
36411
- fieldTypes,
36412
- fieldOptions
36413
- });
36414
- }
36415
- if (!stmt.from.alias) return null;
36416
- return extractSafePushdownLeaves(stmt.where, {
36417
- tableAlias: stmt.from.alias,
36418
- fieldTypes,
36419
- fieldOptions
36420
- });
36421
- }
36422
36795
  function extractMainTypedPushdownCandidate(stmt) {
36423
36796
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36424
36797
  if (stmt.joins.length === 0) {
@@ -36600,23 +36973,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36600
36973
  loadTypedInFieldTypes(stmt, client, cacheContext)
36601
36974
  ]);
36602
36975
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
36603
- const mainPushDown = extractMainSafePushdown(
36604
- stmt,
36605
- pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
36606
- pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
36607
- );
36608
- const tableConditions = /* @__PURE__ */ new Map();
36609
- if (stmt.where !== null) {
36610
- for (const join of stmt.joins) {
36611
- if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
36612
- const cond = extractSafePushdownLeaves(stmt.where, {
36613
- tableAlias: join.table.alias,
36614
- fieldTypes: pushdownMeta.fieldTypesByApp.get(join.table.appId),
36615
- fieldOptions: pushdownMeta.fieldOptionsByApp.get(join.table.appId)
36616
- });
36617
- if (cond) tableConditions.set(join.table.alias, cond);
36618
- }
36619
- }
36976
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
36977
+ validateKlikePushdownPlan(pushdownPlan);
36978
+ const mainPushDown = pushdownPlan.mainCondition;
36979
+ const tableConditions = pushdownPlan.joinConditions;
36620
36980
  const mainFetch = fetchTableRecordsForFullScan(
36621
36981
  stmt,
36622
36982
  stmt.from,
@@ -36697,7 +37057,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36697
37057
  optionOrders,
36698
37058
  sortKinds,
36699
37059
  fieldTypeResolver: fieldTypeResolvers.row,
36700
- havingFieldTypeResolver: fieldTypeResolvers.having
37060
+ havingFieldTypeResolver: fieldTypeResolvers.having,
37061
+ appliedKlikes: pushdownPlan.appliedKlikes
36701
37062
  });
36702
37063
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
36703
37064
  }
@@ -36746,83 +37107,6 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
36746
37107
  }
36747
37108
  return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
36748
37109
  }
36749
- function canInlineSingleCte(stmt) {
36750
- if (stmt.ctes.length !== 1) return false;
36751
- const cteDef = stmt.ctes[0];
36752
- if (cteDef.query.type !== "SELECT") return false;
36753
- if (resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
36754
- const finalQuery = stmt.query;
36755
- if (finalQuery.type !== "SELECT") return false;
36756
- if (finalQuery.from.cteName !== cteDef.name) return false;
36757
- if (finalQuery.joins.length > 0) return false;
36758
- if (finalQuery.groupBy.length > 0) return false;
36759
- if (finalQuery.distinct) return false;
36760
- if (finalQuery.columns.some(
36761
- (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"
36762
- )) return false;
36763
- return true;
36764
- }
36765
- function buildInlinedQuery(stmt) {
36766
- const cteBody = stmt.ctes[0].query;
36767
- const final = stmt.query;
36768
- const cteAlias = final.from.alias;
36769
- const finalWhere = stripCteAlias(final.where, cteAlias);
36770
- let mergedWhere;
36771
- if (cteBody.where === null) mergedWhere = finalWhere;
36772
- else if (finalWhere === null) mergedWhere = cteBody.where;
36773
- else mergedWhere = { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
36774
- const columns = final.columns.every((c) => c.type === "WILDCARD") ? cteBody.columns : final.columns;
36775
- return {
36776
- type: "SELECT",
36777
- from: cteBody.from,
36778
- joins: [],
36779
- columns,
36780
- where: mergedWhere,
36781
- groupBy: [],
36782
- having: null,
36783
- orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
36784
- limit: final.limit ?? cteBody.limit,
36785
- offset: final.offset ?? cteBody.offset,
36786
- distinct: false
36787
- };
36788
- }
36789
- function stripCteAlias(where, alias) {
36790
- if (where === null || alias === null) return where;
36791
- switch (where.type) {
36792
- case "BINARY":
36793
- return {
36794
- type: "BINARY",
36795
- op: where.op,
36796
- left: stripCteAliasFromFieldValue(where.left, alias),
36797
- right: where.right
36798
- };
36799
- case "NULL_CHECK":
36800
- return {
36801
- type: "NULL_CHECK",
36802
- not: where.not,
36803
- field: stripCteAliasFromFieldValue(where.field, alias)
36804
- };
36805
- case "LOGICAL":
36806
- return {
36807
- type: "LOGICAL",
36808
- op: where.op,
36809
- left: stripCteAlias(where.left, alias),
36810
- right: stripCteAlias(where.right, alias)
36811
- };
36812
- case "NOT":
36813
- return { type: "NOT", expr: stripCteAlias(where.expr, alias) };
36814
- case "GROUP":
36815
- return { type: "GROUP", expr: stripCteAlias(where.expr, alias) };
36816
- case "EXISTS":
36817
- return where;
36818
- }
36819
- }
36820
- function stripCteAliasFromFieldValue(fv, alias) {
36821
- if (fv.type === "FIELD" && fv.tableAlias === alias) {
36822
- return { type: "FIELD", field: fv.field, tableAlias: null };
36823
- }
36824
- return fv;
36825
- }
36826
37110
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
36827
37111
  if (query.type === "UNION") {
36828
37112
  const [leftResult, rightResult] = await Promise.all([
@@ -36857,8 +37141,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36857
37141
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
36858
37142
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
36859
37143
  ]);
36860
- const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
37144
+ const [pushdownMeta, typedInFieldTypes] = await Promise.all([
37145
+ loadTypedPushdownMeta(stmt, client, cacheContext),
37146
+ loadTypedInFieldTypes(stmt, client, cacheContext)
37147
+ ]);
36861
37148
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
37149
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
37150
+ validateKlikePushdownPlan(pushdownPlan);
36862
37151
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
36863
37152
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
36864
37153
  scalarCachePromise.catch(() => {
@@ -36878,7 +37167,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36878
37167
  parallel,
36879
37168
  true,
36880
37169
  options.onLimitReached ?? "error",
36881
- warnings
37170
+ warnings,
37171
+ pushdownPlan.mainCondition
36882
37172
  );
36883
37173
  tables.set(stmt.from.alias, mainRecords);
36884
37174
  }
@@ -36887,6 +37177,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36887
37177
  const rows2 = cteCache.get(join.table.cteName) ?? [];
36888
37178
  tables.set(join.table.alias, rows2.map(processRowToKintoneRecord));
36889
37179
  } else {
37180
+ const pushDownCond = join.table.alias ? pushdownPlan.joinConditions.get(join.table.alias) ?? null : null;
36890
37181
  const optimized = await tryFetchJoinRecordsBySourceKeys(
36891
37182
  stmt,
36892
37183
  join,
@@ -36895,7 +37186,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36895
37186
  maxRecords2,
36896
37187
  parallel,
36897
37188
  options.onLimitReached ?? "error",
36898
- warnings
37189
+ warnings,
37190
+ pushDownCond
36899
37191
  );
36900
37192
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
36901
37193
  stmt,
@@ -36905,7 +37197,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36905
37197
  parallel,
36906
37198
  false,
36907
37199
  options.onLimitReached ?? "error",
36908
- warnings
37200
+ warnings,
37201
+ pushDownCond
36909
37202
  );
36910
37203
  tables.set(join.table.alias, joinRecords);
36911
37204
  }
@@ -36920,7 +37213,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
36920
37213
  optionOrders,
36921
37214
  sortKinds,
36922
37215
  fieldTypeResolver: fieldTypeResolvers.row,
36923
- havingFieldTypeResolver: fieldTypeResolvers.having
37216
+ havingFieldTypeResolver: fieldTypeResolvers.having,
37217
+ appliedKlikes: pushdownPlan.appliedKlikes
36924
37218
  });
36925
37219
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
36926
37220
  }
@@ -37874,7 +38168,9 @@ async function executeDescribe(stmt, client, cacheContext) {
37874
38168
  function parseSql(sql) {
37875
38169
  try {
37876
38170
  const tokens = new Lexer(sql).tokenize();
37877
- return new Parser(tokens).parse();
38171
+ const stmt = new Parser(tokens).parse();
38172
+ validateKlikeStatement(stmt);
38173
+ return stmt;
37878
38174
  } catch (e) {
37879
38175
  if (e instanceof LexError || e instanceof ParseError) {
37880
38176
  throw e;
@@ -37985,6 +38281,7 @@ function buildBatchExplainPlans(sql, injectedVariables) {
37985
38281
  statementCount: statements.length,
37986
38282
  statements: statements.map((stmt, i) => {
37987
38283
  const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
38284
+ validateKlikeStatement(planStmt);
37988
38285
  const result = {
37989
38286
  index: i,
37990
38287
  type: analysis.statements[i].statementType,
@@ -38126,9 +38423,10 @@ function buildSelectPlan(stmt, label) {
38126
38423
  lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
38127
38424
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
38128
38425
  } else {
38426
+ const pushdownPlan = buildKlikePushdownPlan(stmt);
38129
38427
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
38130
38428
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
38131
- const mainPushDown = extractMainSafePushdown(stmt);
38429
+ const mainPushDown = pushdownPlan.mainCondition;
38132
38430
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
38133
38431
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38134
38432
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
@@ -38141,7 +38439,7 @@ function buildSelectPlan(stmt, label) {
38141
38439
  const joinFields = selectToFetchAllFields(stmt, join.table);
38142
38440
  const joinAliasStr = join.table.alias ? ` AS ${join.table.alias}` : "";
38143
38441
  const joinType = join.type === "INNER" ? "JOIN" : `${join.type} JOIN`;
38144
- const joinPushDown = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join.table.alias }) : null;
38442
+ const joinPushDown = join.table.alias ? pushdownPlan.joinConditions.get(join.table.alias) ?? null : null;
38145
38443
  const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
38146
38444
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38147
38445
  lines.push(` ${joinType}: APP${join.table.appId}${joinAliasStr} (${join.table.appId})`);
@@ -38184,6 +38482,10 @@ function buildWithPlan(stmt) {
38184
38482
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
38185
38483
  lines.push(...buildExplainPlan(stmt.query, "[main]"));
38186
38484
  }
38485
+ if (canInlineSingleCte(stmt)) {
38486
+ lines.push("");
38487
+ lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
38488
+ }
38187
38489
  return lines;
38188
38490
  }
38189
38491
  function collectFullScanReasons(stmt) {
@@ -38418,11 +38720,15 @@ var OperationCancelledError = class extends Error {
38418
38720
  // src/core/sql.ts
38419
38721
  function parseSqlStatement(sql) {
38420
38722
  const tokens = new Lexer(sql).tokenize();
38421
- return new Parser(tokens).parse();
38723
+ const stmt = new Parser(tokens).parse();
38724
+ validateKlikeStatement(stmt);
38725
+ return stmt;
38422
38726
  }
38423
38727
  function parseSqlStatements(sql) {
38424
38728
  const tokens = new Lexer(sql).tokenize();
38425
- return new Parser(tokens).parseStatements();
38729
+ const statements = new Parser(tokens).parseStatements();
38730
+ statements.forEach(validateKlikeStatement);
38731
+ return statements;
38426
38732
  }
38427
38733
 
38428
38734
  // src/output/batchEnvelope.ts
@@ -40516,7 +40822,7 @@ Options:
40516
40822
  -h, --help Show help
40517
40823
  `);
40518
40824
  }
40519
- var SERVER_VERSION = true ? "2.7.0" : "0.0.0-dev";
40825
+ var SERVER_VERSION = true ? "2.9.0" : "0.0.0-dev";
40520
40826
  function createServer(args) {
40521
40827
  const server = new McpServer({
40522
40828
  name: "ksql-mcp",