@rex0220/kintone-sql-tools 3.8.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"
@@ -7904,26 +7980,55 @@ var VALIDATION_META_COLUMNS = [
7904
7980
  "$err_row",
7905
7981
  "$err_field",
7906
7982
  "$err_code",
7907
- "$err_message"
7983
+ "$err_message",
7984
+ "$err_value",
7985
+ "$err_subtable",
7986
+ "$err_subrow",
7987
+ "$err_subrow_id"
7908
7988
  ];
7909
- function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision, checkGroups = [], validateMissingCreateFields = true, includePreErrors = true) {
7989
+ function materializeDmlUpdateModeSparseRecords(candidates, targetFields, fieldInfos) {
7990
+ const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
7991
+ for (const candidate of candidates) {
7992
+ if (candidate.mode !== "update") continue;
7993
+ candidate.record ??= {};
7994
+ for (const code of targetFields) {
7995
+ if (!candidate.payload.has(code)) continue;
7996
+ const original = candidate.payload.get(code);
7997
+ const type = infoByCode.get(code).fieldType;
7998
+ const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
7999
+ let normalized = original;
8000
+ try {
8001
+ normalized = normalizeRaw(original, type);
8002
+ } catch {
8003
+ }
8004
+ candidate.record[code] = { value: preserveCodes ? original : normalized };
8005
+ }
8006
+ }
8007
+ }
8008
+ function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision, checkGroups = [], validateMissingCreateFields = true, includePreErrors = true, validationOptions = {}) {
7910
8009
  const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
7911
8010
  const errors = [];
7912
8011
  const invalid = /* @__PURE__ */ new Set();
8012
+ const candidateResults = [];
8013
+ if (validationOptions.validateUpdateBuiltIns === false) {
8014
+ materializeDmlUpdateModeSparseRecords(candidates, targetFields, fieldInfos);
8015
+ }
7913
8016
  let firstEvaluationError;
7914
8017
  for (const candidate of candidates) {
7915
8018
  candidate.record ??= {};
7916
- const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
8019
+ const preErrors = includePreErrors ? [...candidate.preErrors] : [];
8020
+ const builtInErrors = [];
8021
+ const checkErrors = [];
8022
+ const validateBuiltIns = candidate.mode === "create" || validationOptions.validateUpdateBuiltIns !== false;
7917
8023
  for (const code of targetFields) {
7918
8024
  if (!candidate.payload.has(code)) continue;
7919
- const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
7920
- if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
7921
- else {
7922
- const original = candidate.payload.get(code);
7923
- const type = infoByCode.get(code).fieldType;
7924
- const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
7925
- candidate.record[code] = { value: preserveCodes ? original : result.value };
7926
- }
8025
+ const original = candidate.payload.get(code);
8026
+ const type = infoByCode.get(code).fieldType;
8027
+ const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
8028
+ if (!validateBuiltIns) continue;
8029
+ const result = validateAndNormalizeDmlValue(original, infoByCode.get(code), numberPrecision);
8030
+ if (!result.ok) builtInErrors.push({ field: code, code: result.code, message: result.message });
8031
+ else candidate.record[code] = { value: preserveCodes ? original : result.value };
7927
8032
  }
7928
8033
  if (validateMissingCreateFields && candidate.mode === "create") {
7929
8034
  for (const info of fieldInfos) {
@@ -7932,7 +8037,7 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7932
8037
  const emptyDefault = isEmptyDmlValue(info.defaultValue);
7933
8038
  if (!emptyDefault) {
7934
8039
  const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info, numberPrecision);
7935
- if (!defaultResult.ok) rowErrors.push({
8040
+ if (!defaultResult.ok) builtInErrors.push({
7936
8041
  field: info.code,
7937
8042
  code: defaultResult.code,
7938
8043
  message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
@@ -7940,9 +8045,9 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7940
8045
  } else {
7941
8046
  const emptyResult = validateAndNormalizeDmlValue("", info, numberPrecision);
7942
8047
  if (!emptyResult.ok) {
7943
- rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
8048
+ builtInErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
7944
8049
  } else if (info.required) {
7945
- rowErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
8050
+ builtInErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
7946
8051
  }
7947
8052
  }
7948
8053
  }
@@ -7958,14 +8063,13 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7958
8063
  };
7959
8064
  try {
7960
8065
  for (const custom of evaluateCustomChecks(checkGroups, row, resolveType)) {
7961
- rowErrors.push({ field: "", code: "ERR_CHECK", message: custom.message });
8066
+ checkErrors.push({ field: "", code: "ERR_CHECK", message: custom.message });
7962
8067
  }
7963
8068
  } catch (error) {
7964
8069
  firstEvaluationError ??= error;
7965
8070
  }
7966
8071
  }
7967
- if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
7968
- for (const error of rowErrors) {
8072
+ const materializeErrors = (source) => source.map((error) => {
7969
8073
  const row = {};
7970
8074
  for (const field of payloadFields) row[field] = renderValidationValue(candidate.payload.get(field));
7971
8075
  row["$err_statement"] = String(statementNumber);
@@ -7974,11 +8078,25 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7974
8078
  row["$err_field"] = error.field;
7975
8079
  row["$err_code"] = error.code;
7976
8080
  row["$err_message"] = error.message;
7977
- errors.push(row);
7978
- }
8081
+ row["$err_value"] = "";
8082
+ row["$err_subtable"] = "";
8083
+ row["$err_subrow"] = "";
8084
+ row["$err_subrow_id"] = "";
8085
+ return row;
8086
+ });
8087
+ const materialized = {
8088
+ rowNumber: candidate.rowNumber,
8089
+ preErrors: materializeErrors(preErrors),
8090
+ builtInErrors: materializeErrors(builtInErrors),
8091
+ checkErrors: materializeErrors(checkErrors)
8092
+ };
8093
+ candidateResults.push(materialized);
8094
+ const rowErrors = [...materialized.preErrors, ...materialized.builtInErrors, ...materialized.checkErrors];
8095
+ if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
8096
+ errors.push(...rowErrors);
7979
8097
  }
7980
8098
  if (firstEvaluationError !== void 0) throw firstEvaluationError;
7981
- return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
8099
+ return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid, candidateResults };
7982
8100
  }
7983
8101
  function renderValidationValue(value) {
7984
8102
  if (value == null) return "";
@@ -8085,13 +8203,7 @@ var NON_AUDIT_SYSTEM_TYPES = /* @__PURE__ */ new Set([
8085
8203
  "CATEGORY",
8086
8204
  "REFERENCE_TABLE"
8087
8205
  ]);
8088
- var POST_IMAGE_VALIDATION_SUFFIX_COLUMNS = [
8089
- ...VALIDATION_META_COLUMNS,
8090
- "$err_value",
8091
- "$err_subtable",
8092
- "$err_subrow",
8093
- "$err_subrow_id"
8094
- ];
8206
+ var POST_IMAGE_VALIDATION_SUFFIX_COLUMNS = VALIDATION_META_COLUMNS;
8095
8207
  function buildPostImageFieldIndex(fieldInfos, payloadFields = ["$id"]) {
8096
8208
  const metadata = buildValidationFieldMetadataIndex(fieldInfos);
8097
8209
  const topLevel = metadata.topLevel.filter((field) => field.fieldType !== "SUBTABLE" && field.fieldType !== "FILE" && !NON_AUDIT_SYSTEM_TYPES.has(field.fieldType));
@@ -9332,6 +9444,21 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
9332
9444
  };
9333
9445
  }
9334
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
+
9335
9462
  // src/core/optimization/canonicalOrderPlanner.ts
9336
9463
  var REST_OFFSET_MAX = 1e4;
9337
9464
  var REST_LIMIT_MAX = 500;
@@ -10299,6 +10426,71 @@ function toFlatString(value) {
10299
10426
  }
10300
10427
  }
