@rex0220/kintone-sql-tools 3.5.0 → 3.6.1

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,6 +21,7 @@ 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,
@@ -37,6 +38,7 @@ __export(index_exports, {
37
38
  parseTokenMap: () => parseTokenMap,
38
39
  runWithArgv: () => runWithArgv,
39
40
  shouldExitOnEmpty: () => shouldExitOnEmpty,
41
+ toCliImportError: () => toCliImportError,
40
42
  writeBatchOutput: () => writeBatchOutput
41
43
  });
42
44
  module.exports = __toCommonJS(index_exports);
@@ -651,8 +653,9 @@ var ParseError = class extends Error {
651
653
  }
652
654
  };
653
655
  var Parser = class {
654
- constructor(tokens) {
656
+ constructor(tokens, capabilities = {}) {
655
657
  this.tokens = tokens;
658
+ this.capabilities = capabilities;
656
659
  this.allowUnaryPlusNumber = false;
657
660
  this.scalarAllowsAggregateArgs = true;
658
661
  this.scalarAllowsCase = true;
@@ -745,6 +748,12 @@ var Parser = class {
745
748
  if (upper === "DROP") return this.parseDropTempTable();
746
749
  if (upper === "DECLARE") return this.parseDeclareVariable();
747
750
  if (upper === "VALIDATE") return this.parseValidate();
751
+ if (upper === "IMPORT") {
752
+ if (!this.capabilities.import) {
753
+ throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
754
+ }
755
+ return this.parseImport();
756
+ }
748
757
  break;
749
758
  }
750
759
  default:
@@ -925,11 +934,213 @@ var Parser = class {
925
934
  query = this.parseReorder();
926
935
  } else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
927
936
  query = this.parseValidate();
937
+ } else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "IMPORT") {
938
+ if (!this.capabilities.import) {
939
+ throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
940
+ }
941
+ query = this.parseImport();
928
942
  } else {
929
943
  throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
930
944
  }
931
945
  return { type: "EXPLAIN", query };
932
946
  }
