@rex0220/kintone-sql-tools 3.5.0 → 3.7.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
@@ -21,11 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/cli/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ CLI_IMPORT_SOURCE_REQUIRED_MESSAGE: () => CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
24
25
  HELP_TEXT: () => HELP_TEXT,
25
26
  buildBatchDmlConfirmMessage: () => buildBatchDmlConfirmMessage,
26
27
  buildBatchStatementSummary: () => buildBatchStatementSummary,
27
28
  buildOutput: () => buildOutput,
28
29
  buildReplExecArgv: () => buildReplExecArgv,
30
+ buildSelectSummary: () => buildSelectSummary,
29
31
  buildValidationOutput: () => buildValidationOutput,
30
32
  extractAppIds: () => extractAppIds,
31
33
  normalizeAppKey: () => normalizeAppKey,
@@ -37,6 +39,7 @@ __export(index_exports, {
37
39
  parseTokenMap: () => parseTokenMap,
38
40
  runWithArgv: () => runWithArgv,
39
41
  shouldExitOnEmpty: () => shouldExitOnEmpty,
42
+ toCliImportError: () => toCliImportError,
40
43
  writeBatchOutput: () => writeBatchOutput
41
44
  });
42
45
  module.exports = __toCommonJS(index_exports);
@@ -651,8 +654,9 @@ var ParseError = class extends Error {
651
654
  }
652
655
  };
653
656
  var Parser = class {
654
- constructor(tokens) {
657
+ constructor(tokens, capabilities = {}) {
655
658
  this.tokens = tokens;
659
+ this.capabilities = capabilities;
656
660
  this.allowUnaryPlusNumber = false;
657
661
  this.scalarAllowsAggregateArgs = true;
658
662
  this.scalarAllowsCase = true;
@@ -745,6 +749,12 @@ var Parser = class {
745
749
  if (upper === "DROP") return this.parseDropTempTable();
746
750
  if (upper === "DECLARE") return this.parseDeclareVariable();
747
751
  if (upper === "VALIDATE") return this.parseValidate();
752
+ if (upper === "IMPORT") {
753
+ if (!this.capabilities.import) {
754
+ throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
755
+ }
756
+ return this.parseImport();
757
+ }
748
758
  break;
749
759
  }
750
760
  default:
@@ -925,24 +935,242 @@ var Parser = class {
925
935
  query = this.parseReorder();
926
936
  } else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
927
937
  query = this.parseValidate();
938
+ } else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "IMPORT") {
939
+ if (!this.capabilities.import) {
940
+ throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
941
+ }
942
+ query = this.parseImport();
928
943
  } else {
929
944
  throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
930
945
  }
931
946
  return { type: "EXPLAIN", query };
932
947
  }
948
+ parseImport() {
949
+ this.advance();
950
+ let writeMode;
951
+ if (this.peek().kind === "UPDATE" /* UPDATE */) {
952
+ this.advance();
953
+ writeMode = "UPDATE_RECORD_NUMBER";
954
+ }
955
+ this.expect("INTO" /* INTO */);
956
+ this.rejectTempTableDml();
957
+ const target = this.parseIdentifier();
958
+ const { appId, subtableCode } = extractTableRef(target, this.prev());
959
+ if (subtableCode) throw new ParseError("IMPORT does not support subtables in Phase 1.", this.prev());
960
+ this.expect("(" /* LPAREN */);
961
+ const targets = [];
962
+ const fields = [];
963
+ const targetNames = /* @__PURE__ */ new Set();
964
+ while (true) {
965
+ const name = this.parseIdentifier();
966
+ if (targetNames.has(name)) throw new ParseError(`IMPORT target ${name} is declared more than once.`, this.prev());
967
+ targetNames.add(name);
968
+ if (this.peek().kind === "(" /* LPAREN */) {
969
+ this.advance();
970
+ const children = this.parseIdentList();
971
+ this.expect(")" /* RPAREN */);
972
+ if (new Set(children).size !== children.length) {
973
+ throw new ParseError(`IMPORT subtable ${name} contains duplicate child declarations.`, this.prev());
974
+ }
975
+ let rowIdSourceHeader;
976
+ if (this.isSoftKeyword("ROW")) {
977
+ this.advance();
978
+ for (const word of ["ID", "SOURCE"]) {
979
+ if (!this.isSoftKeyword(word)) throw new ParseError(`ROW must be followed by ID SOURCE <header>.`, this.peek());
980
+ this.advance();
981
+ }
982
+ rowIdSourceHeader = this.parseIdentifier();
983
+ }
984
+ targets.push({ kind: "SUBTABLE", subtableCode: name, children, ...rowIdSourceHeader ? { rowIdSourceHeader } : {} });
985
+ } else {
986
+ fields.push(name);
987
+ targets.push({ kind: "FIELD", field: name });
988
+ }
989
+ if (this.peek().kind !== "," /* COMMA */) break;
990
+ this.advance();
991
+ }
992
+ this.expect(")" /* RPAREN */);
993
+ this.expect("FROM" /* FROM */);
994
+ if (!this.isSoftKeyword("CSV") && !this.isSoftKeyword("JSON")) throw new ParseError("IMPORT FROM requires CSV or JSON.", this.peek());
995
+ const sourceKind = this.peek().value.toUpperCase();
996
+ this.advance();
997
+ const sourceName = this.parseIdentifier();
998
+ let encoding;
999
+ let hasHeader = true;
1000
+ let columns;
1001
+ if (this.isSoftKeyword("ENCODING")) {
1002
+ if (sourceKind === "JSON") throw new ParseError("JSON source is UTF-8 only; ENCODING is not allowed.", this.peek());
1003
+ this.advance();
1004
+ const value = this.parseIdentifier().toUpperCase();
1005
+ if (value !== "UTF8" && value !== "SJIS") throw new ParseError("ENCODING must be UTF8 or SJIS.", this.prev());
1006
+ encoding = value === "UTF8" ? "utf8" : "sjis";
1007
+ }
1008
+ if (this.peek().kind === "NOT" /* NOT */ && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
1009
+ if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
1010
+ this.advance();
1011
+ this.advance();
1012
+ hasHeader = false;
1013
+ } else if (this.isSoftKeyword("NO") && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
1014
+ if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
1015
+ this.advance();
1016
+ this.advance();
1017
+ hasHeader = false;
1018
+ }
1019
+ if (this.isSoftKeyword("COLUMNS")) {
1020
+ if (sourceKind === "JSON") throw new ParseError("COLUMNS is CSV-only.", this.peek());
1021
+ if (hasHeader) throw new ParseError("COLUMNS requires NO HEADER.", this.peek());
1022
+ this.advance();
1023
+ this.expect("(" /* LPAREN */);
1024
+ columns = this.parseIdentList();
1025
+ this.expect(")" /* RPAREN */);
1026
+ }
1027
+ let projection;
1028
+ if (this.peek().kind === "SELECT" /* SELECT */) {
1029
+ if (sourceKind === "JSON") throw new ParseError("SELECT projection is CSV-only.", this.peek());
1030
+ projection = this.parseSelect();
1031
+ if (projection.from.cteName !== NO_FROM_CTE_NAME || projection.joins.length > 0) {
1032
+ throw new ParseError("IMPORT projection cannot use FROM or JOIN.", this.prev());
1033
+ }
1034
+ if (projection.where || projection.groupBy.length || projection.having || projection.orderBy.length || projection.limit !== null || projection.offset !== null) {
1035
+ throw new ParseError("IMPORT projection supports SELECT expressions only.", this.prev());
1036
+ }
1037
+ this.validateImportProjectionScope(projection, this.prev());
1038
+ if (targets.some((item) => item.kind === "SUBTABLE")) {
1039
+ throw new ParseError("IMPORT subtable sources cannot use SELECT projection.", this.prev());
1040
+ }
1041
+ if (projection.columns.length !== fields.length) {
1042
+ throw new ParseError(`IMPORT projection has ${projection.columns.length} columns; target has ${fields.length}.`, this.prev());
1043
+ }
1044
+ }
1045
+ let mappingMode = "POSITION";
1046
+ let ignoreUnknownColumns = false;
1047
+ if (this.peek().kind === "BY" /* BY */ || this.isSoftKeyword("BY")) {
1048
+ if (sourceKind === "JSON") throw new ParseError("BY NAME is CSV-only.", this.peek());
1049
+ this.advance();
1050
+ if (!this.isSoftKeyword("NAME")) throw new ParseError("BY must be followed by NAME in IMPORT.", this.peek());
1051
+ this.advance();
1052
+ if (!hasHeader) throw new ParseError("BY NAME requires HEADER.", this.prev());
1053
+ if (projection) throw new ParseError("BY NAME and SELECT projection are mutually exclusive.", this.prev());
1054
+ mappingMode = "BY_NAME";
1055
+ if (this.isSoftKeyword("IGNORE")) {
1056
+ this.advance();
1057
+ if (!this.isSoftKeyword("UNKNOWN")) throw new ParseError("IGNORE must be followed by UNKNOWN COLUMNS.", this.peek());
1058
+ this.advance();
1059
+ if (!this.isSoftKeyword("COLUMNS")) throw new ParseError("IGNORE UNKNOWN must be followed by COLUMNS.", this.peek());
1060
+ this.advance();
1061
+ ignoreUnknownColumns = true;
1062
+ }
1063
+ }
1064
+ let keyFields;
1065
+ let recordNumberSourceHeader;
1066
+ if (this.isSoftKeyword("MATCH")) {
1067
+ this.advance();
1068
+ for (const word of ["RECORD", "NUMBER", "SOURCE"]) {
1069
+ if (!this.isSoftKeyword(word)) throw new ParseError(`MATCH must be followed by RECORD NUMBER SOURCE <header>.`, this.peek());
1070
+ this.advance();
1071
+ }
1072
+ recordNumberSourceHeader = this.parseIdentifier();
1073
+ }
1074
+ if (this.peek().kind === "ON" /* ON */ && this.peekAt(1).kind === "DUPLICATE" /* DUPLICATE */) keyFields = this.parseOnDuplicate();
1075
+ let replaceSubtables;
1076
+ if (this.peek().kind === "REPLACE" /* REPLACE */ || this.isSoftKeyword("REPLACE")) {
1077
+ this.advance();
1078
+ if (!this.isSoftKeyword("SUBTABLES")) throw new ParseError("REPLACE must be followed by SUBTABLES (...).", this.peek());
1079
+ this.advance();
1080
+ this.expect("(" /* LPAREN */);
1081
+ replaceSubtables = this.parseIdentList();
1082
+ this.expect(")" /* RPAREN */);
1083
+ if (new Set(replaceSubtables).size !== replaceSubtables.length) throw new ParseError("REPLACE SUBTABLES contains duplicates.", this.prev());
1084
+ }
1085
+ const subtableTargets = targets.filter((item) => item.kind === "SUBTABLE");
1086
+ if (subtableTargets.length) {
1087
+ if (projection) throw new ParseError("IMPORT subtables cannot use SELECT projection.", this.prev());
1088
+ if (sourceKind === "JSON") {
1089
+ if (subtableTargets.some((item) => item.rowIdSourceHeader)) throw new ParseError("JSON subtable IMPORT does not accept ROW ID SOURCE.", this.prev());
1090
+ if (replaceSubtables) throw new ParseError("REPLACE SUBTABLES is CSV-only; JSON uses nested-array replacement semantics.", this.prev());
1091
+ } else {
1092
+ if (writeMode !== "UPDATE_RECORD_NUMBER" || !recordNumberSourceHeader) throw new ParseError("CSV subtable IMPORT requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.", this.prev());
1093
+ if (mappingMode !== "BY_NAME") throw new ParseError("CSV subtable IMPORT requires BY NAME.", this.prev());
1094
+ if (!replaceSubtables) throw new ParseError("CSV subtable IMPORT requires REPLACE SUBTABLES (...).", this.prev());
1095
+ const replacement = new Set(replaceSubtables);
1096
+ for (const item of subtableTargets) {
1097
+ if (!item.rowIdSourceHeader) throw new ParseError(`CSV subtable ${item.subtableCode} requires ROW ID SOURCE <header>.`, this.prev());
1098
+ if (!replacement.has(item.subtableCode)) throw new ParseError(`IMPORT declares child columns for non-replaced subtable ${item.subtableCode}.`, this.prev());
1099
+ }
1100
+ for (const code of replacement) {
1101
+ if (!subtableTargets.some((item) => item.subtableCode === code)) throw new ParseError(`REPLACE SUBTABLES target ${code} is not declared in INTO.`, this.prev());
1102
+ }
1103
+ }
1104
+ } else if (replaceSubtables) {
1105
+ throw new ParseError("REPLACE SUBTABLES requires subtable targets in INTO.", this.prev());
1106
+ }
1107
+ if (writeMode) {
1108
+ if (sourceKind !== "CSV") throw new ParseError("IMPORT UPDATE supports CSV only.", this.prev());
1109
+ if (mappingMode !== "BY_NAME") throw new ParseError("IMPORT UPDATE requires BY NAME.", this.prev());
1110
+ if (!recordNumberSourceHeader) throw new ParseError("IMPORT UPDATE requires MATCH RECORD NUMBER SOURCE <header>.", this.peek());
1111
+ if (keyFields) throw new ParseError("IMPORT UPDATE and ON DUPLICATE are mutually exclusive.", this.prev());
1112
+ } else if (recordNumberSourceHeader) {
1113
+ throw new ParseError("MATCH RECORD NUMBER SOURCE requires IMPORT UPDATE.", this.prev());
1114
+ }
1115
+ const checkGroups = this.parseCheckGroups();
1116
+ const control = this.parseDmlControlSuffix();
1117
+ return {
1118
+ type: "IMPORT",
1119
+ appId,
1120
+ fields,
1121
+ targets,
1122
+ source: sourceKind === "JSON" ? { kind: "JSON", sourceName } : { kind: "CSV", sourceName, encoding, hasHeader, mappingMode, ignoreUnknownColumns, ...columns ? { columns } : {}, ...projection ? { projection } : {} },
1123
+ ...writeMode ? { writeMode, recordNumberSourceHeader } : {},
1124
+ ...replaceSubtables ? { replaceSubtables } : {},
1125
+ ...keyFields ? { keyFields } : {},
1126
+ ...checkGroups,
1127
+ ...control
1128
+ };
1129
+ }
1130
+ validateImportProjectionScope(node, token) {
1131
+ if (Array.isArray(node)) {
1132
+ node.forEach((item) => this.validateImportProjectionScope(item, token));
1133
+ return;
1134
+ }
1135
+ if (node === null || typeof node !== "object") return;
1136
+ const value = node;
1137
+ if (value.type === "SCALAR_SUBQUERY" || value.type === "SCALAR_SUBQUERY_COL") {
1138
+ throw new ParseError("IMPORT projection cannot use subqueries.", token);
1139
+ }
1140
+ if (typeof value.tableAlias === "string") {
1141
+ throw new ParseError("IMPORT projection cannot use qualified column references.", token);
1142
+ }
1143
+ Object.values(value).forEach((item) => this.validateImportProjectionScope(item, token));
1144
+ }
933
1145
  /** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
934
1146
  parseValidate() {
935
1147
  const validateTok = this.advance();
936
1148
  const name = this.parseIdentifier();
937
1149
  const { appId, subtableCode } = extractTableRef(name, this.prev());
938
- if (subtableCode) {
939
- throw new ParseError("VALIDATE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3092\u5BFE\u8C61\u306B\u3067\u304D\u307E\u305B\u3093", this.prev());
940
- }
941
- let fields;
1150
+ if (subtableCode) throw new ParseError(
1151
+ `VALIDATE APP${appId}$${subtableCode} \u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002VALIDATE APP${appId} (${subtableCode}) \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044`,
1152
+ this.prev()
1153
+ );
1154
+ let targets;
942
1155
  if (this.consume("(" /* LPAREN */)) {
943
- fields = this.parseIdentList();
1156
+ targets = [];
1157
+ do {
1158
+ const field = this.parseIdentifier();
1159
+ if (this.consume("(" /* LPAREN */)) {
1160
+ const children = this.peek().kind === ")" /* RPAREN */ ? [] : this.parseIdentList();
1161
+ this.expect(")" /* RPAREN */);
1162
+ targets.push({ kind: "SUBTABLE", subtableCode: field, children });
1163
+ } else {
1164
+ targets.push({ kind: "FIELD", field });
1165
+ }
1166
+ } while (this.consume("," /* COMMA */));
944
1167
  this.expect(")" /* RPAREN */);
945
1168
  }
1169
+ let summary;
1170
+ if (this.isSoftKeyword("SUMMARY")) {
1171
+ this.advance();
1172
+ summary = true;
1173
+ }
946
1174
  const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
947
1175
  const checks = this.parseCheckGroups();
948
1176
  let errorTable;
@@ -953,7 +1181,7 @@ var Parser = class {
953
1181
  }
954
1182
  errorTable = this.parseTableName();
955
1183
  }
956
- const stmt = { type: "VALIDATE", appId, fields, where, ...checks, ...errorTable ? { errorTable } : {} };
1184
+ const stmt = { type: "VALIDATE", appId, targets, ...summary ? { summary } : {}, where, ...checks, ...errorTable ? { errorTable } : {} };
957
1185
  this.assertValidateExpressions(stmt, validateTok);
958
1186
  return stmt;
959
1187
  }
@@ -3094,7 +3322,7 @@ function getStatementType(stmt) {
3094
3322
  return typeof obj.type === "string" ? obj.type : "UNKNOWN";
3095
3323
  }
3096
3324
  function isDmlType(type) {
3097
- return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
3325
+ return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER" || type === "IMPORT";
3098
3326
  }
