@rex0220/kintone-sql-tools 3.9.0 → 3.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
@@ -4020,18 +4020,18 @@ function assertSafeParentWhere(where, multipleParents) {
4020
4020
  if (!multipleParents) {
4021
4021
  unsupported("a parent WHERE other than the single condition $id = <positive safe integer> in this phase");
4022
4022
  }
4023
- assertSafeParentPredicateNode(where);
4023
+ assertSafeParentPredicateNode(where, multipleParents);
4024
4024
  }
4025
- function assertSafeParentPredicateNode(node) {
4025
+ function assertSafeParentPredicateNode(node, allowKlike) {
4026
4026
  if (Array.isArray(node)) {
4027
- for (const value of node) assertSafeParentPredicateNode(value);
4027
+ for (const value of node) assertSafeParentPredicateNode(value, allowKlike);
4028
4028
  return;
4029
4029
  }
4030
4030
  if (node === null || typeof node !== "object") return;
4031
4031
  const item = node;
4032
4032
  const type = typeof item["type"] === "string" ? item["type"] : null;
4033
4033
  const op = typeof item["op"] === "string" ? item["op"] : null;
4034
- if (op === "KLIKE" || op === "NOT_KLIKE") unsupported("KLIKE in parent WHERE");
4034
+ if (!allowKlike && (op === "KLIKE" || op === "NOT_KLIKE")) unsupported("KLIKE in parent WHERE");
4035
4035
  if (type === "SELECT" || type === "SCALAR_SUBQUERY" || type === "SUBQUERY_IN_LIST" || type === "EXISTS") {
4036
4036
  unsupported("subqueries in parent WHERE");
4037
4037
  }
@@ -4040,7 +4040,7 @@ function assertSafeParentPredicateNode(node) {
4040
4040
  unsupported("aggregate or window expressions in parent WHERE");
4041
4041
  }
4042
4042
  if (type === "KINTONE_FUNC") unsupported("non-deterministic kintone functions in parent WHERE");
4043
- for (const value of Object.values(item)) assertSafeParentPredicateNode(value);
4043
+ for (const value of Object.values(item)) assertSafeParentPredicateNode(value, allowKlike);
4044
4044
  }
4045
4045
  function assertSafeChildPredicate(where, idxSelectors) {
4046
4046
  assertSafeApplyNode(where, "child row selectors", idxSelectors);
@@ -5082,31 +5082,31 @@ function isTargetField(field, options) {
5082
5082
  }
5083
5083
 
5084
5084
  // src/core/optimization/klikePushdownPlan.ts
5085
+ function buildSingleTableKlikePushdownPlan(where, options = {}) {
5086
+ const condition = where !== null && options.extractCondition !== false ? extractSafePushdownLeaves(where, options) : null;
5087
+ const appliedKlikes = /* @__PURE__ */ new Set();
5088
+ collectKlikes(condition, appliedKlikes);
5089
+ const allKlikes = /* @__PURE__ */ new Set();
5090
+ collectKlikes(where, allKlikes);
5091
+ return { condition, appliedKlikes, allKlikes: [...allKlikes] };
5092
+ }
5085
5093
  function buildKlikePushdownPlan(stmt, options = {}) {
5086
5094
  const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
5087
5095
  const common = {
5088
5096
  allowKlike: joinsAreSafeForKlike,
5089
5097
  allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
5090
5098
  };
5091
- let mainCondition = null;
5092
- if (stmt.where !== null && !stmt.from.subtableCode && stmt.from.cteName === null) {
5093
- if (stmt.joins.length === 0) {
5094
- mainCondition = extractSafePushdownLeaves(stmt.where, {
5095
- ...common,
5096
- tableAlias: stmt.from.alias ?? void 0,
5097
- allowUnqualifiedFields: true,
5098
- fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
5099
- fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
5100
- });
5101
- } else if (stmt.from.alias) {
5102
- mainCondition = extractSafePushdownLeaves(stmt.where, {
5103
- ...common,
5104
- tableAlias: stmt.from.alias,
5105
- fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
5106
- fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
5107
- });
5108
- }
5109
- }
5099
+ const mainIsPhysical = !stmt.from.subtableCode && stmt.from.cteName === null;
5100
+ const mainHasUsableAlias = stmt.joins.length === 0 || stmt.from.alias !== null;
5101
+ const mainPlan = buildSingleTableKlikePushdownPlan(stmt.where, {
5102
+ ...common,
5103
+ extractCondition: mainIsPhysical && mainHasUsableAlias,
5104
+ tableAlias: stmt.from.alias ?? void 0,
5105
+ allowUnqualifiedFields: stmt.joins.length === 0,
5106
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
5107
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
5108
+ });
5109
+ const mainCondition = mainPlan.condition;
5110
5110
  const joinConditions = /* @__PURE__ */ new Map();
5111
5111
  if (stmt.where !== null) {
5112
5112
  for (const join2 of stmt.joins) {
@@ -5120,16 +5120,13 @@ function buildKlikePushdownPlan(stmt, options = {}) {
5120
5120
  if (condition !== null) joinConditions.set(join2.table.alias, condition);
5121
5121
  }
5122
5122
  }
5123
- const appliedKlikes = /* @__PURE__ */ new Set();
5124
- collectKlikes(mainCondition, appliedKlikes);
5123
+ const appliedKlikes = new Set(mainPlan.appliedKlikes);
5125
5124
  for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
5126
- const allKlikes = /* @__PURE__ */ new Set();
5127
- collectKlikes(stmt.where, allKlikes);
5128
5125
  return {
5129
5126
  mainCondition,
5130
5127
  joinConditions,
5131
5128
  appliedKlikes,
5132
- allKlikes: [...allKlikes]
5129
+ allKlikes: mainPlan.allKlikes
5133
5130
  };
5134
5131
  }
5135
5132
  function unappliedKlikes(plan) {
@@ -5197,16 +5194,65 @@ function validateStatement(stmt) {
5197
5194
  case "ASSERT":
5198
5195
  validateNestedSelects(stmt);
5199
5196
  return;
5197
+ case "UPDATE":
5198
+ if (stmt.applyBlocks?.length && !isSinglePositiveRecordIdWhere(stmt.where) && whereHasKlike(stmt.where)) {
5199
+ validateKlikeWhereExpressions(stmt.where);
5200
+ validateNestedSelects(stmt);
5201
+ return;
5202
+ }
5203
+ if (containsKlike(stmt) && stmt.subtableCode) {
5204
+ throw new KlikeValidationError(
5205
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5206
+ );
5207
+ }
5208
+ if (!stmt.applyBlocks?.length && !stmt.subtableCode && whereHasKlike(stmt.where) && !containsKlikeOutsideWhereAndNestedSelects(stmt, stmt.where)) {
5209
+ validateKlikeWhereExpressions(stmt.where);
5210
+ validateNestedSelects(stmt);
5211
+ return;
5212
+ }
5213
+ if (containsKlike(stmt)) {
5214
+ throw new KlikeValidationError(
5215
+ "KLIKE / NOT KLIKE \u306F\u901A\u5E38\u89AA UPDATE \u306E WHERE\u3001\u307E\u305F\u306F APPLY \u8907\u6570\u89AA UPDATE \u306E\u5B89\u5168\u306A\u89AA WHERE \u3060\u3051\u3067\u4F7F\u7528\u3067\u304D\u307E\u3059"
5216
+ );
5217
+ }
5218
+ return;
5219
+ case "DELETE":
5220
+ if (containsKlike(stmt) && stmt.subtableCode) {
5221
+ throw new KlikeValidationError(
5222
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB DELETE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5223
+ );
5224
+ }
5225
+ if (!stmt.subtableCode && whereHasKlike(stmt.where)) {
5226
+ validateKlikeWhereExpressions(stmt.where);
5227
+ validateNestedSelects(stmt);
5228
+ return;
5229
+ }
5230
+ if (containsKlike(stmt)) {
5231
+ throw new KlikeValidationError(
5232
+ "KLIKE / NOT KLIKE \u306F\u901A\u5E38\u89AA DELETE \u306E WHERE \u3060\u3051\u3067\u4F7F\u7528\u3067\u304D\u307E\u3059"
5233
+ );
5234
+ }
5235
+ return;
5200
5236
  case "INSERT":
5201
5237
  case "INSERT_SELECT":
5238
+ if (containsKlike(stmt)) {
5239
+ throw new KlikeValidationError(
5240
+ "KLIKE / NOT KLIKE \u306F INSERT / INSERT SELECT \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5241
+ );
5242
+ }
5243
+ return;
5202
5244
  case "UPSERT":
5203
5245
  case "UPSERT_SELECT":
5204
- case "UPDATE":
5205
- case "DELETE":
5246
+ if (containsKlike(stmt)) {
5247
+ throw new KlikeValidationError(
5248
+ "KLIKE / NOT KLIKE \u306F UPSERT / UPSERT SELECT \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5249
+ );
5250
+ }
5251
+ return;
5206
5252
  case "REORDER":
5207
5253
  if (containsKlike(stmt)) {
5208
5254
  throw new KlikeValidationError(
5209
- "KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5255
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB REORDER \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5210
5256
  );
5211
5257
  }
5212
5258
  return;
@@ -5221,6 +5267,22 @@ function validateStatement(stmt) {
5221
5267
  return;
5222
5268
  }
5223
5269
  }
5270
+ function validateKlikeWhereExpressions(where) {
5271
+ walkWithoutNestedSelects(where, (expr) => {
5272
+ if (!isKlike(expr)) return;
5273
+ const right = expr.right;
5274
+ if (right.type !== "STRING" && right.type !== "VARIABLE") {
5275
+ throw new KlikeValidationError(
5276
+ "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"
5277
+ );
5278
+ }
5279
+ if (right.type === "STRING" && right.value.includes("%")) {
5280
+ throw new KlikeValidationError(
5281
+ "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"
5282
+ );
5283
+ }
5284
+ });
5285
+ }
5224
5286
  function validateSelectLike(query) {
5225
5287
  if (query.type === "SELECT") validateSelect(query);
5226
5288
  else if (query.type === "UNION") validateUnion(query);
@@ -5289,6 +5351,25 @@ function containsKlike(node) {
5289
5351
  });
5290
5352
  return found;
5291
5353
  }
5354
+ function containsKlikeOutsideWhereAndNestedSelects(node, allowedWhere) {
5355
+ let found = false;
5356
+ const visit = (value) => {
5357
+ if (found || value === allowedWhere || value === null || typeof value !== "object") return;
5358
+ if (Array.isArray(value)) {
5359
+ for (const item of value) visit(item);
5360
+ return;
5361
+ }
5362
+ const obj = value;
5363
+ if (obj.type === "SELECT") return;
5364
+ if (obj.type === "BINARY" && (obj.op === "KLIKE" || obj.op === "NOT_KLIKE")) {
5365
+ found = true;
5366
+ return;
5367
+ }
5368
+ for (const child of Object.values(obj)) visit(child);
5369
+ };
5370
+ visit(node);
5371
+ return found;
5372
+ }
5292
5373
  function isDescendantOf(root, target) {
5293
5374
  if (root === null) return false;
5294
5375
  if (root === target) return true;
@@ -6598,11 +6679,6 @@ function evaluateCustomChecks(groups, row, resolveFieldType) {
6598
6679
 
6599
6680
  // src/converter/dmlToKintone.ts
6600
6681
  function assertDmlWhereIsSafe(where) {
6601
- if (whereHasKlike(where)) {
6602
- throw new DmlConvertError(
6603
- "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"
6604
- );
6605
- }
6606
6682
  if (!whereHasLike(where)) return;
6607
6683
  throw new DmlConvertError(
6608
6684
  "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"
@@ -9368,6 +9444,21 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
9368
9444
  };
9369
9445
  }
9370
9446
 
9447
+ // src/core/optimization/applyParentSelectionPlan.ts
9448
+ function buildApplyParentSelectionPlan(where, metadata) {
9449
+ const plan = buildSingleTableKlikePushdownPlan(where, {
9450
+ allowUnqualifiedFields: true,
9451
+ allowKlike: true,
9452
+ fieldTypes: metadata.fieldTypes,
9453
+ fieldOptions: metadata.fieldOptions
9454
+ });
9455
+ return {
9456
+ prefilter: plan.condition,
9457
+ appliedKlikes: plan.appliedKlikes,
9458
+ unappliedKlikes: plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr))
9459
+ };
9460
+ }
9461
+
9371
9462
  // src/core/optimization/canonicalOrderPlanner.ts
9372
9463
  var REST_OFFSET_MAX = 1e4;
9373
9464
  var REST_LIMIT_MAX = 500;
@@ -11645,7 +11736,7 @@ function attachSearchAbortWarning(result, collector) {
11645
11736
  }
11646
11737
  async function executeParsedStatement(stmt, client, options, cacheContext) {
11647
11738
  const unresolved = findVariableRef(stmt);
11648
- if (unresolved !== null) {
11739
+ if (unresolved !== null && !isApplyParentKlikeStatement(stmt)) {
11649
11740
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
11650
11741
  }
11651
11742
  assertApplyScope("phase15b", stmt);
@@ -16141,16 +16232,21 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
16141
16232
  );
16142
16233
  const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
16143
16234
  const metadata = resolveApplyPatchMetadata(stmt, fieldInfos);
16144
- const fields = collectApplySnapshotFields(stmt, fieldInfos);
16145
- const baseQuery = updateToGetQuery(stmt).query;
16146
- const detectionLimit = dmlMaxRows + 1;
16147
- const snapshots = await fetchAll(client.getRecords, stmt.appId, baseQuery, [...fields], {
16148
- pageSize: Math.min(500, detectionLimit),
16149
- parallel: options.fetchParallel ?? 1,
16150
- maxRecords: detectionLimit,
16151
- stopAfter: detectionLimit,
16152
- onLimit: "error"
16153
- });
16235
+ let snapshots;
16236
+ if (usesApplyParentResidualSelection(stmt)) {
16237
+ snapshots = await selectApplyParentSnapshots(stmt, client, options, fieldInfos, cacheContext);
16238
+ } else {
16239
+ const fields = collectApplySnapshotFields(stmt, fieldInfos);
16240
+ const baseQuery = updateToGetQuery(stmt).query;
16241
+ const detectionLimit = dmlMaxRows + 1;
16242
+ snapshots = await fetchAll(client.getRecords, stmt.appId, baseQuery, [...fields], {
16243
+ pageSize: Math.min(500, detectionLimit),
16244
+ parallel: options.fetchParallel ?? 1,
16245
+ maxRecords: detectionLimit,
16246
+ stopAfter: detectionLimit,
16247
+ onLimit: "error"
16248
+ });
16249
+ }
16154
16250
  if (!stmt.validateOnly && snapshots.length > dmlMaxRows) {
16155
16251
  throw new Error(`ArgumentError: APPLY parent rows (${snapshots.length}) exceed dmlMaxRows (${dmlMaxRows}).`);
16156
16252
  }
@@ -16182,6 +16278,95 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
16182
16278
  const result = await executePreparedApplyWrite(prepared, client, diagnostic2);
16183
16279
  return { ...result, diagnostic: withApplyDiagnosticProgress(diagnostic2, result) };
16184
16280
  }
16281
+ function usesApplyParentResidualSelection(stmt) {
16282
+ return (stmt.applyBlocks?.length ?? 0) > 0 && !isSinglePositiveRecordIdWhere(stmt.where) && (whereHasLike(stmt.where) || whereHasKlike(stmt.where));
16283
+ }
16284
+ function isApplyParentKlikeStatement(stmt) {
16285
+ const target = stmt.type === "EXPLAIN" ? stmt.query : stmt;
16286
+ return target.type === "UPDATE" && usesApplyParentResidualSelection(target) && whereHasKlike(target.where);
16287
+ }
16288
+ var APPLY_PARENT_UNAPPLIED_KLIKE_ERROR = "APPLY \u8907\u6570\u89AA UPDATE \u306E\u89AA WHERE \u306B\u3001\u5B89\u5168\u306B\u62BC\u3057\u4E0B\u3052\u3089\u308C\u306A\u3044 KLIKE / NOT KLIKE \u304C\u3042\u308A\u307E\u3059\u3002\nOR / NOT \u914D\u4E0B\u306A\u3069 native query \u3078\u5B8C\u5168\u306B\u9069\u7528\u3067\u304D\u306A\u3044 KLIKE \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\nWHERE \u3092 AND \u306E\u5B89\u5168\u306A KLIKE \u6761\u4EF6\u3078\u66F8\u304D\u63DB\u3048\u308B\u304B\u3001SELECT \u3067\u78BA\u8A8D\u3057\u305F $id IN (...) \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
16289
+ function assertApplyParentKlikesFullyApplied(plan) {
16290
+ if (plan.unappliedKlikes.length > 0) throw new Error(`UnsupportedError: ${APPLY_PARENT_UNAPPLIED_KLIKE_ERROR}`);
16291
+ }
16292
+ async function selectApplyParentSnapshots(stmt, client, options, fieldInfos, cacheContext) {
16293
+ const topLevelInfos = fieldInfos.filter((field) => !field.inSubtable);
16294
+ const infoByCode = new Map(topLevelInfos.map((field) => [field.code, field]));
16295
+ const fieldTypes = new Map(topLevelInfos.map((field) => [field.code, field.fieldType]));
16296
+ const fieldOptions = new Map(topLevelInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
16297
+ const selectionPlan = buildApplyParentSelectionPlan(stmt.where, { fieldTypes, fieldOptions });
16298
+ assertApplyParentKlikesFullyApplied(selectionPlan);
16299
+ const prefilterQuery = selectionPlan.prefilter === null ? "" : whereToKintone(selectionPlan.prefilter);
16300
+ const fields = new Set(collectApplySnapshotFields(stmt, fieldInfos));
16301
+ for (const field of collectApplyParentWhereFields(stmt.where)) fields.add(field);
16302
+ const resolvers = await buildApplyParentFieldResolvers(
16303
+ stmt.appId,
16304
+ stmt.where,
16305
+ infoByCode,
16306
+ client,
16307
+ cacheContext
16308
+ );
16309
+ const candidates = await fetchAll(
16310
+ client.getRecords,
16311
+ stmt.appId,
16312
+ prefilterQuery,
16313
+ [...fields],
16314
+ {
16315
+ maxRecords: options.maxRecords ?? 1e4,
16316
+ parallel: options.fetchParallel ?? 1,
16317
+ onLimit: "error"
16318
+ // prefilter は target の超集合なので stopAfter を設定せず最後まで取得する。
16319
+ }
16320
+ );
16321
+ return candidates.map((snapshot) => ({ snapshot, row: flatten(snapshot, null) })).filter(({ row }) => evalWhere(
16322
+ stmt.where,
16323
+ row,
16324
+ resolvers.fieldTypeResolver,
16325
+ selectionPlan.appliedKlikes,
16326
+ resolvers.fieldSemanticsResolver
16327
+ )).map(({ snapshot }) => snapshot);
16328
+ }
16329
+ function collectApplyParentWhereFields(where) {
16330
+ return collectValidateWhereFields(where);
16331
+ }
16332
+ async function buildApplyParentFieldResolvers(appId, where, infoByCode, client, cacheContext) {
16333
+ const orderedFields = collectOrderedWhereFields(where);
16334
+ const needsStatusOrder = [...orderedFields].some((field) => infoByCode.get(field)?.fieldType === "STATUS");
16335
+ const statusOrder = needsStatusOrder ? await loadProcessStatusOrder(appId, client, cacheContext) : void 0;
16336
+ const fieldTypeResolver = (field) => field.field === "$id" ? "NUMBER" : infoByCode.get(field.field)?.fieldType;
16337
+ const fieldSemanticsResolver = (field) => {
16338
+ if (field.field === "$id") {
16339
+ return withFieldSemanticSource(resolveFieldSemantics({ fieldType: "__ID__" }), appId, "$id");
16340
+ }
16341
+ const info = infoByCode.get(field.field);
16342
+ if (!info) return void 0;
16343
+ const base = info.semantics ?? resolveFieldSemantics(info);
16344
+ const semantics = info.fieldType === "STATUS" && statusOrder ? { ...base, optionOrder: statusOrder } : base;
16345
+ return withFieldSemanticSource(semantics, appId, info.code);
16346
+ };
16347
+ return { fieldTypeResolver, fieldSemanticsResolver };
16348
+ }
16349
+ function collectOrderedWhereFields(where) {
16350
+ const fields = /* @__PURE__ */ new Set();
16351
+ const visit = (node) => {
16352
+ if (Array.isArray(node)) {
16353
+ node.forEach(visit);
16354
+ return;
16355
+ }
16356
+ if (node === null || typeof node !== "object") return;
16357
+ const value = node;
16358
+ if (value["type"] === "SELECT") return;
16359
+ if (value["type"] === "BINARY" && [">", "<", ">=", "<="].includes(String(value["op"]))) {
16360
+ const left = value["left"];
16361
+ if (left?.["type"] === "FIELD" && typeof left["field"] === "string") {
16362
+ fields.add(left["field"]);
16363
+ }
16364
+ }
16365
+ Object.values(value).forEach(visit);
16366
+ };
16367
+ visit(where);
16368
+ return fields;
16369
+ }
16185
16370
  function materializePreparedApplyInsertValidation(stmt, prepared, fieldInfos) {
16186
16371
  const errors = prepared.validations.flatMap((validation) => validation.errors);
16187
16372
  const invalidRows = prepared.validations.reduce(
@@ -17067,6 +17252,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
17067
17252
  return cache;
17068
17253
  }
17069
17254
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
17255
+ var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
17070
17256
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
17071
17257
  const fieldApps = /* @__PURE__ */ new Set();
17072
17258
  const processStatusApps = /* @__PURE__ */ new Set();
@@ -17191,12 +17377,19 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
17191
17377
  const update = node;
17192
17378
  const fields = await getFieldsCached(update.appId, tracedClient, cacheContext);
17193
17379
  resolveApplyPatchMetadata(update, fields);
17380
+ if (usesApplyParentResidualSelection(update)) {
17381
+ const topLevel = fields.filter((field) => !field.inSubtable);
17382
+ const selectionPlan = buildApplyParentSelectionPlan(update.where, {
17383
+ fieldTypes: new Map(topLevel.map((field) => [field.code, field.fieldType])),
17384
+ fieldOptions: new Map(topLevel.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []))
17385
+ });
17386
+ applyParentExplainPlan.set(update, selectionPlan);
17387
+ }
17388
+ }
17389
+ const dml = node;
17390
+ if (dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) {
17391
+ await assertDmlWhereCapability(dml, tracedClient, cacheContext);
17194
17392
  }
17195
- await assertDmlWhereCapability(
17196
- node,
17197
- tracedClient,
17198
- cacheContext
17199
- );
17200
17393
  }
17201
17394
  await Promise.all(Object.values(typed).map(visit));
17202
17395
  };
@@ -17367,7 +17560,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
17367
17560
  analysis.capabilities,
17368
17561
  analysis.orderPlans,
17369
17562
  dmlMaxRows,
17370
- dmlMaxSubtableRows
17563
+ dmlMaxSubtableRows,
17564
+ maxRecords
17371
17565
  ),
