@rex0220/kintone-sql-tools 3.9.0 → 3.11.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);
@@ -4930,6 +4930,9 @@ function canInlineSingleCte(stmt) {
4930
4930
  if (stmt.ctes.length !== 1) return false;
4931
4931
  const cteDef = stmt.ctes[0];
4932
4932
  if (cteDef.query.type !== "SELECT" || resolveSelectMode(cteDef.query) !== "SIMPLE") return false;
4933
+ if (!cteDef.query.columns.every(
4934
+ (column) => column.type === "WILDCARD" || column.type === "FIELD" && (column.alias === null || column.alias === column.field)
4935
+ )) return false;
4933
4936
  const finalQuery = stmt.query;
4934
4937
  if (finalQuery.type !== "SELECT") return false;
4935
4938
  if (finalQuery.from.cteName !== cteDef.name || finalQuery.joins.length > 0) return false;
@@ -5082,31 +5085,31 @@ function isTargetField(field, options) {
5082
5085
  }
5083
5086
 
5084
5087
  // src/core/optimization/klikePushdownPlan.ts
5088
+ function buildSingleTableKlikePushdownPlan(where, options = {}) {
5089
+ const condition = where !== null && options.extractCondition !== false ? extractSafePushdownLeaves(where, options) : null;
5090
+ const appliedKlikes = /* @__PURE__ */ new Set();
5091
+ collectKlikes(condition, appliedKlikes);
5092
+ const allKlikes = /* @__PURE__ */ new Set();
5093
+ collectKlikes(where, allKlikes);
5094
+ return { condition, appliedKlikes, allKlikes: [...allKlikes] };
5095
+ }
5085
5096
  function buildKlikePushdownPlan(stmt, options = {}) {
5086
5097
  const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
5087
5098
  const common = {
5088
5099
  allowKlike: joinsAreSafeForKlike,
5089
5100
  allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
5090
5101
  };
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
- }
5102
+ const mainIsPhysical = !stmt.from.subtableCode && stmt.from.cteName === null;
5103
+ const mainHasUsableAlias = stmt.joins.length === 0 || stmt.from.alias !== null;
5104
+ const mainPlan = buildSingleTableKlikePushdownPlan(stmt.where, {
5105
+ ...common,
5106
+ extractCondition: mainIsPhysical && mainHasUsableAlias,
5107
+ tableAlias: stmt.from.alias ?? void 0,
5108
+ allowUnqualifiedFields: stmt.joins.length === 0,
5109
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
5110
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
5111
+ });
5112
+ const mainCondition = mainPlan.condition;
5110
5113
  const joinConditions = /* @__PURE__ */ new Map();
5111
5114
  if (stmt.where !== null) {
5112
5115
  for (const join2 of stmt.joins) {
@@ -5120,16 +5123,13 @@ function buildKlikePushdownPlan(stmt, options = {}) {
5120
5123
  if (condition !== null) joinConditions.set(join2.table.alias, condition);
5121
5124
  }
5122
5125
  }
5123
- const appliedKlikes = /* @__PURE__ */ new Set();
5124
- collectKlikes(mainCondition, appliedKlikes);
5126
+ const appliedKlikes = new Set(mainPlan.appliedKlikes);
5125
5127
  for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
5126
- const allKlikes = /* @__PURE__ */ new Set();
5127
- collectKlikes(stmt.where, allKlikes);
5128
5128
  return {
5129
5129
  mainCondition,
5130
5130
  joinConditions,
5131
5131
  appliedKlikes,
5132
- allKlikes: [...allKlikes]
5132
+ allKlikes: mainPlan.allKlikes
5133
5133
  };
5134
5134
  }
5135
5135
  function unappliedKlikes(plan) {
@@ -5197,16 +5197,65 @@ function validateStatement(stmt) {
5197
5197
  case "ASSERT":
5198
5198
  validateNestedSelects(stmt);
5199
5199
  return;
5200
+ case "UPDATE":
5201
+ if (stmt.applyBlocks?.length && !isSinglePositiveRecordIdWhere(stmt.where) && whereHasKlike(stmt.where)) {
5202
+ validateKlikeWhereExpressions(stmt.where);
5203
+ validateNestedSelects(stmt);
5204
+ return;
5205
+ }
5206
+ if (containsKlike(stmt) && stmt.subtableCode) {
5207
+ throw new KlikeValidationError(
5208
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5209
+ );
5210
+ }
5211
+ if (!stmt.applyBlocks?.length && !stmt.subtableCode && whereHasKlike(stmt.where) && !containsKlikeOutsideWhereAndNestedSelects(stmt, stmt.where)) {
5212
+ validateKlikeWhereExpressions(stmt.where);
5213
+ validateNestedSelects(stmt);
5214
+ return;
5215
+ }
5216
+ if (containsKlike(stmt)) {
5217
+ throw new KlikeValidationError(
5218
+ "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"
5219
+ );
5220
+ }
5221
+ return;
5222
+ case "DELETE":
5223
+ if (containsKlike(stmt) && stmt.subtableCode) {
5224
+ throw new KlikeValidationError(
5225
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB DELETE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5226
+ );
5227
+ }
5228
+ if (!stmt.subtableCode && whereHasKlike(stmt.where)) {
5229
+ validateKlikeWhereExpressions(stmt.where);
5230
+ validateNestedSelects(stmt);
5231
+ return;
5232
+ }
5233
+ if (containsKlike(stmt)) {
5234
+ throw new KlikeValidationError(
5235
+ "KLIKE / NOT KLIKE \u306F\u901A\u5E38\u89AA DELETE \u306E WHERE \u3060\u3051\u3067\u4F7F\u7528\u3067\u304D\u307E\u3059"
5236
+ );
5237
+ }
5238
+ return;
5200
5239
  case "INSERT":
5201
5240
  case "INSERT_SELECT":
5241
+ if (containsKlike(stmt)) {
5242
+ throw new KlikeValidationError(
5243
+ "KLIKE / NOT KLIKE \u306F INSERT / INSERT SELECT \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5244
+ );
5245
+ }
5246
+ return;
5202
5247
  case "UPSERT":
5203
5248
  case "UPSERT_SELECT":
5204
- case "UPDATE":
5205
- case "DELETE":
5249
+ if (containsKlike(stmt)) {
5250
+ throw new KlikeValidationError(
5251
+ "KLIKE / NOT KLIKE \u306F UPSERT / UPSERT SELECT \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5252
+ );
5253
+ }
5254
+ return;
5206
5255
  case "REORDER":
5207
5256
  if (containsKlike(stmt)) {
5208
5257
  throw new KlikeValidationError(
5209
- "KLIKE / NOT KLIKE \u306F\u5168 DML\uFF08UPDATE / DELETE / INSERT / UPSERT / REORDER\uFF09\u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5258
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB REORDER \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
5210
5259
  );
5211
5260
  }
5212
5261
  return;
@@ -5221,6 +5270,22 @@ function validateStatement(stmt) {
5221
5270
  return;
5222
5271
  }
5223
5272
  }
5273
+ function validateKlikeWhereExpressions(where) {
5274
+ walkWithoutNestedSelects(where, (expr) => {
5275
+ if (!isKlike(expr)) return;
5276
+ const right = expr.right;
5277
+ if (right.type !== "STRING" && right.type !== "VARIABLE") {
5278
+ throw new KlikeValidationError(
5279
+ "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"
5280
+ );
5281
+ }
5282
+ if (right.type === "STRING" && right.value.includes("%")) {
5283
+ throw new KlikeValidationError(
5284
+ "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"
5285
+ );
5286
+ }
5287
+ });
5288
+ }
5224
5289
  function validateSelectLike(query) {
5225
5290
  if (query.type === "SELECT") validateSelect(query);
5226
5291
  else if (query.type === "UNION") validateUnion(query);
@@ -5289,6 +5354,25 @@ function containsKlike(node) {
5289
5354
  });
5290
5355
  return found;
5291
5356
  }
5357
+ function containsKlikeOutsideWhereAndNestedSelects(node, allowedWhere) {
5358
+ let found = false;
5359
+ const visit = (value) => {
5360
+ if (found || value === allowedWhere || value === null || typeof value !== "object") return;
5361
+ if (Array.isArray(value)) {
5362
+ for (const item of value) visit(item);
5363
+ return;
5364
+ }
5365
+ const obj = value;
5366
+ if (obj.type === "SELECT") return;
5367
+ if (obj.type === "BINARY" && (obj.op === "KLIKE" || obj.op === "NOT_KLIKE")) {
5368
+ found = true;
5369
+ return;
5370
+ }
5371
+ for (const child of Object.values(obj)) visit(child);
5372
+ };
5373
+ visit(node);
5374
+ return found;
5375
+ }
5292
5376
  function isDescendantOf(root, target) {
5293
5377
  if (root === null) return false;
5294
5378
  if (root === target) return true;
@@ -6598,11 +6682,6 @@ function evaluateCustomChecks(groups, row, resolveFieldType) {
6598
6682
 
6599
6683
  // src/converter/dmlToKintone.ts
6600
6684
  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
6685
  if (!whereHasLike(where)) return;
6607
6686
  throw new DmlConvertError(
6608
6687
  "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 +9447,21 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
9368
9447
  };
9369
9448
  }
9370
9449
 
9450
+ // src/core/optimization/applyParentSelectionPlan.ts
9451
+ function buildApplyParentSelectionPlan(where, metadata) {
9452
+ const plan = buildSingleTableKlikePushdownPlan(where, {
9453
+ allowUnqualifiedFields: true,
9454
+ allowKlike: true,
9455
+ fieldTypes: metadata.fieldTypes,
9456
+ fieldOptions: metadata.fieldOptions
9457
+ });
9458
+ return {
9459
+ prefilter: plan.condition,
9460
+ appliedKlikes: plan.appliedKlikes,
9461
+ unappliedKlikes: plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr))
9462
+ };
9463
+ }
9464
+
9371
9465
  // src/core/optimization/canonicalOrderPlanner.ts