3099
3327
  function isReadOnlyType(type) {
3100
3328
  return type === "SELECT" || type === "VALIDATE" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
@@ -4591,7 +4819,7 @@ function analyzeBatch(statements) {
4591
4819
  dependsOn.add(at);
4592
4820
  }
4593
4821
  if (validationTable) {
4594
- const payloadFields = stmt.type === "VALIDATE" ? ["$id", "$err_field", "$err_code", "$err_message", "$err_value"] : stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
4822
+ const payloadFields = stmt.type === "VALIDATE" ? stmt.summary ? ["$id", "$err_subtable", "$err_field", "$err_code", "$err_count"] : ["$id", "$err_field", "$err_code", "$err_message", "$err_value", "$err_subtable", "$err_subrow", "$err_subrow_id", "$err_count"] : stmt.type === "IMPORT" && stmt.targets?.some((target) => target.kind === "SUBTABLE") ? [...new Set(stmt.targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children)), "$err_subtable", "$err_subrow", "$err_source_row"] : stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
4595
4823
  const signature = JSON.stringify(payloadFields);
4596
4824
  const at = defined.get(validationTable);
4597
4825
  if (at === void 0) {
@@ -7456,7 +7684,7 @@ function validateAndNormalizeDmlValue(raw, field, numberPrecision) {
7456
7684
  return { ok: false, code: "ERR_LENGTH_MAX", message: `${field.code} \u306F ${max} \u6587\u5B57\u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
7457
7685
  }
7458
7686
  }
7459
- if (CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
7687
+ if (!isEmpty(value) && CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
7460
7688
  const selected = Array.isArray(value) ? value.map(String) : [String(value)];
7461
7689
  if (selected.some((choice) => !(choice in field.optionOrder))) {
7462
7690
  return { ok: false, code: "ERR_CHOICE_INVALID", message: `${field.code} \u306B\u5B9A\u7FA9\u5916\u306E\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u3059` };
@@ -7553,9 +7781,15 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7553
7781
  candidate.record ??= {};
7554
7782
  const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
7555
7783
  for (const code of targetFields) {
7784
+ if (!candidate.payload.has(code)) continue;
7556
7785
  const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
7557
7786
  if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
7558
- else candidate.record[code] = { value: result.value };
7787
+ else {
7788
+ const original = candidate.payload.get(code);
7789
+ const type = infoByCode.get(code).fieldType;
7790
+ const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
7791
+ candidate.record[code] = { value: preserveCodes ? original : result.value };
7792
+ }
7559
7793
  }
7560
7794
  if (validateMissingCreateFields && candidate.mode === "create") {
7561
7795
  for (const info of fieldInfos) {
@@ -7799,22 +8033,878 @@ function combineLogical(op, left, right) {
7799
8033
  if (left.capability === "UNSUPPORTED" || right.capability === "UNSUPPORTED") {
7800
8034
  return { capability: "UNSUPPORTED", reasons };
7801
8035
  }
7802
- if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
7803
- return { capability: "EXACT_PUSHDOWN", reasons };
8036
+ if (left.capability === "EXACT_PUSHDOWN" && right.capability === "EXACT_PUSHDOWN") {
8037
+ return { capability: "EXACT_PUSHDOWN", reasons };
8038
+ }
8039
+ if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
8040
+ return {
8041
+ capability: "SUPERSET_PREFILTER",
8042
+ reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
8043
+ };
8044
+ }
8045
+ return { capability: "LOCAL_ONLY", reasons };
8046
+ }
8047
+ function localExpression() {
8048
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
8049
+ }
8050
+ function unsupported(code, field, fieldType, operator) {
8051
+ return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
8052
+ }
8053
+
8054
+ // src/import/sourceLoader.ts
8055
+ var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
8056
+ var ImportSourceError = class extends Error {
8057
+ constructor(message) {
8058
+ super(`ImportSourceError: ${message}`);
8059
+ this.name = "ImportSourceError";
8060
+ }
8061
+ };
8062
+ function resolveImportSource(name, resolver) {
8063
+ if (!resolver) throw new ImportSourceError("IMPORT source capability is not available.");
8064
+ const handle = resolver(name);
8065
+ if (!handle) throw new ImportSourceError(`source "${name}" is not supplied.`);
8066
+ return handle;
8067
+ }
8068
+ async function loadImportSource(handle, cache) {
8069
+ let pending = cache.get(handle);
8070
+ if (!pending) {
8071
+ pending = handle.load().then((payload) => {
8072
+ if (!(payload.bytes instanceof Uint8Array)) throw new ImportSourceError("loader must return Uint8Array bytes.");
8073
+ if (payload.bytes.byteLength > IMPORT_MAX_BYTES) {
8074
+ throw new ImportSourceError(`source exceeds the ${IMPORT_MAX_BYTES} byte limit.`);
8075
+ }
8076
+ return payload;
8077
+ });
8078
+ cache.set(handle, pending);
8079
+ }
8080
+ return pending;
8081
+ }
8082
+
8083
+ // src/import/csvDecoder.ts
8084
+ function decodeImportText(bytes, encoding) {
8085
+ try {
8086
+ return new TextDecoder(encoding === "sjis" ? "shift_jis" : "utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
8087
+ } catch {
8088
+ throw new ImportSourceError(`invalid ${encoding.toUpperCase()} byte sequence.`);
8089
+ }
8090
+ }
8091
+ function parseRfc4180(text) {
8092
+ const records = [];
8093
+ let record = [];
8094
+ let cell = "";
8095
+ let quoted = false;
8096
+ let afterQuote = false;
8097
+ let i = 0;
8098
+ const finishCell = () => {
8099
+ record.push(cell);
8100
+ cell = "";
8101
+ afterQuote = false;
8102
+ };
8103
+ const finishRecord = () => {
8104
+ finishCell();
8105
+ records.push(record);
8106
+ record = [];
8107
+ };
8108
+ while (i < text.length) {
8109
+ const ch = text[i];
8110
+ if (quoted) {
8111
+ if (ch === '"') {
8112
+ if (text[i + 1] === '"') {
8113
+ cell += '"';
8114
+ i += 2;
8115
+ continue;
8116
+ }
8117
+ quoted = false;
8118
+ afterQuote = true;
8119
+ i++;
8120
+ continue;
8121
+ }
8122
+ cell += ch;
8123
+ i++;
8124
+ continue;
8125
+ }
8126
+ if (afterQuote && ch !== "," && ch !== "\r" && ch !== "\n") {
8127
+ throw new ImportSourceError(`unexpected character after closing quote at offset ${i}.`);
8128
+ }
8129
+ if (ch === '"') {
8130
+ if (cell.length !== 0) throw new ImportSourceError(`quote in unquoted cell at offset ${i}.`);
8131
+ quoted = true;
8132
+ i++;
8133
+ continue;
8134
+ }
8135
+ if (ch === ",") {
8136
+ finishCell();
8137
+ i++;
8138
+ continue;
8139
+ }
8140
+ if (ch === "\r" || ch === "\n") {
8141
+ if (ch === "\r" && text[i + 1] === "\n") i++;
8142
+ finishRecord();
8143
+ i++;
8144
+ continue;
8145
+ }
8146
+ cell += ch;
8147
+ i++;
8148
+ }
8149
+ if (quoted) throw new ImportSourceError("unterminated quoted cell.");
8150
+ if (cell.length > 0 || record.length > 0 || afterQuote) finishRecord();
8151
+ return records;
8152
+ }
8153
+ function assertColumns(columns) {
8154
+ const seen = /* @__PURE__ */ new Set();
8155
+ columns.forEach((column, index) => {
8156
+ if (column === "") throw new ImportSourceError(`CSV column ${index + 1} has an empty name.`);
8157
+ if (seen.has(column)) throw new ImportSourceError(`CSV column name "${column}" is duplicated.`);
8158
+ seen.add(column);
8159
+ });
8160
+ }
8161
+ function decodeCsv(bytes, options) {
8162
+ const records = parseRfc4180(decodeImportText(bytes, options.encoding));
8163
+ let columns;
8164
+ let rows;
8165
+ if (options.hasHeader) {
8166
+ columns = records[0] ?? [];
8167
+ rows = records.slice(1);
8168
+ } else {
8169
+ rows = records;
8170
+ columns = options.columns ? [...options.columns] : Array.from({ length: rows[0]?.length ?? 0 }, (_, i) => `c${i + 1}`);
8171
+ }
8172
+ assertColumns(columns);
8173
+ if (rows.length === 0) throw new ImportSourceError("CSV has no data rows.");
8174
+ rows.forEach((row, i) => {
8175
+ if (row.length !== columns.length) {
8176
+ throw new ImportSourceError(`CSV row ${i + (options.hasHeader ? 2 : 1)} has ${row.length} cells; expected ${columns.length}.`);
8177
+ }
8178
+ });
8179
+ return { columns, rows };
8180
+ }
8181
+
8182
+ // src/import/convertImportCsvValue.ts
8183
+ var LF_MULTI_TYPES = /* @__PURE__ */ new Set([
8184
+ "CHECK_BOX",
8185
+ "MULTI_SELECT",
8186
+ "USER_SELECT",
8187
+ "ORGANIZATION_SELECT",
8188
+ "GROUP_SELECT"
8189
+ ]);
8190
+ var USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8191
+ var ImportCsvValueError = class extends Error {
8192
+ constructor() {
8193
+ super("multiple-value CSV cell contains an empty LF-delimited item");
8194
+ this.code = "ERR_IMPORT_MULTI_EMPTY_ITEM";
8195
+ this.name = "ImportCsvValueError";
8196
+ }
8197
+ };
8198
+ function convertImportCsvValue(raw, type, options) {
8199
+ void options;
8200
+ if (!LF_MULTI_TYPES.has(type ?? "")) return raw;
8201
+ if (raw === "") return [];
8202
+ const items = raw.split(/\r\n|\n/);
8203
+ if (items.some((item) => item === "")) throw new ImportCsvValueError();
8204
+ return USER_TYPES2.has(type ?? "") ? items.map((code) => ({ code })) : items;
8205
+ }
8206
+
8207
+ // src/import/jsonTokenizer.ts
8208
+ function fail(message, offset, line, column) {
8209
+ throw new ImportSourceError(`JSON ${message} (offset=${offset}, line=${line}, column=${column}).`);
8210
+ }
8211
+ function decodeUtf8Json(bytes) {
8212
+ try {
8213
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
8214
+ } catch {
8215
+ throw new ImportSourceError("JSON source is not valid UTF-8.");
8216
+ }
8217
+ }
8218
+ function tokenizeJson(text) {
8219
+ const tokens = [];
8220
+ let i = 0, line = 1, column = 1;
8221
+ const advance = () => {
8222
+ const ch = text[i++];
8223
+ if (ch === "\n") {
8224
+ line++;
8225
+ column = 1;
8226
+ } else column++;
8227
+ return ch;
8228
+ };
8229
+ const position = () => ({ offset: i, line, column });
8230
+ while (i < text.length) {
8231
+ const ch = text[i];
8232
+ if (ch === " " || ch === " " || ch === "\r" || ch === "\n") {
8233
+ advance();
8234
+ continue;
8235
+ }
8236
+ const start = position();
8237
+ if ("{}[]:,".includes(ch)) {
8238
+ advance();
8239
+ tokens.push({ kind: "punct", value: ch, ...start });
8240
+ continue;
8241
+ }
8242
+ if (ch === '"') {
8243
+ advance();
8244
+ let value = "";
8245
+ let closed = false;
8246
+ while (i < text.length) {
8247
+ const c = advance();
8248
+ if (c === '"') {
8249
+ closed = true;
8250
+ break;
8251
+ }
8252
+ if (c.charCodeAt(0) < 32) fail("string contains an unescaped control character", start.offset, start.line, start.column);
8253
+ if (c !== "\\") {
8254
+ value += c;
8255
+ continue;
8256
+ }
8257
+ if (i >= text.length) fail("string has an unterminated escape", start.offset, start.line, start.column);
8258
+ const esc = advance();
8259
+ const simple = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
8260
+ if (esc in simple) {
8261
+ value += simple[esc];
8262
+ continue;
8263
+ }
8264
+ if (esc !== "u") fail(`has invalid escape \\${esc}`, i - 2, line, Math.max(1, column - 2));
8265
+ const hex = text.slice(i, i + 4);
8266
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail("has invalid unicode escape", i, line, column);
8267
+ for (let n = 0; n < 4; n++) advance();
8268
+ const code = Number.parseInt(hex, 16);
8269
+ if (code >= 55296 && code <= 56319) {
8270
+ if (text.slice(i, i + 2) !== "\\u" || !/^[0-9a-fA-F]{4}$/.test(text.slice(i + 2, i + 6))) fail("has an unpaired high surrogate", i, line, column);
8271
+ advance();
8272
+ advance();
8273
+ const lowHex = text.slice(i, i + 4);
8274
+ for (let n = 0; n < 4; n++) advance();
8275
+ const low = Number.parseInt(lowHex, 16);
8276
+ if (low < 56320 || low > 57343) fail("has an invalid surrogate pair", i - 4, line, Math.max(1, column - 4));
8277
+ value += String.fromCodePoint(65536 + (code - 55296 << 10) + low - 56320);
8278
+ } else if (code >= 56320 && code <= 57343) {
8279
+ fail("has an unpaired low surrogate", i - 4, line, Math.max(1, column - 4));
8280
+ } else value += String.fromCharCode(code);
8281
+ }
8282
+ if (!closed) fail("string is unterminated", start.offset, start.line, start.column);
8283
+ tokens.push({ kind: "string", value, ...start });
8284
+ continue;
8285
+ }
8286
+ const rest = text.slice(i);
8287
+ const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest)?.[0];
8288
+ if (number) {
8289
+ for (let n = 0; n < number.length; n++) advance();
8290
+ tokens.push({ kind: "number", lexeme: number, ...start });
8291
+ continue;
8292
+ }
8293
+ const literal = /^(true|false|null)/.exec(rest)?.[0];
8294
+ if (literal) {
8295
+ for (let n = 0; n < literal.length; n++) advance();
8296
+ tokens.push({ kind: "literal", value: literal === "true" ? true : literal === "false" ? false : null, ...start });
8297
+ continue;
8298
+ }
8299
+ fail(`has an unexpected token ${JSON.stringify(ch)}`, start.offset, start.line, start.column);
8300
+ }
8301
+ tokens.push({ kind: "eof", offset: i, line, column });
8302
+ return tokens;
8303
+ }
8304
+
8305
+ // src/import/jsonDecoder.ts
8306
+ function describe(token) {
8307
+ return token.kind === "eof" ? "end of input" : token.kind === "punct" ? token.value : token.kind;
8308
+ }
8309
+ function decodeJsonRecords(bytes) {
8310
+ if (bytes.byteLength === 0) throw new ImportSourceError("JSON source is empty.");
8311
+ const tokens = tokenizeJson(decodeUtf8Json(bytes));
8312
+ let index = 0;
8313
+ const fail3 = (message, token = tokens[index]) => {
8314
+ throw new ImportSourceError(`JSON ${message} (offset=${token.offset}, line=${token.line}, column=${token.column}).`);
8315
+ };
8316
+ const isPunct = (token, value) => token.kind === "punct" && token.value === value;
8317
+ const punct = (value) => {
8318
+ const token = tokens[index];
8319
+ if (token.kind !== "punct" || token.value !== value) fail3(`expected ${value}; found ${describe(token)}`, token);
8320
+ index++;
8321
+ };
8322
+ const parseValue = () => {
8323
+ const token = tokens[index++];
8324
+ if (token.kind === "string") return token.value;
8325
+ if (token.kind === "number") return { kind: "number", lexeme: token.lexeme };
8326
+ if (token.kind === "literal") return token.value;
8327
+ if (token.kind === "punct" && token.value === "{") {
8328
+ const object = /* @__PURE__ */ new Map();
8329
+ if (isPunct(tokens[index], "}")) {
8330
+ index++;
8331
+ return object;
8332
+ }
8333
+ while (true) {
8334
+ const key = tokens[index++];
8335
+ if (key.kind !== "string") return fail3(`object key must be a string; found ${describe(key)}`, key);
8336
+ const keyValue = key.value;
8337
+ if (object.has(keyValue)) fail3(`duplicate key ${JSON.stringify(keyValue)}`, key);
8338
+ punct(":");
8339
+ object.set(keyValue, parseValue());
8340
+ const separator = tokens[index];
8341
+ if (isPunct(separator, "}")) {
8342
+ index++;
8343
+ break;
8344
+ }
8345
+ punct(",");
8346
+ }
8347
+ return object;
8348
+ }
8349
+ if (token.kind === "punct" && token.value === "[") {
8350
+ const array = [];
8351
+ if (isPunct(tokens[index], "]")) {
8352
+ index++;
8353
+ return array;
8354
+ }
8355
+ while (true) {
8356
+ array.push(parseValue());
8357
+ const separator = tokens[index];
8358
+ if (isPunct(separator, "]")) {
8359
+ index++;
8360
+ break;
8361
+ }
8362
+ punct(",");
8363
+ }
8364
+ return array;
8365
+ }
8366
+ return fail3(`expected a value; found ${describe(token)}`, token);
8367
+ };
8368
+ const root = parseValue();
8369
+ if (tokens[index].kind !== "eof") fail3(`has trailing data; found ${describe(tokens[index])}`);
8370
+ const records = root instanceof Map ? [root] : Array.isArray(root) ? root : fail3("root must be an object or array.", tokens[0]);
8371
+ if (records.length === 0) throw new ImportSourceError("JSON source contains no records.");
8372
+ records.forEach((record, i) => {
8373
+ if (!(record instanceof Map)) throw new ImportSourceError(`JSON record ${i + 1} must be an object.`);
8374
+ });
8375
+ return records;
8376
+ }
8377
+
8378
+ // src/import/jsonMaterializer.ts
8379
+ var STRING_ARRAY_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
8380
+ var CODE_ARRAY_TYPES = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8381
+ function fail2(row, field, message) {
8382
+ throw new ImportSourceError(`JSON field validation failed (row=${row}, field=${field}): ${message}`);
8383
+ }
8384
+ function isNumber(value) {
8385
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map) && value.kind === "number";
8386
+ }
8387
+ function materializeValue(value, target, row) {
8388
+ if (value === null) return "";
8389
+ if (typeof value === "string") return value;
8390
+ if (typeof value === "boolean") fail2(row, target.code, "boolean is not accepted.");
8391
+ if (isNumber(value)) {
8392
+ if (target.fieldType === "NUMBER") fail2(row, target.code, "precision target requires a JSON string.");
8393
+ if (!/^-?(?:0|[1-9]\d*)$/.test(value.lexeme) || value.lexeme === "-0") {
8394
+ fail2(row, target.code, `JSON number ${value.lexeme} must be a non-negative-zero safe integer lexeme.`);
8395
+ }
8396
+ const number = Number(value.lexeme);
8397
+ if (!Number.isSafeInteger(number)) fail2(row, target.code, `JSON number ${value.lexeme} is outside the safe integer range.`);
8398
+ return String(number);
8399
+ }
8400
+ if (value instanceof Map) fail2(row, target.code, "object is not accepted for a flat field.");
8401
+ if (!Array.isArray(value)) fail2(row, target.code, "unsupported value type.");
8402
+ if (!STRING_ARRAY_TYPES.has(target.fieldType) && !CODE_ARRAY_TYPES.has(target.fieldType)) {
8403
+ fail2(row, target.code, "array is accepted only for multi-value fields.");
8404
+ }
8405
+ const strings = value.map((entry) => {
8406
+ if (typeof entry !== "string") fail2(row, target.code, "array elements must be strings.");
8407
+ return entry;
8408
+ });
8409
+ if (new Set(strings).size !== strings.length) fail2(row, target.code, "array elements must not contain duplicates.");
8410
+ return CODE_ARRAY_TYPES.has(target.fieldType) ? JSON.stringify(strings.map((code) => ({ code }))) : JSON.stringify(strings);
8411
+ }
8412
+ function materializeJsonDmlSource(_source, payload, targets, maxRows) {
8413
+ if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
8414
+ const records = decodeJsonRecords(payload.bytes);
8415
+ if (records.length > maxRows) throw new ImportSourceError(`source rows (${records.length}) exceed maxRecords (${maxRows}).`);
8416
+ const targetByCode = new Map(targets.map((target) => [target.code, target]));
8417
+ if (targetByCode.size !== targets.length) throw new ImportSourceError("JSON target fields contain duplicates.");
8418
+ const rows = [];
8419
+ const importPresence = [];
8420
+ records.forEach((record, index) => {
8421
+ for (const key of record.keys()) {
8422
+ if (!targetByCode.has(key)) fail2(index + 1, key, "unknown key (not declared in INTO).");
8423
+ }
8424
+ const row = {};
8425
+ const present = /* @__PURE__ */ new Set();
8426
+ for (const target of targets) {
8427
+ if (!record.has(target.code)) continue;
8428
+ present.add(target.code);
8429
+ row[target.code] = materializeValue(record.get(target.code), target, index + 1);
8430
+ }
8431
+ rows.push(row);
8432
+ importPresence.push(present);
8433
+ });
8434
+ return {
8435
+ rows,
8436
+ columns: targets.map((target) => target.code),
8437
+ columnMeta: new Map(targets.map((target) => [target.code, { fieldType: target.fieldType }])),
8438
+ importPresence
8439
+ };
8440
+ }
8441
+
8442
+ // src/import/materializeDmlSource.ts
8443
+ function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInfos, recordNumberSourceHeader) {
8444
+ const decoded = decodeCsv(payload.bytes, {
8445
+ encoding: source.encoding ?? payload.encoding ?? "utf8",
8446
+ hasHeader: source.hasHeader,
8447
+ columns: source.columns
8448
+ });
8449
+ if (decoded.rows.length > maxRows) {
8450
+ throw new ImportSourceError(`source rows (${decoded.rows.length}) exceed maxRecords (${maxRows}).`);
8451
+ }
8452
+ if (source.mappingMode === "BY_NAME") {
8453
+ if (!targetCodes || !fieldInfos) throw new Error("InternalError: BY NAME requires destination form metadata.");
8454
+ if (new Set(targetCodes).size !== targetCodes.length) {
8455
+ throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: a BY NAME header cannot be consumed more than once.");
8456
+ }
8457
+ const indexes = new Map(decoded.columns.map((column, index) => [column, index]));
8458
+ for (const code of targetCodes) {
8459
+ if (!indexes.has(code)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${code}" is missing.`);
8460
+ }
8461
+ const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
8462
+ const targetSet = new Set(targetCodes);
8463
+ if (recordNumberSourceHeader && targetSet.has(recordNumberSourceHeader)) {
8464
+ throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: record-number source header is lookup-only and cannot be a write target.");
8465
+ }
8466
+ if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) {
8467
+ throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
8468
+ }
8469
+ const ignoredKnownColumns = [];
8470
+ const ignoredUnknownColumns = [];
8471
+ const nonEmpty = (index) => decoded.rows.filter((row) => row[index] !== "").length;
8472
+ const reasonFor = (info) => {
8473
+ if (info.fieldType === "FILE") return "FILE attachment is outside flat IMPORT scope";
8474
+ if (info.inSubtable || info.fieldType === "SUBTABLE") return "subtable field is not writable in Phase 3";
8475
+ if (info.writable === false) return `non-writable ${info.fieldType} field`;
8476
+ return `known export-only ${info.fieldType} field`;
8477
+ };
8478
+ for (const [index, column] of decoded.columns.entries()) {
8479
+ if (targetSet.has(column) || column === recordNumberSourceHeader) continue;
8480
+ const info = infoByCode.get(column);
8481
+ if (info) ignoredKnownColumns.push({ column, reason: reasonFor(info), nonEmptyCells: nonEmpty(index) });
8482
+ else if (!source.ignoreUnknownColumns) throw new ImportSourceError(`ERR_IMPORT_UNKNOWN_COLUMN: unknown CSV header "${column}".`);
8483
+ else ignoredUnknownColumns.push({ column, reason: "unknown column ignored by explicit policy", nonEmptyCells: nonEmpty(index) });
8484
+ }
8485
+ for (const code of targetCodes) {
8486
+ const info = infoByCode.get(code);
8487
+ if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
8488
+ if (info.inSubtable || info.writable === false || info.fieldType === "FILE" || info.fieldType === "SUBTABLE") {
8489
+ throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
8490
+ }
8491
+ }
8492
+ const importRowErrors = [];
8493
+ const rows2 = decoded.rows.map((values) => {
8494
+ const errors = [];
8495
+ const row = {};
8496
+ for (const code of targetCodes) {
8497
+ const raw = values[indexes.get(code)];
8498
+ try {
8499
+ row[code] = convertImportCsvValue(raw, infoByCode.get(code)?.fieldType, { cliKintone: true });
8500
+ } catch (error) {
8501
+ if (!(error instanceof ImportCsvValueError)) throw error;
8502
+ row[code] = raw;
8503
+ errors.push({ field: code, code: error.code, message: error.message });
8504
+ }
8505
+ }
8506
+ importRowErrors.push(errors);
8507
+ return row;
8508
+ });
8509
+ return {
8510
+ rows: rows2,
8511
+ columns: [...targetCodes],
8512
+ columnMeta: new Map(targetCodes.map((code) => [code, { fieldType: infoByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }])),
8513
+ importRowErrors,
8514
+ ...recordNumberSourceHeader ? { recordNumberSourceValues: decoded.rows.map((row) => row[indexes.get(recordNumberSourceHeader)]) } : {},
8515
+ importAudit: { mapping: "BY_NAME", writtenColumns: [...targetCodes], ignoredKnownColumns, ignoredUnknownColumns }
8516
+ };
8517
+ }
8518
+ const rows = decoded.rows.map((values) => Object.fromEntries(decoded.columns.map((column, i) => [column, values[i]])));
8519
+ return {
8520
+ rows,
8521
+ columns: decoded.columns,
8522
+ // CSV cells are decoded as strings; projections such as CAST may replace this metadata downstream.
8523
+ columnMeta: new Map(decoded.columns.map((column) => [column, { fieldType: "SINGLE_LINE_TEXT" }]))
8524
+ };
8525
+ }
8526
+
8527
+ // src/import/importRecordsMaterializer.ts
8528
+ var sourceFail = (parentRow, code, message) => {
8529
+ throw new ImportSourceError(`JSON subtable validation failed (parentRow=${parentRow}, field=${code}): ${message}`);
8530
+ };
8531
+ function materializeJsonImportRecords(_source, payload, targets, maxParents, maxChildRows = maxParents) {
8532
+ if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
8533
+ const decoded = decodeJsonRecords(payload.bytes);
8534
+ if (decoded.length > maxParents) throw new ImportSourceError(`source parent rows (${decoded.length}) exceed maxRecords (${maxParents}).`);
8535
+ const targetByCode = new Map(targets.map((target) => [target.kind === "FIELD" ? target.field : target.subtableCode, target]));
8536
+ if (targetByCode.size !== targets.length) throw new ImportSourceError("IMPORT targets contain duplicates.");
8537
+ let childTotal = 0;
8538
+ return {
8539
+ records: decoded.map((record, index) => {
8540
+ const parentRow = index + 1;
8541
+ for (const code of record.keys()) if (!targetByCode.has(code)) sourceFail(parentRow, code, "unknown key (not declared in INTO).");
8542
+ const top = /* @__PURE__ */ new Map();
8543
+ const subtables = /* @__PURE__ */ new Map();
8544
+ const replacementTables = /* @__PURE__ */ new Set();
8545
+ for (const target of targets) {
8546
+ const code = target.kind === "FIELD" ? target.field : target.subtableCode;
8547
+ if (!record.has(code)) continue;
8548
+ const value = record.get(code);
8549
+ if (target.kind === "FIELD") {
8550
+ if (value instanceof Map) sourceFail(parentRow, code, "object is not accepted for a top-level field.");
8551
+ top.set(code, value);
8552
+ continue;
8553
+ }
8554
+ if (!Array.isArray(value)) sourceFail(parentRow, code, "subtable value must be an array.");
8555
+ replacementTables.add(code);
8556
+ const children = new Set(target.children);
8557
+ const rows = value.map((entry, childIndex) => {
8558
+ if (!(entry instanceof Map)) sourceFail(parentRow, code, `childRow=${childIndex + 1} must be an object.`);
8559
+ const child = entry;
8560
+ for (const childCode of child.keys()) {
8561
+ if (!children.has(childCode)) sourceFail(parentRow, childCode, `unknown child key in subtable ${code} at childRow=${childIndex + 1}.`);
8562
+ }
8563
+ childTotal++;
8564
+ if (childTotal > maxChildRows) throw new ImportSourceError(`source child rows (${childTotal}) exceed limit (${maxChildRows}).`);
8565
+ return { childRowNumber: childIndex + 1, values: child };
8566
+ });
8567
+ subtables.set(code, rows);
8568
+ }
8569
+ return { rowNumber: parentRow, top, subtables, replacementTables };
8570
+ })
8571
+ };
8572
+ }
8573
+ function materializeCliKintoneCsvImportRecords(source, payload, targets, replacementTables, maxParents, recordNumberSourceHeader) {
8574
+ const decoded = decodeCsv(payload.bytes, {
8575
+ encoding: source.encoding ?? payload.encoding ?? "utf8",
8576
+ hasHeader: source.hasHeader,
8577
+ columns: source.columns
8578
+ });
8579
+ if (!source.hasHeader || decoded.columns[0] !== "*") throw new ImportSourceError('ERR_IMPORT_MARKER: first CSV header must be "*".');
8580
+ const indexes = new Map(decoded.columns.map((code, index) => [code, index]));
8581
+ if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
8582
+ const fields = targets.filter((target) => target.kind === "FIELD");
8583
+ const tables = targets.filter((target) => target.kind === "SUBTABLE");
8584
+ for (const field of fields) if (!indexes.has(field.field)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${field.field}" is missing.`);
8585
+ for (const table of tables) {
8586
+ if (!table.rowIdSourceHeader || !indexes.has(table.rowIdSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: row-ID header for ${table.subtableCode} is missing.`);
8587
+ for (const child of table.children) if (!indexes.has(child)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required child header "${child}" is missing.`);
8588
+ }
8589
+ const records = [];
8590
+ let current;
8591
+ decoded.rows.forEach((cells, physicalIndex) => {
8592
+ const sourceRowNumber = physicalIndex + 2;
8593
+ const marker = cells[0];
8594
+ if (marker !== "" && marker !== "*") throw new ImportSourceError(`ERR_IMPORT_MARKER: invalid marker ${JSON.stringify(marker)} at source row ${sourceRowNumber}.`);
8595
+ if (marker === "*") {
8596
+ if (records.length >= maxParents) throw new ImportSourceError(`source parent rows exceed maxRecords (${maxParents}).`);
8597
+ current = {
8598
+ rowNumber: records.length + 1,
8599
+ markerRowNumber: sourceRowNumber,
8600
+ top: new Map(fields.map((field) => [field.field, cells[indexes.get(field.field)]])),
8601
+ subtables: new Map(tables.map((table) => [table.subtableCode, []])),
8602
+ replacementTables: new Set(replacementTables),
8603
+ ...recordNumberSourceHeader ? { recordNumberSourceValue: cells[indexes.get(recordNumberSourceHeader)] } : {}
8604
+ };
8605
+ records.push(current);
8606
+ } else if (!current) {
8607
+ throw new ImportSourceError(`ERR_IMPORT_MARKER: first data row must start a parent (source row ${sourceRowNumber}).`);
8608
+ } else {
8609
+ for (const field of fields) {
8610
+ const continuationValue = cells[indexes.get(field.field)];
8611
+ if (continuationValue !== "" && continuationValue !== current.top.get(field.field)) {
8612
+ throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${field.field} at source row ${sourceRowNumber}.`);
8613
+ }
8614
+ }
8615
+ if (recordNumberSourceHeader) {
8616
+ const continuationValue = cells[indexes.get(recordNumberSourceHeader)];
8617
+ if (continuationValue !== "" && continuationValue !== current.recordNumberSourceValue) {
8618
+ throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${recordNumberSourceHeader} at source row ${sourceRowNumber}.`);
8619
+ }
8620
+ }
8621
+ }
8622
+ for (const table of tables) {
8623
+ const rowId = cells[indexes.get(table.rowIdSourceHeader)];
8624
+ const values = new Map(table.children.map((child) => [child, cells[indexes.get(child)]]));
8625
+ if (rowId === "" && [...values.values()].every((value) => value === "")) continue;
8626
+ const rows = current.subtables.get(table.subtableCode);
8627
+ rows.push({ childRowNumber: rows.length + 1, sourceRowNumber, ...rowId ? { rowId } : {}, values });
8628
+ }
8629
+ });
8630
+ if (records.length === 0) throw new ImportSourceError("CSV has no parent rows.");
8631
+ return { records };
8632
+ }
8633
+
8634
+ // src/import/importRecordValidation.ts
8635
+ var USER_TYPES3 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8636
+ var UNSUPPORTED_CHILD_TYPES = /* @__PURE__ */ new Set(["SUBTABLE", "FILE", "CALC", "RECORD_NUMBER", "CREATOR", "CREATED_TIME", "MODIFIER", "UPDATED_TIME", "STATUS", "STATUS_ASSIGNEE", "CATEGORY", "REFERENCE_TABLE"]);
8637
+ function assertImportRejectLimit(prepared, rejectLimit) {
8638
+ if (rejectLimit != null && prepared.invalidParentRows.size > rejectLimit) {
8639
+ throw new Error(`RejectLimitExceededError: rejected parents (${prepared.invalidParentRows.size}) exceed REJECT LIMIT (${rejectLimit}).`);
8640
+ }
8641
+ }
8642
+ function prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation) {
8643
+ const topInfos = new Map(fieldInfos.filter((f) => !f.inSubtable).map((f) => [f.code, f]));
8644
+ const scoped = /* @__PURE__ */ new Map();
8645
+ for (const info of fieldInfos) if (info.inSubtable && info.subtableCode) {
8646
+ let children = scoped.get(info.subtableCode);
8647
+ if (!children) scoped.set(info.subtableCode, children = /* @__PURE__ */ new Map());
8648
+ children.set(info.code, info);
8649
+ }
8650
+ const targetTop = targets.filter((t) => t.kind === "FIELD");
8651
+ const targetTables = targets.filter((t) => t.kind === "SUBTABLE");
8652
+ for (const target of targetTop) assertWritable(target.field, topInfos.get(target.field), void 0);
8653
+ for (const target of targetTables) {
8654
+ const table = topInfos.get(target.subtableCode);
8655
+ if (!table || table.fieldType !== "SUBTABLE") throw new Error(`ArgumentError: IMPORT subtable ${target.subtableCode} does not exist.`);
8656
+ const children = scoped.get(target.subtableCode) ?? /* @__PURE__ */ new Map();
8657
+ for (const child of target.children) assertWritable(child, children.get(child), target.subtableCode);
8658
+ }
8659
+ const tableCounts = new Map(targetTables.map((t) => [t.subtableCode, { parentsPresent: 0, childRows: 0, validChildRows: 0, invalidChildRows: 0 }]));
8660
+ const parents = materialized.records.map((record) => validateParent(record, targetTop, targetTables, topInfos, scoped, numberPrecision, operation, tableCounts));
8661
+ const errors = parents.flatMap((parent) => [...parent.errors]);
8662
+ return { parents, errors, invalidParentRows: new Set(parents.filter((p) => !p.valid).map((p) => p.parentRow)), tableCounts };
8663
+ }
8664
+ function validateParent(source, topTargets, tableTargets, topInfos, scoped, precision, operation, tableCounts) {
8665
+ const errors = [];
8666
+ const top = {};
8667
+ for (const target of topTargets) {
8668
+ if (!source.top.has(target.field)) continue;
8669
+ validateValue(source.top.get(target.field), topInfos.get(target.field), precision, top, target.field, errors, location(source, operation, target.field));
8670
+ }
8671
+ const createValidationOnly = {};
8672
+ if (operation === "INSERT") for (const info of topInfos.values()) {
8673
+ if (info.fieldType === "SUBTABLE" || info.writable === false || source.top.has(info.code)) continue;
8674
+ validateMissing(info, precision, createValidationOnly, errors, location(source, operation, info.code));
8675
+ }
8676
+ const subtables = /* @__PURE__ */ new Map();
8677
+ for (const target of tableTargets) {
8678
+ if (!source.subtables.has(target.subtableCode)) continue;
8679
+ const count = tableCounts.get(target.subtableCode);
8680
+ count.parentsPresent++;
8681
+ const preparedRows = [];
8682
+ for (const child of source.subtables.get(target.subtableCode)) {
8683
+ count.childRows++;
8684
+ const before = errors.length;
8685
+ const record = {};
8686
+ const infos = scoped.get(target.subtableCode);
8687
+ for (const code of target.children) {
8688
+ const info = infos.get(code);
8689
+ const loc = location(source, operation, code, target.subtableCode, child.childRowNumber, child.sourceRowNumber ?? source.markerRowNumber);
8690
+ if (child.values.has(code)) validateValue(child.values.get(code), info, precision, record, code, errors, loc);
8691
+ else validateMissing(info, precision, record, errors, loc);
8692
+ }
8693
+ if (errors.length === before) {
8694
+ count.validChildRows++;
8695
+ preparedRows.push(record);
8696
+ } else count.invalidChildRows++;
8697
+ }
8698
+ subtables.set(target.subtableCode, preparedRows);
8699
+ }
8700
+ return { parentRow: source.rowNumber, valid: errors.length === 0, top, subtables, replacementTables: source.replacementTables, errors };
8701
+ }
8702
+ function assertWritable(code, info, table) {
8703
+ if (!info) throw new Error(table ? `ArgumentError: IMPORT child ${code} does not belong to subtable ${table}.` : `ArgumentError: IMPORT top-level field ${code} does not exist.`);
8704
+ if (info.writable === false || table && UNSUPPORTED_CHILD_TYPES.has(info.fieldType)) {
8705
+ throw new Error(`ArgumentError: IMPORT ${table ? `child ${table}.${code}` : `field ${code}`} is not writable (${info.fieldType}).`);
8706
+ }
8707
+ }
8708
+ function validateMissing(info, precision, record, errors, loc) {
8709
+ const raw = isEmptyDmlValue(info.defaultValue) ? "" : info.defaultValue;
8710
+ validateValue(raw, info, precision, record, info.code, errors, loc, !isEmptyDmlValue(info.defaultValue));
8711
+ }
8712
+ function validateValue(raw, info, precision, record, code, errors, loc, isDefault = false) {
8713
+ const normalizedRaw = decodeRaw(raw);
8714
+ const result = validateAndNormalizeDmlValue(normalizedRaw, info, precision);
8715
+ if (!result.ok) errors.push({ ...loc, code: result.code, message: isDefault ? `\u65E2\u5B9A\u5024: ${result.message}` : result.message });
8716
+ else record[code] = { value: preserveUserCodes(normalizedRaw, info) ? normalizedRaw : result.value };
8717
+ }
8718
+ function decodeRaw(raw) {
8719
+ if (isJsonNumber(raw)) return raw.lexeme;
8720
+ if (Array.isArray(raw)) return raw.map((value) => value instanceof Map ? value : isJsonNumber(value) ? value.lexeme : value);
8721
+ return raw;
8722
+ }
8723
+ function isJsonNumber(raw) {
8724
+ return typeof raw === "object" && raw !== null && raw.kind === "number";
8725
+ }
8726
+ function preserveUserCodes(raw, info) {
8727
+ return USER_TYPES3.has(info.fieldType) && Array.isArray(raw) && raw.every((v) => typeof v === "object" && v !== null && "code" in v);
8728
+ }
8729
+ function location(source, operation, field, subtable, subrow, sourceRow) {
8730
+ const physicalRow = sourceRow ?? source.markerRowNumber;
8731
+ return { operation, parentRow: source.rowNumber, field, ...subtable ? { subtable } : {}, ...subrow == null ? {} : { subrow }, ...physicalRow == null ? {} : { sourceRow: physicalRow }, sourceValues: subtable ? source.subtables.get(subtable)?.[subrow - 1]?.values ?? /* @__PURE__ */ new Map() : source.top };
8732
+ }
8733
+
8734
+ // src/import/importErrors.ts
8735
+ var IMPORT_VALIDATION_META_COLUMNS = [
8736
+ "$err_statement",
8737
+ "$err_operation",
8738
+ "$err_row",
8739
+ "$err_field",
8740
+ "$err_subtable",
8741
+ "$err_subrow",
8742
+ "$err_source_row",
8743
+ "$err_code",
8744
+ "$err_message"
8745
+ ];
8746
+ function materializeImportValidationErrors(errors, payloadFields, statementNumber = 1) {
8747
+ return errors.map((error) => {
8748
+ const row = {};
8749
+ for (const field of payloadFields) row[field] = error.sourceValues.get(field) == null ? "" : render(error.sourceValues.get(field));
8750
+ row["$err_statement"] = String(statementNumber);
8751
+ row["$err_operation"] = error.operation;
8752
+ row["$err_row"] = String(error.parentRow);
8753
+ row["$err_field"] = error.field;
8754
+ row["$err_subtable"] = error.subtable ?? "";
8755
+ row["$err_subrow"] = error.subrow == null ? "" : String(error.subrow);
8756
+ row["$err_source_row"] = error.sourceRow == null ? null : String(error.sourceRow);
8757
+ row["$err_code"] = error.code;
8758
+ row["$err_message"] = error.message;
8759
+ return row;
8760
+ });
8761
+ }
8762
+ function render(value) {
8763
+ if (value === null || value === void 0) return "";
8764
+ if (typeof value === "object" && value !== null && "kind" in value && "lexeme" in value && value.kind === "number") {
8765
+ return String(value.lexeme);
8766
+ }
8767
+ if (Array.isArray(value)) return JSON.stringify(value);
8768
+ return String(value);
8769
+ }
8770
+
8771
+ // src/import/subtablePayload.ts
8772
+ function buildImportRecordPayload(top, subtables, rowIdMode) {
8773
+ const record = {};
8774
+ for (const [code, value] of top) record[code] = { value };
8775
+ for (const [tableCode, sourceRows] of subtables) {
8776
+ record[tableCode] = {
8777
+ value: sourceRows.map((sourceRow) => ({
8778
+ ...rowIdMode === "PRESERVE" && sourceRow.rowId ? { id: sourceRow.rowId } : {},
8779
+ value: Object.fromEntries([...sourceRow.values].map(([childCode, value]) => [childCode, { value }]))
8780
+ }))
8781
+ };
8782
+ }
8783
+ return record;
8784
+ }
8785
+ function buildJsonImportRecordPayload(top, subtables) {
8786
+ return buildImportRecordPayload(top, subtables, "DROP");
8787
+ }
8788
+
8789
+ // src/import/jsonSubtableWritePlan.ts
8790
+ function assertJsonImportHasNoRowIds(materialized) {
8791
+ for (const parent of materialized.records) for (const [table, rows] of parent.subtables) {
8792
+ for (const row of rows) {
8793
+ if (row.rowId !== void 0 || row.values.has("_rid") || row.values.has("id")) {
8794
+ throw new Error(`ArgumentError: JSON IMPORT subtable ${table} does not accept _rid/id; rows are always newly numbered.`);
8795
+ }
8796
+ }
7804
8797
  }
7805
- if (op === "AND" && (left.capability === "EXACT_PUSHDOWN" || right.capability === "EXACT_PUSHDOWN" || left.capability === "SUPERSET_PREFILTER" || right.capability === "SUPERSET_PREFILTER")) {
8798
+ }
8799
+ function buildJsonSubtableWritePlan(parents, targetIds, existingById) {
8800
+ return parents.map((parent, index) => {
8801
+ const targetId = targetIds[index];
8802
+ const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
8803
+ if (targetId !== void 0 && !existing) throw new Error(`InternalError: IMPORT UPSERT target APP record ${targetId} was not loaded.`);
8804
+ const tables = [...parent.subtables].map(([table, input]) => {
8805
+ const raw = existing?.record[table]?.value;
8806
+ const existingRows = Array.isArray(raw) ? raw.length : 0;
8807
+ return { table, existingRows, inputRows: input.length, addRows: input.length, deleteRows: existingRows };
8808
+ });
7806
8809
  return {
7807
- capability: "SUPERSET_PREFILTER",
7808
- reasons: [{ code: "WHERE_SUPERSET_PREFILTER" }, ...reasons]
8810
+ parentRow: parent.parentRow,
8811
+ mode: targetId === void 0 ? "INSERT" : "UPDATE",
8812
+ ...targetId === void 0 ? {} : { targetId, revision: existing?.revision },
8813
+ top: parent.top,
8814
+ subtables: parent.subtables,
8815
+ tables
7809
8816
  };
7810
- }
7811
- return { capability: "LOCAL_ONLY", reasons };
8817
+ });
7812
8818
  }