17372
17566
  cursorMaxActive
17373
17567
  )
@@ -17390,7 +17584,7 @@ function addCursorConcurrency(lines, cursorMaxActive) {
17390
17584
  }
17391
17585
  return result;
17392
17586
  }
17393
- function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
17587
+ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
17394
17588
  if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
17395
17589
  if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
17396
17590
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
@@ -17403,7 +17597,8 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
17403
17597
  capabilities,
17404
17598
  orderPlans,
17405
17599
  dmlMaxRows,
17406
- dmlMaxSubtableRows
17600
+ dmlMaxSubtableRows,
17601
+ maxRecords
17407
17602
  );
17408
17603
  if (query.type === "DELETE") return buildDeletePlan(query, label);
17409
17604
  if (query.type === "REORDER") return buildReorderPlan(query, label);
@@ -17709,9 +17904,9 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
17709
17904
  lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
17710
17905
  return lines;
17711
17906
  }
17712
- function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
17907
+ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
17713
17908
  if (stmt.applyBlocks?.length) {
17714
- return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows);
17909
+ return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords);
17715
17910
  }
17716
17911
  const isArith = hasArithAssignment(stmt);
17717
17912
  const isStringFunc = stmt.assignments.some((a) => a.value.type === "STRING_FUNC");
@@ -17729,6 +17924,10 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17729
17924
  } else {
17730
17925
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
17731
17926
  }