10301
10428
 
10429
+ // src/core/dmlPrevalidation.ts
10430
+ function collectDmlPrevalidationSnapshotFields(fieldIndex) {
10431
+ return [
10432
+ "$id",
10433
+ .../* @__PURE__ */ new Set([
10434
+ ...fieldIndex.topLevel.map((field) => field.code).filter((code) => code !== "$id" && code !== "$revision"),
10435
+ ...fieldIndex.subtables.keys()
10436
+ ])
10437
+ ];
10438
+ }
10439
+ function buildDmlValidationPostImage(snapshot, sparseRecord) {
10440
+ const postImage = deepClone(snapshot);
10441
+ for (const [field, cell] of Object.entries(sparseRecord)) {
10442
+ postImage[field] = deepClone(cell);
10443
+ }
10444
+ return postImage;
10445
+ }
10446
+ function mergeDmlCandidateValidation(input) {
10447
+ const errors = [
10448
+ ...input.preErrors.map((row) => normalizePlainError(row)),
10449
+ ...input.postImageErrors.map((row) => normalizePlainError(row, row["$err_subtable"] !== "")),
10450
+ ...input.checkErrors.map((row) => normalizePlainError(row))
10451
+ ];
10452
+ const invalidRowNumbers = /* @__PURE__ */ new Set();
10453
+ if (errors.length > 0) invalidRowNumbers.add(input.rowNumber);
10454
+ const writeRecord = {};
10455
+ for (const field of new Set(input.setFields)) {
10456
+ if (!Object.prototype.hasOwnProperty.call(input.normalizedPostImage, field)) {
10457
+ throw new Error(`InternalError: normalized post-image is missing SET field: ${field}`);
10458
+ }
10459
+ writeRecord[field] = deepClone(input.normalizedPostImage[field]);
10460
+ }
10461
+ return {
10462
+ errors,
10463
+ invalidRows: invalidRowNumbers.size,
10464
+ invalidRowNumbers,
10465
+ writeRecord
10466
+ };
10467
+ }
10468
+ function normalizePlainError(row, childCell = false) {
10469
+ return {
10470
+ ...row,
10471
+ $err_value: childCell ? row["$err_value"] ?? "" : "",
10472
+ $err_subtable: childCell ? row["$err_subtable"] ?? "" : "",
10473
+ $err_subrow: childCell ? row["$err_subrow"] ?? "" : "",
10474
+ $err_subrow_id: childCell ? row["$err_subrow_id"] ?? "" : ""
10475
+ };
10476
+ }
10477
+ function deepClone(value, seen = /* @__PURE__ */ new Map()) {
10478
+ if (value === null || typeof value !== "object") return value;
10479
+ const object = value;
10480
+ const existing = seen.get(object);
10481
+ if (existing !== void 0) return existing;
10482
+ if (Array.isArray(value)) {
10483
+ const clone2 = [];
10484
+ seen.set(object, clone2);
10485
+ for (const item of value) clone2.push(deepClone(item, seen));
10486
+ return clone2;
10487
+ }
10488
+ const clone = {};
10489
+ seen.set(object, clone);
10490
+ for (const [key, child] of Object.entries(value)) clone[key] = deepClone(child, seen);
10491
+ return clone;
10492
+ }
10493
+
10302
10494
  // src/core/optimization/whereCapability.ts
10303
10495
  var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
10304
10496
  var EQUALITY_IN = ["=", "!=", "in", "not in"];
@@ -11544,7 +11736,7 @@ function attachSearchAbortWarning(result, collector) {
11544
11736
  }