7813
- function localExpression() {
7814
- return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
8819
+
8820
+ // src/import/subtableReplacementPlan.ts
8821
+ function tableRows(record, table) {
8822
+ const raw = record[table]?.value;
8823
+ return Array.isArray(raw) ? raw : [];
8824
+ }
8825
+ function assertNoDuplicateCsvSubtableRowIds(records) {
8826
+ const seen = /* @__PURE__ */ new Map();
8827
+ for (const parent of records) for (const [table, rows] of parent.subtables) for (const row of rows) {
8828
+ if (!row.rowId) continue;
8829
+ const key = `${table}\0${row.rowId}`;
8830
+ if (seen.has(key)) throw new Error(`ERR_SUBTABLE_ROW_ID_DUP_SOURCE: duplicate row ID ${row.rowId} in ${table}`);
8831
+ seen.set(key, parent.rowNumber);
8832
+ }
8833
+ }
8834
+ function buildCsvSubtableReplacementPlan(sources, prepared, targetIds, existingById, ownership) {
8835
+ return prepared.map((parent, index) => {
8836
+ const source = sources[index];
8837
+ const targetId = targetIds[index];
8838
+ const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
8839
+ const errors = [...parent.errors];
8840
+ if (!existing || targetId === void 0) return { parentRow: parent.parentRow, targetId: targetId ?? 0, valid: false, top: parent.top, subtables: /* @__PURE__ */ new Map(), tables: [], errors };
8841
+ const subtables = /* @__PURE__ */ new Map();
8842
+ const tables = [];
8843
+ for (const table of parent.replacementTables) {
8844
+ const current = tableRows(existing.record, table);
8845
+ const currentIds = new Set(current.map((row) => row.id).filter((id) => !!id));
8846
+ const input = source.subtables.get(table) ?? [];
8847
+ const normalized = parent.subtables.get(table) ?? [];
8848
+ let updateRows = 0, addRows = 0, rowIdNotFound = 0;
8849
+ const payloadRows = input.map((row, rowIndex) => {
8850
+ const normalizedRecord = normalized[rowIndex] ?? {};
8851
+ if (row.rowId && currentIds.has(row.rowId)) {
8852
+ updateRows++;
8853
+ return { rowId: row.rowId, record: normalizedRecord };
8854
+ }
8855
+ if (row.rowId) {
8856
+ const owners = ownership.get(row.rowId) ?? [];
8857
+ if (owners.some((owner) => owner.parentId !== targetId || owner.table !== table)) errors.push({
8858
+ operation: "UPDATE",
8859
+ parentRow: parent.parentRow,
8860
+ field: row.rowId,
8861
+ subtable: table,
8862
+ subrow: row.childRowNumber,
8863
+ sourceRow: row.sourceRowNumber,
8864
+ code: "ERR_IMPORT_FIELD_OWNERSHIP",
8865
+ message: `rowIdOwnedElsewhere: ${row.rowId}`,
8866
+ sourceValues: row.values
8867
+ });
8868
+ rowIdNotFound++;
8869
+ }
8870
+ addRows++;
8871
+ return { record: normalizedRecord };
8872
+ });
8873
+ subtables.set(table, payloadRows);
8874
+ tables.push({ table, existingRows: current.length, inputRows: input.length, updateRows, addRows, deleteRows: current.length - updateRows, rowIdNotFound });
8875
+ }
8876
+ return { parentRow: parent.parentRow, targetId, ...existing.revision === void 0 ? {} : { revision: existing.revision }, valid: errors.length === 0, top: parent.top, subtables, tables, errors };
8877
+ });
7815
8878
  }
7816
- function unsupported(code, field, fieldType, operator) {
7817
- return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
8879
+
8880
+ // src/import/importProjection.ts
8881
+ var IMPORT_PROJECTION_SOURCE = "#__import_source";
8882
+ function bindImportProjection(projection) {
8883
+ return { ...projection, from: { appId: 0, alias: null, cteName: IMPORT_PROJECTION_SOURCE } };
8884
+ }
8885
+
8886
+ // src/import/recordNumberUpdate.ts
8887
+ function normalizeImportRecordNumber(raw) {
8888
+ return /^[0-9]+$/.test(raw) ? raw.replace(/^0+(?=\d)/, "") : null;
8889
+ }
8890
+ function preflightImportRecordNumbers(values, header) {
8891
+ const normalized = values.map(normalizeImportRecordNumber);
8892
+ const seen = /* @__PURE__ */ new Set();
8893
+ for (const key of normalized) {
8894
+ if (key === null) continue;
8895
+ if (seen.has(key)) {
8896
+ throw new Error("ERR_RECORD_NUMBER_DUP_SOURCE: source contains a duplicate record number");
8897
+ }
8898
+ seen.add(key);
8899
+ }
8900
+ return {
8901
+ normalized,
8902
+ errors: normalized.map((key) => key === null ? [{
8903
+ field: header,
8904
+ code: "ERR_RECORD_NUMBER_INVALID",
8905
+ message: `${header} must be a non-empty ASCII decimal record number`
8906
+ }] : [])
8907
+ };
7818
8908
  }
7819
8909
 
7820
8910
  // src/execute.ts
@@ -7827,6 +8917,7 @@ var SearchAbortedError = class extends Error {
7827
8917
  };
7828
8918
  var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
7829
8919
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
8920
+ var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
7830
8921
  var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
7831
8922
  var nextDefaultCacheContextId = 1;
7832
8923
  function resolveCacheContext(client, explicit) {
@@ -7841,7 +8932,7 @@ function resolveCacheContext(client, explicit) {
7841
8932
  async function execute(sql, client, options = {}) {
7842
8933
  const startedAt = Date.now();
7843
8934
  const cacheContext = resolveCacheContext(client, options.cacheContext);
7844
- const stmt = parseSql(sql);
8935
+ const stmt = parseSql(sql, options.enableImport === true);
7845
8936
  const metrics = createEmptyMetrics();
7846
8937
  const countedClient = wrapClientWithMetrics(client, metrics);
7847
8938
  const collector = { aborted: false };
@@ -8021,6 +9112,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
8021
9112
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
8022
9113
  }
8023
9114
  validateKlikeStatement(stmt);
9115
+ if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
8024
9116
  if ("validateOnly" in stmt && stmt.validateOnly === true) {
8025
9117
  if (stmt.validationErrorTable) {
8026
9118
  throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
@@ -8078,25 +9170,84 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
8078
9170
  return executeAssert(stmt, client, options, cacheContext);
8079
9171
  }
8080
9172
  }
8081
- var EXISTING_VALIDATION_COLUMNS = ["$id", "$err_field", "$err_code", "$err_message", "$err_value"];
9173
+ var EXISTING_VALIDATION_COLUMNS = [
9174
+ "$id",
9175
+ "$err_field",
9176
+ "$err_code",
9177
+ "$err_message",
9178
+ "$err_value",
9179
+ "$err_subtable",
9180
+ "$err_subrow",
9181
+ "$err_subrow_id",
9182
+ "$err_count"
9183
+ ];
9184
+ var EXISTING_VALIDATION_SUMMARY_COLUMNS = [
9185
+ "$id",
9186
+ "$err_subtable",
9187
+ "$err_field",
9188
+ "$err_code",
9189
+ "$err_count"
9190
+ ];
8082
9191
  function hasAuditableConstraint(field) {
8083
9192
  return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
8084
9193
  }
8085
9194
  function resolveExistingValidationTargets(stmt, fieldInfos) {
8086
- const byCode = new Map(fieldInfos.map((field) => [field.code, field]));
8087
- const auditable = (field) => !field.inSubtable && (field.fieldType === "NUMBER" || hasAuditableConstraint(field));
8088
- if (stmt.fields === void 0) return fieldInfos.filter(auditable);
9195
+ const topByCode = new Map(fieldInfos.filter((field) => !field.inSubtable).map((field) => [field.code, field]));
9196
+ const childrenByTable = /* @__PURE__ */ new Map();
9197
+ for (const field of fieldInfos) {
9198
+ if (!field.inSubtable || !field.subtableCode) continue;
9199
+ const children = childrenByTable.get(field.subtableCode) ?? [];
9200
+ children.push(field);
9201
+ childrenByTable.set(field.subtableCode, children);
9202
+ }
9203
+ const auditable = (field) => field.fieldType === "NUMBER" || hasAuditableConstraint(field);
9204
+ if (stmt.targets === void 0) return [
9205
+ ...fieldInfos.filter((field) => !field.inSubtable && field.fieldType !== "SUBTABLE" && auditable(field)),
9206
+ ...fieldInfos.filter((field) => field.inSubtable && !!field.subtableCode && auditable(field))
9207
+ ].map((field) => ({ field, ...field.subtableCode ? { subtableCode: field.subtableCode } : {} }));
9208
+ const result = [];
8089
9209
  const seen = /* @__PURE__ */ new Set();
8090
- return stmt.fields.map((code) => {
8091
- if (seen.has(code)) throw new Error(`ArgumentError: VALIDATE field ${code} is duplicated.`);
8092
- seen.add(code);
8093
- if (code === "$id") throw new Error("ArgumentError: VALIDATE cannot audit system field $id.");
8094
- const info = byCode.get(code);
8095
- if (!info) throw new Error(`ArgumentError: VALIDATE field ${code} does not exist.`);
8096
- if (info.inSubtable) throw new Error(`ArgumentError: VALIDATE field ${code} is a subtable child field.`);
8097
- if (!auditable(info)) throw new Error(`ArgumentError: VALIDATE field ${code} has no auditable constraint.`);
8098
- return info;
8099
- });
9210
+ const add = (field, subtableCode) => {
9211
+ const key = subtableCode ? `${subtableCode}\0${field.code}` : field.code;
9212
+ if (seen.has(key)) throw new Error(`ArgumentError: VALIDATE \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${field.code} \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002`);
9213
+ seen.add(key);
9214
+ if (!auditable(field)) throw new Error(`ArgumentError: VALIDATE \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${field.code} \u306B\u306F\u76E3\u67FB\u53EF\u80FD\u306A\u5236\u7D04\u304C\u3042\u308A\u307E\u305B\u3093\u3002`);
9215
+ result.push({ field, ...subtableCode ? { subtableCode } : {} });
9216
+ };
9217
+ for (const target of stmt.targets) {
9218
+ if (target.kind === "SUBTABLE") {
9219
+ const children = childrenByTable.get(target.subtableCode);
9220
+ if (!children) throw new Error(`ArgumentError: VALIDATE \u306E\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB ${target.subtableCode} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`);
9221
+ if (target.children.length === 0) throw new Error(`ArgumentError: VALIDATE \u306E\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB ${target.subtableCode} \u306B\u306F1\u3064\u4EE5\u4E0A\u306E\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002`);
9222
+ for (const code2 of target.children) {
9223
+ const child = children.find((field) => field.code === code2);
9224
+ if (!child) {
9225
+ const belongsElsewhere = [...childrenByTable.entries()].some(([table, fields]) => table !== target.subtableCode && fields.some((field) => field.code === code2));
9226
+ throw new Error(belongsElsewhere ? `ArgumentError: VALIDATE \u306E\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${code2} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB ${target.subtableCode} \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093\u3002` : `ArgumentError: VALIDATE \u306E\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${code2} \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB ${target.subtableCode} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`);
9227
+ }
9228
+ add(child, target.subtableCode);
9229
+ }
9230
+ continue;
9231
+ }
9232
+ const code = target.field;
9233
+ if (code === "$id") throw new Error("ArgumentError: VALIDATE \u3067\u306F\u30B7\u30B9\u30C6\u30E0\u30D5\u30A3\u30FC\u30EB\u30C9 $id \u3092\u76E3\u67FB\u3067\u304D\u307E\u305B\u3093\u3002");
9234
+ const top = topByCode.get(code);
9235
+ if (top?.fieldType === "SUBTABLE") {
9236
+ const children = (childrenByTable.get(code) ?? []).filter(auditable);
9237
+ if (children.length === 0) throw new Error(`ArgumentError: VALIDATE \u306E\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB ${code} \u306B\u306F\u76E3\u67FB\u53EF\u80FD\u306A\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u305B\u3093\u3002`);
9238
+ children.forEach((child) => add(child, code));
9239
+ continue;
9240
+ }
9241
+ if (top) {
9242
+ add(top);
9243
+ continue;
9244
+ }
9245
+ if ([...childrenByTable.values()].some((children) => children.some((field) => field.code === code))) {
9246
+ 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`);
9247
+ }
9248
+ throw new Error(`ArgumentError: VALIDATE \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${code} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002`);
9249
+ }
9250
+ return result;
8100
9251
  }
8101
9252
  function collectValidateWhereFields(where) {
8102
9253
  const fields = [];
@@ -8121,11 +9272,12 @@ function collectValidateWhereFields(where) {
8121
9272
  visit(where);
8122
9273
  return fields;
8123
9274
  }
8124
- function existingValidationColumnMeta() {
8125
- return new Map(EXISTING_VALIDATION_COLUMNS.map((column) => [column, {
8126
- fieldType: column === "$id" ? "KSQL_NUMBER" : "KSQL_STRING",
8127
- sortKind: column === "$id" ? "number" : "string",
8128
- semantics: syntheticSemantics(column === "$id" ? "number" : "string")
9275
+ function existingValidationColumnMeta(summary = false) {
9276
+ const columns = summary ? EXISTING_VALIDATION_SUMMARY_COLUMNS : EXISTING_VALIDATION_COLUMNS;
9277
+ return new Map(columns.map((column) => [column, {
9278
+ fieldType: column === "$id" || column === "$err_count" ? "KSQL_NUMBER" : "KSQL_STRING",
9279
+ sortKind: column === "$id" || column === "$err_count" ? "number" : "string",
9280
+ semantics: syntheticSemantics(column === "$id" || column === "$err_count" ? "number" : "string")
8129
9281
  }]));
8130
9282
  }
8131
9283
  async function executeExistingRecordValidation(stmt, client, options, cacheContext) {
@@ -8134,38 +9286,45 @@ async function executeExistingRecordValidation(stmt, client, options, cacheConte
8134
9286
  }
8135
9287
  async function executeExistingRecordValidationCore(stmt, client, options, cacheContext) {
8136
9288
  const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
8137
- const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
9289
+ const infoByCode = new Map(fieldInfos.filter((field) => !field.inSubtable).map((field) => [field.code, field]));
9290
+ const childCodes = new Set(fieldInfos.filter((field) => field.inSubtable).map((field) => field.code));
8138
9291
  const targets = resolveExistingValidationTargets(stmt, fieldInfos);
8139
9292
  const checkGroups = stmt.checkGroups ?? [];
8140
9293
  const checkRefs2 = collectCheckFieldRefs(checkGroups);
8141
9294
  for (const ref of checkRefs2) {
8142
- if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
9295
+ if (ref.field !== "$id" && !infoByCode.has(ref.field) && !childCodes.has(ref.field)) {
8143
9296
  throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${stmt.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
8144
9297
  }
9298
+ if (ref.field !== "$id" && !infoByCode.has(ref.field) && childCodes.has(ref.field)) {
9299
+ throw new Error(`ArgumentError: VALIDATE \u306E CHECK \u3067\u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\u3002`);
9300
+ }
8145
9301
  }
8146
- const evaluationTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
9302
+ const evaluationTypes = new Map([...infoByCode].map(([code, field]) => [code, field.fieldType]));
8147
9303
  evaluationTypes.set("$id", "RECORD_NUMBER");
8148
9304
  assertCheckComparisonTypes(stmt, evaluationTypes);
8149
9305
  const whereFields = collectValidateWhereFields(stmt.where);
8150
9306
  const requiredFields = [.../* @__PURE__ */ new Set([
8151
9307
  "$id",
8152
- ...targets.map((field) => field.code),
9308
+ ...targets.map((target) => target.subtableCode ?? target.field.code),
8153
9309
  ...whereFields,
8154
9310
  ...checkRefs2.map((ref) => ref.field)
8155
9311
  ])];
8156
9312
  for (const field of whereFields) {
8157
- if (field !== "$id" && !infoByCode.has(field)) {
9313
+ if (field !== "$id" && !infoByCode.has(field) && !childCodes.has(field)) {
8158
9314
  throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${stmt.appId}.`);
8159
9315
  }
9316
+ if (field !== "$id" && !infoByCode.has(field) && childCodes.has(field)) {
9317
+ throw new Error(`ArgumentError: VALIDATE \u306E WHERE \u3067\u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\u3002`);
9318
+ }
8160
9319
  }
8161
- const numberPrecision = targets.some((field) => field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
9320
+ const numberPrecision = targets.some((target) => target.field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
8162
9321
  const semantics = (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0);
8163
9322
  const capability = classifyWhereCapability(stmt.where, semantics);
8164
9323
  if (capability.capability === "UNSUPPORTED") {
8165
9324
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
8166
9325
  }
8167
- const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
8168
- const fieldOptions = new Map(fieldInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
9326
+ const fieldTypes = new Map([...infoByCode].map(([code, field]) => [code, field.fieldType]));
9327
+ const fieldOptions = new Map([...infoByCode.values()].flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
8169
9328
  const prefilter = stmt.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? stmt.where : extractSafePushdownLeaves(stmt.where, {
8170
9329
  allowUnqualifiedFields: true,
8171
9330
  fieldTypes,
@@ -8184,36 +9343,110 @@ async function executeExistingRecordValidationCore(stmt, client, options, cacheC
8184
9343
  flat: flatten(record, null)
8185
9344
  })).filter((row) => stmt.where === null || evalWhere(stmt.where, row.flat, (field) => evaluationTypes.get(field.field)));
8186
9345
  const rows = [];
9346
+ const detailRows = /* @__PURE__ */ new Map();
9347
+ const summaryRows = /* @__PURE__ */ new Map();
9348
+ const errorRecordIds = /* @__PURE__ */ new Set();
9349
+ let errorCount = 0;
9350
+ const appendError = (error) => {
9351
+ errorRecordIds.add(error.id);
9352
+ errorCount += 1;
9353
+ if (stmt.summary) {
9354
+ const key2 = JSON.stringify([error.id, error.subtable ?? "", error.field, error.code]);
9355
+ const current2 = summaryRows.get(key2);
9356
+ if (current2) current2["$err_count"] = String(Number(current2["$err_count"]) + 1);
9357
+ else summaryRows.set(key2, {
9358
+ "$id": error.id,
9359
+ "$err_subtable": error.subtable ?? "",
9360
+ "$err_field": error.field,
9361
+ "$err_code": error.code,
9362
+ "$err_count": "1"
9363
+ });
9364
+ return;
9365
+ }
9366
+ const key = JSON.stringify([error.id, error.subtable ?? "", error.field, error.code, error.message]);
9367
+ const current = detailRows.get(key);
9368
+ if (current) {
9369
+ current["$err_count"] = String(Number(current["$err_count"]) + 1);
9370
+ if (error.subrow !== void 0) {
9371
+ current["$err_subrow"] = `${current["$err_subrow"]},${error.subrow}`;
9372
+ current["$err_subrow_id"] = `${current["$err_subrow_id"]},${error.subrowId ?? ""}`;
9373
+ }
9374
+ } else detailRows.set(key, {
9375
+ "$id": error.id,
9376
+ "$err_field": error.field,
9377
+ "$err_code": error.code,
9378
+ "$err_message": error.message,
9379
+ "$err_value": error.value,
9380
+ "$err_subtable": error.subtable ?? "",
9381
+ "$err_subrow": error.subrow === void 0 ? "" : String(error.subrow),
9382
+ "$err_subrow_id": error.subrowId ?? "",
9383
+ "$err_count": "1"
9384
+ });
9385
+ };
9386
+ const topTargets = targets.filter((target) => !target.subtableCode);
9387
+ const subtableTargets = /* @__PURE__ */ new Map();
9388
+ for (const target of targets) {
9389
+ if (!target.subtableCode) continue;
9390
+ const children = subtableTargets.get(target.subtableCode) ?? [];
9391
+ children.push(target);
9392
+ subtableTargets.set(target.subtableCode, children);
9393
+ }
8187
9394
  for (const row of validationRows) {
8188
- for (const field of targets) {
8189
- const raw = row.record[field.code]?.value;
8190
- const validation = validateAndNormalizeDmlValue(raw, field, numberPrecision);
8191
- if (validation.ok) continue;
8192
- rows.push({
8193
- "$id": row.id,
8194
- "$err_field": field.code,
8195
- "$err_code": validation.code,
8196
- "$err_message": validation.message,
8197
- "$err_value": renderExistingValidationValue(raw, field.fieldType)
9395
+ for (const target of topTargets) {
9396
+ const raw = row.record[target.field.code]?.value;
9397
+ const validation = validateAndNormalizeDmlValue(raw, target.field, numberPrecision);
9398
+ if (!validation.ok) appendError({
9399
+ id: row.id,
9400
+ field: target.field.code,
9401
+ code: validation.code,
9402
+ message: validation.message,
9403
+ value: renderExistingValidationValue(raw, target.field.fieldType)
8198
9404
  });
8199
9405
  }
9406
+ for (const [tableCode, childTargets] of subtableTargets) {
9407
+ const tableRows2 = row.record[tableCode]?.value;
9408
+ if (!Array.isArray(tableRows2)) continue;
9409
+ for (let i = 0; i < tableRows2.length; i++) {
9410
+ const tableRow = tableRows2[i];
9411
+ for (const target of childTargets) {
9412
+ const raw = tableRow.value?.[target.field.code]?.value;
9413
+ const validation = validateAndNormalizeDmlValue(raw, target.field, numberPrecision);
9414
+ if (!validation.ok) appendError({
9415
+ id: row.id,
9416
+ field: target.field.code,
9417
+ code: validation.code,
9418
+ message: validation.message,
9419
+ value: renderExistingValidationValue(raw, target.field.fieldType),
9420
+ subtable: tableCode,
9421
+ subrow: i + 1,
9422
+ subrowId: String(tableRow.id ?? "")
9423
+ });
9424
+ }
9425
+ }
9426
+ }
8200
9427
  for (const check of evaluateCustomChecks(checkGroups, row.flat, (field) => evaluationTypes.get(field.field))) {
8201
- rows.push({
8202
- "$id": row.id,
8203
- "$err_field": "",
8204
- "$err_code": "ERR_CHECK",
8205
- "$err_message": check.message,
8206
- "$err_value": ""
8207
- });
9428
+ appendError({ id: row.id, field: "", code: "ERR_CHECK", message: check.message, value: "" });
9429
+ }
9430
+ }
9431
+ if (stmt.summary) rows.push(...summaryRows.values());
9432
+ else {
9433
+ for (const row of detailRows.values()) {
9434
+ const count = Number(row["$err_count"]);
9435
+ if (row["$err_subtable"] !== "" && count >= 2) {
9436
+ row["$err_message"] = `${row["$err_message"]}\uFF08${count}\u884C: ${row["$err_subrow"]}\uFF09`;
9437
+ }
9438
+ rows.push(row);
8208
9439
  }
8209
9440
  }
9441
+ const columns = stmt.summary ? EXISTING_VALIDATION_SUMMARY_COLUMNS : EXISTING_VALIDATION_COLUMNS;
8210
9442
  const result = {
8211
9443
  type: "SELECT",
8212
- columns: [...EXISTING_VALIDATION_COLUMNS],
9444
+ columns: [...columns],
8213
9445
  rows,
8214
- rowCount: rows.length
9446
+ rowCount: rows.length,
9447
+ validateStats: { errorRecords: errorRecordIds.size, errorCount }
8215
9448
  };
8216
- materializedMetaBySelectResult.set(result, existingValidationColumnMeta());
9449
+ materializedMetaBySelectResult.set(result, existingValidationColumnMeta(stmt.summary === true));
8217
9450
  return result;
8218
9451
  }
8219
9452
  var TEMP_TABLE_MAX_ROWS = 1e4;
@@ -8243,7 +9476,7 @@ var BatchTimeoutError = class extends Error {
8243
9476
  }
8244
9477
  };
8245
9478
  async function executeBatch(sql, client, options = {}) {
8246
- const statements = parseSqlBatch(sql);
9479
+ const statements = parseSqlBatch(sql, options.enableImport === true);
8247
9480
  const analysis = analyzeBatch(statements);
8248
9481
  const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
8249
9482
  const batchOptions = { ...options, variables: injectedVariables };
@@ -8296,11 +9529,12 @@ async function executeBatch(sql, client, options = {}) {
8296
9529
  const userConfirm = batchOptions.confirm;
8297
9530
  const stmtOptions = userConfirm ? {
8298
9531
  ...batchOptions,
8299
- confirm: (count, operation) => userConfirm(count, operation, {
9532
+ confirm: (count, operation, detailContext) => userConfirm(count, operation, {
8300
9533
  statementIndex: i,
8301
9534
  statementCount: statements.length,
8302
9535
  statementType: info.statementType,
8303
- targetAppId: info.targetAppId
9536
+ targetAppId: info.targetAppId,
9537
+ ...detailContext?.importDetail ? { importDetail: detailContext.importDetail } : {}
8304
9538
  })
8305
9539
  } : batchOptions;
8306
9540
  const searchAbortCollector = { aborted: false };
@@ -8409,11 +9643,14 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
8409
9643
  result.columns,
8410
9644
  result.rows,
8411
9645
  options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
8412
- materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta()
9646
+ materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta(resolvedStmt.summary === true)
8413
9647
  );
8414
9648
  }
8415
9649
  return { result };
8416
9650
  }
9651
+ if (resolvedStmt.type === "IMPORT") {
9652
+ return { result: await executeImport(resolvedStmt, client, options, cacheContext, tempTables) };
9653
+ }
8417
9654
  if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
8418
9655
  const result = await executeDmlValidation(
8419
9656
  resolvedStmt,
@@ -8575,9 +9812,9 @@ function safeJsonStringify(v) {
8575
9812
  return String(v);
8576
9813
  }
8577
9814
  }
8578
- function parseSqlBatch(sql) {
9815
+ function parseSqlBatch(sql, enableImport = false) {
8579
9816
  const tokens = new Lexer(sql).tokenize();
8580
- return new Parser(tokens).parseStatements();
9817
+ return new Parser(tokens, { import: enableImport }).parseStatements();
8581
9818
  }
8582
9819
  function evaluateScalarExpr(expr) {
8583
9820
  switch (expr.type) {
@@ -9646,8 +10883,14 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
9646
10883
  }
9647
10884
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
9648
10885
  meta = syntheticColumnMeta("number");
9649
- } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") {
10886
+ } else if (column.type === "LITERAL_COL") {
9650
10887
  meta = syntheticColumnMeta("string");
10888
+ } else if (column.type === "SCALAR_VALUE_COL") {
10889
+ const expr = column.expr;
10890
+ if (expr.type === "STRING_FUNC") meta = stringFunctionColumnMeta(expr);
10891
+ else if (expr.type === "NUMBER" || expr.type === "SCALAR_ARITH") meta = syntheticColumnMeta("number");
10892
+ else if (expr.type === "FIELD") meta = resolveField2(expr);
10893
+ else meta = syntheticColumnMeta("string");
9651
10894
  } else if (column.type === "STRFUNC_COL") {
9652
10895
  meta = stringFunctionColumnMeta(column.expr);
9653
10896
  } else if (column.type === "WINDOW_COL") {
@@ -10461,9 +11704,10 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
10461
11704
  return sortKinds;
10462
11705
  }
10463
11706
  function convertProcessRowValue(raw, dstFieldType) {
10464
- const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
11707
+ if (typeof raw !== "string") return raw;
11708
+ const USER_TYPES4 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
10465
11709
  const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
10466
- if (USER_TYPES2.has(dstFieldType ?? "")) {
11710
+ if (USER_TYPES4.has(dstFieldType ?? "")) {
10467
11711
  if (raw === "") return [];
10468
11712
  try {
10469
11713
  const parsed = JSON.parse(raw);
@@ -10528,11 +11772,13 @@ function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecisio
10528
11772
  records.forEach((record, rowIndex) => {
10529
11773
  for (const code of targetFields) {
10530
11774
  const info = infoByCode.get(code);
10531
- const result = validateAndNormalizeDmlValue(record[code]?.value ?? "", info, numberPrecision);
11775
+ const original = record[code]?.value ?? "";
11776
+ const result = validateAndNormalizeDmlValue(original, info, numberPrecision);
10532
11777
  if (!result.ok) {
10533
11778
  throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
10534
11779
  }
10535
- record[code] = { value: result.value };
11780
+ const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(info.fieldType) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
11781
+ record[code] = { value: preserveCodes ? original : result.value };
10536
11782
  }
10537
11783
  });
10538
11784
  }
@@ -10692,6 +11938,8 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10692
11938
  if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
10693
11939
  let rows;
10694
11940
  let sourceRows;
11941
+ let sourcePresence;
11942
+ let sourceRowErrors;
10695
11943
  let evaluationTypes;
10696
11944
  if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
10697
11945
  assertInsertCheckRefs(stmt, stmt.fields);
@@ -10701,7 +11949,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10701
11949
  (value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
10702
11950
  ));
10703
11951
  } else {
10704
- const selectResult = tempTables && tempTables.size > 0 ? await executeQueryWithCte(stmt.select, client, { ...options, onLimitReached: "error" }, tempTables, cacheContext) : await executeSelect(stmt.select, client, { ...options, onLimitReached: "error" }, cacheContext, void 0, true);
11952
+ const selectResult = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, tempTables, [...infoByCode.values()]);
10705
11953
  const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
10706
11954
  if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
10707
11955
  throw new Error(`SELECT \u306E\u5217\u6570\uFF08${selectResult.columns.length}\uFF09\u3068 DML \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u6570\uFF08${stmt.fields.length}\uFF09\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`);
@@ -10711,7 +11959,9 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10711
11959
  }
10712
11960
  assertInsertCheckRefs(stmt, selectResult.columns);
10713
11961
  sourceRows = selectResult.rows;
10714
- const meta = materializedMetaBySelectResult.get(selectResult);
11962
+ sourcePresence = selectResult.importPresence;
11963
+ sourceRowErrors = selectResult.importRowErrors;
11964
+ const meta = selectResult.columnMeta;
10715
11965
  evaluationTypes = new Map(selectResult.columns.map((column) => {
10716
11966
  const columnMeta = meta?.get(column);
10717
11967
  const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
@@ -10724,8 +11974,10 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10724
11974
  rowNumber: index + 1,
10725
11975
  operation,
10726
11976
  mode: "create",
10727
- payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
10728
- preErrors: [],
11977
+ payload: new Map(stmt.fields.flatMap(
11978
+ (field, i) => sourcePresence && !sourcePresence[index]?.has(field) ? [] : [[field, values[i]]]
11979
+ )),
11980
+ preErrors: [...sourceRowErrors?.[index] ?? []],
10729
11981
  record: {},
10730
11982
  evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
10731
11983
  stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
@@ -10738,13 +11990,17 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10738
11990
  }
10739
11991
  const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
10740
11992
  const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
10741
- const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
10742
11993
  const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
10743
11994
  const keyCounts = /* @__PURE__ */ new Map();
10744
11995
  for (const parts of rowKeys) {
10745
11996
  const key = upsertNormalizedKey(parts, numeric);
10746
11997
  keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
10747
11998
  }
11999
+ const isImport = importSourceByDmlStatement.has(stmt);
12000
+ if (isImport && [...keyCounts.values()].some((count) => count > 1)) {
12001
+ throw new Error("ERR_KEY_DUP_SOURCE: UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059");
12002
+ }
12003
+ const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
10748
12004
  candidates.forEach((candidate, index) => {
10749
12005
  const parts = rowKeys[index];
10750
12006
  const targetId = lookupUpsertTarget(targets, parts);
@@ -10753,7 +12009,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10753
12009
  stmt.keyFields.forEach((key, keyIndex) => {
10754
12010
  if (parts[keyIndex] === "") candidate.preErrors.push({ field: key, code: "ERR_KEY_EMPTY", message: `UPSERT \u30AD\u30FC ${key} \u306F\u7A7A\u306B\u3067\u304D\u307E\u305B\u3093` });
10755
12011
  });
10756
- if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
12012
+ if (!isImport && (keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
10757
12013
  candidate.preErrors.push({ field: stmt.keyFields[0], code: "ERR_KEY_DUP_SOURCE", message: "UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059" });
10758
12014
  }
10759
12015
  });
@@ -11113,12 +12369,508 @@ async function executeInsert(stmt, client, options, cacheContext) {
11113
12369
  insertedCount: createdIds.flat().length
11114
12370
  };
11115
12371
  }
12372
+ function importPlaceholderSelect() {
12373
+ return {
12374
+ type: "SELECT",
12375
+ distinct: false,
12376
+ columns: [],
12377
+ from: { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME },
12378
+ joins: [],
12379
+ where: null,
12380
+ groupBy: [],
12381
+ having: null,
12382
+ orderMode: "CANONICAL",
12383
+ orderBy: [],
12384
+ limit: null,
12385
+ offset: null
12386
+ };
12387
+ }
12388
+ async function executeImport(stmt, client, options, cacheContext, tempTables) {
12389
+ if (!options.enableImport) throw new Error("UnsupportedError: IMPORT capability is disabled.");
12390
+ const handle = resolveImportSource(stmt.source.sourceName, options.importSource);
12391
+ if (stmt.targets?.some((target) => target.kind === "SUBTABLE")) {
12392
+ if (!stmt.validateOnly && !options.supportsImportConfirmDetail) {
12393
+ throw new Error("UnsupportedError: IMPORT subtable mutation requires a surface that displays parent/table replacement and deletion detail; use VALIDATE ONLY/EXPLAIN.");
12394
+ }
12395
+ if (stmt.source.kind === "CSV") {
12396
+ if (stmt.writeMode !== "UPDATE_RECORD_NUMBER" || !stmt.recordNumberSourceHeader) throw new Error("ArgumentError: CSV subtable replacement requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.");
12397
+ if (!stmt.replaceSubtables?.length) throw new Error("ArgumentError: CSV subtable replacement requires REPLACE SUBTABLES (...).");
12398
+ const declared = new Set(stmt.targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode));
12399
+ if (stmt.replaceSubtables.some((table) => !declared.has(table))) throw new Error("ArgumentError: REPLACE SUBTABLES contains a table not declared in INTO.");
12400
+ for (const target of stmt.targets.filter((target2) => target2.kind === "SUBTABLE")) {
12401
+ if (!target.rowIdSourceHeader || !stmt.replaceSubtables.includes(target.subtableCode)) throw new Error(`ArgumentError: CSV subtable ${target.subtableCode} requires ROW ID SOURCE and REPLACE SUBTABLES declaration.`);
12402
+ }
12403
+ }
12404
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12405
+ const targets = stmt.targets;
12406
+ const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
12407
+ const targetCodes = targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children);
12408
+ const numberPrecision = fieldInfos.some((info) => targetCodes.includes(info.code) && info.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
12409
+ const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
12410
+ const materialized = stmt.source.kind === "JSON" ? materializeJsonImportRecords(stmt.source, payload, targets, options.maxRecords ?? 1e4) : materializeCliKintoneCsvImportRecords(stmt.source, payload, targets, stmt.replaceSubtables ?? [], options.maxRecords ?? 1e4, stmt.recordNumberSourceHeader);
12411
+ const operation = stmt.source.kind === "CSV" ? "UPDATE" : stmt.keyFields ? "UPSERT" : "INSERT";
12412
+ const prepared = prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation);
12413
+ if (stmt.source.kind === "CSV") return executeCsvSubtableReplacement(stmt, materialized, prepared, fieldInfos, client, options, tempTables);
12414
+ assertImportRejectLimit(prepared, stmt.rejectLimit);
12415
+ const payloadFields = [...new Set(targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
12416
+ const errors = materializeImportValidationErrors(prepared.errors, payloadFields);
12417
+ const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
12418
+ const invalidRows = prepared.invalidParentRows.size;
12419
+ const detail = {
12420
+ preflight: "ACTUAL_DATA",
12421
+ parents: { total: prepared.parents.length, valid: prepared.parents.length - invalidRows, invalid: invalidRows, mutationCandidates: prepared.parents.filter((parent) => parent.valid).length },
12422
+ tables: Object.fromEntries(prepared.tableCounts),
12423
+ writesKintone: false
12424
+ };
12425
+ const result2 = {
12426
+ type: "VALIDATION",
12427
+ operation,
12428
+ validatedRows: prepared.parents.length,
12429
+ validRows: prepared.parents.length - invalidRows,
12430
+ invalidRows,
12431
+ errorCount: errors.length,
12432
+ columns,
12433
+ errors,
12434
+ importDetail: detail,
12435
+ ...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {}
12436
+ };
12437
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(
12438
+ tempTables,
12439
+ stmt.validationErrorTable,
12440
+ columns,
12441
+ errors,
12442
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12443
+ /* @__PURE__ */ new Map()
12444
+ );
12445
+ if (stmt.validateOnly) return result2;
12446
+ assertJsonImportHasNoRowIds(materialized);
12447
+ if (prepared.errors.length > 0 && !stmt.onErrorSkip) {
12448
+ const first = prepared.errors[0];
12449
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
12450
+ }
12451
+ if (stmt.onErrorSkip) {
12452
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
12453
+ appendValidationErrors(
12454
+ tempTables,
12455
+ stmt.errorTable,
12456
+ columns,
12457
+ errors,
12458
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12459
+ /* @__PURE__ */ new Map()
12460
+ );
12461
+ }
12462
+ const validParents = prepared.parents.filter((parent) => parent.valid);
12463
+ const fieldTypes = new Map(fieldInfos.map((info) => [info.code, info.fieldType]));
12464
+ const targetIds = validParents.map(() => void 0);
12465
+ if (stmt.keyFields) {
12466
+ for (const key of stmt.keyFields) if (!stmt.fields.includes(key)) {
12467
+ throw new Error(`ON DUPLICATE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C UPSERT \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093`);
12468
+ }
12469
+ const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
12470
+ const sourceKeys = /* @__PURE__ */ new Set();
12471
+ const rowKeys = validParents.map((parent) => stmt.keyFields.map((key) => String(parent.top[key]?.value ?? "")));
12472
+ for (const parts of rowKeys) {
12473
+ const normalized = upsertNormalizedKey(parts, numeric);
12474
+ if (sourceKeys.has(normalized)) throw new Error("ERR_KEY_DUP_SOURCE: UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059");
12475
+ sourceKeys.add(normalized);
12476
+ }
12477
+ const targetsIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
12478
+ rowKeys.forEach((parts, index) => {
12479
+ targetIds[index] = lookupUpsertTarget(targetsIndex, parts);
12480
+ });
12481
+ }
12482
+ const tableCodes = targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode);
12483
+ const existingById = /* @__PURE__ */ new Map();
12484
+ const updateIds = targetIds.filter((id) => id !== void 0);
12485
+ for (const chunk2 of splitChunks([...new Set(updateIds)], 100)) {
12486
+ const response = await client.getRecords({ app: stmt.appId, query: `$id in (${chunk2.join(",")}) limit 500`, fields: ["$id", "$revision", ...tableCodes] });
12487
+ for (const record of response.records) {
12488
+ const id = Number(record["$id"]?.value);
12489
+ const revision = Number(record["$revision"]?.value);
12490
+ if (Number.isFinite(id)) existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record });
12491
+ }
12492
+ }
12493
+ const writePlan = buildJsonSubtableWritePlan(validParents, targetIds, existingById);
12494
+ const importDetail = {
12495
+ kind: "IMPORT_JSON_SUBTABLE",
12496
+ rowIdPolicy: "DROP_AND_RENUMBER_ALL",
12497
+ parentsToWrite: writePlan.length,
12498
+ insertedParents: writePlan.filter((parent) => parent.mode === "INSERT").length,
12499
+ updatedParents: writePlan.filter((parent) => parent.mode === "UPDATE").length,
12500
+ hasDeletes: writePlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
12501
+ parents: writePlan.map((parent) => ({ parentRow: parent.parentRow, mode: parent.mode, ...parent.targetId === void 0 ? {} : { targetId: parent.targetId }, tables: parent.tables }))
12502
+ };
12503
+ if (writePlan.length > 0) {
12504
+ if (!options.confirm) throw new Error("UnsupportedError: JSON IMPORT subtable mutation requires explicit confirmation detail approval.");
12505
+ const ok = await options.confirm(writePlan.length, stmt.keyFields ? "UPDATE" : "INSERT", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
12506
+ if (!ok) throw new OperationCancelledError(stmt.keyFields ? "UPDATE" : "INSERT", writePlan.length);
12507
+ }
12508
+ const toScalarMap = (record) => new Map(
12509
+ Object.entries(record).map(([code, field]) => [code, field.value])
12510
+ );
12511
+ const payloadFor = (parent) => buildJsonImportRecordPayload(
12512
+ toScalarMap(parent.top),
12513
+ new Map([...parent.subtables].map(([table, rows]) => [table, rows.map((row) => ({ values: toScalarMap(row) }))]))
12514
+ );
12515
+ const inserts = writePlan.filter((parent) => parent.mode === "INSERT");
12516
+ const updates = writePlan.filter((parent) => parent.mode === "UPDATE");
12517
+ const createdIds = [];
12518
+ for (let i = 0; i < inserts.length; i += 100) {
12519
+ const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map(payloadFor) });
12520
+ createdIds.push(response.ids);
12521
+ }
12522
+ for (let i = 0; i < updates.length; i += 100) await client.putRecords({
12523
+ app: stmt.appId,
12524
+ records: updates.slice(i, i + 100).map((parent) => ({ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record: payloadFor(parent) }))
12525
+ });
12526
+ return stmt.keyFields ? { type: "UPSERT", insertedCount: createdIds.flat().length, updatedCount: updates.length, affectedRows: writePlan.length, skippedRows: prepared.invalidParentRows.size, rejectLimit: stmt.rejectLimit, ...stmt.errorTable ? { errTable: stmt.errorTable } : {}, importDetail } : { type: "INSERT", createdIds, insertedCount: createdIds.flat().length, affectedRows: writePlan.length, skippedRows: prepared.invalidParentRows.size, rejectLimit: stmt.rejectLimit, ...stmt.errorTable ? { errTable: stmt.errorTable } : {}, importDetail };
12527
+ }
12528
+ if (stmt.writeMode === "UPDATE_RECORD_NUMBER") {
12529
+ return executeImportRecordNumberUpdate(
12530
+ stmt,
12531
+ handle,
12532
+ client,
12533
+ options,
12534
+ cacheContext,
12535
+ tempTables
12536
+ );
12537
+ }
12538
+ const common = {
12539
+ appId: stmt.appId,
12540
+ fields: stmt.fields,
12541
+ select: importPlaceholderSelect(),
12542
+ validateOnly: stmt.validateOnly,
12543
+ validationErrorTable: stmt.validationErrorTable,
12544
+ onErrorSkip: stmt.onErrorSkip,
12545
+ errorTable: stmt.errorTable,
12546
+ rejectLimit: stmt.rejectLimit,
12547
+ checkGroups: stmt.checkGroups
12548
+ };
12549
+ const generated = stmt.keyFields ? { type: "UPSERT_SELECT", ...common, keyFields: stmt.keyFields } : { type: "INSERT_SELECT", ...common };
12550
+ const executionSource = { source: stmt.source, handle, cache: /* @__PURE__ */ new Map() };
12551
+ importSourceByDmlStatement.set(generated, executionSource);
12552
+ const withAudit = (result2) => {
12553
+ if (executionSource.audit) Object.assign(result2, { importAudit: executionSource.audit });
12554
+ return result2;
12555
+ };
12556
+ if (generated.validateOnly) {
12557
+ if (generated.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12558
+ const result2 = await executeDmlValidation(generated, client, { ...options, onLimitReached: "error" }, cacheContext, tempTables, 1);
12559
+ if (generated.validationErrorTable && tempTables) {
12560
+ appendValidationErrors(
12561
+ tempTables,
12562
+ generated.validationErrorTable,
12563
+ result2.columns,
12564
+ result2.errors,
12565
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12566
+ materializedMetaByValidationResult.get(result2) ?? /* @__PURE__ */ new Map()
12567
+ );
12568
+ }
12569
+ return withAudit(result2);
12570
+ }
12571
+ if (generated.onErrorSkip) {
12572
+ if (!tempTables) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
12573
+ const result2 = await (generated.type === "UPSERT_SELECT" ? executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1) : executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1));
12574
+ return withAudit(result2);
12575
+ }
12576
+ const result = await (generated.type === "UPSERT_SELECT" ? executeUpsertSelect(generated, client, options, cacheContext, tempTables) : executeInsertSelect(generated, client, options, cacheContext, tempTables));
12577
+ return withAudit(result);
12578
+ }
12579
+ async function executeCsvSubtableReplacement(stmt, materialized, preparedBase, fieldInfos, client, options, tempTables) {
12580
+ if (!stmt.recordNumberSourceHeader || !stmt.replaceSubtables?.length) throw new Error("InternalError: incomplete CSV subtable replacement AST.");
12581
+ assertNoDuplicateCsvSubtableRowIds(materialized.records);
12582
+ const rawKeys = materialized.records.map((record) => record.recordNumberSourceValue ?? "");
12583
+ const keyPlan = preflightImportRecordNumbers(rawKeys, stmt.recordNumberSourceHeader);
12584
+ const tableCodes = [...stmt.replaceSubtables];
12585
+ const ownershipTableCodes = [...new Set(fieldInfos.filter((info) => !info.inSubtable && info.fieldType === "SUBTABLE").map((info) => info.code))];
12586
+ const allRecords = await fetchAll(client.getRecords, stmt.appId, "", ["$id", "$revision", ...ownershipTableCodes], {
12587
+ maxRecords: options.maxRecords ?? 1e4,
12588
+ parallel: options.fetchParallel ?? 1,
12589
+ onLimit: "error"
12590
+ });
12591
+ const existingById = /* @__PURE__ */ new Map();
12592
+ const ownership = /* @__PURE__ */ new Map();
12593
+ for (const record of allRecords) {
12594
+ const id = Number(record["$id"]?.value);
12595
+ const revision = Number(record["$revision"]?.value);
12596
+ if (!Number.isFinite(id)) continue;
12597
+ existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record });
12598
+ for (const table of ownershipTableCodes) {
12599
+ const rows = record[table]?.value;
12600
+ if (!Array.isArray(rows)) continue;
12601
+ for (const row of rows) if (row.id) {
12602
+ const owners = ownership.get(row.id) ?? [];
12603
+ owners.push({ parentId: id, table });
12604
+ ownership.set(row.id, owners);
12605
+ }
12606
+ }
12607
+ }
12608
+ const targetIds = keyPlan.normalized.map((key) => key === null ? void 0 : Number(key));
12609
+ const parents = preparedBase.parents.map((parent, index) => {
12610
+ const errors2 = [...parent.errors];
12611
+ for (const error of keyPlan.errors[index]) errors2.push({
12612
+ operation: "UPDATE",
12613
+ parentRow: parent.parentRow,
12614
+ field: error.field,
12615
+ code: error.code,
12616
+ message: error.message,
12617
+ sourceValues: materialized.records[index].top
12618
+ });
12619
+ const targetId = targetIds[index];
12620
+ if (targetId !== void 0 && !existingById.has(targetId)) errors2.push({
12621
+ operation: "UPDATE",
12622
+ parentRow: parent.parentRow,
12623
+ field: stmt.recordNumberSourceHeader,
12624
+ code: "ERR_RECORD_NUMBER_NOT_FOUND",
12625
+ message: `record number ${targetId} does not exist in APP${stmt.appId}`,
12626
+ sourceValues: materialized.records[index].top
12627
+ });
12628
+ return { ...parent, valid: errors2.length === 0, errors: errors2 };
12629
+ });
12630
+ const initialPlan = buildCsvSubtableReplacementPlan(materialized.records, parents, targetIds, existingById, ownership);
12631
+ const planErrors = initialPlan.flatMap((parent) => [...parent.errors]);
12632
+ const invalidParentRows = new Set(initialPlan.filter((parent) => !parent.valid).map((parent) => parent.parentRow));
12633
+ const prepared = { ...preparedBase, parents, errors: planErrors, invalidParentRows };
12634
+ assertImportRejectLimit(prepared, stmt.rejectLimit);
12635
+ const validPlan = initialPlan.filter((parent) => parent.valid);
12636
+ const allTables = initialPlan.flatMap((parent) => parent.tables);
12637
+ const sum = (table, key) => allTables.filter((item) => item.table === table).reduce((n, item) => n + Number(item[key]), 0);
12638
+ const tableDetail = Object.fromEntries(tableCodes.map((table) => [table, {
12639
+ existingRows: sum(table, "existingRows"),
12640
+ inputRows: sum(table, "inputRows"),
12641
+ updateRows: sum(table, "updateRows"),
12642
+ addRows: sum(table, "addRows"),
12643
+ deleteRows: sum(table, "deleteRows"),
12644
+ rowIdNotFound: sum(table, "rowIdNotFound")
12645
+ }]));
12646
+ const importDetail = {
12647
+ kind: "IMPORT_CSV_SUBTABLE_REPLACE",
12648
+ rowIdPolicy: "PRESERVE_EXISTING",
12649
+ parentsToWrite: validPlan.length,
12650
+ insertedParents: 0,
12651
+ updatedParents: validPlan.length,
12652
+ hasDeletes: validPlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
12653
+ totalDeleteRows: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.deleteRows, 0),
12654
+ rowIdNotFound: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.rowIdNotFound, 0),
12655
+ invalidParents: invalidParentRows.size,
12656
+ parents: validPlan.map((parent) => ({ parentRow: parent.parentRow, mode: "UPDATE", targetId: parent.targetId, tables: parent.tables }))
12657
+ };
12658
+ const payloadFields = [...new Set(stmt.targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
12659
+ const errors = materializeImportValidationErrors(planErrors, payloadFields);
12660
+ const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
12661
+ if (stmt.validateOnly) {
12662
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12663
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(tempTables, stmt.validationErrorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
12664
+ return {
12665
+ type: "VALIDATION",
12666
+ operation: "UPDATE",
12667
+ validatedRows: parents.length,
12668
+ validRows: parents.length - invalidParentRows.size,
12669
+ invalidRows: invalidParentRows.size,
12670
+ errorCount: errors.length,
12671
+ columns,
12672
+ errors,
12673
+ ...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {},
12674
+ importDetail: { preflight: "ACTUAL_DATA", parents: { total: parents.length, valid: validPlan.length, invalid: invalidParentRows.size, mutationCandidates: validPlan.length }, tables: tableDetail, rowIdPolicy: "PRESERVE_EXISTING", rowIdNotFound: importDetail.rowIdNotFound, writesKintone: false }
12675
+ };
12676
+ }
12677
+ if (planErrors.length && !stmt.onErrorSkip) {
12678
+ const first = planErrors[0];
12679
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
12680
+ }
12681
+ if (stmt.onErrorSkip) {
12682
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
12683
+ appendValidationErrors(tempTables, stmt.errorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
12684
+ }
12685
+ if (validPlan.length) {
12686
+ if (!options.supportsImportConfirmDetail || !options.confirm) throw new Error("UnsupportedError: CSV subtable replacement requires explicit rendered detail approval.");
12687
+ const ok = await options.confirm(validPlan.length, "UPDATE", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
12688
+ if (!ok) throw new OperationCancelledError("UPDATE", validPlan.length);
12689
+ }
12690
+ const scalarMap = (record) => new Map(Object.entries(record).map(([code, field]) => [code, field.value]));
12691
+ for (const parent of validPlan) {
12692
+ const record = buildImportRecordPayload(scalarMap(parent.top), new Map([...parent.subtables].map(([table, rows]) => [table, rows.map((row) => ({ ...row.rowId ? { rowId: row.rowId } : {}, values: scalarMap(row.record) }))])), "PRESERVE");
12693
+ await client.putRecords({ app: stmt.appId, records: [{ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record }] });
12694
+ }
12695
+ return { type: "UPDATE", updatedCount: validPlan.length, affectedRows: validPlan.length, skippedRows: invalidParentRows.size, rejectLimit: stmt.rejectLimit, ...stmt.errorTable ? { errTable: stmt.errorTable } : {}, importDetail };
12696
+ }
12697
+ async function executeImportRecordNumberUpdate(stmt, handle, client, options, cacheContext, tempTables) {
12698
+ if (stmt.source.kind !== "CSV" || stmt.source.mappingMode !== "BY_NAME" || !stmt.recordNumberSourceHeader) {
12699
+ throw new Error("InternalError: invalid IMPORT UPDATE AST.");
12700
+ }
12701
+ if (new Set(stmt.fields).size !== stmt.fields.length) throw new Error("ArgumentError: DML target fields contain duplicates.");
12702
+ const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
12703
+ const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
12704
+ const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
12705
+ const sourceTable = materializeCsvDmlSource(
12706
+ stmt.source,
12707
+ payload,
12708
+ options.maxRecords ?? 1e4,
12709
+ stmt.fields,
12710
+ fieldInfos,
12711
+ stmt.recordNumberSourceHeader
12712
+ );
12713
+ const keyValues = sourceTable.recordNumberSourceValues;
12714
+ if (!keyValues) throw new Error("InternalError: record-number source values were not materialized.");
12715
+ const keyPlan = preflightImportRecordNumbers(keyValues, stmt.recordNumberSourceHeader);
12716
+ const matchedIds = /* @__PURE__ */ new Set();
12717
+ const lookupKeys = [...new Set(keyPlan.normalized.filter((key) => key !== null))];
12718
+ for (let i = 0; i < lookupKeys.length; i += 100) {
12719
+ const chunk2 = lookupKeys.slice(i, i + 100);
12720
+ const response = await client.getRecords({
12721
+ app: stmt.appId,
12722
+ query: `$id in (${chunk2.join(",")}) limit 500`,
12723
+ fields: ["$id"]
12724
+ });
12725
+ for (const record of response.records) {
12726
+ const id = record["$id"]?.value;
12727
+ if (typeof id === "string" && id !== "") matchedIds.add(id.replace(/^0+(?=\d)/, ""));
12728
+ }
12729
+ }
12730
+ const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
12731
+ const evaluationTypes = new Map(stmt.fields.map((field) => [field, infoByCode.get(field)?.fieldType ?? "SINGLE_LINE_TEXT"]));
12732
+ const candidates = sourceTable.rows.map((row, index) => {
12733
+ const key = keyPlan.normalized[index];
12734
+ const preErrors = [
12735
+ ...sourceTable.importRowErrors?.[index] ?? [],
12736
+ ...keyPlan.errors[index]
12737
+ ];
12738
+ if (key !== null && !matchedIds.has(key)) preErrors.push({
12739
+ field: stmt.recordNumberSourceHeader,
12740
+ code: "ERR_RECORD_NUMBER_NOT_FOUND",
12741
+ message: `record number ${key} does not exist in APP${stmt.appId}`
12742
+ });
12743
+ return {
12744
+ rowNumber: index + 1,
12745
+ operation: "UPDATE",
12746
+ mode: "update",
12747
+ ...key !== null && matchedIds.has(key) ? { targetId: Number(key) } : {},
12748
+ payload: new Map([
12749
+ [stmt.recordNumberSourceHeader, keyValues[index]],
12750
+ ...stmt.fields.map((field) => [field, row[field] ?? ""])
12751
+ ]),
12752
+ preErrors,
12753
+ record: {},
12754
+ evaluationRow: row,
12755
+ evaluationFieldTypes: evaluationTypes
12756
+ };
12757
+ });
12758
+ const diagnosticFields = [stmt.recordNumberSourceHeader, ...stmt.fields];
12759
+ const validation = validateDmlCandidates(
12760
+ candidates,
12761
+ "UPDATE",
12762
+ diagnosticFields,
12763
+ stmt.fields,
12764
+ fieldInfos,
12765
+ 1,
12766
+ numberPrecision,
12767
+ stmt.checkGroups ?? [],
12768
+ false
12769
+ );
12770
+ const columns = [...diagnosticFields, ...VALIDATION_META_COLUMNS];
12771
+ const validationResult = {
12772
+ type: "VALIDATION",
12773
+ operation: "UPDATE",
12774
+ validatedRows: candidates.length,
12775
+ validRows: candidates.length - validation.invalidRows,
12776
+ invalidRows: validation.invalidRows,
12777
+ errorCount: validation.errors.length,
12778
+ columns,
12779
+ errors: validation.errors,
12780
+ ...stmt.validationErrorTable ?? stmt.errorTable ? { errTable: stmt.validationErrorTable ?? stmt.errorTable } : {}
12781
+ };
12782
+ Object.assign(validationResult, { importAudit: sourceTable.importAudit });
12783
+ if (stmt.validateOnly) {
12784
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12785
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(
12786
+ tempTables,
12787
+ stmt.validationErrorTable,
12788
+ columns,
12789
+ validation.errors,
12790
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12791
+ /* @__PURE__ */ new Map()
12792
+ );
12793
+ return validationResult;
12794
+ }
12795
+ if (!stmt.onErrorSkip && validation.invalidRows > 0) {
12796
+ const first = validation.errors[0];
12797
+ throw new Error(`DmlValidationError: ${first.$err_code} ${first.$err_message} (row=${first.$err_row}, field=${first.$err_field})`);
12798
+ }
12799
+ if (stmt.onErrorSkip) {
12800
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
12801
+ appendValidationErrors(
12802
+ tempTables,
12803
+ stmt.errorTable,
12804
+ columns,
12805
+ validation.errors,
12806
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12807
+ /* @__PURE__ */ new Map()
12808
+ );
12809
+ if (stmt.rejectLimit != null && validation.invalidRows > stmt.rejectLimit) {
12810
+ throw new RejectLimitExceededError(
12811
+ `rejected rows (${validation.invalidRows}) exceed REJECT LIMIT (${stmt.rejectLimit}).`,
12812
+ validationResult
12813
+ );
12814
+ }
12815
+ }
12816
+ const valid = candidates.filter((candidate) => !validation.invalidRowNumbers.has(candidate.rowNumber));
12817
+ if (options.confirm) {
12818
+ const ok = await options.confirm(valid.length, "UPDATE");
12819
+ if (!ok) throw new OperationCancelledError("UPDATE", valid.length);
12820
+ }
12821
+ const updates = valid.map((candidate) => ({ id: candidate.targetId, record: candidate.record }));
12822
+ for (let i = 0; i < updates.length; i += 100) {
12823
+ await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
12824
+ }
12825
+ const result = {
12826
+ type: "UPDATE",
12827
+ updatedCount: updates.length,
12828
+ ...stmt.onErrorSkip ? {
12829
+ affectedRows: updates.length,
12830
+ skippedRows: validation.invalidRows,
12831
+ rejectLimit: stmt.rejectLimit ?? null,
12832
+ errTable: stmt.errorTable
12833
+ } : {}
12834
+ };
12835
+ Object.assign(result, { insertedCount: 0, importAudit: sourceTable.importAudit });
12836
+ return result;
12837
+ }
12838
+ async function materializeDmlSource(stmt, client, options, cacheContext, tempTables, targetFields) {
12839
+ const imported = importSourceByDmlStatement.get(stmt);
12840
+ if (!imported) {
12841
+ const selected2 = tempTables && tempTables.size > 0 ? await executeQueryWithCte(stmt.select, client, options, tempTables, cacheContext, true) : await executeSelect(stmt.select, client, options, cacheContext, void 0, true);
12842
+ return { rows: selected2.rows, columns: selected2.columns, columnMeta: materializedMetaBySelectResult.get(selected2) };
12843
+ }
12844
+ const payload = await loadImportSource(imported.handle, imported.cache);
12845
+ const rowLimit = options.maxRecords ?? 1e4;
12846
+ const targetByCode = new Map((targetFields ?? []).map((info) => [info.code, info]));
12847
+ const jsonTargets = stmt.fields.map((code) => ({ code, fieldType: targetByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }));
12848
+ const raw = imported.source.kind === "JSON" ? materializeJsonDmlSource(imported.source, payload, jsonTargets, rowLimit) : materializeCsvDmlSource(imported.source, payload, rowLimit, stmt.fields, targetFields);
12849
+ imported.audit = raw.importAudit;
12850
+ if (imported.source.kind === "JSON") return raw;
12851
+ if (!imported.source.projection) return raw;
12852
+ const projection = bindImportProjection(imported.source.projection);
12853
+ const tables = new Map(tempTables ?? []);
12854
+ tables.set(IMPORT_PROJECTION_SOURCE, raw);
12855
+ const selected = await executeQueryWithCte(projection, client, { ...options, onLimitReached: "error" }, tables, cacheContext, true);
12856
+ return { rows: selected.rows, columns: selected.columns, columnMeta: materializedMetaBySelectResult.get(selected) };
12857
+ }
12858
+ var dmlSourceMaterializer = { materialize: materializeDmlSource };
12859
+ function assertNoImportRowErrors(table) {
12860
+ for (let rowIndex = 0; rowIndex < (table.importRowErrors?.length ?? 0); rowIndex++) {
12861
+ const first = table.importRowErrors?.[rowIndex]?.[0];
12862
+ if (first) {
12863
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${rowIndex + 1}, field=${first.field})`);
12864
+ }
12865
+ }
12866
+ }
11116
12867
  async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
11117
12868
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
11118
12869
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
11119
12870
  const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
11120
- const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
11121
- const { rows, columns } = selectResult;
12871
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
12872
+ const { rows, columns } = sourceTable;
12873
+ assertNoImportRowErrors(sourceTable);
11122
12874
  if (columns.length !== stmt.fields.length) {
11123
12875
  const emptySourceHint = columns.length === 0 && rows.length === 0 ? "\u3002\u7D50\u679C\u304C 0 \u884C\u306E\u305F\u3081\u5217\u3092\u7279\u5B9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08SELECT * \u3092\u7A7A\u30BD\u30FC\u30B9\u306B\u4F7F\u3046\u3068\u5217\u3092\u6C7A\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002\u660E\u793A\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
11124
12876
  throw new Error(
@@ -11130,15 +12882,16 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
11130
12882
  if (!ok) throw new OperationCancelledError("INSERT", rows.length);
11131
12883
  }
11132
12884
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
11133
- const allRecords = rows.map((row) => {
12885
+ const allRecords = rows.map((row, rowIndex) => {
11134
12886
  const record = {};
11135
12887
  stmt.fields.forEach((field, i) => {
12888
+ if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
11136
12889
  const raw = row[columns[i]] ?? "";
11137
12890
  record[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
11138
12891
  });
11139
12892
  return record;
11140
12893
  });
11141
- assertValidDmlRecords(allRecords, stmt.fields, fieldInfos, numberPrecision);
12894
+ allRecords.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
11142
12895
  const createdIds = [];
11143
12896
  for (let i = 0; i < allRecords.length; i += 100) {
11144
12897
  const batch = allRecords.slice(i, i + 100);
@@ -11531,9 +13284,9 @@ function expandRowsForSubtableDml(parents, subtableCode) {
11531
13284
  for (const parent of parents) {
11532
13285
  const parentId = String(parent["$id"]?.value ?? "");
11533
13286
  const parentRevision = getRevision(parent);
11534
- const tableRows = getMutableTableRows(parent, subtableCode);
11535
- for (let i = 0; i < tableRows.length; i++) {
11536
- const row = tableRows[i];
13287
+ const tableRows2 = getMutableTableRows(parent, subtableCode);
13288
+ for (let i = 0; i < tableRows2.length; i++) {
13289
+ const row = tableRows2[i];
11537
13290
  const flat = {
11538
13291
  _pid: parentId,
11539
13292
  _rid: row.id ?? "",
@@ -11739,8 +13492,9 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11739
13492
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
11740
13493
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
11741
13494
  const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
11742
- const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
11743
- const { rows, columns } = selectResult;
13495
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
13496
+ const { rows, columns } = sourceTable;
13497
+ assertNoImportRowErrors(sourceTable);
11744
13498
  if (columns.length !== stmt.fields.length) {
11745
13499
  const emptySourceHint = columns.length === 0 && rows.length === 0 ? "\u3002\u7D50\u679C\u304C 0 \u884C\u306E\u305F\u3081\u5217\u3092\u7279\u5B9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08SELECT * \u3092\u7A7A\u30BD\u30FC\u30B9\u306B\u4F7F\u3046\u3068\u5217\u3092\u6C7A\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002\u660E\u793A\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
11746
13500
  throw new Error(
@@ -11754,18 +13508,29 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11754
13508
  }
11755
13509
  const toInsert = [];
11756
13510
  const toUpdate = [];
11757
- const records = rows.map((row) => {
13511
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13512
+ const records = rows.map((row, rowIndex) => {
11758
13513
  const record = {};
11759
13514
  stmt.fields.forEach((field, i) => {
11760
- record[field] = { value: row[columns[i]] ?? "" };
13515
+ if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
13516
+ const raw = row[columns[i]] ?? "";
13517
+ record[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
11761
13518
  });
11762
13519
  return record;
11763
13520
  });
11764
- assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
11765
- const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13521
+ records.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
11766
13522
  const rowKeyValues = records.map(
11767
13523
  (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
11768
13524
  );
13525
+ if (importSourceByDmlStatement.has(stmt)) {
13526
+ const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
13527
+ const sourceKeys = /* @__PURE__ */ new Set();
13528
+ for (const parts of rowKeyValues) {
13529
+ const normalized = upsertNormalizedKey(parts, numericKey);
13530
+ if (sourceKeys.has(normalized)) throw new Error("ERR_KEY_DUP_SOURCE: UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059");
13531
+ sourceKeys.add(normalized);
13532
+ }
13533
+ }
11769
13534
  const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
11770
13535
  records.forEach((record, rowIdx) => {
11771
13536
  const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
@@ -11814,10 +13579,10 @@ async function executeDescribe(stmt, client, cacheContext) {
11814
13579
  }));
11815
13580
  return { type: "SELECT", rows, columns, rowCount: rows.length };
11816
13581
  }
11817
- function parseSql(sql) {
13582
+ function parseSql(sql, enableImport = false) {
11818
13583
  try {
11819
13584
  const tokens = new Lexer(sql).tokenize();
11820
- const stmt = new Parser(tokens).parse();
13585
+ const stmt = new Parser(tokens, { import: enableImport }).parse();
11821
13586
  validateKlikeStatement(stmt);
11822
13587
  return stmt;
11823
13588
  } catch (e) {
@@ -11993,43 +13758,51 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
11993
13758
  const validate = node;
11994
13759
  fieldApps.add(validate.appId);
11995
13760
  const fields = await getFieldsCached(validate.appId, tracedClient, cacheContext);
11996
- const infoByCode = new Map(fields.map((field) => [field.code, field]));
13761
+ const infoByCode = new Map(fields.filter((field) => !field.inSubtable).map((field) => [field.code, field]));
13762
+ const childCodes = new Set(fields.filter((field) => field.inSubtable).map((field) => field.code));
11997
13763
  const targets = resolveExistingValidationTargets(validate, fields);
11998
13764
  const checks = collectCheckFieldRefs(validate.checkGroups ?? []);
11999
13765
  const whereFields = collectValidateWhereFields(validate.where);
12000
13766
  for (const ref of checks) {
12001
- if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
13767
+ if (ref.field !== "$id" && !infoByCode.has(ref.field) && !childCodes.has(ref.field)) {
12002
13768
  throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${validate.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
12003
13769
  }
13770
+ if (ref.field !== "$id" && !infoByCode.has(ref.field) && childCodes.has(ref.field)) {
13771
+ throw new Error(`ArgumentError: VALIDATE \u306E CHECK \u3067\u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\u3002`);
13772
+ }
12004
13773
  }
12005
13774
  for (const field of whereFields) {
12006
- if (field !== "$id" && !infoByCode.has(field)) {
13775
+ if (field !== "$id" && !infoByCode.has(field) && !childCodes.has(field)) {
12007
13776
  throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${validate.appId}.`);
12008
13777
  }
13778
+ if (field !== "$id" && !infoByCode.has(field) && childCodes.has(field)) {
13779
+ throw new Error(`ArgumentError: VALIDATE \u306E WHERE \u3067\u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5B50\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u3092\u53C2\u7167\u3067\u304D\u307E\u305B\u3093\u3002`);
13780
+ }
12009
13781
  }
12010
- const types = new Map(fields.map((field) => [field.code, field.fieldType]));
13782
+ const types = new Map([...infoByCode].map(([code, field]) => [code, field.fieldType]));
12011
13783
  types.set("$id", "RECORD_NUMBER");
12012
13784
  assertCheckComparisonTypes(validate, types);
12013
13785
  const capability = classifyWhereCapability(validate.where, (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0));
12014
13786
  if (capability.capability === "UNSUPPORTED") {
12015
13787
  throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
12016
13788
  }
12017
- const fieldTypes = new Map(fields.map((field) => [field.code, field.fieldType]));
12018
- const fieldOptions = new Map(fields.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
13789
+ const fieldTypes = new Map([...infoByCode].map(([code, field]) => [code, field.fieldType]));
13790
+ const fieldOptions = new Map([...infoByCode.values()].flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
12019
13791
  const prefilter = validate.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? validate.where : extractSafePushdownLeaves(validate.where, {
12020
13792
  allowUnqualifiedFields: true,
12021
13793
  fieldTypes,
12022
13794
  fieldOptions,
12023
13795
  allowKlike: false
12024
13796
  });
12025
- const needsPrecision = targets.some((field) => field.fieldType === "NUMBER");
13797
+ const needsPrecision = targets.some((target) => target.field.fieldType === "NUMBER");
12026
13798
  if (needsPrecision) {
12027
13799
  numberPrecisionApps.add(validate.appId);
12028
13800
  await getNumberPrecisionCached(validate.appId, tracedClient, cacheContext);
12029
13801
  }
12030
13802
  validateExplainInfo.set(validate, {
12031
- targetFields: targets.map((field) => field.code),
12032
- fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((field) => field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
13803
+ targetFields: targets.map((target) => target.subtableCode ? `${target.subtableCode}(${target.field.code})` : target.field.code),
13804
+ fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((target) => target.subtableCode ?? target.field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
13805
+ subtables: new Map([...new Set(targets.flatMap((target) => target.subtableCode ? [target.subtableCode] : []))].map((table) => [table, targets.filter((target) => target.subtableCode === table).length])),
12033
13806
  capability,
12034
13807
  prefilter,
12035
13808
  numberPrecision: needsPrecision
@@ -12073,8 +13846,8 @@ function explainMetadataLines(analysis) {
12073
13846
  ...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
12074
13847
  ];
12075
13848
  }
12076
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2) {
12077
- const statements = parseSqlBatch(sql);
13849
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false) {
13850
+ const statements = parseSqlBatch(sql, enableImport);
12078
13851
  const analysis = analyzeBatch(statements);
12079
13852
  validateDeclaredBatchVariables(statements, injectedVariables);
12080
13853
  const variables = /* @__PURE__ */ new Map();
@@ -12233,6 +14006,79 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
12233
14006
  if (query.type === "DELETE") return buildDeletePlan(query, label);
12234
14007
  if (query.type === "REORDER") return buildReorderPlan(query, label);
12235
14008
  if (query.type === "VALIDATE") return buildValidatePlan(query, label);
14009
+ if (query.type === "IMPORT") {
14010
+ if (query.writeMode === "UPDATE_RECORD_NUMBER") {
14011
+ const csvTables = query.targets?.filter((target) => target.kind === "SUBTABLE") ?? [];
14012
+ return [
14013
+ ...label ? [label] : [],
14014
+ `IMPORT UPDATE INTO APP${query.appId}`,
14015
+ ` writeMode: UPDATE_RECORD_NUMBER`,
14016
+ ` source: CSV ${query.source.sourceName}`,
14017
+ ` keyHeader: ${query.recordNumberSourceHeader}`,
14018
+ ` mapping: BY_NAME`,
14019
+ ` parentRows: requires source load`,
14020
+ ` duplicate: preflight before lookup/write`,
14021
+ ` matched: requires lookup`,
14022
+ ` unmatched: requires lookup`,
14023
+ ` invalid: requires source load`,
14024
+ ` requiresLookup:true`,
14025
+ ` inserted: 0`,
14026
+ ` keyInPayload: false`,
14027
+ ...csvTables.length ? [
14028
+ ` replaceSubtables: ${query.replaceSubtables?.join(", ") ?? "ERROR: required"}`,
14029
+ ` subtableRowIdPolicy: PRESERVE existing; empty/unknown add without id`,
14030
+ ` rowIdOwnership: owned elsewhere invalidates the parent`,
14031
+ ` replacementDiff: existing/input/update/add/delete/rowIdNotFound requires actual-data preflight`,
14032
+ ` confirmPolicy: highest warning "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FBN\u884C\u524A\u9664" plus per-table detail (including delete=0)`
14033
+ ] : [],
14034
+ ` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
14035
+ ` gate: enabled for this parse`,
14036
+ ` writesKintone: ${query.validateOnly ? "false" : "true"}`
14037
+ ];
14038
+ }
14039
+ const mode = query.keyFields ? "UPSERT" : "INSERT";
14040
+ const hasSubtables = query.targets?.some((target) => target.kind === "SUBTABLE") === true;
14041
+ return [
14042
+ ...label ? [label] : [],
14043
+ `IMPORT ${mode} INTO APP${query.appId}`,
14044
+ ` source: ${query.source.kind} ${query.source.sourceName}`,
14045
+ ` sourceFormat: ${query.source.kind}`,
14046
+ ` encoding: ${query.source.kind === "JSON" ? "UTF8 only" : query.source.encoding ?? "UTF8 (or loader metadata)"}`,
14047
+ ` mapping: ${query.source.kind === "JSON" ? "BY NAME (INTO order)" : query.source.projection ? "SELECT expressions" : query.source.mappingMode}`,
14048
+ ...query.source.kind === "JSON" ? [
14049
+ ` duplicateKeyPolicy: reject`,
14050
+ ` numberLexemePolicy: preserve; JSON number accepts safe integer only`,
14051
+ ` precisionTargetsRequireString: true`,
14052
+ ` unknownKeyPolicy: reject`,
14053
+ ` presenceAware: true`,
14054
+ ...hasSubtables ? [
14055
+ ` subtableRowIdPolicy: reject _rid/id; DROP IDs and renumber every input row`,
14056
+ ` subtableUpdatePolicy: present table replaces all rows; missing table is preserved; [] deletes all rows`,
14057
+ ` confirmPolicy: parent/table existing/input/add/delete detail required; delete is highest warning`
14058
+ ] : []
14059
+ ] : [
14060
+ ` header: ${query.source.hasHeader ? "HEADER" : "NO HEADER"}`,
14061
+ ...query.source.mappingMode === "BY_NAME" ? [
14062
+ ` writtenColumns: ${query.fields.join(", ")}`,
14063
+ ` knownExportColumns: audit and ignore with reason/non-empty count`,
14064
+ ` unknownColumnPolicy: ${query.source.ignoreUnknownColumns ? "ignore with audit/non-empty count" : "ERR_IMPORT_UNKNOWN_COLUMN"}`,
14065
+ ` multipleValueDelimiter: LF (CRLF or LF)`,
14066
+ ` sourceValueMode: string-preserving`,
14067
+ ` roundTripNumericGuarantee: exact CSV lexeme passes strict decimal validation`,
14068
+ ` FILE: audit-ignore unless named in INTO (analyze error)`
14069
+ ] : []
14070
+ ],
14071
+ ` sourceLimit: 10485760 bytes / ${query.fields.length} target columns`,
14072
+ ` key: ${query.keyFields?.join(", ") ?? "none"}`,
14073
+ ` checks: ${query.checkGroups?.length ?? 0}`,
14074
+ ` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
14075
+ ` gate: enabled for this parse`,
14076
+ ` preflight: ${query.validateOnly && hasSubtables ? "requires actual source load at execution; this EXPLAIN is static" : "requires load"}`,
14077
+ ...hasSubtables ? [query.source.kind === "JSON" ? ` Phase5C: JSON mutation requires detail-capable confirmation surface` : ` Phase5D: CSV mutation requires detail-capable confirmation surface`] : [],
14078
+ ` writesKintone: ${query.validateOnly ? "false" : "true"}`,
14079
+ ` duplicateKey: preflight before lookup/write (requires load)`
14080
+ ];
14081
+ }
12236
14082
  return buildSelectPlan(query, label, capabilities, orderPlans);
12237
14083
  }
12238
14084
  function buildValidatePlan(stmt, label) {
@@ -12251,6 +14097,10 @@ function buildValidatePlan(stmt, label) {
12251
14097
  lines.push(` kintone query: ${info.prefilter === null ? "(\u5168\u4EF6\u53D6\u5F97)" : whereToKintone(info.prefilter)}`);
12252
14098
  lines.push(` audit fields: ${info.targetFields.length === 0 ? "(\u306A\u3057)" : info.targetFields.join(", ")}`);
12253
14099
  lines.push(` fetch fields: ${info.fetchFields.join(", ")}`);
14100
+ lines.push(` mode: ${stmt.summary ? "SUMMARY" : "DETAIL"}`);
14101
+ if (info.subtables.size > 0) lines.push(` subtable audit: ${[...info.subtables].map(([table, count]) => `${table}(${count} fields)`).join(", ")}`);
14102
+ lines.push(` output schema: ${(stmt.summary ? EXISTING_VALIDATION_SUMMARY_COLUMNS : EXISTING_VALIDATION_COLUMNS).join(", ")}`);
14103
+ lines.push(stmt.summary ? " aggregation: record/subtable/field/code; row locator=none" : " row locator: grouped by message; $err_subrow / $err_subrow_id list all matching rows (first-occurrence order)");
12254
14104
  lines.push(` number precision: ${info.numberPrecision ? "required" : "not required"}`);
12255
14105
  lines.push(" local checks: original WHERE re-evaluation + built-in constraints + CHECK groups");
12256
14106
  lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
@@ -12686,15 +14536,15 @@ var OperationCancelledError = class extends Error {
12686
14536
  };
12687
14537
 
12688
14538
  // src/core/sql.ts
12689
- function parseSqlStatement(sql) {
14539
+ function parseSqlStatement(sql, capabilities = {}) {
12690
14540
  const tokens = new Lexer(sql).tokenize();
12691
- const stmt = new Parser(tokens).parse();
14541
+ const stmt = new Parser(tokens, capabilities).parse();
12692
14542
  validateKlikeStatement(stmt);
12693
14543
  return stmt;
12694
14544
  }
12695
- function parseSqlStatements(sql) {
14545
+ function parseSqlStatements(sql, capabilities = {}) {
12696
14546
  const tokens = new Lexer(sql).tokenize();
12697
- const statements = new Parser(tokens).parseStatements();
14547
+ const statements = new Parser(tokens, capabilities).parseStatements();
12698
14548
  statements.forEach(validateKlikeStatement);
12699
14549
  return statements;
12700
14550
  }
@@ -12842,7 +14692,8 @@ function buildBatchEnvelope(batch, options = {}) {
12842
14692
  columns: s.result.columns,
12843
14693
  rows: s.result.rows,
12844
14694
  rowCount: s.result.rowCount,
12845
- warnings: s.result.warnings ?? []
14695
+ warnings: s.result.warnings ?? [],
14696
+ ...s.result.validateStats ? { validateStats: s.result.validateStats } : {}
12846
14697
  });
12847
14698
  } else if (s.result?.type === "VALIDATION") {
12848
14699
  totalRows += s.result.errorCount;
@@ -12861,7 +14712,8 @@ function buildBatchEnvelope(batch, options = {}) {
12861
14712
  validRows: s.result.validRows,
12862
14713
  invalidRows: s.result.invalidRows,
12863
14714
  errorCount: s.result.errorCount,
12864
- ...s.result.errTable ? { errTable: s.result.errTable } : {}
14715
+ ...s.result.errTable ? { errTable: s.result.errTable } : {},
14716
+ ...s.result.importDetail ? { importDetail: s.result.importDetail } : {}
12865
14717
  });
12866
14718
  } else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
12867
14719
  Object.assign(entry, toMutationSummary(s.result));
@@ -13164,7 +15016,7 @@ function clampInt(v, min, max) {
13164
15016
  function flattenFormFieldProperties(properties) {
13165
15017
  return flattenFields(properties, collectLookupCopyFields(properties));
13166
15018
  }
13167
- function flattenFields(properties, lookupCopyFields, inSubtable = false) {
15019
+ function flattenFields(properties, lookupCopyFields, inSubtable = false, subtableCode) {
13168
15020
  const out = [];
13169
15021
  for (const field of Object.values(properties)) {
13170
15022
  const optionOrder = toOptionOrderMap(field.options);
@@ -13182,11 +15034,12 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
13182
15034
  maxLength: normalizeConstraintValue(field.maxLength),
13183
15035
  defaultValue: field.defaultValue,
13184
15036
  inSubtable,
15037
+ ...subtableCode ? { subtableCode } : {},
13185
15038
  writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
13186
15039
  };
13187
15040
  info.semantics = resolveFieldSemantics(info);
13188
15041
  out.push(info);
13189
- if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
15042
+ if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true, field.type === "SUBTABLE" ? field.code : subtableCode));
13190
15043
  }
13191
15044
  return out;
13192
15045
  }
@@ -14170,6 +16023,17 @@ function restoreSqlContextError(err, sourceSql, context) {
14170
16023
  return err;
14171
16024
  }
14172
16025
 
16026
+ // src/import/importGateError.ts
16027
+ var IMPORT_CAPABILITY_GATE_MARKER = "capability is disabled";
16028
+ function errorMessage(error) {
16029
+ if (error instanceof Error) return error.message;
16030
+ if (typeof error === "string") return error;
16031
+ return null;
16032
+ }
16033
+ function isImportCapabilityGateError(error) {
16034
+ return errorMessage(error)?.includes(IMPORT_CAPABILITY_GATE_MARKER) === true;
16035
+ }
16036
+
14173
16037
  // src/cli/index.ts
14174
16038
  var HELP_TEXT = `ksql - Execute SQL against kintone apps
14175
16039
 
@@ -14184,6 +16048,8 @@ Options:
14184
16048
  --console Start interactive console mode
14185
16049
  --dry-run Parse and show execution plan only
14186
16050
  --var <name=value> Override a DECLARE variable (repeatable; not for secrets)
16051
+ --import-csv <name=path> Supply named CSV and enable IMPORT (repeatable)
16052
+ --import-json <name=path> Supply named JSON and enable IMPORT (repeatable)
14187
16053
  --format <type> Output format: table | json | jsonl | csv | markdown | md
14188
16054
  (batch + json: prints one JSON envelope for the whole batch)
14189
16055
  --max-records <n> Max records to fetch (default: 500)
@@ -14231,6 +16097,15 @@ Options:
14231
16097
  -h, --help Show help
14232
16098
  -v, --version Show version
14233
16099
  `;
16100
+ var CLI_IMPORT_SOURCE_REQUIRED_MESSAGE = "IMPORT \u306B\u306F\u30BD\u30FC\u30B9\u304C\u5FC5\u8981\u3067\u3059\u3002--import-csv <name=path> \u307E\u305F\u306F --import-json <name=path> \u3067\u30D5\u30A1\u30A4\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
16101
+ function toCliImportError(error, importEnabled) {
16102
+ if (importEnabled || !isImportCapabilityGateError(error)) return error;
16103
+ if (error instanceof Error) {
16104
+ error.message = CLI_IMPORT_SOURCE_REQUIRED_MESSAGE;
16105
+ return error;
16106
+ }
16107
+ return CLI_IMPORT_SOURCE_REQUIRED_MESSAGE;
16108
+ }
14234
16109
  function parseArgs(argv) {
14235
16110
  const out = {
14236
16111
  help: false,
@@ -14282,7 +16157,9 @@ function parseArgs(argv) {
14282
16157
  arrayFormat: null,
14283
16158
  tableFormat: null,
14284
16159
  dateFormat: null,
14285
- attachmentFormat: null
16160
+ attachmentFormat: null,
16161
+ importCsv: /* @__PURE__ */ Object.create(null),
16162
+ importJson: /* @__PURE__ */ Object.create(null)
14286
16163
  };
14287
16164
  for (let i = 0; i < argv.length; i++) {
14288
16165
  const a = argv[i];
@@ -14364,6 +16241,28 @@ function parseArgs(argv) {
14364
16241
  i++;
14365
16242
  continue;
14366
16243
  }
16244
+ if (a === "--import-csv") {
16245
+ const raw = v ?? "";
16246
+ const eq = raw.indexOf("=");
16247
+ if (eq <= 0 || eq === raw.length - 1) throw new Error("ArgumentError: --import-csv must use name=path.");
16248
+ const name = raw.slice(0, eq);
16249
+ if (Object.prototype.hasOwnProperty.call(out.importCsv, name) || Object.prototype.hasOwnProperty.call(out.importJson, name)) throw new Error(`ArgumentError: import source "${name}" is specified more than once.`);
16250
+ out.importCsv[name] = raw.slice(eq + 1);
16251
+ i++;
16252
+ continue;
16253
+ }
16254
+ if (a === "--import-json") {
16255
+ const raw = v ?? "";
16256
+ const eq = raw.indexOf("=");
16257
+ if (eq <= 0 || eq === raw.length - 1) throw new Error("ArgumentError: --import-json must use name=path.");
16258
+ const name = raw.slice(0, eq);
16259
+ if (Object.prototype.hasOwnProperty.call(out.importJson, name) || Object.prototype.hasOwnProperty.call(out.importCsv, name)) {
16260
+ throw new Error(`ArgumentError: import source "${name}" is specified more than once.`);
16261
+ }
16262
+ out.importJson[name] = raw.slice(eq + 1);
16263
+ i++;
16264
+ continue;
16265
+ }
14367
16266
  if (a === "-e" || a === "--execute") {
14368
16267
  out.executeSql = v ?? "";
14369
16268
  i++;
@@ -14713,7 +16612,8 @@ function buildOutput(result, format, noHeader, pretty, display) {
14713
16612
  columns: result.columns,
14714
16613
  rowCount: result.rowCount,
14715
16614
  warnings: result.warnings ?? [],
14716
- rows: result.rows
16615
+ rows: result.rows,
16616
+ ...result.validateStats ? { validateStats: result.validateStats } : {}
14717
16617
  };
14718
16618
  return JSON.stringify(obj, null, pretty ? 2 : 0);
14719
16619
  }
@@ -14768,7 +16668,12 @@ function buildBatchStatementSummary(s) {
14768
16668
  const parts = [`[${s.index + 1}] ${s.type}`, s.status];
14769
16669
  if (s.tempTable) parts.push(`temp=${s.tempTable}`);
14770
16670
  if (s.rowCount !== void 0) parts.push(`rows=${s.rowCount}`);
14771
- if (s.status === "success" && s.result?.type === "SELECT") parts.push(`rowCount=${s.result.rowCount}`);
16671
+ if (s.status === "success" && s.result?.type === "SELECT") {
16672
+ parts.push(`rowCount=${s.result.rowCount}`);
16673
+ if (s.result.validateStats) {
16674
+ parts.push(`errorRecords=${s.result.validateStats.errorRecords} errorCount=${s.result.validateStats.errorCount}`);
16675
+ }
16676
+ }
14772
16677
  if (s.status === "success" && s.result && s.result.type !== "SELECT") {
14773
16678
  const r = s.result;
14774
16679
  if (r.type === "INSERT") parts.push(`inserted=${r.insertedCount}`);
@@ -14789,6 +16694,10 @@ function buildBatchStatementSummary(s) {
14789
16694
  if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
14790
16695
  return parts.join(" ");
14791
16696
  }
16697
+ function buildSelectSummary(result) {
16698
+ const validateSummary = result.validateStats ? ` errorRecords=${result.validateStats.errorRecords} errorCount=${result.validateStats.errorCount}` : "";
16699
+ return `rowCount=${result.rowCount}${validateSummary}`;
16700
+ }
14792
16701
  function buildBatchDmlConfirmMessage(analysis) {
14793
16702
  const lines = ["[DML Confirm] batch"];
14794
16703
  for (const s of analysis.statements) {
@@ -15511,15 +17420,16 @@ async function run() {
15511
17420
  `);
15512
17421
  return 2;
15513
17422
  }
17423
+ const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
15514
17424
  try {
15515
- const statements = parseSqlStatements(sql);
17425
+ const statements = parseSqlStatements(sql, { import: importEnabled });
15516
17426
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
15517
17427
  if (statements.length > 1) {
15518
17428
  batchAnalysis = analyzeBatch(statements);
15519
17429
  isBatchSql = true;
15520
17430
  batchContainsDml = batchAnalysis.containsDml;
15521
17431
  } else {
15522
- const stmt = parseSqlStatement(sql);
17432
+ const stmt = parseSqlStatement(sql, { import: importEnabled });
15523
17433
  parsedStmt = stmt;
15524
17434
  stmtType = getStatementType(stmt);
15525
17435
  isDmlStatement = writesKintone(stmt);
@@ -15537,7 +17447,8 @@ async function run() {
15537
17447
  bindings: sqlDiagnosticContext.appBindingByMappedApp,
15538
17448
  rewriteSegments: sqlDiagnosticContext.rewriteSegments
15539
17449
  }) : err;
15540
- process.stderr.write(`${restored instanceof Error ? restored.message : String(restored)}
17450
+ const surfaced = toCliImportError(restored, importEnabled);
17451
+ process.stderr.write(`${surfaced instanceof Error ? surfaced.message : String(surfaced)}
15541
17452
  `);
15542
17453
  return 1;
15543
17454
  }
@@ -15894,7 +17805,8 @@ async function run() {
15894
17805
  args.variables,
15895
17806
  cacheContext,
15896
17807
  maxRecords,
15897
- cursorMaxActive
17808
+ cursorMaxActive,
17809
+ Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0
15898
17810
  );
15899
17811
  const out = [];
15900
17812
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -15933,10 +17845,30 @@ async function run() {
15933
17845
  }
15934
17846
  }
15935
17847
  }
15936
- const confirm = async (count, operation) => {
17848
+ const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
17849
+ const importSource = importEnabled ? (name) => {
17850
+ const sourcePath = args.importCsv[name] ?? args.importJson[name];
17851
+ return sourcePath === void 0 ? void 0 : { load: async () => ({ bytes: new Uint8Array((0, import_fs2.readFileSync)(sourcePath)) }) };
17852
+ } : void 0;
17853
+ const confirm = async (count, operation, context) => {
15937
17854
  if (count > dmlMaxRows) {
15938
17855
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15939
17856
  }
17857
+ if (context?.importDetail) {
17858
+ const detail = context.importDetail;
17859
+ const csv = detail.kind === "IMPORT_CSV_SUBTABLE_REPLACE";
17860
+ const lines = [
17861
+ ...csv ? [`\u3010\u6700\u91CD\u8981\u8B66\u544A\u3011\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FB${detail.totalDeleteRows}\u884C\u524A\u9664`] : [],
17862
+ `[IMPORT ${csv ? "CSV" : "JSON"} Confirm] parentsToWrite=${detail.parentsToWrite} insert=${detail.insertedParents} update=${detail.updatedParents}`,
17863
+ csv ? `rowIdPolicy=PRESERVE_EXISTING rowIdNotFound=${detail.rowIdNotFound} invalidParents=${detail.invalidParents}` : `rowIdPolicy=DROP_AND_RENUMBER_ALL (JSON child rows are all newly numbered)`,
17864
+ ...!csv && detail.hasDeletes ? ["WARNING: existing subtable rows will be deleted/replaced."] : [],
17865
+ ...detail.parents.flatMap((parent) => parent.tables.map(
17866
+ (table) => `parentRow=${parent.parentRow} mode=${parent.mode} table=${table.table} existing=${table.existingRows} input=${table.inputRows} update=${"updateRows" in table ? table.updateRows : 0} add=${table.addRows} delete=${table.deleteRows}${"rowIdNotFound" in table ? ` rowIdNotFound=${table.rowIdNotFound}` : ""}`
17867
+ ))
17868
+ ];
17869
+ process.stderr.write(`${lines.join("\n")}
17870
+ `);
17871
+ }
15940
17872
  if (yes) return true;
15941
17873
  if (args.console) return true;
15942
17874
  const label = sql?.replace(/\s+/g, " ").trim() ?? operation;
@@ -15961,10 +17893,20 @@ query=${label}`);
15961
17893
  timeoutMs: timeout,
15962
17894
  cursorMaxActive,
15963
17895
  variables: args.variables,
15964
- confirm: batchContainsDml ? async (count, operation) => {
17896
+ enableImport: importEnabled,
17897
+ importSource,
17898
+ supportsImportConfirmDetail: true,
17899
+ confirm: batchContainsDml ? async (count, operation, context) => {
15965
17900
  if (count > dmlMaxRows) {
15966
17901
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15967
17902
  }
17903
+ if (context?.importDetail) {
17904
+ const importDetail = context.importDetail;
17905
+ if (importDetail.kind === "IMPORT_CSV_SUBTABLE_REPLACE") process.stderr.write(`\u3010\u6700\u91CD\u8981\u8B66\u544A\u3011\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FB${importDetail.totalDeleteRows}\u884C\u524A\u9664
17906
+ `);
17907
+ process.stderr.write(`[IMPORT ${importDetail.kind === "IMPORT_CSV_SUBTABLE_REPLACE" ? "CSV" : "JSON"} Confirm] ${JSON.stringify(importDetail)}
17908
+ `);
17909
+ }
15968
17910
  return true;
15969
17911
  } : void 0
15970
17912
  });
@@ -15974,14 +17916,19 @@ query=${label}`);
15974
17916
  maxRecords,
15975
17917
  onLimitReached: onLimit,
15976
17918
  cacheContext,
15977
- cursorMaxActive
17919
+ cursorMaxActive,
17920
+ enableImport: importEnabled,
17921
+ importSource
15978
17922
  }) : await execute(sql, client, {
15979
17923
  maxRecords,
15980
17924
  fetchParallel,
15981
17925
  onLimitReached: effectiveOnLimit,
15982
17926
  confirm: isDmlStatement ? confirm : void 0,
15983
17927
  cacheContext,
15984
- cursorMaxActive
17928
+ cursorMaxActive,
17929
+ enableImport: importEnabled,
17930
+ importSource,
17931
+ supportsImportConfirmDetail: true
15985
17932
  });
15986
17933
  if (args.dryRun && sqlDiagnosticContext) {
15987
17934
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
@@ -16019,8 +17966,10 @@ query=${label}`);
16019
17966
  `, "utf-8");
16020
17967
  else if (output) process.stdout.write(`${output}
16021
17968
  `);
16022
- if (!quiet) process.stderr.write(`rowCount=${result.rowCount}
17969
+ if (!quiet) {
17970
+ process.stderr.write(`${buildSelectSummary(result)}
16023
17971
  `);
17972
+ }
16024
17973
  if (shouldExitOnEmpty(args.dryRun, exitOnEmpty, result.rowCount)) return 1;
16025
17974
  return 0;
16026
17975
  } catch (err) {
@@ -16049,11 +17998,13 @@ if (isDirectCliRun()) {
16049
17998
  }
16050
17999
  // Annotate the CommonJS export names for ESM import in node:
16051
18000
  0 && (module.exports = {
18001
+ CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
16052
18002
  HELP_TEXT,
16053
18003
  buildBatchDmlConfirmMessage,
16054
18004
  buildBatchStatementSummary,
16055
18005
  buildOutput,
16056
18006
  buildReplExecArgv,
18007
+ buildSelectSummary,
16057
18008
  buildValidationOutput,
16058
18009
  extractAppIds,
16059
18010
  normalizeAppKey,
@@ -16065,5 +18016,6 @@ if (isDirectCliRun()) {
16065
18016
  parseTokenMap,
16066
18017
  runWithArgv,
16067
18018
  shouldExitOnEmpty,
18019
+ toCliImportError,
16068
18020
  writeBatchOutput
16069
18021
  });