17927
+ if (!stmt.subtableCode) {
17928
+ lines.push(" selection: exact native pushdown; JS residual none");
17929
+ lines.push(" search abort: DML fail-closed (SearchAbortedError; mutation 0)");
17930
+ }
17732
17931
  lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
17733
17932
  const setTypes = [];
17734
17933
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
@@ -17754,7 +17953,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17754
17953
  }
17755
17954
  return lines;
17756
17955
  }
17757
- function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
17956
+ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords) {
17758
17957
  const diagnostic2 = buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows);
17759
17958
  const branch = diagnostic2.branches[0];
17760
17959
  const blocks = stmt.applyBlocks;
@@ -17768,12 +17967,24 @@ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
17768
17967
  });
17769
17968
  const operationKinds = [...new Set(branch.targets.flatMap((target) => target.operations.map((operation) => operation.kind)))];
17770
17969
  const hasRemove = operationKinds.includes("REMOVE");
17970
+ const selectionPlan = applyParentExplainPlan.get(stmt);
17971
+ const selectionLines = selectionPlan ? [
17972
+ "parent selection: safe prefilter + JS residual evaluation",
17973
+ `kintone prefilter: ${selectionPlan.prefilter === null ? "(none; empty query)" : whereToKintone(selectionPlan.prefilter)}`,
17974
+ "JS residual: original parent WHERE",
17975
+ `applied KLIKE: ${selectionPlan.appliedKlikes.size}`,
17976
+ `unapplied KLIKE: ${selectionPlan.unappliedKlikes.length}${selectionPlan.unappliedKlikes.length > 0 ? " (unsupported: cannot be fully applied to native query)" : ""}`,
17977
+ `candidate limit: maxRecords=${maxRecords}, onLimit=error, stopAfter=none`,
17978
+ `target guard: dmlMaxRows=${dmlMaxRows} after JS residual evaluation`,
17979
+ "search abort: DML fail-closed (B7-P3; all surfaces, no surface gate)"
17980
+ ] : [];
17771
17981
  return [
17772
17982
  ...label ? [label] : [],
17773
17983
  "statement: UPDATE APPLY",
17774
17984
  `target app: APP${stmt.appId}`,
17775
17985
  `parent selector: ${safeWhereToKintone(stmt.where)}`,
17776
- "parent cardinality: single",
17986
+ `parent cardinality: ${isSinglePositiveRecordIdWhere(stmt.where) ? "single" : "multiple"}`,
17987
+ ...selectionLines,
17777
17988
  `apply target: ${branch.targets.map((target) => `${target.field} (${target.targetKind})`).join(" | ")}`,
17778
17989
  `operations: ${operationKinds.join(" | ")}`,
17779
17990
  `selector: ${selectorKinds.join(" | ")}`,
@@ -17799,6 +18010,10 @@ function buildDeletePlan(stmt, label) {
17799
18010
  lines.push(` [DELETE]`);
17800
18011
  lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
17801
18012
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
18013
+ if (!stmt.subtableCode) {
18014
+ lines.push(" selection: exact native pushdown; JS residual none");
18015
+ lines.push(" search abort: DML fail-closed (SearchAbortedError; mutation 0)");
18016
+ }
17802
18017
  lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
17803
18018
  return lines;
17804
18019
  }
@@ -18802,6 +19017,12 @@ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
18802
19017
  return manager;
18803
19018
  }
