@rex0220/kintone-sql-tools 3.61.0 → 3.62.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
@@ -7024,357 +7024,1066 @@ function stripCteAliasFromFieldValue(value, alias) {
7024
7024
  return value;
7025
7025
  }
7026
7026
 
7027
- // src/core/optimization/wherePredicatePushdown.ts
7028
- function extractSafePushdownLeaves(where, options = {}) {
7029
- return extractAndLeaves(where, (expr) => isSafeComparison(expr, options));
7030
- }
7031
- function extractTypedPushdownCandidates(where, options = {}) {
7032
- return extractAndLeaves(
7033
- where,
7034
- (expr) => isNumericCandidate(expr, options) || isSelectionInCandidate(expr, options)
7035
- );
7036
- }
7037
- function extractAndLeaves(where, accept) {
7038
- switch (where.type) {
7039
- case "BINARY":
7040
- return accept(where) ? where : null;
7041
- case "LOGICAL":
7042
- if (where.op !== "AND") return null;
7043
- {
7044
- const left = extractAndLeaves(where.left, accept);
7045
- const right = extractAndLeaves(where.right, accept);
7046
- if (left && right) return { ...where, left, right };
7047
- return left ?? right ?? null;
7048
- }
7049
- case "GROUP":
7050
- return extractAndLeaves(where.expr, accept);
7051
- case "NULL_CHECK":
7052
- case "NOT":
7053
- case "EXISTS":
7054
- case "BOOLEAN":
7055
- return null;
7056
- }
7057
- }
7058
- function isSafeComparison(expr, options) {
7059
- if (isKlikeComparison(expr, options)) return true;
7060
- if (isSafeIdComparison(expr, options)) return true;
7061
- if (isNumericCandidate(expr, options)) {
7062
- return options.fieldTypes?.get(expr.left.field) === "NUMBER";
7063
- }
7064
- return isSelectionInComparison(expr, options);
7065
- }
7066
- function isKlikeComparison(expr, options) {
7067
- if (options.allowKlike === false) return false;
7068
- if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
7069
- if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
7070
- return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
7071
- }
7072
- var SELECTION_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
7027
+ // src/core/fieldSemantics.ts
7028
+ var STRING_FIELD_TYPES = /* @__PURE__ */ new Set([
7029
+ "SINGLE_LINE_TEXT",
7030
+ "MULTI_LINE_TEXT",
7031
+ "RICH_TEXT",
7032
+ "LINK",
7033
+ "DATE",
7034
+ "TIME",
7035
+ "DATETIME",
7036
+ "CREATED_TIME",
7037
+ "UPDATED_TIME",
7038
+ "CREATOR",
7039
+ "MODIFIER"
7040
+ ]);
7041
+ var OPTION_FIELD_TYPES = /* @__PURE__ */ new Set([
7073
7042
  "DROP_DOWN",
7074
7043
  "RADIO_BUTTON",
7075
7044
  "CHECK_BOX",
7076
7045
  "MULTI_SELECT",
7077
7046
  "STATUS"
7078
7047
  ]);
7079
- function isSelectionInComparison(expr, options) {
7080
- if (!isSelectionInCandidate(expr, options)) return false;
7081
- if (expr.left.type !== "FIELD" || expr.right.type !== "IN_LIST") return false;
7082
- const fieldType = options.fieldTypes?.get(expr.left.field);
7083
- if (fieldType === void 0 || !SELECTION_IN_FIELD_TYPES.has(fieldType)) return false;
7084
- const validOptions = options.fieldOptions?.get(expr.left.field);
7085
- if (validOptions === void 0) return false;
7086
- return expr.right.values.every(
7087
- (value) => value.type === "STRING" && value.value !== "" && validOptions.has(value.value)
7088
- );
7089
- }
7090
- function isSafeIdComparison(expr, options) {
7091
- if (!isTargetIdField(expr.left, options)) return false;
7092
- if (expr.right.type !== "NUMBER") return false;
7093
- return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
7094
- }
7095
- function isTargetIdField(field, options) {
7096
- if (field.type !== "FIELD" || field.field !== "$id") return false;
7097
- const targetAlias = options.tableAlias ?? null;
7098
- if (field.tableAlias === targetAlias) return true;
7099
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
7100
- }
7101
- function isNumericCandidate(expr, options) {
7102
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
7103
- if (!isTargetField(expr.left, options)) return false;
7104
- if (expr.right.type !== "NUMBER") return false;
7105
- if (expr.op === "=") return true;
7106
- return (expr.op === "<" || expr.op === ">") && /^[+-]?\d+$/.test(numberLiteralText(expr.right)) && Number.isSafeInteger(expr.right.value);
7107
- }
7108
- function isSelectionInCandidate(expr, options) {
7109
- if (expr.left.type !== "FIELD" || expr.left.field === "$id") return false;
7110
- if (!isTargetField(expr.left, options)) return false;
7111
- if (expr.op !== "IN" && expr.op !== "NOT_IN") return false;
7112
- if (expr.right.type !== "IN_LIST" || expr.right.values.length === 0) return false;
7113
- return expr.right.values.every((value) => value.type === "STRING" && value.value !== "");
7114
- }
7115
- function isTargetField(field, options) {
7116
- const targetAlias = options.tableAlias ?? null;
7117
- if (field.tableAlias === targetAlias) return true;
7118
- return options.allowUnqualifiedFields === true && field.tableAlias === null;
7119
- }
7120
-
7121
- // src/core/optimization/klikePushdownPlan.ts
7122
- function buildSingleTableKlikePushdownPlan(where, options = {}) {
7123
- const condition = where !== null && options.extractCondition !== false ? extractSafePushdownLeaves(where, options) : null;
7124
- const appliedKlikes = /* @__PURE__ */ new Set();
7125
- collectKlikes(condition, appliedKlikes);
7126
- const allKlikes = /* @__PURE__ */ new Set();
7127
- collectKlikes(where, allKlikes);
7128
- return { condition, appliedKlikes, allKlikes: [...allKlikes] };
7129
- }
7130
- function buildKlikePushdownPlan(stmt, options = {}) {
7131
- const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
7132
- const common = {
7133
- allowKlike: joinsAreSafeForKlike,
7134
- allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
7135
- };
7136
- const mainIsPhysical = !stmt.from.subtableCode && stmt.from.cteName === null;
7137
- const mainHasUsableAlias = stmt.joins.length === 0 || stmt.from.alias !== null;
7138
- const mainPlan = buildSingleTableKlikePushdownPlan(stmt.where, {
7139
- ...common,
7140
- extractCondition: mainIsPhysical && mainHasUsableAlias,
7141
- tableAlias: stmt.from.alias ?? void 0,
7142
- allowUnqualifiedFields: stmt.joins.length === 0,
7143
- fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
7144
- fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
7145
- });
7146
- const mainCondition = mainPlan.condition;
7147
- const joinConditions = /* @__PURE__ */ new Map();
7148
- if (stmt.where !== null) {
7149
- for (const join2 of stmt.joins) {
7150
- if (!join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
7151
- const condition = extractSafePushdownLeaves(stmt.where, {
7152
- ...common,
7153
- tableAlias: join2.table.alias,
7154
- fieldTypes: options.fieldTypesByApp?.get(join2.table.appId),
7155
- fieldOptions: options.fieldOptionsByApp?.get(join2.table.appId)
7156
- });
7157
- if (condition !== null) joinConditions.set(join2.table.alias, condition);
7158
- }
7048
+ function resolveFieldSemantics(source) {
7049
+ let compareMode;
7050
+ if (source.fieldType === "RECORD_NUMBER" || source.fieldType === "__ID__") {
7051
+ compareMode = "recordNumber";
7052
+ } else if (source.fieldType === "NUMBER") {
7053
+ compareMode = "number";
7054
+ } else if (source.fieldType === "CALC") {
7055
+ compareMode = source.sortKind === "number" ? "number" : "string";
7056
+ } else if (OPTION_FIELD_TYPES.has(source.fieldType)) {
7057
+ compareMode = "option";
7058
+ } else if (STRING_FIELD_TYPES.has(source.fieldType)) {
7059
+ compareMode = "string";
7060
+ } else {
7061
+ compareMode = "unsupported";
7159
7062
  }
7160
- const appliedKlikes = new Set(mainPlan.appliedKlikes);
7161
- for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
7063
+ const optionOrder = source.optionOrder ? new Map(Object.entries(source.optionOrder)) : void 0;
7162
7064
  return {
7163
- mainCondition,
7164
- joinConditions,
7165
- appliedKlikes,
7166
- allKlikes: mainPlan.allKlikes
7065
+ fieldType: source.fieldType,
7066
+ compareMode,
7067
+ inSubtable: source.inSubtable === true,
7068
+ requiresCollectionOperators: source.inSubtable === true || source.requiresCollectionOperators === true,
7069
+ ...optionOrder && optionOrder.size > 0 ? { optionOrder } : {}
7167
7070
  };
7168
7071
  }
7169
- function unappliedKlikes(plan) {
7170
- return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
7171
- }
7172
- function collectKlikes(where, out) {
7173
- if (where === null) return;
7174
- if (isKlike(where)) {
7175
- out.add(where);
7176
- return;
7177
- }
7178
- switch (where.type) {
7179
- case "LOGICAL":
7180
- collectKlikes(where.left, out);
7181
- collectKlikes(where.right, out);
7182
- return;
7183
- case "NOT":
7184
- case "GROUP":
7185
- collectKlikes(where.expr, out);
7186
- return;
7187
- case "BINARY":
7188
- case "NULL_CHECK":
7189
- case "EXISTS":
7190
- case "BOOLEAN":
7191
- return;
7192
- }
7072
+ function syntheticSemantics(compareMode, fieldType = compareMode === "number" ? "KSQL_NUMBER" : "KSQL_STRING") {
7073
+ return { fieldType, compareMode, inSubtable: false, requiresCollectionOperators: false };
7193
7074
  }
7194
-
7195
- // src/core/optimization/relativeDateFullScanExactPlan.ts
7196
- function buildRelativeDateFullScanExactPlan(input) {
7197
- const {
7198
- select,
7199
- selectMode,
7200
- capability,
7201
- context,
7202
- serializedWholeWhere,
7203
- relativeFunctionNames
7204
- } = input;
7205
- if (select.where === null) return null;
7206
- if (select.from.appId <= 0 || select.from.cteName !== null) return null;
7207
- if (select.from.subtableCode) return null;
7208
- if (select.joins.length > 0) return null;
7209
- if (!context.allowFullScanExact) return null;
7210
- if (select.orderMode === "KINTONE_NATIVE") return null;
7211
- const hasCanonicalOrder2 = select.orderMode === "CANONICAL" && select.orderBy.length > 0;
7212
- if (selectMode !== "FULL_SCAN" && !hasCanonicalOrder2) return null;
7213
- if (capability.capability !== "EXACT_PUSHDOWN") return null;
7214
- const occurrences = serverOnlyFunctionOccurrencesInWhere(select.where);
7215
- if (occurrences.length === 0) return null;
7216
- if (!sameOccurrenceList(occurrences, relativeFunctionNames)) return null;
7217
- if (serializedWholeWhere === null || !serializedMultisetContains(serializedWholeWhere, occurrences)) {
7218
- return null;
7219
- }
7220
- const prefilterPlan = {
7221
- prefilterWhere: select.where,
7222
- residualWhere: null,
7223
- exactRelativeLeaves: collectExactServerFunctionLeaves(select.where),
7224
- relativeFunctionNames: new Set(occurrences),
7225
- appliedKlikes: /* @__PURE__ */ new Set(),
7226
- capability: capability.capability,
7227
- reasons: capability.reasons
7228
- };
7229
- const plan = {
7230
- allowForm: "FULL_SCAN_EXACT",
7231
- clientWhereEvaluation: false,
7232
- serializedWholeWhere,
7233
- prefilterPlan
7234
- };
7235
- assertRelativeDateFullScanExactPlan(plan, input, occurrences);
7236
- return plan;
7075
+ function withFieldSemanticSource(semantics, appId, fieldCode) {
7076
+ return { ...semantics, source: { appId, fieldCode } };
7237
7077
  }
7238
- function assertRelativeDateFullScanExactPlan(plan, input, occurrences) {
7239
- if (plan.allowForm !== "FULL_SCAN_EXACT") {
7240
- throw new Error("FULL_SCAN_EXACT invariant: allowForm");
7241
- }
7242
- if (plan.clientWhereEvaluation !== false) {
7243
- throw new Error("FULL_SCAN_EXACT invariant: clientWhereEvaluation");
7244
- }
7245
- if (input.capability.capability !== "EXACT_PUSHDOWN") {
7246
- throw new Error("FULL_SCAN_EXACT invariant: capability");
7247
- }
7248
- if (input.select.where === null || plan.prefilterPlan.prefilterWhere !== input.select.where) {
7249
- throw new Error("FULL_SCAN_EXACT invariant: whole WHERE identity");
7250
- }
7251
- if (plan.prefilterPlan.residualWhere !== null) {
7252
- throw new Error("FULL_SCAN_EXACT invariant: residualWhere");
7253
- }
7254
- if (plan.prefilterPlan.capability !== "EXACT_PUSHDOWN") {
7255
- throw new Error("FULL_SCAN_EXACT invariant: transport capability");
7256
- }
7257
- if (!serializedMultisetContains(plan.serializedWholeWhere, occurrences)) {
7258
- throw new Error("FULL_SCAN_EXACT invariant: relative occurrence serialization");
7078
+ function fieldSemanticsEqual(left, right) {
7079
+ if (left === right) return true;
7080
+ if (!left || !right) return false;
7081
+ if (left.fieldType !== right.fieldType || left.compareMode !== right.compareMode || left.inSubtable !== right.inSubtable || left.requiresCollectionOperators !== right.requiresCollectionOperators) return false;
7082
+ if (left.source?.appId !== right.source?.appId || left.source?.fieldCode !== right.source?.fieldCode) return false;
7083
+ const a = left.optionOrder;
7084
+ const b = right.optionOrder;
7085
+ if (a === b) return true;
7086
+ if (!a || !b || a.size !== b.size) return false;
7087
+ for (const [key, value] of a) {
7088
+ if (b.get(key) !== value) return false;
7259
7089
  }
7090
+ return true;
7260
7091
  }
7261
- function serverOnlyFunctionOccurrencesInWhere(where) {
7262
- const names = [];
7263
- const visit = (node) => {
7264
- if (Array.isArray(node)) {
7265
- node.forEach(visit);
7266
- return;
7267
- }
7268
- if (node === null || typeof node !== "object") return;
7269
- const value = node;
7270
- if (value["type"] === "SELECT") return;
7271
- if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isServerOnlyWhereFunctionName(value["name"])) {
7272
- names.push(value["name"]);
7273
- return;
7274
- }
7275
- Object.values(value).forEach(visit);
7276
- };
7277
- visit(where);
7278
- return names;
7092
+
7093
+ // src/core/optimization/whereCapability.ts
7094
+ var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
7095
+ var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
7096
+ "DATE",
7097
+ "DATETIME",
7098
+ "CREATED_TIME",
7099
+ "UPDATED_TIME"
7100
+ ]);
7101
+ var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
7102
+ var LEGACY_KINTONE_FUNCTION_FIELD_TYPES = /* @__PURE__ */ new Map([
7103
+ ["TODAY", /* @__PURE__ */ new Set(["DATE", "DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
7104
+ ["NOW", /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
7105
+ ["LOGINUSER", /* @__PURE__ */ new Set(["CREATOR", "MODIFIER", "USER_SELECT"])],
7106
+ ["PRIMARY_ORGANIZATION", /* @__PURE__ */ new Set(["ORGANIZATION_SELECT"])]
7107
+ ]);
7108
+ var LEGACY_KINTONE_FUNCTION_OPERATORS = /* @__PURE__ */ new Map([
7109
+ ["TODAY", new Set(RANGE_AND_EQUALITY)],
7110
+ ["NOW", new Set(RANGE_AND_EQUALITY)],
7111
+ ["LOGINUSER", /* @__PURE__ */ new Set(["in", "not in"])],
7112
+ ["PRIMARY_ORGANIZATION", /* @__PURE__ */ new Set(["in", "not in"])]
7113
+ ]);
7114
+ var EQUALITY_IN = ["=", "!=", "in", "not in"];
7115
+ var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
7116
+ ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
7117
+ ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
7118
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
7119
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
7120
+ ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
7121
+ ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
7122
+ ["DATE", new Set(RANGE_AND_EQUALITY)],
7123
+ ["TIME", new Set(RANGE_AND_EQUALITY)],
7124
+ ["DATETIME", new Set(RANGE_AND_EQUALITY)],
7125
+ ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
7126
+ ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
7127
+ ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
7128
+ ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
7129
+ ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
7130
+ ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
7131
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
7132
+ ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
7133
+ ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
7134
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
7135
+ ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
7136
+ ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
7137
+ ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
7138
+ ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
7139
+ ["STATUS", new Set(EQUALITY_IN)],
7140
+ ["STATUS_ASSIGNEE", /* @__PURE__ */ new Set(["in", "not in"])]
7141
+ ]);
7142
+ var LOCAL_VALID_OPERATORS = /* @__PURE__ */ new Map([
7143
+ ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
7144
+ ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
7145
+ ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
7146
+ ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])]
7147
+ ]);
7148
+ var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
7149
+ "RECORD_NUMBER",
7150
+ "__ID__",
7151
+ "CREATOR",
7152
+ "MODIFIER",
7153
+ "CREATED_TIME",
7154
+ "UPDATED_TIME",
7155
+ "DATE",
7156
+ "TIME",
7157
+ "DATETIME",
7158
+ "SINGLE_LINE_TEXT",
7159
+ "LINK",
7160
+ "NUMBER",
7161
+ "CALC",
7162
+ "MULTI_LINE_TEXT",
7163
+ "RICH_TEXT",
7164
+ "RADIO_BUTTON",
7165
+ "DROP_DOWN",
7166
+ "STATUS",
7167
+ // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
7168
+ "KSQL_STRING",
7169
+ "KSQL_NUMBER",
7170
+ "KSQL_BOOLEAN"
7171
+ ]);
7172
+ var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
7173
+ "CHECK_BOX",
7174
+ "MULTI_SELECT",
7175
+ "FILE",
7176
+ "USER_SELECT",
7177
+ "ORGANIZATION_SELECT",
7178
+ "GROUP_SELECT",
7179
+ "STATUS_ASSIGNEE",
7180
+ "CATEGORY"
7181
+ ]);
7182
+ function nativeWhereOperatorsForType(fieldType) {
7183
+ return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
7279
7184
  }