11545
11737
  async function executeParsedStatement(stmt, client, options, cacheContext) {
11546
11738
  const unresolved = findVariableRef(stmt);
11547
- if (unresolved !== null) {
11739
+ if (unresolved !== null && !isApplyParentKlikeStatement(stmt)) {
11548
11740
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
11549
11741
  }
11550
11742
  assertApplyScope("phase15b", stmt);
@@ -14501,15 +14693,72 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
14501
14693
  await assertDmlWhereCapability(stmt, client, cacheContext);
14502
14694
  }
14503
14695
  const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
14504
- const numberPrecision = await loadNumberPrecisionForTargets(
14696
+ let numberPrecision = await loadNumberPrecisionForTargets(
14505
14697
  stmt.appId,
14506
14698
  targetFields,
14507
14699
  fieldInfos,
14508
14700
  client,
14509
14701
  cacheContext
14510
14702
  );
14511
- const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
14512
- const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
14703
+ const fieldIndex = buildPostImageFieldIndex(fieldInfos, payloadFields);
14704
+ const snapshotFields = collectDmlPrevalidationSnapshotFields(fieldIndex);
14705
+ const candidates = (await materializeValidationCandidates(
14706
+ stmt,
14707
+ operation,
14708
+ client,
14709
+ options,
14710
+ cacheContext,
14711
+ tempTables,
14712
+ infoByCode,
14713
+ stmt.type === "UPDATE" ? snapshotFields : void 0
14714
+ )).sort((left, right) => left.rowNumber - right.rowNumber);
14715
+ const updateCandidates = candidates.filter((candidate) => candidate.mode === "update");
14716
+ const productionUpdateLoader = stmt.type === "UPDATE" ? loadMaterializedUpdateSnapshots : stmt.type.startsWith("UPSERT") ? (input) => loadUpsertValidationSnapshots(
14717
+ input,
14718
+ client,
14719
+ options.maxRecords ?? 1e4
14720
+ ) : void 0;
14721
+ const updateSnapshotLoader = options.loadUpdateModeSnapshots ?? productionUpdateLoader;
14722
+ const usePreparedPostImages = updateCandidates.length > 0 && updateSnapshotLoader !== void 0;
14723
+ const preparedPostImages = /* @__PURE__ */ new Map();
14724
+ if (usePreparedPostImages) {
14725
+ materializeDmlUpdateModeSparseRecords(updateCandidates, targetFields, fieldInfos);
14726
+ const snapshots = await updateSnapshotLoader({
14727
+ appId: stmt.appId,
14728
+ candidates: updateCandidates,
14729
+ fields: snapshotFields
14730
+ });
14731
+ const postImages = /* @__PURE__ */ new Map();
14732
+ for (const candidate of updateCandidates) {
14733
+ if (candidate.targetId === void 0) {
14734
+ throw new Error(`InternalError: update-mode validation candidate has no targetId (row=${candidate.rowNumber}).`);
14735
+ }
14736
+ const snapshot = snapshots.get(candidate.targetId);
14737
+ if (!snapshot) {
14738
+ throw new Error(`InternalError: update-mode snapshot is missing for record ${candidate.targetId}.`);
14739
+ }
14740
+ const postImage = buildDmlValidationPostImage(snapshot, candidate.record ?? {});
14741
+ postImages.set(candidate.rowNumber, postImage);
14742
+ }
14743
+ if (numberPrecision === void 0 && [...postImages.values()].some(
14744
+ (record) => postImageNeedsNumberPrecision(record, fieldIndex)
14745
+ )) {
14746
+ numberPrecision = await getNumberPrecisionCached(stmt.appId, client, cacheContext);
14747
+ }
14748
+ for (const candidate of updateCandidates) {
14749
+ const postImage = postImages.get(candidate.rowNumber);
14750
+ if (!postImage) throw new Error(`InternalError: validation post-image is missing for row ${candidate.rowNumber}.`);
14751
+ preparedPostImages.set(candidate.rowNumber, validatePostImage(
14752
+ postImage,
14753
+ fieldIndex,
14754
+ numberPrecision,
14755
+ statementNumber,
14756
+ candidate.rowNumber,
14757
+ operation
14758
+ ));
14759
+ }
14760
+ }
14761
+ const candidateValidation = validateDmlCandidates(
14513
14762
  candidates,
14514
14763
  operation,
14515
14764
  payloadFields,
@@ -14519,8 +14768,41 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
14519
14768
  numberPrecision,
14520
14769
  stmt.checkGroups ?? [],
14521
14770
  validateMissingCreateFields,
14522
- includePreErrors
14771
+ includePreErrors,
14772
+ { validateUpdateBuiltIns: !usePreparedPostImages }
14523
14773
  );
14774
+ let { errors, invalidRows, invalidRowNumbers } = candidateValidation;
14775
+ if (usePreparedPostImages) {
14776
+ const detailsByRow = new Map(candidateValidation.candidateResults.map((detail) => [detail.rowNumber, detail]));
14777
+ errors = [];
14778
+ invalidRowNumbers = /* @__PURE__ */ new Set();
14779
+ for (const candidate of candidates) {
14780
+ const detail = detailsByRow.get(candidate.rowNumber);
14781
+ if (!detail) throw new Error(`InternalError: validation detail is missing for row ${candidate.rowNumber}.`);
14782
+ if (candidate.mode === "create") {
14783
+ const candidateErrors = [...detail.preErrors, ...detail.builtInErrors, ...detail.checkErrors];
14784
+ errors.push(...candidateErrors);
14785
+ if (candidateErrors.length > 0) invalidRowNumbers.add(candidate.rowNumber);
14786
+ continue;
14787
+ }
14788
+ const postImageValidation = preparedPostImages.get(candidate.rowNumber);
14789
+ if (!postImageValidation) {
14790
+ throw new Error(`InternalError: prepared post-image validation is missing for row ${candidate.rowNumber}.`);
14791
+ }
14792
+ const merged = mergeDmlCandidateValidation({
14793
+ rowNumber: candidate.rowNumber,
14794
+ setFields: targetFields,
14795
+ normalizedPostImage: postImageValidation.normalizedRecord,
14796
+ preErrors: detail.preErrors,
14797
+ postImageErrors: postImageValidation.errors,
14798
+ checkErrors: detail.checkErrors
14799
+ });
14800
+ candidate.record = merged.writeRecord;
14801
+ errors.push(...merged.errors);
14802
+ for (const rowNumber of merged.invalidRowNumbers) invalidRowNumbers.add(rowNumber);
14803
+ }
14804
+ invalidRows = invalidRowNumbers.size;
14805
+ }
14524
14806
  const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
14525
14807
  const result = {
14526
14808
  type: "VALIDATION",
@@ -14546,12 +14828,10 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
14546
14828
  const info = infoByCode.get(column);
14547
14829
  if (info) columnMeta.set(column, materializedMetaFromFieldInfo(info, stmt.appId));
14548
14830
  }
14549
- columnMeta.set("$err_statement", syntheticColumnMeta("number"));
14550
- columnMeta.set("$err_operation", syntheticColumnMeta("string"));
14551
- columnMeta.set("$err_row", syntheticColumnMeta("number"));
14552
- columnMeta.set("$err_field", syntheticColumnMeta("string"));
14553
- columnMeta.set("$err_code", syntheticColumnMeta("string"));
14554
- columnMeta.set("$err_message", syntheticColumnMeta("string"));
14831
+ const numericValidationMeta = /* @__PURE__ */ new Set(["$err_statement", "$err_row"]);
14832
+ for (const column of VALIDATION_META_COLUMNS) {
14833
+ columnMeta.set(column, syntheticColumnMeta(numericValidationMeta.has(column) ? "number" : "string"));
14834
+ }
14555
14835
  materializedMetaByValidationResult.set(result, columnMeta);
14556
14836
  return { result, candidates, invalidRowNumbers, columnMeta };
14557
14837
  }
@@ -14626,8 +14906,17 @@ async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTable
14626
14906
  }
14627
14907
  return { type: "UPSERT", insertedCount, updatedCount: updates.length, ...common };
14628
14908
  }
14629
- async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
14630
- if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
14909
+ async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode, updateSnapshotFields) {
14910
+ if (stmt.type === "UPDATE") {
14911
+ return materializeUpdateValidationCandidates(
14912
+ stmt,
14913
+ client,
14914
+ options,
14915
+ cacheContext,
14916
+ tempTables,
14917
+ updateSnapshotFields
14918
+ );
14919
+ }
14631
14920
  let rows;
14632
14921
  let sourceRows;
14633
14922
  let sourcePresence;
@@ -14740,40 +15029,70 @@ function assertCheckComparisonTypes(stmt, types) {
14740
15029
  }
14741
15030
  }