9372
9466
  var REST_OFFSET_MAX = 1e4;
9373
9467
  var REST_LIMIT_MAX = 500;
@@ -9609,10 +9703,12 @@ function flatten(record, alias) {
9609
9703
  }
9610
9704
  return row;
9611
9705
  }
9612
- function applyJoin(leftRows, rightRows, join2) {
9706
+ function applyJoin(leftRows, rightRows, join2, columns = {}) {
9613
9707
  const { on, type: joinType } = join2;
9614
9708
  const leftKey = on.left.tableAlias ? `${on.left.tableAlias}.${on.left.field}` : on.left.field;
9615
9709
  const rightKey = on.right.tableAlias ? `${on.right.tableAlias}.${on.right.field}` : on.right.field;
9710
+ assertJoinKeyAvailable(leftRows, leftKey, columns.leftColumns);
9711
+ assertJoinKeyAvailable(rightRows, rightKey, columns.rightColumns);
9616
9712
  if (joinType === "RIGHT") {
9617
9713
  const leftIndex = /* @__PURE__ */ new Map();
9618
9714
  for (const lRow of leftRows) {
@@ -9622,7 +9718,7 @@ function applyJoin(leftRows, rightRows, join2) {
9622
9718
  else leftIndex.set(k, [lRow]);
9623
9719
  }
9624
9720
  const emptyLeft = {};
9625
- for (const key of Object.keys(leftRows[0] ?? {})) emptyLeft[key] = "";
9721
+ for (const key of columns.leftColumns ?? Object.keys(leftRows[0] ?? {})) emptyLeft[key] = "";
9626
9722
  const result2 = [];
9627
9723
  for (const rRow of rightRows) {
9628
9724
  const k = rRow[rightKey] ?? "";
@@ -9644,7 +9740,7 @@ function applyJoin(leftRows, rightRows, join2) {
9644
9740
  }
9645
9741
  const result = [];
9646
9742
  const emptyRight = {};
9647
- for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
9743
+ for (const key of columns.rightColumns ?? Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
9648
9744
  for (const lRow of leftRows) {
9649
9745
  const k = lRow[leftKey] ?? "";
9650
9746
  const matched = rightIndex.get(k) ?? [];
@@ -9658,6 +9754,12 @@ function applyJoin(leftRows, rightRows, join2) {
9658
9754
  }
9659
9755
  return result;
9660
9756
  }
9757
+ function assertJoinKeyAvailable(rows, key, savedColumns) {
9758
+ const missing = rows.length > 0 ? rows.some((row) => !Object.prototype.hasOwnProperty.call(row, key)) : savedColumns !== void 0 && !savedColumns.includes(key);
9759
+ if (missing) {
9760
+ throw new Error(`ArgumentError: JOIN key ${key} is not available in the materialized table.`);
9761
+ }
9762
+ }
9661
9763
  function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
9662
9764
  if (where === null) return rows;
9663
9765
  return rows.filter((row) => evalWhere(where, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2));
@@ -9956,9 +10058,12 @@ function applyLimit(rows, limit, offset) {
9956
10058
  if (limit === null) return rows.slice(start);
9957
10059
  return rows.slice(start, start + limit);
9958
10060
  }
9959
- function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, resolveFieldSemantics2) {
10061
+ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, resolveFieldSemantics2, hiddenQualifiedAliases) {
9960
10062
  if (columns.length === 1 && columns[0].type === "WILDCARD") {
9961
- const projected2 = rows.map((row) => stripParentShortcutColumns(row));
10063
+ const projected2 = rows.map((row) => stripHiddenQualifiedColumns(
10064
+ stripParentShortcutColumns(row),
10065
+ hiddenQualifiedAliases
10066
+ ));
9962
10067
  const cols = projected2.length > 0 ? Object.keys(projected2[0]) : [...sourceColumns ?? []];
9963
10068
  return { rows: projected2, columns: cols };
9964
10069
  }
@@ -9978,7 +10083,10 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
9978
10083
  case "VARIABLE_COL":
9979
10084
  throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
9980
10085
  case "WILDCARD":
9981
- Object.assign(out, stripParentShortcutColumns(row));
10086
+ Object.assign(out, stripHiddenQualifiedColumns(
10087
+ stripParentShortcutColumns(row),
10088
+ hiddenQualifiedAliases
10089
+ ));
9982
10090
  break;
9983
10091
  case "PARENT_WILDCARD": {
9984
10092
  const parentKeys = Object.keys(row).filter((k) => k.startsWith("_p.")).sort();
@@ -10231,6 +10339,27 @@ function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
10231
10339
  args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
10232
10340
  };
10233
10341
  }
10342
+ function stripHiddenQualifiedColumns(row, hiddenQualifiedAliases) {
10343
+ if (!hiddenQualifiedAliases || hiddenQualifiedAliases.size === 0) return row;
10344
+ const out = {};
10345
+ for (const [key, value] of Object.entries(row)) {
10346
+ if ([...hiddenQualifiedAliases].some((alias) => key.startsWith(`${alias}.`))) continue;
10347
+ out[key] = value;
10348
+ }
10349
+ return out;
10350
+ }
10351
+ function flattenedColumns(columns, alias) {
10352
+ if (columns === void 0) return void 0;
10353
+ return alias ? columns.flatMap((column) => [`${alias}.${column}`, column]) : [...columns];
10354
+ }
10355
+ function mergeKnownColumns(left, right, rows) {
10356
+ if (left === void 0 && right === void 0 && rows.length === 0) return void 0;
10357
+ return [.../* @__PURE__ */ new Set([
10358
+ ...left ?? [],
10359
+ ...right ?? [],
10360
+ ...Object.keys(rows[0] ?? {})
10361
+ ])];
10362
+ }
10234
10363
  function deriveOutputOrderSemantics(columns) {
10235
10364
  const result = /* @__PURE__ */ new Map();
10236
10365
  for (const column of columns) {
@@ -10265,7 +10394,9 @@ function runFullScan(input) {
10265
10394
  havingFieldSemanticsResolver,
10266
10395
  aggregateSortKindResolver,
10267
10396
  appliedKlikes,
10268
- sourceColumns
10397
+ sourceColumns,
10398
+ tableColumns,
10399
+ hiddenQualifiedAliases
10269
10400
  } = input;
10270
10401
  const effectiveOrderSemantics = deriveOutputOrderSemantics(stmt.columns);
10271
10402
  for (const [key, value] of orderSemantics ?? []) effectiveOrderSemantics.set(key, value);
@@ -10273,11 +10404,25 @@ function runFullScan(input) {
10273
10404
  const mainAlias = stmt.from.alias;
10274
10405
  const mainRecords = tables.get(mainAlias) ?? tables.get(null) ?? [];
10275
10406
  rows = mainRecords.map((r) => flatten(r, mainAlias));
10407
+ let knownColumns = mergeKnownColumns(
10408
+ flattenedColumns(tableColumns?.get(mainAlias), mainAlias),
10409
+ void 0,
10410
+ rows
10411
+ );
10276
10412
  for (const join2 of stmt.joins) {
10277
10413
  const rightAlias = join2.table.alias;
10278
10414
  const rightRecords = tables.get(rightAlias) ?? [];
10279
10415
  const rightRows = rightRecords.map((r) => flatten(r, rightAlias));
10280
- rows = applyJoin(rows, rightRows, join2);
10416
+ const rightColumns = mergeKnownColumns(
10417
+ flattenedColumns(tableColumns?.get(rightAlias), rightAlias),
10418
+ void 0,
10419
+ rightRows
10420
+ );
10421
+ rows = applyJoin(rows, rightRows, join2, {
10422
+ leftColumns: knownColumns,
10423
+ rightColumns
10424
+ });
10425
+ knownColumns = mergeKnownColumns(knownColumns, rightColumns, rows);
10281
10426
  }
10282
10427
  rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes, fieldSemanticsResolver);
10283
10428
  if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
@@ -10290,7 +10435,15 @@ function runFullScan(input) {
10290
10435
  }
10291
10436
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds, effectiveOrderSemantics);
10292
10437
  rows = applyLimit(rows, stmt.limit, stmt.offset);
10293
- return project(rows, stmt.columns, scalarCache, fieldTypeResolver, sourceColumns, fieldSemanticsResolver);
10438
+ return project(
10439
+ rows,
10440
+ stmt.columns,
10441
+ scalarCache,
10442
+ fieldTypeResolver,
10443
+ sourceColumns,
10444
+ fieldSemanticsResolver,
10445
+ hiddenQualifiedAliases
10446
+ );
10294
10447
  }
10295
10448
 
10296
10449
  // src/converter/subtableAdapter.ts
@@ -11645,7 +11798,7 @@ function attachSearchAbortWarning(result, collector) {
11645
11798
  }
11646
11799
  async function executeParsedStatement(stmt, client, options, cacheContext) {
11647
11800
  const unresolved = findVariableRef(stmt);
11648
- if (unresolved !== null) {
11801
+ if (unresolved !== null && !isApplyParentKlikeStatement(stmt)) {
11649
11802
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
11650
11803
  }
11651
11804
  assertApplyScope("phase15b", stmt);
@@ -12602,7 +12755,7 @@ async function buildWhereFieldSemanticsResolver(stmt, client, cacheContext, mate
12602
12755
  if (field.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
12603
12756
  return fromPhysical(stmt.from, field.field, false);
12604
12757
  }
12605
- const table = tables.find((candidate) => candidate.alias === field.tableAlias);
12758
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === field.tableAlias);
12606
12759
  if (!table) return void 0;
12607
12760
  if (table.cteName !== null) {
12608
12761
  return materializedTables?.get(table.cteName)?.columnMeta?.get(field.field)?.semantics ?? syntheticSemantics("string");
@@ -13036,8 +13189,11 @@ function collectSelectTypedInFieldRefs(stmt) {
13036
13189
  }
13037
13190
  return refs;
13038
13191
  }
13192
+ function effectiveTableAlias(table) {
13193
+ return table.alias ?? table.cteName;
13194
+ }
13039
13195
  function findTableForAlias(stmt, alias) {
13040
- return [stmt.from, ...stmt.joins.map((join2) => join2.table)].find((table) => table.alias === alias);
13196
+ return [stmt.from, ...stmt.joins.map((join2) => join2.table)].find((table) => effectiveTableAlias(table) === alias);
13041
13197
  }
13042
13198
  function physicalSelectTables(stmt) {
13043
13199
  return [stmt.from, ...stmt.joins.map((join2) => join2.table)].filter((table) => table.cteName === null);
@@ -13189,7 +13345,7 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
13189
13345
  if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
13190
13346
  info = fieldInfosByApp.get(stmt.from.appId)?.get(ref.field);
13191
13347
  } else {
13192
- const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
13348
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === ref.tableAlias);
13193
13349
  if (!table) return void 0;
13194
13350
  if (table.cteName !== null) {
13195
13351
  return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field)?.semantics ?? syntheticSemantics("string");
@@ -13214,7 +13370,7 @@ async function loadAggregateSortKindResolver(stmt, client, cacheContext, materia
13214
13370
  return matches[0];
13215
13371
  }
13216
13372
  if (!info) return void 0;
13217
- const sourceTable = ref.tableAlias !== null ? tables.find((table) => table.alias === ref.tableAlias) : stmt.joins.length === 0 ? stmt.from : void 0;
13373
+ const sourceTable = ref.tableAlias !== null ? tables.find((table) => effectiveTableAlias(table) === ref.tableAlias) : stmt.joins.length === 0 ? stmt.from : void 0;
13218
13374
  return semanticsForInfo(info, sourceTable?.appId ?? stmt.from.appId);
13219
13375
  };
13220
13376
  }
@@ -13344,7 +13500,7 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
13344
13500
  const info2 = physicalInfos.get(stmt.from.appId)?.get(ref.field);
13345
13501
  return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
13346
13502
  }
13347
- const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
13503
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === ref.tableAlias);
13348
13504
  if (!table) return void 0;
13349
13505
  if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
13350
13506
  const info = physicalInfos.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
@@ -13449,7 +13605,7 @@ function buildSelectFieldTypeResolvers(stmt, fieldTypesByApp) {
13449
13605
  if (field.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
13450
13606
  return fieldTypesByApp.get(stmt.from.appId)?.get(field.field);
13451
13607
  }
13452
- const table = tables.find((candidate) => candidate.alias === field.tableAlias);
13608
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === field.tableAlias);
13453
13609
  if (!table || table.cteName !== null) return void 0;
13454
13610
  return subtableSystemFieldType(table, field.field) ?? fieldTypesByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, field.field));
13455
13611
  }
