@rex0220/kintone-sql-tools 2.8.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.
@@ -34017,6 +34017,235 @@ function isAggregateSyntheticName(name) {
34017
34017
  return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
34018
34018
  }
34019
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
+
34020
34249
  // src/core/klikeValidation.ts
34021
34250
  var KlikeValidationError = class extends Error {
34022
34251
  constructor(message) {
@@ -34027,6 +34256,13 @@ var KlikeValidationError = class extends Error {
34027
34256
  function validateKlikeStatement(stmt) {
34028
34257
  validateStatement(stmt);
34029
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
+ }
34030
34266
  function validateStatement(stmt) {
34031
34267
  switch (stmt.type) {
34032
34268
  case "SELECT":
@@ -34058,7 +34294,7 @@ function validateStatement(stmt) {
34058
34294
  case "REORDER":
34059
34295
  if (containsKlike(stmt)) {
34060
34296
  throw new KlikeValidationError(
34061
- "KLIKE / NOT KLIKE \u306F v1 \u3067\u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
34297
+ "KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
34062
34298
  );
34063
34299
  }
34064
34300
  return;
@@ -34078,9 +34314,8 @@ function validateUnion(stmt) {
34078
34314
  validateSelect(stmt.right);
34079
34315
  }
34080
34316
  function validateWith(stmt) {
34081
- const inlined = buildEffectiveInlineSelect(stmt);
34082
- if (inlined !== null) {
34083
- validateSelect(inlined);
34317
+ if (canInlineSingleCte(stmt)) {
34318
+ validateSelect(buildInlinedQuery(stmt));
34084
34319
  return;
34085
34320
  }
34086
34321
  for (const cte of stmt.ctes) {
@@ -34092,10 +34327,15 @@ function validateWith(stmt) {
34092
34327
  function validateSelect(stmt) {
34093
34328
  validateOwnKlikeExpressions(stmt);
34094
34329
  if (whereHasKlike(stmt.where)) {
34095
- const inMemorySource = stmt.from.cteName !== null || stmt.joins.some((join) => join.table.cteName !== null);
34096
- if (inMemorySource || resolveSelectMode(stmt) === "FULL_SCAN") {
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
+ }
34097
34337
  throw new KlikeValidationError(
34098
- "KLIKE / NOT KLIKE \u306F kintone \u3078\u62BC\u3057\u4E0B\u3052\u308B SIMPLE SELECT \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u3053\u306E SELECT \u306F FULL_SCAN \u306B\u306A\u308A\u307E\u3059"
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"
34099
34339
  );
34100
34340
  }
34101
34341
  }
@@ -34125,33 +34365,6 @@ function validateNestedSelects(node) {
34125
34365
  if (obj.type === "SELECT") validateSelect(obj);
34126
34366
  }, true);
34127
34367
  }
34128
- function buildEffectiveInlineSelect(stmt) {
34129
- if (stmt.ctes.length !== 1) return null;
34130
- const cte = stmt.ctes[0];
34131
- if (cte.query.type !== "SELECT" || resolveSelectMode(cte.query) !== "SIMPLE") return null;
34132
- if (stmt.query.type !== "SELECT") return null;
34133
- const final = stmt.query;
34134
- if (final.from.cteName !== cte.name || final.joins.length > 0) return null;
34135
- if (final.groupBy.length > 0 || final.distinct) return null;
34136
- if (final.columns.some(
34137
- (column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
34138
- )) return null;
34139
- const where = cte.query.where === null ? final.where : final.where === null ? cte.query.where : { type: "LOGICAL", op: "AND", left: cte.query.where, right: final.where };
34140
- const columns = final.columns.every((column) => column.type === "WILDCARD") ? cte.query.columns : final.columns;
34141
- return {
34142
- type: "SELECT",
34143
- from: cte.query.from,
34144
- joins: [],
34145
- columns,
34146
- where,
34147
- groupBy: [],
34148
- having: null,
34149
- orderBy: final.orderBy.length > 0 ? final.orderBy : cte.query.orderBy,
34150
- limit: final.limit ?? cte.query.limit,
34151
- offset: final.offset ?? cte.query.offset,
34152
- distinct: false
34153
- };
34154
- }
34155
34368
  function containsKlike(node) {
34156
34369
  let found = false;
34157
34370
  walkObjects(node, (obj) => {
@@ -34656,25 +34869,29 @@ function resolveFieldRef(row, field) {
34656
34869
  }
34657
34870
 
34658
34871
  // src/engine/evalWhere.ts
34659
- function evalWhere(expr, row, resolveFieldType) {
34872
+ function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
34660
34873
  switch (expr.type) {
34661
34874
  case "BINARY":
34662
- return evalBinary(expr, row, resolveFieldType);
34875
+ return evalBinary(expr, row, resolveFieldType, appliedKlikes);
34663
34876
  case "NULL_CHECK":
34664
34877
  return evalNullCheck(expr, row);
34665
34878
  case "LOGICAL":
34666
- return evalLogical(expr, row, resolveFieldType);
34879
+ return evalLogical(expr, row, resolveFieldType, appliedKlikes);
34667
34880
  case "NOT":
34668
- return !evalWhere(expr.expr, row, resolveFieldType);
34881
+ return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
34669
34882
  case "GROUP":
34670
- return evalWhere(expr.expr, row, resolveFieldType);
34883
+ return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
34671
34884
  case "EXISTS": {
34672
34885
  const exists = expr.resolved;
34673
34886
  return expr.not ? !exists : exists;
34674
34887
  }
34675
34888
  }
34676
34889
  }
34677
- 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
+ }
34678
34895
  const left = resolveField(expr.left, row, resolveFieldType);
34679
34896
  const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
34680
34897
  return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
@@ -34756,11 +34973,11 @@ function evalNullCheck(expr, row) {
34756
34973
  const val = resolveField(expr.field, row);
34757
34974
  return expr.not ? val !== "" : val === "";
34758
34975
  }
34759
- function evalLogical(expr, row, resolveFieldType) {
34976
+ function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
34760
34977
  if (expr.op === "AND") {
34761
- 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);
34762
34979
  }
34763
- 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);
34764
34981
  }
34765
34982
  function resolveField(field, row, resolveFieldType) {
34766
34983
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
@@ -34861,7 +35078,7 @@ function matchLike(value, pattern) {
34861
35078
  function assertDmlWhereIsSafe(where) {
34862
35079
  if (whereHasKlike(where)) {
34863
35080
  throw new DmlConvertError(
34864
- "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\u3001v1 \u3067\u306F\u5168 DML \u3067\u5B89\u5168\u4E0A\u62D2\u5426\u3057\u307E\u3057\u305F\u3002"
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"
34865
35082
  );
34866
35083
  }
34867
35084
  if (!whereHasLike(where)) return;
@@ -35351,92 +35568,6 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
35351
35568
  };
35352
35569
  }
35353
35570
 
35354
- // src/core/optimization/wherePredicatePushdown.ts
35355
- function extractSafePushdownLeaves(where, options = {}) {
35356
- return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
35357
- }
35358
- function extractTypedPushdownCandidates(where, options = {}) {
35359
- return extractAndLeaves(
35360
- where,
35361
- (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
35362
- );
35363
- }
35364
- function extractAndLeaves(where, accept) {
35365
- switch (where.type) {
35366
- case "BINARY":
35367
- return accept(where) ? where : null;
35368
- case "LOGICAL":
35369
- if (where.op !== "AND") return null;
35370
- {
35371
- const left = extractAndLeaves(where.left, accept);
35372
- const right = extractAndLeaves(where.right, accept);
35373
- if (left && right) return { ...where, left, right };
35374
- return left ?? right ?? null;
35375
- }
35376
- case "GROUP":
35377
- return extractAndLeaves(where.expr, accept);
35378
- case "NULL_CHECK":
35379
- case "NOT":
35380
- case "EXISTS":
35381
- return null;
35382
- }
35383
- }
35384
- function isSafeComparison(expr, options) {
35385
- if (isSafeIdComparison(expr, options)) return true;
35386
- if (isNumericCandidate(expr, options)) {
35387
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
35388
- }
35389
- return isSelectionInComparison(expr, options);
35390
- }
35391
- var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
35392
- "DROP_DOWN",
35393
- "RADIO_BUTTON",
35394
- "CHECK_BOX",
35395
- "MULTI_SELECT",
35396
- "STATUS"
35397
- ]);
35398
- function isSelectionInComparison(expr, options) {
35399
- if (!isSelectionInCandidate(expr, options)) return false;
35400
- if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
35401
- const fieldType = options.fieldTypes?.get(expr.left.field);
35402
- if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
35403
- const validOptions = options.fieldOptions?.get(expr.left.field);
35404
- if (validOptions === void 0) return false;
35405
- return expr.right.values.every(
35406
- (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
35407
- );
35408
- }
35409
- function isSafeIdComparison(expr, options) {
35410
- if (!isTargetIdField(expr.left, options)) return false;
35411
- if (expr.right.type !== "NUMBER") return false;
35412
- return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
35413
- }
35414
- function isTargetIdField(field, options) {
35415
- if (field.type !== "FIELD" || field.field !== "$id") return false;
35416
- const targetAlias = options.tableAlias ?? null;
35417
- if (field.tableAlias === targetAlias) return true;
35418
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
35419
- }
35420
- function isNumericCandidate(expr, options) {
35421
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
35422
- if (!isTargetField(expr.left, options)) return false;
35423
- if (expr.right.type !== "NUMBER") return false;
35424
- if (expr.op === "=") return true;
35425
- return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
35426
- }
35427
- function isSelectionInCandidate(expr, options) {
35428
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
35429
- if (!isTargetField(expr.left, options)) return false;
35430
- if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
35431
- if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
35432
- return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
35433
- }
35434
- function isTargetField(field, options) {
35435
- const targetAlias = options.tableAlias ?? null;
35436
- if (field.tableAlias === targetAlias) return true;
35437
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
35438
- }
35439
-
35440
35571
  // src/engine/process.ts
35441
35572
  function flatten(record2, alias) {
35442
35573
  const row = {};
@@ -35501,9 +35632,9 @@ function applyJoin(leftRows, rightRows, join) {
35501
35632
  }
35502
35633
  return result;
35503
35634
  }
35504
- function applyFilter(rows, where, resolveFieldType) {
35635
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
35505
35636
  if (where === null) return rows;
35506
- return rows.filter((row) => evalWhere(where, row, resolveFieldType));
35637
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
35507
35638
  }
35508
35639
  function hasAggregateColumns(columns) {
35509
35640
  return columns.some(
@@ -35960,7 +36091,8 @@ function runFullScan(input) {
35960
36091
  optionOrders,
35961
36092
  sortKinds,
35962
36093
  fieldTypeResolver,
35963
- havingFieldTypeResolver
36094
+ havingFieldTypeResolver,
36095
+ appliedKlikes
35964
36096
  } = input;
35965
36097
  let rows = [];
35966
36098
  const mainAlias = stmt.from.alias;
@@ -35972,7 +36104,7 @@ function runFullScan(input) {
35972
36104
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
35973
36105
  rows = applyJoin(rows, rightRows, join);
35974
36106
  }
35975
- rows = applyFilter(rows, stmt.where, fieldTypeResolver);
36107
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
35976
36108
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
35977
36109
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
35978
36110
  }
@@ -36660,23 +36792,6 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
36660
36792
  }
36661
36793
  }
36662
36794
  }
36663
- function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
36664
- if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36665
- if (stmt.joins.length === 0) {
36666
- return extractSafePushdownLeaves(stmt.where, {
36667
- tableAlias: stmt.from.alias ?? void 0,
36668
- allowUnqualifiedFields: true,
36669
- fieldTypes,
36670
- fieldOptions
36671
- });
36672
- }
36673
- if (!stmt.from.alias) return null;
36674
- return extractSafePushdownLeaves(stmt.where, {
36675
- tableAlias: stmt.from.alias,
36676
- fieldTypes,
36677
- fieldOptions
36678
- });
36679
- }
36680
36795
  function extractMainTypedPushdownCandidate(stmt) {
36681
36796
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
36682
36797
  if (stmt.joins.length === 0) {
@@ -36858,23 +36973,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36858
36973
  loadTypedInFieldTypes(stmt, client, cacheContext)
36859
36974
  ]);
36860
36975
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
36861
- const mainPushDown = extractMainSafePushdown(
36862
- stmt,
36863
- pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
36864
- pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
36865
- );
36866
- const tableConditions = /* @__PURE__ */ new Map();
36867
- if (stmt.where !== null) {
36868
- for (const join of stmt.joins) {
36869
- if (!join.table.alias || join.table.subtableCode || join.table.cteName !== null) continue;
36870
- const cond = extractSafePushdownLeaves(stmt.where, {
36871
- tableAlias: join.table.alias,
36872
- fieldTypes: pushdownMeta.fieldTypesByApp.get(join.table.appId),
36873
- fieldOptions: pushdownMeta.fieldOptionsByApp.get(join.table.appId)
36874
- });
36875
- if (cond) tableConditions.set(join.table.alias, cond);
36876
- }
36877
- }
36976
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
36977
+ validateKlikePushdownPlan(pushdownPlan);
36978
+ const mainPushDown = pushdownPlan.mainCondition;
36979
+ const tableConditions = pushdownPlan.joinConditions;
36878
36980
  const mainFetch = fetchTableRecordsForFullScan(
36879
36981
  stmt,
36880
36982
  stmt.from,
@@ -36955,7 +37057,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
36955
37057
  optionOrders,
36956
37058
  sortKinds,
36957
37059
  fieldTypeResolver: fieldTypeResolvers.row,
36958
- havingFieldTypeResolver: fieldTypeResolvers.having
37060
+ havingFieldTypeResolver: fieldTypeResolvers.having,
37061
+ appliedKlikes: pushdownPlan.appliedKlikes
36959
37062
  });
36960
37063
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
36961
37064
  }
@@ -37004,83 +37107,6 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
37004
37107
  }
37005
37108
  return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
37006
37109
  }
37007
- function canInlineSingleCte(stmt) {
37008
- if (stmt.ctes.length !== 1) return false;
37009
- const cteDef = stmt.ctes[0];
37010
- if (cteDef.query.type !== "SELECT") return false;
37011
- if (resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
37012
- const finalQuery = stmt.query;
37013
- if (finalQuery.type !== "SELECT") return false;
37014
- if (finalQuery.from.cteName !== cteDef.name) return false;
37015
- if (finalQuery.joins.length > 0) return false;
37016
- if (finalQuery.groupBy.length > 0) return false;
37017
- if (finalQuery.distinct) return false;
37018
- if (finalQuery.columns.some(
37019
- (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"
37020
- )) return false;
37021
- return true;
37022
- }
37023
- function buildInlinedQuery(stmt) {
37024
- const cteBody = stmt.ctes[0].query;
37025
- const final = stmt.query;
37026
- const cteAlias = final.from.alias;
37027
- const finalWhere = stripCteAlias(final.where, cteAlias);
37028
- let mergedWhere;
37029
- if (cteBody.where === null) mergedWhere = finalWhere;
37030
- else if (finalWhere === null) mergedWhere = cteBody.where;
37031
- else mergedWhere = { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
37032
- const columns = final.columns.every((c) => c.type === "WILDCARD") ? cteBody.columns : final.columns;
37033
- return {
37034
- type: "SELECT",
37035
- from: cteBody.from,
37036
- joins: [],
37037
- columns,
37038
- where: mergedWhere,
37039
- groupBy: [],
37040
- having: null,
37041
- orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
37042
- limit: final.limit ?? cteBody.limit,
37043
- offset: final.offset ?? cteBody.offset,
37044
- distinct: false
37045
- };
37046
- }
37047
- function stripCteAlias(where, alias) {
37048
- if (where === null || alias === null) return where;
37049
- switch (where.type) {
37050
- case "BINARY":
37051
- return {
37052
- type: "BINARY",
37053
- op: where.op,
37054
- left: stripCteAliasFromFieldValue(where.left, alias),
37055
- right: where.right
37056
- };
37057
- case "NULL_CHECK":
37058
- return {
37059
- type: "NULL_CHECK",
37060
- not: where.not,
37061
- field: stripCteAliasFromFieldValue(where.field, alias)
37062
- };
37063
- case "LOGICAL":
37064
- return {
37065
- type: "LOGICAL",
37066
- op: where.op,
37067
- left: stripCteAlias(where.left, alias),
37068
- right: stripCteAlias(where.right, alias)
37069
- };
37070
- case "NOT":
37071
- return { type: "NOT", expr: stripCteAlias(where.expr, alias) };
37072
- case "GROUP":
37073
- return { type: "GROUP", expr: stripCteAlias(where.expr, alias) };
37074
- case "EXISTS":
37075
- return where;
37076
- }
37077
- }
37078
- function stripCteAliasFromFieldValue(fv, alias) {
37079
- if (fv.type === "FIELD" && fv.tableAlias === alias) {
37080
- return { type: "FIELD", field: fv.field, tableAlias: null };
37081
- }
37082
- return fv;
37083
- }
37084
37110
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
37085
37111
  if (query.type === "UNION") {
37086
37112
  const [leftResult, rightResult] = await Promise.all([
@@ -37115,8 +37141,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37115
37141
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
37116
37142
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
37117
37143
  ]);
37118
- 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
+ ]);
37119
37148
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
37149
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
37150
+ validateKlikePushdownPlan(pushdownPlan);
37120
37151
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
37121
37152
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
37122
37153
  scalarCachePromise.catch(() => {
@@ -37136,7 +37167,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37136
37167
  parallel,
37137
37168
  true,
37138
37169
  options.onLimitReached ?? "error",
37139
- warnings
37170
+ warnings,
37171
+ pushdownPlan.mainCondition
37140
37172
  );
37141
37173
  tables.set(stmt.from.alias, mainRecords);
37142
37174
  }
@@ -37145,6 +37177,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37145
37177
  const rows2 = cteCache.get(join.table.cteName) ?? [];
37146
37178
  tables.set(join.table.alias, rows2.map(processRowToKintoneRecord));
37147
37179
  } else {
37180
+ const pushDownCond = join.table.alias ? pushdownPlan.joinConditions.get(join.table.alias) ?? null : null;
37148
37181
  const optimized = await tryFetchJoinRecordsBySourceKeys(
37149
37182
  stmt,
37150
37183
  join,
@@ -37153,7 +37186,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37153
37186
  maxRecords2,
37154
37187
  parallel,
37155
37188
  options.onLimitReached ?? "error",
37156
- warnings
37189
+ warnings,
37190
+ pushDownCond
37157
37191
  );
37158
37192
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
37159
37193
  stmt,
@@ -37163,7 +37197,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37163
37197
  parallel,
37164
37198
  false,
37165
37199
  options.onLimitReached ?? "error",
37166
- warnings
37200
+ warnings,
37201
+ pushDownCond
37167
37202
  );
37168
37203
  tables.set(join.table.alias, joinRecords);
37169
37204
  }
@@ -37178,7 +37213,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
37178
37213
  optionOrders,
37179
37214
  sortKinds,
37180
37215
  fieldTypeResolver: fieldTypeResolvers.row,
37181
- havingFieldTypeResolver: fieldTypeResolvers.having
37216
+ havingFieldTypeResolver: fieldTypeResolvers.having,
37217
+ appliedKlikes: pushdownPlan.appliedKlikes
37182
37218
  });
37183
37219
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
37184
37220
  }