14742
15031
  }
14743
- async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
14744
- if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
15032
+ async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables, snapshotFields) {
15033
+ if (stmt.from) {
15034
+ return materializeUpdateFromValidationCandidates(
15035
+ stmt,
15036
+ stmt.from,
15037
+ client,
15038
+ options,
15039
+ cacheContext,
15040
+ tempTables,
15041
+ snapshotFields
15042
+ );
15043
+ }
14745
15044
  await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
14746
15045
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
14747
15046
  const checkTargetFields = assertUpdateCheckRefs(stmt, fieldTypes);
14748
15047
  assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes, stmt.appId));
14749
15048
  let records;
14750
15049
  let evaluationById = /* @__PURE__ */ new Map();
15050
+ let snapshotsById = /* @__PURE__ */ new Map();
14751
15051
  if (hasRowDependentAssignment(stmt)) {
14752
15052
  const getParams = updateToGetQueryForArith(stmt);
14753
- const fields = [.../* @__PURE__ */ new Set([...getParams.fields, ...checkTargetFields])];
15053
+ const fields = [.../* @__PURE__ */ new Set([...getParams.fields, ...checkTargetFields, ...snapshotFields ?? []])];
14754
15054
  const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, fields, {
14755
15055
  maxRecords: options.maxRecords ?? 1e4,
14756
15056
  parallel: options.fetchParallel ?? 1,
14757
15057
  onLimit: "error"
14758
15058
  });
14759
- evaluationById = new Map(resolved.records.map((record) => [Number(record["$id"]?.value), record]));
15059
+ snapshotsById = indexDmlUpdateSnapshots(resolved.records);
15060
+ evaluationById = snapshotsById;
14760
15061
  records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
14761
15062
  } else {
14762
15063
  const getParams = updateToGetQuery(stmt);
14763
15064
  if (checkTargetFields.length > 0) {
14764
- const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [.../* @__PURE__ */ new Set(["$id", ...checkTargetFields])], {
15065
+ const fields = [.../* @__PURE__ */ new Set(["$id", ...checkTargetFields, ...snapshotFields ?? []])];
15066
+ const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, fields, {
14765
15067
  maxRecords: options.maxRecords ?? 1e4,
14766
15068
  parallel: options.fetchParallel ?? 1,
14767
15069
  onLimit: "error"
14768
15070
  });
14769
- evaluationById = new Map(resolved.records.map((record) => [Number(record["$id"]?.value), record]));
15071
+ snapshotsById = indexDmlUpdateSnapshots(resolved.records);
15072
+ evaluationById = snapshotsById;
14770
15073
  records = updateToPutBatches(stmt, [...evaluationById.keys()], fieldTypes).flatMap((batch) => batch.records);
14771
15074
  } else {
14772
- const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
14773
- maxRecords: options.maxRecords ?? 1e4,
14774
- parallel: options.fetchParallel ?? 1
14775
- });
14776
- records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
15075
+ if (snapshotFields) {
15076
+ const resolved = await fetchRecordsForSharedPlan(
15077
+ client.getRecords,
15078
+ getParams.app,
15079
+ getParams.query,
15080
+ [...snapshotFields],
15081
+ {
15082
+ maxRecords: options.maxRecords ?? 1e4,
15083
+ parallel: options.fetchParallel ?? 1,
15084
+ onLimit: "error"
15085
+ }
15086
+ );
15087
+ snapshotsById = indexDmlUpdateSnapshots(resolved.records);
15088
+ records = updateToPutBatches(stmt, [...snapshotsById.keys()], fieldTypes).flatMap((batch) => batch.records);
15089
+ } else {
15090
+ const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
15091
+ maxRecords: options.maxRecords ?? 1e4,
15092
+ parallel: options.fetchParallel ?? 1
15093
+ });
15094
+ records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
15095
+ }
14777
15096
  }
14778
15097
  }
14779
15098
  return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
@@ -14784,10 +15103,86 @@ async function materializeUpdateValidationCandidates(stmt, client, options, cach
14784
15103
  preErrors: [],
14785
15104
  record: entry.record,
14786
15105
  targetId: entry.id,
15106
+ validationSnapshot: snapshotsById.get(entry.id),
14787
15107
  evaluationRow: updateEvaluationRow(evaluationById.get(entry.id), stmt.appId),
14788
15108
  evaluationFieldTypes: updateEvaluationTypes(fieldTypes, stmt.appId)
14789
15109
  }));
14790
15110
  }