@@ -13674,6 +13830,37 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
13674
13830
  return result;
13675
13831
  }
13676
13832
  async function executeFullScanWithCte(stmt, client, options, cteCache, cacheContext) {
13833
+ const hiddenQualifiedAliases = /* @__PURE__ */ new Set();
13834
+ const withEffectiveAlias = (table) => {
13835
+ if (table.alias !== null || table.cteName === null) return table;
13836
+ const alias = effectiveTableAlias(table);
13837
+ hiddenQualifiedAliases.add(alias);
13838
+ return { ...table, alias };
13839
+ };
13840
+ stmt = {
13841
+ ...stmt,
13842
+ from: withEffectiveAlias(stmt.from),
13843
+ joins: stmt.joins.map((join2) => ({ ...join2, table: withEffectiveAlias(join2.table) }))
13844
+ };
13845
+ const aliases = /* @__PURE__ */ new Set();
13846
+ for (const table of [stmt.from, ...stmt.joins.map((join2) => join2.table)]) {
13847
+ const alias = effectiveTableAlias(table);
13848
+ if (alias === null) continue;
13849
+ if (aliases.has(alias)) {
13850
+ throw new Error(`ArgumentError: effective alias ${alias} is used by multiple tables.`);
13851
+ }
13852
+ aliases.add(alias);
13853
+ }
13854
+ const requireMaterializedTable = (name) => {
13855
+ const table = cteCache.get(name);
13856
+ if (!table) {
13857
+ throw new Error(`ArgumentError: materialized source ${name} is not available.`);
13858
+ }
13859
+ return table;
13860
+ };
13861
+ for (const table of [stmt.from, ...stmt.joins.map((join2) => join2.table)]) {
13862
+ if (table.cteName !== null) requireMaterializedTable(table.cteName);
13863
+ }
13677
13864
  const maxRecords = options.maxRecords ?? 1e4;
13678
13865
  const warnings = /* @__PURE__ */ new Set();
13679
13866
  const parallel = options.fetchParallel ?? 1;
@@ -13720,9 +13907,11 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
13720
13907
  orderByMetaPromise.catch(() => {
13721
13908
  });
13722
13909
  const tables = /* @__PURE__ */ new Map();
13910
+ const tableColumns = /* @__PURE__ */ new Map();
13723
13911
  if (stmt.from.cteName != null) {
13724
- const table = cteCache.get(stmt.from.cteName);
13725
- tables.set(stmt.from.alias, (table?.rows ?? []).map(processRowToKintoneRecord));
13912
+ const table = requireMaterializedTable(stmt.from.cteName);
13913
+ tables.set(stmt.from.alias, table.rows.map(processRowToKintoneRecord));
13914
+ tableColumns.set(stmt.from.alias, table.columns);
13726
13915
  } else {
13727
13916
  const mainRecords = await fetchTableRecordsForFullScan(
13728
13917
  stmt,
@@ -13740,8 +13929,9 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
13740
13929
  }
13741
13930
  const joinFetches = stmt.joins.map(async (join2) => {
13742
13931
  if (join2.table.cteName != null) {
13743
- const table = cteCache.get(join2.table.cteName);
13744
- tables.set(join2.table.alias, (table?.rows ?? []).map(processRowToKintoneRecord));
13932
+ const table = requireMaterializedTable(join2.table.cteName);
13933
+ tables.set(join2.table.alias, table.rows.map(processRowToKintoneRecord));
13934
+ tableColumns.set(join2.table.alias, table.columns);
13745
13935
  } else {
13746
13936
  const pushDownCond = join2.table.alias ? pushdownPlan.joinConditions.get(join2.table.alias) ?? null : null;
13747
13937
  const optimized = await tryFetchJoinRecordsBySourceKeys(
@@ -13772,7 +13962,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
13772
13962
  await Promise.all(joinFetches);
13773
13963
  const scalarCache = await scalarCachePromise;
13774
13964
  const { optionOrders, sortKinds, semantics } = await orderByMetaPromise;
13775
- const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? cteCache.get(stmt.from.cteName)?.columns : void 0;
13965
+ const sourceColumns = stmt.joins.length === 0 && stmt.from.cteName != null ? requireMaterializedTable(stmt.from.cteName).columns : void 0;
13776
13966
  const { rows, columns } = runFullScan({
13777
13967
  tables,
13778
13968
  stmt,
@@ -13786,7 +13976,9 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
13786
13976
  havingFieldSemanticsResolver,
13787
13977
  aggregateSortKindResolver,
13788
13978
  appliedKlikes: pushdownPlan.appliedKlikes,
13789
- sourceColumns
13979
+ sourceColumns,
13980
+ tableColumns,
13981
+ hiddenQualifiedAliases
13790
13982
  });
13791
13983
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
13792
13984
  }
@@ -14088,7 +14280,7 @@ async function buildOrderSemanticsForSelect(stmt, client, cacheContext, material
14088
14280
  const info2 = infosByApp.get(stmt.from.appId)?.get(ref.field);
14089
14281
  return info2 ? materializedMetaFromFieldInfo(info2, stmt.from.appId) : void 0;
14090
14282
  }
14091
- const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
14283
+ const table = tables.find((candidate) => effectiveTableAlias(candidate) === ref.tableAlias);
14092
14284
  if (!table) return void 0;
14093
14285
  if (table.cteName !== null) return materializedTables?.get(table.cteName)?.columnMeta?.get(ref.field);
14094
14286
  const info = infosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
@@ -16141,16 +16333,21 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
16141
16333
  );
16142
16334
  const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
16143
16335
  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
- });
16336
+ let snapshots;
16337
+ if (usesApplyParentResidualSelection(stmt)) {
16338
+ snapshots = await selectApplyParentSnapshots(stmt, client, options, fieldInfos, cacheContext);
16339
+ } else {
16340
+ const fields = collectApplySnapshotFields(stmt, fieldInfos);
16341
+ const baseQuery = updateToGetQuery(stmt).query;
16342
+ const detectionLimit = dmlMaxRows + 1;
16343
+ snapshots = await fetchAll(client.getRecords, stmt.appId, baseQuery, [...fields], {
16344
+ pageSize: Math.min(500, detectionLimit),
16345
+ parallel: options.fetchParallel ?? 1,
16346
+ maxRecords: detectionLimit,
16347
+ stopAfter: detectionLimit,
16348
+ onLimit: "error"
16349
+ });
16350
+ }
16154
16351
  if (!stmt.validateOnly && snapshots.length > dmlMaxRows) {
16155
16352
  throw new Error(`ArgumentError: APPLY parent rows (${snapshots.length}) exceed dmlMaxRows (${dmlMaxRows}).`);
16156
16353
  }
@@ -16182,6 +16379,95 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
16182
16379
  const result = await executePreparedApplyWrite(prepared, client, diagnostic2);
16183
16380
  return { ...result, diagnostic: withApplyDiagnosticProgress(diagnostic2, result) };
16184
16381
  }
16382
+ function usesApplyParentResidualSelection(stmt) {
16383
+ return (stmt.applyBlocks?.length ?? 0) > 0 && !isSinglePositiveRecordIdWhere(stmt.where) && (whereHasLike(stmt.where) || whereHasKlike(stmt.where));
16384
+ }
16385
+ function isApplyParentKlikeStatement(stmt) {
16386
+ const target = stmt.type === "EXPLAIN" ? stmt.query : stmt;
16387
+ return target.type === "UPDATE" && usesApplyParentResidualSelection(target) && whereHasKlike(target.where);
16388
+ }
16389
+ 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";
16390
+ function assertApplyParentKlikesFullyApplied(plan) {
16391
+ if (plan.unappliedKlikes.length > 0) throw new Error(`UnsupportedError: ${APPLY_PARENT_UNAPPLIED_KLIKE_ERROR}`);
16392
+ }
16393
+ async function selectApplyParentSnapshots(stmt, client, options, fieldInfos, cacheContext) {
16394
+ const topLevelInfos = fieldInfos.filter((field) => !field.inSubtable);
16395
+ const infoByCode = new Map(topLevelInfos.map((field) => [field.code, field]));
16396
+ const fieldTypes = new Map(topLevelInfos.map((field) => [field.code, field.fieldType]));
16397
+ const fieldOptions = new Map(topLevelInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
16398
+ const selectionPlan = buildApplyParentSelectionPlan(stmt.where, { fieldTypes, fieldOptions });
16399
+ assertApplyParentKlikesFullyApplied(selectionPlan);
16400
+ const prefilterQuery = selectionPlan.prefilter === null ? "" : whereToKintone(selectionPlan.prefilter);
16401
+ const fields = new Set(collectApplySnapshotFields(stmt, fieldInfos));
16402
+ for (const field of collectApplyParentWhereFields(stmt.where)) fields.add(field);
16403
+ const resolvers = await buildApplyParentFieldResolvers(
16404
+ stmt.appId,
16405
+ stmt.where,
16406
+ infoByCode,
16407
+ client,
16408
+ cacheContext
16409
+ );
16410
+ const candidates = await fetchAll(
16411
+ client.getRecords,
16412
+ stmt.appId,
16413
+ prefilterQuery,
16414
+ [...fields],
16415
+ {
16416
+ maxRecords: options.maxRecords ?? 1e4,
16417
+ parallel: options.fetchParallel ?? 1,
16418
+ onLimit: "error"
16419
+ // prefilter は target の超集合なので stopAfter を設定せず最後まで取得する。
16420
+ }
16421
+ );
16422
+ return candidates.map((snapshot) => ({ snapshot, row: flatten(snapshot, null) })).filter(({ row }) => evalWhere(
16423
+ stmt.where,
16424
+ row,
16425
+ resolvers.fieldTypeResolver,
16426
+ selectionPlan.appliedKlikes,
16427
+ resolvers.fieldSemanticsResolver
16428
+ )).map(({ snapshot }) => snapshot);
16429
+ }
16430
+ function collectApplyParentWhereFields(where) {
16431
+ return collectValidateWhereFields(where);
16432
+ }
16433
+ async function buildApplyParentFieldResolvers(appId, where, infoByCode, client, cacheContext) {
16434
+ const orderedFields = collectOrderedWhereFields(where);
16435
+ const needsStatusOrder = [...orderedFields].some((field) => infoByCode.get(field)?.fieldType === "STATUS");
16436
+ const statusOrder = needsStatusOrder ? await loadProcessStatusOrder(appId, client, cacheContext) : void 0;
16437
+ const fieldTypeResolver = (field) => field.field === "$id" ? "NUMBER" : infoByCode.get(field.field)?.fieldType;
16438
+ const fieldSemanticsResolver = (field) => {
16439
+ if (field.field === "$id") {
16440
+ return withFieldSemanticSource(resolveFieldSemantics({ fieldType: "__ID__" }), appId, "$id");
16441
+ }
16442
+ const info = infoByCode.get(field.field);
16443
+ if (!info) return void 0;
16444
+ const base = info.semantics ?? resolveFieldSemantics(info);
16445
+ const semantics = info.fieldType === "STATUS" && statusOrder ? { ...base, optionOrder: statusOrder } : base;
16446
+ return withFieldSemanticSource(semantics, appId, info.code);
16447
+ };
16448
+ return { fieldTypeResolver, fieldSemanticsResolver };
16449
+ }
16450
+ function collectOrderedWhereFields(where) {
16451
+ const fields = /* @__PURE__ */ new Set();
16452
+ const visit = (node) => {
16453
+ if (Array.isArray(node)) {
16454
+ node.forEach(visit);
16455
+ return;
16456
+ }
16457
+ if (node === null || typeof node !== "object") return;
16458
+ const value = node;
16459
+ if (value["type"] === "SELECT") return;
16460
+ if (value["type"] === "BINARY" && [">", "<", ">=", "<="].includes(String(value["op"]))) {
16461
+ const left = value["left"];
16462
+ if (left?.["type"] === "FIELD" && typeof left["field"] === "string") {
16463
+ fields.add(left["field"]);
16464
+ }
16465
+ }
16466
+ Object.values(value).forEach(visit);
16467
+ };
16468
+ visit(where);
16469
+ return fields;
16470
+ }
16185
16471
  function materializePreparedApplyInsertValidation(stmt, prepared, fieldInfos) {
16186
16472
  const errors = prepared.validations.flatMap((validation) => validation.errors);
16187
16473
  const invalidRows = prepared.validations.reduce(
@@ -17067,6 +17353,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
17067
17353
  return cache;
17068
17354
  }
17069
17355
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
17356
+ var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
17070
17357
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
17071
17358
  const fieldApps = /* @__PURE__ */ new Set();
17072
17359
  const processStatusApps = /* @__PURE__ */ new Set();
@@ -17191,12 +17478,19 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
17191
17478
  const update = node;
17192
17479
  const fields = await getFieldsCached(update.appId, tracedClient, cacheContext);
17193
17480
  resolveApplyPatchMetadata(update, fields);
17481
+ if (usesApplyParentResidualSelection(update)) {
17482
+ const topLevel = fields.filter((field) => !field.inSubtable);
17483
+ const selectionPlan = buildApplyParentSelectionPlan(update.where, {
17484
+ fieldTypes: new Map(topLevel.map((field) => [field.code, field.fieldType])),
17485
+ fieldOptions: new Map(topLevel.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []))
17486
+ });
17487
+ applyParentExplainPlan.set(update, selectionPlan);
17488
+ }
17489
+ }
17490
+ const dml = node;
17491
+ if (dml.type !== "UPDATE" || !usesApplyParentResidualSelection(dml)) {
17492
+ await assertDmlWhereCapability(dml, tracedClient, cacheContext);
17194
17493
  }
17195
- await assertDmlWhereCapability(
17196
- node,
17197
- tracedClient,
17198
- cacheContext
17199
- );
17200
17494
  }
