@rex0220/kintone-sql-tools 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/ksql.js CHANGED
@@ -449,6 +449,9 @@ function isJapanese(cp) {
449
449
  return cp >= 12352 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65281 && cp <= 65376;
450
450
  }
451
451
 
452
+ // src/types/ast.ts
453
+ var NO_FROM_CTE_NAME = "__NO_FROM__";
454
+
452
455
  // src/parser/parser.ts
453
456
  var MAX_BATCH_STATEMENTS = 20;
454
457
  var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
@@ -962,7 +965,7 @@ var Parser = class {
962
965
  const distinct = this.consume("DISTINCT" /* DISTINCT */);
963
966
  const columns = this.parseSelectColumns();
964
967
  const hasFrom = this.consume("FROM" /* FROM */);
965
- const from = hasFrom ? this.parseTableRef() : { appId: 0, alias: null, cteName: "__NO_FROM__" };
968
+ const from = hasFrom ? this.parseTableRef() : { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME };
966
969
  const joins = hasFrom ? this.parseJoins() : [];
967
970
  const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
968
971
  let groupBy = [];
@@ -2350,7 +2353,7 @@ function hasWhereClause(stmt) {
2350
2353
  function isNoFromSelectStatement(stmt) {
2351
2354
  if (!stmt || typeof stmt !== "object") return false;
2352
2355
  const obj = stmt;
2353
- return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
2356
+ return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === NO_FROM_CTE_NAME;
2354
2357
  }
2355
2358
  function getInsertValuesCount(stmt) {
2356
2359
  if (!stmt || typeof stmt !== "object") return null;
@@ -3116,6 +3119,235 @@ function isAggregateSyntheticName(name) {
3116
3119
  return /^(COUNT|SUM|AVG|MAX|MIN)\(/i.test(name);
3117
3120
  }
3118
3121
 
3122
+ // src/core/cteInlining.ts
3123
+ function canInlineSingleCte(stmt) {
3124
+ if (stmt.ctes.length !== 1) return false;
3125
+ const cteDef = stmt.ctes[0];
3126
+ if (cteDef.query.type !== "SELECT" || resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
3127
+ const finalQuery = stmt.query;
3128
+ if (finalQuery.type !== "SELECT") return false;
3129
+ if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
3130
+ if (finalQuery.groupBy.length > 0 || finalQuery.distinct) return false;
3131
+ return !finalQuery.columns.some(
3132
+ (column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
3133
+ );
3134
+ }
3135
+ function buildInlinedQuery(stmt) {
3136
+ const cteBody = stmt.ctes[0].query;
3137
+ const final = stmt.query;
3138
+ const finalWhere = stripCteAlias(final.where, final.from.alias);
3139
+ const where = cteBody.where === null ? finalWhere : finalWhere === null ? cteBody.where : { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
3140
+ const columns = final.columns.every((column) => column.type === "WILDCARD") ? cteBody.columns : final.columns;
3141
+ return {
3142
+ type: "SELECT",
3143
+ from: cteBody.from,
3144
+ joins: [],
3145
+ columns,
3146
+ where,
3147
+ groupBy: [],
3148
+ having: null,
3149
+ orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
3150
+ limit: final.limit ?? cteBody.limit,
3151
+ offset: final.offset ?? cteBody.offset,
3152
+ distinct: false
3153
+ };
3154
+ }
3155
+ function stripCteAlias(where, alias) {
3156
+ if (where === null || alias === null) return where;
3157
+ switch (where.type) {
3158
+ case "BINARY":
3159
+ return { ...where, left: stripCteAliasFromFieldValue(where.left, alias) };
3160
+ case "NULL_CHECK":
3161
+ return { ...where, field: stripCteAliasFromFieldValue(where.field, alias) };
3162
+ case "LOGICAL":
3163
+ return {
3164
+ ...where,
3165
+ left: stripCteAlias(where.left, alias),
3166
+ right: stripCteAlias(where.right, alias)
3167
+ };
3168
+ case "NOT":
3169
+ case "GROUP":
3170
+ return { ...where, expr: stripCteAlias(where.expr, alias) };
3171
+ case "EXISTS":
3172
+ return where;
3173
+ }
3174
+ }
3175
+ function stripCteAliasFromFieldValue(value, alias) {
3176
+ if (value.type === "FIELD" && value.tableAlias === alias) {
3177
+ return { ...value, tableAlias: null };
3178
+ }
3179
+ return value;
3180
+ }
3181
+
3182
+ // src/core/optimization/wherePredicatePushdown.ts
3183
+ function extractSafePushdownLeaves(where, options = {}) {
3184
+ return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
3185
+ }
3186
+ function extractTypedPushdownCandidates(where, options = {}) {
3187
+ return extractAndLeaves(
3188
+ where,
3189
+ (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
3190
+ );
3191
+ }
3192
+ function extractAndLeaves(where, accept) {
3193
+ switch (where.type) {
3194
+ case "BINARY":
3195
+ return accept(where) ? where : null;
3196
+ case "LOGICAL":
3197
+ if (where.op !== "AND") return null;
3198
+ {
3199
+ const left = extractAndLeaves(where.left, accept);
3200
+ const right = extractAndLeaves(where.right, accept);
3201
+ if (left && right) return { ...where, left, right };
3202
+ return left ?? right ?? null;
3203
+ }
3204
+ case "GROUP":
3205
+ return extractAndLeaves(where.expr, accept);
3206
+ case "NULL_CHECK":
3207
+ case "NOT":
3208
+ case "EXISTS":
3209
+ return null;
3210
+ }
3211
+ }
3212
+ function isSafeComparison(expr, options) {
3213
+ if (isKlikeComparison(expr, options)) return true;
3214
+ if (isSafeIdComparison(expr, options)) return true;
3215
+ if (isNumericCandidate(expr, options)) {
3216
+ return options.fieldTypes?.get(expr.left.field) === "NUMBER";
3217
+ }
3218
+ return isSelectionInComparison(expr, options);
3219
+ }
3220
+ function isKlikeComparison(expr, options) {
3221
+ if (options.allowKlike === false) return false;
3222
+ if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
3223
+ if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
3224
+ return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
3225
+ }
3226
+ var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
3227
+ "DROP_DOWN",
3228
+ "RADIO_BUTTON",
3229
+ "CHECK_BOX",
3230
+ "MULTI_SELECT",
3231
+ "STATUS"
3232
+ ]);
3233
+ function isSelectionInComparison(expr, options) {
3234
+ if (!isSelectionInCandidate(expr, options)) return false;
3235
+ if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
3236
+ const fieldType = options.fieldTypes?.get(expr.left.field);
3237
+ if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
3238
+ const validOptions = options.fieldOptions?.get(expr.left.field);
3239
+ if (validOptions === void 0) return false;
3240
+ return expr.right.values.every(
3241
+ (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
3242
+ );
3243
+ }
3244
+ function isSafeIdComparison(expr, options) {
3245
+ if (!isTargetIdField(expr.left, options)) return false;
3246
+ if (expr.right.type !== "NUMBER") return false;
3247
+ return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
3248
+ }
3249
+ function isTargetIdField(field, options) {
3250
+ if (field.type !== "FIELD" || field.field !== "$id") return false;
3251
+ const targetAlias = options.tableAlias ?? null;
3252
+ if (field.tableAlias === targetAlias) return true;
3253
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
3254
+ }
3255
+ function isNumericCandidate(expr, options) {
3256
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
3257
+ if (!isTargetField(expr.left, options)) return false;
3258
+ if (expr.right.type !== "NUMBER") return false;
3259
+ if (expr.op === "=") return true;
3260
+ return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
3261
+ }
3262
+ function isSelectionInCandidate(expr, options) {
3263
+ if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
3264
+ if (!isTargetField(expr.left, options)) return false;
3265
+ if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
3266
+ if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
3267
+ return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
3268
+ }
3269
+ function isTargetField(field, options) {
3270
+ const targetAlias = options.tableAlias ?? null;
3271
+ if (field.tableAlias === targetAlias) return true;
3272
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
3273
+ }
3274
+
3275
+ // src/core/optimization/klikePushdownPlan.ts
3276
+ function buildKlikePushdownPlan(stmt, options = {}) {
3277
+ const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
3278
+ const common = {
3279
+ allowKlike: joinsAreSafeForKlike,
3280
+ allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
3281
+ };
3282
+ let mainCondition = null;
3283
+ if (stmt.where !== null && !stmt.from.subtableCode && stmt.from.cteName === null) {
3284
+ if (stmt.joins.length === 0) {
3285
+ mainCondition = extractSafePushdownLeaves(stmt.where, {
3286
+ ...common,
3287
+ tableAlias: stmt.from.alias ?? void 0,
3288
+ allowUnqualifiedFields: true,
3289
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
3290
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
3291
+ });
3292
+ } else if (stmt.from.alias) {
3293
+ mainCondition = extractSafePushdownLeaves(stmt.where, {
3294
+ ...common,
3295
+ tableAlias: stmt.from.alias,
3296
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
3297
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
3298
+ });
3299
+ }
3300
+ }
3301
+ const joinConditions = /* @__PURE__ */ new Map();
3302
+ if (stmt.where !== null) {
3303
+ for (const join2 of stmt.joins) {
3304
+ if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
3305
+ const condition = extractSafePushdownLeaves(stmt.where, {
3306
+ ...common,
3307
+ tableAlias: join2.table.alias,
3308
+ fieldTypes: options.fieldTypesByApp?.get(join2.table.appId),
3309
+ fieldOptions: options.fieldOptionsByApp?.get(join2.table.appId)
3310
+ });
3311
+ if (condition !== null) joinConditions.set(join2.table.alias, condition);
3312
+ }
3313
+ }
3314
+ const appliedKlikes = /* @__PURE__ */ new Set();
3315
+ collectKlikes(mainCondition, appliedKlikes);
3316
+ for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
3317
+ const allKlikes = /* @__PURE__ */ new Set();
3318
+ collectKlikes(stmt.where, allKlikes);
3319
+ return {
3320
+ mainCondition,
3321
+ joinConditions,
3322
+ appliedKlikes,
3323
+ allKlikes: [...allKlikes]
3324
+ };
3325
+ }
3326
+ function unappliedKlikes(plan) {
3327
+ return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
3328
+ }
3329
+ function collectKlikes(where, out) {
3330
+ if (where === null) return;
3331
+ if (isKlike(where)) {
3332
+ out.add(where);
3333
+ return;
3334
+ }
3335
+ switch (where.type) {
3336
+ case "LOGICAL":
3337
+ collectKlikes(where.left, out);
3338
+ collectKlikes(where.right, out);
3339
+ return;
3340
+ case "NOT":
3341
+ case "GROUP":
3342
+ collectKlikes(where.expr, out);
3343
+ return;
3344
+ case "BINARY":
3345
+ case "NULL_CHECK":
3346
+ case "EXISTS":
3347
+ return;
3348
+ }
3349
+ }
3350
+
3119
3351
  // src/core/klikeValidation.ts
3120
3352
  var KlikeValidationError = class extends Error {
3121
3353
  constructor(message) {
@@ -3126,6 +3358,13 @@ var KlikeValidationError = class extends Error {
3126
3358
  function validateKlikeStatement(stmt) {
3127
3359
  validateStatement(stmt);
3128
3360
  }
3361
+ function validateKlikePushdownPlan(plan) {
3362
+ if (unappliedKlikes(plan).length > 0) {
3363
+ throw new KlikeValidationError(
3364
+ "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"
3365
+ );
3366
+ }
3367
+ }
3129
3368
  function validateStatement(stmt) {
3130
3369
  switch (stmt.type) {
3131
3370
  case "SELECT":
@@ -3157,7 +3396,7 @@ function validateStatement(stmt) {
3157
3396
  case "REORDER":
3158
3397
  if (containsKlike(stmt)) {
3159
3398
  throw new KlikeValidationError(
3160
- "KLIKE / NOT KLIKE \u306F v1 \u3067\u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
3399
+ "KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
3161
3400
  );
3162
3401
  }
3163
3402
  return;
@@ -3177,9 +3416,8 @@ function validateUnion(stmt) {
3177
3416
  validateSelect(stmt.right);
3178
3417
  }
3179
3418
  function validateWith(stmt) {
3180
- const inlined = buildEffectiveInlineSelect(stmt);
3181
- if (inlined !== null) {
3182
- validateSelect(inlined);
3419
+ if (canInlineSingleCte(stmt)) {
3420
+ validateSelect(buildInlinedQuery(stmt));
3183
3421
  return;
3184
3422
  }
3185
3423
  for (const cte of stmt.ctes) {
@@ -3191,10 +3429,15 @@ function validateWith(stmt) {
3191
3429
  function validateSelect(stmt) {
3192
3430
  validateOwnKlikeExpressions(stmt);
3193
3431
  if (whereHasKlike(stmt.where)) {
3194
- const inMemorySource = stmt.from.cteName !== null || stmt.joins.some((join2) => join2.table.cteName !== null);
3195
- if (inMemorySource || resolveSelectMode(stmt) === "FULL_SCAN") {
3432
+ const directKintoneSimple = resolveSelectMode(stmt) === "SIMPLE" && stmt.from.cteName === null && stmt.joins.every((join2) => join2.table.cteName === null);
3433
+ if (!directKintoneSimple) {
3434
+ const plan = buildKlikePushdownPlan(stmt, { allowUnresolvedVariables: true });
3435
+ if (unappliedKlikes(plan).length === 0) {
3436
+ validateNestedSelects(stmt);
3437
+ return;
3438
+ }
3196
3439
  throw new KlikeValidationError(
3197
- "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"
3440
+ "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"
3198
3441
  );
3199
3442
  }
3200
3443
  }
@@ -3224,33 +3467,6 @@ function validateNestedSelects(node) {
3224
3467
  if (obj.type === "SELECT") validateSelect(obj);
3225
3468
  }, true);
3226
3469
  }
3227
- function buildEffectiveInlineSelect(stmt) {
3228
- if (stmt.ctes.length !== 1) return null;
3229
- const cte = stmt.ctes[0];
3230
- if (cte.query.type !== "SELECT" || resolveSelectMode(cte.query) !== "SIMPLE") return null;
3231
- if (stmt.query.type !== "SELECT") return null;
3232
- const final = stmt.query;
3233
- if (final.from.cteName !== cte.name || final.joins.length > 0) return null;
3234
- if (final.groupBy.length > 0 || final.distinct) return null;
3235
- if (final.columns.some(
3236
- (column) => column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL"
3237
- )) return null;
3238
- 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 };
3239
- const columns = final.columns.every((column) => column.type === "WILDCARD") ? cte.query.columns : final.columns;
3240
- return {
3241
- type: "SELECT",
3242
- from: cte.query.from,
3243
- joins: [],
3244
- columns,
3245
- where,
3246
- groupBy: [],
3247
- having: null,
3248
- orderBy: final.orderBy.length > 0 ? final.orderBy : cte.query.orderBy,
3249
- limit: final.limit ?? cte.query.limit,
3250
- offset: final.offset ?? cte.query.offset,
3251
- distinct: false
3252
- };
3253
- }
3254
3470
  function containsKlike(node) {
3255
3471
  let found = false;
3256
3472
  walkObjects(node, (obj) => {
@@ -3755,25 +3971,29 @@ function resolveFieldRef(row, field) {
3755
3971
  }
3756
3972
 
3757
3973
  // src/engine/evalWhere.ts
3758
- function evalWhere(expr, row, resolveFieldType) {
3974
+ function evalWhere(expr, row, resolveFieldType, appliedKlikes) {
3759
3975
  switch (expr.type) {
3760
3976
  case "BINARY":
3761
- return evalBinary(expr, row, resolveFieldType);
3977
+ return evalBinary(expr, row, resolveFieldType, appliedKlikes);
3762
3978
  case "NULL_CHECK":
3763
3979
  return evalNullCheck(expr, row);
3764
3980
  case "LOGICAL":
3765
- return evalLogical(expr, row, resolveFieldType);
3981
+ return evalLogical(expr, row, resolveFieldType, appliedKlikes);
3766
3982
  case "NOT":
3767
- return !evalWhere(expr.expr, row, resolveFieldType);
3983
+ return !evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
3768
3984
  case "GROUP":
3769
- return evalWhere(expr.expr, row, resolveFieldType);
3985
+ return evalWhere(expr.expr, row, resolveFieldType, appliedKlikes);
3770
3986
  case "EXISTS": {
3771
3987
  const exists = expr.resolved;
3772
3988
  return expr.not ? !exists : exists;
3773
3989
  }
3774
3990
  }
3775
3991
  }
3776
- function evalBinary(expr, row, resolveFieldType) {
3992
+ function evalBinary(expr, row, resolveFieldType, appliedKlikes) {
3993
+ if (expr.op === "KLIKE" || expr.op === "NOT_KLIKE") {
3994
+ if (appliedKlikes?.has(expr)) return true;
3995
+ 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");
3996
+ }
3777
3997
  const left = resolveField(expr.left, row, resolveFieldType);
3778
3998
  const fieldType = expr.left.type === "FIELD" ? resolveFieldType?.(expr.left) : void 0;
3779
3999
  return evalOp(expr.op, left, expr.right, row, fieldType, resolveFieldType);
@@ -3855,11 +4075,11 @@ function evalNullCheck(expr, row) {
3855
4075
  const val = resolveField(expr.field, row);
3856
4076
  return expr.not ? val !== "" : val === "";
3857
4077
  }
3858
- function evalLogical(expr, row, resolveFieldType) {
4078
+ function evalLogical(expr, row, resolveFieldType, appliedKlikes) {
3859
4079
  if (expr.op === "AND") {
3860
- return evalWhere(expr.left, row, resolveFieldType) && evalWhere(expr.right, row, resolveFieldType);
4080
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) && evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
3861
4081
  }
3862
- return evalWhere(expr.left, row, resolveFieldType) || evalWhere(expr.right, row, resolveFieldType);
4082
+ return evalWhere(expr.left, row, resolveFieldType, appliedKlikes) || evalWhere(expr.right, row, resolveFieldType, appliedKlikes);
3863
4083
  }
3864
4084
  function resolveField(field, row, resolveFieldType) {
3865
4085
  if (field.type === "FUNC_FIELD") return evalStringFunc(field.expr, row);
@@ -3960,7 +4180,7 @@ function matchLike(value, pattern) {
3960
4180
  function assertDmlWhereIsSafe(where) {
3961
4181
  if (whereHasKlike(where)) {
3962
4182
  throw new DmlConvertError(
3963
- "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"
4183
+ "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"
3964
4184
  );
3965
4185
  }
3966
4186
  if (!whereHasLike(where)) return;
@@ -4302,6 +4522,7 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4302
4522
  let windowOffset = 0;
4303
4523
  const cursorQuery0 = buildCursorQuery(query, cursorId);
4304
4524
  const first = await fetchPage(fetcher, app, cursorQuery0, fetchFields, pageSize, windowOffset);
4525
+ notifySearchAborted(first, options);
4305
4526
  allRecords.push(...first.records);
4306
4527
  if (allRecords.length > maxRecords) {
4307
4528
  if (onLimit === "truncate") {
@@ -4345,6 +4566,7 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4345
4566
  (offset) => fetchPage(fetcher, app, cq, fetchFields, pageSize, offset)
4346
4567
  )
4347
4568
  );
4569
+ for (const response of responses) notifySearchAborted(response, options);
4348
4570
  let done = false;
4349
4571
  for (const res of responses) {
4350
4572
  allRecords.push(...res.records);
@@ -4373,6 +4595,9 @@ async function fetchAll(fetcher, app, query, fields, options = {}) {
4373
4595
  }
4374
4596
  return allRecords;
4375
4597
  }
4598
+ function notifySearchAborted(response, options) {
4599
+ if (response.searchAborted) options.onSearchAborted?.();
4600
+ }
4376
4601
  function extractIds(records) {
4377
4602
  return records.map((r) => {
4378
4603
  const raw = r["$id"]?.value;
@@ -4429,7 +4654,8 @@ async function fetchRecordsForSharedPlan(getRecords, app, query, fields, options
4429
4654
  maxRecords: options.maxRecords,
4430
4655
  parallel: options.parallel,
4431
4656
  onLimit: options.onLimit ?? "error",
4432
- onTruncate: options.onTruncate
4657
+ onTruncate: options.onTruncate,
4658
+ onSearchAborted: options.onSearchAborted
4433
4659
  });
4434
4660
  return {
4435
4661
  records,
@@ -4450,92 +4676,6 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
4450
4676
  };
4451
4677
  }
4452
4678
 
4453
- // src/core/optimization/wherePredicatePushdown.ts
4454
- function extractSafePushdownLeaves(where, options = {}) {
4455
- return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
4456
- }
4457
- function extractTypedPushdownCandidates(where, options = {}) {
4458
- return extractAndLeaves(
4459
- where,
4460
- (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
4461
- );
4462
- }
4463
- function extractAndLeaves(where, accept) {
4464
- switch (where.type) {
4465
- case "BINARY":
4466
- return accept(where) ? where : null;
4467
- case "LOGICAL":
4468
- if (where.op !== "AND") return null;
4469
- {
4470
- const left = extractAndLeaves(where.left, accept);
4471
- const right = extractAndLeaves(where.right, accept);
4472
- if (left && right) return { ...where, left, right };
4473
- return left ?? right ?? null;
4474
- }
4475
- case "GROUP":
4476
- return extractAndLeaves(where.expr, accept);
4477
- case "NULL_CHECK":
4478
- case "NOT":
4479
- case "EXISTS":
4480
- return null;
4481
- }
4482
- }
4483
- function isSafeComparison(expr, options) {
4484
- if (isSafeIdComparison(expr, options)) return true;
4485
- if (isNumericCandidate(expr, options)) {
4486
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
4487
- }
4488
- return isSelectionInComparison(expr, options);
4489
- }
4490
- var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
4491
- "DROP_DOWN",
4492
- "RADIO_BUTTON",
4493
- "CHECK_BOX",
4494
- "MULTI_SELECT",
4495
- "STATUS"
4496
- ]);
4497
- function isSelectionInComparison(expr, options) {
4498
- if (!isSelectionInCandidate(expr, options)) return false;
4499
- if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
4500
- const fieldType = options.fieldTypes?.get(expr.left.field);
4501
- if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
4502
- const validOptions = options.fieldOptions?.get(expr.left.field);
4503
- if (validOptions === void 0) return false;
4504
- return expr.right.values.every(
4505
- (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
4506
- );
4507
- }
4508
- function isSafeIdComparison(expr, options) {
4509
- if (!isTargetIdField(expr.left, options)) return false;
4510
- if (expr.right.type !== "NUMBER") return false;
4511
- return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
4512
- }
4513
- function isTargetIdField(field, options) {
4514
- if (field.type !== "FIELD" || field.field !== "$id") return false;
4515
- const targetAlias = options.tableAlias ?? null;
4516
- if (field.tableAlias === targetAlias) return true;
4517
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
4518
- }
4519
- function isNumericCandidate(expr, options) {
4520
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
4521
- if (!isTargetField(expr.left, options)) return false;
4522
- if (expr.right.type !== "NUMBER") return false;
4523
- if (expr.op === "=") return true;
4524
- return (expr.op === "<" || expr.op === ">") && Number.isSafeInteger(expr.right.value);
4525
- }
4526
- function isSelectionInCandidate(expr, options) {
4527
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
4528
- if (!isTargetField(expr.left, options)) return false;
4529
- if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
4530
- if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
4531
- return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
4532
- }
4533
- function isTargetField(field, options) {
4534
- const targetAlias = options.tableAlias ?? null;
4535
- if (field.tableAlias === targetAlias) return true;
4536
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
4537
- }
4538
-
4539
4679
  // src/engine/process.ts
4540
4680
  function flatten(record, alias) {
4541
4681
  const row = {};
@@ -4600,9 +4740,9 @@ function applyJoin(leftRows, rightRows, join2) {
4600
4740
  }
4601
4741
  return result;
4602
4742
  }
4603
- function applyFilter(rows, where, resolveFieldType) {
4743
+ function applyFilter(rows, where, resolveFieldType, appliedKlikes) {
4604
4744
  if (where === null) return rows;
4605
- return rows.filter((row) => evalWhere(where, row, resolveFieldType));
4745
+ return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes));
4606
4746
  }
4607
4747
  function hasAggregateColumns(columns) {
4608
4748
  return columns.some(
@@ -5059,7 +5199,8 @@ function runFullScan(input) {
5059
5199
  optionOrders,
5060
5200
  sortKinds,
5061
5201
  fieldTypeResolver,
5062
- havingFieldTypeResolver
5202
+ havingFieldTypeResolver,
5203
+ appliedKlikes
5063
5204
  } = input;
5064
5205
  let rows = [];
5065
5206
  const mainAlias = stmt.from.alias;
@@ -5071,7 +5212,7 @@ function runFullScan(input) {
5071
5212
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
5072
5213
  rows = applyJoin(rows, rightRows, join2);
5073
5214
  }
5074
- rows = applyFilter(rows, stmt.where, fieldTypeResolver);
5215
+ rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
5075
5216
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
5076
5217
  rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
5077
5218
  }
@@ -5127,13 +5268,32 @@ function toFlatString(value) {
5127
5268
  }
5128
5269
 
5129
5270
  // src/execute.ts
5271
+ var SEARCH_ABORTED_WARNING = "\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u3001\u7D50\u679C\u304C\u6B20\u843D\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002";
5272
+ var SearchAbortedError = class extends Error {
5273
+ constructor() {
5274
+ super("SearchAbortedError: kintone \u306E\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u305F\u305F\u3081\u3001\u5B8C\u5168\u306A\u5BFE\u8C61\u96C6\u5408\u3092\u78BA\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002");
5275
+ this.name = "SearchAbortedError";
5276
+ }
5277
+ };
5130
5278
  async function execute(sql, client, options = {}) {
5279
+ const startedAt = Date.now();
5280
+ const stmt = parseSql(sql);
5131
5281
  const metrics = createEmptyMetrics();
5132
5282
  const countedClient = wrapClientWithMetrics(client, metrics);
5133
- const startedAt = Date.now();
5134
- const result = await executeStatement(sql, countedClient, options);
5283
+ const collector = { aborted: false };
5284
+ const guardedClient = wrapClientWithSearchAbort(
5285
+ countedClient,
5286
+ collector,
5287
+ !isSelectLikeStatement(stmt)
5288
+ );
5289
+ const result = await executeParsedStatement(
5290
+ stmt,
5291
+ guardedClient,
5292
+ options,
5293
+ options.cacheContext ?? "default"
5294
+ );
5135
5295
  metrics.elapsedMs = Date.now() - startedAt;
5136
- return { ...result, metrics };
5296
+ return { ...attachSearchAbortWarning(result, collector), metrics };
5137
5297
  }
5138
5298
  function createEmptyMetrics() {
5139
5299
  return {
@@ -5182,10 +5342,27 @@ function wrapClientWithMetrics(client, metrics) {
5182
5342
  }
5183
5343
  };
5184
5344
  }
5185
- async function executeStatement(sql, client, options) {
5186
- const cacheContext = options.cacheContext ?? "default";
5187
- const stmt = parseSql(sql);
5188
- return executeParsedStatement(stmt, client, options, cacheContext);
5345
+ function wrapClientWithSearchAbort(client, collector, failClosed) {
5346
+ return {
5347
+ ...client,
5348
+ getRecords: async (params) => {
5349
+ const response = await client.getRecords(params);
5350
+ if (response.searchAborted) {
5351
+ collector.aborted = true;
5352
+ if (failClosed) throw new SearchAbortedError();
5353
+ }
5354
+ return response;
5355
+ }
5356
+ };
5357
+ }
5358
+ function isSelectLikeStatement(stmt) {
5359
+ return stmt.type === "SELECT" || stmt.type === "UNION" || stmt.type === "WITH";
5360
+ }
5361
+ function attachSearchAbortWarning(result, collector) {
5362
+ if (!collector.aborted || result.type !== "SELECT") return result;
5363
+ const warnings = new Set(result.warnings ?? []);
5364
+ warnings.add(SEARCH_ABORTED_WARNING);
5365
+ return { ...result, warnings: [...warnings] };
5189
5366
  }
5190
5367
  async function executeParsedStatement(stmt, client, options, cacheContext) {
5191
5368
  const unresolved = findVariableRef(stmt);
@@ -5298,10 +5475,19 @@ async function executeBatch(sql, client, options = {}) {
5298
5475
  targetAppId: info.targetAppId
5299
5476
  })
5300
5477
  } : batchOptions;
5478
+ const searchAbortCollector = { aborted: false };
5479
+ const statementClient = wrapClientWithSearchAbort(
5480
+ countedClient,
5481
+ searchAbortCollector,
5482
+ info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
5483
+ );
5301
5484
  const outcome = await runWithDeadline(
5302
- executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables, variables),
5485
+ executeBatchStatement(statements[i], info, statementClient, stmtOptions, cacheContext, tempTables, variables),
5303
5486
  remaining
5304
5487
  );
5488
+ if (outcome.result) {
5489
+ outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
5490
+ }
5305
5491
  results.push({ ...base, status: "success", ...outcome });
5306
5492
  } catch (e) {
5307
5493
  results.push({ ...base, status: "error", error: toBatchStatementError(e) });
@@ -5632,7 +5818,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache) {
5632
5818
  }
5633
5819
  }
5634
5820
  function isNoFromSelect(stmt) {
5635
- return stmt.from.appId === 0 && stmt.from.cteName === "__NO_FROM__";
5821
+ return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
5636
5822
  }
5637
5823
  function arithHasFieldRef(node) {
5638
5824
  if (node.type === "FIELD_REF") return true;
@@ -5759,23 +5945,6 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
5759
5945
  }
5760
5946
  }
5761
5947
  }
5762
- function extractMainSafePushdown(stmt, fieldTypes, fieldOptions) {
5763
- if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
5764
- if (stmt.joins.length === 0) {
5765
- return extractSafePushdownLeaves(stmt.where, {
5766
- tableAlias: stmt.from.alias ?? void 0,
5767
- allowUnqualifiedFields: true,
5768
- fieldTypes,
5769
- fieldOptions
5770
- });
5771
- }
5772
- if (!stmt.from.alias) return null;
5773
- return extractSafePushdownLeaves(stmt.where, {
5774
- tableAlias: stmt.from.alias,
5775
- fieldTypes,
5776
- fieldOptions
5777
- });
5778
- }
5779
5948
  function extractMainTypedPushdownCandidate(stmt) {
5780
5949
  if (stmt.where === null || stmt.from.subtableCode || stmt.from.cteName !== null) return null;
5781
5950
  if (stmt.joins.length === 0) {
@@ -5957,23 +6126,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
5957
6126
  loadTypedInFieldTypes(stmt, client, cacheContext)
5958
6127
  ]);
5959
6128
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
5960
- const mainPushDown = extractMainSafePushdown(
5961
- stmt,
5962
- pushdownMeta.fieldTypesByApp.get(stmt.from.appId),
5963
- pushdownMeta.fieldOptionsByApp.get(stmt.from.appId)
5964
- );
5965
- const tableConditions = /* @__PURE__ */ new Map();
5966
- if (stmt.where !== null) {
5967
- for (const join2 of stmt.joins) {
5968
- if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
5969
- const cond = extractSafePushdownLeaves(stmt.where, {
5970
- tableAlias: join2.table.alias,
5971
- fieldTypes: pushdownMeta.fieldTypesByApp.get(join2.table.appId),
5972
- fieldOptions: pushdownMeta.fieldOptionsByApp.get(join2.table.appId)
5973
- });
5974
- if (cond) tableConditions.set(join2.table.alias, cond);
5975
- }
5976
- }
6129
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
6130
+ validateKlikePushdownPlan(pushdownPlan);
6131
+ const mainPushDown = pushdownPlan.mainCondition;
6132
+ const tableConditions = pushdownPlan.joinConditions;
5977
6133
  const mainFetch = fetchTableRecordsForFullScan(
5978
6134
  stmt,
5979
6135
  stmt.from,
@@ -6054,7 +6210,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
6054
6210
  optionOrders,
6055
6211
  sortKinds,
6056
6212
  fieldTypeResolver: fieldTypeResolvers.row,
6057
- havingFieldTypeResolver: fieldTypeResolvers.having
6213
+ havingFieldTypeResolver: fieldTypeResolvers.having,
6214
+ appliedKlikes: pushdownPlan.appliedKlikes
6058
6215
  });
6059
6216
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
6060
6217
  }
@@ -6103,83 +6260,6 @@ async function executeWith(stmt, client, options, cacheContext, seed) {
6103
6260
  }
6104
6261
  return executeQueryWithCte(stmt.query, client, options, cteCache, cacheContext);
6105
6262
  }
6106
- function canInlineSingleCte(stmt) {
6107
- if (stmt.ctes.length !== 1) return false;
6108
- const cteDef = stmt.ctes[0];
6109
- if (cteDef.query.type !== "SELECT") return false;
6110
- if (resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
6111
- const finalQuery = stmt.query;
6112
- if (finalQuery.type !== "SELECT") return false;
6113
- if (finalQuery.from.cteName !== cteDef.name) return false;
6114
- if (finalQuery.joins.length > 0) return false;
6115
- if (finalQuery.groupBy.length > 0) return false;
6116
- if (finalQuery.distinct) return false;
6117
- if (finalQuery.columns.some(
6118
- (c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL"
6119
- )) return false;
6120
- return true;
6121
- }
6122
- function buildInlinedQuery(stmt) {
6123
- const cteBody = stmt.ctes[0].query;
6124
- const final = stmt.query;
6125
- const cteAlias = final.from.alias;
6126
- const finalWhere = stripCteAlias(final.where, cteAlias);
6127
- let mergedWhere;
6128
- if (cteBody.where === null) mergedWhere = finalWhere;
6129
- else if (finalWhere === null) mergedWhere = cteBody.where;
6130
- else mergedWhere = { type: "LOGICAL", op: "AND", left: cteBody.where, right: finalWhere };
6131
- const columns = final.columns.every((c) => c.type === "WILDCARD") ? cteBody.columns : final.columns;
6132
- return {
6133
- type: "SELECT",
6134
- from: cteBody.from,
6135
- joins: [],
6136
- columns,
6137
- where: mergedWhere,
6138
- groupBy: [],
6139
- having: null,
6140
- orderBy: final.orderBy.length > 0 ? final.orderBy : cteBody.orderBy,
6141
- limit: final.limit ?? cteBody.limit,
6142
- offset: final.offset ?? cteBody.offset,
6143
- distinct: false
6144
- };
6145
- }
6146
- function stripCteAlias(where, alias) {
6147
- if (where === null || alias === null) return where;
6148
- switch (where.type) {
6149
- case "BINARY":
6150
- return {
6151
- type: "BINARY",
6152
- op: where.op,
6153
- left: stripCteAliasFromFieldValue(where.left, alias),
6154
- right: where.right
6155
- };
6156
- case "NULL_CHECK":
6157
- return {
6158
- type: "NULL_CHECK",
6159
- not: where.not,
6160
- field: stripCteAliasFromFieldValue(where.field, alias)
6161
- };
6162
- case "LOGICAL":
6163
- return {
6164
- type: "LOGICAL",
6165
- op: where.op,
6166
- left: stripCteAlias(where.left, alias),
6167
- right: stripCteAlias(where.right, alias)
6168
- };
6169
- case "NOT":
6170
- return { type: "NOT", expr: stripCteAlias(where.expr, alias) };
6171
- case "GROUP":
6172
- return { type: "GROUP", expr: stripCteAlias(where.expr, alias) };
6173
- case "EXISTS":
6174
- return where;
6175
- }
6176
- }
6177
- function stripCteAliasFromFieldValue(fv, alias) {
6178
- if (fv.type === "FIELD" && fv.tableAlias === alias) {
6179
- return { type: "FIELD", field: fv.field, tableAlias: null };
6180
- }
6181
- return fv;
6182
- }
6183
6263
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
6184
6264
  if (query.type === "UNION") {
6185
6265
  const [leftResult, rightResult] = await Promise.all([
@@ -6199,7 +6279,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
6199
6279
  const rows = query.all ? combined : deduplicateRows(combined, leftCols);
6200
6280
  return { type: "SELECT", rows, columns: leftCols, rowCount: rows.length };
6201
6281
  }
6202
- const hasCteRef = query.from.cteName != null || query.joins.some((j) => j.table.cteName != null);
6282
+ const hasCteRef = query.from.cteName != null && query.from.cteName !== NO_FROM_CTE_NAME || query.joins.some((j) => j.table.cteName != null);
6203
6283
  if (!hasCteRef) {
6204
6284
  return executeSelect(query, client, options, cacheContext, cteCache);
6205
6285
  }
@@ -6214,8 +6294,13 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6214
6294
  resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
6215
6295
  resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
6216
6296
  ]);
6217
- const typedInFieldTypes = await loadTypedInFieldTypes(stmt, client, cacheContext);
6297
+ const [pushdownMeta, typedInFieldTypes] = await Promise.all([
6298
+ loadTypedPushdownMeta(stmt, client, cacheContext),
6299
+ loadTypedInFieldTypes(stmt, client, cacheContext)
6300
+ ]);
6218
6301
  const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
6302
+ const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
6303
+ validateKlikePushdownPlan(pushdownPlan);
6219
6304
  const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
6220
6305
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
6221
6306
  scalarCachePromise.catch(() => {
@@ -6235,7 +6320,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6235
6320
  parallel,
6236
6321
  true,
6237
6322
  options.onLimitReached ?? "error",
6238
- warnings
6323
+ warnings,
6324
+ pushdownPlan.mainCondition
6239
6325
  );
6240
6326
  tables.set(stmt.from.alias, mainRecords);
6241
6327
  }
@@ -6244,6 +6330,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6244
6330
  const rows2 = cteCache.get(join2.table.cteName) ?? [];
6245
6331
  tables.set(join2.table.alias, rows2.map(processRowToKintoneRecord));
6246
6332
  } else {
6333
+ const pushDownCond = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
6247
6334
  const optimized = await tryFetchJoinRecordsBySourceKeys(
6248
6335
  stmt,
6249
6336
  join2,
@@ -6252,7 +6339,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6252
6339
  maxRecords,
6253
6340
  parallel,
6254
6341
  options.onLimitReached ?? "error",
6255
- warnings
6342
+ warnings,
6343
+ pushDownCond
6256
6344
  );
6257
6345
  const joinRecords = optimized ?? await fetchTableRecordsForFullScan(
6258
6346
  stmt,
@@ -6262,7 +6350,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6262
6350
  parallel,
6263
6351
  false,
6264
6352
  options.onLimitReached ?? "error",
6265
- warnings
6353
+ warnings,
6354
+ pushDownCond
6266
6355
  );
6267
6356
  tables.set(join2.table.alias, joinRecords);
6268
6357
  }
@@ -6277,7 +6366,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
6277
6366
  optionOrders,
6278
6367
  sortKinds,
6279
6368
  fieldTypeResolver: fieldTypeResolvers.row,
6280
- havingFieldTypeResolver: fieldTypeResolvers.having
6369
+ havingFieldTypeResolver: fieldTypeResolvers.having,
6370
+ appliedKlikes: pushdownPlan.appliedKlikes
6281
6371
  });
6282
6372
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
6283
6373
  }
@@ -7486,9 +7576,10 @@ function buildSelectPlan(stmt, label) {
7486
7576
  lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
7487
7577
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
7488
7578
  } else {
7579
+ const pushdownPlan = buildKlikePushdownPlan(stmt);
7489
7580
  const mainFields = selectToFetchAllFields(stmt, stmt.from);
7490
7581
  const mainAliasStr = stmt.from.alias ? ` AS ${stmt.from.alias}` : "";
7491
- const mainPushDown = extractMainSafePushdown(stmt);
7582
+ const mainPushDown = pushdownPlan.mainCondition;
7492
7583
  const mainCandidate = extractMainTypedPushdownCandidate(stmt);
7493
7584
  const mainQ = mainPushDown !== null ? whereToKintone(mainPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
7494
7585
  lines.push(` app: APP${stmt.from.appId}${mainAliasStr} (${stmt.from.appId})`);
@@ -7501,7 +7592,7 @@ function buildSelectPlan(stmt, label) {
7501
7592
  const joinFields = selectToFetchAllFields(stmt, join2.table);
7502
7593
  const joinAliasStr = join2.table.alias ? ` AS ${join2.table.alias}` : "";
7503
7594
  const joinType = join2.type === "INNER" ? "JOIN" : `${join2.type} JOIN`;
7504
- const joinPushDown = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractSafePushdownLeaves(stmt.where, { tableAlias: join2.table.alias }) : null;
7595
+ const joinPushDown = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
7505
7596
  const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
7506
7597
  const joinQ = joinPushDown !== null ? whereToKintone(joinPushDown) : "(\u5168\u4EF6\u53D6\u5F97)";
7507
7598
  lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
@@ -7544,6 +7635,10 @@ function buildWithPlan(stmt) {
7544
7635
  if (stmt.query.type === "SELECT" || stmt.query.type === "UNION") {
7545
7636
  lines.push(...buildExplainPlan(stmt.query, "[main]"));
7546
7637
  }
7638
+ if (canInlineSingleCte(stmt)) {
7639
+ lines.push("");
7640
+ lines.push(...buildSelectPlan(buildInlinedQuery(stmt), "[effective: inlined CTE]"));
7641
+ }
7547
7642
  return lines;
7548
7643
  }
7549
7644
  function collectFullScanReasons(stmt) {
@@ -8228,10 +8323,11 @@ function detectSortKind(fieldType, calcFormat) {
8228
8323
  }
8229
8324
 
8230
8325
  // src/cli/nodeKintoneClient.ts
8326
+ var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
8231
8327
  function createNodeKintoneClient(baseUrl, tokenResolver) {
8232
8328
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
8233
8329
  const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
8234
- async function requestJson(path, init, appIdForToken) {
8330
+ async function requestJsonResponse(path, init, appIdForToken) {
8235
8331
  const headers = new Headers(init.headers ?? {});
8236
8332
  if (tokenResolver.auth.type === "token") {
8237
8333
  headers.set("X-Cybozu-API-Token", tokenResolver.auth.resolveToken(appIdForToken));
@@ -8279,7 +8375,14 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
8279
8375
  if (tokenResolver.debug) {
8280
8376
  tokenResolver.log?.(`[debug] response status=${res.status}`);
8281
8377
  }
8282
- return await res.json();
8378
+ const warning = res.headers.get("X-Cybozu-Warning") ?? "";
8379
+ return {
8380
+ body: await res.json(),
8381
+ searchAborted: warning.includes(SEARCH_ABORTED_HEADER_VALUE)
8382
+ };
8383
+ }
8384
+ async function requestJson(path, init, appIdForToken) {
8385
+ return (await requestJsonResponse(path, init, appIdForToken)).body;
8283
8386
  }
8284
8387
  function shouldRetryWithRecordNumberOrder(path, bodyText) {
8285
8388
  if (!path.includes("/v1/records.json?")) return false;
@@ -8313,11 +8416,12 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
8313
8416
  }
8314
8417
  const path = `${apiBasePath}/records.json?${qs}`;
8315
8418
  try {
8316
- return await requestJson(
8419
+ const response = await requestJsonResponse(
8317
8420
  path,
8318
8421
  { method: "GET" },
8319
8422
  params.app
8320
8423
  );
8424
+ return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
8321
8425
  } catch (err) {
8322
8426
  const msg = err instanceof Error ? err.message : String(err);
8323
8427
  if (!shouldRetryWithRecordNumberOrder(path, msg)) throw err;
@@ -8325,11 +8429,12 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
8325
8429
  if (tokenResolver.debug) {
8326
8430
  tokenResolver.log?.("[debug] retry with fallback query order by \u30EC\u30B3\u30FC\u30C9\u756A\u53F7 asc");
8327
8431
  }
8328
- return await requestJson(
8432
+ const response = await requestJsonResponse(
8329
8433
  retryPath,
8330
8434
  { method: "GET" },
8331
8435
  params.app
8332
8436
  );
8437
+ return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
8333
8438
  }
8334
8439
  },
8335
8440
  async postRecords(_params) {