15111
+ function indexDmlUpdateSnapshots(records) {
15112
+ const snapshots = /* @__PURE__ */ new Map();
15113
+ for (const record of records) {
15114
+ const rawId = record["$id"]?.value;
15115
+ const id = Number(rawId);
15116
+ if (!Number.isSafeInteger(id) || id <= 0) {
15117
+ throw new Error(`InternalError: invalid $id in UPDATE validation snapshot: ${String(rawId)}.`);
15118
+ }
15119
+ if (snapshots.has(id)) {
15120
+ throw new Error(`InternalError: duplicate $id in UPDATE validation snapshot: ${id}.`);
15121
+ }
15122
+ snapshots.set(id, record);
15123
+ }
15124
+ return snapshots;
15125
+ }
15126
+ async function loadMaterializedUpdateSnapshots(input) {
15127
+ const snapshots = /* @__PURE__ */ new Map();
15128
+ for (const candidate of input.candidates) {
15129
+ if (candidate.targetId === void 0 || !candidate.validationSnapshot) {
15130
+ throw new Error(
15131
+ `InternalError: update-mode snapshot is missing for record ${String(candidate.targetId)}.`
15132
+ );
15133
+ }
15134
+ if (snapshots.has(candidate.targetId)) {
15135
+ throw new Error(`InternalError: duplicate UPDATE validation candidate target: ${candidate.targetId}.`);
15136
+ }
15137
+ snapshots.set(candidate.targetId, candidate.validationSnapshot);
15138
+ }
15139
+ return snapshots;
15140
+ }
15141
+ async function loadUpsertValidationSnapshots(input, client, maxRecords) {
15142
+ const updateIds = [...new Set(input.candidates.map((candidate) => {
15143
+ const id = candidate.targetId;
15144
+ if (!Number.isSafeInteger(id) || id === void 0 || id <= 0) {
15145
+ throw new Error(
15146
+ `InternalError: invalid targetId in UPSERT validation candidate: ${String(id)}.`
15147
+ );
15148
+ }
15149
+ return id;
15150
+ }))];
15151
+ if (updateIds.length > maxRecords) {
15152
+ throw new FetchAllLimitError(
15153
+ `\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650\uFF08${maxRecords} \u4EF6\uFF09\u3092\u8D85\u3048\u307E\u3057\u305F\u3002WHERE \u53E5\u3067\u7D5E\u308A\u8FBC\u3080\u304B\u3001maxRecords \u3092\u5F15\u304D\u4E0A\u3052\u3066\u304F\u3060\u3055\u3044\u3002`
15154
+ );
15155
+ }
15156
+ const requestedIds = new Set(updateIds);
15157
+ const snapshots = /* @__PURE__ */ new Map();
15158
+ for (const ids of splitChunks(updateIds, 100)) {
15159
+ const response = await client.getRecords({
15160
+ app: input.appId,
15161
+ query: `$id in (${ids.join(",")}) limit 500`,
15162
+ fields: [...new Set(input.fields)]
15163
+ });
15164
+ for (const snapshot of response.records) {
15165
+ const rawId = snapshot["$id"]?.value;
15166
+ const id = Number(rawId);
15167
+ if (!Number.isSafeInteger(id) || id <= 0) {
15168
+ throw new Error(`InternalError: invalid $id in UPSERT validation snapshot: ${String(rawId)}.`);
15169
+ }
15170
+ if (!requestedIds.has(id)) {
15171
+ throw new Error(`InternalError: unexpected $id in UPSERT validation snapshot: ${id}.`);
15172
+ }
15173
+ if (snapshots.has(id)) {
15174
+ throw new Error(`InternalError: duplicate $id in UPSERT validation snapshot: ${id}.`);
15175
+ }
15176
+ snapshots.set(id, snapshot);
15177
+ }
15178
+ }
15179
+ for (const id of updateIds) {
15180
+ if (!snapshots.has(id)) {
15181
+ throw new Error(`InternalError: update-mode snapshot is missing for record ${id}.`);
15182
+ }
15183
+ }
15184
+ return snapshots;
15185
+ }
14791
15186
  function assertUpdateCheckRefs(stmt, targetTypes) {
14792
15187
  if (stmt.from) return [];
14793
15188
  const fields = /* @__PURE__ */ new Set();
@@ -14818,13 +15213,22 @@ function updateEvaluationTypes(types, appId) {
14818
15213
  [`APP${appId}.$id`, "RECORD_NUMBER"]
14819
15214
  ]);
14820
15215
  }
14821
- async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
15216
+ async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables, snapshotFields) {
14822
15217
  const scope = await resolveUpdateFromCheckScope(stmt, from, client, cacheContext, tempTables);
14823
15218
  assertCheckComparisonTypes(stmt, scope.evaluationTypes);
14824
- const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
15219
+ const matched = await resolveUpdateFromMatchedRecords(
15220
+ stmt,
15221
+ from,
15222
+ client,
15223
+ options,
15224
+ cacheContext,
15225
+ tempTables,
15226
+ snapshotFields
15227
+ );
14825
15228
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
14826
15229
  const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
14827
15230
  const matchedById = new Map(matched.map((pair) => [Number(pair.target["$id"]?.value), pair]));
15231
+ const snapshotsById = snapshotFields ? indexDmlUpdateSnapshots(matched.map((pair) => pair.target)) : /* @__PURE__ */ new Map();
14828
15232
  return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
14829
15233
  rowNumber: index + 1,
14830
15234
  operation: "UPDATE",
@@ -14833,6 +15237,7 @@ async function materializeUpdateFromValidationCandidates(stmt, from, client, opt
14833
15237
  preErrors: [],
14834
15238
  record: entry.record,
14835
15239
  targetId: entry.id,
15240
+ validationSnapshot: snapshotsById.get(entry.id),
14836
15241
  evaluationRow: updateFromEvaluationRow(matchedById.get(entry.id), stmt.appId, from.alias),
14837
15242
  evaluationFieldTypes: scope.evaluationTypes
14838
15243
  }));
@@ -14846,7 +15251,7 @@ var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
14846
15251
  "GROUP_SELECT",
14847
15252
  "FILE"
14848
15253
  ]);
14849
- async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
15254
+ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables, snapshotFields) {
14850
15255
  const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
14851
15256
  const checkScope = await resolveUpdateFromCheckScope(stmt, from, client, cacheContext, tempTables);
14852
15257
  const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : "").concat(checkScope.sourceFields))];
@@ -14875,7 +15280,11 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
14875
15280
  }
14876
15281
  if (sourceByKey.size === 0) return [];
14877
15282
  const maxRecords = options.maxRecords ?? 1e4;
14878
- const targetFields = [.../* @__PURE__ */ new Set([...collectUpdateFromTargetFields(stmt), ...checkScope.targetFields])];
15283
+ const targetFields = [.../* @__PURE__ */ new Set([
15284
+ ...collectUpdateFromTargetFields(stmt),
15285
+ ...checkScope.targetFields,
15286
+ ...snapshotFields ?? []
15287
+ ])];
14879
15288
  const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter, checkGroups: void 0 }).query;
14880
15289
  const targetRecords = [];
14881
15290
  const seenTargetIds = /* @__PURE__ */ new Set();
@@ -15823,16 +16232,21 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
15823
16232
  );
15824
16233
  const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
15825
16234
  const metadata = resolveApplyPatchMetadata(stmt, fieldInfos);
15826
- const fields = collectApplySnapshotFields(stmt, fieldInfos);
15827
- const baseQuery = updateToGetQuery(stmt).query;
15828
- const detectionLimit = dmlMaxRows + 1;
15829
- const snapshots = await fetchAll(client.getRecords, stmt.appId, baseQuery, [...fields], {
15830
- pageSize: Math.min(500, detectionLimit),
15831
- parallel: options.fetchParallel ?? 1,
15832
- maxRecords: detectionLimit,
15833
- stopAfter: detectionLimit,
15834
- onLimit: "error"
15835
- });
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
+ }
15836
16250
  if (!stmt.validateOnly && snapshots.length > dmlMaxRows) {
15837
16251
  throw new Error(`ArgumentError: APPLY parent rows (${snapshots.length}) exceed dmlMaxRows (${dmlMaxRows}).`);
15838
16252
  }
@@ -15864,6 +16278,95 @@ async function executeMultipleParentApplyPreflight(stmt, client, options, cacheC
15864
16278
  const result = await executePreparedApplyWrite(prepared, client, diagnostic2);
15865
16279
  return { ...result, diagnostic: withApplyDiagnosticProgress(diagnostic2, result) };
15866
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
+ }
15867
16370
  function materializePreparedApplyInsertValidation(stmt, prepared, fieldInfos) {
15868
16371
  const errors = prepared.validations.flatMap((validation) => validation.errors);
15869
16372
  const invalidRows = prepared.validations.reduce(
@@ -16749,6 +17252,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
16749
17252
  return cache;
16750
17253
  }
16751
17254
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
17255
+ var applyParentExplainPlan = /* @__PURE__ */ new WeakMap();
16752
17256
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
16753
17257
  const fieldApps = /* @__PURE__ */ new Set();
16754
17258
  const processStatusApps = /* @__PURE__ */ new Set();
@@ -16873,12 +17377,19 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
16873
17377
  const update = node;
16874
17378
  const fields = await getFieldsCached(update.appId, tracedClient, cacheContext);
16875
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);
16876
17392
  }