17201
17495
  await Promise.all(Object.values(typed).map(visit));
17202
17496
  };
@@ -17367,7 +17661,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
17367
17661
  analysis.capabilities,
17368
17662
  analysis.orderPlans,
17369
17663
  dmlMaxRows,
17370
- dmlMaxSubtableRows
17664
+ dmlMaxSubtableRows,
17665
+ maxRecords
17371
17666
  ),
17372
17667
  cursorMaxActive
17373
17668
  )
@@ -17390,7 +17685,7 @@ function addCursorConcurrency(lines, cursorMaxActive) {
17390
17685
  }
17391
17686
  return result;
17392
17687
  }
17393
- function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
17688
+ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
17394
17689
  if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
17395
17690
  if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
17396
17691
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
@@ -17403,7 +17698,8 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
17403
17698
  capabilities,
17404
17699
  orderPlans,
17405
17700
  dmlMaxRows,
17406
- dmlMaxSubtableRows
17701
+ dmlMaxSubtableRows,
17702
+ maxRecords
17407
17703
  );
17408
17704
  if (query.type === "DELETE") return buildDeletePlan(query, label);
17409
17705
  if (query.type === "REORDER") return buildReorderPlan(query, label);
@@ -17709,9 +18005,9 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
17709
18005
  lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
17710
18006
  return lines;