7280
- function collectExactServerFunctionLeaves(where) {
7281
- const leaves = [];
7185
+ function normalizeChoiceEquality(where, resolveField2) {
7186
+ const rewrites = [];
7282
7187
  const visit = (node) => {
7283
- switch (node.type) {
7284
- case "BINARY":
7285
- if (node.right.type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.name) || node.right.type === "IN_LIST" && node.right.values.length === 1 && node.right.values[0].type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.values[0].name)) {
7286
- leaves.push(node);
7188
+ if (node.type === "BINARY") {
7189
+ if ((node.op === "=" || node.op === "!=" || node.op === "<>") && node.left.type === "FIELD" && node.right.type === "STRING" && node.right.value !== "") {
7190
+ const semantics = resolveField2(node.left);
7191
+ if (semantics !== void 0 && semantics.compareMode === "option" && LOCAL_SCALAR_TYPES.has(semantics.fieldType) && nativeWhereOperatorsForType(semantics.fieldType).has("in") && semantics.optionOrder?.has(node.right.value) === true) {
7192
+ const normalizedOperator = node.op === "=" ? "IN" : "NOT_IN";
7193
+ rewrites.push({
7194
+ field: node.left,
7195
+ originalOperator: node.op,
7196
+ normalizedOperator,
7197
+ value: node.right.value
7198
+ });
7199
+ return {
7200
+ ...node,
7201
+ op: normalizedOperator,
7202
+ right: { type: "IN_LIST", values: [node.right] }
7203
+ };
7287
7204
  }
7288
- return;
7289
- case "LOGICAL":
7290
- visit(node.left);
7291
- visit(node.right);
7292
- return;
7293
- case "NOT":
7294
- case "GROUP":
7295
- visit(node.expr);
7296
- return;
7297
- case "EXISTS":
7298
- case "NULL_CHECK":
7299
- case "BOOLEAN":
7300
- return;
7205
+ }
7206
+ return node;
7207
+ }
7208
+ if (node.type === "LOGICAL") {
7209
+ const left = visit(node.left);
7210
+ const right = visit(node.right);
7211
+ return left === node.left && right === node.right ? node : { ...node, left, right };
7301
7212
  }
7213
+ if (node.type === "GROUP" || node.type === "NOT") {
7214
+ const expr = visit(node.expr);
7215
+ return expr === node.expr ? node : { ...node, expr };
7216
+ }
7217
+ return node;
7302
7218
  };
7303
- visit(where);
7304
- return leaves;
7305
- }
7306
- function sameOccurrenceList(actual, expected) {
7307
- return actual.length === expected.length && actual.every((name, index) => name === expected[index]);
7219
+ return { normalizedWhere: visit(where), rewrites };
7308
7220
  }
7309
- function serializedMultisetContains(query, expectedNames) {
7310
- const expected = /* @__PURE__ */ new Map();
7311
- for (const name of expectedNames) {
7312
- if (!isServerOnlyWhereFunctionName(name)) return false;
7313
- expected.set(name, (expected.get(name) ?? 0) + 1);
7314
- }
7315
- for (const [name, count] of expected) {
7316
- const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
7317
- if ((matches?.length ?? 0) < count) return false;
7221
+ function classifyWhereCapability(where, resolveField2) {
7222
+ if (where === null) {
7223
+ return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
7318
7224
  }
7319
- return true;
7225
+ return classifyNode(where, resolveField2);
7320
7226
  }
7321
-
7322
- // src/core/klikeValidation.ts
7323
- var KlikeValidationError = class extends Error {
7324
- constructor(message) {
7325
- super(`ArgumentError: ${message}`);
7326
- this.name = "ArgumentError";
7227
+ function classifyNode(where, resolveField2) {
7228
+ switch (where.type) {
7229
+ case "BOOLEAN":
7230
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
7231
+ case "BINARY":
7232
+ return classifyBinary(where.op, where.left, where.right, resolveField2);
7233
+ case "NULL_CHECK":
7234
+ if (where.field.type !== "FIELD") return localExpression();
7235
+ return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
7236
+ case "EXISTS":
7237
+ return localExpression();
7238
+ case "GROUP":
7239
+ return classifyNode(where.expr, resolveField2);
7240
+ case "NOT": {
7241
+ const inner = classifyNode(where.expr, resolveField2);
7242
+ if (inner.capability !== "SUPERSET_PREFILTER") return inner;
7243
+ if (!hasRelativeDateReason(inner.reasons)) {
7244
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
7245
+ }
7246
+ return requireExactFunctionPushdown({
7247
+ capability: "LOCAL_ONLY",
7248
+ reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
7249
+ });
7250
+ }
7251
+ case "LOGICAL": {
7252
+ const left = classifyNode(where.left, resolveField2);
7253
+ const right = classifyNode(where.right, resolveField2);
7254
+ return combineLogical(where.op, left, right);
7255
+ }
7327
7256
  }
7328
- };
7329
- function validateKlikeStatement(stmt) {
7330
- validateStatement(stmt);
7331
7257
  }
7332
- function validateKlikePushdownPlan(plan) {
7333
- if (unappliedKlikes(plan).length > 0) {
7334
- throw new KlikeValidationError(
7335
- "FULL_SCAN \u306E KLIKE / NOT KLIKE \u3092\u5B89\u5168\u306B\u62BC\u3057\u4E0B\u3052\u3089\u308C\u307E\u305B\u3093\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044"
7336
- );
7258
+ function classifyBinary(op, left, right, resolveField2) {
7259
+ if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
7260
+ return classifyRelativeDateBinary(op, left, right, resolveField2);
7337
7261
  }
7338
- }
7339
- function validateStatement(stmt) {
7340
- switch (stmt.type) {
7341
- case "SELECT":
7342
- validateSelect(stmt);
7343
- return;
7344
- case "UNION":
7345
- validateUnion(stmt);
7346
- return;
7347
- case "WITH":
7348
- validateWith(stmt);
7349
- return;
7350
- case "EXPLAIN":
7351
- validateStatement(stmt.query);
7352
- return;
7353
- case "CREATE_TEMP_TABLE":
7354
- validateSelectLike(stmt.query);
7355
- return;
7356
- case "SET_VARIABLE":
7357
- case "DECLARE_VARIABLE":
7358
- case "ASSERT":
7359
- validateNestedSelects(stmt);
7360
- return;
7361
- case "UPDATE":
7362
- if (stmt.applyBlocks?.length && !isSinglePositiveRecordIdWhere(stmt.where) && whereHasKlike(stmt.where)) {
7363
- validateKlikeWhereExpressions(stmt.where);
7364
- validateNestedSelects(stmt);
7365
- return;
7366
- }
7367
- if (containsKlike(stmt) && stmt.subtableCode) {
7368
- throw new KlikeValidationError(
7369
- "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
7370
- );
7371
- }
7372
- if (!stmt.applyBlocks?.length && !stmt.subtableCode && whereHasKlike(stmt.where) && !containsKlikeOutsideWhereAndNestedSelects(stmt, stmt.where)) {
7373
- validateKlikeWhereExpressions(stmt.where);
7374
- validateNestedSelects(stmt);
7375
- return;
7262
+ if (right.type === "KINTONE_FUNC" && isLegacyKintoneFunction(right)) {
7263
+ return classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2);
7264
+ }
7265
+ if (right.type === "IN_LIST") {
7266
+ const functions = right.values.filter(
7267
+ (value) => value.type === "KINTONE_FUNC"
7268
+ );
7269
+ if (functions.length > 0) {
7270
+ if (right.values.length === 1 && functions.length === 1) {
7271
+ return classifyLegacyKintoneFunctionBinary(op, left, functions[0], resolveField2);
7376
7272
  }
7377
- if (containsKlike(stmt)) {
7273
+ return legacyKintoneFunctionUnsupported(
7274
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
7275
+ functions[0].name,
7276
+ left.type === "FIELD" ? left.field : void 0,
7277
+ left.type === "FIELD" ? resolveField2(left)?.fieldType : void 0,
7278
+ normalizeOperator(op)
7279
+ );
7280
+ }
7281
+ }
7282
+ if (left.type !== "FIELD") return localExpression();
7283
+ const semantics = resolveField2(left);
7284
+ if (!semantics) {
7285
+ return unsupported2("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
7286
+ }
7287
+ const nativeOp = normalizeOperator(op);
7288
+ if (!isLocallyValidOperator(semantics.fieldType, nativeOp)) {
7289
+ return unsupported2(
7290
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
7291
+ left.field,
7292
+ semantics.fieldType,
7293
+ nativeOp
7294
+ );
7295
+ }
7296
+ if (!hasLocalContract(semantics.fieldType, op)) {
7297
+ return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, nativeOp);
7298
+ }
7299
+ const native = nativeWhereOperatorsForType(semantics.fieldType);
7300
+ const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
7301
+ const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
7302
+ const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
7303
+ if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
7304
+ return {
7305
+ capability: "EXACT_PUSHDOWN",
7306
+ reasons: [{
7307
+ code: "WHERE_EXACT",
7308
+ field: left.field,
7309
+ fieldType: semantics.fieldType,
7310
+ operator: nativeOp
7311
+ }]
7312
+ };
7313
+ }
7314
+ return {
7315
+ capability: "LOCAL_ONLY",
7316
+ reasons: [{
7317
+ code: "WHERE_RESIDUAL",
7318
+ field: left.field,
7319
+ fieldType: semantics.fieldType,
7320
+ operator: nativeOp
7321
+ }]
7322
+ };
7323
+ }
7324
+ function isLegacyKintoneFunction(value) {
7325
+ return value.type === "KINTONE_FUNC" && LEGACY_KINTONE_FUNCTION_NAMES.has(value.name);
7326
+ }
7327
+ function classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2) {
7328
+ const operator = normalizeOperator(op);
7329
+ const functionName = right.name;
7330
+ if (!LEGACY_KINTONE_FUNCTION_NAMES.has(functionName) || left.type !== "FIELD") {
7331
+ return legacyKintoneFunctionUnsupported(
7332
+ "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
7333
+ functionName,
7334
+ void 0,
7335
+ void 0,
7336
+ operator
7337
+ );
7338
+ }
7339
+ const semantics = resolveField2(left);
7340
+ const validFieldTypes = LEGACY_KINTONE_FUNCTION_FIELD_TYPES.get(functionName);
7341
+ if (!semantics || !validFieldTypes.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
7342
+ return legacyKintoneFunctionUnsupported(
7343
+ "WHERE_KINTONE_FUNCTION_FIELD_TYPE_UNSUPPORTED",
7344
+ functionName,
7345
+ left.field,
7346
+ semantics?.fieldType,
7347
+ operator
7348
+ );
7349
+ }
7350
+ const validOperators = LEGACY_KINTONE_FUNCTION_OPERATORS.get(functionName);
7351
+ if (!validOperators.has(operator)) {
7352
+ return legacyKintoneFunctionUnsupported(
7353
+ "WHERE_KINTONE_FUNCTION_OPERATOR_UNSUPPORTED",
7354
+ functionName,
7355
+ left.field,
7356
+ semantics.fieldType,
7357
+ operator
7358
+ );
7359
+ }
7360
+ return {
7361
+ capability: "EXACT_PUSHDOWN",
7362
+ reasons: [{
7363
+ code: "WHERE_EXACT",
7364
+ functionName,
7365
+ field: left.field,
7366
+ fieldType: semantics.fieldType,
7367
+ operator
7368
+ }]
7369
+ };
7370
+ }
7371
+ function classifyRelativeDateBinary(op, left, right, resolveField2) {
7372
+ const operator = normalizeOperator(op);
7373
+ const functionName = right.name;
7374
+ if (left.type !== "FIELD") {
7375
+ return relativeDateUnsupported(
7376
+ "WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
7377
+ functionName,
7378
+ void 0,
7379
+ void 0,
7380
+ operator
7381
+ );
7382
+ }
7383
+ const semantics = resolveField2(left);
7384
+ if (!semantics) {
7385
+ return relativeDateUnsupported(
7386
+ "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
7387
+ functionName,
7388
+ left.field,
7389
+ void 0,
7390
+ operator
7391
+ );
7392
+ }
7393
+ if (!hasValidRelativeDateArguments(right)) {
7394
+ return relativeDateUnsupported(
7395
+ "WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
7396
+ functionName,
7397
+ left.field,
7398
+ semantics.fieldType,
7399
+ operator
7400
+ );
7401
+ }
7402
+ if (!RELATIVE_DATE_OPERATORS.has(operator)) {
7403
+ return relativeDateUnsupported(
7404
+ "WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
7405
+ functionName,
7406
+ left.field,
7407
+ semantics.fieldType,
7408
+ operator
7409
+ );
7410
+ }
7411
+ if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
7412
+ return relativeDateUnsupported(
7413
+ "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
7414
+ functionName,
7415
+ left.field,
7416
+ semantics.fieldType,
7417
+ operator
7418
+ );
7419
+ }
7420
+ return {
7421
+ capability: "EXACT_PUSHDOWN",
7422
+ reasons: [{
7423
+ code: "WHERE_EXACT",
7424
+ functionName,
7425
+ field: left.field,
7426
+ fieldType: semantics.fieldType,
7427
+ operator
7428
+ }]
7429
+ };
7430
+ }
7431
+ function hasValidRelativeDateArguments(value) {
7432
+ if (!("args" in value) || !value.args) return false;
7433
+ switch (value.name) {
7434
+ case "YESTERDAY":
7435
+ case "TOMORROW":
7436
+ case "THIS_YEAR":
7437
+ case "LAST_YEAR":
7438
+ case "NEXT_YEAR":
7439
+ return value.args.kind === "NONE";
7440
+ case "FROM_TODAY":
7441
+ return value.args.kind === "FROM_TODAY" && Number.isSafeInteger(value.args.offset) && value.args.offsetText === String(value.args.offset === 0 ? 0 : value.args.offset) && (value.args.unit === "DAYS" || value.args.unit === "WEEKS" || value.args.unit === "MONTHS" || value.args.unit === "YEARS");
7442
+ case "THIS_WEEK":
7443
+ case "LAST_WEEK":
7444
+ case "NEXT_WEEK":
7445
+ return value.args.kind === "WEEK" && (value.args.weekday === null || value.args.weekday === "SUNDAY" || value.args.weekday === "MONDAY" || value.args.weekday === "TUESDAY" || value.args.weekday === "WEDNESDAY" || value.args.weekday === "THURSDAY" || value.args.weekday === "FRIDAY" || value.args.weekday === "SATURDAY");
7446
+ case "THIS_MONTH":
7447
+ case "LAST_MONTH":
7448
+ case "NEXT_MONTH":
7449
+ return value.args.kind === "MONTH" && (value.args.day === null || value.args.day === "LAST" || Number.isInteger(value.args.day) && value.args.day >= 1 && value.args.day <= 31);
7450
+ default:
7451
+ return false;
7452
+ }
7453
+ }
7454
+ function classifyLocalOnlyField(field, operator, resolveField2) {
7455
+ const semantics = resolveField2(field);
7456
+ if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
7457
+ if (!isLocallyValidOperator(semantics.fieldType, operator)) {
7458
+ return unsupported2(
7459
+ "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
7460
+ field.field,
7461
+ semantics.fieldType,
7462
+ operator
7463
+ );
7464
+ }
7465
+ if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
7466
+ return unsupported2("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
7467
+ }
7468
+ return {
7469
+ capability: "LOCAL_ONLY",
7470
+ reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
7471
+ };
7472
+ }
7473
+ function isLocallyValidOperator(fieldType, operator) {
7474
+ const policy = LOCAL_VALID_OPERATORS.get(fieldType);
7475
+ return policy === void 0 || policy.has(operator);
7476
+ }
7477
+ function hasLocalContract(fieldType, op) {
7478
+ if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
7479
+ if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
7480
+ return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
7481
+ }
7482
+ function normalizeOperator(op) {
7483
+ switch (op) {
7484
+ case "<>":
7485
+ return "!=";
7486
+ case "IN":
7487
+ return "in";
7488
+ case "NOT_IN":
7489
+ return "not in";
7490
+ case "LIKE":
7491
+ case "KLIKE":
7492
+ return "like";
7493
+ case "NOT_LIKE":
7494
+ case "NOT_KLIKE":
7495
+ return "not like";
7496
+ default:
7497
+ return op;
7498
+ }
7499
+ }
7500
+ function combineLogical(op, left, right) {
7501
+ const reasons = [...left.reasons, ...right.reasons];
7502
+ if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
7503
+ return requireExactFunctionPushdown({ capability: "UNSUPPORTED", reasons });
7504
+ }
7505
+ if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
7506
+ return { capability: "EXACT_PUSHDOWN", reasons };
7507
+ }
7508
+ if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
7509
+ return requireExactFunctionPushdown({
7510
+ capability: "SUPERSET_PREFILTER",
7511
+ reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
7512
+ });
7513
+ }
7514
+ return requireExactFunctionPushdown({ capability: "LOCAL_ONLY", reasons });
7515
+ }
7516
+ function localExpression() {
7517
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
7518
+ }
7519
+ function unsupported2(code, field, fieldType, operator) {
7520
+ return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
7521
+ }
7522
+ function legacyKintoneFunctionUnsupported(code, functionName, field, fieldType, operator) {
7523
+ return requireExactFunctionPushdown({
7524
+ capability: "UNSUPPORTED",
7525
+ reasons: [{ code, functionName, field, fieldType, operator }]
7526
+ });
7527
+ }
7528
+ function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
7529
+ return requireExactRelativeDatePushdown({
7530
+ capability: "UNSUPPORTED",
7531
+ reasons: [{ code, functionName, field, fieldType, operator }]
7532
+ });
7533
+ }
7534
+ function hasRelativeDateReason(reasons) {
7535
+ return reasons.some(
7536
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
7537
+ );
7538
+ }
7539
+ function hasLegacyKintoneFunctionReason(reasons) {
7540
+ return reasons.some(
7541
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
7542
+ );
7543
+ }
7544
+ function requireExactRelativeDatePushdown(result) {
7545
+ if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
7546
+ return result;
7547
+ }
7548
+ const relative = result.reasons.find(
7549
+ (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
7550
+ );
7551
+ return {
7552
+ capability: result.capability,
7553
+ reasons: [
7554
+ ...result.reasons,
7555
+ {
7556
+ code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
7557
+ functionName: relative.functionName,
7558
+ field: relative.field,
7559
+ fieldType: relative.fieldType,
7560
+ operator: relative.operator
7561
+ }
7562
+ ]
7563
+ };
7564
+ }
7565
+ function requireExactLegacyKintoneFunctionPushdown(result) {
7566
+ if (result.capability === "EXACT_PUSHDOWN" || !hasLegacyKintoneFunctionReason(result.reasons) || result.reasons.some(
7567
+ (reason) => reason.code === "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN"
7568
+ )) {
7569
+ return result;
7570
+ }
7571
+ const legacy = result.reasons.find(
7572
+ (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
7573
+ );
7574
+ return {
7575
+ capability: result.capability,
7576
+ reasons: [
7577
+ ...result.reasons,
7578
+ {
7579
+ code: "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN",
7580
+ functionName: legacy.functionName,
7581
+ field: legacy.field,
7582
+ fieldType: legacy.fieldType,
7583
+ operator: legacy.operator
7584
+ }
7585
+ ]
7586
+ };
7587
+ }
7588
+ function requireExactFunctionPushdown(result) {
7589
+ return requireExactLegacyKintoneFunctionPushdown(
7590
+ requireExactRelativeDatePushdown(result)
7591
+ );
7592
+ }
7593
+
7594
+ // src/core/optimization/joinDateTimeLiteralPolicy.ts
7595
+ function isCanonicalJoinDate(value) {
7596
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
7597
+ if (!match) return false;
7598
+ const year = Number(match[1]);
7599
+ const month = Number(match[2]);
7600
+ const day = Number(match[3]);
7601
+ if (year < 1 || year > 9999) return false;
7602
+ const date = /* @__PURE__ */ new Date(0);
7603
+ date.setUTCFullYear(year, month - 1, day);
7604
+ date.setUTCHours(0, 0, 0, 0);
7605
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
7606
+ }
7607
+ function isCanonicalJoinTime(value) {
7608
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
7609
+ return match !== null && Number(match[1]) <= 23 && Number(match[2]) <= 59;
7610
+ }
7611
+ function isCanonicalJoinDateTime(value) {
7612
+ const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.exec(value);
7613
+ if (!match || !isCanonicalJoinDate(match[1])) return false;
7614
+ return Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59;
7615
+ }
7616
+
7617
+ // src/core/optimization/joinNumberLiteralPolicy.ts
7618
+ function isJoinNumberLiteralSupported(literal) {
7619
+ const source = literal.raw ?? String(literal.value);
7620
+ const decimal = parseExactDecimal(source);
7621
+ if (decimal === null) return false;
7622
+ if (decimal.sign === 0) return numberLiteralText(literal) === "0";
7623
+ const fractionDigits = Math.max(decimal.scale, 0);
7624
+ const integerDigits = Math.max(decimal.coefficient.length - decimal.scale, 0);
7625
+ if (fractionDigits > 10 || integerDigits + fractionDigits > 30) {
7626
+ return false;
7627
+ }
7628
+ const canonical = formatPlainDecimal(decimal);
7629
+ return numberLiteralText(literal) === canonical;
7630
+ }
7631
+
7632
+ // src/core/optimization/supportedLeafPolicy.ts
7633
+ var SELECTION_TYPES = /* @__PURE__ */ new Set([
7634
+ "DROP_DOWN",
7635
+ "RADIO_BUTTON",
7636
+ "CHECK_BOX",
7637
+ "MULTI_SELECT",
7638
+ "STATUS"
7639
+ ]);
7640
+ var USER_CODE_TYPES = /* @__PURE__ */ new Set([
7641
+ "CREATOR",
7642
+ "MODIFIER",
7643
+ "USER_SELECT",
7644
+ "ORGANIZATION_SELECT",
7645
+ "GROUP_SELECT",
7646
+ "STATUS_ASSIGNEE"
7647
+ ]);
7648
+ var KLIKE_TYPES = /* @__PURE__ */ new Set([
7649
+ "SINGLE_LINE_TEXT",
7650
+ "LINK",
7651
+ "MULTI_LINE_TEXT",
7652
+ "RICH_TEXT",
7653
+ "FILE"
7654
+ ]);
7655
+ var DATETIME_TYPES = /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"]);
7656
+ function classifySupportedLeaf(predicate, metadata) {
7657
+ const { fieldCode, fieldType, fieldOptions } = metadata;
7658
+ if (fieldType.startsWith("KSQL_")) return "unsafe";
7659
+ if (predicate.op === "LIKE" || predicate.op === "NOT_LIKE") return "unsafe";
7660
+ if (predicate.op === "KLIKE" || predicate.op === "NOT_KLIKE") {
7661
+ return KLIKE_TYPES.has(fieldType) && predicate.right.type === "STRING" && predicate.right.value !== "" ? "exact" : "unsafe";
7662
+ }
7663
+ if (fieldType === "__ID__" || fieldCode === "$id") {
7664
+ return isPositiveSafeInteger(predicate.right) && (predicate.op === "=" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") ? "exact" : "unsafe";
7665
+ }
7666
+ if (fieldType === "RECORD_NUMBER" || fieldType === "CALC") {
7667
+ return classifySupersetScalarOrListLiteral(predicate);
7668
+ }
7669
+ if (fieldType === "NUMBER") {
7670
+ if ((predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST" && predicate.right.values.length > 0 && predicate.right.values.every((value) => value.type === "NUMBER" && isJoinNumberLiteralSupported(value))) {
7671
+ return "exact";
7672
+ }
7673
+ return (predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") && predicate.right.type === "NUMBER" && isJoinNumberLiteralSupported(predicate.right) ? "exact" : "unsafe";
7674
+ }
7675
+ if (USER_CODE_TYPES.has(fieldType)) {
7676
+ return isNonEmptyStringList(predicate) ? "exact" : "unsafe";
7677
+ }
7678
+ if (fieldType === "SINGLE_LINE_TEXT" || fieldType === "LINK") {
7679
+ if (isNonEmptyStringList(predicate)) return "exact";
7680
+ return (predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>") && predicate.right.type === "STRING" && predicate.right.value !== "" ? "exact" : "unsafe";
7681
+ }
7682
+ if (fieldType === "DATE" || fieldType === "TIME" || DATETIME_TYPES.has(fieldType)) {
7683
+ if (predicate.op !== "=" && predicate.op !== "!=" && predicate.op !== "<>" && predicate.op !== "<" && predicate.op !== ">" && predicate.op !== "<=" && predicate.op !== ">=" || predicate.right.type !== "STRING") return "unsafe";
7684
+ if (fieldType === "DATE") return isCanonicalJoinDate(predicate.right.value) ? "exact" : "unsafe";
7685
+ if (fieldType === "TIME") return isCanonicalJoinTime(predicate.right.value) ? "exact" : "unsafe";
7686
+ return isCanonicalJoinDateTime(predicate.right.value) ? "exact" : "unsafe";
7687
+ }
7688
+ if (SELECTION_TYPES.has(fieldType)) {
7689
+ if (!isNonEmptyStringList(predicate) || fieldOptions === void 0) return "unsafe";
7690
+ return predicate.right.type === "IN_LIST" && predicate.right.values.every((value) => value.type === "STRING" && fieldOptions.has(value.value)) ? "exact" : "unsafe";
7691
+ }
7692
+ return "unsafe";
7693
+ }
7694
+ function isSupportedLeafMetadataCandidate(predicate, isTargetField2) {
7695
+ if (predicate.left.type !== "FIELD" || predicate.left.field === "$id") return false;
7696
+ if (!isTargetField2(predicate.left)) return false;
7697
+ if (predicate.op === "IN" || predicate.op === "NOT_IN") {
7698
+ if (predicate.right.type !== "IN_LIST" || predicate.right.values.length === 0) return false;
7699
+ const firstType = predicate.right.values[0].type;
7700
+ if (firstType !== "NUMBER" && firstType !== "STRING") return false;
7701
+ return predicate.right.values.every((value) => value.type === firstType && (value.type !== "STRING" || value.value !== ""));
7702
+ }
7703
+ if (predicate.op !== "=" && predicate.op !== "!=" && predicate.op !== "<>" && predicate.op !== "<" && predicate.op !== ">" && predicate.op !== "<=" && predicate.op !== ">=") return false;
7704
+ return predicate.right.type === "NUMBER" || predicate.right.type === "STRING" && predicate.right.value !== "";
7705
+ }
7706
+ function isNonEmptyStringList(predicate) {
7707
+ return (predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST" && predicate.right.values.length > 0 && predicate.right.values.every((value) => value.type === "STRING" && value.value !== "");
7708
+ }
7709
+ function classifySupersetScalarOrListLiteral(predicate) {
7710
+ const supportedLiteral = (value) => value.type === "NUMBER" && isJoinNumberLiteralSupported(value) || value.type === "STRING" && value.value !== "";
7711
+ if ((predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST") {
7712
+ const values = predicate.right.values;
7713
+ if (values.length > 0 && values.every(supportedLiteral) && values.every((value) => value.type === values[0].type)) return "superset";
7714
+ }
7715
+ return (predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") && supportedLiteral(predicate.right) ? "superset" : "unsafe";
7716
+ }
7717
+ function isPositiveSafeInteger(value) {
7718
+ return value.type === "NUMBER" && Number.isSafeInteger(value.value) && value.value > 0;
7719
+ }
7720
+
7721
+ // src/core/optimization/wherePredicatePushdown.ts
7722
+ function extractSafePushdownLeaves(where, options = {}) {
7723
+ return extractSafePushdownPlan(where, options).condition;
7724
+ }
7725
+ function extractSafePushdownPlan(where, options = {}) {
7726
+ return extractAndLeafPlan(where, (expr) => classifySafeComparison(expr, options));
7727
+ }
7728
+ function extractTypedPushdownCandidates(where, options = {}) {
7729
+ return extractAndLeaves(
7730
+ where,
7731
+ (expr) => isSupportedLeafMetadataCandidate(expr, (field) => isTargetField(field, options))
7732
+ );
7733
+ }
7734
+ function extractAndLeafPlan(where, classify) {
7735
+ switch (where.type) {
7736
+ case "BINARY": {
7737
+ const relation = classify(where);
7738
+ return relation === "unsafe" ? { condition: null, relation: null } : { condition: where, relation };
7739
+ }
7740
+ case "LOGICAL": {
7741
+ if (where.op !== "AND") return { condition: null, relation: null };
7742
+ const left = extractAndLeafPlan(where.left, classify);
7743
+ const right = extractAndLeafPlan(where.right, classify);
7744
+ if (left.condition && right.condition) {
7745
+ return {
7746
+ condition: { ...where, left: left.condition, right: right.condition },
7747
+ relation: left.relation === "exact" && right.relation === "exact" ? "exact" : "superset"
7748
+ };
7749
+ }
7750
+ return left.condition ? left : right;
7751
+ }
7752
+ case "GROUP":
7753
+ return extractAndLeafPlan(where.expr, classify);
7754
+ case "NULL_CHECK":
7755
+ case "NOT":
7756
+ case "EXISTS":
7757
+ case "BOOLEAN":
7758
+ return { condition: null, relation: null };
7759
+ }
7760
+ }
7761
+ function extractAndLeaves(where, accept) {
7762
+ switch (where.type) {
7763
+ case "BINARY":
7764
+ return accept(where) ? where : null;
7765
+ case "LOGICAL":
7766
+ if (where.op !== "AND") return null;
7767
+ {
7768
+ const left = extractAndLeaves(where.left, accept);
7769
+ const right = extractAndLeaves(where.right, accept);
7770
+ if (left && right) return { ...where, left, right };
7771
+ return left ?? right ?? null;
7772
+ }
7773
+ case "GROUP":
7774
+ return extractAndLeaves(where.expr, accept);
7775
+ case "NULL_CHECK":
7776
+ case "NOT":
7777
+ case "EXISTS":
7778
+ case "BOOLEAN":
7779
+ return null;
7780
+ }
7781
+ }
7782
+ function classifySafeComparison(expr, options) {
7783
+ if (isKlikeComparison(expr, options)) return "exact";
7784
+ if (isSafeIdComparison(expr, options)) return "exact";
7785
+ if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return "unsafe";
7786
+ const fieldType = options.fieldTypes?.get(expr.left.field);
7787
+ if (fieldType === void 0 || fieldType.startsWith("KSQL_")) return "unsafe";
7788
+ const capability = classifyWhereCapability(expr, (field) => {
7789
+ if (!isTargetField(field, options)) return void 0;
7790
+ const type = options.fieldTypes?.get(field.field);
7791
+ return type === void 0 ? void 0 : resolveFieldSemantics({ fieldType: type });
7792
+ });
7793
+ if (capability.capability !== "EXACT_PUSHDOWN") return "unsafe";
7794
+ return classifySupportedLeaf(expr, {
7795
+ fieldCode: expr.left.field,
7796
+ fieldType,
7797
+ fieldOptions: options.fieldOptions?.get(expr.left.field)
7798
+ });
7799
+ }
7800
+ function isKlikeComparison(expr, options) {
7801
+ if (options.allowKlike === false) return false;
7802
+ if (expr.op !== "KLIKE" && expr.op !== "NOT_KLIKE") return false;
7803
+ if (expr.left.type !== "FIELD" || !isTargetField(expr.left, options)) return false;
7804
+ return expr.right.type === "STRING" || options.allowUnresolvedKlikeVariables === true && expr.right.type === "VARIABLE";
7805
+ }
7806
+ function isSafeIdComparison(expr, options) {
7807
+ if (!isTargetIdField(expr.left, options)) return false;
7808
+ if (expr.right.type !== "NUMBER") return false;
7809
+ return expr.op === "=" || expr.op === ">" || expr.op === "<" || expr.op === ">=" || expr.op === "<=";
7810
+ }
7811
+ function isTargetIdField(field, options) {
7812
+ if (field.type !== "FIELD" || field.field !== "$id") return false;
7813
+ const targetAlias = options.tableAlias ?? null;
7814
+ if (field.tableAlias === targetAlias) return true;
7815
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
7816
+ }
7817
+ function isTargetField(field, options) {
7818
+ const targetAlias = options.tableAlias ?? null;
7819
+ if (field.tableAlias === targetAlias) return true;
7820
+ return options.allowUnqualifiedFields === true && field.tableAlias === null;
7821
+ }
7822
+
7823
+ // src/core/optimization/klikePushdownPlan.ts
7824
+ function buildSingleTableKlikePushdownPlan(where, options = {}) {
7825
+ const extracted = where !== null && options.extractCondition !== false ? extractSafePushdownPlan(where, options) : { condition: null, relation: null };
7826
+ const { condition, relation } = extracted;
7827
+ const appliedKlikes = /* @__PURE__ */ new Set();
7828
+ collectKlikes(condition, appliedKlikes);
7829
+ const allKlikes = /* @__PURE__ */ new Set();
7830
+ collectKlikes(where, allKlikes);
7831
+ return { condition, relation, appliedKlikes, allKlikes: [...allKlikes] };
7832
+ }
7833
+ function buildKlikePushdownPlan(stmt, options = {}) {
7834
+ const joinsAreSafeForKlike = stmt.joins.every((join2) => join2.type === "INNER");
7835
+ const common = {
7836
+ allowKlike: joinsAreSafeForKlike,
7837
+ allowUnresolvedKlikeVariables: options.allowUnresolvedVariables
7838
+ };
7839
+ const mainIsPhysical = !stmt.from.subtableCode && stmt.from.cteName === null;
7840
+ const mainHasUsableAlias = stmt.joins.length === 0 || stmt.from.alias !== null;
7841
+ const mainPlan = buildSingleTableKlikePushdownPlan(stmt.where, {
7842
+ ...common,
7843
+ extractCondition: mainIsPhysical && mainHasUsableAlias && joinsAreSafeForKlike,
7844
+ tableAlias: stmt.from.alias ?? void 0,
7845
+ allowUnqualifiedFields: stmt.joins.length === 0,
7846
+ fieldTypes: options.fieldTypesByApp?.get(stmt.from.appId),
7847
+ fieldOptions: options.fieldOptionsByApp?.get(stmt.from.appId)
7848
+ });
7849
+ const mainCondition = mainPlan.condition;
7850
+ const joinConditions = /* @__PURE__ */ new Map();
7851
+ const joinRelations = /* @__PURE__ */ new Map();
7852
+ if (stmt.where !== null) {
7853
+ for (const join2 of stmt.joins) {
7854
+ if (join2.type !== "INNER" || !join2.table.alias || join2.table.subtableCode || join2.table.cteName !== null) continue;
7855
+ const extracted = extractSafePushdownPlan(stmt.where, {
7856
+ ...common,
7857
+ tableAlias: join2.table.alias,
7858
+ fieldTypes: options.fieldTypesByApp?.get(join2.table.appId),
7859
+ fieldOptions: options.fieldOptionsByApp?.get(join2.table.appId)
7860
+ });
7861
+ if (extracted.condition !== null && extracted.relation !== null) {
7862
+ joinConditions.set(join2.table.alias, extracted.condition);
7863
+ joinRelations.set(join2.table.alias, extracted.relation);
7864
+ }
7865
+ }
7866
+ }
7867
+ const appliedKlikes = new Set(mainPlan.appliedKlikes);
7868
+ for (const condition of joinConditions.values()) collectKlikes(condition, appliedKlikes);
7869
+ return {
7870
+ mainCondition,
7871
+ mainRelation: mainPlan.relation,
7872
+ joinConditions,
7873
+ joinRelations,
7874
+ appliedKlikes,
7875
+ allKlikes: mainPlan.allKlikes
7876
+ };
7877
+ }
7878
+ function unappliedKlikes(plan) {
7879
+ return plan.allKlikes.filter((expr) => !plan.appliedKlikes.has(expr));
7880
+ }
7881
+ function collectKlikes(where, out) {
7882
+ if (where === null) return;
7883
+ if (isKlike(where)) {
7884
+ out.add(where);
7885
+ return;
7886
+ }
7887
+ switch (where.type) {
7888
+ case "LOGICAL":
7889
+ collectKlikes(where.left, out);
7890
+ collectKlikes(where.right, out);
7891
+ return;
7892
+ case "NOT":
7893
+ case "GROUP":
7894
+ collectKlikes(where.expr, out);
7895
+ return;
7896
+ case "BINARY":
7897
+ case "NULL_CHECK":
7898
+ case "EXISTS":
7899
+ case "BOOLEAN":
7900
+ return;
7901
+ }
7902
+ }
7903
+
7904
+ // src/core/optimization/relativeDateFullScanExactPlan.ts
7905
+ function buildRelativeDateFullScanExactPlan(input) {
7906
+ const {
7907
+ select,
7908
+ selectMode,
7909
+ capability,
7910
+ context,
7911
+ serializedWholeWhere,
7912
+ relativeFunctionNames
7913
+ } = input;
7914
+ if (select.where === null) return null;
7915
+ if (select.from.appId <= 0 || select.from.cteName !== null) return null;
7916
+ if (select.from.subtableCode) return null;
7917
+ if (select.joins.length > 0) return null;
7918
+ if (!context.allowFullScanExact) return null;
7919
+ if (select.orderMode === "KINTONE_NATIVE") return null;
7920
+ const hasCanonicalOrder2 = select.orderMode === "CANONICAL" && select.orderBy.length > 0;
7921
+ if (selectMode !== "FULL_SCAN" && !hasCanonicalOrder2) return null;
7922
+ if (capability.capability !== "EXACT_PUSHDOWN") return null;
7923
+ const occurrences = serverOnlyFunctionOccurrencesInWhere(select.where);
7924
+ if (occurrences.length === 0) return null;
7925
+ if (!sameOccurrenceList(occurrences, relativeFunctionNames)) return null;
7926
+ if (serializedWholeWhere === null || !serializedMultisetContains(serializedWholeWhere, occurrences)) {
7927
+ return null;
7928
+ }
7929
+ const prefilterPlan = {
7930
+ prefilterWhere: select.where,
7931
+ residualWhere: null,
7932
+ exactRelativeLeaves: collectExactServerFunctionLeaves(select.where),
7933
+ relativeFunctionNames: new Set(occurrences),
7934
+ appliedKlikes: /* @__PURE__ */ new Set(),
7935
+ capability: capability.capability,
7936
+ reasons: capability.reasons
7937
+ };
7938
+ const plan = {
7939
+ allowForm: "FULL_SCAN_EXACT",
7940
+ clientWhereEvaluation: false,
7941
+ serializedWholeWhere,
7942
+ prefilterPlan
7943
+ };
7944
+ assertRelativeDateFullScanExactPlan(plan, input, occurrences);
7945
+ return plan;
7946
+ }
7947
+ function assertRelativeDateFullScanExactPlan(plan, input, occurrences) {
7948
+ if (plan.allowForm !== "FULL_SCAN_EXACT") {
7949
+ throw new Error("FULL_SCAN_EXACT invariant: allowForm");
7950
+ }
7951
+ if (plan.clientWhereEvaluation !== false) {
7952
+ throw new Error("FULL_SCAN_EXACT invariant: clientWhereEvaluation");
7953
+ }
7954
+ if (input.capability.capability !== "EXACT_PUSHDOWN") {
7955
+ throw new Error("FULL_SCAN_EXACT invariant: capability");
7956
+ }
7957
+ if (input.select.where === null || plan.prefilterPlan.prefilterWhere !== input.select.where) {
7958
+ throw new Error("FULL_SCAN_EXACT invariant: whole WHERE identity");
7959
+ }
7960
+ if (plan.prefilterPlan.residualWhere !== null) {
7961
+ throw new Error("FULL_SCAN_EXACT invariant: residualWhere");
7962
+ }
7963
+ if (plan.prefilterPlan.capability !== "EXACT_PUSHDOWN") {
7964
+ throw new Error("FULL_SCAN_EXACT invariant: transport capability");
7965
+ }
7966
+ if (!serializedMultisetContains(plan.serializedWholeWhere, occurrences)) {
7967
+ throw new Error("FULL_SCAN_EXACT invariant: relative occurrence serialization");
7968
+ }
7969
+ }
7970
+ function serverOnlyFunctionOccurrencesInWhere(where) {
7971
+ const names = [];
7972
+ const visit = (node) => {
7973
+ if (Array.isArray(node)) {
7974
+ node.forEach(visit);
7975
+ return;
7976
+ }
7977
+ if (node === null || typeof node !== "object") return;
7978
+ const value = node;
7979
+ if (value["type"] === "SELECT") return;
7980
+ if (value["type"] === "KINTONE_FUNC" && typeof value["name"] === "string" && isServerOnlyWhereFunctionName(value["name"])) {
7981
+ names.push(value["name"]);
7982
+ return;
7983
+ }
7984
+ Object.values(value).forEach(visit);
7985
+ };
7986
+ visit(where);
7987
+ return names;
7988
+ }
7989
+ function collectExactServerFunctionLeaves(where) {
7990
+ const leaves = [];
7991
+ const visit = (node) => {
7992
+ switch (node.type) {
7993
+ case "BINARY":
7994
+ if (node.right.type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.name) || node.right.type === "IN_LIST" && node.right.values.length === 1 && node.right.values[0].type === "KINTONE_FUNC" && isServerOnlyWhereFunctionName(node.right.values[0].name)) {
7995
+ leaves.push(node);
7996
+ }
7997
+ return;
7998
+ case "LOGICAL":
7999
+ visit(node.left);
8000
+ visit(node.right);
8001
+ return;
8002
+ case "NOT":
8003
+ case "GROUP":
8004
+ visit(node.expr);
8005
+ return;
8006
+ case "EXISTS":
8007
+ case "NULL_CHECK":
8008
+ case "BOOLEAN":
8009
+ return;
8010
+ }
8011
+ };
8012
+ visit(where);
8013
+ return leaves;
8014
+ }
8015
+ function sameOccurrenceList(actual, expected) {
8016
+ return actual.length === expected.length && actual.every((name, index) => name === expected[index]);
8017
+ }
8018
+ function serializedMultisetContains(query, expectedNames) {
8019
+ const expected = /* @__PURE__ */ new Map();
8020
+ for (const name of expectedNames) {
8021
+ if (!isServerOnlyWhereFunctionName(name)) return false;
8022
+ expected.set(name, (expected.get(name) ?? 0) + 1);
8023
+ }
8024
+ for (const [name, count] of expected) {
8025
+ const matches = query.match(new RegExp(`\\b${name}\\s*\\(`, "g"));
8026
+ if ((matches?.length ?? 0) < count) return false;
8027
+ }
8028
+ return true;
8029
+ }
8030
+
8031
+ // src/core/klikeValidation.ts
8032
+ var KlikeValidationError = class extends Error {
8033
+ constructor(message) {
8034
+ super(`ArgumentError: ${message}`);
8035
+ this.name = "ArgumentError";
8036
+ }
8037
+ };
8038
+ function validateKlikeStatement(stmt) {
8039
+ validateStatement(stmt);
8040
+ }
8041
+ function validateKlikePushdownPlan(plan) {
8042
+ if (unappliedKlikes(plan).length > 0) {
8043
+ throw new KlikeValidationError(
8044
+ "FULL_SCAN \u306E KLIKE / NOT KLIKE \u3092\u5B89\u5168\u306B\u62BC\u3057\u4E0B\u3052\u3089\u308C\u307E\u305B\u3093\u3002OR / NOT \u914D\u4E0B\u3001CTE\u30FB\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3001LEFT / RIGHT JOIN \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044"
8045
+ );
8046
+ }
8047
+ }
8048
+ function validateStatement(stmt) {
8049
+ switch (stmt.type) {
8050
+ case "SELECT":
8051
+ validateSelect(stmt);
8052
+ return;
8053
+ case "UNION":
8054
+ validateUnion(stmt);
8055
+ return;
8056
+ case "WITH":
8057
+ validateWith(stmt);
8058
+ return;
8059
+ case "EXPLAIN":
8060
+ validateStatement(stmt.query);
8061
+ return;
8062
+ case "CREATE_TEMP_TABLE":
8063
+ validateSelectLike(stmt.query);
8064
+ return;
8065
+ case "SET_VARIABLE":
8066
+ case "DECLARE_VARIABLE":
8067
+ case "ASSERT":
8068
+ validateNestedSelects(stmt);
8069
+ return;
8070
+ case "UPDATE":
8071
+ if (stmt.applyBlocks?.length && !isSinglePositiveRecordIdWhere(stmt.where) && whereHasKlike(stmt.where)) {
8072
+ validateKlikeWhereExpressions(stmt.where);
8073
+ validateNestedSelects(stmt);
8074
+ return;
8075
+ }
8076
+ if (containsKlike(stmt) && stmt.subtableCode) {
8077
+ throw new KlikeValidationError(
8078
+ "KLIKE / NOT KLIKE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306E WHERE \u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093"
8079
+ );
8080
+ }
8081
+ if (!stmt.applyBlocks?.length && !stmt.subtableCode && whereHasKlike(stmt.where) && !containsKlikeOutsideWhereAndNestedSelects(stmt, stmt.where)) {
8082
+ validateKlikeWhereExpressions(stmt.where);
8083
+ validateNestedSelects(stmt);
8084
+ return;
8085
+ }
8086
+ if (containsKlike(stmt)) {
7378
8087
  throw new KlikeValidationError(
7379
8088
  "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"
7380
8089
  );
@@ -8250,72 +8959,6 @@ function analyzeBatch(statements) {
8250
8959
  };
8251
8960
  }
8252
8961
 
8253
- // src/core/fieldSemantics.ts
8254
- var STRING_FIELD_TYPES = /* @__PURE__ */ new Set([
8255
- "SINGLE_LINE_TEXT",
8256
- "MULTI_LINE_TEXT",
8257
- "RICH_TEXT",
8258
- "LINK",
8259
- "DATE",
8260
- "TIME",
8261
- "DATETIME",
8262
- "CREATED_TIME",
8263
- "UPDATED_TIME",
8264
- "CREATOR",
8265
- "MODIFIER"
8266
- ]);
8267
- var OPTION_FIELD_TYPES = /* @__PURE__ */ new Set([
8268
- "DROP_DOWN",
8269
- "RADIO_BUTTON",
8270
- "CHECK_BOX",
8271
- "MULTI_SELECT",
8272
- "STATUS"
8273
- ]);
8274
- function resolveFieldSemantics(source) {
8275
- let compareMode;
8276
- if (source.fieldType === "RECORD_NUMBER" || source.fieldType === "__ID__") {
8277
- compareMode = "recordNumber";
8278
- } else if (source.fieldType === "NUMBER") {
8279
- compareMode = "number";
8280
- } else if (source.fieldType === "CALC") {
8281
- compareMode = source.sortKind === "number" ? "number" : "string";
8282
- } else if (OPTION_FIELD_TYPES.has(source.fieldType)) {
8283
- compareMode = "option";
8284
- } else if (STRING_FIELD_TYPES.has(source.fieldType)) {
8285
- compareMode = "string";
8286
- } else {
8287
- compareMode = "unsupported";
8288
- }
8289
- const optionOrder = source.optionOrder ? new Map(Object.entries(source.optionOrder)) : void 0;
8290
- return {
8291
- fieldType: source.fieldType,
8292
- compareMode,
8293
- inSubtable: source.inSubtable === true,
8294
- requiresCollectionOperators: source.inSubtable === true || source.requiresCollectionOperators === true,
8295
- ...optionOrder && optionOrder.size > 0 ? { optionOrder } : {}
8296
- };
8297
- }
8298
- function syntheticSemantics(compareMode, fieldType = compareMode === "number" ? "KSQL_NUMBER" : "KSQL_STRING") {
8299
- return { fieldType, compareMode, inSubtable: false, requiresCollectionOperators: false };
8300
- }
8301
- function withFieldSemanticSource(semantics, appId, fieldCode) {
8302
- return { ...semantics, source: { appId, fieldCode } };
8303
- }
8304
- function fieldSemanticsEqual(left, right) {
8305
- if (left === right) return true;
8306
- if (!left || !right) return false;
8307
- if (left.fieldType !== right.fieldType || left.compareMode !== right.compareMode || left.inSubtable !== right.inSubtable || left.requiresCollectionOperators !== right.requiresCollectionOperators) return false;
8308
- if (left.source?.appId !== right.source?.appId || left.source?.fieldCode !== right.source?.fieldCode) return false;
8309
- const a = left.optionOrder;
8310
- const b = right.optionOrder;
8311
- if (a === b) return true;
8312
- if (!a || !b || a.size !== b.size) return false;
8313
- for (const [key, value] of a) {
8314
- if (b.get(key) !== value) return false;
8315
- }
8316
- return true;
8317
- }
8318
-
8319
8962
  // src/core/scalarCompare.ts
8320
8963
  function compareCodePointStrings(left, right) {
8321
8964
  const a = left[Symbol.iterator]();
@@ -10972,231 +11615,23 @@ function resolveExistingValidationTargets(stmt, fieldInfos) {
10972
11615
  children.forEach((child) => add(child, code));
10973
11616
  continue;
10974
11617
  }
10975
- if (top) {
10976
- add(top);
10977
- continue;
10978
- }
10979
- if ([...childrenByTable.values()].some((children) => children.some((field) => field.code === code))) {
10980
- throw new Error(`ArgumentError: VALIDATE \u306E\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${code} \u306F\u6240\u6709\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u3092\u542B\u3080 T(${code}) \u5F62\u5F0F\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
10981
- }
10982
- throw new Error(`ArgumentError: VALIDATE \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${code} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`);
10983
- }
10984
- return result;
10985
- }
10986
- function renderExistingValidationValue(raw, fieldType) {
10987
- return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
10988
- }
10989
-
10990
- // src/core/postImageValidation.ts
10991
- var NON_AUDIT_SYSTEM_TYPES = /* @__PURE__ */ new Set([
10992
- "CALC",
10993
- "RECORD_NUMBER",
10994
- "CREATOR",
10995
- "CREATED_TIME",
10996
- "MODIFIER",
10997
- "UPDATED_TIME",
10998
- "STATUS",
10999
- "STATUS_ASSIGNEE",
11000
- "CATEGORY",
11001
- "REFERENCE_TABLE"
11002
- ]);
11003
- var POST_IMAGE_VALIDATION_SUFFIX_COLUMNS = VALIDATION_META_COLUMNS;
11004
- function buildPostImageFieldIndex(fieldInfos, payloadFields = ["$id"]) {
11005
- const metadata = buildValidationFieldMetadataIndex(fieldInfos);
11006
- const topLevel = metadata.topLevel.filter((field) => field.fieldType !== "SUBTABLE" && field.fieldType !== "FILE" && !NON_AUDIT_SYSTEM_TYPES.has(field.fieldType));
11007
- const subtables = /* @__PURE__ */ new Map();
11008
- for (const [tableCode, fields] of metadata.childrenByTable) {
11009
- subtables.set(tableCode, fields.filter((field) => field.fieldType !== "FILE"));
11010
- }
11011
- return {
11012
- topLevel,
11013
- subtables,
11014
- payloadFields: ["$id", ...new Set(payloadFields.filter((field) => field !== "$id"))]
11015
- };
11016
- }
11017
- function postImageNeedsNumberPrecision(record, fieldIndex) {
11018
- if (fieldIndex.topLevel.some((field) => field.fieldType === "NUMBER" && field.code in record)) return true;
11019
- for (const [tableCode, children] of fieldIndex.subtables) {
11020
- if (!children.some((field) => field.fieldType === "NUMBER")) continue;
11021
- const rows = record[tableCode]?.value;
11022
- if (!Array.isArray(rows)) continue;
11023
- for (const row of rows) {
11024
- const values = row?.value;
11025
- if (children.some((field) => field.fieldType === "NUMBER" && !!values && field.code in values)) return true;
11026
- }
11027
- }
11028
- return false;
11029
- }
11030
- function validatePostImage(record, fieldIndex, numberPrecision, statementNumber, parentRowNumber = 1, operation = "UPDATE") {
11031
- const normalizedRecord = cloneRecord(record);
11032
- const errors = [];
11033
- const invalidRowNumbers = /* @__PURE__ */ new Set();
11034
- const columns = [...fieldIndex.payloadFields, ...POST_IMAGE_VALIDATION_SUFFIX_COLUMNS];
11035
- const parentId = renderValidationValue(record["$id"]?.value);
11036
- const appendError = (field, raw, validation, locator) => {
11037
- invalidRowNumbers.add(parentRowNumber);
11038
- const row = {};
11039
- for (const code of fieldIndex.payloadFields) {
11040
- row[code] = code === "$id" ? parentId : renderValidationValue(record[code]?.value);
11041
- }
11042
- row["$err_statement"] = String(statementNumber);
11043
- row["$err_operation"] = operation;
11044
- row["$err_row"] = String(parentRowNumber);
11045
- row["$err_field"] = field.code;
11046
- row["$err_code"] = validation.code;
11047
- row["$err_message"] = validation.message;
11048
- row["$err_value"] = renderExistingValidationValue(raw, field.fieldType);
11049
- row["$err_subtable"] = locator?.subtable ?? "";
11050
- row["$err_subrow"] = locator ? String(locator.subrow) : "";
11051
- row["$err_subrow_id"] = locator?.subrowId ?? "";
11052
- errors.push(row);
11053
- };
11054
- for (const field of fieldIndex.topLevel) {
11055
- const raw = record[field.code]?.value;
11056
- const result = validateAndNormalizeDmlValue(raw, field, numberPrecision);
11057
- if (!result.ok) appendError(field, raw, result);
11058
- else normalizedRecord[field.code] = { value: preserveCodeObjects(raw, field.fieldType, result.value) };
11059
- }
11060
- for (const [tableCode, children] of fieldIndex.subtables) {
11061
- const sourceRows2 = record[tableCode]?.value;
11062
- if (!Array.isArray(sourceRows2)) continue;
11063
- const normalizedRows = normalizedRecord[tableCode]?.value;
11064
- for (let rowIndex = 0; rowIndex < sourceRows2.length; rowIndex++) {
11065
- const sourceRow = sourceRows2[rowIndex];
11066
- const normalizedRow = normalizedRows[rowIndex];
11067
- for (const field of children) {
11068
- const raw = sourceRow.value?.[field.code]?.value;
11069
- const result = validateAndNormalizeDmlValue(raw, field, numberPrecision);
11070
- if (!result.ok) appendError(field, raw, result, buildValidationCellLocator(tableCode, rowIndex, sourceRow));
11071
- else {
11072
- normalizedRow.value ??= {};
11073
- normalizedRow.value[field.code] = { value: preserveCodeObjects(raw, field.fieldType, result.value) };
11074
- }
11075
- }
11076
- }
11077
- }
11078
- return {
11079
- normalizedRecord,
11080
- errors,
11081
- columns,
11082
- invalidRows: invalidRowNumbers.size,
11083
- invalidRowNumbers,
11084
- errorCount: errors.length
11085
- };
11086
- }
11087
- function preserveCodeObjects(raw, fieldType, normalized) {
11088
- return ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(fieldType) && Array.isArray(raw) && raw.every((item) => typeof item === "object" && item !== null && "code" in item) ? raw : normalized;
11089
- }
11090
- function cloneRecord(record) {
11091
- const clone = {};
11092
- for (const [code, cell] of Object.entries(record)) {
11093
- const value = cell?.value;
11094
- clone[code] = { value: Array.isArray(value) ? value.map((item) => {
11095
- if (item === null || typeof item !== "object") return item;
11096
- const row = item;
11097
- if (!row.value) return { ...row };
11098
- return {
11099
- ...row,
11100
- value: Object.fromEntries(Object.entries(row.value).map(([field, child]) => [field, { ...child }]))
11101
- };
11102
- }) : value };
11103
- }
11104
- return clone;
11105
- }
11106
-
11107
- // src/core/applyPatchPrepare.ts
11108
- async function prepareApplyPatchWrite(input) {
11109
- const {
11110
- statement,
11111
- snapshots,
11112
- fieldInfos,
11113
- metadata,
11114
- dmlMaxRows,
11115
- dmlMaxSubtableRows,
11116
- statementNumber = 1
11117
- } = input;
11118
- assertPositiveLimit(dmlMaxRows, "dmlMaxRows");
11119
- assertPositiveLimit(dmlMaxSubtableRows, "dmlMaxSubtableRows");
11120
- for (const snapshot of snapshots) requireRevision(snapshot);
11121
- const rawPlans = buildApplyPatchPlans(statement, snapshots, fieldInfos, metadata);
11122
- const fieldIndex = buildPostImageFieldIndex(
11123
- fieldInfos,
11124
- statement.assignments.map((assignment) => assignment.field)
11125
- );
11126
- const needsNumberPrecision = rawPlans.some(
11127
- (plan) => postImageNeedsNumberPrecision(plan.postImage, fieldIndex)
11128
- );
11129
- if (needsNumberPrecision && !input.loadNumberPrecision) {
11130
- throw new Error("InternalError: APPLY number precision loader is required for NUMBER post-images.");
11131
- }
11132
- const numberPrecision = needsNumberPrecision ? await input.loadNumberPrecision() : void 0;
11133
- const validationResults = rawPlans.map(
11134
- (plan, index) => validatePostImage(
11135
- plan.postImage,
11136
- fieldIndex,
11137
- numberPrecision,
11138
- statementNumber,
11139
- input.parentRowNumbers?.[index] ?? index + 1
11140
- )
11141
- );
11142
- if (!statement.validateOnly) {
11143
- const errors = validationResults.flatMap((validation) => validation.errors);
11144
- if (errors.length > 0) {
11145
- throw new Error(`ArgumentError: APPLY post-image validation failed: ${JSON.stringify({
11146
- columns: validationResults[0]?.columns ?? [],
11147
- errors
11148
- })}`);
11149
- }
11150
- }
11151
- const parentRows = rawPlans.length;
11152
- const subtableRows = rawPlans.reduce((sum, plan) => sum + plan.changedSubtableRows, 0);
11153
- const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11154
- if (!statement.validateOnly && parentRows > dmlMaxRows) {
11155
- throw new Error(`ArgumentError: APPLY parent rows (${parentRows}) exceed dmlMaxRows (${dmlMaxRows}).`);
11156
- }
11157
- if (!statement.validateOnly && subtableRows > dmlMaxSubtableRows) {
11158
- throw new Error(
11159
- `ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`
11160
- );
11161
- }
11162
- const plans = rawPlans.map(
11163
- (plan, index) => normalizeApplyPatchPlan(plan, validationResults[index].normalizedRecord)
11164
- );
11165
- const records = plans.flatMap((plan) => applyPatchPlanToKintone(plan).records);
11166
- const validations = validationResults.map((validation) => ({
11167
- errors: validation.errors,
11168
- columns: validation.columns,
11169
- invalidRows: validation.invalidRows,
11170
- errorCount: validation.errorCount
11171
- }));
11172
- return deepFreeze({
11173
- plans,
11174
- records,
11175
- validations,
11176
- guards: {
11177
- revisionRequired: true,
11178
- parentRows,
11179
- dmlMaxRows,
11180
- subtableRows,
11181
- dmlMaxSubtableRows,
11182
- wouldExceed
11618
+ if (top) {
11619
+ add(top);
11620
+ continue;
11183
11621
  }
11184
- });
11185
- }
11186
- function assertPositiveLimit(value, name) {
11187
- if (!Number.isSafeInteger(value) || value <= 0) {
11188
- throw new Error(`ArgumentError: ${name} must be a positive safe integer.`);
11622
+ if ([...childrenByTable.values()].some((children) => children.some((field) => field.code === code))) {
11623
+ throw new Error(`ArgumentError: VALIDATE \u306E\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${code} \u306F\u6240\u6709\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u3092\u542B\u3080 T(${code}) \u5F62\u5F0F\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
11624
+ }
11625
+ throw new Error(`ArgumentError: VALIDATE \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${code} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`);
11189
11626
  }
11627
+ return result;
11190
11628
  }
11191
- function deepFreeze(value, seen = /* @__PURE__ */ new Set()) {
11192
- if (value === null || typeof value !== "object" || seen.has(value)) return value;
11193
- seen.add(value);
11194
- for (const child of Object.values(value)) deepFreeze(child, seen);
11195
- return Object.freeze(value);
11629
+ function renderExistingValidationValue(raw, fieldType) {
11630
+ return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
11196
11631
  }
11197
11632
 
11198
- // src/core/applyInsertPrepare.ts
11199
- var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
11633
+ // src/core/postImageValidation.ts
11634
+ var NON_AUDIT_SYSTEM_TYPES = /* @__PURE__ */ new Set([
11200
11635
  "CALC",
11201
11636
  "RECORD_NUMBER",
11202
11637
  "CREATOR",
@@ -11208,1362 +11643,1046 @@ var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
11208
11643
  "CATEGORY",
11209
11644
  "REFERENCE_TABLE"
11210
11645
  ]);
11211
- function resolveApplyInsertMetadata(statement, fieldInfos) {
11212
- if (new Set(statement.fields).size !== statement.fields.length) {
11213
- return argument4("DML target fields contain duplicates.");
11214
- }
11215
- const fieldsByCode = new Map(fieldInfos.map((field) => [field.code, field]));
11216
- for (const code of statement.fields) assertWritable(code, null, fieldsByCode);
11217
- const targetTables = /* @__PURE__ */ new Map();
11218
- const childrenByTable = /* @__PURE__ */ new Map();
11219
- for (const block of statement.applyBlocks ?? []) {
11220
- if (targetTables.has(block.field)) return argument4(`APPLY has more than one block for table ${block.field}.`);
11221
- const table = fieldInfos.find((field) => field.code === block.field && !field.inSubtable);
11222
- if (!table || table.fieldType !== "SUBTABLE") return argument4(`APPLY target ${block.field} is not a SUBTABLE.`);
11223
- const children = new Map(fieldInfos.filter((field) => field.inSubtable && field.subtableCode === block.field).map((field) => [field.code, field]));
11224
- targetTables.set(block.field, table);
11225
- childrenByTable.set(block.field, children);
11226
- for (const operation of block.operations) {
11227
- if (operation.kind !== "APPEND") return argument4(`APPLY INSERT supports APPEND only (${operation.kind}).`);
11228
- const specified = /* @__PURE__ */ new Set();
11229
- for (const code of operation.fields) {
11230
- if (specified.has(code)) return argument4(`APPLY APPEND specifies child ${code} more than once.`);
11231
- specified.add(code);
11232
- assertWritable(code, block.field, fieldsByCode, children);
11233
- }
11234
- for (const row of operation.values) {
11235
- if (row.length !== operation.fields.length) {
11236
- return argument4(`APPLY APPEND for ${block.field} has ${row.length} values for ${operation.fields.length} fields.`);
11237
- }
11238
- }
11239
- }
11240
- }
11241
- return { targetTables, targetMultiValueFields: /* @__PURE__ */ new Map(), childrenByTable, fieldsByCode };
11242
- }
11243
- function buildApplyInsertCandidates(statement, fieldInfos, metadata = resolveApplyInsertMetadata(statement, fieldInfos), parentRowNumbers) {
11244
- const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
11245
- const parentRecords = insertToPostBatches(statement, fieldTypes).flatMap((batch) => batch.records);
11246
- if (parentRowNumbers && parentRowNumbers.length !== parentRecords.length) {
11247
- throw new Error("InternalError: APPLY create parent row number count differs from VALUES rows.");
11646
+ var POST_IMAGE_VALIDATION_SUFFIX_COLUMNS = VALIDATION_META_COLUMNS;
11647
+ function buildPostImageFieldIndex(fieldInfos, payloadFields = ["$id"]) {
11648
+ const metadata = buildValidationFieldMetadataIndex(fieldInfos);
11649
+ const topLevel = metadata.topLevel.filter((field) => field.fieldType !== "SUBTABLE" && field.fieldType !== "FILE" && !NON_AUDIT_SYSTEM_TYPES.has(field.fieldType));
11650
+ const subtables = /* @__PURE__ */ new Map();
11651
+ for (const [tableCode, fields] of metadata.childrenByTable) {
11652
+ subtables.set(tableCode, fields.filter((field) => field.fieldType !== "FILE"));
11248
11653
  }
11249
- const templates = (statement.applyBlocks ?? []).map((block) => {
11250
- const children = metadata.childrenByTable.get(block.field);
11251
- const rows = block.operations.flatMap(
11252
- (operation) => buildApplyAppendRows(operation, children, block.field)
11253
- );
11254
- return { table: block.field, rows, addedRows: rows.length };
11255
- });
11256
- return parentRecords.map((parentRecord, index) => {
11257
- const postImage = {};
11258
- for (const field of fieldInfos) {
11259
- if (field.inSubtable || field.fieldType === "SUBTABLE" || field.fieldType === "FILE" || field.writable === false || NON_WRITABLE_FIELD_TYPES.has(field.fieldType)) continue;
11260
- postImage[field.code] = parentRecord[field.code] ?? { value: appendDefaultValue(field) };
11261
- }
11262
- const record = { ...parentRecord };
11263
- for (const template of templates) {
11264
- const rows = template.rows.map((row) => ({ value: row.value }));
11265
- postImage[template.table] = { value: rows };
11266
- record[template.table] = { value: rows };
11267
- }
11268
- return {
11269
- parentRowNumber: parentRowNumbers?.[index] ?? index + 1,
11270
- tables: templates,
11271
- postImage,
11272
- record
11273
- };
11274
- });
11654
+ return {
11655
+ topLevel,
11656
+ subtables,
11657
+ payloadFields: ["$id", ...new Set(payloadFields.filter((field) => field !== "$id"))]
11658
+ };
11275
11659
  }
11276
- async function prepareApplyInsert(input) {
11277
- const {
11278
- statement,
11279
- fieldInfos,
11280
- dmlMaxRows,
11281
- dmlMaxSubtableRows,
11282
- statementNumber = 1
11283
- } = input;
11284
- assertPositiveLimit2(dmlMaxRows, "dmlMaxRows");
11285
- assertPositiveLimit2(dmlMaxSubtableRows, "dmlMaxSubtableRows");
11286
- const metadata = input.metadata ?? resolveApplyInsertMetadata(statement, fieldInfos);
11287
- const rawCandidates = buildApplyInsertCandidates(statement, fieldInfos, metadata, input.parentRowNumbers);
11288
- const creatableFieldInfos = fieldInfos.filter(
11289
- (field) => field.fieldType === "SUBTABLE" || field.fieldType !== "FILE" && field.writable !== false && !NON_WRITABLE_FIELD_TYPES.has(field.fieldType)
11290
- );
11291
- const fieldIndex = buildPostImageFieldIndex(creatableFieldInfos, statement.fields);
11292
- const needsNumberPrecision = rawCandidates.some(
11293
- (candidate) => postImageNeedsNumberPrecision(candidate.postImage, fieldIndex)
11294
- );
11295
- if (needsNumberPrecision && !input.loadNumberPrecision) {
11296
- throw new Error("InternalError: APPLY number precision loader is required for NUMBER post-images.");
11297
- }
11298
- const numberPrecision = needsNumberPrecision ? await input.loadNumberPrecision() : void 0;
11299
- const results = rawCandidates.map((candidate) => validatePostImage(
11300
- candidate.postImage,
11301
- fieldIndex,
11302
- numberPrecision,
11303
- statementNumber,
11304
- candidate.parentRowNumber,
11305
- "INSERT"
11306
- ));
11307
- if (!statement.validateOnly) {
11308
- const errors = results.flatMap((result) => result.errors);
11309
- if (errors.length > 0) {
11310
- throw new Error(`ArgumentError: APPLY post-image validation failed: ${JSON.stringify({
11311
- columns: results[0]?.columns ?? [],
11312
- errors
11313
- })}`);
11314
- }
11315
- }
11316
- const parentRows = rawCandidates.length;
11317
- const subtableRows = rawCandidates.reduce(
11318
- (sum, candidate) => sum + candidate.tables.reduce((tableSum, table) => tableSum + table.addedRows, 0),
11319
- 0
11320
- );
11321
- const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11322
- if (!statement.validateOnly && parentRows > dmlMaxRows) {
11323
- throw new Error(`ArgumentError: APPLY parent rows (${parentRows}) exceed dmlMaxRows (${dmlMaxRows}).`);
11324
- }
11325
- if (!statement.validateOnly && subtableRows > dmlMaxSubtableRows) {
11326
- throw new Error(`ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`);
11327
- }
11328
- const candidates = rawCandidates.map((candidate, index) => {
11329
- const normalized = results[index].normalizedRecord;
11330
- const record = {};
11331
- for (const code of statement.fields) record[code] = normalized[code];
11332
- for (const table of candidate.tables) {
11333
- const normalizedRows = normalized[table.table]?.value;
11334
- record[table.table] = { value: table.rows.map((sourceRow, rowIndex) => ({
11335
- value: Object.fromEntries(Object.keys(sourceRow.value).map((code) => [
11336
- code,
11337
- normalizedRows[rowIndex]?.value?.[code] ?? sourceRow.value[code]
11338
- ]))
11339
- })) };
11660
+ function postImageNeedsNumberPrecision(record, fieldIndex) {
11661
+ if (fieldIndex.topLevel.some((field) => field.fieldType === "NUMBER" && field.code in record)) return true;
11662
+ for (const [tableCode, children] of fieldIndex.subtables) {
11663
+ if (!children.some((field) => field.fieldType === "NUMBER")) continue;
11664
+ const rows = record[tableCode]?.value;
11665
+ if (!Array.isArray(rows)) continue;
11666
+ for (const row of rows) {
11667
+ const values = row?.value;
11668
+ if (children.some((field) => field.fieldType === "NUMBER" && !!values && field.code in values)) return true;
11340
11669
  }
11341
- return { ...candidate, postImage: normalized, record };
11342
- });
11343
- const records = candidates.map((candidate) => candidate.record);
11344
- const batches = [];
11345
- for (let index = 0; index < records.length; index += 100) {
11346
- batches.push({ app: statement.appId, records: records.slice(index, index + 100) });
11347
11670
  }
11348
- const validations = results.map((result) => ({
11349
- errors: result.errors,
11350
- columns: result.columns,
11351
- invalidRows: result.invalidRows,
11352
- errorCount: result.errorCount
11353
- }));
11354
- return deepFreeze2({
11355
- applyBlocks: statement.applyBlocks ?? [],
11356
- candidates,
11357
- records,
11358
- batches,
11359
- validations,
11360
- guards: {
11361
- revisionRequired: false,
11362
- parentRows,
11363
- dmlMaxRows,
11364
- subtableRows,
11365
- dmlMaxSubtableRows,
11366
- wouldExceed
11367
- }
11368
- });
11671
+ return false;
11369
11672
  }
11370
- function assertWritable(code, table, fieldsByCode, children) {
11371
- if (code.startsWith("_") || code.startsWith("$")) return argument4(`APPLY assignment target ${code} is a system field.`);
11372
- const field = table === null ? [...fieldsByCode.values()].find((candidate) => candidate.code === code && !candidate.inSubtable) : children?.get(code);
11373
- if (!field) {
11374
- const elsewhere = fieldsByCode.get(code);
11375
- if (table !== null && elsewhere?.inSubtable) return argument4(`APPLY child ${code} does not belong to subtable ${table}.`);
11376
- return argument4(`APPLY ${table === null ? "parent field" : "child"} ${code} does not exist.`);
11377
- }
11378
- if (field.fieldType === "SUBTABLE" || field.fieldType === "FILE" || field.writable === false || NON_WRITABLE_FIELD_TYPES.has(field.fieldType)) {
11379
- return argument4(`APPLY assignment target ${code} is not writable (${field.fieldType}).`);
11673
+ function validatePostImage(record, fieldIndex, numberPrecision, statementNumber, parentRowNumber = 1, operation = "UPDATE") {
11674
+ const normalizedRecord = cloneRecord(record);
11675
+ const errors = [];
11676
+ const invalidRowNumbers = /* @__PURE__ */ new Set();
11677
+ const columns = [...fieldIndex.payloadFields, ...POST_IMAGE_VALIDATION_SUFFIX_COLUMNS];
11678
+ const parentId = renderValidationValue(record["$id"]?.value);
11679
+ const appendError = (field, raw, validation, locator) => {
11680
+ invalidRowNumbers.add(parentRowNumber);
11681
+ const row = {};
11682
+ for (const code of fieldIndex.payloadFields) {
11683
+ row[code] = code === "$id" ? parentId : renderValidationValue(record[code]?.value);
11684
+ }
11685
+ row["$err_statement"] = String(statementNumber);
11686
+ row["$err_operation"] = operation;
11687
+ row["$err_row"] = String(parentRowNumber);
11688
+ row["$err_field"] = field.code;
11689
+ row["$err_code"] = validation.code;
11690
+ row["$err_message"] = validation.message;
11691
+ row["$err_value"] = renderExistingValidationValue(raw, field.fieldType);
11692
+ row["$err_subtable"] = locator?.subtable ?? "";
11693
+ row["$err_subrow"] = locator ? String(locator.subrow) : "";
11694
+ row["$err_subrow_id"] = locator?.subrowId ?? "";
11695
+ errors.push(row);
11696
+ };
11697
+ for (const field of fieldIndex.topLevel) {
11698
+ const raw = record[field.code]?.value;
11699
+ const result = validateAndNormalizeDmlValue(raw, field, numberPrecision);
11700
+ if (!result.ok) appendError(field, raw, result);
11701
+ else normalizedRecord[field.code] = { value: preserveCodeObjects(raw, field.fieldType, result.value) };
11380
11702
  }
11381
- }
11382
- function assertPositiveLimit2(value, name) {
11383
- if (!Number.isSafeInteger(value) || value <= 0) {
11384
- throw new Error(`ArgumentError: ${name} must be a positive safe integer.`);
11703
+ for (const [tableCode, children] of fieldIndex.subtables) {
11704
+ const sourceRows2 = record[tableCode]?.value;
11705
+ if (!Array.isArray(sourceRows2)) continue;
11706
+ const normalizedRows = normalizedRecord[tableCode]?.value;
11707
+ for (let rowIndex = 0; rowIndex < sourceRows2.length; rowIndex++) {
11708
+ const sourceRow = sourceRows2[rowIndex];
11709
+ const normalizedRow = normalizedRows[rowIndex];
11710
+ for (const field of children) {
11711
+ const raw = sourceRow.value?.[field.code]?.value;
11712
+ const result = validateAndNormalizeDmlValue(raw, field, numberPrecision);
11713
+ if (!result.ok) appendError(field, raw, result, buildValidationCellLocator(tableCode, rowIndex, sourceRow));
11714
+ else {
11715
+ normalizedRow.value ??= {};
11716
+ normalizedRow.value[field.code] = { value: preserveCodeObjects(raw, field.fieldType, result.value) };
11717
+ }
11718
+ }
11719
+ }
11385
11720
  }
11721
+ return {
11722
+ normalizedRecord,
11723
+ errors,
11724
+ columns,
11725
+ invalidRows: invalidRowNumbers.size,
11726
+ invalidRowNumbers,
11727
+ errorCount: errors.length
11728
+ };
11386
11729
  }
11387
- function argument4(message) {
11388
- throw new Error(`ArgumentError: ${message}`);
11730
+ function preserveCodeObjects(raw, fieldType, normalized) {
11731
+ return ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(fieldType) && Array.isArray(raw) && raw.every((item) => typeof item === "object" && item !== null && "code" in item) ? raw : normalized;
11389
11732
  }
11390
- function deepFreeze2(value, seen = /* @__PURE__ */ new Set()) {
11391
- if (value === null || typeof value !== "object" || seen.has(value)) return value;
11392
- seen.add(value);
11393
- for (const child of Object.values(value)) deepFreeze2(child, seen);
11394
- return Object.freeze(value);
11733
+ function cloneRecord(record) {
11734
+ const clone = {};
11735
+ for (const [code, cell] of Object.entries(record)) {
11736
+ const value = cell?.value;
11737
+ clone[code] = { value: Array.isArray(value) ? value.map((item) => {
11738
+ if (item === null || typeof item !== "object") return item;
11739
+ const row = item;
11740
+ if (!row.value) return { ...row };
11741
+ return {
11742
+ ...row,
11743
+ value: Object.fromEntries(Object.entries(row.value).map(([field, child]) => [field, { ...child }]))
11744
+ };
11745
+ }) : value };
11746
+ }
11747
+ return clone;
11395
11748
  }
11396
11749
 
11397
- // src/core/applyUpsertPrepare.ts
11398
- async function prepareApplyUpsert(input) {
11399
- const { statement, matches, fieldInfos, dmlMaxRows, dmlMaxSubtableRows, statementNumber = 1 } = input;
11400
- assertPositiveLimit3(dmlMaxRows, "dmlMaxRows");
11401
- assertPositiveLimit3(dmlMaxSubtableRows, "dmlMaxSubtableRows");
11402
- assertMatchCoverage(statement, matches);
11403
- const createMatches = matches.filter((match) => match.targetId === void 0);
11404
- const updateMatches = matches.filter((match) => match.targetId !== void 0);
11405
- const createStatement = toInsertStatement(statement, createMatches.map((match) => match.sourceRowIndex));
11406
- const create = await prepareApplyInsert({
11407
- statement: createStatement,
11750
+ // src/core/applyPatchPrepare.ts
11751
+ async function prepareApplyPatchWrite(input) {
11752
+ const {
11753
+ statement,
11754
+ snapshots,
11408
11755
  fieldInfos,
11409
- metadata: resolveApplyInsertMetadata(createStatement, fieldInfos),
11756
+ metadata,
11410
11757
  dmlMaxRows,
11411
11758
  dmlMaxSubtableRows,
11412
- statementNumber,
11413
- parentRowNumbers: createMatches.map((match) => match.sourceRowIndex + 1),
11414
- loadNumberPrecision: input.loadNumberPrecision
11415
- });
11416
- const updateParts = [];
11417
- const seenTargets = /* @__PURE__ */ new Set();
11418
- for (const match of updateMatches) {
11419
- const targetId = match.targetId;
11420
- if (seenTargets.has(targetId)) argument5(`UPSERT APPLY resolves more than one source row to parent $id ${targetId}.`);
11421
- seenTargets.add(targetId);
11422
- if (!match.snapshot) argument5(`UPSERT APPLY snapshot for parent $id ${targetId} is missing.`);
11423
- const snapshotId = Number(match.snapshot["$id"]?.value);
11424
- if (snapshotId !== targetId) argument5(`UPSERT APPLY snapshot $id ${snapshotId} does not match target $id ${targetId}.`);
11425
- const updateStatement = toUpdateStatement(statement, match.sourceRowIndex, targetId);
11426
- const metadata = updateStatement.applyBlocks?.length ? resolveApplyPatchMetadata(updateStatement, fieldInfos) : emptyPatchMetadata(fieldInfos);
11427
- updateParts.push(await prepareApplyPatchWrite({
11428
- statement: updateStatement,
11429
- snapshots: [match.snapshot],
11430
- fieldInfos,
11431
- metadata,
11432
- dmlMaxRows,
11433
- dmlMaxSubtableRows,
11434
- statementNumber,
11435
- parentRowNumbers: [match.sourceRowIndex + 1],
11436
- loadNumberPrecision: input.loadNumberPrecision
11437
- }));
11759
+ statementNumber = 1
11760
+ } = input;
11761
+ assertPositiveLimit(dmlMaxRows, "dmlMaxRows");
11762
+ assertPositiveLimit(dmlMaxSubtableRows, "dmlMaxSubtableRows");
11763
+ for (const snapshot of snapshots) requireRevision(snapshot);
11764
+ const rawPlans = buildApplyPatchPlans(statement, snapshots, fieldInfos, metadata);
11765
+ const fieldIndex = buildPostImageFieldIndex(
11766
+ fieldInfos,
11767
+ statement.assignments.map((assignment) => assignment.field)
11768
+ );
11769
+ const needsNumberPrecision = rawPlans.some(
11770
+ (plan) => postImageNeedsNumberPrecision(plan.postImage, fieldIndex)
11771
+ );
11772
+ if (needsNumberPrecision && !input.loadNumberPrecision) {
11773
+ throw new Error("InternalError: APPLY number precision loader is required for NUMBER post-images.");
11438
11774
  }
11439
- const update = combineUpdatePrepared(updateParts, dmlMaxRows, dmlMaxSubtableRows);
11440
- const parentRows = create.guards.parentRows + update.guards.parentRows;
11441
- const subtableRows = create.guards.subtableRows + update.guards.subtableRows;
11442
- const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11775
+ const numberPrecision = needsNumberPrecision ? await input.loadNumberPrecision() : void 0;
11776
+ const validationResults = rawPlans.map(
11777
+ (plan, index) => validatePostImage(
11778
+ plan.postImage,
11779
+ fieldIndex,
11780
+ numberPrecision,
11781
+ statementNumber,
11782
+ input.parentRowNumbers?.[index] ?? index + 1
11783
+ )
11784
+ );
11443
11785
  if (!statement.validateOnly) {
11444
- const validationErrors = [
11445
- ...create.validations.flatMap((validation) => validation.errors),
11446
- ...update.validations.flatMap((validation) => validation.errors)
11447
- ];
11448
- if (validationErrors.length > 0) {
11786
+ const errors = validationResults.flatMap((validation) => validation.errors);
11787
+ if (errors.length > 0) {
11449
11788
  throw new Error(`ArgumentError: APPLY post-image validation failed: ${JSON.stringify({
11450
- columns: create.validations[0]?.columns ?? update.validations[0]?.columns ?? [],
11451
- errors: validationErrors
11789
+ columns: validationResults[0]?.columns ?? [],
11790
+ errors
11452
11791
  })}`);
11453
11792
  }
11454
11793
  }
11794
+ const parentRows = rawPlans.length;
11795
+ const subtableRows = rawPlans.reduce((sum, plan) => sum + plan.changedSubtableRows, 0);
11796
+ const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11455
11797
  if (!statement.validateOnly && parentRows > dmlMaxRows) {
11456
11798
  throw new Error(`ArgumentError: APPLY parent rows (${parentRows}) exceed dmlMaxRows (${dmlMaxRows}).`);
11457
11799
  }
11458
11800
  if (!statement.validateOnly && subtableRows > dmlMaxSubtableRows) {
11459
- throw new Error(`ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`);
11801
+ throw new Error(
11802
+ `ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`
11803
+ );
11460
11804
  }
11461
- return deepFreeze3({
11462
- create,
11463
- update,
11464
- createBatches: create.batches,
11465
- updateBatches: chunk2(update.records, statement.appId),
11466
- guards: {
11467
- revisionRequired: update.guards.parentRows > 0,
11468
- parentRows,
11469
- dmlMaxRows,
11470
- subtableRows,
11471
- dmlMaxSubtableRows,
11472
- wouldExceed,
11473
- create: create.guards,
11474
- update: update.guards
11475
- }
11476
- });
11477
- }
11478
- function toInsertStatement(statement, indices) {
11479
- return {
11480
- type: "INSERT",
11481
- appId: statement.appId,
11482
- fields: [...statement.fields],
11483
- values: indices.map((index) => statement.values[index]),
11484
- applyBlocks: statement.onInsertApplyBlocks ?? [],
11485
- // Branch planners must finish every candidate before mixed errors/guards are raised below.
11486
- validateOnly: true
11487
- };
11488
- }
11489
- function toUpdateStatement(statement, sourceRowIndex, targetId) {
11490
- const assignments = statement.fields.map((field, index) => ({
11491
- field,
11492
- value: statement.values[sourceRowIndex][index]
11805
+ const plans = rawPlans.map(
11806
+ (plan, index) => normalizeApplyPatchPlan(plan, validationResults[index].normalizedRecord)
11807
+ );
11808
+ const records = plans.flatMap((plan) => applyPatchPlanToKintone(plan).records);
11809
+ const validations = validationResults.map((validation) => ({
11810
+ errors: validation.errors,
11811
+ columns: validation.columns,
11812
+ invalidRows: validation.invalidRows,
11813
+ errorCount: validation.errorCount
11493
11814
  }));
11494
- return {
11495
- type: "UPDATE",
11496
- appId: statement.appId,
11497
- subtableCode: null,
11498
- assignments,
11499
- from: null,
11500
- where: {
11501
- type: "BINARY",
11502
- op: "=",
11503
- left: { type: "FIELD", tableAlias: null, field: "$id" },
11504
- right: { type: "NUMBER", value: targetId, raw: String(targetId) }
11505
- },
11506
- applyBlocks: statement.onUpdateApplyBlocks ?? [],
11507
- validateOnly: true
11508
- };
11509
- }
11510
- function emptyPatchMetadata(fieldInfos) {
11511
- return {
11512
- targetTables: /* @__PURE__ */ new Map(),
11513
- targetMultiValueFields: /* @__PURE__ */ new Map(),
11514
- childrenByTable: /* @__PURE__ */ new Map(),
11515
- fieldsByCode: new Map(fieldInfos.map((field) => [field.code, field]))
11516
- };
11517
- }
11518
- function combineUpdatePrepared(parts, dmlMaxRows, dmlMaxSubtableRows) {
11519
- const plans = parts.flatMap((part) => part.plans);
11520
- const records = parts.flatMap((part) => part.records);
11521
- const validations = parts.flatMap((part) => part.validations);
11522
- const subtableRows = plans.reduce((sum, plan) => sum + plan.changedSubtableRows, 0);
11523
- return deepFreeze3({
11815
+ return deepFreeze({
11524
11816
  plans,
11525
11817
  records,
11526
11818
  validations,
11527
11819
  guards: {
11528
11820
  revisionRequired: true,
11529
- parentRows: plans.length,
11821
+ parentRows,
11530
11822
  dmlMaxRows,
11531
11823
  subtableRows,
11532
11824
  dmlMaxSubtableRows,
11533
- wouldExceed: plans.length > dmlMaxRows || subtableRows > dmlMaxSubtableRows
11825
+ wouldExceed
11534
11826
  }
11535
11827
  });
11536
11828
  }
11537
- function assertMatchCoverage(statement, matches) {
11538
- if (matches.length !== statement.values.length) {
11539
- throw new Error("InternalError: UPSERT APPLY match count differs from VALUES rows.");
11540
- }
11541
- const seen = /* @__PURE__ */ new Set();
11542
- for (const match of matches) {
11543
- if (!Number.isSafeInteger(match.sourceRowIndex) || match.sourceRowIndex < 0 || match.sourceRowIndex >= statement.values.length || seen.has(match.sourceRowIndex)) {
11544
- throw new Error("InternalError: UPSERT APPLY matches must cover each source row exactly once.");
11545
- }
11546
- seen.add(match.sourceRowIndex);
11547
- }
11548
- }
11549
- function chunk2(records, app) {
11550
- const batches = [];
11551
- for (let index = 0; index < records.length; index += 100) {
11552
- batches.push({ app, records: records.slice(index, index + 100) });
11553
- }
11554
- return batches;
11555
- }
11556
- function assertPositiveLimit3(value, name) {
11829
+ function assertPositiveLimit(value, name) {
11557
11830
  if (!Number.isSafeInteger(value) || value <= 0) {
11558
11831
  throw new Error(`ArgumentError: ${name} must be a positive safe integer.`);
11559
11832
  }
11560
11833
  }
11561
- function argument5(message) {
11562
- throw new Error(`ArgumentError: ${message}`);
11563
- }
11564
- function deepFreeze3(value, seen = /* @__PURE__ */ new Set()) {
11834
+ function deepFreeze(value, seen = /* @__PURE__ */ new Set()) {
11565
11835
  if (value === null || typeof value !== "object" || seen.has(value)) return value;
11566
11836
  seen.add(value);
11567
- for (const child of Object.values(value)) deepFreeze3(child, seen);
11837
+ for (const child of Object.values(value)) deepFreeze(child, seen);
11568
11838
  return Object.freeze(value);
11569
11839
  }
11570
11840
 
11571
- // src/core/applyDiagnostic.ts
11572
- function buildPreparedApplyUpdateDiagnostic(prepared) {
11573
- return diagnostic("UPDATE", [buildPreparedUpdateBranch(prepared)]);
11574
- }
11575
- function buildPreparedApplyInsertDiagnostic(prepared) {
11576
- return diagnostic("INSERT", [buildPreparedInsertBranch(prepared)]);
11577
- }
11578
- function buildPreparedApplyUpsertDiagnostic(prepared) {
11579
- return diagnostic("UPSERT", [
11580
- buildPreparedInsertBranch(prepared.create),
11581
- buildPreparedUpdateBranch(prepared.update)
11582
- ]);
11583
- }
11584
- function withApplyDiagnosticProgress(base, progress) {
11585
- const failedBranch = progress.failedBranch?.toLowerCase();
11586
- const branches = base.branches.map((branch) => {
11587
- const successfulParents = base.statementKind === "UPSERT" ? branch.branch === "insert" ? progress.successfulInserts : progress.successfulUpdates : progress.successfulParents;
11588
- const successfulChunks = base.statementKind === "UPSERT" ? branch.branch === "insert" ? progress.successfulInsertChunks : progress.successfulUpdateChunks : progress.successfulChunks;
11589
- const isFailedBranch = failedBranch === branch.branch;
11590
- return {
11591
- ...branch,
11592
- ...successfulParents !== void 0 ? { successfulParents } : {},
11593
- chunk: {
11594
- ...branch.chunk,
11595
- ...successfulChunks !== void 0 ? { successfulChunks } : {},
11596
- ...isFailedBranch && progress.failedChunkIndex !== void 0 ? { failedChunkIndex: progress.failedChunkIndex } : {},
11597
- ...isFailedBranch && progress.failedStage !== void 0 ? { failedStage: progress.failedStage } : {}
11598
- }
11599
- };
11600
- });
11601
- return {
11602
- ...base,
11603
- branches,
11604
- partialSuccess: {
11605
- possible: true,
11606
- successfulParents: progress.successfulParents,
11607
- successfulChunks: progress.successfulChunks,
11608
- ...failedBranch ? { failedBranch } : {},
11609
- ...progress.retryAttempted !== void 0 ? { retryAttempted: progress.retryAttempted } : {}
11610
- }
11611
- };
11612
- }
11613
- function buildStaticApplyDiagnostic(statement, dmlMaxRows, dmlMaxSubtableRows) {
11614
- if (statement.type === "UPDATE") {
11615
- return diagnostic("UPDATE", [staticBranch(
11616
- "update",
11617
- statement.applyBlocks ?? [],
11618
- null,
11619
- true,
11620
- dmlMaxRows,
11621
- dmlMaxSubtableRows
11622
- )]);
11623
- }
11624
- if (statement.type === "INSERT") {
11625
- return diagnostic("INSERT", [staticBranch(
11626
- "insert",
11627
- statement.applyBlocks ?? [],
11628
- statement.values.length,
11629
- false,
11630
- dmlMaxRows,
11631
- dmlMaxSubtableRows
11632
- )]);
11841
+ // src/core/applyInsertPrepare.ts
11842
+ var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
11843
+ "CALC",
11844
+ "RECORD_NUMBER",
11845
+ "CREATOR",
11846
+ "CREATED_TIME",
11847
+ "MODIFIER",
11848
+ "UPDATED_TIME",
11849
+ "STATUS",
11850
+ "STATUS_ASSIGNEE",
11851
+ "CATEGORY",
11852
+ "REFERENCE_TABLE"
11853
+ ]);
11854
+ function resolveApplyInsertMetadata(statement, fieldInfos) {
11855
+ if (new Set(statement.fields).size !== statement.fields.length) {
11856
+ return argument4("DML target fields contain duplicates.");
11633
11857
  }
11634
- return diagnostic("UPSERT", [
11635
- staticBranch("insert", statement.onInsertApplyBlocks ?? [], null, false, dmlMaxRows, dmlMaxSubtableRows),
11636
- staticBranch("update", statement.onUpdateApplyBlocks ?? [], null, true, dmlMaxRows, dmlMaxSubtableRows)
11637
- ]);
11638
- }
11639
- function diagnostic(statementKind, branches) {
11640
- return {
11641
- statementKind,
11642
- branches,
11643
- nonTransactional: true,
11644
- partialSuccess: { possible: true }
11645
- };
11646
- }
11647
- function buildPreparedInsertBranch(prepared) {
11648
- const targets = /* @__PURE__ */ new Map();
11649
- if (prepared.applyBlocks) {
11650
- for (const block of prepared.applyBlocks) {
11651
- const operations = block.operations.map((operation) => {
11652
- if (operation.kind !== "APPEND") {
11653
- throw new Error(`InternalError: prepared APPLY INSERT contains ${operation.kind}.`);
11858
+ const fieldsByCode = new Map(fieldInfos.map((field) => [field.code, field]));
11859
+ for (const code of statement.fields) assertWritable(code, null, fieldsByCode);
11860
+ const targetTables = /* @__PURE__ */ new Map();
11861
+ const childrenByTable = /* @__PURE__ */ new Map();
11862
+ for (const block of statement.applyBlocks ?? []) {
11863
+ if (targetTables.has(block.field)) return argument4(`APPLY has more than one block for table ${block.field}.`);
11864
+ const table = fieldInfos.find((field) => field.code === block.field && !field.inSubtable);
11865
+ if (!table || table.fieldType !== "SUBTABLE") return argument4(`APPLY target ${block.field} is not a SUBTABLE.`);
11866
+ const children = new Map(fieldInfos.filter((field) => field.inSubtable && field.subtableCode === block.field).map((field) => [field.code, field]));
11867
+ targetTables.set(block.field, table);
11868
+ childrenByTable.set(block.field, children);
11869
+ for (const operation of block.operations) {
11870
+ if (operation.kind !== "APPEND") return argument4(`APPLY INSERT supports APPEND only (${operation.kind}).`);
11871
+ const specified = /* @__PURE__ */ new Set();
11872
+ for (const code of operation.fields) {
11873
+ if (specified.has(code)) return argument4(`APPLY APPEND specifies child ${code} more than once.`);
11874
+ specified.add(code);
11875
+ assertWritable(code, block.field, fieldsByCode, children);
11876
+ }
11877
+ for (const row of operation.values) {
11878
+ if (row.length !== operation.fields.length) {
11879
+ return argument4(`APPLY APPEND for ${block.field} has ${row.length} values for ${operation.fields.length} fields.`);
11654
11880
  }
11655
- const addedRows = operation.values.length * prepared.guards.parentRows;
11656
- return { kind: "APPEND", count: addedRows, addedRows };
11657
- });
11658
- targets.set(block.field, {
11659
- targetKind: block.targetKind,
11660
- field: block.field,
11661
- operations,
11662
- changedCount: operations.reduce((sum, operation) => sum + (operation.addedRows ?? 0), 0)
11663
- });
11664
- }
11665
- } else {
11666
- for (const candidate of prepared.candidates) {
11667
- for (const table of candidate.tables) {
11668
- const current = targets.get(table.table);
11669
- const addedRows = current?.operations[0]?.addedRows ?? 0;
11670
- targets.set(table.table, {
11671
- targetKind: "SUBTABLE",
11672
- field: table.table,
11673
- operations: [{ kind: "APPEND", count: addedRows + table.addedRows, addedRows: addedRows + table.addedRows }],
11674
- changedCount: (current?.changedCount ?? 0) + table.addedRows
11675
- });
11676
11881
  }
11677
11882
  }
11678
11883
  }
11679
- return {
11680
- branch: "insert",
11681
- parentRows: prepared.guards.parentRows,
11682
- targets: [...targets.values()],
11683
- guards: prepared.guards,
11684
- chunk: { size: 100, plannedChunks: prepared.batches.length },
11685
- deletedParentRows: 0
11686
- };
11884
+ return { targetTables, targetMultiValueFields: /* @__PURE__ */ new Map(), childrenByTable, fieldsByCode };
11687
11885
  }
11688
- function buildPreparedUpdateBranch(prepared) {
11689
- const targets = /* @__PURE__ */ new Map();
11690
- let deletedParentRows = 0;
11691
- for (const plan of prepared.plans) {
11692
- let parentHasDeletes = false;
11693
- for (const table of plan.tables) {
11694
- const current = targets.get(table.table);
11695
- targets.set(table.table, {
11696
- targetKind: "SUBTABLE",
11697
- field: table.table,
11698
- operations: mergeOperations(current?.operations, table.operations.map((operation) => ({
11699
- ...operation,
11700
- count: operation.kind === "PATCH" ? operation.changedRows : operation.kind === "APPEND" ? operation.addedRows : operation.removedRows
11701
- }))),
11702
- changedCount: (current?.changedCount ?? 0) + table.changedSubtableRows
11703
- });
11704
- parentHasDeletes ||= table.deletedRows > 0;
11886
+ function buildApplyInsertCandidates(statement, fieldInfos, metadata = resolveApplyInsertMetadata(statement, fieldInfos), parentRowNumbers) {
11887
+ const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
11888
+ const parentRecords = insertToPostBatches(statement, fieldTypes).flatMap((batch) => batch.records);
11889
+ if (parentRowNumbers && parentRowNumbers.length !== parentRecords.length) {
11890
+ throw new Error("InternalError: APPLY create parent row number count differs from VALUES rows.");
11891
+ }
11892
+ const templates = (statement.applyBlocks ?? []).map((block) => {
11893
+ const children = metadata.childrenByTable.get(block.field);
11894
+ const rows = block.operations.flatMap(
11895
+ (operation) => buildApplyAppendRows(operation, children, block.field)
11896
+ );
11897
+ return { table: block.field, rows, addedRows: rows.length };
11898
+ });
11899
+ return parentRecords.map((parentRecord, index) => {
11900
+ const postImage = {};
11901
+ for (const field of fieldInfos) {
11902
+ if (field.inSubtable || field.fieldType === "SUBTABLE" || field.fieldType === "FILE" || field.writable === false || NON_WRITABLE_FIELD_TYPES.has(field.fieldType)) continue;
11903
+ postImage[field.code] = parentRecord[field.code] ?? { value: appendDefaultValue(field) };
11705
11904
  }
11706
- for (const field of plan.multiValues) {
11707
- const current = targets.get(field.field);
11708
- const postImage = { parentId: plan.parentId, value: field.postImageValue };
11709
- targets.set(field.field, {
11710
- targetKind: "MULTI_VALUE",
11711
- field: field.field,
11712
- fieldType: field.fieldType,
11713
- operations: mergeOperations(current?.operations, field.operations.map((operation) => ({
11714
- kind: operation.kind,
11715
- value: operation.value,
11716
- count: operation.changed ? 1 : 0,
11717
- changed: operation.changed
11718
- }))),
11719
- changedCount: (current?.changedCount ?? 0) + field.changedValues,
11720
- postImages: [...current?.postImages ?? [], postImage]
11721
- });
11905
+ const record = { ...parentRecord };
11906
+ for (const template of templates) {
11907
+ const rows = template.rows.map((row) => ({ value: row.value }));
11908
+ postImage[template.table] = { value: rows };
11909
+ record[template.table] = { value: rows };
11910
+ }
11911
+ return {
11912
+ parentRowNumber: parentRowNumbers?.[index] ?? index + 1,
11913
+ tables: templates,
11914
+ postImage,
11915
+ record
11916
+ };
11917
+ });
11918
+ }
11919
+ async function prepareApplyInsert(input) {
11920
+ const {
11921
+ statement,
11922
+ fieldInfos,
11923
+ dmlMaxRows,
11924
+ dmlMaxSubtableRows,
11925
+ statementNumber = 1
11926
+ } = input;
11927
+ assertPositiveLimit2(dmlMaxRows, "dmlMaxRows");
11928
+ assertPositiveLimit2(dmlMaxSubtableRows, "dmlMaxSubtableRows");
11929
+ const metadata = input.metadata ?? resolveApplyInsertMetadata(statement, fieldInfos);
11930
+ const rawCandidates = buildApplyInsertCandidates(statement, fieldInfos, metadata, input.parentRowNumbers);
11931
+ const creatableFieldInfos = fieldInfos.filter(
11932
+ (field) => field.fieldType === "SUBTABLE" || field.fieldType !== "FILE" && field.writable !== false && !NON_WRITABLE_FIELD_TYPES.has(field.fieldType)
11933
+ );
11934
+ const fieldIndex = buildPostImageFieldIndex(creatableFieldInfos, statement.fields);
11935
+ const needsNumberPrecision = rawCandidates.some(
11936
+ (candidate) => postImageNeedsNumberPrecision(candidate.postImage, fieldIndex)
11937
+ );
11938
+ if (needsNumberPrecision && !input.loadNumberPrecision) {
11939
+ throw new Error("InternalError: APPLY number precision loader is required for NUMBER post-images.");
11940
+ }
11941
+ const numberPrecision = needsNumberPrecision ? await input.loadNumberPrecision() : void 0;
11942
+ const results = rawCandidates.map((candidate) => validatePostImage(
11943
+ candidate.postImage,
11944
+ fieldIndex,
11945
+ numberPrecision,
11946
+ statementNumber,
11947
+ candidate.parentRowNumber,
11948
+ "INSERT"
11949
+ ));
11950
+ if (!statement.validateOnly) {
11951
+ const errors = results.flatMap((result) => result.errors);
11952
+ if (errors.length > 0) {
11953
+ throw new Error(`ArgumentError: APPLY post-image validation failed: ${JSON.stringify({
11954
+ columns: results[0]?.columns ?? [],
11955
+ errors
11956
+ })}`);
11722
11957
  }
11723
- if (parentHasDeletes) deletedParentRows += 1;
11724
11958
  }
11725
- return {
11726
- branch: "update",
11727
- parentRows: prepared.guards.parentRows,
11728
- targets: [...targets.values()],
11729
- guards: prepared.guards,
11730
- chunk: { size: 100, plannedChunks: Math.ceil(prepared.records.length / 100) },
11731
- deletedParentRows
11732
- };
11733
- }
11734
- function mergeOperations(current, next) {
11735
- if (!current) return next.map((operation) => ({ ...operation }));
11736
- return next.map((operation, index) => {
11737
- const previous = current[index];
11738
- if (!previous || previous.kind !== operation.kind || previous.value !== operation.value) {
11739
- throw new Error("InternalError: APPLY diagnostic operation shape differs between parents.");
11959
+ const parentRows = rawCandidates.length;
11960
+ const subtableRows = rawCandidates.reduce(
11961
+ (sum, candidate) => sum + candidate.tables.reduce((tableSum, table) => tableSum + table.addedRows, 0),
11962
+ 0
11963
+ );
11964
+ const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11965
+ if (!statement.validateOnly && parentRows > dmlMaxRows) {
11966
+ throw new Error(`ArgumentError: APPLY parent rows (${parentRows}) exceed dmlMaxRows (${dmlMaxRows}).`);
11967
+ }
11968
+ if (!statement.validateOnly && subtableRows > dmlMaxSubtableRows) {
11969
+ throw new Error(`ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`);
11970
+ }
11971
+ const candidates = rawCandidates.map((candidate, index) => {
11972
+ const normalized = results[index].normalizedRecord;
11973
+ const record = {};
11974
+ for (const code of statement.fields) record[code] = normalized[code];
11975
+ for (const table of candidate.tables) {
11976
+ const normalizedRows = normalized[table.table]?.value;
11977
+ record[table.table] = { value: table.rows.map((sourceRow, rowIndex) => ({
11978
+ value: Object.fromEntries(Object.keys(sourceRow.value).map((code) => [
11979
+ code,
11980
+ normalizedRows[rowIndex]?.value?.[code] ?? sourceRow.value[code]
11981
+ ]))
11982
+ })) };
11740
11983
  }
11741
- return {
11742
- ...operation,
11743
- ...previous.changed !== void 0 ? { changed: previous.changed } : {},
11744
- count: addNullable(previous.count, operation.count),
11745
- ...operation.matchedRows !== void 0 ? { matchedRows: addNullable(previous.matchedRows, operation.matchedRows) } : {},
11746
- ...operation.changedRows !== void 0 ? { changedRows: addNullable(previous.changedRows, operation.changedRows) } : {},
11747
- ...operation.addedRows !== void 0 ? { addedRows: addNullable(previous.addedRows, operation.addedRows) } : {},
11748
- ...operation.removedRows !== void 0 ? { removedRows: addNullable(previous.removedRows, operation.removedRows) } : {}
11749
- };
11750
- });
11751
- }
11752
- function addNullable(left, right) {
11753
- return left === null || left === void 0 || right === null ? null : left + right;
11754
- }
11755
- function staticBranch(branch, blocks, parentRows, revisionRequired, dmlMaxRows, dmlMaxSubtableRows) {
11756
- const targets = blocks.map((block) => {
11757
- const operations = block.operations.map((operation) => staticOperation(operation, parentRows));
11758
- const changedCount = operations.every((operation) => operation.count !== null) ? operations.reduce((sum, operation) => sum + operation.count, 0) : null;
11759
- return {
11760
- targetKind: block.targetKind,
11761
- field: block.field,
11762
- operations,
11763
- changedCount
11764
- };
11984
+ return { ...candidate, postImage: normalized, record };
11765
11985
  });
11766
- const subtableTargets = targets.filter((target) => target.targetKind === "SUBTABLE");
11767
- const subtableRows = subtableTargets.every((target) => target.changedCount !== null) ? subtableTargets.reduce((sum, target) => sum + target.changedCount, 0) : null;
11768
- const wouldExceed = parentRows === null || subtableRows === null ? null : parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
11769
- return {
11770
- branch,
11771
- parentRows,
11772
- targets,
11986
+ const records = candidates.map((candidate) => candidate.record);
11987
+ const batches = [];
11988
+ for (let index = 0; index < records.length; index += 100) {
11989
+ batches.push({ app: statement.appId, records: records.slice(index, index + 100) });
11990
+ }
11991
+ const validations = results.map((result) => ({
11992
+ errors: result.errors,
11993
+ columns: result.columns,
11994
+ invalidRows: result.invalidRows,
11995
+ errorCount: result.errorCount
11996
+ }));
11997
+ return deepFreeze2({
11998
+ applyBlocks: statement.applyBlocks ?? [],
11999
+ candidates,
12000
+ records,
12001
+ batches,
12002
+ validations,
11773
12003
  guards: {
11774
- revisionRequired,
12004
+ revisionRequired: false,
11775
12005
  parentRows,
11776
12006
  dmlMaxRows,
11777
12007
  subtableRows,
11778
12008
  dmlMaxSubtableRows,
11779
12009
  wouldExceed
11780
- },
11781
- chunk: { size: 100, plannedChunks: parentRows === null ? null : Math.ceil(parentRows / 100) },
11782
- deletedParentRows: branch === "insert" ? 0 : null
11783
- };
12010
+ }
12011
+ });
11784
12012
  }
11785
- function staticOperation(operation, parentRows) {
11786
- if (operation.kind === "APPEND") {
11787
- const addedRows = parentRows === null ? null : operation.values.length * parentRows;
11788
- return { kind: operation.kind, count: addedRows, addedRows };
12013
+ function assertWritable(code, table, fieldsByCode, children) {
12014
+ if (code.startsWith("_") || code.startsWith("$")) return argument4(`APPLY assignment target ${code} is a system field.`);
12015
+ const field = table === null ? [...fieldsByCode.values()].find((candidate) => candidate.code === code && !candidate.inSubtable) : children?.get(code);
12016
+ if (!field) {
12017
+ const elsewhere = fieldsByCode.get(code);
12018
+ if (table !== null && elsewhere?.inSubtable) return argument4(`APPLY child ${code} does not belong to subtable ${table}.`);
12019
+ return argument4(`APPLY ${table === null ? "parent field" : "child"} ${code} does not exist.`);
11789
12020
  }
11790
- if (operation.kind === "PATCH") return { kind: operation.kind, count: null, matchedRows: null, changedRows: null };
11791
- if (operation.kind === "REMOVE") return { kind: operation.kind, count: null, removedRows: null };
11792
- return { kind: operation.kind, value: operation.value, count: null };
11793
- }
11794
-
11795
- // src/core/applyPatchExecutePrepared.ts
11796
- var ApplyWritePartialFailureError = class extends Error {
11797
- constructor(partialSuccess, cause) {
11798
- const detail = cause instanceof Error ? cause.message : String(cause);
11799
- const method = partialSuccess.failedStage === "POST_CHUNK" ? "POST" : "PUT";
11800
- const branch = partialSuccess.failedBranch ? ` UPSERT ${partialSuccess.failedBranch}` : "";
11801
- super(
11802
- `ApplyWritePartialFailureError: APPLY${branch} ${method} chunk ${partialSuccess.failedChunkIndex + 1} failed (index ${partialSuccess.failedChunkIndex}) after ${partialSuccess.successfulChunks} successful chunk(s) and ${partialSuccess.successfulParents} successful parent(s); writes are non-transactional and were not retried. Cause: ${detail}`
11803
- );
11804
- this.name = "ApplyWritePartialFailureError";
11805
- this.partialSuccess = partialSuccess;
11806
- this.cause = cause;
12021
+ if (field.fieldType === "SUBTABLE" || field.fieldType === "FILE" || field.writable === false || NON_WRITABLE_FIELD_TYPES.has(field.fieldType)) {
12022
+ return argument4(`APPLY assignment target ${code} is not writable (${field.fieldType}).`);
11807
12023
  }
11808
- };
11809
- async function executePreparedApplyWrite(prepared, client, diagnostic2) {
11810
- assertApplyInternalWriteScope("phase10c");
11811
- const batches = applyPatchPlansToKintoneBatches(prepared);
11812
- let successfulChunks = 0;
11813
- let successfulParents = 0;
11814
- for (let failedChunkIndex = 0; failedChunkIndex < batches.length; failedChunkIndex += 1) {
11815
- const batch = batches[failedChunkIndex];
11816
- try {
11817
- await client.putRecords(batch);
11818
- } catch (cause) {
11819
- throw new ApplyWritePartialFailureError({
11820
- successfulChunks,
11821
- successfulParents,
11822
- failedChunkIndex,
11823
- failedStage: "PUT_CHUNK",
11824
- nonTransactional: true,
11825
- retryAttempted: false,
11826
- ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
11827
- successfulChunks,
11828
- successfulParents,
11829
- failedChunkIndex,
11830
- failedStage: "PUT_CHUNK",
11831
- failedBranch: "UPDATE",
11832
- retryAttempted: false
11833
- }) } : {}
11834
- }, cause);
11835
- }
11836
- successfulChunks += 1;
11837
- successfulParents += batch.records.length;
12024
+ }
12025
+ function assertPositiveLimit2(value, name) {
12026
+ if (!Number.isSafeInteger(value) || value <= 0) {
12027
+ throw new Error(`ArgumentError: ${name} must be a positive safe integer.`);
11838
12028
  }
11839
- return {
11840
- type: "UPDATE",
11841
- updatedCount: successfulParents,
11842
- successfulChunks,
11843
- successfulParents,
11844
- nonTransactional: true
11845
- };
12029
+ }
12030
+ function argument4(message) {
12031
+ throw new Error(`ArgumentError: ${message}`);
12032
+ }
12033
+ function deepFreeze2(value, seen = /* @__PURE__ */ new Set()) {
12034
+ if (value === null || typeof value !== "object" || seen.has(value)) return value;
12035
+ seen.add(value);
12036
+ for (const child of Object.values(value)) deepFreeze2(child, seen);
12037
+ return Object.freeze(value);
11846
12038
  }
11847
12039
 
11848
- // src/core/applyInsertExecutePrepared.ts
11849
- async function executePreparedApplyInsert(prepared, client, diagnostic2) {
11850
- assertApplyInternalWriteScope("phase13c");
11851
- const createdIds = [];
11852
- let successfulChunks = 0;
11853
- let successfulParents = 0;
11854
- for (let failedChunkIndex = 0; failedChunkIndex < prepared.batches.length; failedChunkIndex += 1) {
11855
- const batch = prepared.batches[failedChunkIndex];
11856
- try {
11857
- const response = await client.postRecords({ app: batch.app, records: [...batch.records] });
11858
- createdIds.push(response.ids);
11859
- } catch (cause) {
11860
- throw new ApplyWritePartialFailureError({
11861
- successfulChunks,
11862
- successfulParents,
11863
- failedChunkIndex,
11864
- failedStage: "POST_CHUNK",
11865
- nonTransactional: true,
11866
- retryAttempted: false,
11867
- ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
11868
- successfulChunks,
11869
- successfulParents,
11870
- failedChunkIndex,
11871
- failedStage: "POST_CHUNK",
11872
- failedBranch: "INSERT",
11873
- retryAttempted: false
11874
- }) } : {}
11875
- }, cause);
12040
+ // src/core/applyUpsertPrepare.ts
12041
+ async function prepareApplyUpsert(input) {
12042
+ const { statement, matches, fieldInfos, dmlMaxRows, dmlMaxSubtableRows, statementNumber = 1 } = input;
12043
+ assertPositiveLimit3(dmlMaxRows, "dmlMaxRows");
12044
+ assertPositiveLimit3(dmlMaxSubtableRows, "dmlMaxSubtableRows");
12045
+ assertMatchCoverage(statement, matches);
12046
+ const createMatches = matches.filter((match) => match.targetId === void 0);
12047
+ const updateMatches = matches.filter((match) => match.targetId !== void 0);
12048
+ const createStatement = toInsertStatement(statement, createMatches.map((match) => match.sourceRowIndex));
12049
+ const create = await prepareApplyInsert({
12050
+ statement: createStatement,
12051
+ fieldInfos,
12052
+ metadata: resolveApplyInsertMetadata(createStatement, fieldInfos),
12053
+ dmlMaxRows,
12054
+ dmlMaxSubtableRows,
12055
+ statementNumber,
12056
+ parentRowNumbers: createMatches.map((match) => match.sourceRowIndex + 1),
12057
+ loadNumberPrecision: input.loadNumberPrecision
12058
+ });
12059
+ const updateParts = [];
12060
+ const seenTargets = /* @__PURE__ */ new Set();
12061
+ for (const match of updateMatches) {
12062
+ const targetId = match.targetId;
12063
+ if (seenTargets.has(targetId)) argument5(`UPSERT APPLY resolves more than one source row to parent $id ${targetId}.`);
12064
+ seenTargets.add(targetId);
12065
+ if (!match.snapshot) argument5(`UPSERT APPLY snapshot for parent $id ${targetId} is missing.`);
12066
+ const snapshotId = Number(match.snapshot["$id"]?.value);
12067
+ if (snapshotId !== targetId) argument5(`UPSERT APPLY snapshot $id ${snapshotId} does not match target $id ${targetId}.`);
12068
+ const updateStatement = toUpdateStatement(statement, match.sourceRowIndex, targetId);
12069
+ const metadata = updateStatement.applyBlocks?.length ? resolveApplyPatchMetadata(updateStatement, fieldInfos) : emptyPatchMetadata(fieldInfos);
12070
+ updateParts.push(await prepareApplyPatchWrite({
12071
+ statement: updateStatement,
12072
+ snapshots: [match.snapshot],
12073
+ fieldInfos,
12074
+ metadata,
12075
+ dmlMaxRows,
12076
+ dmlMaxSubtableRows,
12077
+ statementNumber,
12078
+ parentRowNumbers: [match.sourceRowIndex + 1],
12079
+ loadNumberPrecision: input.loadNumberPrecision
12080
+ }));
12081
+ }
12082
+ const update = combineUpdatePrepared(updateParts, dmlMaxRows, dmlMaxSubtableRows);
12083
+ const parentRows = create.guards.parentRows + update.guards.parentRows;
12084
+ const subtableRows = create.guards.subtableRows + update.guards.subtableRows;
12085
+ const wouldExceed = parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
12086
+ if (!statement.validateOnly) {
12087
+ const validationErrors = [
12088
+ ...create.validations.flatMap((validation) => validation.errors),
12089
+ ...update.validations.flatMap((validation) => validation.errors)
12090
+ ];
12091
+ if (validationErrors.length > 0) {
12092
+ throw new Error(`ArgumentError: APPLY post-image validation failed: ${JSON.stringify({
12093
+ columns: create.validations[0]?.columns ?? update.validations[0]?.columns ?? [],
12094
+ errors: validationErrors
12095
+ })}`);
11876
12096
  }
11877
- successfulChunks += 1;
11878
- successfulParents += batch.records.length;
11879
12097
  }
12098
+ if (!statement.validateOnly && parentRows > dmlMaxRows) {
12099
+ throw new Error(`ArgumentError: APPLY parent rows (${parentRows}) exceed dmlMaxRows (${dmlMaxRows}).`);
12100
+ }
12101
+ if (!statement.validateOnly && subtableRows > dmlMaxSubtableRows) {
12102
+ throw new Error(`ArgumentError: APPLY changed subtable rows (${subtableRows}) exceed dmlMaxSubtableRows (${dmlMaxSubtableRows}).`);
12103
+ }
12104
+ return deepFreeze3({
12105
+ create,
12106
+ update,
12107
+ createBatches: create.batches,
12108
+ updateBatches: chunk2(update.records, statement.appId),
12109
+ guards: {
12110
+ revisionRequired: update.guards.parentRows > 0,
12111
+ parentRows,
12112
+ dmlMaxRows,
12113
+ subtableRows,
12114
+ dmlMaxSubtableRows,
12115
+ wouldExceed,
12116
+ create: create.guards,
12117
+ update: update.guards
12118
+ }
12119
+ });
12120
+ }
12121
+ function toInsertStatement(statement, indices) {
11880
12122
  return {
11881
12123
  type: "INSERT",
11882
- createdIds,
11883
- insertedCount: successfulParents,
11884
- successfulChunks,
11885
- successfulParents,
11886
- nonTransactional: true
12124
+ appId: statement.appId,
12125
+ fields: [...statement.fields],
12126
+ values: indices.map((index) => statement.values[index]),
12127
+ applyBlocks: statement.onInsertApplyBlocks ?? [],
12128
+ // Branch planners must finish every candidate before mixed errors/guards are raised below.
12129
+ validateOnly: true
11887
12130
  };
11888
12131
  }
11889
-
11890
- // src/core/applyUpsertExecutePrepared.ts
11891
- async function executePreparedApplyUpsert(prepared, client, diagnostic2) {
11892
- assertApplyInternalWriteScope("phase14c");
11893
- const createdIds = [];
11894
- let successfulInsertChunks = 0;
11895
- let successfulUpdateChunks = 0;
11896
- let successfulInserts = 0;
11897
- let successfulUpdates = 0;
11898
- for (let failedChunkIndex = 0; failedChunkIndex < prepared.createBatches.length; failedChunkIndex += 1) {
11899
- const batch = prepared.createBatches[failedChunkIndex];
11900
- try {
11901
- const response = await client.postRecords({ app: batch.app, records: [...batch.records] });
11902
- createdIds.push(response.ids);
11903
- } catch (cause) {
11904
- throw new ApplyWritePartialFailureError({
11905
- successfulChunks: successfulInsertChunks + successfulUpdateChunks,
11906
- successfulParents: successfulInserts + successfulUpdates,
11907
- successfulInserts,
11908
- successfulUpdates,
11909
- failedChunkIndex,
11910
- failedBranch: "INSERT",
11911
- failedStage: "POST_CHUNK",
11912
- nonTransactional: true,
11913
- retryAttempted: false,
11914
- ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
11915
- successfulChunks: successfulInsertChunks + successfulUpdateChunks,
11916
- successfulParents: successfulInserts + successfulUpdates,
11917
- successfulInserts,
11918
- successfulUpdates,
11919
- successfulInsertChunks,
11920
- successfulUpdateChunks,
11921
- failedChunkIndex,
11922
- failedStage: "POST_CHUNK",
11923
- failedBranch: "INSERT",
11924
- retryAttempted: false
11925
- }) } : {}
11926
- }, cause);
11927
- }
11928
- successfulInsertChunks += 1;
11929
- successfulInserts += batch.records.length;
11930
- }
11931
- for (let failedChunkIndex = 0; failedChunkIndex < prepared.updateBatches.length; failedChunkIndex += 1) {
11932
- const batch = prepared.updateBatches[failedChunkIndex];
11933
- try {
11934
- await client.putRecords({ app: batch.app, records: [...batch.records] });
11935
- } catch (cause) {
11936
- throw new ApplyWritePartialFailureError({
11937
- successfulChunks: successfulInsertChunks + successfulUpdateChunks,
11938
- successfulParents: successfulInserts + successfulUpdates,
11939
- successfulInserts,
11940
- successfulUpdates,
11941
- failedChunkIndex,
11942
- failedBranch: "UPDATE",
11943
- failedStage: "PUT_CHUNK",
11944
- nonTransactional: true,
11945
- retryAttempted: false,
11946
- ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
11947
- successfulChunks: successfulInsertChunks + successfulUpdateChunks,
11948
- successfulParents: successfulInserts + successfulUpdates,
11949
- successfulInserts,
11950
- successfulUpdates,
11951
- successfulInsertChunks,
11952
- successfulUpdateChunks,
11953
- failedChunkIndex,
11954
- failedStage: "PUT_CHUNK",
11955
- failedBranch: "UPDATE",
11956
- retryAttempted: false
11957
- }) } : {}
11958
- }, cause);
11959
- }
11960
- successfulUpdateChunks += 1;
11961
- successfulUpdates += batch.records.length;
11962
- }
12132
+ function toUpdateStatement(statement, sourceRowIndex, targetId) {
12133
+ const assignments = statement.fields.map((field, index) => ({
12134
+ field,
12135
+ value: statement.values[sourceRowIndex][index]
12136
+ }));
11963
12137
  return {
11964
- type: "UPSERT",
11965
- createdIds,
11966
- insertedCount: successfulInserts,
11967
- updatedCount: successfulUpdates,
11968
- successfulChunks: successfulInsertChunks + successfulUpdateChunks,
11969
- successfulParents: successfulInserts + successfulUpdates,
11970
- successfulInsertChunks,
11971
- successfulUpdateChunks,
11972
- nonTransactional: true
12138
+ type: "UPDATE",
12139
+ appId: statement.appId,
12140
+ subtableCode: null,
12141
+ assignments,
12142
+ from: null,
12143
+ where: {
12144
+ type: "BINARY",
12145
+ op: "=",
12146
+ left: { type: "FIELD", tableAlias: null, field: "$id" },
12147
+ right: { type: "NUMBER", value: targetId, raw: String(targetId) }
12148
+ },
12149
+ applyBlocks: statement.onUpdateApplyBlocks ?? [],
12150
+ validateOnly: true
11973
12151
  };
11974
12152
  }
11975
-
11976
- // src/core/batchVariables.ts
11977
- var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
11978
- function normalizeBatchVariableName(name) {
11979
- if (!VARIABLE_NAME_RE.test(name)) {
11980
- throw new Error(
11981
- `ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
11982
- );
11983
- }
11984
- return name.toLowerCase();
11985
- }
11986
- function normalizeBatchVariables(input) {
11987
- const normalized = /* @__PURE__ */ Object.create(null);
11988
- for (const [rawName, value] of Object.entries(input ?? {})) {
11989
- const name = normalizeBatchVariableName(rawName);
11990
- if (Object.prototype.hasOwnProperty.call(normalized, name)) {
11991
- throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
11992
- }
11993
- normalized[name] = value;
11994
- }
11995
- return normalized;
12153
+ function emptyPatchMetadata(fieldInfos) {
12154
+ return {
12155
+ targetTables: /* @__PURE__ */ new Map(),
12156
+ targetMultiValueFields: /* @__PURE__ */ new Map(),
12157
+ childrenByTable: /* @__PURE__ */ new Map(),
12158
+ fieldsByCode: new Map(fieldInfos.map((field) => [field.code, field]))
12159
+ };
11996
12160
  }
11997
- function validateDeclaredBatchVariables(statements, input) {
11998
- const normalized = normalizeBatchVariables(input);
11999
- const declared = new Set(
12000
- statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
12001
- );
12002
- for (const name of Object.keys(normalized)) {
12003
- if (!declared.has(name)) {
12004
- throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
12161
+ function combineUpdatePrepared(parts, dmlMaxRows, dmlMaxSubtableRows) {
12162
+ const plans = parts.flatMap((part) => part.plans);
12163
+ const records = parts.flatMap((part) => part.records);
12164
+ const validations = parts.flatMap((part) => part.validations);
12165
+ const subtableRows = plans.reduce((sum, plan) => sum + plan.changedSubtableRows, 0);
12166
+ return deepFreeze3({
12167
+ plans,
12168
+ records,
12169
+ validations,
12170
+ guards: {
12171
+ revisionRequired: true,
12172
+ parentRows: plans.length,
12173
+ dmlMaxRows,
12174
+ subtableRows,
12175
+ dmlMaxSubtableRows,
12176
+ wouldExceed: plans.length > dmlMaxRows || subtableRows > dmlMaxSubtableRows
12005
12177
  }
12006
- }
12007
- return normalized;
12008
- }
12009
-
12010
- // src/core/outerJoinSearchAbortGuard.ts
12011
- function isOuterJoinSelect(value) {
12012
- if (value["type"] !== "SELECT" || !Array.isArray(value["joins"])) return false;
12013
- return value["joins"].some((join2) => {
12014
- if (join2 === null || typeof join2 !== "object") return false;
12015
- const type = join2["type"];
12016
- return type === "LEFT" || type === "RIGHT";
12017
12178
  });
12018
12179
  }
12019
- function statementContainsOuterJoin(statement) {
12020
- const seen = /* @__PURE__ */ new Set();
12021
- const visit = (value) => {
12022
- if (Array.isArray(value)) return value.some(visit);
12023
- if (value === null || typeof value !== "object" || seen.has(value)) return false;
12024
- seen.add(value);
12025
- const object = value;
12026
- if (isOuterJoinSelect(object)) return true;
12027
- return Object.values(object).some(visit);
12028
- };
12029
- return visit(statement);
12030
- }
12031
- function isOuterJoinNonPreservedTable(statement, table, isMainTable) {
12032
- if (isMainTable) {
12033
- return table === statement.from && statement.joins.some((join2) => join2.type === "RIGHT");
12180
+ function assertMatchCoverage(statement, matches) {
12181
+ if (matches.length !== statement.values.length) {
12182
+ throw new Error("InternalError: UPSERT APPLY match count differs from VALUES rows.");
12034
12183
  }
12035
- for (let index = 0; index < statement.joins.length; index += 1) {
12036
- const join2 = statement.joins[index];
12037
- if (join2.type === "LEFT" && table === join2.table) return true;
12038
- if (join2.type === "RIGHT" && statement.joins.slice(0, index).some((previousJoin) => table === previousJoin.table)) {
12039
- return true;
12184
+ const seen = /* @__PURE__ */ new Set();
12185
+ for (const match of matches) {
12186
+ if (!Number.isSafeInteger(match.sourceRowIndex) || match.sourceRowIndex < 0 || match.sourceRowIndex >= statement.values.length || seen.has(match.sourceRowIndex)) {
12187
+ throw new Error("InternalError: UPSERT APPLY matches must cover each source row exactly once.");
12040
12188
  }
12189
+ seen.add(match.sourceRowIndex);
12041
12190
  }
12042
- return false;
12043
12191
  }
12044
-
12045
- // src/core/optimization/joinDateTimeLiteralPolicy.ts
12046
- function isCanonicalJoinDate(value) {
12047
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
12048
- if (!match) return false;
12049
- const year = Number(match[1]);
12050
- const month = Number(match[2]);
12051
- const day = Number(match[3]);
12052
- if (year < 1 || year > 9999) return false;
12053
- const date = /* @__PURE__ */ new Date(0);
12054
- date.setUTCFullYear(year, month - 1, day);
12055
- date.setUTCHours(0, 0, 0, 0);
12056
- return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
12192
+ function chunk2(records, app) {
12193
+ const batches = [];
12194
+ for (let index = 0; index < records.length; index += 100) {
12195
+ batches.push({ app, records: records.slice(index, index + 100) });
12196
+ }
12197
+ return batches;
12057
12198
  }
12058
- function isCanonicalJoinTime(value) {
12059
- const match = /^(\d{2}):(\d{2})$/.exec(value);
12060
- return match !== null && Number(match[1]) <= 23 && Number(match[2]) <= 59;
12199
+ function assertPositiveLimit3(value, name) {
12200
+ if (!Number.isSafeInteger(value) || value <= 0) {
12201
+ throw new Error(`ArgumentError: ${name} must be a positive safe integer.`);
12202
+ }
12061
12203
  }
12062
- function isCanonicalJoinDateTime(value) {
12063
- const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/.exec(value);
12064
- if (!match || !isCanonicalJoinDate(match[1])) return false;
12065
- return Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59;
12204
+ function argument5(message) {
12205
+ throw new Error(`ArgumentError: ${message}`);
12206
+ }
12207
+ function deepFreeze3(value, seen = /* @__PURE__ */ new Set()) {
12208
+ if (value === null || typeof value !== "object" || seen.has(value)) return value;
12209
+ seen.add(value);
12210
+ for (const child of Object.values(value)) deepFreeze3(child, seen);
12211
+ return Object.freeze(value);
12066
12212
  }
12067
12213
 
12068
- // src/core/optimization/whereCapability.ts
12069
- var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
12070
- var RELATIVE_DATE_FIELD_TYPES = /* @__PURE__ */ new Set([
12071
- "DATE",
12072
- "DATETIME",
12073
- "CREATED_TIME",
12074
- "UPDATED_TIME"
12075
- ]);
12076
- var RELATIVE_DATE_OPERATORS = new Set(RANGE_AND_EQUALITY);
12077
- var LEGACY_KINTONE_FUNCTION_FIELD_TYPES = /* @__PURE__ */ new Map([
12078
- ["TODAY", /* @__PURE__ */ new Set(["DATE", "DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
12079
- ["NOW", /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"])],
12080
- ["LOGINUSER", /* @__PURE__ */ new Set(["CREATOR", "MODIFIER", "USER_SELECT"])],
12081
- ["PRIMARY_ORGANIZATION", /* @__PURE__ */ new Set(["ORGANIZATION_SELECT"])]
12082
- ]);
12083
- var LEGACY_KINTONE_FUNCTION_OPERATORS = /* @__PURE__ */ new Map([
12084
- ["TODAY", new Set(RANGE_AND_EQUALITY)],
12085
- ["NOW", new Set(RANGE_AND_EQUALITY)],
12086
- ["LOGINUSER", /* @__PURE__ */ new Set(["in", "not in"])],
12087
- ["PRIMARY_ORGANIZATION", /* @__PURE__ */ new Set(["in", "not in"])]
12088
- ]);
12089
- var EQUALITY_IN = ["=", "!=", "in", "not in"];
12090
- var NATIVE_OPERATORS = /* @__PURE__ */ new Map([
12091
- ["RECORD_NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12092
- ["__ID__", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12093
- ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
12094
- ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
12095
- ["CREATED_TIME", new Set(RANGE_AND_EQUALITY)],
12096
- ["UPDATED_TIME", new Set(RANGE_AND_EQUALITY)],
12097
- ["DATE", new Set(RANGE_AND_EQUALITY)],
12098
- ["TIME", new Set(RANGE_AND_EQUALITY)],
12099
- ["DATETIME", new Set(RANGE_AND_EQUALITY)],
12100
- ["SINGLE_LINE_TEXT", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
12101
- ["LINK", /* @__PURE__ */ new Set(["=", "!=", "in", "not in", "like", "not like"])],
12102
- ["NUMBER", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12103
- ["CALC", /* @__PURE__ */ new Set([...RANGE_AND_EQUALITY, "in", "not in"])],
12104
- ["MULTI_LINE_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
12105
- ["RICH_TEXT", /* @__PURE__ */ new Set(["like", "not like"])],
12106
- ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
12107
- ["RADIO_BUTTON", /* @__PURE__ */ new Set(["in", "not in"])],
12108
- ["DROP_DOWN", /* @__PURE__ */ new Set(["in", "not in"])],
12109
- ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12110
- ["FILE", /* @__PURE__ */ new Set(["like", "not like"])],
12111
- ["USER_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12112
- ["ORGANIZATION_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12113
- ["GROUP_SELECT", /* @__PURE__ */ new Set(["in", "not in"])],
12114
- ["STATUS", new Set(EQUALITY_IN)],
12115
- ["STATUS_ASSIGNEE", /* @__PURE__ */ new Set(["in", "not in"])]
12116
- ]);
12117
- var LOCAL_VALID_OPERATORS = /* @__PURE__ */ new Map([
12118
- ["CREATOR", /* @__PURE__ */ new Set(["in", "not in"])],
12119
- ["MODIFIER", /* @__PURE__ */ new Set(["in", "not in"])],
12120
- ["CHECK_BOX", /* @__PURE__ */ new Set(["in", "not in"])],
12121
- ["MULTI_SELECT", /* @__PURE__ */ new Set(["in", "not in"])]
12122
- ]);
12123
- var LOCAL_SCALAR_TYPES = /* @__PURE__ */ new Set([
12124
- "RECORD_NUMBER",
12125
- "__ID__",
12126
- "CREATOR",
12127
- "MODIFIER",
12128
- "CREATED_TIME",
12129
- "UPDATED_TIME",
12130
- "DATE",
12131
- "TIME",
12132
- "DATETIME",
12133
- "SINGLE_LINE_TEXT",
12134
- "LINK",
12135
- "NUMBER",
12136
- "CALC",
12137
- "MULTI_LINE_TEXT",
12138
- "RICH_TEXT",
12139
- "RADIO_BUTTON",
12140
- "DROP_DOWN",
12141
- "STATUS",
12142
- // 一時表・CTE・式列は kintone REST へは送らず、共有ローカル評価器で扱う。
12143
- "KSQL_STRING",
12144
- "KSQL_NUMBER",
12145
- "KSQL_BOOLEAN"
12146
- ]);
12147
- var LOCAL_COLLECTION_TYPES = /* @__PURE__ */ new Set([
12148
- "CHECK_BOX",
12149
- "MULTI_SELECT",
12150
- "FILE",
12151
- "USER_SELECT",
12152
- "ORGANIZATION_SELECT",
12153
- "GROUP_SELECT",
12154
- "STATUS_ASSIGNEE",
12155
- "CATEGORY"
12156
- ]);
12157
- function nativeWhereOperatorsForType(fieldType) {
12158
- return NATIVE_OPERATORS.get(fieldType) ?? /* @__PURE__ */ new Set();
12214
+ // src/core/applyDiagnostic.ts
12215
+ function buildPreparedApplyUpdateDiagnostic(prepared) {
12216
+ return diagnostic("UPDATE", [buildPreparedUpdateBranch(prepared)]);
12159
12217
  }
12160
- function normalizeChoiceEquality(where, resolveField2) {
12161
- const rewrites = [];
12162
- const visit = (node) => {
12163
- if (node.type === "BINARY") {
12164
- if ((node.op === "=" || node.op === "!=" || node.op === "<>") && node.left.type === "FIELD" && node.right.type === "STRING" && node.right.value !== "") {
12165
- const semantics = resolveField2(node.left);
12166
- if (semantics !== void 0 && semantics.compareMode === "option" && LOCAL_SCALAR_TYPES.has(semantics.fieldType) && nativeWhereOperatorsForType(semantics.fieldType).has("in") && semantics.optionOrder?.has(node.right.value) === true) {
12167
- const normalizedOperator = node.op === "=" ? "IN" : "NOT_IN";
12168
- rewrites.push({
12169
- field: node.left,
12170
- originalOperator: node.op,
12171
- normalizedOperator,
12172
- value: node.right.value
12173
- });
12174
- return {
12175
- ...node,
12176
- op: normalizedOperator,
12177
- right: { type: "IN_LIST", values: [node.right] }
12178
- };
12179
- }
12218
+ function buildPreparedApplyInsertDiagnostic(prepared) {
12219
+ return diagnostic("INSERT", [buildPreparedInsertBranch(prepared)]);
12220
+ }
12221
+ function buildPreparedApplyUpsertDiagnostic(prepared) {
12222
+ return diagnostic("UPSERT", [
12223
+ buildPreparedInsertBranch(prepared.create),
12224
+ buildPreparedUpdateBranch(prepared.update)
12225
+ ]);
12226
+ }
12227
+ function withApplyDiagnosticProgress(base, progress) {
12228
+ const failedBranch = progress.failedBranch?.toLowerCase();
12229
+ const branches = base.branches.map((branch) => {
12230
+ const successfulParents = base.statementKind === "UPSERT" ? branch.branch === "insert" ? progress.successfulInserts : progress.successfulUpdates : progress.successfulParents;
12231
+ const successfulChunks = base.statementKind === "UPSERT" ? branch.branch === "insert" ? progress.successfulInsertChunks : progress.successfulUpdateChunks : progress.successfulChunks;
12232
+ const isFailedBranch = failedBranch === branch.branch;
12233
+ return {
12234
+ ...branch,
12235
+ ...successfulParents !== void 0 ? { successfulParents } : {},
12236
+ chunk: {
12237
+ ...branch.chunk,
12238
+ ...successfulChunks !== void 0 ? { successfulChunks } : {},
12239
+ ...isFailedBranch && progress.failedChunkIndex !== void 0 ? { failedChunkIndex: progress.failedChunkIndex } : {},
12240
+ ...isFailedBranch && progress.failedStage !== void 0 ? { failedStage: progress.failedStage } : {}
12180
12241
  }
12181
- return node;
12182
- }
12183
- if (node.type === "LOGICAL") {
12184
- const left = visit(node.left);
12185
- const right = visit(node.right);
12186
- return left === node.left && right === node.right ? node : { ...node, left, right };
12187
- }
12188
- if (node.type === "GROUP" || node.type === "NOT") {
12189
- const expr = visit(node.expr);
12190
- return expr === node.expr ? node : { ...node, expr };
12242
+ };
12243
+ });
12244
+ return {
12245
+ ...base,
12246
+ branches,
12247
+ partialSuccess: {
12248
+ possible: true,
12249
+ successfulParents: progress.successfulParents,
12250
+ successfulChunks: progress.successfulChunks,
12251
+ ...failedBranch ? { failedBranch } : {},
12252
+ ...progress.retryAttempted !== void 0 ? { retryAttempted: progress.retryAttempted } : {}
12191
12253
  }
12192
- return node;
12193
12254
  };
12194
- return { normalizedWhere: visit(where), rewrites };
12195
12255
  }
12196
- function classifyWhereCapability(where, resolveField2) {
12197
- if (where === null) {
12198
- return { capability: "EXACT_PUSHDOWN", reasons: [{ code: "WHERE_EXACT" }] };
12256
+ function buildStaticApplyDiagnostic(statement, dmlMaxRows, dmlMaxSubtableRows) {
12257
+ if (statement.type === "UPDATE") {
12258
+ return diagnostic("UPDATE", [staticBranch(
12259
+ "update",
12260
+ statement.applyBlocks ?? [],
12261
+ null,
12262
+ true,
12263
+ dmlMaxRows,
12264
+ dmlMaxSubtableRows
12265
+ )]);
12199
12266
  }
12200
- return classifyNode(where, resolveField2);
12267
+ if (statement.type === "INSERT") {
12268
+ return diagnostic("INSERT", [staticBranch(
12269
+ "insert",
12270
+ statement.applyBlocks ?? [],
12271
+ statement.values.length,
12272
+ false,
12273
+ dmlMaxRows,
12274
+ dmlMaxSubtableRows
12275
+ )]);
12276
+ }
12277
+ return diagnostic("UPSERT", [
12278
+ staticBranch("insert", statement.onInsertApplyBlocks ?? [], null, false, dmlMaxRows, dmlMaxSubtableRows),
12279
+ staticBranch("update", statement.onUpdateApplyBlocks ?? [], null, true, dmlMaxRows, dmlMaxSubtableRows)
12280
+ ]);
12201
12281
  }
12202
- function classifyNode(where, resolveField2) {
12203
- switch (where.type) {
12204
- case "BOOLEAN":
12205
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12206
- case "BINARY":
12207
- return classifyBinary(where.op, where.left, where.right, resolveField2);
12208
- case "NULL_CHECK":
12209
- if (where.field.type !== "FIELD") return localExpression();
12210
- return classifyLocalOnlyField(where.field, where.not ? "IS NOT NULL" : "IS NULL", resolveField2);
12211
- case "EXISTS":
12212
- return localExpression();
12213
- case "GROUP":
12214
- return classifyNode(where.expr, resolveField2);
12215
- case "NOT": {
12216
- const inner = classifyNode(where.expr, resolveField2);
12217
- if (inner.capability !== "SUPERSET_PREFILTER") return inner;
12218
- if (!hasRelativeDateReason(inner.reasons)) {
12219
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12220
- }
12221
- return requireExactFunctionPushdown({
12222
- capability: "LOCAL_ONLY",
12223
- reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }, ...inner.reasons]
12282
+ function diagnostic(statementKind, branches) {
12283
+ return {
12284
+ statementKind,
12285
+ branches,
12286
+ nonTransactional: true,
12287
+ partialSuccess: { possible: true }
12288
+ };
12289
+ }
12290
+ function buildPreparedInsertBranch(prepared) {
12291
+ const targets = /* @__PURE__ */ new Map();
12292
+ if (prepared.applyBlocks) {
12293
+ for (const block of prepared.applyBlocks) {
12294
+ const operations = block.operations.map((operation) => {
12295
+ if (operation.kind !== "APPEND") {
12296
+ throw new Error(`InternalError: prepared APPLY INSERT contains ${operation.kind}.`);
12297
+ }
12298
+ const addedRows = operation.values.length * prepared.guards.parentRows;
12299
+ return { kind: "APPEND", count: addedRows, addedRows };
12300
+ });
12301
+ targets.set(block.field, {
12302
+ targetKind: block.targetKind,
12303
+ field: block.field,
12304
+ operations,
12305
+ changedCount: operations.reduce((sum, operation) => sum + (operation.addedRows ?? 0), 0)
12224
12306
  });
12225
12307
  }
12226
- case "LOGICAL": {
12227
- const left = classifyNode(where.left, resolveField2);
12228
- const right = classifyNode(where.right, resolveField2);
12229
- return combineLogical(where.op, left, right);
12308
+ } else {
12309
+ for (const candidate of prepared.candidates) {
12310
+ for (const table of candidate.tables) {
12311
+ const current = targets.get(table.table);
12312
+ const addedRows = current?.operations[0]?.addedRows ?? 0;
12313
+ targets.set(table.table, {
12314
+ targetKind: "SUBTABLE",
12315
+ field: table.table,
12316
+ operations: [{ kind: "APPEND", count: addedRows + table.addedRows, addedRows: addedRows + table.addedRows }],
12317
+ changedCount: (current?.changedCount ?? 0) + table.addedRows
12318
+ });
12319
+ }
12230
12320
  }
12231
12321
  }
12322
+ return {
12323
+ branch: "insert",
12324
+ parentRows: prepared.guards.parentRows,
12325
+ targets: [...targets.values()],
12326
+ guards: prepared.guards,
12327
+ chunk: { size: 100, plannedChunks: prepared.batches.length },
12328
+ deletedParentRows: 0
12329
+ };
12232
12330
  }
12233
- function classifyBinary(op, left, right, resolveField2) {
12234
- if (right.type === "KINTONE_FUNC" && isRelativeDateFunctionName(right.name)) {
12235
- return classifyRelativeDateBinary(op, left, right, resolveField2);
12236
- }
12237
- if (right.type === "KINTONE_FUNC" && isLegacyKintoneFunction(right)) {
12238
- return classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2);
12239
- }
12240
- if (right.type === "IN_LIST") {
12241
- const functions = right.values.filter(
12242
- (value) => value.type === "KINTONE_FUNC"
12243
- );
12244
- if (functions.length > 0) {
12245
- if (right.values.length === 1 && functions.length === 1) {
12246
- return classifyLegacyKintoneFunctionBinary(op, left, functions[0], resolveField2);
12247
- }
12248
- return legacyKintoneFunctionUnsupported(
12249
- "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
12250
- functions[0].name,
12251
- left.type === "FIELD" ? left.field : void 0,
12252
- left.type === "FIELD" ? resolveField2(left)?.fieldType : void 0,
12253
- normalizeOperator(op)
12254
- );
12331
+ function buildPreparedUpdateBranch(prepared) {
12332
+ const targets = /* @__PURE__ */ new Map();
12333
+ let deletedParentRows = 0;
12334
+ for (const plan of prepared.plans) {
12335
+ let parentHasDeletes = false;
12336
+ for (const table of plan.tables) {
12337
+ const current = targets.get(table.table);
12338
+ targets.set(table.table, {
12339
+ targetKind: "SUBTABLE",
12340
+ field: table.table,
12341
+ operations: mergeOperations(current?.operations, table.operations.map((operation) => ({
12342
+ ...operation,
12343
+ count: operation.kind === "PATCH" ? operation.changedRows : operation.kind === "APPEND" ? operation.addedRows : operation.removedRows
12344
+ }))),
12345
+ changedCount: (current?.changedCount ?? 0) + table.changedSubtableRows
12346
+ });
12347
+ parentHasDeletes ||= table.deletedRows > 0;
12255
12348
  }
12256
- }
12257
- if (left.type !== "FIELD") return localExpression();
12258
- const semantics = resolveField2(left);
12259
- if (!semantics) {
12260
- return unsupported2("WHERE_FIELD_UNRESOLVED", left.field, void 0, normalizeOperator(op));
12261
- }
12262
- const nativeOp = normalizeOperator(op);
12263
- if (!isLocallyValidOperator(semantics.fieldType, nativeOp)) {
12264
- return unsupported2(
12265
- "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
12266
- left.field,
12267
- semantics.fieldType,
12268
- nativeOp
12269
- );
12270
- }
12271
- if (!hasLocalContract(semantics.fieldType, op)) {
12272
- return unsupported2("WHERE_OPERATOR_UNSUPPORTED", left.field, semantics.fieldType, nativeOp);
12273
- }
12274
- const native = nativeWhereOperatorsForType(semantics.fieldType);
12275
- const rightCanPush = right.type === "STRING" || right.type === "NUMBER" || right.type === "IN_LIST" || isLegacyKintoneFunction(right);
12276
- const structureAllows = !semantics.requiresCollectionOperators || nativeOp !== "=" && nativeOp !== "!=";
12277
- const sqlLikeIsResidual = op === "LIKE" || op === "NOT_LIKE";
12278
- if (rightCanPush && structureAllows && native.has(nativeOp) && !sqlLikeIsResidual) {
12279
- return {
12280
- capability: "EXACT_PUSHDOWN",
12281
- reasons: [{
12282
- code: "WHERE_EXACT",
12283
- field: left.field,
12284
- fieldType: semantics.fieldType,
12285
- operator: nativeOp
12286
- }]
12287
- };
12349
+ for (const field of plan.multiValues) {
12350
+ const current = targets.get(field.field);
12351
+ const postImage = { parentId: plan.parentId, value: field.postImageValue };
12352
+ targets.set(field.field, {
12353
+ targetKind: "MULTI_VALUE",
12354
+ field: field.field,
12355
+ fieldType: field.fieldType,
12356
+ operations: mergeOperations(current?.operations, field.operations.map((operation) => ({
12357
+ kind: operation.kind,
12358
+ value: operation.value,
12359
+ count: operation.changed ? 1 : 0,
12360
+ changed: operation.changed
12361
+ }))),
12362
+ changedCount: (current?.changedCount ?? 0) + field.changedValues,
12363
+ postImages: [...current?.postImages ?? [], postImage]
12364
+ });
12365
+ }
12366
+ if (parentHasDeletes) deletedParentRows += 1;
12288
12367
  }
12289
12368
  return {
12290
- capability: "LOCAL_ONLY",
12291
- reasons: [{
12292
- code: "WHERE_RESIDUAL",
12293
- field: left.field,
12294
- fieldType: semantics.fieldType,
12295
- operator: nativeOp
12296
- }]
12369
+ branch: "update",
12370
+ parentRows: prepared.guards.parentRows,
12371
+ targets: [...targets.values()],
12372
+ guards: prepared.guards,
12373
+ chunk: { size: 100, plannedChunks: Math.ceil(prepared.records.length / 100) },
12374
+ deletedParentRows
12297
12375
  };
12298
12376
  }
12299
- function isLegacyKintoneFunction(value) {
12300
- return value.type === "KINTONE_FUNC" && LEGACY_KINTONE_FUNCTION_NAMES.has(value.name);
12377
+ function mergeOperations(current, next) {
12378
+ if (!current) return next.map((operation) => ({ ...operation }));
12379
+ return next.map((operation, index) => {
12380
+ const previous = current[index];
12381
+ if (!previous || previous.kind !== operation.kind || previous.value !== operation.value) {
12382
+ throw new Error("InternalError: APPLY diagnostic operation shape differs between parents.");
12383
+ }
12384
+ return {
12385
+ ...operation,
12386
+ ...previous.changed !== void 0 ? { changed: previous.changed } : {},
12387
+ count: addNullable(previous.count, operation.count),
12388
+ ...operation.matchedRows !== void 0 ? { matchedRows: addNullable(previous.matchedRows, operation.matchedRows) } : {},
12389
+ ...operation.changedRows !== void 0 ? { changedRows: addNullable(previous.changedRows, operation.changedRows) } : {},
12390
+ ...operation.addedRows !== void 0 ? { addedRows: addNullable(previous.addedRows, operation.addedRows) } : {},
12391
+ ...operation.removedRows !== void 0 ? { removedRows: addNullable(previous.removedRows, operation.removedRows) } : {}
12392
+ };
12393
+ });
12301
12394
  }
12302
- function classifyLegacyKintoneFunctionBinary(op, left, right, resolveField2) {
12303
- const operator = normalizeOperator(op);
12304
- const functionName = right.name;
12305
- if (!LEGACY_KINTONE_FUNCTION_NAMES.has(functionName) || left.type !== "FIELD") {
12306
- return legacyKintoneFunctionUnsupported(
12307
- "WHERE_KINTONE_FUNCTION_CONTEXT_UNSUPPORTED",
12308
- functionName,
12309
- void 0,
12310
- void 0,
12311
- operator
12312
- );
12313
- }
12314
- const semantics = resolveField2(left);
12315
- const validFieldTypes = LEGACY_KINTONE_FUNCTION_FIELD_TYPES.get(functionName);
12316
- if (!semantics || !validFieldTypes.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
12317
- return legacyKintoneFunctionUnsupported(
12318
- "WHERE_KINTONE_FUNCTION_FIELD_TYPE_UNSUPPORTED",
12319
- functionName,
12320
- left.field,
12321
- semantics?.fieldType,
12322
- operator
12323
- );
12324
- }
12325
- const validOperators = LEGACY_KINTONE_FUNCTION_OPERATORS.get(functionName);
12326
- if (!validOperators.has(operator)) {
12327
- return legacyKintoneFunctionUnsupported(
12328
- "WHERE_KINTONE_FUNCTION_OPERATOR_UNSUPPORTED",
12329
- functionName,
12330
- left.field,
12331
- semantics.fieldType,
12332
- operator
12333
- );
12334
- }
12395
+ function addNullable(left, right) {
12396
+ return left === null || left === void 0 || right === null ? null : left + right;
12397
+ }
12398
+ function staticBranch(branch, blocks, parentRows, revisionRequired, dmlMaxRows, dmlMaxSubtableRows) {
12399
+ const targets = blocks.map((block) => {
12400
+ const operations = block.operations.map((operation) => staticOperation(operation, parentRows));
12401
+ const changedCount = operations.every((operation) => operation.count !== null) ? operations.reduce((sum, operation) => sum + operation.count, 0) : null;
12402
+ return {
12403
+ targetKind: block.targetKind,
12404
+ field: block.field,
12405
+ operations,
12406
+ changedCount
12407
+ };
12408
+ });
12409
+ const subtableTargets = targets.filter((target) => target.targetKind === "SUBTABLE");
12410
+ const subtableRows = subtableTargets.every((target) => target.changedCount !== null) ? subtableTargets.reduce((sum, target) => sum + target.changedCount, 0) : null;
12411
+ const wouldExceed = parentRows === null || subtableRows === null ? null : parentRows > dmlMaxRows || subtableRows > dmlMaxSubtableRows;
12335
12412
  return {
12336
- capability: "EXACT_PUSHDOWN",
12337
- reasons: [{
12338
- code: "WHERE_EXACT",
12339
- functionName,
12340
- field: left.field,
12341
- fieldType: semantics.fieldType,
12342
- operator
12343
- }]
12413
+ branch,
12414
+ parentRows,
12415
+ targets,
12416
+ guards: {
12417
+ revisionRequired,
12418
+ parentRows,
12419
+ dmlMaxRows,
12420
+ subtableRows,
12421
+ dmlMaxSubtableRows,
12422
+ wouldExceed
12423
+ },
12424
+ chunk: { size: 100, plannedChunks: parentRows === null ? null : Math.ceil(parentRows / 100) },
12425
+ deletedParentRows: branch === "insert" ? 0 : null
12344
12426
  };
12345
- }
12346
- function classifyRelativeDateBinary(op, left, right, resolveField2) {
12347
- const operator = normalizeOperator(op);
12348
- const functionName = right.name;
12349
- if (left.type !== "FIELD") {
12350
- return relativeDateUnsupported(
12351
- "WHERE_RELATIVE_DATE_CONTEXT_UNSUPPORTED",
12352
- functionName,
12353
- void 0,
12354
- void 0,
12355
- operator
12356
- );
12357
- }
12358
- const semantics = resolveField2(left);
12359
- if (!semantics) {
12360
- return relativeDateUnsupported(
12361
- "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
12362
- functionName,
12363
- left.field,
12364
- void 0,
12365
- operator
12366
- );
12367
- }
12368
- if (!hasValidRelativeDateArguments(right)) {
12369
- return relativeDateUnsupported(
12370
- "WHERE_RELATIVE_DATE_ARGUMENT_INVALID",
12371
- functionName,
12372
- left.field,
12373
- semantics.fieldType,
12374
- operator
12375
- );
12376
- }
12377
- if (!RELATIVE_DATE_OPERATORS.has(operator)) {
12378
- return relativeDateUnsupported(
12379
- "WHERE_RELATIVE_DATE_OPERATOR_UNSUPPORTED",
12380
- functionName,
12381
- left.field,
12382
- semantics.fieldType,
12383
- operator
12384
- );
12427
+ }
12428
+ function staticOperation(operation, parentRows) {
12429
+ if (operation.kind === "APPEND") {
12430
+ const addedRows = parentRows === null ? null : operation.values.length * parentRows;
12431
+ return { kind: operation.kind, count: addedRows, addedRows };
12385
12432
  }
12386
- if (!RELATIVE_DATE_FIELD_TYPES.has(semantics.fieldType) || semantics.inSubtable || semantics.requiresCollectionOperators) {
12387
- return relativeDateUnsupported(
12388
- "WHERE_RELATIVE_DATE_FIELD_TYPE_UNSUPPORTED",
12389
- functionName,
12390
- left.field,
12391
- semantics.fieldType,
12392
- operator
12433
+ if (operation.kind === "PATCH") return { kind: operation.kind, count: null, matchedRows: null, changedRows: null };
12434
+ if (operation.kind === "REMOVE") return { kind: operation.kind, count: null, removedRows: null };
12435
+ return { kind: operation.kind, value: operation.value, count: null };
12436
+ }
12437
+
12438
+ // src/core/applyPatchExecutePrepared.ts
12439
+ var ApplyWritePartialFailureError = class extends Error {
12440
+ constructor(partialSuccess, cause) {
12441
+ const detail = cause instanceof Error ? cause.message : String(cause);
12442
+ const method = partialSuccess.failedStage === "POST_CHUNK" ? "POST" : "PUT";
12443
+ const branch = partialSuccess.failedBranch ? ` UPSERT ${partialSuccess.failedBranch}` : "";
12444
+ super(
12445
+ `ApplyWritePartialFailureError: APPLY${branch} ${method} chunk ${partialSuccess.failedChunkIndex + 1} failed (index ${partialSuccess.failedChunkIndex}) after ${partialSuccess.successfulChunks} successful chunk(s) and ${partialSuccess.successfulParents} successful parent(s); writes are non-transactional and were not retried. Cause: ${detail}`
12393
12446
  );
12447
+ this.name = "ApplyWritePartialFailureError";
12448
+ this.partialSuccess = partialSuccess;
12449
+ this.cause = cause;
12450
+ }
12451
+ };
12452
+ async function executePreparedApplyWrite(prepared, client, diagnostic2) {
12453
+ assertApplyInternalWriteScope("phase10c");
12454
+ const batches = applyPatchPlansToKintoneBatches(prepared);
12455
+ let successfulChunks = 0;
12456
+ let successfulParents = 0;
12457
+ for (let failedChunkIndex = 0; failedChunkIndex < batches.length; failedChunkIndex += 1) {
12458
+ const batch = batches[failedChunkIndex];
12459
+ try {
12460
+ await client.putRecords(batch);
12461
+ } catch (cause) {
12462
+ throw new ApplyWritePartialFailureError({
12463
+ successfulChunks,
12464
+ successfulParents,
12465
+ failedChunkIndex,
12466
+ failedStage: "PUT_CHUNK",
12467
+ nonTransactional: true,
12468
+ retryAttempted: false,
12469
+ ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
12470
+ successfulChunks,
12471
+ successfulParents,
12472
+ failedChunkIndex,
12473
+ failedStage: "PUT_CHUNK",
12474
+ failedBranch: "UPDATE",
12475
+ retryAttempted: false
12476
+ }) } : {}
12477
+ }, cause);
12478
+ }
12479
+ successfulChunks += 1;
12480
+ successfulParents += batch.records.length;
12394
12481
  }
12395
12482
  return {
12396
- capability: "EXACT_PUSHDOWN",
12397
- reasons: [{
12398
- code: "WHERE_EXACT",
12399
- functionName,
12400
- field: left.field,
12401
- fieldType: semantics.fieldType,
12402
- operator
12403
- }]
12483
+ type: "UPDATE",
12484
+ updatedCount: successfulParents,
12485
+ successfulChunks,
12486
+ successfulParents,
12487
+ nonTransactional: true
12404
12488
  };
12405
12489
  }
12406
- function hasValidRelativeDateArguments(value) {
12407
- if (!("args" in value) || !value.args) return false;
12408
- switch (value.name) {
12409
- case "YESTERDAY":
12410
- case "TOMORROW":
12411
- case "THIS_YEAR":
12412
- case "LAST_YEAR":
12413
- case "NEXT_YEAR":
12414
- return value.args.kind === "NONE";
12415
- case "FROM_TODAY":
12416
- return value.args.kind === "FROM_TODAY" && Number.isSafeInteger(value.args.offset) && value.args.offsetText === String(value.args.offset === 0 ? 0 : value.args.offset) && (value.args.unit === "DAYS" || value.args.unit === "WEEKS" || value.args.unit === "MONTHS" || value.args.unit === "YEARS");
12417
- case "THIS_WEEK":
12418
- case "LAST_WEEK":
12419
- case "NEXT_WEEK":
12420
- return value.args.kind === "WEEK" && (value.args.weekday === null || value.args.weekday === "SUNDAY" || value.args.weekday === "MONDAY" || value.args.weekday === "TUESDAY" || value.args.weekday === "WEDNESDAY" || value.args.weekday === "THURSDAY" || value.args.weekday === "FRIDAY" || value.args.weekday === "SATURDAY");
12421
- case "THIS_MONTH":
12422
- case "LAST_MONTH":
12423
- case "NEXT_MONTH":
12424
- return value.args.kind === "MONTH" && (value.args.day === null || value.args.day === "LAST" || Number.isInteger(value.args.day) && value.args.day >= 1 && value.args.day <= 31);
12425
- default:
12426
- return false;
12490
+
12491
+ // src/core/applyInsertExecutePrepared.ts
12492
+ async function executePreparedApplyInsert(prepared, client, diagnostic2) {
12493
+ assertApplyInternalWriteScope("phase13c");
12494
+ const createdIds = [];
12495
+ let successfulChunks = 0;
12496
+ let successfulParents = 0;
12497
+ for (let failedChunkIndex = 0; failedChunkIndex < prepared.batches.length; failedChunkIndex += 1) {
12498
+ const batch = prepared.batches[failedChunkIndex];
12499
+ try {
12500
+ const response = await client.postRecords({ app: batch.app, records: [...batch.records] });
12501
+ createdIds.push(response.ids);
12502
+ } catch (cause) {
12503
+ throw new ApplyWritePartialFailureError({
12504
+ successfulChunks,
12505
+ successfulParents,
12506
+ failedChunkIndex,
12507
+ failedStage: "POST_CHUNK",
12508
+ nonTransactional: true,
12509
+ retryAttempted: false,
12510
+ ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
12511
+ successfulChunks,
12512
+ successfulParents,
12513
+ failedChunkIndex,
12514
+ failedStage: "POST_CHUNK",
12515
+ failedBranch: "INSERT",
12516
+ retryAttempted: false
12517
+ }) } : {}
12518
+ }, cause);
12519
+ }
12520
+ successfulChunks += 1;
12521
+ successfulParents += batch.records.length;
12427
12522
  }
12523
+ return {
12524
+ type: "INSERT",
12525
+ createdIds,
12526
+ insertedCount: successfulParents,
12527
+ successfulChunks,
12528
+ successfulParents,
12529
+ nonTransactional: true
12530
+ };
12428
12531
  }
12429
- function classifyLocalOnlyField(field, operator, resolveField2) {
12430
- const semantics = resolveField2(field);
12431
- if (!semantics) return unsupported2("WHERE_FIELD_UNRESOLVED", field.field, void 0, operator);
12432
- if (!isLocallyValidOperator(semantics.fieldType, operator)) {
12433
- return unsupported2(
12434
- "WHERE_OPERATOR_INVALID_FOR_FIELD_TYPE",
12435
- field.field,
12436
- semantics.fieldType,
12437
- operator
12438
- );
12532
+
12533
+ // src/core/applyUpsertExecutePrepared.ts
12534
+ async function executePreparedApplyUpsert(prepared, client, diagnostic2) {
12535
+ assertApplyInternalWriteScope("phase14c");
12536
+ const createdIds = [];
12537
+ let successfulInsertChunks = 0;
12538
+ let successfulUpdateChunks = 0;
12539
+ let successfulInserts = 0;
12540
+ let successfulUpdates = 0;
12541
+ for (let failedChunkIndex = 0; failedChunkIndex < prepared.createBatches.length; failedChunkIndex += 1) {
12542
+ const batch = prepared.createBatches[failedChunkIndex];
12543
+ try {
12544
+ const response = await client.postRecords({ app: batch.app, records: [...batch.records] });
12545
+ createdIds.push(response.ids);
12546
+ } catch (cause) {
12547
+ throw new ApplyWritePartialFailureError({
12548
+ successfulChunks: successfulInsertChunks + successfulUpdateChunks,
12549
+ successfulParents: successfulInserts + successfulUpdates,
12550
+ successfulInserts,
12551
+ successfulUpdates,
12552
+ failedChunkIndex,
12553
+ failedBranch: "INSERT",
12554
+ failedStage: "POST_CHUNK",
12555
+ nonTransactional: true,
12556
+ retryAttempted: false,
12557
+ ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
12558
+ successfulChunks: successfulInsertChunks + successfulUpdateChunks,
12559
+ successfulParents: successfulInserts + successfulUpdates,
12560
+ successfulInserts,
12561
+ successfulUpdates,
12562
+ successfulInsertChunks,
12563
+ successfulUpdateChunks,
12564
+ failedChunkIndex,
12565
+ failedStage: "POST_CHUNK",
12566
+ failedBranch: "INSERT",
12567
+ retryAttempted: false
12568
+ }) } : {}
12569
+ }, cause);
12570
+ }
12571
+ successfulInsertChunks += 1;
12572
+ successfulInserts += batch.records.length;
12439
12573
  }
12440
- if (!LOCAL_SCALAR_TYPES.has(semantics.fieldType) && !LOCAL_COLLECTION_TYPES.has(semantics.fieldType)) {
12441
- return unsupported2("WHERE_OPERATOR_UNSUPPORTED", field.field, semantics.fieldType, operator);
12574
+ for (let failedChunkIndex = 0; failedChunkIndex < prepared.updateBatches.length; failedChunkIndex += 1) {
12575
+ const batch = prepared.updateBatches[failedChunkIndex];
12576
+ try {
12577
+ await client.putRecords({ app: batch.app, records: [...batch.records] });
12578
+ } catch (cause) {
12579
+ throw new ApplyWritePartialFailureError({
12580
+ successfulChunks: successfulInsertChunks + successfulUpdateChunks,
12581
+ successfulParents: successfulInserts + successfulUpdates,
12582
+ successfulInserts,
12583
+ successfulUpdates,
12584
+ failedChunkIndex,
12585
+ failedBranch: "UPDATE",
12586
+ failedStage: "PUT_CHUNK",
12587
+ nonTransactional: true,
12588
+ retryAttempted: false,
12589
+ ...diagnostic2 ? { diagnostic: withApplyDiagnosticProgress(diagnostic2, {
12590
+ successfulChunks: successfulInsertChunks + successfulUpdateChunks,
12591
+ successfulParents: successfulInserts + successfulUpdates,
12592
+ successfulInserts,
12593
+ successfulUpdates,
12594
+ successfulInsertChunks,
12595
+ successfulUpdateChunks,
12596
+ failedChunkIndex,
12597
+ failedStage: "PUT_CHUNK",
12598
+ failedBranch: "UPDATE",
12599
+ retryAttempted: false
12600
+ }) } : {}
12601
+ }, cause);
12602
+ }
12603
+ successfulUpdateChunks += 1;
12604
+ successfulUpdates += batch.records.length;
12442
12605
  }
12443
12606
  return {
12444
- capability: "LOCAL_ONLY",
12445
- reasons: [{ code: "WHERE_RESIDUAL", field: field.field, fieldType: semantics.fieldType, operator }]
12607
+ type: "UPSERT",
12608
+ createdIds,
12609
+ insertedCount: successfulInserts,
12610
+ updatedCount: successfulUpdates,
12611
+ successfulChunks: successfulInsertChunks + successfulUpdateChunks,
12612
+ successfulParents: successfulInserts + successfulUpdates,
12613
+ successfulInsertChunks,
12614
+ successfulUpdateChunks,
12615
+ nonTransactional: true
12446
12616
  };
12447
12617
  }
12448
- function isLocallyValidOperator(fieldType, operator) {
12449
- const policy = LOCAL_VALID_OPERATORS.get(fieldType);
12450
- return policy === void 0 || policy.has(operator);
12451
- }
12452
- function hasLocalContract(fieldType, op) {
12453
- if (LOCAL_SCALAR_TYPES.has(fieldType)) return true;
12454
- if (!LOCAL_COLLECTION_TYPES.has(fieldType)) return false;
12455
- return op === "=" || op === "!=" || op === "<>" || op === "IN" || op === "NOT_IN" || op === "LIKE" || op === "NOT_LIKE" || op === "KLIKE" || op === "NOT_KLIKE";
12456
- }
12457
- function normalizeOperator(op) {
12458
- switch (op) {
12459
- case "<>":
12460
- return "!=";
12461
- case "IN":
12462
- return "in";
12463
- case "NOT_IN":
12464
- return "not in";
12465
- case "LIKE":
12466
- case "KLIKE":
12467
- return "like";
12468
- case "NOT_LIKE":
12469
- case "NOT_KLIKE":
12470
- return "not like";
12471
- default:
12472
- return op;
12618
+
12619
+ // src/core/batchVariables.ts
12620
+ var VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
12621
+ function normalizeBatchVariableName(name) {
12622
+ if (!VARIABLE_NAME_RE.test(name)) {
12623
+ throw new Error(
12624
+ `ArgumentError: invalid variable name "${name}". Use a name without @ matching [A-Za-z_][A-Za-z0-9_]{0,63}.`
12625
+ );
12473
12626
  }
12627
+ return name.toLowerCase();
12474
12628
  }
12475
- function combineLogical(op, left, right) {
12476
- const reasons = [...left.reasons, ...right.reasons];
12477
- if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
12478
- return requireExactFunctionPushdown({ capability: "UNSUPPORTED", reasons });
12479
- }
12480
- if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
12481
- return { capability: "EXACT_PUSHDOWN", reasons };
12482
- }
12483
- if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
12484
- return requireExactFunctionPushdown({
12485
- capability: "SUPERSET_PREFILTER",
12486
- reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
12487
- });
12629
+ function normalizeBatchVariables(input) {
12630
+ const normalized = /* @__PURE__ */ Object.create(null);
12631
+ for (const [rawName, value] of Object.entries(input ?? {})) {
12632
+ const name = normalizeBatchVariableName(rawName);
12633
+ if (Object.prototype.hasOwnProperty.call(normalized, name)) {
12634
+ throw new Error(`ArgumentError: variable "${rawName}" is specified more than once.`);
12635
+ }
12636
+ normalized[name] = value;
12488
12637
  }
12489
- return requireExactFunctionPushdown({ capability: "LOCAL_ONLY", reasons });
12490
- }
12491
- function localExpression() {
12492
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
12493
- }
12494
- function unsupported2(code, field, fieldType, operator) {
12495
- return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
12496
- }
12497
- function legacyKintoneFunctionUnsupported(code, functionName, field, fieldType, operator) {
12498
- return requireExactFunctionPushdown({
12499
- capability: "UNSUPPORTED",
12500
- reasons: [{ code, functionName, field, fieldType, operator }]
12501
- });
12502
- }
12503
- function relativeDateUnsupported(code, functionName, field, fieldType, operator) {
12504
- return requireExactRelativeDatePushdown({
12505
- capability: "UNSUPPORTED",
12506
- reasons: [{ code, functionName, field, fieldType, operator }]
12507
- });
12638
+ return normalized;
12508
12639
  }
12509
- function hasRelativeDateReason(reasons) {
12510
- return reasons.some(
12511
- (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12640
+ function validateDeclaredBatchVariables(statements, input) {
12641
+ const normalized = normalizeBatchVariables(input);
12642
+ const declared = new Set(
12643
+ statements.filter((stmt) => stmt.type === "DECLARE_VARIABLE").map((stmt) => stmt.name)
12512
12644
  );
12645
+ for (const name of Object.keys(normalized)) {
12646
+ if (!declared.has(name)) {
12647
+ throw new Error(`ArgumentError: injected variable @${name} is not declared.`);
12648
+ }
12649
+ }
12650
+ return normalized;
12513
12651
  }
12514
- function hasLegacyKintoneFunctionReason(reasons) {
12515
- return reasons.some(
12516
- (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
12517
- );
12652
+
12653
+ // src/core/outerJoinSearchAbortGuard.ts
12654
+ function isOuterJoinSelect(value) {
12655
+ if (value["type"] !== "SELECT" || !Array.isArray(value["joins"])) return false;
12656
+ return value["joins"].some((join2) => {
12657
+ if (join2 === null || typeof join2 !== "object") return false;
12658
+ const type = join2["type"];
12659
+ return type === "LEFT" || type === "RIGHT";
12660
+ });
12518
12661
  }
12519
- function requireExactRelativeDatePushdown(result) {
12520
- if (result.capability === "EXACT_PUSHDOWN" || !hasRelativeDateReason(result.reasons) || result.reasons.some((reason) => reason.code === "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN")) {
12521
- return result;
12522
- }
12523
- const relative = result.reasons.find(
12524
- (reason) => reason.code.startsWith("WHERE_RELATIVE_DATE_") || reason.functionName !== void 0 && isRelativeDateFunctionName(reason.functionName)
12525
- );
12526
- return {
12527
- capability: result.capability,
12528
- reasons: [
12529
- ...result.reasons,
12530
- {
12531
- code: "WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN",
12532
- functionName: relative.functionName,
12533
- field: relative.field,
12534
- fieldType: relative.fieldType,
12535
- operator: relative.operator
12536
- }
12537
- ]
12662
+ function statementContainsOuterJoin(statement) {
12663
+ const seen = /* @__PURE__ */ new Set();
12664
+ const visit = (value) => {
12665
+ if (Array.isArray(value)) return value.some(visit);
12666
+ if (value === null || typeof value !== "object" || seen.has(value)) return false;
12667
+ seen.add(value);
12668
+ const object = value;
12669
+ if (isOuterJoinSelect(object)) return true;
12670
+ return Object.values(object).some(visit);
12538
12671
  };
12672
+ return visit(statement);
12539
12673
  }
12540
- function requireExactLegacyKintoneFunctionPushdown(result) {
12541
- if (result.capability === "EXACT_PUSHDOWN" || !hasLegacyKintoneFunctionReason(result.reasons) || result.reasons.some(
12542
- (reason) => reason.code === "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN"
12543
- )) {
12544
- return result;
12674
+ function isOuterJoinNonPreservedTable(statement, table, isMainTable) {
12675
+ if (isMainTable) {
12676
+ return table === statement.from && statement.joins.some((join2) => join2.type === "RIGHT");
12545
12677
  }
12546
- const legacy = result.reasons.find(
12547
- (reason) => reason.code.startsWith("WHERE_KINTONE_FUNCTION_") || reason.functionName !== void 0 && LEGACY_KINTONE_FUNCTION_NAMES.has(reason.functionName)
12548
- );
12549
- return {
12550
- capability: result.capability,
12551
- reasons: [
12552
- ...result.reasons,
12553
- {
12554
- code: "WHERE_KINTONE_FUNCTION_REQUIRES_EXACT_PUSHDOWN",
12555
- functionName: legacy.functionName,
12556
- field: legacy.field,
12557
- fieldType: legacy.fieldType,
12558
- operator: legacy.operator
12559
- }
12560
- ]
12561
- };
12562
- }
12563
- function requireExactFunctionPushdown(result) {
12564
- return requireExactLegacyKintoneFunctionPushdown(
12565
- requireExactRelativeDatePushdown(result)
12566
- );
12678
+ for (let index = 0; index < statement.joins.length; index += 1) {
12679
+ const join2 = statement.joins[index];
12680
+ if (join2.type === "LEFT" && table === join2.table) return true;
12681
+ if (join2.type === "RIGHT" && statement.joins.slice(0, index).some((previousJoin) => table === previousJoin.table)) {
12682
+ return true;
12683
+ }
12684
+ }
12685
+ return false;
12567
12686
  }
12568
12687
 
12569
12688
  // src/core/optimization/joinKeyPrefilter.ts
@@ -12580,7 +12699,7 @@ function buildJoinKeyPrefilterQueries(plan, field, quoteValue) {
12580
12699
  }
12581
12700
  return queries;
12582
12701
  }
12583
- var DATETIME_TYPES = /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"]);
12702
+ var DATETIME_TYPES2 = /* @__PURE__ */ new Set(["DATETIME", "CREATED_TIME", "UPDATED_TIME"]);
12584
12703
  var JOIN_KEY_EMPTY_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
12585
12704
  "SINGLE_LINE_TEXT",
12586
12705
  "LINK",
@@ -12595,14 +12714,14 @@ var JOIN_KEY_EMPTY_IN_FIELD_TYPES = /* @__PURE__ */ new Set([
12595
12714
  function canonicalFor(fieldType, value) {
12596
12715
  if (fieldType === "DATE") return isCanonicalJoinDate(value);
12597
12716
  if (fieldType === "TIME") return isCanonicalJoinTime(value);
12598
- if (DATETIME_TYPES.has(fieldType)) return isCanonicalJoinDateTime(value);
12717
+ if (DATETIME_TYPES2.has(fieldType)) return isCanonicalJoinDateTime(value);
12599
12718
  return false;
12600
12719
  }
12601
12720
  function semanticsMatch(fieldType, semantics) {
12602
12721
  if (!semantics || semantics.compareMode !== "string") return false;
12603
12722
  if (fieldType === "DATE") return semantics.fieldType === "DATE";
12604
12723
  if (fieldType === "TIME") return semantics.fieldType === "TIME";
12605
- if (DATETIME_TYPES.has(fieldType)) return DATETIME_TYPES.has(semantics.fieldType);
12724
+ if (DATETIME_TYPES2.has(fieldType)) return DATETIME_TYPES2.has(semantics.fieldType);
12606
12725
  return false;
12607
12726
  }
12608
12727
  function planJoinKeyPrefilter(input) {
@@ -12904,49 +13023,7 @@ async function resolveDmlTargetIds(getRecords, app, query, options) {
12904
13023
  };
12905
13024
  }
12906
13025
 
12907
- // src/core/optimization/joinNumberLiteralPolicy.ts
12908
- function isJoinNumberLiteralSupported(literal) {
12909
- const source = literal.raw ?? String(literal.value);
12910
- const decimal = parseExactDecimal(source);
12911
- if (decimal === null) return false;
12912
- if (decimal.sign === 0) return numberLiteralText(literal) === "0";
12913
- const fractionDigits = Math.max(decimal.scale, 0);
12914
- const integerDigits = Math.max(decimal.coefficient.length - decimal.scale, 0);
12915
- if (fractionDigits > 10 || integerDigits + fractionDigits > 30) {
12916
- return false;
12917
- }
12918
- const canonical = formatPlainDecimal(decimal);
12919
- return numberLiteralText(literal) === canonical;
12920
- }
12921
-
12922
13026
  // src/core/optimization/joinPredicatePushdown.ts
12923
- var SELECTION_TYPES = /* @__PURE__ */ new Set([
12924
- "DROP_DOWN",
12925
- "RADIO_BUTTON",
12926
- "CHECK_BOX",
12927
- "MULTI_SELECT",
12928
- "STATUS"
12929
- ]);
12930
- var USER_CODE_TYPES = /* @__PURE__ */ new Set([
12931
- "CREATOR",
12932
- "MODIFIER",
12933
- "USER_SELECT",
12934
- "ORGANIZATION_SELECT",
12935
- "GROUP_SELECT",
12936
- "STATUS_ASSIGNEE"
12937
- ]);
12938
- var KLIKE_TYPES = /* @__PURE__ */ new Set([
12939
- "SINGLE_LINE_TEXT",
12940
- "LINK",
12941
- "MULTI_LINE_TEXT",
12942
- "RICH_TEXT",
12943
- "FILE"
12944
- ]);
12945
- var DATETIME_TYPES2 = /* @__PURE__ */ new Set([
12946
- "DATETIME",
12947
- "CREATED_TIME",
12948
- "UPDATED_TIME"
12949
- ]);
12950
13027
  function resolveJoinFieldOwner(field, sources) {
12951
13028
  if (field.tableAlias !== null) {
12952
13029
  const aliases = sources.filter((source2) => source2.alias === field.tableAlias);
@@ -12974,7 +13051,11 @@ function classifyJoinPushdownLeaf(predicate, sources) {
12974
13051
  return type === void 0 ? void 0 : resolveFieldSemantics({ fieldType: type });
12975
13052
  });
12976
13053
  if (capability.capability !== "EXACT_PUSHDOWN") return unsafe();
12977
- const relation = classifySupportedLeaf(predicate, owner, fieldType);
13054
+ const relation = classifySupportedLeaf(predicate, {
13055
+ fieldCode: owner.fieldCode,
13056
+ fieldType,
13057
+ fieldOptions: owner.source.fieldOptions?.get(owner.fieldCode)
13058
+ });
12978
13059
  return relation === "unsafe" ? unsafe() : Object.freeze({ relation, owner });
12979
13060
  }
12980
13061
  function classifyJoinServerFunctionLeaf(predicate, sources) {
@@ -13529,78 +13610,6 @@ function mergeAndFragments(fragments) {
13529
13610
  }
13530
13611
  return merged;
13531
13612
  }
13532
- function classifySupportedLeaf(predicate, owner, fieldType) {
13533
- if (predicate.op === "LIKE" || predicate.op === "NOT_LIKE") return "unsafe";
13534
- if (predicate.op === "KLIKE" || predicate.op === "NOT_KLIKE") {
13535
- return KLIKE_TYPES.has(fieldType) && predicate.right.type === "STRING" && predicate.right.value !== "" ? "exact" : "unsafe";
13536
- }
13537
- if (fieldType === "__ID__" || owner.fieldCode === "$id") {
13538
- return isPositiveSafeInteger(predicate.right) && (predicate.op === "=" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") ? "exact" : "unsafe";
13539
- }
13540
- if (fieldType === "RECORD_NUMBER") {
13541
- return classifySupersetScalarOrListLiteral(predicate);
13542
- }
13543
- if (fieldType === "NUMBER") {
13544
- if ((predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST" && predicate.right.values.length > 0 && predicate.right.values.every(
13545
- (value) => value.type === "NUMBER" && isJoinNumberLiteralSupported(value)
13546
- )) {
13547
- return "exact";
13548
- }
13549
- if ((predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") && predicate.right.type === "NUMBER" && isJoinNumberLiteralSupported(predicate.right)) {
13550
- return "exact";
13551
- }
13552
- return "unsafe";
13553
- }
13554
- if (fieldType === "CALC") {
13555
- return classifySupersetScalarOrListLiteral(predicate);
13556
- }
13557
- if (USER_CODE_TYPES.has(fieldType)) {
13558
- if (predicate.op !== "IN" && predicate.op !== "NOT_IN" || predicate.right.type !== "IN_LIST" || predicate.right.values.length === 0) return "unsafe";
13559
- return predicate.right.values.every(
13560
- (value) => value.type === "STRING" && value.value !== ""
13561
- ) ? "exact" : "unsafe";
13562
- }
13563
- if (fieldType === "SINGLE_LINE_TEXT" || fieldType === "LINK") {
13564
- if ((predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST" && predicate.right.values.length > 0 && predicate.right.values.every(
13565
- (value) => value.type === "STRING" && value.value !== ""
13566
- )) {
13567
- return "exact";
13568
- }
13569
- return (predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>") && predicate.right.type === "STRING" && predicate.right.value !== "" ? "exact" : "unsafe";
13570
- }
13571
- if (fieldType === "DATE" || fieldType === "TIME" || DATETIME_TYPES2.has(fieldType)) {
13572
- if (predicate.op !== "=" && predicate.op !== "!=" && predicate.op !== "<>" && predicate.op !== "<" && predicate.op !== ">" && predicate.op !== "<=" && predicate.op !== ">=" || predicate.right.type !== "STRING") return "unsafe";
13573
- if (fieldType === "DATE") {
13574
- return isCanonicalJoinDate(predicate.right.value) ? "exact" : "unsafe";
13575
- }
13576
- if (fieldType === "TIME") {
13577
- return isCanonicalJoinTime(predicate.right.value) ? "exact" : "unsafe";
13578
- }
13579
- return isCanonicalJoinDateTime(predicate.right.value) ? "exact" : "unsafe";
13580
- }
13581
- if (SELECTION_TYPES.has(fieldType)) {
13582
- if (predicate.op !== "IN" && predicate.op !== "NOT_IN" || predicate.right.type !== "IN_LIST" || predicate.right.values.length === 0) return "unsafe";
13583
- const options = owner.source.fieldOptions?.get(owner.fieldCode);
13584
- if (options === void 0) return "unsafe";
13585
- return predicate.right.values.every(
13586
- (value) => value.type === "STRING" && value.value !== "" && options.has(value.value)
13587
- ) ? "exact" : "unsafe";
13588
- }
13589
- return "unsafe";
13590
- }
13591
- function classifySupersetScalarOrListLiteral(predicate) {
13592
- const supportedLiteral = (value) => value.type === "NUMBER" && isJoinNumberLiteralSupported(value) || value.type === "STRING" && value.value !== "";
13593
- if ((predicate.op === "IN" || predicate.op === "NOT_IN") && predicate.right.type === "IN_LIST") {
13594
- const values = predicate.right.values;
13595
- if (values.length > 0 && values.every(supportedLiteral) && values.every((value) => value.type === values[0].type)) {
13596
- return "superset";
13597
- }
13598
- }
13599
- if ((predicate.op === "=" || predicate.op === "!=" || predicate.op === "<>" || predicate.op === "<" || predicate.op === ">" || predicate.op === "<=" || predicate.op === ">=") && supportedLiteral(predicate.right)) {
13600
- return "superset";
13601
- }
13602
- return "unsafe";
13603
- }
13604
13613
  function owned(source, fieldCode) {
13605
13614
  return Object.freeze({
13606
13615
  status: "OWNED",
@@ -13619,12 +13628,6 @@ function sameOwner(left, right) {
13619
13628
  function combineRelation(left, right) {
13620
13629
  return left === "exact" && right === "exact" ? "exact" : "superset";
13621
13630
  }
13622
- function isPositiveSafeInteger(value) {
13623
- return value.type === "NUMBER" && isSafeIntegerLiteral(value) && value.value > 0;
13624
- }
13625
- function isSafeIntegerLiteral(value) {
13626
- return /^-?\d+$/.test(numberLiteralText(value)) && Number.isSafeInteger(value.value);
13627
- }
13628
13631
  function collectKlikes2(where, out) {
13629
13632
  if (where === null) return;
13630
13633
  if (isKlike(where)) {
@@ -15785,6 +15788,11 @@ function rejectionFor(candidate, capability) {
15785
15788
  reasonCodes
15786
15789
  };
15787
15790
  }
15791
+ function statementUsesRelativeDateResolution(statement) {
15792
+ const candidates = [];
15793
+ collectStatement(statement, "statement", candidates);
15794
+ return candidates.length > 0;
15795
+ }
15788
15796
  async function buildRelativeDatePushdownPlan(statement, resolver) {
15789
15797
  const candidates = [];
15790
15798
  collectStatement(statement, "statement", candidates);
@@ -19473,6 +19481,20 @@ function extractMainTypedPushdownCandidate(stmt) {
19473
19481
  if (!stmt.from.alias) return null;
19474
19482
  return extractTypedPushdownCandidates(stmt.where, { tableAlias: stmt.from.alias });
19475
19483
  }
19484
+ function hasPushdownPlaceholder(where) {
19485
+ if (where.type === "BINARY") {
19486
+ if (where.right.type === "STRING") return where.right.value.startsWith("@");
19487
+ if (where.right.type === "IN_LIST") {
19488
+ return where.right.values.some((value) => value.type === "STRING" && value.value.startsWith("@"));
19489
+ }
19490
+ return false;
19491
+ }
19492
+ if (where.type === "LOGICAL") {
19493
+ return hasPushdownPlaceholder(where.left) || hasPushdownPlaceholder(where.right);
19494
+ }
19495
+ if (where.type === "GROUP" || where.type === "NOT") return hasPushdownPlaceholder(where.expr);
19496
+ return false;
19497
+ }
19476
19498
  var boundJoinRuntimePlans = /* @__PURE__ */ new WeakMap();
19477
19499
  function buildRuntimeJoinPushdownPlan(stmt, metadata) {
19478
19500
  if (stmt.joins.length === 0 || stmt.where === null || stmt.joins.some((join2) => join2.type !== "INNER")) {
@@ -19511,11 +19533,16 @@ function buildRuntimeJoinPushdownPlan(stmt, metadata) {
19511
19533
  const mainCondition = conditionsByAlias.get(mainAlias) ?? null;
19512
19534
  const joinConditions = new Map(conditionsByAlias);
19513
19535
  joinConditions.delete(mainAlias);
19536
+ const relationByAlias = new Map(plan.items.map((item) => [item.targetAlias, item.relation]));
19537
+ const mainRelation = relationByAlias.get(mainAlias) ?? null;
19538
+ relationByAlias.delete(mainAlias);
19514
19539
  return {
19515
19540
  joinPlan: plan,
19516
19541
  queriesByAlias,
19517
19542
  mainCondition,
19543
+ mainRelation,
19518
19544
  joinConditions,
19545
+ joinRelations: relationByAlias,
19519
19546
  appliedKlikes: plan.appliedKlikes,
19520
19547
  allKlikes: plan.allKlikes
19521
19548
  };
@@ -24243,6 +24270,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
24243
24270
  return cache;
24244
24271
  }
24245
24272
  var explainJoinPushdownPlans = /* @__PURE__ */ new WeakMap();
24273
+ var explainPushdownPlans = /* @__PURE__ */ new WeakMap();
24246
24274
  var explainJoinKeyPrefilters = /* @__PURE__ */ new WeakMap();
24247
24275
  var explainChoiceEqualityRewrites = /* @__PURE__ */ new WeakMap();
24248
24276
  var validateExplainInfo = /* @__PURE__ */ new WeakMap();
@@ -24440,12 +24468,12 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24440
24468
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
24441
24469
  }
24442
24470
  capabilities.set(select, capability);
24471
+ const pushdownMetadata = await loadTypedPushdownMeta(select, tracedClient, cacheContext);
24443
24472
  const preboundJoinPushdownPlan = boundJoinRuntimePlans.get(select);
24444
- const joinPushdownPlan = preboundJoinPushdownPlan ?? buildRuntimeJoinPushdownPlan(
24445
- select,
24446
- await loadTypedPushdownMeta(select, tracedClient, cacheContext)
24447
- );
24473
+ const joinPushdownPlan = preboundJoinPushdownPlan ?? buildRuntimeJoinPushdownPlan(select, pushdownMetadata);
24448
24474
  if (joinPushdownPlan) explainJoinPushdownPlans.set(select, joinPushdownPlan);
24475
+ const resolvedPushdownPlan = joinPushdownPlan ?? buildKlikePushdownPlan(select, pushdownMetadata);
24476
+ explainPushdownPlans.set(select, resolvedPushdownPlan);
24449
24477
  const joinKeyPlans = /* @__PURE__ */ new Map();
24450
24478
  for (const join2 of select.joins) {
24451
24479
  const joinAlias = join2.table.alias;
@@ -24494,20 +24522,9 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
24494
24522
  if (runtimePlanConsumesJoin) continue;
24495
24523
  }
24496
24524
  const queries = buildJoinKeyPrefilterQueries(plan, joinField, sqlQuote);
24497
- const additionalCandidate = select.where ? extractTypedPushdownCandidates(select.where, { tableAlias: joinAlias }) : null;
24498
- let additionalQuery;
24499
- let additionalRelation;
24500
- if (additionalCandidate) {
24501
- const infoByCode = new Map(targetInfos.map((info) => [info.code, info]));
24502
- const additionalCapability = classifyWhereCapability(additionalCandidate, (field) => {
24503
- const info = infoByCode.get(field.field);
24504
- return info?.semantics ?? (info ? resolveFieldSemantics(info) : systemColumnMeta(field.field)?.semantics);
24505
- });
24506
- if (additionalCapability.capability === "EXACT_PUSHDOWN" || additionalCapability.capability === "SUPERSET_PREFILTER") {
24507
- additionalQuery = whereToKintone(additionalCandidate);
24508
- additionalRelation = additionalCapability.capability === "EXACT_PUSHDOWN" ? "exact" : "superset";
24509
- }
24510
- }
24525
+ const additionalCondition = resolvedPushdownPlan.joinConditions.get(joinAlias);
24526
+ const additionalQuery = additionalCondition ? whereToKintone(additionalCondition) : void 0;
24527
+ const additionalRelation = resolvedPushdownPlan.joinRelations.get(joinAlias);
24511
24528
  joinKeyPlans.set(joinAlias, { plan, queries, additionalQuery, additionalRelation });
24512
24529
  }
24513
24530
  if (joinKeyPlans.size > 0) explainJoinKeyPrefilters.set(select, joinKeyPlans);
@@ -24940,7 +24957,7 @@ var EXPLAIN_FETCH_PLAN = /* @__PURE__ */ Symbol("ksql.explainFetchPlan");
24940
24957
  function setExplainFetchPlan(result, plan) {
24941
24958
  result[EXPLAIN_FETCH_PLAN] = plan;
24942
24959
  }
24943
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS) {
24960
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false, dmlMaxRows = 100, dmlMaxSubtableRows = DEFAULT_APPLY_MAX_SUBTABLE_ROWS, resolveMetadata = true) {
24944
24961
  const invocationCacheContext = createInvocationCacheContext(cacheContext);
24945
24962
  try {
24946
24963
  const statements = parseSqlBatch(sql, enableImport);
@@ -24955,13 +24972,21 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
24955
24972
  const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
24956
24973
  validateStatementStatic(planStmt);
24957
24974
  const relativeDatePlan = await resolveRelativeDateExecutionPlan(planStmt, client, invocationCacheContext);
24958
- const whereAnalysis = await buildExplainWhereAnalysis(
24975
+ const whereAnalysis = resolveMetadata ? await buildExplainWhereAnalysis(
24959
24976
  planStmt,
24960
24977
  client,
24961
24978
  invocationCacheContext,
24962
24979
  maxRecords,
24963
24980
  relativeDatePlan
24964
- );
24981
+ ) : {
24982
+ capabilities: /* @__PURE__ */ new Map(),
24983
+ orderPlans: /* @__PURE__ */ new Map(),
24984
+ plainGroupByPlans: /* @__PURE__ */ new Map(),
24985
+ fieldApps: /* @__PURE__ */ new Set(),
24986
+ processStatusApps: /* @__PURE__ */ new Set(),
24987
+ numberPrecisionApps: /* @__PURE__ */ new Set(),
24988
+ relativeDatePlan
24989
+ };
24965
24990
  const fetchCollector = { sources: [] };
24966
24991
  const statementPlan = relativeDatePlan.hasServerOnlyWhereFunction && !relativeDatePlan.allowed ? relativeDateExplainLines(relativeDatePlan) : [
24967
24992
  ...relativeDateExplainLines(relativeDatePlan),
@@ -25554,7 +25579,8 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25554
25579
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
25555
25580
  } else {
25556
25581
  const runtimeJoinPlan = explainJoinPushdownPlans.get(stmt);
25557
- const pushdownPlan = runtimeJoinPlan ?? buildKlikePushdownPlan(stmt);
25582
+ const metadataAwarePushdownPlan = explainPushdownPlans.get(stmt);
25583
+ const pushdownPlan = metadataAwarePushdownPlan ?? buildKlikePushdownPlan(stmt);
25558
25584
  if (stmt.joins.length > 0) {
25559
25585
  if (runtimeJoinPlan) {
25560
25586
  lines.push(" join pushdown plan: applied (runtime metadata resolved)");
@@ -25581,7 +25607,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25581
25607
  const reason = stmt.joins.some((join2) => join2.type !== "INNER") ? "OUTER_JOIN" : [stmt.from, ...stmt.joins.map((join2) => join2.table)].some(
25582
25608
  (table) => table.cteName !== null || Boolean(table.subtableCode)
25583
25609
  ) ? "SOURCE_KIND" : "PLAN_NOT_APPLICABLE";
25584
- lines.push(" join pushdown plan: not applied");
25610
+ lines.push(" join pushdown plan: not applied (join key/WHERE prefilters are reported per source below)");
25585
25611
  lines.push(` join pushdown not applied: ${reason}`);
25586
25612
  }
25587
25613
  }
@@ -25601,7 +25627,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25601
25627
  (consumption) => consumption.targetAlias === stmt.from.alias
25602
25628
  );
25603
25629
  if (emitFetch && stmt.from.cteName === null) {
25604
- const mainPending = !runtimeJoinPlan && mainCandidate !== null;
25630
+ const mainPending = !metadataAwarePushdownPlan && mainCandidate !== null;
25605
25631
  const mainFetchScope = mainQ === "(\u5168\u4EF6\u53D6\u5F97)" ? "ALL" : mainPending ? "PREFILTERED" : mainJoinItem?.relation === "exact" || mainFunctionConsumption || exactOriginalWhere !== "" ? "EXACT" : "PREFILTERED";
25606
25632
  lines.push(renderFetchScope(createExplainFetchSource(
25607
25633
  collector,
@@ -25616,7 +25642,10 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25616
25642
  if (mainJoinItem || mainFunctionConsumption) {
25617
25643
  lines.push(` pushdown applied: ${mainBoundQuery}`);
25618
25644
  lines.push(` relation: ${mainJoinItem?.relation ?? "exact"}`);
25619
- } else if (!runtimeJoinPlan && mainCandidate !== null) {
25645
+ } else if (metadataAwarePushdownPlan && mainPushDown !== null) {
25646
+ lines.push(` pushdown applied: ${mainQ}`);
25647
+ lines.push(` relation: ${pushdownPlan.mainRelation ?? "exact"}`);
25648
+ } else if (mainCandidate !== null && (!metadataAwarePushdownPlan || hasPushdownPlaceholder(mainCandidate))) {
25620
25649
  lines.push(` pushdown candidate: ${whereToKintone(mainCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
25621
25650
  }
25622
25651
  lines.push(` fields: ${mainFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : mainFields.join(", ")}`);
@@ -25628,8 +25657,9 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25628
25657
  const joinCandidate = join2.table.alias && !join2.table.subtableCode && join2.table.cteName === null && stmt.where ? extractTypedPushdownCandidates(stmt.where, { tableAlias: join2.table.alias }) : null;
25629
25658
  const joinBoundQuery = join2.table.alias ? runtimeJoinPlan?.queriesByAlias.get(join2.table.alias) : void 0;
25630
25659
  const joinKey = join2.table.alias ? explainJoinKeyPrefilters.get(stmt)?.get(join2.table.alias) : void 0;
25660
+ const offlineJoinKeyCandidate = !metadataAwarePushdownPlan && join2.type === "INNER" && join2.table.cteName === null && !join2.table.subtableCode && [stmt.from, ...stmt.joins.map((candidate) => candidate.table)].some((table) => table.cteName !== null);
25631
25661
  const baseJoinQ = joinBoundQuery || (joinPushDown !== null ? whereToKintone(joinPushDown) : joinKey?.additionalQuery ? joinKey.additionalQuery : "(\u5168\u4EF6\u53D6\u5F97)");
25632
- const runtimeJoinKeyCandidate = joinKey?.plan.kind === "RANGE_CANDIDATE" || joinKey?.plan.kind === "FALLBACK" && joinKey.plan.reason === "JOIN_KEY_VALUES_RUNTIME";
25662
+ const runtimeJoinKeyCandidate = offlineJoinKeyCandidate || joinKey?.plan.kind === "RANGE_CANDIDATE" || joinKey?.plan.kind === "FALLBACK" && joinKey.plan.reason === "JOIN_KEY_VALUES_RUNTIME";
25633
25663
  const joinQueries = joinKey && joinKey.queries.length > 0 ? joinKey.queries.map((query) => baseJoinQ !== "(\u5168\u4EF6\u53D6\u5F97)" ? `(${query}) and (${baseJoinQ})` : query) : [runtimeJoinKeyCandidate ? "(runtime source keys)" : baseJoinQ];
25634
25664
  const joinQ = joinQueries.join(" | ");
25635
25665
  lines.push(` ${joinType}: APP${join2.table.appId}${joinAliasStr} (${join2.table.appId})`);
@@ -25641,7 +25671,7 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25641
25671
  (consumption) => consumption.targetAlias === join2.table.alias
25642
25672
  );
25643
25673
  if (emitFetch && join2.table.cteName === null) {
25644
- const joinPending = runtimeJoinKeyCandidate || !joinKey && !runtimeJoinPlan && joinCandidate !== null;
25674
+ const joinPending = runtimeJoinKeyCandidate || !joinKey && !metadataAwarePushdownPlan && joinCandidate !== null;
25645
25675
  const joinFetchScope = joinQ === "(\u5168\u4EF6\u53D6\u5F97)" ? "ALL" : joinPending ? "PREFILTERED" : joinKey?.plan.kind === "IN" && joinPlanItem?.relation !== "superset" && joinKey.additionalRelation !== "superset" ? "EXACT" : joinPlanItem?.relation === "exact" || joinFunctionConsumption ? "EXACT" : "PREFILTERED";
25646
25676
  lines.push(renderFetchScope(createExplainFetchSource(
25647
25677
  collector,
@@ -25671,10 +25701,19 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans, plainGroupByPlan
25671
25701
  lines.push(joinKey.plan.reason === "JOIN_KEY_VALUES_RUNTIME" ? " join key prefilter: runtime candidate" : " join key prefilter: not applied");
25672
25702
  lines.push(` join key prefilter reason: ${joinKey.plan.reason}`);
25673
25703
  }
25704
+ } else if (offlineJoinKeyCandidate) {
25705
+ lines.push(" join key prefilter: runtime candidate");
25706
+ lines.push(" join key prefilter reason: JOIN_KEY_VALUES_RUNTIME");
25707
+ if (joinCandidate !== null) {
25708
+ lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
25709
+ }
25674
25710
  } else if (joinPlanItem || joinFunctionConsumption) {
25675
25711
  lines.push(` pushdown applied: ${joinBoundQuery}`);
25676
25712
  lines.push(` relation: ${joinPlanItem?.relation ?? "exact"}`);
25677
- } else if (!runtimeJoinPlan && joinCandidate !== null) {
25713
+ } else if (metadataAwarePushdownPlan && joinPushDown !== null) {
25714
+ lines.push(` pushdown applied: ${baseJoinQ}`);
25715
+ lines.push(` relation: ${pushdownPlan.joinRelations.get(join2.table.alias) ?? "exact"}`);
25716
+ } else if (joinCandidate !== null && (!metadataAwarePushdownPlan || hasPushdownPlaceholder(joinCandidate))) {
25678
25717
  lines.push(` pushdown candidate: ${whereToKintone(joinCandidate)}\uFF08\u5B9F\u884C\u6642\u306E\u578B\u30FB\u5B9F\u5728\u78BA\u8A8D\u5F85\u3061\uFF09`);
25679
25718
  }
25680
25719
  lines.push(` fields: ${joinFields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : joinFields.join(", ")}`);
@@ -28860,14 +28899,23 @@ function createDryRunClient() {
28860
28899
  deleteRecords: notUsed,
28861
28900
  getApps: notUsed,
28862
28901
  getFields: notUsed,
28863
- async getProcessStatuses() {
28864
- return { enable: false, states: [] };
28865
- },
28866
- async getNumberPrecision() {
28867
- return { digits: 30, decimalPlaces: 10, roundingMode: "HALF_EVEN" };
28868
- }
28902
+ getProcessStatuses: notUsed,
28903
+ getNumberPrecision: notUsed
28869
28904
  };
28870
28905
  }
28906
+ function hasStaticTypedPushdownCandidate(statement) {
28907
+ if (statement === null || typeof statement !== "object") return false;
28908
+ const node = statement;
28909
+ if (node["type"] === "WITH") return hasStaticTypedPushdownCandidate(node["query"]);
28910
+ if (node["type"] !== "SELECT") return false;
28911
+ const select = statement;
28912
+ if (!select.where || !Array.isArray(select.joins) || select.joins.length === 0) return false;
28913
+ if (![select.from, ...select.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) return false;
28914
+ const where = select.where;
28915
+ return select.joins.some(
28916
+ (join2) => join2.type === "INNER" && join2.table?.alias && join2.table?.cteName === null && !join2.table?.subtableCode && extractTypedPushdownCandidates(where, { tableAlias: join2.table.alias }) !== null
28917
+ );
28918
+ }
28871
28919
  async function runDiagnosticRecordGet(params) {
28872
28920
  const qs = `app=${encodeURIComponent(String(params.appId))}&id=${encodeURIComponent(String(params.recordId))}`;
28873
28921
  const apiRoot = params.guestSpaceId !== null ? `/k/guest/${params.guestSpaceId}/v1` : "/k/v1";
@@ -29512,6 +29560,7 @@ async function run() {
29512
29560
  let containsApplyStatement = false;
29513
29561
  let containsApplyMutation = false;
29514
29562
  let dryRunNeedsMetadata = false;
29563
+ let dryRunUsesStaticTypedPlan = false;
29515
29564
  let parsedStatements = [];
29516
29565
  if (args.diagRecordId === null) {
29517
29566
  sql = args.executeSql;
@@ -29551,6 +29600,7 @@ async function run() {
29551
29600
  return false;
29552
29601
  });
29553
29602
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
29603
+ dryRunUsesStaticTypedPlan = statements.some(hasStaticTypedPushdownCandidate) && !statements.some(statementUsesRelativeDateResolution);
29554
29604
  if (statements.length > 1) {
29555
29605
  batchAnalysis = analyzeBatch(statements);
29556
29606
  isBatchSql = true;
@@ -29677,7 +29727,7 @@ async function run() {
29677
29727
  appProfileByApp.set(appId, appBindingByMappedApp.get(appId)?.profile ?? profileName.toLowerCase());
29678
29728
  }
29679
29729
  const cacheContext = buildCacheContext(profileName, appBindingByMappedApp);
29680
- if (args.dryRun && !dryRunNeedsMetadata) {
29730
+ if (args.dryRun && (!dryRunNeedsMetadata || dryRunUsesStaticTypedPlan)) {
29681
29731
  client = createDryRunClient();
29682
29732
  } else {
29683
29733
  for (const explicitProfile of appProfileByApp.values()) {
@@ -29925,7 +29975,7 @@ async function run() {
29925
29975
  maxDelayMs: args.retryMaxDelay ?? profile.query?.retryMaxDelayMs
29926
29976
  })));
29927
29977
  }
29928
- if (isBatchSql && args.dryRun) {
29978
+ if (args.dryRun && (isBatchSql || dryRunUsesStaticTypedPlan)) {
29929
29979
  try {
29930
29980
  const plans = await buildBatchExplainPlans(
29931
29981
  sql,
@@ -29936,7 +29986,8 @@ async function run() {
29936
29986
  cursorMaxActive,
29937
29987
  Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0,
29938
29988
  dmlMaxRows,
29939
- dmlMaxSubtableRows
29989
+ dmlMaxSubtableRows,
29990
+ false
29940
29991
  );
29941
29992
  const out = [];
29942
29993
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;