16877
- await assertDmlWhereCapability(
16878
- node,
16879
- tracedClient,
16880
- cacheContext
16881
- );
16882
17393
  }
16883
17394
  await Promise.all(Object.values(typed).map(visit));
16884
17395
  };
@@ -17049,7 +17560,8 @@ async function executeExplain(stmt, client, cacheContext, maxRecords, cursorMaxA
17049
17560
  analysis.capabilities,
17050
17561
  analysis.orderPlans,
17051
17562
  dmlMaxRows,
17052
- dmlMaxSubtableRows
17563
+ dmlMaxSubtableRows,
17564
+ maxRecords
17053
17565
  ),
17054
17566
  cursorMaxActive
17055
17567
  )
@@ -17072,7 +17584,7 @@ function addCursorConcurrency(lines, cursorMaxActive) {
17072
17584
  }
17073
17585
  return result;
17074
17586
  }
17075
- 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) {
17076
17588
  if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
17077
17589
  if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
17078
17590
  if (query.type === "INSERT") return buildInsertPlan(query, label, dmlMaxRows, dmlMaxSubtableRows);
@@ -17085,7 +17597,8 @@ function buildExplainPlan(query, label, capabilities, orderPlans, dmlMaxRows = 1
17085
17597
  capabilities,
17086
17598
  orderPlans,
17087
17599
  dmlMaxRows,
17088
- dmlMaxSubtableRows
17600
+ dmlMaxSubtableRows,
17601
+ maxRecords
17089
17602
  );
17090
17603
  if (query.type === "DELETE") return buildDeletePlan(query, label);
17091
17604
  if (query.type === "REORDER") return buildReorderPlan(query, label);
@@ -17391,9 +17904,9 @@ function buildInsertSelectPlan(stmt, label, capabilities, orderPlans) {
17391
17904
  lines.push(...buildSelectPlan(stmt.select, "[source SELECT]", capabilities, orderPlans));
17392
17905
  return lines;
17393
17906
  }
17394
- 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) {
17395
17908
  if (stmt.applyBlocks?.length) {
17396
- return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows);
17909
+ return buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords);
17397
17910
  }
17398
17911
  const isArith = hasArithAssignment(stmt);
17399
17912
  const isStringFunc = stmt.assignments.some((a) => a.value.type === "STRING_FUNC");
@@ -17411,6 +17924,10 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17411
17924
  } else {
17412
17925
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
17413
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
+ }
17414
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`);
17415
17932
  const setTypes = [];
17416
17933
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
@@ -17436,7 +17953,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans, dmlMaxRows = 100
17436
17953
  }
17437
17954
  return lines;
17438
17955
  }
17439
- function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
17956
+ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows, maxRecords) {
17440
17957
  const diagnostic2 = buildStaticApplyDiagnostic(stmt, dmlMaxRows, dmlMaxSubtableRows);
17441
17958
  const branch = diagnostic2.branches[0];
17442
17959
  const blocks = stmt.applyBlocks;
@@ -17450,12 +17967,24 @@ function buildUpdateApplyPlan(stmt, label, dmlMaxRows, dmlMaxSubtableRows) {
17450
17967
  });
17451
17968
  const operationKinds = [...new Set(branch.targets.flatMap((target) => target.operations.map((operation) => operation.kind)))];
17452
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
+ ] : [];
17453
17981
  return [
17454
17982
  ...label ? [label] : [],
17455
17983
  "statement: UPDATE APPLY",
17456
17984
  `target app: APP${stmt.appId}`,
17457
17985
  `parent selector: ${safeWhereToKintone(stmt.where)}`,
17458
- "parent cardinality: single",
17986
+ `parent cardinality: ${isSinglePositiveRecordIdWhere(stmt.where) ? "single" : "multiple"}`,
17987
+ ...selectionLines,
17459
17988
  `apply target: ${branch.targets.map((target) => `${target.field} (${target.targetKind})`).join(" | ")}`,
17460
17989
  `operations: ${operationKinds.join(" | ")}`,
17461
17990
  `selector: ${selectorKinds.join(" | ")}`,
@@ -17481,6 +18010,10 @@ function buildDeletePlan(stmt, label) {
17481
18010
  lines.push(` [DELETE]`);
17482
18011
  lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
17483
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
+ }
17484
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`);
17485
18018
  return lines;
17486
18019
  }
@@ -18484,6 +19017,195 @@ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
18484
19017
  return manager;
18485
19018
  }