17711
18007
  }
17712
- function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
18008
+ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, maxRecords = 1e4) {
17713
18009
  if (stmt.applyBlocks?.length) {
17714
- return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows);
18010
+ return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords);
17715
18011
  }
17716
18012
  const isArith = hasArithAssignment(stmt);
17717
18013
  const isStringFunc = stmt.assignments.some((a) => a.value.type === "STRING_FUNC");
@@ -17729,6 +18025,10 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17729
18025
  } else {
17730
18026
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
17731
18027
  }
18028
+ if (!stmt.subtableCode) {
18029
+ lines.push(" selection: exact native pushdown; JS residual none");
18030
+ lines.push(" search abort: DML fail-closed (SearchAbortedError; mutation 0)");
18031
+ }
17732
18032
  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
18033
  const setTypes = [];
17734
18034
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
@@ -17754,7 +18054,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17754
18054
  }
17755
18055
  return lines;
17756
18056
  }
17757
- function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
18057
+ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords) {
17758
18058
  const diagnostic2 = buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows);
17759
18059
  const branch = diagnostic2.branches[0];
17760
18060
  const blocks = stmt.applyBlocks;
@@ -17768,12 +18068,24 @@ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
17768
18068
  });
17769
18069
  const operationKinds = [...new Set(branch.targets.flatMap((target) => target.operations.map((operation) => operation.kind)))];