18804
19019
 
19020
+ // src/core/searchAbortWarning.ts
19021
+ var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
19022
+ function isSearchAbortedWarning(value) {
19023
+ return value?.includes(SEARCH_ABORTED_HEADER_VALUE) === true;
19024
+ }
19025
+
18805
19026
  // src/node/kintoneMetadata.ts
18806
19027
  var KINTONE_METADATA_RESOURCES = [
18807
19028
  "app",
@@ -18994,7 +19215,6 @@ var KintoneApiError = class extends Error {
18994
19215
  this.name = "KintoneApiError";
18995
19216
  }
18996
19217
  };
18997
- var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
18998
19218
  function createNodeKintoneConnection(baseUrl, tokenResolver) {
18999
19219
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
19000
19220
  const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
@@ -19052,10 +19272,9 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
19052
19272
  if (tokenResolver.debug) {
19053
19273
  tokenResolver.log?.(`[debug] response status=${res.status}`);
19054
19274
  }
19055
- const warning = res.headers.get("X-Cybozu-Warning") ?? "";
19056
19275
  return {
19057
19276
  response: res,
19058
- searchAborted: warning.includes(SEARCH_ABORTED_HEADER_VALUE)
19277
+ searchAborted: isSearchAbortedWarning(res.headers.get("X-Cybozu-Warning"))
19059
19278
  };
19060
19279
  }
19061
19280
  async function requestJsonResponse(path, init, appIdForToken) {