947
+ parseImport() {
948
+ this.advance();
949
+ let writeMode;
950
+ if (this.peek().kind === "UPDATE" /* UPDATE */) {
951
+ this.advance();
952
+ writeMode = "UPDATE_RECORD_NUMBER";
953
+ }
954
+ this.expect("INTO" /* INTO */);
955
+ this.rejectTempTableDml();
956
+ const target = this.parseIdentifier();
957
+ const { appId, subtableCode } = extractTableRef(target, this.prev());
958
+ if (subtableCode) throw new ParseError("IMPORT does not support subtables in Phase 1.", this.prev());
959
+ this.expect("(" /* LPAREN */);
960
+ const targets = [];
961
+ const fields = [];
962
+ const targetNames = /* @__PURE__ */ new Set();
963
+ while (true) {
964
+ const name = this.parseIdentifier();
965
+ if (targetNames.has(name)) throw new ParseError(`IMPORT target ${name} is declared more than once.`, this.prev());
966
+ targetNames.add(name);
967
+ if (this.peek().kind === "(" /* LPAREN */) {
968
+ this.advance();
969
+ const children = this.parseIdentList();
970
+ this.expect(")" /* RPAREN */);
971
+ if (new Set(children).size !== children.length) {
972
+ throw new ParseError(`IMPORT subtable ${name} contains duplicate child declarations.`, this.prev());
973
+ }
974
+ let rowIdSourceHeader;
975
+ if (this.isSoftKeyword("ROW")) {
976
+ this.advance();
977
+ for (const word of ["ID", "SOURCE"]) {
978
+ if (!this.isSoftKeyword(word)) throw new ParseError(`ROW must be followed by ID SOURCE <header>.`, this.peek());
979
+ this.advance();
980
+ }
981
+ rowIdSourceHeader = this.parseIdentifier();
982
+ }
983
+ targets.push({ kind: "SUBTABLE", subtableCode: name, children, ...rowIdSourceHeader ? { rowIdSourceHeader } : {} });
984
+ } else {
985
+ fields.push(name);
986
+ targets.push({ kind: "FIELD", field: name });
987
+ }
988
+ if (this.peek().kind !== "," /* COMMA */) break;
989
+ this.advance();
990
+ }
991
+ this.expect(")" /* RPAREN */);
992
+ this.expect("FROM" /* FROM */);
993
+ if (!this.isSoftKeyword("CSV") && !this.isSoftKeyword("JSON")) throw new ParseError("IMPORT FROM requires CSV or JSON.", this.peek());
994
+ const sourceKind = this.peek().value.toUpperCase();
995
+ this.advance();
996
+ const sourceName = this.parseIdentifier();
997
+ let encoding;
998
+ let hasHeader = true;
999
+ let columns;
1000
+ if (this.isSoftKeyword("ENCODING")) {
1001
+ if (sourceKind === "JSON") throw new ParseError("JSON source is UTF-8 only; ENCODING is not allowed.", this.peek());
1002
+ this.advance();
1003
+ const value = this.parseIdentifier().toUpperCase();
1004
+ if (value !== "UTF8" && value !== "SJIS") throw new ParseError("ENCODING must be UTF8 or SJIS.", this.prev());
1005
+ encoding = value === "UTF8" ? "utf8" : "sjis";
1006
+ }
1007
+ if (this.peek().kind === "NOT" /* NOT */ && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
1008
+ if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
1009
+ this.advance();
1010
+ this.advance();
1011
+ hasHeader = false;
1012
+ } else if (this.isSoftKeyword("NO") && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
1013
+ if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
1014
+ this.advance();
1015
+ this.advance();
1016
+ hasHeader = false;
1017
+ }
1018
+ if (this.isSoftKeyword("COLUMNS")) {
1019
+ if (sourceKind === "JSON") throw new ParseError("COLUMNS is CSV-only.", this.peek());
1020
+ if (hasHeader) throw new ParseError("COLUMNS requires NO HEADER.", this.peek());
1021
+ this.advance();
1022
+ this.expect("(" /* LPAREN */);
1023
+ columns = this.parseIdentList();
1024
+ this.expect(")" /* RPAREN */);
1025
+ }
1026
+ let projection;
1027
+ if (this.peek().kind === "SELECT" /* SELECT */) {
1028
+ if (sourceKind === "JSON") throw new ParseError("SELECT projection is CSV-only.", this.peek());
1029
+ projection = this.parseSelect();
1030
+ if (projection.from.cteName !== NO_FROM_CTE_NAME || projection.joins.length > 0) {
1031
+ throw new ParseError("IMPORT projection cannot use FROM or JOIN.", this.prev());
1032
+ }
1033
+ if (projection.where || projection.groupBy.length || projection.having || projection.orderBy.length || projection.limit !== null || projection.offset !== null) {
1034
+ throw new ParseError("IMPORT projection supports SELECT expressions only.", this.prev());
1035
+ }
1036
+ this.validateImportProjectionScope(projection, this.prev());
1037
+ if (targets.some((item) => item.kind === "SUBTABLE")) {
1038
+ throw new ParseError("IMPORT subtable sources cannot use SELECT projection.", this.prev());
1039
+ }
1040
+ if (projection.columns.length !== fields.length) {
1041
+ throw new ParseError(`IMPORT projection has ${projection.columns.length} columns; target has ${fields.length}.`, this.prev());
1042
+ }
1043
+ }
1044
+ let mappingMode = "POSITION";
1045
+ let ignoreUnknownColumns = false;
1046
+ if (this.peek().kind === "BY" /* BY */ || this.isSoftKeyword("BY")) {
1047
+ if (sourceKind === "JSON") throw new ParseError("BY NAME is CSV-only.", this.peek());
1048
+ this.advance();
1049
+ if (!this.isSoftKeyword("NAME")) throw new ParseError("BY must be followed by NAME in IMPORT.", this.peek());
1050
+ this.advance();
1051
+ if (!hasHeader) throw new ParseError("BY NAME requires HEADER.", this.prev());
1052
+ if (projection) throw new ParseError("BY NAME and SELECT projection are mutually exclusive.", this.prev());
1053
+ mappingMode = "BY_NAME";
1054
+ if (this.isSoftKeyword("IGNORE")) {
1055
+ this.advance();
1056
+ if (!this.isSoftKeyword("UNKNOWN")) throw new ParseError("IGNORE must be followed by UNKNOWN COLUMNS.", this.peek());
1057
+ this.advance();
1058
+ if (!this.isSoftKeyword("COLUMNS")) throw new ParseError("IGNORE UNKNOWN must be followed by COLUMNS.", this.peek());
1059
+ this.advance();
1060
+ ignoreUnknownColumns = true;
1061
+ }
1062
+ }
1063
+ let keyFields;
1064
+ let recordNumberSourceHeader;
1065
+ if (this.isSoftKeyword("MATCH")) {
1066
+ this.advance();
1067
+ for (const word of ["RECORD", "NUMBER", "SOURCE"]) {
1068
+ if (!this.isSoftKeyword(word)) throw new ParseError(`MATCH must be followed by RECORD NUMBER SOURCE <header>.`, this.peek());
1069
+ this.advance();
1070
+ }
1071
+ recordNumberSourceHeader = this.parseIdentifier();
1072
+ }
1073
+ if (this.peek().kind === "ON" /* ON */ && this.peekAt(1).kind === "DUPLICATE" /* DUPLICATE */) keyFields = this.parseOnDuplicate();
1074
+ let replaceSubtables;
1075
+ if (this.peek().kind === "REPLACE" /* REPLACE */ || this.isSoftKeyword("REPLACE")) {
1076
+ this.advance();
1077
+ if (!this.isSoftKeyword("SUBTABLES")) throw new ParseError("REPLACE must be followed by SUBTABLES (...).", this.peek());
1078
+ this.advance();
1079
+ this.expect("(" /* LPAREN */);
1080
+ replaceSubtables = this.parseIdentList();
1081
+ this.expect(")" /* RPAREN */);
1082
+ if (new Set(replaceSubtables).size !== replaceSubtables.length) throw new ParseError("REPLACE SUBTABLES contains duplicates.", this.prev());
1083
+ }
1084
+ const subtableTargets = targets.filter((item) => item.kind === "SUBTABLE");
1085
+ if (subtableTargets.length) {
1086
+ if (projection) throw new ParseError("IMPORT subtables cannot use SELECT projection.", this.prev());
1087
+ if (sourceKind === "JSON") {
1088
+ if (subtableTargets.some((item) => item.rowIdSourceHeader)) throw new ParseError("JSON subtable IMPORT does not accept ROW ID SOURCE.", this.prev());
1089
+ if (replaceSubtables) throw new ParseError("REPLACE SUBTABLES is CSV-only; JSON uses nested-array replacement semantics.", this.prev());
1090
+ } else {
1091
+ if (writeMode !== "UPDATE_RECORD_NUMBER" || !recordNumberSourceHeader) throw new ParseError("CSV subtable IMPORT requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.", this.prev());
1092
+ if (mappingMode !== "BY_NAME") throw new ParseError("CSV subtable IMPORT requires BY NAME.", this.prev());
1093
+ if (!replaceSubtables) throw new ParseError("CSV subtable IMPORT requires REPLACE SUBTABLES (...).", this.prev());
1094
+ const replacement = new Set(replaceSubtables);
1095
+ for (const item of subtableTargets) {
1096
+ if (!item.rowIdSourceHeader) throw new ParseError(`CSV subtable ${item.subtableCode} requires ROW ID SOURCE <header>.`, this.prev());
1097
+ if (!replacement.has(item.subtableCode)) throw new ParseError(`IMPORT declares child columns for non-replaced subtable ${item.subtableCode}.`, this.prev());
1098
+ }
1099
+ for (const code of replacement) {
1100
+ if (!subtableTargets.some((item) => item.subtableCode === code)) throw new ParseError(`REPLACE SUBTABLES target ${code} is not declared in INTO.`, this.prev());
1101
+ }
1102
+ }
1103
+ } else if (replaceSubtables) {
1104
+ throw new ParseError("REPLACE SUBTABLES requires subtable targets in INTO.", this.prev());
1105
+ }
1106
+ if (writeMode) {
1107
+ if (sourceKind !== "CSV") throw new ParseError("IMPORT UPDATE supports CSV only.", this.prev());
1108
+ if (mappingMode !== "BY_NAME") throw new ParseError("IMPORT UPDATE requires BY NAME.", this.prev());
1109
+ if (!recordNumberSourceHeader) throw new ParseError("IMPORT UPDATE requires MATCH RECORD NUMBER SOURCE <header>.", this.peek());
1110
+ if (keyFields) throw new ParseError("IMPORT UPDATE and ON DUPLICATE are mutually exclusive.", this.prev());
1111
+ } else if (recordNumberSourceHeader) {
1112
+ throw new ParseError("MATCH RECORD NUMBER SOURCE requires IMPORT UPDATE.", this.prev());
1113
+ }
1114
+ const checkGroups = this.parseCheckGroups();
1115
+ const control = this.parseDmlControlSuffix();
1116
+ return {
1117
+ type: "IMPORT",
1118
+ appId,
1119
+ fields,
1120
+ targets,
1121
+ source: sourceKind === "JSON" ? { kind: "JSON", sourceName } : { kind: "CSV", sourceName, encoding, hasHeader, mappingMode, ignoreUnknownColumns, ...columns ? { columns } : {}, ...projection ? { projection } : {} },
1122
+ ...writeMode ? { writeMode, recordNumberSourceHeader } : {},
1123
+ ...replaceSubtables ? { replaceSubtables } : {},
1124
+ ...keyFields ? { keyFields } : {},
1125
+ ...checkGroups,
1126
+ ...control
1127
+ };
1128
+ }
1129
+ validateImportProjectionScope(node, token) {
1130
+ if (Array.isArray(node)) {
1131
+ node.forEach((item) => this.validateImportProjectionScope(item, token));
1132
+ return;
1133
+ }
1134
+ if (node === null || typeof node !== "object") return;
1135
+ const value = node;
1136
+ if (value.type === "SCALAR_SUBQUERY" || value.type === "SCALAR_SUBQUERY_COL") {
1137
+ throw new ParseError("IMPORT projection cannot use subqueries.", token);
1138
+ }
1139
+ if (typeof value.tableAlias === "string") {
1140
+ throw new ParseError("IMPORT projection cannot use qualified column references.", token);
1141
+ }
1142
+ Object.values(value).forEach((item) => this.validateImportProjectionScope(item, token));
1143
+ }
933
1144
  /** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
934
1145
  parseValidate() {
935
1146
  const validateTok = this.advance();
@@ -3094,7 +3305,7 @@ function getStatementType(stmt) {
3094
3305
  return typeof obj.type === "string" ? obj.type : "UNKNOWN";
3095
3306
  }
3096
3307
  function isDmlType(type) {
3097
- return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
3308
+ return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER" || type === "IMPORT";
3098
3309
  }
3099
3310
  function isReadOnlyType(type) {
3100
3311
  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 +4802,7 @@ function analyzeBatch(statements) {
4591
4802
  dependsOn.add(at);
4592
4803
  }
4593
4804
  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 : [];
4805
+ const payloadFields = stmt.type === "VALIDATE" ? ["$id", "$err_field", "$err_code", "$err_message", "$err_value"] : 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
4806
  const signature = JSON.stringify(payloadFields);
4596
4807
  const at = defined.get(validationTable);
4597
4808
  if (at === void 0) {
@@ -7553,9 +7764,15 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7553
7764
  candidate.record ??= {};
7554
7765
  const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
7555
7766
  for (const code of targetFields) {
7767
+ if (!candidate.payload.has(code)) continue;
7556
7768
  const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
7557
7769
  if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
7558
- else candidate.record[code] = { value: result.value };
7770
+ else {
7771
+ const original = candidate.payload.get(code);
7772
+ const type = infoByCode.get(code).fieldType;
7773
+ const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
7774
+ candidate.record[code] = { value: preserveCodes ? original : result.value };
7775
+ }
7559
7776
  }
7560
7777
  if (validateMissingCreateFields && candidate.mode === "create") {
7561
7778
  for (const info of fieldInfos) {
@@ -7817,6 +8034,862 @@ function unsupported(code, field, fieldType, operator) {
7817
8034
  return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
7818
8035
  }
7819
8036
 
8037
+ // src/import/sourceLoader.ts
8038
+ var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
8039
+ var ImportSourceError = class extends Error {
8040
+ constructor(message) {
8041
+ super(`ImportSourceError: ${message}`);
8042
+ this.name = "ImportSourceError";
8043
+ }
8044
+ };
8045
+ function resolveImportSource(name, resolver) {
8046
+ if (!resolver) throw new ImportSourceError("IMPORT source capability is not available.");
8047
+ const handle = resolver(name);
8048
+ if (!handle) throw new ImportSourceError(`source "${name}" is not supplied.`);
8049
+ return handle;
8050
+ }
8051
+ async function loadImportSource(handle, cache) {
8052
+ let pending = cache.get(handle);
8053
+ if (!pending) {
8054
+ pending = handle.load().then((payload) => {
8055
+ if (!(payload.bytes instanceof Uint8Array)) throw new ImportSourceError("loader must return Uint8Array bytes.");
8056
+ if (payload.bytes.byteLength > IMPORT_MAX_BYTES) {
8057
+ throw new ImportSourceError(`source exceeds the ${IMPORT_MAX_BYTES} byte limit.`);
8058
+ }
8059
+ return payload;
8060
+ });
8061
+ cache.set(handle, pending);
8062
+ }
8063
+ return pending;
8064
+ }
8065
+
8066
+ // src/import/csvDecoder.ts
8067
+ function decodeImportText(bytes, encoding) {
8068
+ try {
8069
+ return new TextDecoder(encoding === "sjis" ? "shift_jis" : "utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
8070
+ } catch {
8071
+ throw new ImportSourceError(`invalid ${encoding.toUpperCase()} byte sequence.`);
8072
+ }
8073
+ }
8074
+ function parseRfc4180(text) {
8075
+ const records = [];
8076
+ let record = [];
8077
+ let cell = "";
8078
+ let quoted = false;
8079
+ let afterQuote = false;
8080
+ let i = 0;
8081
+ const finishCell = () => {
8082
+ record.push(cell);
8083
+ cell = "";
8084
+ afterQuote = false;
8085
+ };
8086
+ const finishRecord = () => {
8087
+ finishCell();
8088
+ records.push(record);
8089
+ record = [];
8090
+ };
8091
+ while (i < text.length) {
8092
+ const ch = text[i];
8093
+ if (quoted) {
8094
+ if (ch === '"') {
8095
+ if (text[i + 1] === '"') {
8096
+ cell += '"';
8097
+ i += 2;
8098
+ continue;
8099
+ }
8100
+ quoted = false;
8101
+ afterQuote = true;
8102
+ i++;
8103
+ continue;
8104
+ }
8105
+ cell += ch;
8106
+ i++;
8107
+ continue;
8108
+ }
8109
+ if (afterQuote && ch !== "," && ch !== "\r" && ch !== "\n") {
8110
+ throw new ImportSourceError(`unexpected character after closing quote at offset ${i}.`);
8111
+ }
8112
+ if (ch === '"') {
8113
+ if (cell.length !== 0) throw new ImportSourceError(`quote in unquoted cell at offset ${i}.`);
8114
+ quoted = true;
8115
+ i++;
8116
+ continue;
8117
+ }
8118
+ if (ch === ",") {
8119
+ finishCell();
8120
+ i++;
8121
+ continue;
8122
+ }
8123
+ if (ch === "\r" || ch === "\n") {
8124
+ if (ch === "\r" && text[i + 1] === "\n") i++;
8125
+ finishRecord();
8126
+ i++;
8127
+ continue;
8128
+ }
8129
+ cell += ch;
8130
+ i++;
8131
+ }
8132
+ if (quoted) throw new ImportSourceError("unterminated quoted cell.");
8133
+ if (cell.length > 0 || record.length > 0 || afterQuote) finishRecord();
8134
+ return records;
8135
+ }
8136
+ function assertColumns(columns) {
8137
+ const seen = /* @__PURE__ */ new Set();
8138
+ columns.forEach((column, index) => {
8139
+ if (column === "") throw new ImportSourceError(`CSV column ${index + 1} has an empty name.`);
8140
+ if (seen.has(column)) throw new ImportSourceError(`CSV column name "${column}" is duplicated.`);
8141
+ seen.add(column);
8142
+ });
8143
+ }
8144
+ function decodeCsv(bytes, options) {
8145
+ const records = parseRfc4180(decodeImportText(bytes, options.encoding));
8146
+ let columns;
8147
+ let rows;
8148
+ if (options.hasHeader) {
8149
+ columns = records[0] ?? [];
8150
+ rows = records.slice(1);
8151
+ } else {
8152
+ rows = records;
8153
+ columns = options.columns ? [...options.columns] : Array.from({ length: rows[0]?.length ?? 0 }, (_, i) => `c${i + 1}`);
8154
+ }
8155
+ assertColumns(columns);
8156
+ if (rows.length === 0) throw new ImportSourceError("CSV has no data rows.");
8157
+ rows.forEach((row, i) => {
8158
+ if (row.length !== columns.length) {
8159
+ throw new ImportSourceError(`CSV row ${i + (options.hasHeader ? 2 : 1)} has ${row.length} cells; expected ${columns.length}.`);
8160
+ }
8161
+ });
8162
+ return { columns, rows };
8163
+ }
8164
+
8165
+ // src/import/convertImportCsvValue.ts
8166
+ var LF_MULTI_TYPES = /* @__PURE__ */ new Set([
8167
+ "CHECK_BOX",
8168
+ "MULTI_SELECT",
8169
+ "USER_SELECT",
8170
+ "ORGANIZATION_SELECT",
8171
+ "GROUP_SELECT"
8172
+ ]);
8173
+ var USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8174
+ var ImportCsvValueError = class extends Error {
8175
+ constructor() {
8176
+ super("multiple-value CSV cell contains an empty LF-delimited item");
8177
+ this.code = "ERR_IMPORT_MULTI_EMPTY_ITEM";
8178
+ this.name = "ImportCsvValueError";
8179
+ }
8180
+ };
8181
+ function convertImportCsvValue(raw, type, options) {
8182
+ void options;
8183
+ if (!LF_MULTI_TYPES.has(type ?? "")) return raw;
8184
+ if (raw === "") return [];
8185
+ const items = raw.split(/\r\n|\n/);
8186
+ if (items.some((item) => item === "")) throw new ImportCsvValueError();
8187
+ return USER_TYPES2.has(type ?? "") ? items.map((code) => ({ code })) : items;
8188
+ }
8189
+
8190
+ // src/import/jsonTokenizer.ts
8191
+ function fail(message, offset, line, column) {
8192
+ throw new ImportSourceError(`JSON ${message} (offset=${offset}, line=${line}, column=${column}).`);
8193
+ }
8194
+ function decodeUtf8Json(bytes) {
8195
+ try {
8196
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
8197
+ } catch {
8198
+ throw new ImportSourceError("JSON source is not valid UTF-8.");
8199
+ }
8200
+ }
8201
+ function tokenizeJson(text) {
8202
+ const tokens = [];
8203
+ let i = 0, line = 1, column = 1;
8204
+ const advance = () => {
8205
+ const ch = text[i++];
8206
+ if (ch === "\n") {
8207
+ line++;
8208
+ column = 1;
8209
+ } else column++;
8210
+ return ch;
8211
+ };
8212
+ const position = () => ({ offset: i, line, column });
8213
+ while (i < text.length) {
8214
+ const ch = text[i];
8215
+ if (ch === " " || ch === " " || ch === "\r" || ch === "\n") {
8216
+ advance();
8217
+ continue;
8218
+ }
8219
+ const start = position();
8220
+ if ("{}[]:,".includes(ch)) {
8221
+ advance();
8222
+ tokens.push({ kind: "punct", value: ch, ...start });
8223
+ continue;
8224
+ }
8225
+ if (ch === '"') {
8226
+ advance();
8227
+ let value = "";
8228
+ let closed = false;
8229
+ while (i < text.length) {
8230
+ const c = advance();
8231
+ if (c === '"') {
8232
+ closed = true;
8233
+ break;
8234
+ }
8235
+ if (c.charCodeAt(0) < 32) fail("string contains an unescaped control character", start.offset, start.line, start.column);
8236
+ if (c !== "\\") {
8237
+ value += c;
8238
+ continue;
8239
+ }
8240
+ if (i >= text.length) fail("string has an unterminated escape", start.offset, start.line, start.column);
8241
+ const esc = advance();
8242
+ const simple = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
8243
+ if (esc in simple) {
8244
+ value += simple[esc];
8245
+ continue;
8246
+ }
8247
+ if (esc !== "u") fail(`has invalid escape \\${esc}`, i - 2, line, Math.max(1, column - 2));
8248
+ const hex = text.slice(i, i + 4);
8249
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) fail("has invalid unicode escape", i, line, column);
8250
+ for (let n = 0; n < 4; n++) advance();
8251
+ const code = Number.parseInt(hex, 16);
8252
+ if (code >= 55296 && code <= 56319) {
8253
+ 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);
8254
+ advance();
8255
+ advance();
8256
+ const lowHex = text.slice(i, i + 4);
8257
+ for (let n = 0; n < 4; n++) advance();
8258
+ const low = Number.parseInt(lowHex, 16);
8259
+ if (low < 56320 || low > 57343) fail("has an invalid surrogate pair", i - 4, line, Math.max(1, column - 4));
8260
+ value += String.fromCodePoint(65536 + (code - 55296 << 10) + low - 56320);
8261
+ } else if (code >= 56320 && code <= 57343) {
8262
+ fail("has an unpaired low surrogate", i - 4, line, Math.max(1, column - 4));
8263
+ } else value += String.fromCharCode(code);
8264
+ }
8265
+ if (!closed) fail("string is unterminated", start.offset, start.line, start.column);
8266
+ tokens.push({ kind: "string", value, ...start });
8267
+ continue;
8268
+ }
8269
+ const rest = text.slice(i);
8270
+ const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest)?.[0];
8271
+ if (number) {
8272
+ for (let n = 0; n < number.length; n++) advance();
8273
+ tokens.push({ kind: "number", lexeme: number, ...start });
8274
+ continue;
8275
+ }
8276
+ const literal = /^(true|false|null)/.exec(rest)?.[0];
8277
+ if (literal) {
8278
+ for (let n = 0; n < literal.length; n++) advance();
8279
+ tokens.push({ kind: "literal", value: literal === "true" ? true : literal === "false" ? false : null, ...start });
8280
+ continue;
8281
+ }
8282
+ fail(`has an unexpected token ${JSON.stringify(ch)}`, start.offset, start.line, start.column);
8283
+ }
8284
+ tokens.push({ kind: "eof", offset: i, line, column });
8285
+ return tokens;
8286
+ }
8287
+
8288
+ // src/import/jsonDecoder.ts
8289
+ function describe(token) {
8290
+ return token.kind === "eof" ? "end of input" : token.kind === "punct" ? token.value : token.kind;
8291
+ }
8292
+ function decodeJsonRecords(bytes) {
8293
+ if (bytes.byteLength === 0) throw new ImportSourceError("JSON source is empty.");
8294
+ const tokens = tokenizeJson(decodeUtf8Json(bytes));
8295
+ let index = 0;
8296
+ const fail3 = (message, token = tokens[index]) => {
8297
+ throw new ImportSourceError(`JSON ${message} (offset=${token.offset}, line=${token.line}, column=${token.column}).`);
8298
+ };
8299
+ const isPunct = (token, value) => token.kind === "punct" && token.value === value;
8300
+ const punct = (value) => {
8301
+ const token = tokens[index];
8302
+ if (token.kind !== "punct" || token.value !== value) fail3(`expected ${value}; found ${describe(token)}`, token);
8303
+ index++;
8304
+ };
8305
+ const parseValue = () => {
8306
+ const token = tokens[index++];
8307
+ if (token.kind === "string") return token.value;
8308
+ if (token.kind === "number") return { kind: "number", lexeme: token.lexeme };
8309
+ if (token.kind === "literal") return token.value;
8310
+ if (token.kind === "punct" && token.value === "{") {
8311
+ const object = /* @__PURE__ */ new Map();
8312
+ if (isPunct(tokens[index], "}")) {
8313
+ index++;
8314
+ return object;
8315
+ }
8316
+ while (true) {
8317
+ const key = tokens[index++];
8318
+ if (key.kind !== "string") return fail3(`object key must be a string; found ${describe(key)}`, key);
8319
+ const keyValue = key.value;
8320
+ if (object.has(keyValue)) fail3(`duplicate key ${JSON.stringify(keyValue)}`, key);
8321
+ punct(":");
8322
+ object.set(keyValue, parseValue());
8323
+ const separator = tokens[index];
8324
+ if (isPunct(separator, "}")) {
8325
+ index++;
8326
+ break;
8327
+ }
8328
+ punct(",");
8329
+ }
8330
+ return object;
8331
+ }
8332
+ if (token.kind === "punct" && token.value === "[") {
8333
+ const array = [];
8334
+ if (isPunct(tokens[index], "]")) {
8335
+ index++;
8336
+ return array;
8337
+ }
8338
+ while (true) {
8339
+ array.push(parseValue());
8340
+ const separator = tokens[index];
8341
+ if (isPunct(separator, "]")) {
8342
+ index++;
8343
+ break;
8344
+ }
8345
+ punct(",");
8346
+ }
8347
+ return array;
8348
+ }
8349
+ return fail3(`expected a value; found ${describe(token)}`, token);
8350
+ };
8351
+ const root = parseValue();
8352
+ if (tokens[index].kind !== "eof") fail3(`has trailing data; found ${describe(tokens[index])}`);
8353
+ const records = root instanceof Map ? [root] : Array.isArray(root) ? root : fail3("root must be an object or array.", tokens[0]);
8354
+ if (records.length === 0) throw new ImportSourceError("JSON source contains no records.");
8355
+ records.forEach((record, i) => {
8356
+ if (!(record instanceof Map)) throw new ImportSourceError(`JSON record ${i + 1} must be an object.`);
8357
+ });
8358
+ return records;
8359
+ }
8360
+
8361
+ // src/import/jsonMaterializer.ts
8362
+ var STRING_ARRAY_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
8363
+ var CODE_ARRAY_TYPES = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8364
+ function fail2(row, field, message) {
8365
+ throw new ImportSourceError(`JSON field validation failed (row=${row}, field=${field}): ${message}`);
8366
+ }
8367
+ function isNumber(value) {
8368
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map) && value.kind === "number";
8369
+ }
8370
+ function materializeValue(value, target, row) {
8371
+ if (value === null) return "";
8372
+ if (typeof value === "string") return value;
8373
+ if (typeof value === "boolean") fail2(row, target.code, "boolean is not accepted.");
8374
+ if (isNumber(value)) {
8375
+ if (target.fieldType === "NUMBER") fail2(row, target.code, "precision target requires a JSON string.");
8376
+ if (!/^-?(?:0|[1-9]\d*)$/.test(value.lexeme) || value.lexeme === "-0") {
8377
+ fail2(row, target.code, `JSON number ${value.lexeme} must be a non-negative-zero safe integer lexeme.`);
8378
+ }
8379
+ const number = Number(value.lexeme);
8380
+ if (!Number.isSafeInteger(number)) fail2(row, target.code, `JSON number ${value.lexeme} is outside the safe integer range.`);
8381
+ return String(number);
8382
+ }
8383
+ if (value instanceof Map) fail2(row, target.code, "object is not accepted for a flat field.");
8384
+ if (!Array.isArray(value)) fail2(row, target.code, "unsupported value type.");
8385
+ if (!STRING_ARRAY_TYPES.has(target.fieldType) && !CODE_ARRAY_TYPES.has(target.fieldType)) {
8386
+ fail2(row, target.code, "array is accepted only for multi-value fields.");
8387
+ }
8388
+ const strings = value.map((entry) => {
8389
+ if (typeof entry !== "string") fail2(row, target.code, "array elements must be strings.");
8390
+ return entry;
8391
+ });
8392
+ if (new Set(strings).size !== strings.length) fail2(row, target.code, "array elements must not contain duplicates.");
8393
+ return CODE_ARRAY_TYPES.has(target.fieldType) ? JSON.stringify(strings.map((code) => ({ code }))) : JSON.stringify(strings);
8394
+ }
8395
+ function materializeJsonDmlSource(_source, payload, targets, maxRows) {
8396
+ if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
8397
+ const records = decodeJsonRecords(payload.bytes);
8398
+ if (records.length > maxRows) throw new ImportSourceError(`source rows (${records.length}) exceed maxRecords (${maxRows}).`);
8399
+ const targetByCode = new Map(targets.map((target) => [target.code, target]));
8400
+ if (targetByCode.size !== targets.length) throw new ImportSourceError("JSON target fields contain duplicates.");
8401
+ const rows = [];
8402
+ const importPresence = [];
8403
+ records.forEach((record, index) => {
8404
+ for (const key of record.keys()) {
8405
+ if (!targetByCode.has(key)) fail2(index + 1, key, "unknown key (not declared in INTO).");
8406
+ }
8407
+ const row = {};
8408
+ const present = /* @__PURE__ */ new Set();
8409
+ for (const target of targets) {
8410
+ if (!record.has(target.code)) continue;
8411
+ present.add(target.code);
8412
+ row[target.code] = materializeValue(record.get(target.code), target, index + 1);
8413
+ }
8414
+ rows.push(row);
8415
+ importPresence.push(present);
8416
+ });
8417
+ return {
8418
+ rows,
8419
+ columns: targets.map((target) => target.code),
8420
+ columnMeta: new Map(targets.map((target) => [target.code, { fieldType: target.fieldType }])),
8421
+ importPresence
8422
+ };
8423
+ }
8424
+
8425
+ // src/import/materializeDmlSource.ts
8426
+ function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInfos, recordNumberSourceHeader) {
8427
+ const decoded = decodeCsv(payload.bytes, {
8428
+ encoding: source.encoding ?? payload.encoding ?? "utf8",
8429
+ hasHeader: source.hasHeader,
8430
+ columns: source.columns
8431
+ });
8432
+ if (decoded.rows.length > maxRows) {
8433
+ throw new ImportSourceError(`source rows (${decoded.rows.length}) exceed maxRecords (${maxRows}).`);
8434
+ }
8435
+ if (source.mappingMode === "BY_NAME") {
8436
+ if (!targetCodes || !fieldInfos) throw new Error("InternalError: BY NAME requires destination form metadata.");
8437
+ if (new Set(targetCodes).size !== targetCodes.length) {
8438
+ throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: a BY NAME header cannot be consumed more than once.");
8439
+ }
8440
+ const indexes = new Map(decoded.columns.map((column, index) => [column, index]));
8441
+ for (const code of targetCodes) {
8442
+ if (!indexes.has(code)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${code}" is missing.`);
8443
+ }
8444
+ const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
8445
+ const targetSet = new Set(targetCodes);
8446
+ if (recordNumberSourceHeader && targetSet.has(recordNumberSourceHeader)) {
8447
+ throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: record-number source header is lookup-only and cannot be a write target.");
8448
+ }
8449
+ if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) {
8450
+ throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
8451
+ }
8452
+ const ignoredKnownColumns = [];
8453
+ const ignoredUnknownColumns = [];
8454
+ const nonEmpty = (index) => decoded.rows.filter((row) => row[index] !== "").length;
8455
+ const reasonFor = (info) => {
8456
+ if (info.fieldType === "FILE") return "FILE attachment is outside flat IMPORT scope";
8457
+ if (info.inSubtable || info.fieldType === "SUBTABLE") return "subtable field is not writable in Phase 3";
8458
+ if (info.writable === false) return `non-writable ${info.fieldType} field`;
8459
+ return `known export-only ${info.fieldType} field`;
8460
+ };
8461
+ for (const [index, column] of decoded.columns.entries()) {
8462
+ if (targetSet.has(column) || column === recordNumberSourceHeader) continue;
8463
+ const info = infoByCode.get(column);
8464
+ if (info) ignoredKnownColumns.push({ column, reason: reasonFor(info), nonEmptyCells: nonEmpty(index) });
8465
+ else if (!source.ignoreUnknownColumns) throw new ImportSourceError(`ERR_IMPORT_UNKNOWN_COLUMN: unknown CSV header "${column}".`);
8466
+ else ignoredUnknownColumns.push({ column, reason: "unknown column ignored by explicit policy", nonEmptyCells: nonEmpty(index) });
8467
+ }
8468
+ for (const code of targetCodes) {
8469
+ const info = infoByCode.get(code);
8470
+ if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
8471
+ if (info.inSubtable || info.writable === false || info.fieldType === "FILE" || info.fieldType === "SUBTABLE") {
8472
+ throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
8473
+ }
8474
+ }
8475
+ const importRowErrors = [];
8476
+ const rows2 = decoded.rows.map((values) => {
8477
+ const errors = [];
8478
+ const row = {};
8479
+ for (const code of targetCodes) {
8480
+ const raw = values[indexes.get(code)];
8481
+ try {
8482
+ row[code] = convertImportCsvValue(raw, infoByCode.get(code)?.fieldType, { cliKintone: true });
8483
+ } catch (error) {
8484
+ if (!(error instanceof ImportCsvValueError)) throw error;
8485
+ row[code] = raw;
8486
+ errors.push({ field: code, code: error.code, message: error.message });
8487
+ }
8488
+ }
8489
+ importRowErrors.push(errors);
8490
+ return row;
8491
+ });
8492
+ return {
8493
+ rows: rows2,
8494
+ columns: [...targetCodes],
8495
+ columnMeta: new Map(targetCodes.map((code) => [code, { fieldType: infoByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }])),
8496
+ importRowErrors,
8497
+ ...recordNumberSourceHeader ? { recordNumberSourceValues: decoded.rows.map((row) => row[indexes.get(recordNumberSourceHeader)]) } : {},
8498
+ importAudit: { mapping: "BY_NAME", writtenColumns: [...targetCodes], ignoredKnownColumns, ignoredUnknownColumns }
8499
+ };
8500
+ }
8501
+ const rows = decoded.rows.map((values) => Object.fromEntries(decoded.columns.map((column, i) => [column, values[i]])));
8502
+ return {
8503
+ rows,
8504
+ columns: decoded.columns,
8505
+ // CSV cells are decoded as strings; projections such as CAST may replace this metadata downstream.
8506
+ columnMeta: new Map(decoded.columns.map((column) => [column, { fieldType: "SINGLE_LINE_TEXT" }]))
8507
+ };
8508
+ }
8509
+
8510
+ // src/import/importRecordsMaterializer.ts
8511
+ var sourceFail = (parentRow, code, message) => {
8512
+ throw new ImportSourceError(`JSON subtable validation failed (parentRow=${parentRow}, field=${code}): ${message}`);
8513
+ };
8514
+ function materializeJsonImportRecords(_source, payload, targets, maxParents, maxChildRows = maxParents) {
8515
+ if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
8516
+ const decoded = decodeJsonRecords(payload.bytes);
8517
+ if (decoded.length > maxParents) throw new ImportSourceError(`source parent rows (${decoded.length}) exceed maxRecords (${maxParents}).`);
8518
+ const targetByCode = new Map(targets.map((target) => [target.kind === "FIELD" ? target.field : target.subtableCode, target]));
8519
+ if (targetByCode.size !== targets.length) throw new ImportSourceError("IMPORT targets contain duplicates.");
8520
+ let childTotal = 0;
8521
+ return {
8522
+ records: decoded.map((record, index) => {
8523
+ const parentRow = index + 1;
8524
+ for (const code of record.keys()) if (!targetByCode.has(code)) sourceFail(parentRow, code, "unknown key (not declared in INTO).");
8525
+ const top = /* @__PURE__ */ new Map();
8526
+ const subtables = /* @__PURE__ */ new Map();
8527
+ const replacementTables = /* @__PURE__ */ new Set();
8528
+ for (const target of targets) {
8529
+ const code = target.kind === "FIELD" ? target.field : target.subtableCode;
8530
+ if (!record.has(code)) continue;
8531
+ const value = record.get(code);
8532
+ if (target.kind === "FIELD") {
8533
+ if (value instanceof Map) sourceFail(parentRow, code, "object is not accepted for a top-level field.");
8534
+ top.set(code, value);
8535
+ continue;
8536
+ }
8537
+ if (!Array.isArray(value)) sourceFail(parentRow, code, "subtable value must be an array.");
8538
+ replacementTables.add(code);
8539
+ const children = new Set(target.children);
8540
+ const rows = value.map((entry, childIndex) => {
8541
+ if (!(entry instanceof Map)) sourceFail(parentRow, code, `childRow=${childIndex + 1} must be an object.`);
8542
+ const child = entry;
8543
+ for (const childCode of child.keys()) {
8544
+ if (!children.has(childCode)) sourceFail(parentRow, childCode, `unknown child key in subtable ${code} at childRow=${childIndex + 1}.`);
8545
+ }
8546
+ childTotal++;
8547
+ if (childTotal > maxChildRows) throw new ImportSourceError(`source child rows (${childTotal}) exceed limit (${maxChildRows}).`);
8548
+ return { childRowNumber: childIndex + 1, values: child };
8549
+ });
8550
+ subtables.set(code, rows);
8551
+ }
8552
+ return { rowNumber: parentRow, top, subtables, replacementTables };
8553
+ })
8554
+ };
8555
+ }
8556
+ function materializeCliKintoneCsvImportRecords(source, payload, targets, replacementTables, maxParents, recordNumberSourceHeader) {
8557
+ const decoded = decodeCsv(payload.bytes, {
8558
+ encoding: source.encoding ?? payload.encoding ?? "utf8",
8559
+ hasHeader: source.hasHeader,
8560
+ columns: source.columns
8561
+ });
8562
+ if (!source.hasHeader || decoded.columns[0] !== "*") throw new ImportSourceError('ERR_IMPORT_MARKER: first CSV header must be "*".');
8563
+ const indexes = new Map(decoded.columns.map((code, index) => [code, index]));
8564
+ if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
8565
+ const fields = targets.filter((target) => target.kind === "FIELD");
8566
+ const tables = targets.filter((target) => target.kind === "SUBTABLE");
8567
+ for (const field of fields) if (!indexes.has(field.field)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${field.field}" is missing.`);
8568
+ for (const table of tables) {
8569
+ if (!table.rowIdSourceHeader || !indexes.has(table.rowIdSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: row-ID header for ${table.subtableCode} is missing.`);
8570
+ for (const child of table.children) if (!indexes.has(child)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required child header "${child}" is missing.`);
8571
+ }
8572
+ const records = [];
8573
+ let current;
8574
+ decoded.rows.forEach((cells, physicalIndex) => {
8575
+ const sourceRowNumber = physicalIndex + 2;
8576
+ const marker = cells[0];
8577
+ if (marker !== "" && marker !== "*") throw new ImportSourceError(`ERR_IMPORT_MARKER: invalid marker ${JSON.stringify(marker)} at source row ${sourceRowNumber}.`);
8578
+ if (marker === "*") {
8579
+ if (records.length >= maxParents) throw new ImportSourceError(`source parent rows exceed maxRecords (${maxParents}).`);
8580
+ current = {
8581
+ rowNumber: records.length + 1,
8582
+ markerRowNumber: sourceRowNumber,
8583
+ top: new Map(fields.map((field) => [field.field, cells[indexes.get(field.field)]])),
8584
+ subtables: new Map(tables.map((table) => [table.subtableCode, []])),
8585
+ replacementTables: new Set(replacementTables),
8586
+ ...recordNumberSourceHeader ? { recordNumberSourceValue: cells[indexes.get(recordNumberSourceHeader)] } : {}
8587
+ };
8588
+ records.push(current);
8589
+ } else if (!current) {
8590
+ throw new ImportSourceError(`ERR_IMPORT_MARKER: first data row must start a parent (source row ${sourceRowNumber}).`);
8591
+ } else {
8592
+ for (const field of fields) {
8593
+ const continuationValue = cells[indexes.get(field.field)];
8594
+ if (continuationValue !== "" && continuationValue !== current.top.get(field.field)) {
8595
+ throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${field.field} at source row ${sourceRowNumber}.`);
8596
+ }
8597
+ }
8598
+ if (recordNumberSourceHeader) {
8599
+ const continuationValue = cells[indexes.get(recordNumberSourceHeader)];
8600
+ if (continuationValue !== "" && continuationValue !== current.recordNumberSourceValue) {
8601
+ throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${recordNumberSourceHeader} at source row ${sourceRowNumber}.`);
8602
+ }
8603
+ }
8604
+ }
8605
+ for (const table of tables) {
8606
+ const rowId = cells[indexes.get(table.rowIdSourceHeader)];
8607
+ const values = new Map(table.children.map((child) => [child, cells[indexes.get(child)]]));
8608
+ if (rowId === "" && [...values.values()].every((value) => value === "")) continue;
8609
+ const rows = current.subtables.get(table.subtableCode);
8610
+ rows.push({ childRowNumber: rows.length + 1, sourceRowNumber, ...rowId ? { rowId } : {}, values });
8611
+ }
8612
+ });
8613
+ if (records.length === 0) throw new ImportSourceError("CSV has no parent rows.");
8614
+ return { records };
8615
+ }
8616
+
8617
+ // src/import/importRecordValidation.ts
8618
+ var USER_TYPES3 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
8619
+ var UNSUPPORTED_CHILD_TYPES = /* @__PURE__ */ new Set(["SUBTABLE", "FILE", "CALC", "RECORD_NUMBER", "CREATOR", "CREATED_TIME", "MODIFIER", "UPDATED_TIME", "STATUS", "STATUS_ASSIGNEE", "CATEGORY", "REFERENCE_TABLE"]);
8620
+ function assertImportRejectLimit(prepared, rejectLimit) {
8621
+ if (rejectLimit != null && prepared.invalidParentRows.size > rejectLimit) {
8622
+ throw new Error(`RejectLimitExceededError: rejected parents (${prepared.invalidParentRows.size}) exceed REJECT LIMIT (${rejectLimit}).`);
8623
+ }
8624
+ }
8625
+ function prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation) {
8626
+ const topInfos = new Map(fieldInfos.filter((f) => !f.inSubtable).map((f) => [f.code, f]));
8627
+ const scoped = /* @__PURE__ */ new Map();
8628
+ for (const info of fieldInfos) if (info.inSubtable && info.subtableCode) {
8629
+ let children = scoped.get(info.subtableCode);
8630
+ if (!children) scoped.set(info.subtableCode, children = /* @__PURE__ */ new Map());
8631
+ children.set(info.code, info);
8632
+ }
8633
+ const targetTop = targets.filter((t) => t.kind === "FIELD");
8634
+ const targetTables = targets.filter((t) => t.kind === "SUBTABLE");
8635
+ for (const target of targetTop) assertWritable(target.field, topInfos.get(target.field), void 0);
8636
+ for (const target of targetTables) {
8637
+ const table = topInfos.get(target.subtableCode);
8638
+ if (!table || table.fieldType !== "SUBTABLE") throw new Error(`ArgumentError: IMPORT subtable ${target.subtableCode} does not exist.`);
8639
+ const children = scoped.get(target.subtableCode) ?? /* @__PURE__ */ new Map();
8640
+ for (const child of target.children) assertWritable(child, children.get(child), target.subtableCode);
8641
+ }
8642
+ const tableCounts = new Map(targetTables.map((t) => [t.subtableCode, { parentsPresent: 0, childRows: 0, validChildRows: 0, invalidChildRows: 0 }]));
8643
+ const parents = materialized.records.map((record) => validateParent(record, targetTop, targetTables, topInfos, scoped, numberPrecision, operation, tableCounts));
8644
+ const errors = parents.flatMap((parent) => [...parent.errors]);
8645
+ return { parents, errors, invalidParentRows: new Set(parents.filter((p) => !p.valid).map((p) => p.parentRow)), tableCounts };
8646
+ }
8647
+ function validateParent(source, topTargets, tableTargets, topInfos, scoped, precision, operation, tableCounts) {
8648
+ const errors = [];
8649
+ const top = {};
8650
+ for (const target of topTargets) {
8651
+ if (!source.top.has(target.field)) continue;
8652
+ validateValue(source.top.get(target.field), topInfos.get(target.field), precision, top, target.field, errors, location(source, operation, target.field));
8653
+ }
8654
+ const createValidationOnly = {};
8655
+ if (operation === "INSERT") for (const info of topInfos.values()) {
8656
+ if (info.fieldType === "SUBTABLE" || info.writable === false || source.top.has(info.code)) continue;
8657
+ validateMissing(info, precision, createValidationOnly, errors, location(source, operation, info.code));
8658
+ }
8659
+ const subtables = /* @__PURE__ */ new Map();
8660
+ for (const target of tableTargets) {
8661
+ if (!source.subtables.has(target.subtableCode)) continue;
8662
+ const count = tableCounts.get(target.subtableCode);
8663
+ count.parentsPresent++;
8664
+ const preparedRows = [];
8665
+ for (const child of source.subtables.get(target.subtableCode)) {
8666
+ count.childRows++;
8667
+ const before = errors.length;
8668
+ const record = {};
8669
+ const infos = scoped.get(target.subtableCode);
8670
+ for (const code of target.children) {
8671
+ const info = infos.get(code);
8672
+ const loc = location(source, operation, code, target.subtableCode, child.childRowNumber, child.sourceRowNumber ?? source.markerRowNumber);
8673
+ if (child.values.has(code)) validateValue(child.values.get(code), info, precision, record, code, errors, loc);
8674
+ else validateMissing(info, precision, record, errors, loc);
8675
+ }
8676
+ if (errors.length === before) {
8677
+ count.validChildRows++;
8678
+ preparedRows.push(record);
8679
+ } else count.invalidChildRows++;
8680
+ }
8681
+ subtables.set(target.subtableCode, preparedRows);
8682
+ }
8683
+ return { parentRow: source.rowNumber, valid: errors.length === 0, top, subtables, replacementTables: source.replacementTables, errors };
8684
+ }
8685
+ function assertWritable(code, info, table) {
8686
+ 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.`);
8687
+ if (info.writable === false || table && UNSUPPORTED_CHILD_TYPES.has(info.fieldType)) {
8688
+ throw new Error(`ArgumentError: IMPORT ${table ? `child ${table}.${code}` : `field ${code}`} is not writable (${info.fieldType}).`);
8689
+ }
8690
+ }
8691
+ function validateMissing(info, precision, record, errors, loc) {
8692
+ const raw = isEmptyDmlValue(info.defaultValue) ? "" : info.defaultValue;
8693
+ validateValue(raw, info, precision, record, info.code, errors, loc, !isEmptyDmlValue(info.defaultValue));
8694
+ }
8695
+ function validateValue(raw, info, precision, record, code, errors, loc, isDefault = false) {
8696
+ const normalizedRaw = decodeRaw(raw);
8697
+ const result = validateAndNormalizeDmlValue(normalizedRaw, info, precision);
8698
+ if (!result.ok) errors.push({ ...loc, code: result.code, message: isDefault ? `\u65E2\u5B9A\u5024: ${result.message}` : result.message });
8699
+ else record[code] = { value: preserveUserCodes(normalizedRaw, info) ? normalizedRaw : result.value };
8700
+ }
8701
+ function decodeRaw(raw) {
8702
+ if (isJsonNumber(raw)) return raw.lexeme;
8703
+ if (Array.isArray(raw)) return raw.map((value) => value instanceof Map ? value : isJsonNumber(value) ? value.lexeme : value);
8704
+ return raw;
8705
+ }
8706
+ function isJsonNumber(raw) {
8707
+ return typeof raw === "object" && raw !== null && raw.kind === "number";
8708
+ }
8709
+ function preserveUserCodes(raw, info) {
8710
+ return USER_TYPES3.has(info.fieldType) && Array.isArray(raw) && raw.every((v) => typeof v === "object" && v !== null && "code" in v);
8711
+ }
8712
+ function location(source, operation, field, subtable, subrow, sourceRow) {
8713
+ const physicalRow = sourceRow ?? source.markerRowNumber;
8714
+ 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 };
8715
+ }
8716
+
8717
+ // src/import/importErrors.ts
8718
+ var IMPORT_VALIDATION_META_COLUMNS = [
8719
+ "$err_statement",
8720
+ "$err_operation",
8721
+ "$err_row",
8722
+ "$err_field",
8723
+ "$err_subtable",
8724
+ "$err_subrow",
8725
+ "$err_source_row",
8726
+ "$err_code",
8727
+ "$err_message"
8728
+ ];
8729
+ function materializeImportValidationErrors(errors, payloadFields, statementNumber = 1) {
8730
+ return errors.map((error) => {
8731
+ const row = {};
8732
+ for (const field of payloadFields) row[field] = error.sourceValues.get(field) == null ? "" : render(error.sourceValues.get(field));
8733
+ row["$err_statement"] = String(statementNumber);
8734
+ row["$err_operation"] = error.operation;
8735
+ row["$err_row"] = String(error.parentRow);
8736
+ row["$err_field"] = error.field;
8737
+ row["$err_subtable"] = error.subtable ?? "";
8738
+ row["$err_subrow"] = error.subrow == null ? "" : String(error.subrow);
8739
+ row["$err_source_row"] = error.sourceRow == null ? null : String(error.sourceRow);
8740
+ row["$err_code"] = error.code;
8741
+ row["$err_message"] = error.message;
8742
+ return row;
8743
+ });
8744
+ }
8745
+ function render(value) {
8746
+ if (value === null || value === void 0) return "";
8747
+ if (typeof value === "object" && value !== null && "kind" in value && "lexeme" in value && value.kind === "number") {
8748
+ return String(value.lexeme);
8749
+ }
8750
+ if (Array.isArray(value)) return JSON.stringify(value);
8751
+ return String(value);
8752
+ }
8753
+
8754
+ // src/import/subtablePayload.ts
8755
+ function buildImportRecordPayload(top, subtables, rowIdMode) {
8756
+ const record = {};
8757
+ for (const [code, value] of top) record[code] = { value };
8758
+ for (const [tableCode, sourceRows] of subtables) {
8759
+ record[tableCode] = {
8760
+ value: sourceRows.map((sourceRow) => ({
8761
+ ...rowIdMode === "PRESERVE" && sourceRow.rowId ? { id: sourceRow.rowId } : {},
8762
+ value: Object.fromEntries([...sourceRow.values].map(([childCode, value]) => [childCode, { value }]))
8763
+ }))
8764
+ };
8765
+ }
8766
+ return record;
8767
+ }
8768
+ function buildJsonImportRecordPayload(top, subtables) {
8769
+ return buildImportRecordPayload(top, subtables, "DROP");
8770
+ }
8771
+
8772
+ // src/import/jsonSubtableWritePlan.ts
8773
+ function assertJsonImportHasNoRowIds(materialized) {
8774
+ for (const parent of materialized.records) for (const [table, rows] of parent.subtables) {
8775
+ for (const row of rows) {
8776
+ if (row.rowId !== void 0 || row.values.has("_rid") || row.values.has("id")) {
8777
+ throw new Error(`ArgumentError: JSON IMPORT subtable ${table} does not accept _rid/id; rows are always newly numbered.`);
8778
+ }
8779
+ }
8780
+ }
8781
+ }
8782
+ function buildJsonSubtableWritePlan(parents, targetIds, existingById) {
8783
+ return parents.map((parent, index) => {
8784
+ const targetId = targetIds[index];
8785
+ const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
8786
+ if (targetId !== void 0 && !existing) throw new Error(`InternalError: IMPORT UPSERT target APP record ${targetId} was not loaded.`);
8787
+ const tables = [...parent.subtables].map(([table, input]) => {
8788
+ const raw = existing?.record[table]?.value;
8789
+ const existingRows = Array.isArray(raw) ? raw.length : 0;
8790
+ return { table, existingRows, inputRows: input.length, addRows: input.length, deleteRows: existingRows };
8791
+ });
8792
+ return {
8793
+ parentRow: parent.parentRow,
8794
+ mode: targetId === void 0 ? "INSERT" : "UPDATE",
8795
+ ...targetId === void 0 ? {} : { targetId, revision: existing?.revision },
8796
+ top: parent.top,
8797
+ subtables: parent.subtables,
8798
+ tables
8799
+ };
8800
+ });
8801
+ }
8802
+
8803
+ // src/import/subtableReplacementPlan.ts
8804
+ function tableRows(record, table) {
8805
+ const raw = record[table]?.value;
8806
+ return Array.isArray(raw) ? raw : [];
8807
+ }
8808
+ function assertNoDuplicateCsvSubtableRowIds(records) {
8809
+ const seen = /* @__PURE__ */ new Map();
8810
+ for (const parent of records) for (const [table, rows] of parent.subtables) for (const row of rows) {
8811
+ if (!row.rowId) continue;
8812
+ const key = `${table}\0${row.rowId}`;
8813
+ if (seen.has(key)) throw new Error(`ERR_SUBTABLE_ROW_ID_DUP_SOURCE: duplicate row ID ${row.rowId} in ${table}`);
8814
+ seen.set(key, parent.rowNumber);
8815
+ }
8816
+ }
8817
+ function buildCsvSubtableReplacementPlan(sources, prepared, targetIds, existingById, ownership) {
8818
+ return prepared.map((parent, index) => {
8819
+ const source = sources[index];
8820
+ const targetId = targetIds[index];
8821
+ const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
8822
+ const errors = [...parent.errors];
8823
+ if (!existing || targetId === void 0) return { parentRow: parent.parentRow, targetId: targetId ?? 0, valid: false, top: parent.top, subtables: /* @__PURE__ */ new Map(), tables: [], errors };
8824
+ const subtables = /* @__PURE__ */ new Map();
8825
+ const tables = [];
8826
+ for (const table of parent.replacementTables) {
8827
+ const current = tableRows(existing.record, table);
8828
+ const currentIds = new Set(current.map((row) => row.id).filter((id) => !!id));
8829
+ const input = source.subtables.get(table) ?? [];
8830
+ const normalized = parent.subtables.get(table) ?? [];
8831
+ let updateRows = 0, addRows = 0, rowIdNotFound = 0;
8832
+ const payloadRows = input.map((row, rowIndex) => {
8833
+ const normalizedRecord = normalized[rowIndex] ?? {};
8834
+ if (row.rowId && currentIds.has(row.rowId)) {
8835
+ updateRows++;
8836
+ return { rowId: row.rowId, record: normalizedRecord };
8837
+ }
8838
+ if (row.rowId) {
8839
+ const owners = ownership.get(row.rowId) ?? [];
8840
+ if (owners.some((owner) => owner.parentId !== targetId || owner.table !== table)) errors.push({
8841
+ operation: "UPDATE",
8842
+ parentRow: parent.parentRow,
8843
+ field: row.rowId,
8844
+ subtable: table,
8845
+ subrow: row.childRowNumber,
8846
+ sourceRow: row.sourceRowNumber,
8847
+ code: "ERR_IMPORT_FIELD_OWNERSHIP",
8848
+ message: `rowIdOwnedElsewhere: ${row.rowId}`,
8849
+ sourceValues: row.values
8850
+ });
8851
+ rowIdNotFound++;
8852
+ }
8853
+ addRows++;
8854
+ return { record: normalizedRecord };
8855
+ });
8856
+ subtables.set(table, payloadRows);
8857
+ tables.push({ table, existingRows: current.length, inputRows: input.length, updateRows, addRows, deleteRows: current.length - updateRows, rowIdNotFound });
8858
+ }
8859
+ return { parentRow: parent.parentRow, targetId, ...existing.revision === void 0 ? {} : { revision: existing.revision }, valid: errors.length === 0, top: parent.top, subtables, tables, errors };
8860
+ });
8861
+ }
8862
+
8863
+ // src/import/importProjection.ts
8864
+ var IMPORT_PROJECTION_SOURCE = "#__import_source";
8865
+ function bindImportProjection(projection) {
8866
+ return { ...projection, from: { appId: 0, alias: null, cteName: IMPORT_PROJECTION_SOURCE } };
8867
+ }
8868
+
8869
+ // src/import/recordNumberUpdate.ts
8870
+ function normalizeImportRecordNumber(raw) {
8871
+ return /^[0-9]+$/.test(raw) ? raw.replace(/^0+(?=\d)/, "") : null;
8872
+ }
8873
+ function preflightImportRecordNumbers(values, header) {
8874
+ const normalized = values.map(normalizeImportRecordNumber);
8875
+ const seen = /* @__PURE__ */ new Set();
8876
+ for (const key of normalized) {
8877
+ if (key === null) continue;
8878
+ if (seen.has(key)) {
8879
+ throw new Error("ERR_RECORD_NUMBER_DUP_SOURCE: source contains a duplicate record number");
8880
+ }
8881
+ seen.add(key);
8882
+ }
8883
+ return {
8884
+ normalized,
8885
+ errors: normalized.map((key) => key === null ? [{
8886
+ field: header,
8887
+ code: "ERR_RECORD_NUMBER_INVALID",
8888
+ message: `${header} must be a non-empty ASCII decimal record number`
8889
+ }] : [])
8890
+ };
8891
+ }
8892
+
7820
8893
  // src/execute.ts
7821
8894
  var SEARCH_ABORTED_WARNING = "\u691C\u7D22\u304C 10 \u4E07\u4EF6\u3067\u6253\u3061\u5207\u3089\u308C\u3001\u7D50\u679C\u304C\u6B20\u843D\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002";
7822
8895
  var SearchAbortedError = class extends Error {
@@ -7827,6 +8900,7 @@ var SearchAbortedError = class extends Error {
7827
8900
  };
7828
8901
  var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
7829
8902
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
8903
+ var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
7830
8904
  var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
7831
8905
  var nextDefaultCacheContextId = 1;
7832
8906
  function resolveCacheContext(client, explicit) {
@@ -7841,7 +8915,7 @@ function resolveCacheContext(client, explicit) {
7841
8915
  async function execute(sql, client, options = {}) {
7842
8916
  const startedAt = Date.now();
7843
8917
  const cacheContext = resolveCacheContext(client, options.cacheContext);
7844
- const stmt = parseSql(sql);
8918
+ const stmt = parseSql(sql, options.enableImport === true);
7845
8919
  const metrics = createEmptyMetrics();
7846
8920
  const countedClient = wrapClientWithMetrics(client, metrics);
7847
8921
  const collector = { aborted: false };
@@ -8021,6 +9095,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
8021
9095
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
8022
9096
  }
8023
9097
  validateKlikeStatement(stmt);
9098
+ if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
8024
9099
  if ("validateOnly" in stmt && stmt.validateOnly === true) {
8025
9100
  if (stmt.validationErrorTable) {
8026
9101
  throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
@@ -8243,7 +9318,7 @@ var BatchTimeoutError = class extends Error {
8243
9318
  }
8244
9319
  };
8245
9320
  async function executeBatch(sql, client, options = {}) {
8246
- const statements = parseSqlBatch(sql);
9321
+ const statements = parseSqlBatch(sql, options.enableImport === true);
8247
9322
  const analysis = analyzeBatch(statements);
8248
9323
  const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
8249
9324
  const batchOptions = { ...options, variables: injectedVariables };
@@ -8296,11 +9371,12 @@ async function executeBatch(sql, client, options = {}) {
8296
9371
  const userConfirm = batchOptions.confirm;
8297
9372
  const stmtOptions = userConfirm ? {
8298
9373
  ...batchOptions,
8299
- confirm: (count, operation) => userConfirm(count, operation, {
9374
+ confirm: (count, operation, detailContext) => userConfirm(count, operation, {
8300
9375
  statementIndex: i,
8301
9376
  statementCount: statements.length,
8302
9377
  statementType: info.statementType,
8303
- targetAppId: info.targetAppId
9378
+ targetAppId: info.targetAppId,
9379
+ ...detailContext?.importDetail ? { importDetail: detailContext.importDetail } : {}
8304
9380
  })
8305
9381
  } : batchOptions;
8306
9382
  const searchAbortCollector = { aborted: false };
@@ -8414,6 +9490,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
8414
9490
  }
8415
9491
  return { result };
8416
9492
  }
9493
+ if (resolvedStmt.type === "IMPORT") {
9494
+ return { result: await executeImport(resolvedStmt, client, options, cacheContext, tempTables) };
9495
+ }
8417
9496
  if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
8418
9497
  const result = await executeDmlValidation(
8419
9498
  resolvedStmt,
@@ -8575,9 +9654,9 @@ function safeJsonStringify(v) {
8575
9654
  return String(v);
8576
9655
  }
8577
9656
  }
8578
- function parseSqlBatch(sql) {
9657
+ function parseSqlBatch(sql, enableImport = false) {
8579
9658
  const tokens = new Lexer(sql).tokenize();
8580
- return new Parser(tokens).parseStatements();
9659
+ return new Parser(tokens, { import: enableImport }).parseStatements();
8581
9660
  }
8582
9661
  function evaluateScalarExpr(expr) {
8583
9662
  switch (expr.type) {
@@ -9646,8 +10725,14 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
9646
10725
  }
9647
10726
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
9648
10727
  meta = syntheticColumnMeta("number");
9649
- } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") {
10728
+ } else if (column.type === "LITERAL_COL") {
9650
10729
  meta = syntheticColumnMeta("string");
10730
+ } else if (column.type === "SCALAR_VALUE_COL") {
10731
+ const expr = column.expr;
10732
+ if (expr.type === "STRING_FUNC") meta = stringFunctionColumnMeta(expr);
10733
+ else if (expr.type === "NUMBER" || expr.type === "SCALAR_ARITH") meta = syntheticColumnMeta("number");
10734
+ else if (expr.type === "FIELD") meta = resolveField2(expr);
10735
+ else meta = syntheticColumnMeta("string");
9651
10736
  } else if (column.type === "STRFUNC_COL") {
9652
10737
  meta = stringFunctionColumnMeta(column.expr);
9653
10738
  } else if (column.type === "WINDOW_COL") {
@@ -10461,9 +11546,10 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
10461
11546
  return sortKinds;
10462
11547
  }
10463
11548
  function convertProcessRowValue(raw, dstFieldType) {
10464
- const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
11549
+ if (typeof raw !== "string") return raw;
11550
+ const USER_TYPES4 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
10465
11551
  const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
10466
- if (USER_TYPES2.has(dstFieldType ?? "")) {
11552
+ if (USER_TYPES4.has(dstFieldType ?? "")) {
10467
11553
  if (raw === "") return [];
10468
11554
  try {
10469
11555
  const parsed = JSON.parse(raw);
@@ -10528,11 +11614,13 @@ function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecisio
10528
11614
  records.forEach((record, rowIndex) => {
10529
11615
  for (const code of targetFields) {
10530
11616
  const info = infoByCode.get(code);
10531
- const result = validateAndNormalizeDmlValue(record[code]?.value ?? "", info, numberPrecision);
11617
+ const original = record[code]?.value ?? "";
11618
+ const result = validateAndNormalizeDmlValue(original, info, numberPrecision);
10532
11619
  if (!result.ok) {
10533
11620
  throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
10534
11621
  }
10535
- record[code] = { value: result.value };
11622
+ 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);
11623
+ record[code] = { value: preserveCodes ? original : result.value };
10536
11624
  }
10537
11625
  });
10538
11626
  }
@@ -10692,6 +11780,8 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10692
11780
  if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
10693
11781
  let rows;
10694
11782
  let sourceRows;
11783
+ let sourcePresence;
11784
+ let sourceRowErrors;
10695
11785
  let evaluationTypes;
10696
11786
  if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
10697
11787
  assertInsertCheckRefs(stmt, stmt.fields);
@@ -10701,7 +11791,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10701
11791
  (value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
10702
11792
  ));
10703
11793
  } 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);
11794
+ const selectResult = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, tempTables, [...infoByCode.values()]);
10705
11795
  const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
10706
11796
  if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
10707
11797
  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 +11801,9 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10711
11801
  }
10712
11802
  assertInsertCheckRefs(stmt, selectResult.columns);
10713
11803
  sourceRows = selectResult.rows;
10714
- const meta = materializedMetaBySelectResult.get(selectResult);
11804
+ sourcePresence = selectResult.importPresence;
11805
+ sourceRowErrors = selectResult.importRowErrors;
11806
+ const meta = selectResult.columnMeta;
10715
11807
  evaluationTypes = new Map(selectResult.columns.map((column) => {
10716
11808
  const columnMeta = meta?.get(column);
10717
11809
  const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
@@ -10724,8 +11816,10 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10724
11816
  rowNumber: index + 1,
10725
11817
  operation,
10726
11818
  mode: "create",
10727
- payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
10728
- preErrors: [],
11819
+ payload: new Map(stmt.fields.flatMap(
11820
+ (field, i) => sourcePresence && !sourcePresence[index]?.has(field) ? [] : [[field, values[i]]]
11821
+ )),
11822
+ preErrors: [...sourceRowErrors?.[index] ?? []],
10729
11823
  record: {},
10730
11824
  evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
10731
11825
  stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
@@ -10738,13 +11832,17 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10738
11832
  }
10739
11833
  const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
10740
11834
  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
11835
  const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
10743
11836
  const keyCounts = /* @__PURE__ */ new Map();
10744
11837
  for (const parts of rowKeys) {
10745
11838
  const key = upsertNormalizedKey(parts, numeric);
10746
11839
  keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
10747
11840
  }
11841
+ const isImport = importSourceByDmlStatement.has(stmt);
11842
+ if (isImport && [...keyCounts.values()].some((count) => count > 1)) {
11843
+ throw new Error("ERR_KEY_DUP_SOURCE: UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059");
11844
+ }
11845
+ const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
10748
11846
  candidates.forEach((candidate, index) => {
10749
11847
  const parts = rowKeys[index];
10750
11848
  const targetId = lookupUpsertTarget(targets, parts);
@@ -10753,7 +11851,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10753
11851
  stmt.keyFields.forEach((key, keyIndex) => {
10754
11852
  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
11853
  });
10756
- if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
11854
+ if (!isImport && (keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
10757
11855
  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
11856
  }
10759
11857
  });
@@ -11113,12 +12211,508 @@ async function executeInsert(stmt, client, options, cacheContext) {
11113
12211
  insertedCount: createdIds.flat().length
11114
12212
  };
11115
12213
  }
12214
+ function importPlaceholderSelect() {
12215
+ return {
12216
+ type: "SELECT",
12217
+ distinct: false,
12218
+ columns: [],
12219
+ from: { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME },
12220
+ joins: [],
12221
+ where: null,
12222
+ groupBy: [],
12223
+ having: null,
12224
+ orderMode: "CANONICAL",
12225
+ orderBy: [],
12226
+ limit: null,
12227
+ offset: null
12228
+ };
12229
+ }
12230
+ async function executeImport(stmt, client, options, cacheContext, tempTables) {
12231
+ if (!options.enableImport) throw new Error("UnsupportedError: IMPORT capability is disabled.");
12232
+ const handle = resolveImportSource(stmt.source.sourceName, options.importSource);
12233
+ if (stmt.targets?.some((target) => target.kind === "SUBTABLE")) {
12234
+ if (!stmt.validateOnly && !options.supportsImportConfirmDetail) {
12235
+ throw new Error("UnsupportedError: IMPORT subtable mutation requires a surface that displays parent/table replacement and deletion detail; use VALIDATE ONLY/EXPLAIN.");
12236
+ }
12237
+ if (stmt.source.kind === "CSV") {
12238
+ if (stmt.writeMode !== "UPDATE_RECORD_NUMBER" || !stmt.recordNumberSourceHeader) throw new Error("ArgumentError: CSV subtable replacement requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.");
12239
+ if (!stmt.replaceSubtables?.length) throw new Error("ArgumentError: CSV subtable replacement requires REPLACE SUBTABLES (...).");
12240
+ const declared = new Set(stmt.targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode));
12241
+ if (stmt.replaceSubtables.some((table) => !declared.has(table))) throw new Error("ArgumentError: REPLACE SUBTABLES contains a table not declared in INTO.");
12242
+ for (const target of stmt.targets.filter((target2) => target2.kind === "SUBTABLE")) {
12243
+ if (!target.rowIdSourceHeader || !stmt.replaceSubtables.includes(target.subtableCode)) throw new Error(`ArgumentError: CSV subtable ${target.subtableCode} requires ROW ID SOURCE and REPLACE SUBTABLES declaration.`);
12244
+ }
12245
+ }
12246
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12247
+ const targets = stmt.targets;
12248
+ const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
12249
+ const targetCodes = targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children);
12250
+ const numberPrecision = fieldInfos.some((info) => targetCodes.includes(info.code) && info.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
12251
+ const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
12252
+ 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);
12253
+ const operation = stmt.source.kind === "CSV" ? "UPDATE" : stmt.keyFields ? "UPSERT" : "INSERT";
12254
+ const prepared = prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation);
12255
+ if (stmt.source.kind === "CSV") return executeCsvSubtableReplacement(stmt, materialized, prepared, fieldInfos, client, options, tempTables);
12256
+ assertImportRejectLimit(prepared, stmt.rejectLimit);
12257
+ const payloadFields = [...new Set(targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
12258
+ const errors = materializeImportValidationErrors(prepared.errors, payloadFields);
12259
+ const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
12260
+ const invalidRows = prepared.invalidParentRows.size;
12261
+ const detail = {
12262
+ preflight: "ACTUAL_DATA",
12263
+ parents: { total: prepared.parents.length, valid: prepared.parents.length - invalidRows, invalid: invalidRows, mutationCandidates: prepared.parents.filter((parent) => parent.valid).length },
12264
+ tables: Object.fromEntries(prepared.tableCounts),
12265
+ writesKintone: false
12266
+ };
12267
+ const result2 = {
12268
+ type: "VALIDATION",
12269
+ operation,
12270
+ validatedRows: prepared.parents.length,
12271
+ validRows: prepared.parents.length - invalidRows,
12272
+ invalidRows,
12273
+ errorCount: errors.length,
12274
+ columns,
12275
+ errors,
12276
+ importDetail: detail,
12277
+ ...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {}
12278
+ };
12279
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(
12280
+ tempTables,
12281
+ stmt.validationErrorTable,
12282
+ columns,
12283
+ errors,
12284
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12285
+ /* @__PURE__ */ new Map()
12286
+ );
12287
+ if (stmt.validateOnly) return result2;
12288
+ assertJsonImportHasNoRowIds(materialized);
12289
+ if (prepared.errors.length > 0 && !stmt.onErrorSkip) {
12290
+ const first = prepared.errors[0];
12291
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
12292
+ }
12293
+ if (stmt.onErrorSkip) {
12294
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
12295
+ appendValidationErrors(
12296
+ tempTables,
12297
+ stmt.errorTable,
12298
+ columns,
12299
+ errors,
12300
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12301
+ /* @__PURE__ */ new Map()
12302
+ );
12303
+ }
12304
+ const validParents = prepared.parents.filter((parent) => parent.valid);
12305
+ const fieldTypes = new Map(fieldInfos.map((info) => [info.code, info.fieldType]));
12306
+ const targetIds = validParents.map(() => void 0);
12307
+ if (stmt.keyFields) {
12308
+ for (const key of stmt.keyFields) if (!stmt.fields.includes(key)) {
12309
+ 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`);
12310
+ }
12311
+ const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
12312
+ const sourceKeys = /* @__PURE__ */ new Set();
12313
+ const rowKeys = validParents.map((parent) => stmt.keyFields.map((key) => String(parent.top[key]?.value ?? "")));
12314
+ for (const parts of rowKeys) {
12315
+ const normalized = upsertNormalizedKey(parts, numeric);
12316
+ 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");
12317
+ sourceKeys.add(normalized);
12318
+ }
12319
+ const targetsIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
12320
+ rowKeys.forEach((parts, index) => {
12321
+ targetIds[index] = lookupUpsertTarget(targetsIndex, parts);
12322
+ });
12323
+ }
12324
+ const tableCodes = targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode);
12325
+ const existingById = /* @__PURE__ */ new Map();
12326
+ const updateIds = targetIds.filter((id) => id !== void 0);
12327
+ for (const chunk2 of splitChunks([...new Set(updateIds)], 100)) {
12328
+ const response = await client.getRecords({ app: stmt.appId, query: `$id in (${chunk2.join(",")}) limit 500`, fields: ["$id", "$revision", ...tableCodes] });
12329
+ for (const record of response.records) {
12330
+ const id = Number(record["$id"]?.value);
12331
+ const revision = Number(record["$revision"]?.value);
12332
+ if (Number.isFinite(id)) existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record });
12333
+ }
12334
+ }
12335
+ const writePlan = buildJsonSubtableWritePlan(validParents, targetIds, existingById);
12336
+ const importDetail = {
12337
+ kind: "IMPORT_JSON_SUBTABLE",
12338
+ rowIdPolicy: "DROP_AND_RENUMBER_ALL",
12339
+ parentsToWrite: writePlan.length,
12340
+ insertedParents: writePlan.filter((parent) => parent.mode === "INSERT").length,
12341
+ updatedParents: writePlan.filter((parent) => parent.mode === "UPDATE").length,
12342
+ hasDeletes: writePlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
12343
+ parents: writePlan.map((parent) => ({ parentRow: parent.parentRow, mode: parent.mode, ...parent.targetId === void 0 ? {} : { targetId: parent.targetId }, tables: parent.tables }))
12344
+ };
12345
+ if (writePlan.length > 0) {
12346
+ if (!options.confirm) throw new Error("UnsupportedError: JSON IMPORT subtable mutation requires explicit confirmation detail approval.");
12347
+ const ok = await options.confirm(writePlan.length, stmt.keyFields ? "UPDATE" : "INSERT", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
12348
+ if (!ok) throw new OperationCancelledError(stmt.keyFields ? "UPDATE" : "INSERT", writePlan.length);
12349
+ }
12350
+ const toScalarMap = (record) => new Map(
12351
+ Object.entries(record).map(([code, field]) => [code, field.value])
12352
+ );
12353
+ const payloadFor = (parent) => buildJsonImportRecordPayload(
12354
+ toScalarMap(parent.top),
12355
+ new Map([...parent.subtables].map(([table, rows]) => [table, rows.map((row) => ({ values: toScalarMap(row) }))]))
12356
+ );
12357
+ const inserts = writePlan.filter((parent) => parent.mode === "INSERT");
12358
+ const updates = writePlan.filter((parent) => parent.mode === "UPDATE");
12359
+ const createdIds = [];
12360
+ for (let i = 0; i < inserts.length; i += 100) {
12361
+ const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map(payloadFor) });
12362
+ createdIds.push(response.ids);
12363
+ }
12364
+ for (let i = 0; i < updates.length; i += 100) await client.putRecords({
12365
+ app: stmt.appId,
12366
+ records: updates.slice(i, i + 100).map((parent) => ({ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record: payloadFor(parent) }))
12367
+ });
12368
+ 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 };
12369
+ }
12370
+ if (stmt.writeMode === "UPDATE_RECORD_NUMBER") {
12371
+ return executeImportRecordNumberUpdate(
12372
+ stmt,
12373
+ handle,
12374
+ client,
12375
+ options,
12376
+ cacheContext,
12377
+ tempTables
12378
+ );
12379
+ }
12380
+ const common = {
12381
+ appId: stmt.appId,
12382
+ fields: stmt.fields,
12383
+ select: importPlaceholderSelect(),
12384
+ validateOnly: stmt.validateOnly,
12385
+ validationErrorTable: stmt.validationErrorTable,
12386
+ onErrorSkip: stmt.onErrorSkip,
12387
+ errorTable: stmt.errorTable,
12388
+ rejectLimit: stmt.rejectLimit,
12389
+ checkGroups: stmt.checkGroups
12390
+ };
12391
+ const generated = stmt.keyFields ? { type: "UPSERT_SELECT", ...common, keyFields: stmt.keyFields } : { type: "INSERT_SELECT", ...common };
12392
+ const executionSource = { source: stmt.source, handle, cache: /* @__PURE__ */ new Map() };
12393
+ importSourceByDmlStatement.set(generated, executionSource);
12394
+ const withAudit = (result2) => {
12395
+ if (executionSource.audit) Object.assign(result2, { importAudit: executionSource.audit });
12396
+ return result2;
12397
+ };
12398
+ if (generated.validateOnly) {
12399
+ if (generated.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12400
+ const result2 = await executeDmlValidation(generated, client, { ...options, onLimitReached: "error" }, cacheContext, tempTables, 1);
12401
+ if (generated.validationErrorTable && tempTables) {
12402
+ appendValidationErrors(
12403
+ tempTables,
12404
+ generated.validationErrorTable,
12405
+ result2.columns,
12406
+ result2.errors,
12407
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12408
+ materializedMetaByValidationResult.get(result2) ?? /* @__PURE__ */ new Map()
12409
+ );
12410
+ }
12411
+ return withAudit(result2);
12412
+ }
12413
+ if (generated.onErrorSkip) {
12414
+ if (!tempTables) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
12415
+ const result2 = await (generated.type === "UPSERT_SELECT" ? executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1) : executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1));
12416
+ return withAudit(result2);
12417
+ }
12418
+ const result = await (generated.type === "UPSERT_SELECT" ? executeUpsertSelect(generated, client, options, cacheContext, tempTables) : executeInsertSelect(generated, client, options, cacheContext, tempTables));
12419
+ return withAudit(result);
12420
+ }
12421
+ async function executeCsvSubtableReplacement(stmt, materialized, preparedBase, fieldInfos, client, options, tempTables) {
12422
+ if (!stmt.recordNumberSourceHeader || !stmt.replaceSubtables?.length) throw new Error("InternalError: incomplete CSV subtable replacement AST.");
12423
+ assertNoDuplicateCsvSubtableRowIds(materialized.records);
12424
+ const rawKeys = materialized.records.map((record) => record.recordNumberSourceValue ?? "");
12425
+ const keyPlan = preflightImportRecordNumbers(rawKeys, stmt.recordNumberSourceHeader);
12426
+ const tableCodes = [...stmt.replaceSubtables];
12427
+ const ownershipTableCodes = [...new Set(fieldInfos.filter((info) => !info.inSubtable && info.fieldType === "SUBTABLE").map((info) => info.code))];
12428
+ const allRecords = await fetchAll(client.getRecords, stmt.appId, "", ["$id", "$revision", ...ownershipTableCodes], {
12429
+ maxRecords: options.maxRecords ?? 1e4,
12430
+ parallel: options.fetchParallel ?? 1,
12431
+ onLimit: "error"
12432
+ });
12433
+ const existingById = /* @__PURE__ */ new Map();
12434
+ const ownership = /* @__PURE__ */ new Map();
12435
+ for (const record of allRecords) {
12436
+ const id = Number(record["$id"]?.value);
12437
+ const revision = Number(record["$revision"]?.value);
12438
+ if (!Number.isFinite(id)) continue;
12439
+ existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record });
12440
+ for (const table of ownershipTableCodes) {
12441
+ const rows = record[table]?.value;
12442
+ if (!Array.isArray(rows)) continue;
12443
+ for (const row of rows) if (row.id) {
12444
+ const owners = ownership.get(row.id) ?? [];
12445
+ owners.push({ parentId: id, table });
12446
+ ownership.set(row.id, owners);
12447
+ }
12448
+ }
12449
+ }
12450
+ const targetIds = keyPlan.normalized.map((key) => key === null ? void 0 : Number(key));
12451
+ const parents = preparedBase.parents.map((parent, index) => {
12452
+ const errors2 = [...parent.errors];
12453
+ for (const error of keyPlan.errors[index]) errors2.push({
12454
+ operation: "UPDATE",
12455
+ parentRow: parent.parentRow,
12456
+ field: error.field,
12457
+ code: error.code,
12458
+ message: error.message,
12459
+ sourceValues: materialized.records[index].top
12460
+ });
12461
+ const targetId = targetIds[index];
12462
+ if (targetId !== void 0 && !existingById.has(targetId)) errors2.push({
12463
+ operation: "UPDATE",
12464
+ parentRow: parent.parentRow,
12465
+ field: stmt.recordNumberSourceHeader,
12466
+ code: "ERR_RECORD_NUMBER_NOT_FOUND",
12467
+ message: `record number ${targetId} does not exist in APP${stmt.appId}`,
12468
+ sourceValues: materialized.records[index].top
12469
+ });
12470
+ return { ...parent, valid: errors2.length === 0, errors: errors2 };
12471
+ });
12472
+ const initialPlan = buildCsvSubtableReplacementPlan(materialized.records, parents, targetIds, existingById, ownership);
12473
+ const planErrors = initialPlan.flatMap((parent) => [...parent.errors]);
12474
+ const invalidParentRows = new Set(initialPlan.filter((parent) => !parent.valid).map((parent) => parent.parentRow));
12475
+ const prepared = { ...preparedBase, parents, errors: planErrors, invalidParentRows };
12476
+ assertImportRejectLimit(prepared, stmt.rejectLimit);
12477
+ const validPlan = initialPlan.filter((parent) => parent.valid);
12478
+ const allTables = initialPlan.flatMap((parent) => parent.tables);
12479
+ const sum = (table, key) => allTables.filter((item) => item.table === table).reduce((n, item) => n + Number(item[key]), 0);
12480
+ const tableDetail = Object.fromEntries(tableCodes.map((table) => [table, {
12481
+ existingRows: sum(table, "existingRows"),
12482
+ inputRows: sum(table, "inputRows"),
12483
+ updateRows: sum(table, "updateRows"),
12484
+ addRows: sum(table, "addRows"),
12485
+ deleteRows: sum(table, "deleteRows"),
12486
+ rowIdNotFound: sum(table, "rowIdNotFound")
12487
+ }]));
12488
+ const importDetail = {
12489
+ kind: "IMPORT_CSV_SUBTABLE_REPLACE",
12490
+ rowIdPolicy: "PRESERVE_EXISTING",
12491
+ parentsToWrite: validPlan.length,
12492
+ insertedParents: 0,
12493
+ updatedParents: validPlan.length,
12494
+ hasDeletes: validPlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
12495
+ totalDeleteRows: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.deleteRows, 0),
12496
+ rowIdNotFound: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.rowIdNotFound, 0),
12497
+ invalidParents: invalidParentRows.size,
12498
+ parents: validPlan.map((parent) => ({ parentRow: parent.parentRow, mode: "UPDATE", targetId: parent.targetId, tables: parent.tables }))
12499
+ };
12500
+ const payloadFields = [...new Set(stmt.targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
12501
+ const errors = materializeImportValidationErrors(planErrors, payloadFields);
12502
+ const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
12503
+ if (stmt.validateOnly) {
12504
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12505
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(tempTables, stmt.validationErrorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
12506
+ return {
12507
+ type: "VALIDATION",
12508
+ operation: "UPDATE",
12509
+ validatedRows: parents.length,
12510
+ validRows: parents.length - invalidParentRows.size,
12511
+ invalidRows: invalidParentRows.size,
12512
+ errorCount: errors.length,
12513
+ columns,
12514
+ errors,
12515
+ ...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {},
12516
+ 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 }
12517
+ };
12518
+ }
12519
+ if (planErrors.length && !stmt.onErrorSkip) {
12520
+ const first = planErrors[0];
12521
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
12522
+ }
12523
+ if (stmt.onErrorSkip) {
12524
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
12525
+ appendValidationErrors(tempTables, stmt.errorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
12526
+ }
12527
+ if (validPlan.length) {
12528
+ if (!options.supportsImportConfirmDetail || !options.confirm) throw new Error("UnsupportedError: CSV subtable replacement requires explicit rendered detail approval.");
12529
+ const ok = await options.confirm(validPlan.length, "UPDATE", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
12530
+ if (!ok) throw new OperationCancelledError("UPDATE", validPlan.length);
12531
+ }
12532
+ const scalarMap = (record) => new Map(Object.entries(record).map(([code, field]) => [code, field.value]));
12533
+ for (const parent of validPlan) {
12534
+ 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");
12535
+ await client.putRecords({ app: stmt.appId, records: [{ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record }] });
12536
+ }
12537
+ return { type: "UPDATE", updatedCount: validPlan.length, affectedRows: validPlan.length, skippedRows: invalidParentRows.size, rejectLimit: stmt.rejectLimit, ...stmt.errorTable ? { errTable: stmt.errorTable } : {}, importDetail };
12538
+ }
12539
+ async function executeImportRecordNumberUpdate(stmt, handle, client, options, cacheContext, tempTables) {
12540
+ if (stmt.source.kind !== "CSV" || stmt.source.mappingMode !== "BY_NAME" || !stmt.recordNumberSourceHeader) {
12541
+ throw new Error("InternalError: invalid IMPORT UPDATE AST.");
12542
+ }
12543
+ if (new Set(stmt.fields).size !== stmt.fields.length) throw new Error("ArgumentError: DML target fields contain duplicates.");
12544
+ const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
12545
+ const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
12546
+ const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
12547
+ const sourceTable = materializeCsvDmlSource(
12548
+ stmt.source,
12549
+ payload,
12550
+ options.maxRecords ?? 1e4,
12551
+ stmt.fields,
12552
+ fieldInfos,
12553
+ stmt.recordNumberSourceHeader
12554
+ );
12555
+ const keyValues = sourceTable.recordNumberSourceValues;
12556
+ if (!keyValues) throw new Error("InternalError: record-number source values were not materialized.");
12557
+ const keyPlan = preflightImportRecordNumbers(keyValues, stmt.recordNumberSourceHeader);
12558
+ const matchedIds = /* @__PURE__ */ new Set();
12559
+ const lookupKeys = [...new Set(keyPlan.normalized.filter((key) => key !== null))];
12560
+ for (let i = 0; i < lookupKeys.length; i += 100) {
12561
+ const chunk2 = lookupKeys.slice(i, i + 100);
12562
+ const response = await client.getRecords({
12563
+ app: stmt.appId,
12564
+ query: `$id in (${chunk2.join(",")}) limit 500`,
12565
+ fields: ["$id"]
12566
+ });
12567
+ for (const record of response.records) {
12568
+ const id = record["$id"]?.value;
12569
+ if (typeof id === "string" && id !== "") matchedIds.add(id.replace(/^0+(?=\d)/, ""));
12570
+ }
12571
+ }
12572
+ const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
12573
+ const evaluationTypes = new Map(stmt.fields.map((field) => [field, infoByCode.get(field)?.fieldType ?? "SINGLE_LINE_TEXT"]));
12574
+ const candidates = sourceTable.rows.map((row, index) => {
12575
+ const key = keyPlan.normalized[index];
12576
+ const preErrors = [
12577
+ ...sourceTable.importRowErrors?.[index] ?? [],
12578
+ ...keyPlan.errors[index]
12579
+ ];
12580
+ if (key !== null && !matchedIds.has(key)) preErrors.push({
12581
+ field: stmt.recordNumberSourceHeader,
12582
+ code: "ERR_RECORD_NUMBER_NOT_FOUND",
12583
+ message: `record number ${key} does not exist in APP${stmt.appId}`
12584
+ });
12585
+ return {
12586
+ rowNumber: index + 1,
12587
+ operation: "UPDATE",
12588
+ mode: "update",
12589
+ ...key !== null && matchedIds.has(key) ? { targetId: Number(key) } : {},
12590
+ payload: new Map([
12591
+ [stmt.recordNumberSourceHeader, keyValues[index]],
12592
+ ...stmt.fields.map((field) => [field, row[field] ?? ""])
12593
+ ]),
12594
+ preErrors,
12595
+ record: {},
12596
+ evaluationRow: row,
12597
+ evaluationFieldTypes: evaluationTypes
12598
+ };
12599
+ });
12600
+ const diagnosticFields = [stmt.recordNumberSourceHeader, ...stmt.fields];
12601
+ const validation = validateDmlCandidates(
12602
+ candidates,
12603
+ "UPDATE",
12604
+ diagnosticFields,
12605
+ stmt.fields,
12606
+ fieldInfos,
12607
+ 1,
12608
+ numberPrecision,
12609
+ stmt.checkGroups ?? [],
12610
+ false
12611
+ );
12612
+ const columns = [...diagnosticFields, ...VALIDATION_META_COLUMNS];
12613
+ const validationResult = {
12614
+ type: "VALIDATION",
12615
+ operation: "UPDATE",
12616
+ validatedRows: candidates.length,
12617
+ validRows: candidates.length - validation.invalidRows,
12618
+ invalidRows: validation.invalidRows,
12619
+ errorCount: validation.errors.length,
12620
+ columns,
12621
+ errors: validation.errors,
12622
+ ...stmt.validationErrorTable ?? stmt.errorTable ? { errTable: stmt.validationErrorTable ?? stmt.errorTable } : {}
12623
+ };
12624
+ Object.assign(validationResult, { importAudit: sourceTable.importAudit });
12625
+ if (stmt.validateOnly) {
12626
+ if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
12627
+ if (stmt.validationErrorTable && tempTables) appendValidationErrors(
12628
+ tempTables,
12629
+ stmt.validationErrorTable,
12630
+ columns,
12631
+ validation.errors,
12632
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12633
+ /* @__PURE__ */ new Map()
12634
+ );
12635
+ return validationResult;
12636
+ }
12637
+ if (!stmt.onErrorSkip && validation.invalidRows > 0) {
12638
+ const first = validation.errors[0];
12639
+ throw new Error(`DmlValidationError: ${first.$err_code} ${first.$err_message} (row=${first.$err_row}, field=${first.$err_field})`);
12640
+ }
12641
+ if (stmt.onErrorSkip) {
12642
+ if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
12643
+ appendValidationErrors(
12644
+ tempTables,
12645
+ stmt.errorTable,
12646
+ columns,
12647
+ validation.errors,
12648
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
12649
+ /* @__PURE__ */ new Map()
12650
+ );
12651
+ if (stmt.rejectLimit != null && validation.invalidRows > stmt.rejectLimit) {
12652
+ throw new RejectLimitExceededError(
12653
+ `rejected rows (${validation.invalidRows}) exceed REJECT LIMIT (${stmt.rejectLimit}).`,
12654
+ validationResult
12655
+ );
12656
+ }
12657
+ }
12658
+ const valid = candidates.filter((candidate) => !validation.invalidRowNumbers.has(candidate.rowNumber));
12659
+ if (options.confirm) {
12660
+ const ok = await options.confirm(valid.length, "UPDATE");
12661
+ if (!ok) throw new OperationCancelledError("UPDATE", valid.length);
12662
+ }
12663
+ const updates = valid.map((candidate) => ({ id: candidate.targetId, record: candidate.record }));
12664
+ for (let i = 0; i < updates.length; i += 100) {
12665
+ await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
12666
+ }
12667
+ const result = {
12668
+ type: "UPDATE",
12669
+ updatedCount: updates.length,
12670
+ ...stmt.onErrorSkip ? {
12671
+ affectedRows: updates.length,
12672
+ skippedRows: validation.invalidRows,
12673
+ rejectLimit: stmt.rejectLimit ?? null,
12674
+ errTable: stmt.errorTable
12675
+ } : {}
12676
+ };
12677
+ Object.assign(result, { insertedCount: 0, importAudit: sourceTable.importAudit });
12678
+ return result;
12679
+ }
12680
+ async function materializeDmlSource(stmt, client, options, cacheContext, tempTables, targetFields) {
12681
+ const imported = importSourceByDmlStatement.get(stmt);
12682
+ if (!imported) {
12683
+ 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);
12684
+ return { rows: selected2.rows, columns: selected2.columns, columnMeta: materializedMetaBySelectResult.get(selected2) };
12685
+ }
12686
+ const payload = await loadImportSource(imported.handle, imported.cache);
12687
+ const rowLimit = options.maxRecords ?? 1e4;
12688
+ const targetByCode = new Map((targetFields ?? []).map((info) => [info.code, info]));
12689
+ const jsonTargets = stmt.fields.map((code) => ({ code, fieldType: targetByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }));
12690
+ const raw = imported.source.kind === "JSON" ? materializeJsonDmlSource(imported.source, payload, jsonTargets, rowLimit) : materializeCsvDmlSource(imported.source, payload, rowLimit, stmt.fields, targetFields);
12691
+ imported.audit = raw.importAudit;
12692
+ if (imported.source.kind === "JSON") return raw;
12693
+ if (!imported.source.projection) return raw;
12694
+ const projection = bindImportProjection(imported.source.projection);
12695
+ const tables = new Map(tempTables ?? []);
12696
+ tables.set(IMPORT_PROJECTION_SOURCE, raw);
12697
+ const selected = await executeQueryWithCte(projection, client, { ...options, onLimitReached: "error" }, tables, cacheContext, true);
12698
+ return { rows: selected.rows, columns: selected.columns, columnMeta: materializedMetaBySelectResult.get(selected) };
12699
+ }
12700
+ var dmlSourceMaterializer = { materialize: materializeDmlSource };
12701
+ function assertNoImportRowErrors(table) {
12702
+ for (let rowIndex = 0; rowIndex < (table.importRowErrors?.length ?? 0); rowIndex++) {
12703
+ const first = table.importRowErrors?.[rowIndex]?.[0];
12704
+ if (first) {
12705
+ throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${rowIndex + 1}, field=${first.field})`);
12706
+ }
12707
+ }
12708
+ }
11116
12709
  async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
11117
12710
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
11118
12711
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
11119
12712
  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;
12713
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
12714
+ const { rows, columns } = sourceTable;
12715
+ assertNoImportRowErrors(sourceTable);
11122
12716
  if (columns.length !== stmt.fields.length) {
11123
12717
  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
12718
  throw new Error(
@@ -11130,15 +12724,16 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
11130
12724
  if (!ok) throw new OperationCancelledError("INSERT", rows.length);
11131
12725
  }
11132
12726
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
11133
- const allRecords = rows.map((row) => {
12727
+ const allRecords = rows.map((row, rowIndex) => {
11134
12728
  const record = {};
11135
12729
  stmt.fields.forEach((field, i) => {
12730
+ if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
11136
12731
  const raw = row[columns[i]] ?? "";
11137
12732
  record[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
11138
12733
  });
11139
12734
  return record;
11140
12735
  });
11141
- assertValidDmlRecords(allRecords, stmt.fields, fieldInfos, numberPrecision);
12736
+ allRecords.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
11142
12737
  const createdIds = [];
11143
12738
  for (let i = 0; i < allRecords.length; i += 100) {
11144
12739
  const batch = allRecords.slice(i, i + 100);
@@ -11531,9 +13126,9 @@ function expandRowsForSubtableDml(parents, subtableCode) {
11531
13126
  for (const parent of parents) {
11532
13127
  const parentId = String(parent["$id"]?.value ?? "");
11533
13128
  const parentRevision = getRevision(parent);
11534
- const tableRows = getMutableTableRows(parent, subtableCode);
11535
- for (let i = 0; i < tableRows.length; i++) {
11536
- const row = tableRows[i];
13129
+ const tableRows2 = getMutableTableRows(parent, subtableCode);
13130
+ for (let i = 0; i < tableRows2.length; i++) {
13131
+ const row = tableRows2[i];
11537
13132
  const flat = {
11538
13133
  _pid: parentId,
11539
13134
  _rid: row.id ?? "",
@@ -11739,8 +13334,9 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11739
13334
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
11740
13335
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
11741
13336
  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;
13337
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
13338
+ const { rows, columns } = sourceTable;
13339
+ assertNoImportRowErrors(sourceTable);
11744
13340
  if (columns.length !== stmt.fields.length) {
11745
13341
  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
13342
  throw new Error(
@@ -11754,18 +13350,29 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11754
13350
  }
11755
13351
  const toInsert = [];
11756
13352
  const toUpdate = [];
11757
- const records = rows.map((row) => {
13353
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13354
+ const records = rows.map((row, rowIndex) => {
11758
13355
  const record = {};
11759
13356
  stmt.fields.forEach((field, i) => {
11760
- record[field] = { value: row[columns[i]] ?? "" };
13357
+ if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
13358
+ const raw = row[columns[i]] ?? "";
13359
+ record[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
11761
13360
  });
11762
13361
  return record;
11763
13362
  });
11764
- assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
11765
- const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13363
+ records.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
11766
13364
  const rowKeyValues = records.map(
11767
13365
  (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
11768
13366
  );
13367
+ if (importSourceByDmlStatement.has(stmt)) {
13368
+ const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
13369
+ const sourceKeys = /* @__PURE__ */ new Set();
13370
+ for (const parts of rowKeyValues) {
13371
+ const normalized = upsertNormalizedKey(parts, numericKey);
13372
+ 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");
13373
+ sourceKeys.add(normalized);
13374
+ }
13375
+ }
11769
13376
  const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
11770
13377
  records.forEach((record, rowIdx) => {
11771
13378
  const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
@@ -11814,10 +13421,10 @@ async function executeDescribe(stmt, client, cacheContext) {
11814
13421
  }));
11815
13422
  return { type: "SELECT", rows, columns, rowCount: rows.length };
11816
13423
  }
11817
- function parseSql(sql) {
13424
+ function parseSql(sql, enableImport = false) {
11818
13425
  try {
11819
13426
  const tokens = new Lexer(sql).tokenize();
11820
- const stmt = new Parser(tokens).parse();
13427
+ const stmt = new Parser(tokens, { import: enableImport }).parse();
11821
13428
  validateKlikeStatement(stmt);
11822
13429
  return stmt;
11823
13430
  } catch (e) {
@@ -12073,8 +13680,8 @@ function explainMetadataLines(analysis) {
12073
13680
  ...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
12074
13681
  ];
12075
13682
  }
12076
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2) {
12077
- const statements = parseSqlBatch(sql);
13683
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2, enableImport = false) {
13684
+ const statements = parseSqlBatch(sql, enableImport);
12078
13685
  const analysis = analyzeBatch(statements);
12079
13686
  validateDeclaredBatchVariables(statements, injectedVariables);
12080
13687
  const variables = /* @__PURE__ */ new Map();
@@ -12233,6 +13840,79 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
12233
13840
  if (query.type === "DELETE") return buildDeletePlan(query, label);
12234
13841
  if (query.type === "REORDER") return buildReorderPlan(query, label);
12235
13842
  if (query.type === "VALIDATE") return buildValidatePlan(query, label);
13843
+ if (query.type === "IMPORT") {
13844
+ if (query.writeMode === "UPDATE_RECORD_NUMBER") {
13845
+ const csvTables = query.targets?.filter((target) => target.kind === "SUBTABLE") ?? [];
13846
+ return [
13847
+ ...label ? [label] : [],
13848
+ `IMPORT UPDATE INTO APP${query.appId}`,
13849
+ ` writeMode: UPDATE_RECORD_NUMBER`,
13850
+ ` source: CSV ${query.source.sourceName}`,
13851
+ ` keyHeader: ${query.recordNumberSourceHeader}`,
13852
+ ` mapping: BY_NAME`,
13853
+ ` parentRows: requires source load`,
13854
+ ` duplicate: preflight before lookup/write`,
13855
+ ` matched: requires lookup`,
13856
+ ` unmatched: requires lookup`,
13857
+ ` invalid: requires source load`,
13858
+ ` requiresLookup:true`,
13859
+ ` inserted: 0`,
13860
+ ` keyInPayload: false`,
13861
+ ...csvTables.length ? [
13862
+ ` replaceSubtables: ${query.replaceSubtables?.join(", ") ?? "ERROR: required"}`,
13863
+ ` subtableRowIdPolicy: PRESERVE existing; empty/unknown add without id`,
13864
+ ` rowIdOwnership: owned elsewhere invalidates the parent`,
13865
+ ` replacementDiff: existing/input/update/add/delete/rowIdNotFound requires actual-data preflight`,
13866
+ ` confirmPolicy: highest warning "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FBN\u884C\u524A\u9664" plus per-table detail (including delete=0)`
13867
+ ] : [],
13868
+ ` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
13869
+ ` gate: enabled for this parse`,
13870
+ ` writesKintone: ${query.validateOnly ? "false" : "true"}`
13871
+ ];
13872
+ }
13873
+ const mode = query.keyFields ? "UPSERT" : "INSERT";
13874
+ const hasSubtables = query.targets?.some((target) => target.kind === "SUBTABLE") === true;
13875
+ return [
13876
+ ...label ? [label] : [],
13877
+ `IMPORT ${mode} INTO APP${query.appId}`,
13878
+ ` source: ${query.source.kind} ${query.source.sourceName}`,
13879
+ ` sourceFormat: ${query.source.kind}`,
13880
+ ` encoding: ${query.source.kind === "JSON" ? "UTF8 only" : query.source.encoding ?? "UTF8 (or loader metadata)"}`,
13881
+ ` mapping: ${query.source.kind === "JSON" ? "BY NAME (INTO order)" : query.source.projection ? "SELECT expressions" : query.source.mappingMode}`,
13882
+ ...query.source.kind === "JSON" ? [
13883
+ ` duplicateKeyPolicy: reject`,
13884
+ ` numberLexemePolicy: preserve; JSON number accepts safe integer only`,
13885
+ ` precisionTargetsRequireString: true`,
13886
+ ` unknownKeyPolicy: reject`,
13887
+ ` presenceAware: true`,
13888
+ ...hasSubtables ? [
13889
+ ` subtableRowIdPolicy: reject _rid/id; DROP IDs and renumber every input row`,
13890
+ ` subtableUpdatePolicy: present table replaces all rows; missing table is preserved; [] deletes all rows`,
13891
+ ` confirmPolicy: parent/table existing/input/add/delete detail required; delete is highest warning`
13892
+ ] : []
13893
+ ] : [
13894
+ ` header: ${query.source.hasHeader ? "HEADER" : "NO HEADER"}`,
13895
+ ...query.source.mappingMode === "BY_NAME" ? [
13896
+ ` writtenColumns: ${query.fields.join(", ")}`,
13897
+ ` knownExportColumns: audit and ignore with reason/non-empty count`,
13898
+ ` unknownColumnPolicy: ${query.source.ignoreUnknownColumns ? "ignore with audit/non-empty count" : "ERR_IMPORT_UNKNOWN_COLUMN"}`,
13899
+ ` multipleValueDelimiter: LF (CRLF or LF)`,
13900
+ ` sourceValueMode: string-preserving`,
13901
+ ` roundTripNumericGuarantee: exact CSV lexeme passes strict decimal validation`,
13902
+ ` FILE: audit-ignore unless named in INTO (analyze error)`
13903
+ ] : []
13904
+ ],
13905
+ ` sourceLimit: 10485760 bytes / ${query.fields.length} target columns`,
13906
+ ` key: ${query.keyFields?.join(", ") ?? "none"}`,
13907
+ ` checks: ${query.checkGroups?.length ?? 0}`,
13908
+ ` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
13909
+ ` gate: enabled for this parse`,
13910
+ ` preflight: ${query.validateOnly && hasSubtables ? "requires actual source load at execution; this EXPLAIN is static" : "requires load"}`,
13911
+ ...hasSubtables ? [query.source.kind === "JSON" ? ` Phase5C: JSON mutation requires detail-capable confirmation surface` : ` Phase5D: CSV mutation requires detail-capable confirmation surface`] : [],
13912
+ ` writesKintone: ${query.validateOnly ? "false" : "true"}`,
13913
+ ` duplicateKey: preflight before lookup/write (requires load)`
13914
+ ];
13915
+ }
12236
13916
  return buildSelectPlan(query, label, capabilities, orderPlans);
12237
13917
  }
12238
13918
  function buildValidatePlan(stmt, label) {
@@ -12686,15 +14366,15 @@ var OperationCancelledError = class extends Error {
12686
14366
  };
12687
14367
 
12688
14368
  // src/core/sql.ts
12689
- function parseSqlStatement(sql) {
14369
+ function parseSqlStatement(sql, capabilities = {}) {
12690
14370
  const tokens = new Lexer(sql).tokenize();
12691
- const stmt = new Parser(tokens).parse();
14371
+ const stmt = new Parser(tokens, capabilities).parse();
12692
14372
  validateKlikeStatement(stmt);
12693
14373
  return stmt;
12694
14374
  }
12695
- function parseSqlStatements(sql) {
14375
+ function parseSqlStatements(sql, capabilities = {}) {
12696
14376
  const tokens = new Lexer(sql).tokenize();
12697
- const statements = new Parser(tokens).parseStatements();
14377
+ const statements = new Parser(tokens, capabilities).parseStatements();
12698
14378
  statements.forEach(validateKlikeStatement);
12699
14379
  return statements;
12700
14380
  }
@@ -12861,7 +14541,8 @@ function buildBatchEnvelope(batch, options = {}) {
12861
14541
  validRows: s.result.validRows,
12862
14542
  invalidRows: s.result.invalidRows,
12863
14543
  errorCount: s.result.errorCount,
12864
- ...s.result.errTable ? { errTable: s.result.errTable } : {}
14544
+ ...s.result.errTable ? { errTable: s.result.errTable } : {},
14545
+ ...s.result.importDetail ? { importDetail: s.result.importDetail } : {}
12865
14546
  });
12866
14547
  } else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
12867
14548
  Object.assign(entry, toMutationSummary(s.result));
@@ -13164,7 +14845,7 @@ function clampInt(v, min, max) {
13164
14845
  function flattenFormFieldProperties(properties) {
13165
14846
  return flattenFields(properties, collectLookupCopyFields(properties));
13166
14847
  }
13167
- function flattenFields(properties, lookupCopyFields, inSubtable = false) {
14848
+ function flattenFields(properties, lookupCopyFields, inSubtable = false, subtableCode) {
13168
14849
  const out = [];
13169
14850
  for (const field of Object.values(properties)) {
13170
14851
  const optionOrder = toOptionOrderMap(field.options);
@@ -13182,11 +14863,12 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
13182
14863
  maxLength: normalizeConstraintValue(field.maxLength),
13183
14864
  defaultValue: field.defaultValue,
13184
14865
  inSubtable,
14866
+ ...subtableCode ? { subtableCode } : {},
13185
14867
  writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
13186
14868
  };
13187
14869
  info.semantics = resolveFieldSemantics(info);
13188
14870
  out.push(info);
13189
- if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
14871
+ if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true, field.type === "SUBTABLE" ? field.code : subtableCode));
13190
14872
  }
13191
14873
  return out;
13192
14874
  }
@@ -14170,6 +15852,17 @@ function restoreSqlContextError(err, sourceSql, context) {
14170
15852
  return err;
14171
15853
  }
14172
15854
 
15855
+ // src/import/importGateError.ts
15856
+ var IMPORT_CAPABILITY_GATE_MARKER = "capability is disabled";
15857
+ function errorMessage(error) {
15858
+ if (error instanceof Error) return error.message;
15859
+ if (typeof error === "string") return error;
15860
+ return null;
15861
+ }
15862
+ function isImportCapabilityGateError(error) {
15863
+ return errorMessage(error)?.includes(IMPORT_CAPABILITY_GATE_MARKER) === true;
15864
+ }
15865
+
14173
15866
  // src/cli/index.ts
14174
15867
  var HELP_TEXT = `ksql - Execute SQL against kintone apps
14175
15868
 
@@ -14184,6 +15877,8 @@ Options:
14184
15877
  --console Start interactive console mode
14185
15878
  --dry-run Parse and show execution plan only
14186
15879
  --var <name=value> Override a DECLARE variable (repeatable; not for secrets)
15880
+ --import-csv <name=path> Supply named CSV and enable IMPORT (repeatable)
15881
+ --import-json <name=path> Supply named JSON and enable IMPORT (repeatable)
14187
15882
  --format <type> Output format: table | json | jsonl | csv | markdown | md
14188
15883
  (batch + json: prints one JSON envelope for the whole batch)
14189
15884
  --max-records <n> Max records to fetch (default: 500)
@@ -14231,6 +15926,15 @@ Options:
14231
15926
  -h, --help Show help
14232
15927
  -v, --version Show version
14233
15928
  `;
15929
+ 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";
15930
+ function toCliImportError(error, importEnabled) {
15931
+ if (importEnabled || !isImportCapabilityGateError(error)) return error;
15932
+ if (error instanceof Error) {
15933
+ error.message = CLI_IMPORT_SOURCE_REQUIRED_MESSAGE;
15934
+ return error;
15935
+ }
15936
+ return CLI_IMPORT_SOURCE_REQUIRED_MESSAGE;
15937
+ }
14234
15938
  function parseArgs(argv) {
14235
15939
  const out = {
14236
15940
  help: false,
@@ -14282,7 +15986,9 @@ function parseArgs(argv) {
14282
15986
  arrayFormat: null,
14283
15987
  tableFormat: null,
14284
15988
  dateFormat: null,
14285
- attachmentFormat: null
15989
+ attachmentFormat: null,
15990
+ importCsv: /* @__PURE__ */ Object.create(null),
15991
+ importJson: /* @__PURE__ */ Object.create(null)
14286
15992
  };
14287
15993
  for (let i = 0; i < argv.length; i++) {
14288
15994
  const a = argv[i];
@@ -14364,6 +16070,28 @@ function parseArgs(argv) {
14364
16070
  i++;
14365
16071
  continue;
14366
16072
  }
16073
+ if (a === "--import-csv") {
16074
+ const raw = v ?? "";
16075
+ const eq = raw.indexOf("=");
16076
+ if (eq <= 0 || eq === raw.length - 1) throw new Error("ArgumentError: --import-csv must use name=path.");
16077
+ const name = raw.slice(0, eq);
16078
+ 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.`);
16079
+ out.importCsv[name] = raw.slice(eq + 1);
16080
+ i++;
16081
+ continue;
16082
+ }
16083
+ if (a === "--import-json") {
16084
+ const raw = v ?? "";
16085
+ const eq = raw.indexOf("=");
16086
+ if (eq <= 0 || eq === raw.length - 1) throw new Error("ArgumentError: --import-json must use name=path.");
16087
+ const name = raw.slice(0, eq);
16088
+ if (Object.prototype.hasOwnProperty.call(out.importJson, name) || Object.prototype.hasOwnProperty.call(out.importCsv, name)) {
16089
+ throw new Error(`ArgumentError: import source "${name}" is specified more than once.`);
16090
+ }
16091
+ out.importJson[name] = raw.slice(eq + 1);
16092
+ i++;
16093
+ continue;
16094
+ }
14367
16095
  if (a === "-e" || a === "--execute") {
14368
16096
  out.executeSql = v ?? "";
14369
16097
  i++;
@@ -15511,15 +17239,16 @@ async function run() {
15511
17239
  `);
15512
17240
  return 2;
15513
17241
  }
17242
+ const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
15514
17243
  try {
15515
- const statements = parseSqlStatements(sql);
17244
+ const statements = parseSqlStatements(sql, { import: importEnabled });
15516
17245
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
15517
17246
  if (statements.length > 1) {
15518
17247
  batchAnalysis = analyzeBatch(statements);
15519
17248
  isBatchSql = true;
15520
17249
  batchContainsDml = batchAnalysis.containsDml;
15521
17250
  } else {
15522
- const stmt = parseSqlStatement(sql);
17251
+ const stmt = parseSqlStatement(sql, { import: importEnabled });
15523
17252
  parsedStmt = stmt;
15524
17253
  stmtType = getStatementType(stmt);
15525
17254
  isDmlStatement = writesKintone(stmt);
@@ -15537,7 +17266,8 @@ async function run() {
15537
17266
  bindings: sqlDiagnosticContext.appBindingByMappedApp,
15538
17267
  rewriteSegments: sqlDiagnosticContext.rewriteSegments
15539
17268
  }) : err;
15540
- process.stderr.write(`${restored instanceof Error ? restored.message : String(restored)}
17269
+ const surfaced = toCliImportError(restored, importEnabled);
17270
+ process.stderr.write(`${surfaced instanceof Error ? surfaced.message : String(surfaced)}
15541
17271
  `);
15542
17272
  return 1;
15543
17273
  }
@@ -15894,7 +17624,8 @@ async function run() {
15894
17624
  args.variables,
15895
17625
  cacheContext,
15896
17626
  maxRecords,
15897
- cursorMaxActive
17627
+ cursorMaxActive,
17628
+ Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0
15898
17629
  );
15899
17630
  const out = [];
15900
17631
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -15933,10 +17664,30 @@ async function run() {
15933
17664
  }
15934
17665
  }
15935
17666
  }