17770
18070
  const hasRemove = operationKinds.includes("REMOVE");
18071
+ const selectionPlan = applyParentExplainPlan.get(stmt);
18072
+ const selectionLines = selectionPlan ? [
18073
+ "parent selection: safe prefilter + JS residual evaluation",
18074
+ `kintone prefilter: ${selectionPlan.prefilter === null ? "(none; empty query)" : whereToKintone(selectionPlan.prefilter)}`,
18075
+ "JS residual: original parent WHERE",
18076
+ `applied KLIKE: ${selectionPlan.appliedKlikes.size}`,
18077
+ `unapplied KLIKE: ${selectionPlan.unappliedKlikes.length}${selectionPlan.unappliedKlikes.length > 0 ? " (unsupported: cannot be fully applied to native query)" : ""}`,
18078
+ `candidate limit: maxRecords=${maxRecords}, onLimit=error, stopAfter=none`,
18079
+ `target guard: dmlMaxRows=${dmlMaxRows} after JS residual evaluation`,
18080
+ "search abort: DML fail-closed (B7-P3; all surfaces, no surface gate)"
18081
+ ] : [];
17771
18082
  return [
17772
18083
  ...label ? [label] : [],
17773
18084
  "statement: UPDATE APPLY",
17774
18085
  `target app: APP${stmt.appId}`,
17775
18086
  `parent selector: ${safeWhereToKintone(stmt.where)}`,
17776
- "parent cardinality: single",
18087
+ `parent cardinality: ${isSinglePositiveRecordIdWhere(stmt.where) ? "single" : "multiple"}`,
18088
+ ...selectionLines,
17777
18089
  `apply target: ${branch.targets.map((target) => `${target.field} (${target.targetKind})`).join(" | ")}`,
17778
18090
  `operations: ${operationKinds.join(" | ")}`,
17779
18091
  `selector: ${selectorKinds.join(" | ")}`,
@@ -17799,6 +18111,10 @@ function buildDeletePlan(stmt, label) {
17799
18111
  lines.push(` [DELETE]`);
17800
18112
  lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
17801
18113
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
18114
+ if (!stmt.subtableCode) {
18115
+ lines.push(" selection: exact native pushdown; JS residual none");
18116
+ lines.push(" search abort: DML fail-closed (SearchAbortedError; mutation 0)");
18117
+ }
17802
18118
  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
18119
  return lines;
17804
18120
  }
@@ -18802,6 +19118,12 @@ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
18802
19118
  return manager;
18803
19119
  }