@@ -38387,9 +38423,10 @@ function buildSelectPlan(stmt, label) {
38387
38423
  lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
38388
38424
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
38389
38425
  } else {
38426
+ const pushdownPlan = buildKlikePushdownPlan(stmt);
38390
38427
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
38391
38428
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
38392
- const mainPushDown = extractMainSafePushdown(stmt);
38429
+ const mainPushDown = pushdownPlan.mainCondition;
38393
38430
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
38394
38431
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38395
38432
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
@@ -38402,7 +38439,7 @@ function buildSelectPlan(stmt, label) {
38402
38439
  const joinFields = selectToFetchAllFields(stmt, join.table);
38403
38440
  const joinAliasStr = join.table.alias ? ` AS ${join.table.alias}` : "";
38404
38441
  const joinType = join.type === "INNER" ? "JOIN" : `${join.type} JOIN`;
38405
- 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;
38406
38443
  const joinCandidate = join.table.alias && !join.table.subtableCode && join.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join.table.alias }) : null;
38407
38444
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
38408
38445
  lines.push(` ${joinType}: APP${join.table.appId}${joinAliasStr} (${join.table.appId})`);
@@ -38445,6 +38482,10 @@ function buildWithPlan(stmt) {
38445
38482
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
38446
38483
  lines.push(...buildExplainPlan(stmt.query, "[main]"));
38447
38484
  }
38485
+ if (canInlineSingleCte(stmt)) {
38486
+ lines.push("");
38487
+ lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
38488
+ }
38448
38489
  return lines;
38449
38490
  }
38450
38491
  function collectFullScanReasons(stmt) {
@@ -40781,7 +40822,7 @@ Options:
40781
40822
  -h, --help Show help
40782
40823
  `);
40783
40824
  }
40784
- var SERVER_VERSION = true ? "2.8.0" : "0.0.0-dev";
40825
+ var SERVER_VERSION = true ? "2.9.0" : "0.0.0-dev";
40785
40826
  function createServer(args) {
40786
40827
  const server = new McpServer({
40787
40828
  name: "ksql-mcp",