18486
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
+
19026
+ // src/node/kintoneMetadata.ts
19027
+ var KINTONE_METADATA_RESOURCES = [
19028
+ "app",
19029
+ "fields",
19030
+ "layout",
19031
+ "settings",
19032
+ "status",
19033
+ "views",
19034
+ "reports",
19035
+ "customize"
19036
+ ];
19037
+ var KINTONE_METADATA_LANGS = ["default", "user", "ja", "en", "zh"];
19038
+ var KINTONE_METADATA_MAX_RESPONSE_BYTES = 2097152;
19039
+ var ResponseTooLargeError = class extends Error {
19040
+ constructor(maxBytes = KINTONE_METADATA_MAX_RESPONSE_BYTES, responseBytes) {
19041
+ super(`ResponseTooLargeError: kintone metadata response exceeds ${maxBytes} bytes.`);
19042
+ this.maxBytes = maxBytes;
19043
+ this.responseBytes = responseBytes;
19044
+ this.name = "ResponseTooLargeError";
19045
+ }
19046
+ };
19047
+ var InvalidJsonResponseError = class extends Error {
19048
+ constructor(cause) {
19049
+ super("InvalidJsonResponseError: kintone metadata response is not valid JSON.");
19050
+ this.name = "InvalidJsonResponseError";
19051
+ this.cause = cause;
19052
+ }
19053
+ };
19054
+ var ArgumentError = class extends Error {
19055
+ constructor(message) {
19056
+ super(`ArgumentError: ${message}`);
19057
+ this.name = "ArgumentError";
19058
+ }
19059
+ };
19060
+ var CapabilityError = class extends Error {
19061
+ constructor(message) {
19062
+ super(`CapabilityError: ${message}`);
19063
+ this.name = "CapabilityError";
19064
+ }
19065
+ };
19066
+ var RESOURCE_DEFINITIONS = {
19067
+ app: {
19068
+ productionPath: "/app.json",
19069
+ appParameter: "id",
19070
+ allowsLang: false,
19071
+ authCapability: "token|userpass"
19072
+ },
19073
+ fields: {
19074
+ productionPath: "/app/form/fields.json",
19075
+ previewPath: "/preview/app/form/fields.json",
19076
+ appParameter: "app",
19077
+ allowsLang: true,
19078
+ authCapability: "token|userpass"
19079
+ },
19080
+ layout: {
19081
+ productionPath: "/app/form/layout.json",
19082
+ previewPath: "/preview/app/form/layout.json",
19083
+ appParameter: "app",
19084
+ allowsLang: false,
19085
+ authCapability: "token|userpass"
19086
+ },
19087
+ settings: {
19088
+ productionPath: "/app/settings.json",
19089
+ previewPath: "/preview/app/settings.json",
19090
+ appParameter: "app",
19091
+ allowsLang: true,
19092
+ authCapability: "token|userpass"
19093
+ },
19094
+ status: {
19095
+ productionPath: "/app/status.json",
19096
+ previewPath: "/preview/app/status.json",
19097
+ appParameter: "app",
19098
+ allowsLang: true,
19099
+ authCapability: "token|userpass"
19100
+ },
19101
+ views: {
19102
+ productionPath: "/app/views.json",
19103
+ previewPath: "/preview/app/views.json",
19104
+ appParameter: "app",
19105
+ allowsLang: true,
19106
+ authCapability: "token|userpass"
19107
+ },
19108
+ reports: {
19109
+ productionPath: "/app/reports.json",
19110
+ previewPath: "/preview/app/reports.json",
19111
+ appParameter: "app",
19112
+ allowsLang: true,
19113
+ authCapability: "token|userpass"
19114
+ },
19115
+ customize: {
19116
+ productionPath: "/app/customize.json",
19117
+ previewPath: "/preview/app/customize.json",
19118
+ appParameter: "app",
19119
+ allowsLang: false,
19120
+ authCapability: "userpass-only"
19121
+ }
19122
+ };
19123
+ var RESOURCE_SET = new Set(KINTONE_METADATA_RESOURCES);
19124
+ var LANG_SET = new Set(KINTONE_METADATA_LANGS);
19125
+ var API_BASE_PATH_PATTERN = /^\/k\/(?:guest\/([1-9]\d*)\/)?v1$/;
19126
+ function assertResolvedAppId(resolvedAppId) {
19127
+ if (!Number.isSafeInteger(resolvedAppId) || resolvedAppId <= 0) {
19128
+ throw new ArgumentError("resolved app ID must be a positive safe integer.");
19129
+ }
19130
+ }
19131
+ function assertApiBasePath(apiBasePath) {
19132
+ const match = API_BASE_PATH_PATTERN.exec(apiBasePath);
19133
+ if (!match) {
19134
+ throw new ArgumentError("apiBasePath must be /k/v1 or /k/guest/<positive safe integer>/v1.");
19135
+ }
19136
+ if (match[1] !== void 0) {
19137
+ const guestSpaceId = Number(match[1]);
19138
+ if (!Number.isSafeInteger(guestSpaceId) || guestSpaceId <= 0) {
19139
+ throw new ArgumentError("apiBasePath guest space ID must be a positive safe integer.");
19140
+ }
19141
+ }
19142
+ }
19143
+ function validateRequest(request) {
19144
+ if (typeof request !== "object" || request === null || Array.isArray(request)) {
19145
+ throw new ArgumentError("metadata request must be an object.");
19146
+ }
19147
+ const candidate = request;
19148
+ const resource = candidate.resource;
19149
+ if (typeof resource !== "string" || !RESOURCE_SET.has(resource)) {
19150
+ throw new ArgumentError(`unsupported metadata resource: ${String(resource)}.`);
19151
+ }
19152
+ const typedResource = resource;
19153
+ const definition = RESOURCE_DEFINITIONS[typedResource];
19154
+ const allowedKeys = definition.allowsLang ? /* @__PURE__ */ new Set(["resource", "preview", "lang"]) : /* @__PURE__ */ new Set(["resource", "preview"]);
19155
+ const unexpectedKey = Object.keys(candidate).find((key) => !allowedKeys.has(key));
19156
+ if (unexpectedKey !== void 0) {
19157
+ throw new ArgumentError(`parameter "${unexpectedKey}" is not allowed for resource "${resource}".`);
19158
+ }
19159
+ if (candidate.preview !== void 0 && typeof candidate.preview !== "boolean") {
19160
+ throw new ArgumentError("preview must be a boolean when specified.");
19161
+ }
19162
+ const preview = candidate.preview === true;
19163
+ if (preview && definition.previewPath === void 0) {
19164
+ throw new ArgumentError(`preview is not supported for resource "${resource}".`);
19165
+ }
19166
+ const rawLang = candidate.lang;
19167
+ if (rawLang !== void 0) {
19168
+ if (!definition.allowsLang) {
19169
+ throw new ArgumentError(`lang is not allowed for resource "${resource}".`);
19170
+ }
19171
+ if (typeof rawLang !== "string" || !LANG_SET.has(rawLang)) {
19172
+ throw new ArgumentError(`unsupported lang: ${String(rawLang)}.`);
19173
+ }
19174
+ }
19175
+ return {
19176
+ resource: typedResource,
19177
+ preview,
19178
+ lang: rawLang,
19179
+ definition
19180
+ };
19181
+ }
19182
+ function mapKintoneMetadataRequest(request, resolvedAppId, apiBasePath, authType) {
19183
+ assertResolvedAppId(resolvedAppId);
19184
+ assertApiBasePath(apiBasePath);
19185
+ if (authType !== "token" && authType !== "userpass") {
19186
+ throw new ArgumentError(`unsupported auth type: ${String(authType)}.`);
19187
+ }
19188
+ const { resource, preview, lang, definition } = validateRequest(request);
19189
+ if (definition.authCapability === "userpass-only" && authType === "token") {
19190
+ throw new CapabilityError(`resource "${resource}" requires userpass authentication.`);
19191
+ }
19192
+ const params = new URLSearchParams();
19193
+ params.set(definition.appParameter, String(resolvedAppId));
19194
+ if (lang !== void 0) params.set("lang", lang);
19195
+ const pathFragment = preview ? definition.previewPath : definition.productionPath;
19196
+ if (pathFragment === void 0) {
19197
+ throw new ArgumentError(`preview is not supported for resource "${resource}".`);
19198
+ }
19199
+ return {
19200
+ method: "GET",
19201
+ path: `${apiBasePath}${pathFragment}`,
19202
+ params,
19203
+ environment: preview ? "preview" : "production",
19204
+ resource,
19205
+ authCapability: definition.authCapability
19206
+ };
19207
+ }
19208
+
18487
19209
  // src/cli/nodeKintoneClient.ts