15936
- const confirm = async (count, operation) => {
17667
+ const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
17668
+ const importSource = importEnabled ? (name) => {
17669
+ const sourcePath = args.importCsv[name] ?? args.importJson[name];
17670
+ return sourcePath === void 0 ? void 0 : { load: async () => ({ bytes: new Uint8Array((0, import_fs2.readFileSync)(sourcePath)) }) };
17671
+ } : void 0;
17672
+ const confirm = async (count, operation, context) => {
15937
17673
  if (count > dmlMaxRows) {
15938
17674
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15939
17675
  }
17676
+ if (context?.importDetail) {
17677
+ const detail = context.importDetail;
17678
+ const csv = detail.kind === "IMPORT_CSV_SUBTABLE_REPLACE";
17679
+ const lines = [
17680
+ ...csv ? [`\u3010\u6700\u91CD\u8981\u8B66\u544A\u3011\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FB${detail.totalDeleteRows}\u884C\u524A\u9664`] : [],
17681
+ `[IMPORT ${csv ? "CSV" : "JSON"} Confirm] parentsToWrite=${detail.parentsToWrite} insert=${detail.insertedParents} update=${detail.updatedParents}`,
17682
+ csv ? `rowIdPolicy=PRESERVE_EXISTING rowIdNotFound=${detail.rowIdNotFound} invalidParents=${detail.invalidParents}` : `rowIdPolicy=DROP_AND_RENUMBER_ALL (JSON child rows are all newly numbered)`,
17683
+ ...!csv && detail.hasDeletes ? ["WARNING: existing subtable rows will be deleted/replaced."] : [],
17684
+ ...detail.parents.flatMap((parent) => parent.tables.map(
17685
+ (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}` : ""}`
17686
+ ))
17687
+ ];
17688
+ process.stderr.write(`${lines.join("\n")}
17689
+ `);
17690
+ }
15940
17691
  if (yes) return true;
15941
17692
  if (args.console) return true;
15942
17693
  const label = sql?.replace(/\s+/g, " ").trim() ?? operation;
@@ -15961,10 +17712,20 @@ query=${label}`);
15961
17712
  timeoutMs: timeout,
15962
17713
  cursorMaxActive,
15963
17714
  variables: args.variables,
15964
- confirm: batchContainsDml ? async (count, operation) => {
17715
+ enableImport: importEnabled,
17716
+ importSource,
17717
+ supportsImportConfirmDetail: true,
17718
+ confirm: batchContainsDml ? async (count, operation, context) => {
15965
17719
  if (count > dmlMaxRows) {
15966
17720
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15967
17721
  }
17722
+ if (context?.importDetail) {
17723
+ const importDetail = context.importDetail;
17724
+ 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
17725
+ `);
17726
+ process.stderr.write(`[IMPORT ${importDetail.kind === "IMPORT_CSV_SUBTABLE_REPLACE" ? "CSV" : "JSON"} Confirm] ${JSON.stringify(importDetail)}
17727
+ `);
17728
+ }
15968
17729
  return true;