18804
19120
 
19121
+ // src/core/searchAbortWarning.ts
19122
+ var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
19123
+ function isSearchAbortedWarning(value) {
19124
+ return value?.includes(SEARCH_ABORTED_HEADER_VALUE) === true;
19125
+ }
19126
+
18805
19127
  // src/node/kintoneMetadata.ts
18806
19128
  var KINTONE_METADATA_RESOURCES = [
18807
19129
  "app",
@@ -18994,7 +19316,6 @@ var KintoneApiError = class extends Error {
18994
19316
  this.name = "KintoneApiError";
18995
19317
  }
18996
19318
  };
18997
- var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
18998
19319
  function createNodeKintoneConnection(baseUrl, tokenResolver) {
18999
19320
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
19000
19321
  const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
@@ -19052,10 +19373,9 @@ function createNodeKintoneConnection(baseUrl, tokenResolver) {
19052
19373
  if (tokenResolver.debug) {
19053
19374
  tokenResolver.log?.(`[debug] response status=${res.status}`);
19054
19375
  }
19055
- const warning = res.headers.get("X-Cybozu-Warning") ?? "";
19056
19376
  return {
19057
19377
  response: res,
19058
- searchAborted: warning.includes(SEARCH_ABORTED_HEADER_VALUE)
19378
+ searchAborted: isSearchAbortedWarning(res.headers.get("X-Cybozu-Warning"))
19059
19379
  };
19060
19380
  }
19061
19381
  async function requestJsonResponse(path, init, appIdForToken) {