18488
19210
  var KintoneApiError = class extends Error {
18489
19211
  constructor(status, code, bodyText) {
@@ -18493,11 +19215,10 @@ var KintoneApiError = class extends Error {
18493
19215
  this.name = "KintoneApiError";
18494
19216
  }
18495
19217
  };
18496
- var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
18497
- function createNodeKintoneClient(baseUrl, tokenResolver) {
19218
+ function createNodeKintoneConnection(baseUrl, tokenResolver) {
18498
19219
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
18499
19220
  const apiBasePath = tokenResolver.guestSpaceId && tokenResolver.guestSpaceId > 0 ? `/k/guest/${tokenResolver.guestSpaceId}/v1` : "/k/v1";
18500
- async function requestJsonResponse(path, init, appIdForToken) {
19221
+ async function requestResponse(path, init, appIdForToken) {
18501
19222
  const headers = new Headers(init.headers ?? {});
18502
19223
  if (tokenResolver.auth.type === "token") {
18503
19224
  headers.set("X-Cybozu-API-Token", tokenResolver.auth.resolveToken(appIdForToken));
@@ -18551,15 +19272,69 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
18551
19272
  if (tokenResolver.debug) {
18552
19273
  tokenResolver.log?.(`[debug] response status=${res.status}`);
18553
19274
  }
18554
- const warning = res.headers.get("X-Cybozu-Warning") ?? "";
18555
19275
  return {
18556
- body: await res.json(),
18557
- searchAborted: warning.includes(SEARCH_ABORTED_HEADER_VALUE)
19276
+ response: res,
19277
+ searchAborted: isSearchAbortedWarning(res.headers.get("X-Cybozu-Warning"))
19278
+ };
19279
+ }
19280
+ async function requestJsonResponse(path, init, appIdForToken) {
19281
+ const { response, searchAborted } = await requestResponse(path, init, appIdForToken);
19282
+ return {
19283
+ body: await response.json(),
19284
+ searchAborted
18558
19285
  };
18559
19286
  }
18560
19287
  async function requestJson(path, init, appIdForToken) {
18561
19288
  return (await requestJsonResponse(path, init, appIdForToken)).body;
18562
19289
  }
19290
+ async function requestCappedMetadataJson(path, init, appIdForToken) {
19291
+ const { response } = await requestResponse(path, init, appIdForToken);
19292
+ const contentLength = response.headers.get("Content-Length");
19293
+ if (contentLength !== null && /^\d+$/.test(contentLength.trim())) {
19294
+ const declaredBytes = Number(contentLength);
19295
+ if (declaredBytes > KINTONE_METADATA_MAX_RESPONSE_BYTES) {
19296
+ try {
19297
+ await response.body?.cancel();
19298
+ } catch {
19299
+ }
19300
+ throw new ResponseTooLargeError(KINTONE_METADATA_MAX_RESPONSE_BYTES, declaredBytes);
19301
+ }
19302
+ }
19303
+ const chunks = [];
19304
+ let responseBytes = 0;
19305
+ if (response.body !== null) {
19306
+ const reader = response.body.getReader();
19307
+ while (true) {
19308
+ const { done, value } = await reader.read();
19309
+ if (done) break;
19310
+ responseBytes += value.byteLength;
19311
+ if (responseBytes > KINTONE_METADATA_MAX_RESPONSE_BYTES) {
19312
+ try {
19313
+ await reader.cancel();
19314
+ } catch {
19315
+ }
19316
+ throw new ResponseTooLargeError(KINTONE_METADATA_MAX_RESPONSE_BYTES, responseBytes);
19317
+ }
19318
+ chunks.push(value);
19319
+ }
19320
+ }
19321
+ const bytes = new Uint8Array(responseBytes);
19322
+ let offset = 0;
19323
+ for (const chunk3 of chunks) {
19324
+ bytes.set(chunk3, offset);
19325
+ offset += chunk3.byteLength;
19326
+ }
19327
+ let data;
19328
+ try {
19329
+ data = JSON.parse(new TextDecoder("utf-8").decode(bytes));
19330
+ } catch (cause) {
19331
+ throw new InvalidJsonResponseError(cause);
19332
+ }
19333
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
19334
+ throw new InvalidJsonResponseError();
19335
+ }
19336
+ return { data, responseBytes };
19337
+ }
18563
19338
  function shouldRetryWithRecordNumberOrder(path, bodyText) {
18564
19339
  if (!path.includes("/v1/records.json?")) return false;
18565
19340
  if (!bodyText.includes('"code":"CB_IL02"')) return false;
@@ -18579,7 +19354,7 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
18579
19354
  const nextQuery = encodeURIComponent(rewritten);
18580
19355
  return `${base}query=${nextQuery}${tail.length > 0 ? `&${tail.join("&")}` : ""}`;
18581
19356
  }
18582
- return {
19357
+ const client = {
18583
19358
  async getRecords(params) {
18584
19359
  const queryPart = `query=${encodeURIComponent(params.query)}`;
18585
19360
  const appPart = `app=${encodeURIComponent(String(params.app))}`;
@@ -18751,6 +19526,34 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
18751
19526
  };
18752
19527
  }
18753
19528
  };
19529
+ const metadataReader = {
19530
+ async getMetadata(request, resolvedAppId) {
19531
+ const plan = mapKintoneMetadataRequest(
19532
+ request,
19533
+ resolvedAppId,
19534
+ apiBasePath,
19535
+ tokenResolver.auth.type
19536
+ );
19537
+ const query = plan.params.toString();
19538
+ const { data, responseBytes } = await requestCappedMetadataJson(
19539
+ `${plan.path}${query.length > 0 ? `?${query}` : ""}`,
19540
+ { method: "GET" },
19541
+ resolvedAppId
19542
+ );
19543
+ return {
19544
+ resource: plan.resource,
19545
+ environment: plan.environment,
19546
+ path: plan.path,
19547
+ params: Object.fromEntries(plan.params.entries()),
19548
+ responseBytes,
19549
+ data
19550
+ };
19551
+ }
19552
+ };
19553
+ return { client, metadataReader };
19554
+ }
19555
+ function createNodeKintoneClient(baseUrl, tokenResolver) {
19556
+ return createNodeKintoneConnection(baseUrl, tokenResolver).client;
18754
19557
  }
18755
19558
 
18756
19559
  // src/node/appProfiles.ts