15969
17730
  } : void 0
15970
17731
  });
@@ -15974,14 +17735,19 @@ query=${label}`);
15974
17735
  maxRecords,
15975
17736
  onLimitReached: onLimit,
15976
17737
  cacheContext,
15977
- cursorMaxActive
17738
+ cursorMaxActive,
17739
+ enableImport: importEnabled,
17740
+ importSource
15978
17741
  }) : await execute(sql, client, {
15979
17742
  maxRecords,
15980
17743
  fetchParallel,
15981
17744
  onLimitReached: effectiveOnLimit,
15982
17745
  confirm: isDmlStatement ? confirm : void 0,
15983
17746
  cacheContext,
15984
- cursorMaxActive
17747
+ cursorMaxActive,
17748
+ enableImport: importEnabled,
17749
+ importSource,
17750
+ supportsImportConfirmDetail: true
15985
17751
  });
15986
17752
  if (args.dryRun && sqlDiagnosticContext) {
15987
17753
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
@@ -16049,6 +17815,7 @@ if (isDirectCliRun()) {
16049
17815
  }
16050
17816
  // Annotate the CommonJS export names for ESM import in node:
16051
17817
  0 && (module.exports = {
17818
+ CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
16052
17819
  HELP_TEXT,
16053
17820
  buildBatchDmlConfirmMessage,
16054
17821
  buildBatchStatementSummary,
@@ -16065,5 +17832,6 @@ if (isDirectCliRun()) {
16065
17832
  parseTokenMap,
16066
17833
  runWithArgv,
16067
17834
  shouldExitOnEmpty,
17835
+ toCliImportError,
16068
17836
  writeBatchOutput
16069
17837
  });