@rex0220/kintone-sql-tools 3.4.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;
@@ -744,13 +747,20 @@ var Parser = class {
744
747
  if (upper === "CREATE") return this.parseCreateTempTable();
745
748
  if (upper === "DROP") return this.parseDropTempTable();
746
749
  if (upper === "DECLARE") return this.parseDeclareVariable();
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
+ }
747
757
  break;
748
758
  }
749
759
  default:
750
760
  break;
751
761
  }
752
762
  throw new ParseError(
753
- "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
763
+ "SELECT / INSERT / UPDATE / DELETE / REORDER / VALIDATE / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
754
764
  tok
755
765
  );
756
766
  }
@@ -761,7 +771,7 @@ var Parser = class {
761
771
  this.expect("SET" /* SET */);
762
772
  const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
763
773
  this.expect("=" /* EQ */);
764
- const expr = this.parseScalarExpr("SET", true);
774
+ const expr = this.peek().kind === "[" /* LBRACKET */ ? this.parseArrayLiteral() : this.parseScalarExpr("SET", true);
765
775
  return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
766
776
  }
767
777
  parseDeclareVariable() {
@@ -922,11 +932,265 @@ var Parser = class {
922
932
  query = this.parseDelete();
923
933
  } else if (tok.kind === "REORDER" /* REORDER */) {
924
934
  query = this.parseReorder();
935
+ } else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
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();
925
942
  } else {
926
- throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER \u304C\u5FC5\u8981\u3067\u3059", tok);
943
+ throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
927
944
  }
928
945
  return { type: "EXPLAIN", query };
929
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
+ }
1144
+ /** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
1145
+ parseValidate() {
1146
+ const validateTok = this.advance();
1147
+ const name = this.parseIdentifier();
1148
+ const { appId, subtableCode } = extractTableRef(name, this.prev());
1149
+ if (subtableCode) {
1150
+ throw new ParseError("VALIDATE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3092\u5BFE\u8C61\u306B\u3067\u304D\u307E\u305B\u3093", this.prev());
1151
+ }
1152
+ let fields;
1153
+ if (this.consume("(" /* LPAREN */)) {
1154
+ fields = this.parseIdentList();
1155
+ this.expect(")" /* RPAREN */);
1156
+ }
1157
+ const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
1158
+ const checks = this.parseCheckGroups();
1159
+ let errorTable;
1160
+ if (this.consume("INTO" /* INTO */)) {
1161
+ const tableTok = this.peek();
1162
+ if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
1163
+ throw new ParseError("VALIDATE INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tableTok);
1164
+ }
1165
+ errorTable = this.parseTableName();
1166
+ }
1167
+ const stmt = { type: "VALIDATE", appId, fields, where, ...checks, ...errorTable ? { errorTable } : {} };
1168
+ this.assertValidateExpressions(stmt, validateTok);
1169
+ return stmt;
1170
+ }
1171
+ /** v1 VALIDATE is single-app/local: subqueries and qualified references are rejected. */
1172
+ assertValidateExpressions(stmt, tok) {
1173
+ const visit = (node) => {
1174
+ if (Array.isArray(node)) {
1175
+ node.forEach(visit);
1176
+ return;
1177
+ }
1178
+ if (node === null || typeof node !== "object") return;
1179
+ const obj = node;
1180
+ if (obj.type === "EXISTS" || obj.type === "SUBQUERY_IN_LIST" || obj.type === "SCALAR_SUBQUERY") {
1181
+ throw new ParseError("VALIDATE \u306E WHERE / CHECK \u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
1182
+ }
1183
+ if (obj.type === "FIELD" && obj.tableAlias !== null && obj.tableAlias !== void 0) {
1184
+ throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
1185
+ }
1186
+ if (obj.type === "FIELD_REF" && typeof obj.field === "string" && obj.field.includes(".")) {
1187
+ throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
1188
+ }
1189
+ Object.values(obj).forEach(visit);
1190
+ };
1191
+ visit(stmt.where);
1192
+ visit(stmt.checkGroups);
1193
+ }
930
1194
  // ----------------------------------------------------------
931
1195
  // ASSERT
932
1196
  //
@@ -1212,6 +1476,17 @@ var Parser = class {
1212
1476
  const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
1213
1477
  return { type: "SCALAR_VALUE_COL", expr, alias: alias2 };
1214
1478
  }
1479
+ if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
1480
+ const variable = this.advance();
1481
+ if (!this.consume("AS" /* AS */)) {
1482
+ throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
1483
+ }
1484
+ return {
1485
+ type: "VARIABLE_COL",
1486
+ name: variable.value.slice(1).toLowerCase(),
1487
+ alias: this.parseAliasName()
1488
+ };
1489
+ }
1215
1490
  const windowFunc = this.tryWindowFunc();
1216
1491
  if (windowFunc !== null) {
1217
1492
  return this.parseWindowColumn(windowFunc);
@@ -2027,9 +2302,7 @@ var Parser = class {
2027
2302
  }
2028
2303
  if (this.consume("NOT" /* NOT */)) {
2029
2304
  if (this.consume("IN" /* IN */)) {
2030
- this.expect("(" /* LPAREN */);
2031
- const right2 = this.parseInListOrSubquery();
2032
- this.expect(")" /* RPAREN */);
2305
+ const right2 = this.parseInRight();
2033
2306
  return { type: "BINARY", op: "NOT_IN", left: field, right: right2 };
2034
2307
  }
2035
2308
  if (this.consume("LIKE" /* LIKE */)) {
@@ -2046,9 +2319,7 @@ var Parser = class {
2046
2319
  );
2047
2320
  }
2048
2321
  if (this.consume("IN" /* IN */)) {
2049
- this.expect("(" /* LPAREN */);
2050
- const right2 = this.parseInListOrSubquery();
2051
- this.expect(")" /* RPAREN */);
2322
+ const right2 = this.parseInRight();
2052
2323
  return { type: "BINARY", op: "IN", left: field, right: right2 };
2053
2324
  }
2054
2325
  if (this.consume("KLIKE" /* KLIKE */)) {
@@ -2215,6 +2486,18 @@ var Parser = class {
2215
2486
  );
2216
2487
  }
2217
2488
  // IN (...) — 値リストまたはサブクエリ
2489
+ parseInRight() {
2490
+ if (this.consume("(" /* LPAREN */)) {
2491
+ const right = this.parseInListOrSubquery();
2492
+ this.expect(")" /* RPAREN */);
2493
+ return right;
2494
+ }
2495
+ const variable = this.expect(
2496
+ "VARIABLE" /* VARIABLE */,
2497
+ "IN / NOT IN \u306E\u5F8C\u306B\u306F (\u5024\u30EA\u30B9\u30C8\u307E\u305F\u306F SELECT) \u304B\u914D\u5217\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059"
2498
+ );
2499
+ return { type: "VARIABLE_IN_LIST", name: variable.value.slice(1).toLowerCase() };
2500
+ }
2218
2501
  parseInListOrSubquery() {
2219
2502
  if (this.peek().kind === "SELECT" /* SELECT */) {
2220
2503
  const query = this.parseSelect();
@@ -3022,10 +3305,10 @@ function getStatementType(stmt) {
3022
3305
  return typeof obj.type === "string" ? obj.type : "UNKNOWN";
3023
3306
  }
3024
3307
  function isDmlType(type) {
3025
- 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";
3026
3309
  }
3027
3310
  function isReadOnlyType(type) {
3028
- return type === "SELECT" || 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";
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";
3029
3312
  }
3030
3313
  function writesKintone(stmt) {
3031
3314
  return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
@@ -3036,6 +3319,8 @@ function isReadOnlyStatement(stmt) {
3036
3319
  function requiresCompleteInput(stmt) {
3037
3320
  if (isDmlType(stmt.type)) return true;
3038
3321
  switch (stmt.type) {
3322
+ case "VALIDATE":
3323
+ return true;
3039
3324
  case "SELECT":
3040
3325
  return selectRequiresCompleteInput(stmt);
3041
3326
  case "UNION":
@@ -3076,6 +3361,7 @@ function whereRequiresCompleteInput(where) {
3076
3361
  case "EXISTS":
3077
3362
  return selectRequiresCompleteInput(where.query);
3078
3363
  case "NULL_CHECK":
3364
+ case "BOOLEAN":
3079
3365
  return false;
3080
3366
  }
3081
3367
  }
@@ -3111,6 +3397,8 @@ function collectDmlTargetFields(stmt) {
3111
3397
  // src/engine/pushDownNot.ts
3112
3398
  function pushDownNot(expr) {
3113
3399
  switch (expr.type) {
3400
+ case "BOOLEAN":
3401
+ return { type: "BOOLEAN", value: !expr.value };
3114
3402
  case "BINARY": {
3115
3403
  const negated = negateOp(expr.op);
3116
3404
  if (negated === null) {
@@ -3190,6 +3478,7 @@ function whereHasLike(where) {
3190
3478
  case "BINARY":
3191
3479
  case "NULL_CHECK":
3192
3480
  case "EXISTS":
3481
+ case "BOOLEAN":
3193
3482
  return false;
3194
3483
  }
3195
3484
  }
@@ -3205,6 +3494,7 @@ function whereHasKlike(where) {
3205
3494
  case "BINARY":
3206
3495
  case "NULL_CHECK":
3207
3496
  case "EXISTS":
3497
+ case "BOOLEAN":
3208
3498
  return false;
3209
3499
  }
3210
3500
  }
@@ -3224,6 +3514,8 @@ function whereToKintone(expr) {
3224
3514
  return convertGroup(expr);
3225
3515
  case "EXISTS":
3226
3516
  throw new KintoneQueryError("EXISTS \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
3517
+ case "BOOLEAN":
3518
+ throw new KintoneQueryError("internal error: BOOLEAN predicate reached kintone query conversion");
3227
3519
  }
3228
3520
  }
3229
3521
  function convertBinary(expr) {
@@ -3302,6 +3594,8 @@ function convertValue(value, op) {
3302
3594
  switch (value.type) {
3303
3595
  case "VARIABLE":
3304
3596
  throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
3597
+ case "VARIABLE_IN_LIST":
3598
+ throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
3305
3599
  case "STRING":
3306
3600
  return convertString(value);
3307
3601
  case "NUMBER":
@@ -3381,6 +3675,8 @@ function resolveSelectMode(stmt) {
3381
3675
  function whereRequiresJsEval(where) {
3382
3676
  if (where === null) return false;
3383
3677
  switch (where.type) {
3678
+ case "BOOLEAN":
3679
+ return true;
3384
3680
  case "BINARY":
3385
3681
  return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY" || isLike(where);
3386
3682
  case "NULL_CHECK":
@@ -3775,6 +4071,7 @@ function collectRequiredFieldsByTable(stmt) {
3775
4071
  walkWhere(where.expr, phase);
3776
4072
  return;
3777
4073
  case "EXISTS":
4074
+ case "BOOLEAN":
3778
4075
  return;
3779
4076
  }
3780
4077
  };
@@ -3813,6 +4110,8 @@ function collectRequiredFieldsByTable(stmt) {
3813
4110
  break;
3814
4111
  case "LITERAL_COL":
3815
4112
  break;
4113
+ case "VARIABLE_COL":
4114
+ throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
3816
4115
  case "AGGREGATE":
3817
4116
  if (col.arg.type !== "WILDCARD") walkArith(col.arg, "select");
3818
4117
  break;
@@ -3988,6 +4287,7 @@ function stripCteAlias(where, alias) {
3988
4287
  case "GROUP":
3989
4288
  return { ...where, expr: stripCteAlias(where.expr, alias) };
3990
4289
  case "EXISTS":
4290
+ case "BOOLEAN":
3991
4291
  return where;
3992
4292
  }
3993
4293
  }
@@ -4025,6 +4325,7 @@ function extractAndLeaves(where, accept) {
4025
4325
  case "NULL_CHECK":
4026
4326
  case "NOT":
4027
4327
  case "EXISTS":
4328
+ case "BOOLEAN":
4028
4329
  return null;
4029
4330
  }
4030
4331
  }
@@ -4163,6 +4464,7 @@ function collectKlikes(where, out) {
4163
4464
  case "BINARY":
4164
4465
  case "NULL_CHECK":
4165
4466
  case "EXISTS":
4467
+ case "BOOLEAN":
4166
4468
  return;
4167
4469
  }
4168
4470
  }
@@ -4219,6 +4521,11 @@ function validateStatement(stmt) {
4219
4521
  );
4220
4522
  }
4221
4523
  return;
4524
+ case "VALIDATE":
4525
+ if (containsKlike(stmt)) {
4526
+ throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F VALIDATE \u306E WHERE / CHECK \u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
4527
+ }
4528
+ return;
4222
4529
  case "SHOW_APPS":
4223
4530
  case "DESCRIBE":
4224
4531
  case "DROP_TEMP_TABLE":
@@ -4306,6 +4613,8 @@ function isDescendantOf(root, target) {
4306
4613
  case "NULL_CHECK":
4307
4614
  case "EXISTS":
4308
4615
  return false;
4616
+ case "BOOLEAN":
4617
+ return false;
4309
4618
  }
4310
4619
  }
4311
4620
  function walkWithoutNestedSelects(node, visitWhere) {
@@ -4367,8 +4676,12 @@ function collectVariableRefs(node, refs) {
4367
4676
  }
4368
4677
  if (node !== null && typeof node === "object") {
4369
4678
  const obj = node;
4370
- if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
4371
- refs.add(obj["name"]);
4679
+ const type = obj["type"];
4680
+ if ((type === "VARIABLE" || type === "VARIABLE_COL" || type === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") {
4681
+ refs.push({
4682
+ name: obj["name"],
4683
+ kind: type === "VARIABLE" ? "scalar" : type === "VARIABLE_COL" ? "select-column" : "array-in-list"
4684
+ });
4372
4685
  return;
4373
4686
  }
4374
4687
  for (const v of Object.values(obj)) collectVariableRefs(v, refs);
@@ -4409,9 +4722,9 @@ function analyzeBatch(statements) {
4409
4722
  const variableDefs = /* @__PURE__ */ new Map();
4410
4723
  const variableOrder = [];
4411
4724
  statements.forEach((stmt, index) => {
4412
- const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
4725
+ const validationTable = stmt.type === "VALIDATE" && stmt.errorTable ? stmt.errorTable : "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
4413
4726
  if (statements.length === 1 && validationTable) {
4414
- const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
4727
+ const message = stmt.type === "VALIDATE" ? "ArgumentError: VALIDATE INTO requires a batch." : "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
4415
4728
  throw new BatchAnalysisError(message, index);
4416
4729
  }
4417
4730
  const statementType = getStatementType(stmt);
@@ -4420,23 +4733,43 @@ function analyzeBatch(statements) {
4420
4733
  const refs = /* @__PURE__ */ new Set();
4421
4734
  const stmtAppIds = /* @__PURE__ */ new Set();
4422
4735
  const dependsOn = /* @__PURE__ */ new Set();
4423
- const variableRefs = /* @__PURE__ */ new Set();
4736
+ const variableRefs = [];
4424
4737
  collectVariableRefs(stmt, variableRefs);
4425
- for (const name of variableRefs) {
4426
- const def = variableDefs.get(name);
4738
+ const referencedThisStatement = /* @__PURE__ */ new Set();
4739
+ for (const use of variableRefs) {
4740
+ const def = variableDefs.get(use.name);
4427
4741
  if (def === void 0) {
4428
4742
  throw new BatchAnalysisError(
4429
- `ParseError: variable @${name} is not defined before statement ${index + 1}.`,
4743
+ `ParseError: variable @${use.name} is not defined before statement ${index + 1}.`,
4430
4744
  index
4431
4745
  );
4432
4746
  }
4433
- def.referencedBy.push(index);
4747
+ if (def.kind === "scalar" && use.kind === "array-in-list") {
4748
+ throw new BatchAnalysisError(
4749
+ `ParseError: scalar variable @${use.name} cannot be used as IN @${use.name}; use IN (@${use.name}) instead.`,
4750
+ index
4751
+ );
4752
+ }
4753
+ if (def.kind === "array" && use.kind !== "array-in-list") {
4754
+ throw new BatchAnalysisError(
4755
+ `ParseError: array variable @${use.name} can only be used as IN @${use.name}.`,
4756
+ index
4757
+ );
4758
+ }
4759
+ if (!referencedThisStatement.has(use.name)) {
4760
+ def.referencedBy.push(index);
4761
+ referencedThisStatement.add(use.name);
4762
+ }
4434
4763
  }
4435
4764
  if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
4436
4765
  if (variableDefs.has(stmt.name)) {
4437
4766
  throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
4438
4767
  }
4439
- variableDefs.set(stmt.name, { index, referencedBy: [] });
4768
+ variableDefs.set(stmt.name, {
4769
+ index,
4770
+ kind: stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? "array" : "scalar",
4771
+ referencedBy: []
4772
+ });
4440
4773
  variableOrder.push(stmt.name);
4441
4774
  if (variableOrder.length > MAX_BATCH_VARIABLES) {
4442
4775
  throw new BatchAnalysisError(
@@ -4469,7 +4802,7 @@ function analyzeBatch(statements) {
4469
4802
  dependsOn.add(at);
4470
4803
  }
4471
4804
  if (validationTable) {
4472
- const payloadFields = 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 : [];
4473
4806
  const signature = JSON.stringify(payloadFields);
4474
4807
  const at = defined.get(validationTable);
4475
4808
  if (at === void 0) {
@@ -4544,6 +4877,7 @@ function analyzeBatch(statements) {
4544
4877
  const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
4545
4878
  const variables = variableOrder.map((name) => ({
4546
4879
  name,
4880
+ kind: variableDefs.get(name).kind,
4547
4881
  referencedBy: [...variableDefs.get(name).referencedBy]
4548
4882
  }));
4549
4883
  return {
@@ -4820,6 +5154,7 @@ function whereNeedsFieldMetadata(where) {
4820
5154
  case "GROUP":
4821
5155
  return whereNeedsFieldMetadata(where.expr);
4822
5156
  case "EXISTS":
5157
+ case "BOOLEAN":
4823
5158
  return false;
4824
5159
  }
4825
5160
  }
@@ -4844,6 +5179,7 @@ function explainNeedsAppMetadata(statement) {
4844
5179
  seen.add(node);
4845
5180
  if (Array.isArray(node)) return node.some(visit);
4846
5181
  const item = node;
5182
+ if (item["type"] === "VALIDATE") return true;
4847
5183
  if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
4848
5184
  return true;
4849
5185
  }
@@ -5334,6 +5670,8 @@ function resolveFieldRef(row, field) {
5334
5670
  // src/engine/evalWhere.ts
5335
5671
  function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
5336
5672
  switch (expr.type) {
5673
+ case "BOOLEAN":
5674
+ return expr.value;
5337
5675
  case "BINARY":
5338
5676
  return evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
5339
5677
  case "NULL_CHECK":
@@ -5504,6 +5842,8 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
5504
5842
  switch (value.type) {
5505
5843
  case "VARIABLE":
5506
5844
  throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
5845
+ case "VARIABLE_IN_LIST":
5846
+ throw new Error(`ParseError: unresolved batch array variable @${value.name}.`);
5507
5847
  case "STRING":
5508
5848
  return value.value;
5509
5849
  case "NUMBER":
@@ -5819,6 +6159,9 @@ function collectConditionFields(expr, out) {
5819
6159
  case "GROUP":
5820
6160
  collectConditionFields(expr.expr, out);
5821
6161
  break;
6162
+ case "EXISTS":
6163
+ case "BOOLEAN":
6164
+ break;
5822
6165
  }
5823
6166
  }
5824
6167
  function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new Map()) {
@@ -6034,6 +6377,8 @@ function convertDmlSqlValue(value, fieldType) {
6034
6377
  switch (value.type) {
6035
6378
  case "VARIABLE":
6036
6379
  throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
6380
+ case "VARIABLE_IN_LIST":
6381
+ throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
6037
6382
  case "STRING":
6038
6383
  return convertString2(value.value, fieldType);
6039
6384
  case "NUMBER":
@@ -6869,6 +7214,8 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
6869
7214
  const out = {};
6870
7215
  for (const [colIdx, col] of columns.entries()) {
6871
7216
  switch (col.type) {
7217
+ case "VARIABLE_COL":
7218
+ throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
6872
7219
  case "WILDCARD":
6873
7220
  Object.assign(out, stripParentShortcutColumns(row));
6874
7221
  break;
@@ -6972,6 +7319,8 @@ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
6972
7319
  }
6973
7320
  function computeOutputKey(col, colIdx, defaultFieldKeys) {
6974
7321
  switch (col.type) {
7322
+ case "VARIABLE_COL":
7323
+ throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
6975
7324
  case "FIELD":
6976
7325
  return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
6977
7326
  case "LITERAL_COL":
@@ -7415,9 +7764,15 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
7415
7764
  candidate.record ??= {};
7416
7765
  const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
7417
7766
  for (const code of targetFields) {
7767
+ if (!candidate.payload.has(code)) continue;
7418
7768
  const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
7419
7769
  if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
7420
- 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
+ }
7421
7776
  }
7422
7777
  if (validateMissingCreateFields && candidate.mode === "create") {
7423
7778
  for (const info of fieldInfos) {
@@ -7486,6 +7841,11 @@ function renderValidationValue(value) {
7486
7841
  return String(value);
7487
7842
  }
7488
7843
 
7844
+ // src/core/existingRecordValidation.ts
7845
+ function renderExistingValidationValue(raw, fieldType) {
7846
+ return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
7847
+ }
7848
+
7489
7849
  // src/core/optimization/whereCapability.ts
7490
7850
  var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
7491
7851
  var EQUALITY_IN = ["=", "!=", "in", "not in"];
@@ -7560,6 +7920,8 @@ function classifyWhereCapability(where, resolveField2) {
7560
7920
  }
7561
7921
  function classifyNode(where, resolveField2) {
7562
7922
  switch (where.type) {
7923
+ case "BOOLEAN":
7924
+ return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
7563
7925
  case "BINARY":
7564
7926
  return classifyBinary(where.op, where.left, where.right.type, resolveField2);
7565
7927
  case "NULL_CHECK":
@@ -7672,6 +8034,862 @@ function unsupported(code, field, fieldType, operator) {
7672
8034
  return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
7673
8035
  }
7674
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
+
7675
8893
  // src/execute.ts
7676
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";
7677
8895
  var SearchAbortedError = class extends Error {
@@ -7682,6 +8900,7 @@ var SearchAbortedError = class extends Error {
7682
8900
  };
7683
8901
  var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
7684
8902
  var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
8903
+ var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
7685
8904
  var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
7686
8905
  var nextDefaultCacheContextId = 1;
7687
8906
  function resolveCacheContext(client, explicit) {
@@ -7696,7 +8915,7 @@ function resolveCacheContext(client, explicit) {
7696
8915
  async function execute(sql, client, options = {}) {
7697
8916
  const startedAt = Date.now();
7698
8917
  const cacheContext = resolveCacheContext(client, options.cacheContext);
7699
- const stmt = parseSql(sql);
8918
+ const stmt = parseSql(sql, options.enableImport === true);
7700
8919
  const metrics = createEmptyMetrics();
7701
8920
  const countedClient = wrapClientWithMetrics(client, metrics);
7702
8921
  const collector = { aborted: false };
@@ -7876,6 +9095,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
7876
9095
  throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
7877
9096
  }
7878
9097
  validateKlikeStatement(stmt);
9098
+ if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
7879
9099
  if ("validateOnly" in stmt && stmt.validateOnly === true) {
7880
9100
  if (stmt.validationErrorTable) {
7881
9101
  throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
@@ -7886,6 +9106,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
7886
9106
  throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
7887
9107
  }
7888
9108
  switch (stmt.type) {
9109
+ case "VALIDATE":
9110
+ return executeExistingRecordValidation(stmt, client, options, cacheContext);
7889
9111
  case "SELECT":
7890
9112
  return executeSelect(stmt, client, options, cacheContext);
7891
9113
  case "UNION":
@@ -7931,6 +9153,144 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
7931
9153
  return executeAssert(stmt, client, options, cacheContext);
7932
9154
  }
7933
9155
  }
9156
+ var EXISTING_VALIDATION_COLUMNS = ["$id", "$err_field", "$err_code", "$err_message", "$err_value"];
9157
+ function hasAuditableConstraint(field) {
9158
+ return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
9159
+ }
9160
+ function resolveExistingValidationTargets(stmt, fieldInfos) {
9161
+ const byCode = new Map(fieldInfos.map((field) => [field.code, field]));
9162
+ const auditable = (field) => !field.inSubtable && (field.fieldType === "NUMBER" || hasAuditableConstraint(field));
9163
+ if (stmt.fields === void 0) return fieldInfos.filter(auditable);
9164
+ const seen = /* @__PURE__ */ new Set();
9165
+ return stmt.fields.map((code) => {
9166
+ if (seen.has(code)) throw new Error(`ArgumentError: VALIDATE field ${code} is duplicated.`);
9167
+ seen.add(code);
9168
+ if (code === "$id") throw new Error("ArgumentError: VALIDATE cannot audit system field $id.");
9169
+ const info = byCode.get(code);
9170
+ if (!info) throw new Error(`ArgumentError: VALIDATE field ${code} does not exist.`);
9171
+ if (info.inSubtable) throw new Error(`ArgumentError: VALIDATE field ${code} is a subtable child field.`);
9172
+ if (!auditable(info)) throw new Error(`ArgumentError: VALIDATE field ${code} has no auditable constraint.`);
9173
+ return info;
9174
+ });
9175
+ }
9176
+ function collectValidateWhereFields(where) {
9177
+ const fields = [];
9178
+ const seen = /* @__PURE__ */ new Set();
9179
+ const add = (field) => {
9180
+ if (!seen.has(field)) {
9181
+ seen.add(field);
9182
+ fields.push(field);
9183
+ }
9184
+ };
9185
+ const visit = (node) => {
9186
+ if (Array.isArray(node)) {
9187
+ node.forEach(visit);
9188
+ return;
9189
+ }
9190
+ if (node === null || typeof node !== "object") return;
9191
+ const obj = node;
9192
+ if (obj.type === "FIELD" && typeof obj.field === "string") add(obj.field);
9193
+ if (obj.type === "FIELD_REF" && typeof obj.field === "string") add(obj.field);
9194
+ Object.values(obj).forEach(visit);
9195
+ };
9196
+ visit(where);
9197
+ return fields;
9198
+ }
9199
+ function existingValidationColumnMeta() {
9200
+ return new Map(EXISTING_VALIDATION_COLUMNS.map((column) => [column, {
9201
+ fieldType: column === "$id" ? "KSQL_NUMBER" : "KSQL_STRING",
9202
+ sortKind: column === "$id" ? "number" : "string",
9203
+ semantics: syntheticSemantics(column === "$id" ? "number" : "string")
9204
+ }]));
9205
+ }
9206
+ async function executeExistingRecordValidation(stmt, client, options, cacheContext) {
9207
+ if (stmt.errorTable) throw new Error("ArgumentError: VALIDATE INTO requires a batch.");
9208
+ return executeExistingRecordValidationCore(stmt, client, options, cacheContext);
9209
+ }
9210
+ async function executeExistingRecordValidationCore(stmt, client, options, cacheContext) {
9211
+ const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
9212
+ const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
9213
+ const targets = resolveExistingValidationTargets(stmt, fieldInfos);
9214
+ const checkGroups = stmt.checkGroups ?? [];
9215
+ const checkRefs2 = collectCheckFieldRefs(checkGroups);
9216
+ for (const ref of checkRefs2) {
9217
+ if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
9218
+ throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${stmt.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
9219
+ }
9220
+ }
9221
+ const evaluationTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
9222
+ evaluationTypes.set("$id", "RECORD_NUMBER");
9223
+ assertCheckComparisonTypes(stmt, evaluationTypes);
9224
+ const whereFields = collectValidateWhereFields(stmt.where);
9225
+ const requiredFields = [.../* @__PURE__ */ new Set([
9226
+ "$id",
9227
+ ...targets.map((field) => field.code),
9228
+ ...whereFields,
9229
+ ...checkRefs2.map((ref) => ref.field)
9230
+ ])];
9231
+ for (const field of whereFields) {
9232
+ if (field !== "$id" && !infoByCode.has(field)) {
9233
+ throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${stmt.appId}.`);
9234
+ }
9235
+ }
9236
+ const numberPrecision = targets.some((field) => field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
9237
+ const semantics = (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0);
9238
+ const capability = classifyWhereCapability(stmt.where, semantics);
9239
+ if (capability.capability === "UNSUPPORTED") {
9240
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
9241
+ }
9242
+ const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
9243
+ const fieldOptions = new Map(fieldInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
9244
+ const prefilter = stmt.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? stmt.where : extractSafePushdownLeaves(stmt.where, {
9245
+ allowUnqualifiedFields: true,
9246
+ fieldTypes,
9247
+ fieldOptions,
9248
+ allowKlike: false
9249
+ });
9250
+ const query = prefilter === null ? "" : whereToKintone(prefilter);
9251
+ const records = await fetchAll(client.getRecords, stmt.appId, query, requiredFields, {
9252
+ maxRecords: options.maxRecords ?? 1e4,
9253
+ parallel: options.fetchParallel ?? 1,
9254
+ onLimit: "error"
9255
+ });
9256
+ const validationRows = records.map((record) => ({
9257
+ id: String(record["$id"]?.value ?? ""),
9258
+ record,
9259
+ flat: flatten(record, null)
9260
+ })).filter((row) => stmt.where === null || evalWhere(stmt.where, row.flat, (field) => evaluationTypes.get(field.field)));
9261
+ const rows = [];
9262
+ for (const row of validationRows) {
9263
+ for (const field of targets) {
9264
+ const raw = row.record[field.code]?.value;
9265
+ const validation = validateAndNormalizeDmlValue(raw, field, numberPrecision);
9266
+ if (validation.ok) continue;
9267
+ rows.push({
9268
+ "$id": row.id,
9269
+ "$err_field": field.code,
9270
+ "$err_code": validation.code,
9271
+ "$err_message": validation.message,
9272
+ "$err_value": renderExistingValidationValue(raw, field.fieldType)
9273
+ });
9274
+ }
9275
+ for (const check of evaluateCustomChecks(checkGroups, row.flat, (field) => evaluationTypes.get(field.field))) {
9276
+ rows.push({
9277
+ "$id": row.id,
9278
+ "$err_field": "",
9279
+ "$err_code": "ERR_CHECK",
9280
+ "$err_message": check.message,
9281
+ "$err_value": ""
9282
+ });
9283
+ }
9284
+ }
9285
+ const result = {
9286
+ type: "SELECT",
9287
+ columns: [...EXISTING_VALIDATION_COLUMNS],
9288
+ rows,
9289
+ rowCount: rows.length
9290
+ };
9291
+ materializedMetaBySelectResult.set(result, existingValidationColumnMeta());
9292
+ return result;
9293
+ }
7934
9294
  var TEMP_TABLE_MAX_ROWS = 1e4;
7935
9295
  function appendValidationErrors(tempTables, name, columns, rows, maxRows, columnMeta) {
7936
9296
  const current = tempTables.get(name);
@@ -7958,7 +9318,7 @@ var BatchTimeoutError = class extends Error {
7958
9318
  }
7959
9319
  };
7960
9320
  async function executeBatch(sql, client, options = {}) {
7961
- const statements = parseSqlBatch(sql);
9321
+ const statements = parseSqlBatch(sql, options.enableImport === true);
7962
9322
  const analysis = analyzeBatch(statements);
7963
9323
  const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
7964
9324
  const batchOptions = { ...options, variables: injectedVariables };
@@ -8011,11 +9371,12 @@ async function executeBatch(sql, client, options = {}) {
8011
9371
  const userConfirm = batchOptions.confirm;
8012
9372
  const stmtOptions = userConfirm ? {
8013
9373
  ...batchOptions,
8014
- confirm: (count, operation) => userConfirm(count, operation, {
9374
+ confirm: (count, operation, detailContext) => userConfirm(count, operation, {
8015
9375
  statementIndex: i,
8016
9376
  statementCount: statements.length,
8017
9377
  statementType: info.statementType,
8018
- targetAppId: info.targetAppId
9378
+ targetAppId: info.targetAppId,
9379
+ ...detailContext?.importDetail ? { importDetail: detailContext.importDetail } : {}
8019
9380
  })
8020
9381
  } : batchOptions;
8021
9382
  const searchAbortCollector = { aborted: false };
@@ -8064,9 +9425,14 @@ async function executeBatch(sql, client, options = {}) {
8064
9425
  }
8065
9426
  async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
8066
9427
  if (stmt.type === "SET_VARIABLE") {
8067
- const resolvedStmt2 = resolveVariableRefs(stmt, variables);
9428
+ const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
8068
9429
  validateKlikeStatement(resolvedStmt2);
8069
- if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
9430
+ if (resolvedStmt2.expr.type === "ARRAY") {
9431
+ variables.set(stmt.name, {
9432
+ type: "array",
9433
+ elements: resolvedStmt2.expr.elements.map((element) => ({ type: "string", value: element.value }))
9434
+ });
9435
+ } else if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
8070
9436
  try {
8071
9437
  const value = await evaluateScalarSubquery(
8072
9438
  resolvedStmt2.expr.query,
@@ -8103,8 +9469,30 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
8103
9469
  }
8104
9470
  return {};
8105
9471
  }
8106
- const resolvedStmt = resolveVariableRefs(stmt, variables);
9472
+ const resolvedStmt = resolveBatchVariableReferences(stmt, variables);
8107
9473
  validateKlikeStatement(resolvedStmt);
9474
+ if (resolvedStmt.type === "VALIDATE") {
9475
+ const result = await executeExistingRecordValidationCore(
9476
+ resolvedStmt,
9477
+ client,
9478
+ { ...options, onLimitReached: "error" },
9479
+ cacheContext
9480
+ );
9481
+ if (resolvedStmt.errorTable) {
9482
+ appendValidationErrors(
9483
+ tempTables,
9484
+ resolvedStmt.errorTable,
9485
+ result.columns,
9486
+ result.rows,
9487
+ options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
9488
+ materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta()
9489
+ );
9490
+ }
9491
+ return { result };
9492
+ }
9493
+ if (resolvedStmt.type === "IMPORT") {
9494
+ return { result: await executeImport(resolvedStmt, client, options, cacheContext, tempTables) };
9495
+ }
8108
9496
  if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
8109
9497
  const result = await executeDmlValidation(
8110
9498
  resolvedStmt,
@@ -8266,9 +9654,9 @@ function safeJsonStringify(v) {
8266
9654
  return String(v);
8267
9655
  }
8268
9656
  }
8269
- function parseSqlBatch(sql) {
9657
+ function parseSqlBatch(sql, enableImport = false) {
8270
9658
  const tokens = new Lexer(sql).tokenize();
8271
- return new Parser(tokens).parseStatements();
9659
+ return new Parser(tokens, { import: enableImport }).parseStatements();
8272
9660
  }
8273
9661
  function evaluateScalarExpr(expr) {
8274
9662
  switch (expr.type) {
@@ -8289,9 +9677,9 @@ function evaluateScalarExpr(expr) {
8289
9677
  }
8290
9678
  }
8291
9679
  }
8292
- function resolveVariableRefs(node, variables) {
9680
+ function resolveBatchVariableReferences(node, variables) {
8293
9681
  if (Array.isArray(node)) {
8294
- return node.map((v) => resolveVariableRefs(v, variables));
9682
+ return node.map((v) => resolveBatchVariableReferences(v, variables));
8295
9683
  }
8296
9684
  if (node !== null && typeof node === "object") {
8297
9685
  const obj = node;
@@ -8300,14 +9688,72 @@ function resolveVariableRefs(node, variables) {
8300
9688
  if (value === void 0) {
8301
9689
  throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
8302
9690
  }
9691
+ if (value.type === "array") {
9692
+ throw new Error(`ParseError: array variable @${obj["name"]} can only be used as IN @${obj["name"]}.`);
9693
+ }
8303
9694
  return value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
8304
9695
  }
8305
- return Object.fromEntries(
8306
- Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
9696
+ if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
9697
+ const value = variables.get(obj["name"]);
9698
+ if (value === void 0) throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
9699
+ if (value.type === "array") throw new Error(`ParseError: array variable @${obj["name"]} cannot be used as a SELECT column.`);
9700
+ return value.type === "number" ? { type: "ARITH_COL", expr: { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) }, alias: obj["alias"] } : { type: "LITERAL_COL", value: value.value, alias: obj["alias"] };
9701
+ }
9702
+ if (obj["type"] === "VARIABLE_IN_LIST") return obj;
9703
+ const resolved = Object.fromEntries(
9704
+ Object.entries(obj).map(([key, value]) => [key, resolveBatchVariableReferences(value, variables)])
8307
9705
  );
9706
+ if (resolved["type"] === "BINARY") {
9707
+ const right = resolved["right"];
9708
+ if (right?.["type"] === "VARIABLE_IN_LIST" && typeof right["name"] === "string") {
9709
+ const value = variables.get(right["name"]);
9710
+ if (value === void 0) throw new Error(`ParseError: variable @${right["name"]} is not defined in this batch.`);
9711
+ if (value.type !== "array") {
9712
+ throw new Error(`ParseError: scalar variable @${right["name"]} cannot be used as IN @${right["name"]}; use IN (@${right["name"]}) instead.`);
9713
+ }
9714
+ if (value.elements.length === 0) {
9715
+ return { type: "BOOLEAN", value: resolved["op"] === "NOT_IN" };
9716
+ }
9717
+ resolved["right"] = {
9718
+ type: "IN_LIST",
9719
+ values: value.elements.map((element) => ({ type: "STRING", value: element.value }))
9720
+ };
9721
+ }
9722
+ }
9723
+ const simplified = simplifyBooleanWhere(resolved);
9724
+ if (simplified["type"] === "SELECT" && isBooleanNode(simplified["where"], true)) {
9725
+ simplified["where"] = null;
9726
+ }
9727
+ if ((simplified["type"] === "UPDATE" || simplified["type"] === "DELETE" || simplified["type"] === "REORDER") && isBooleanNode(simplified["where"], true)) {
9728
+ throw new Error("ArgumentError: empty-array simplification makes the target WHERE always true; use an explicit safe target condition.");
9729
+ }
9730
+ return simplified;
8308
9731
  }
8309
9732
  return node;
8310
9733
  }
9734
+ function isBooleanNode(value, expected) {
9735
+ return value !== null && typeof value === "object" && value.type === "BOOLEAN" && (expected === void 0 || value.value === expected);
9736
+ }
9737
+ function simplifyBooleanWhere(obj) {
9738
+ if (obj["type"] === "NOT" && isBooleanNode(obj["expr"])) {
9739
+ return { type: "BOOLEAN", value: !obj["expr"].value };
9740
+ }
9741
+ if (obj["type"] === "GROUP" && isBooleanNode(obj["expr"])) return obj["expr"];
9742
+ if (obj["type"] === "LOGICAL") {
9743
+ const left = obj["left"];
9744
+ const right = obj["right"];
9745
+ if (obj["op"] === "AND") {
9746
+ if (isBooleanNode(left, false) || isBooleanNode(right, false)) return { type: "BOOLEAN", value: false };
9747
+ if (isBooleanNode(left, true)) return right;
9748
+ if (isBooleanNode(right, true)) return left;
9749
+ } else if (obj["op"] === "OR") {
9750
+ if (isBooleanNode(left, true) || isBooleanNode(right, true)) return { type: "BOOLEAN", value: true };
9751
+ if (isBooleanNode(left, false)) return right;
9752
+ if (isBooleanNode(right, false)) return left;
9753
+ }
9754
+ }
9755
+ return obj;
9756
+ }
8311
9757
  function findVariableRef(node) {
8312
9758
  if (Array.isArray(node)) {
8313
9759
  for (const value of node) {
@@ -8318,7 +9764,7 @@ function findVariableRef(node) {
8318
9764
  }
8319
9765
  if (node !== null && typeof node === "object") {
8320
9766
  const obj = node;
8321
- if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
9767
+ if ((obj["type"] === "VARIABLE" || obj["type"] === "VARIABLE_COL" || obj["type"] === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") return obj["name"];
8322
9768
  for (const value of Object.values(obj)) {
8323
9769
  const found = findVariableRef(value);
8324
9770
  if (found !== null) return found;
@@ -8561,6 +10007,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
8561
10007
  if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
8562
10008
  const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
8563
10009
  const byCode = new Map(fields.map((field) => [field.code, field]));
10010
+ if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) return;
8564
10011
  const result = classifyWhereCapability(stmt.where, (field) => {
8565
10012
  if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
8566
10013
  const info = byCode.get(field.field);
@@ -8633,6 +10080,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
8633
10080
  }
8634
10081
  return result;
8635
10082
  }
10083
+ function isConstantFalseWhere(where) {
10084
+ return where?.type === "BOOLEAN" && where.value === false;
10085
+ }
8636
10086
  function isNoFromSelect(stmt) {
8637
10087
  return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
8638
10088
  }
@@ -8664,6 +10114,8 @@ function stringFuncHasFieldRef(expr) {
8664
10114
  function validateNoFromColumns(stmt) {
8665
10115
  for (const col of stmt.columns) {
8666
10116
  switch (col.type) {
10117
+ case "VARIABLE_COL":
10118
+ throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
8667
10119
  case "LITERAL_COL":
8668
10120
  break;
8669
10121
  case "ARITH_COL":
@@ -8899,6 +10351,7 @@ function collectTypedInFieldRefs(expr, out) {
8899
10351
  return;
8900
10352
  case "NULL_CHECK":
8901
10353
  case "EXISTS":
10354
+ case "BOOLEAN":
8902
10355
  return;
8903
10356
  }
8904
10357
  }
@@ -9272,8 +10725,14 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
9272
10725
  }
9273
10726
  } else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
9274
10727
  meta = syntheticColumnMeta("number");
9275
- } else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") {
10728
+ } else if (column.type === "LITERAL_COL") {
9276
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");
9277
10736
  } else if (column.type === "STRFUNC_COL") {
9278
10737
  meta = stringFunctionColumnMeta(column.expr);
9279
10738
  } else if (column.type === "WINDOW_COL") {
@@ -9361,7 +10820,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
9361
10820
  validateKlikePushdownPlan(pushdownPlan);
9362
10821
  const mainPushDown = pushdownPlan.mainCondition;
9363
10822
  const tableConditions = pushdownPlan.joinConditions;
9364
- const mainFetch = fetchTableRecordsForFullScan(
10823
+ const constantFalse = isConstantFalseWhere(stmt.where);
10824
+ const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
9365
10825
  stmt,
9366
10826
  stmt.from,
9367
10827
  client,
@@ -9376,6 +10836,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
9376
10836
  const parallelJoins = [];
9377
10837
  const onOptJoins = [];
9378
10838
  for (const join2 of stmt.joins) {
10839
+ if (constantFalse) {
10840
+ parallelJoins.push({ join: join2, promise: Promise.resolve([]) });
10841
+ continue;
10842
+ }
9379
10843
  const jCond = join2.table.alias ? tableConditions.get(join2.table.alias) ?? null : null;
9380
10844
  if (jCond !== null) {
9381
10845
  parallelJoins.push({
@@ -10082,9 +11546,10 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
10082
11546
  return sortKinds;
10083
11547
  }
10084
11548
  function convertProcessRowValue(raw, dstFieldType) {
10085
- 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"]);
10086
11551
  const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
10087
- if (USER_TYPES2.has(dstFieldType ?? "")) {
11552
+ if (USER_TYPES4.has(dstFieldType ?? "")) {
10088
11553
  if (raw === "") return [];
10089
11554
  try {
10090
11555
  const parsed = JSON.parse(raw);
@@ -10149,11 +11614,13 @@ function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecisio
10149
11614
  records.forEach((record, rowIndex) => {
10150
11615
  for (const code of targetFields) {
10151
11616
  const info = infoByCode.get(code);
10152
- const result = validateAndNormalizeDmlValue(record[code]?.value ?? "", info, numberPrecision);
11617
+ const original = record[code]?.value ?? "";
11618
+ const result = validateAndNormalizeDmlValue(original, info, numberPrecision);
10153
11619
  if (!result.ok) {
10154
11620
  throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
10155
11621
  }
10156
- 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 };
10157
11624
  }
10158
11625
  });
10159
11626
  }
@@ -10313,6 +11780,8 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10313
11780
  if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
10314
11781
  let rows;
10315
11782
  let sourceRows;
11783
+ let sourcePresence;
11784
+ let sourceRowErrors;
10316
11785
  let evaluationTypes;
10317
11786
  if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
10318
11787
  assertInsertCheckRefs(stmt, stmt.fields);
@@ -10322,7 +11791,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10322
11791
  (value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
10323
11792
  ));
10324
11793
  } else {
10325
- 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()]);
10326
11795
  const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
10327
11796
  if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
10328
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`);
@@ -10332,7 +11801,9 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10332
11801
  }
10333
11802
  assertInsertCheckRefs(stmt, selectResult.columns);
10334
11803
  sourceRows = selectResult.rows;
10335
- const meta = materializedMetaBySelectResult.get(selectResult);
11804
+ sourcePresence = selectResult.importPresence;
11805
+ sourceRowErrors = selectResult.importRowErrors;
11806
+ const meta = selectResult.columnMeta;
10336
11807
  evaluationTypes = new Map(selectResult.columns.map((column) => {
10337
11808
  const columnMeta = meta?.get(column);
10338
11809
  const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
@@ -10345,8 +11816,10 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10345
11816
  rowNumber: index + 1,
10346
11817
  operation,
10347
11818
  mode: "create",
10348
- payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
10349
- preErrors: [],
11819
+ payload: new Map(stmt.fields.flatMap(
11820
+ (field, i) => sourcePresence && !sourcePresence[index]?.has(field) ? [] : [[field, values[i]]]
11821
+ )),
11822
+ preErrors: [...sourceRowErrors?.[index] ?? []],
10350
11823
  record: {},
10351
11824
  evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
10352
11825
  stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
@@ -10359,13 +11832,17 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10359
11832
  }
10360
11833
  const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
10361
11834
  const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
10362
- const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
10363
11835
  const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
10364
11836
  const keyCounts = /* @__PURE__ */ new Map();
10365
11837
  for (const parts of rowKeys) {
10366
11838
  const key = upsertNormalizedKey(parts, numeric);
10367
11839
  keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
10368
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);
10369
11846
  candidates.forEach((candidate, index) => {
10370
11847
  const parts = rowKeys[index];
10371
11848
  const targetId = lookupUpsertTarget(targets, parts);
@@ -10374,7 +11851,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
10374
11851
  stmt.keyFields.forEach((key, keyIndex) => {
10375
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` });
10376
11853
  });
10377
- if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
11854
+ if (!isImport && (keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
10378
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" });
10379
11856
  }
10380
11857
  });
@@ -10734,12 +12211,508 @@ async function executeInsert(stmt, client, options, cacheContext) {
10734
12211
  insertedCount: createdIds.flat().length
10735
12212
  };
10736
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
+ }
10737
12709
  async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
10738
12710
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
10739
12711
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
10740
12712
  const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
10741
- const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
10742
- const { rows, columns } = selectResult;
12713
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
12714
+ const { rows, columns } = sourceTable;
12715
+ assertNoImportRowErrors(sourceTable);
10743
12716
  if (columns.length !== stmt.fields.length) {
10744
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" : "";
10745
12718
  throw new Error(
@@ -10751,15 +12724,16 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
10751
12724
  if (!ok) throw new OperationCancelledError("INSERT", rows.length);
10752
12725
  }
10753
12726
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
10754
- const allRecords = rows.map((row) => {
12727
+ const allRecords = rows.map((row, rowIndex) => {
10755
12728
  const record = {};
10756
12729
  stmt.fields.forEach((field, i) => {
12730
+ if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
10757
12731
  const raw = row[columns[i]] ?? "";
10758
12732
  record[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
10759
12733
  });
10760
12734
  return record;
10761
12735
  });
10762
- assertValidDmlRecords(allRecords, stmt.fields, fieldInfos, numberPrecision);
12736
+ allRecords.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
10763
12737
  const createdIds = [];
10764
12738
  for (let i = 0; i < allRecords.length; i += 100) {
10765
12739
  const batch = allRecords.slice(i, i + 100);
@@ -10773,6 +12747,26 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
10773
12747
  };
10774
12748
  }
10775
12749
  async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
12750
+ if (stmt.checkGroups?.length && isConstantFalseWhere(stmt.where)) {
12751
+ const fieldInfos2 = await loadWritableTopLevelDmlFields(
12752
+ stmt.appId,
12753
+ stmt.assignments.map((assignment) => assignment.field),
12754
+ client,
12755
+ cacheContext
12756
+ );
12757
+ await loadNumberPrecisionForTargets(
12758
+ stmt.appId,
12759
+ stmt.assignments.map((assignment) => assignment.field),
12760
+ fieldInfos2,
12761
+ client,
12762
+ cacheContext
12763
+ );
12764
+ const fieldTypes2 = await getFieldTypeMap(stmt.appId, client, cacheContext);
12765
+ assertUpdateCheckRefs(stmt, fieldTypes2);
12766
+ assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes2, stmt.appId));
12767
+ await assertDmlWhereCapability(stmt, client, cacheContext);
12768
+ return { type: "UPDATE", updatedCount: 0 };
12769
+ }
10776
12770
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, tempTables);
10777
12771
  if (stmt.subtableCode) {
10778
12772
  await assertDmlWhereCapability(stmt, client, cacheContext);
@@ -10793,6 +12787,7 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
10793
12787
  cacheContext
10794
12788
  );
10795
12789
  await assertDmlWhereCapability(stmt, client, cacheContext);
12790
+ if (isConstantFalseWhere(stmt.where)) return { type: "UPDATE", updatedCount: 0 };
10796
12791
  if (stmt.from != null) {
10797
12792
  return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
10798
12793
  }
@@ -10874,6 +12869,7 @@ function collectUpdateFromTargetFields(stmt) {
10874
12869
  }
10875
12870
  async function executeDelete(stmt, client, options, cacheContext) {
10876
12871
  await assertDmlWhereCapability(stmt, client, cacheContext);
12872
+ if (isConstantFalseWhere(stmt.where)) return { type: "DELETE", deletedCount: 0 };
10877
12873
  if (stmt.subtableCode) {
10878
12874
  return executeDeleteSubtable(stmt, client, options, cacheContext);
10879
12875
  }
@@ -11130,9 +13126,9 @@ function expandRowsForSubtableDml(parents, subtableCode) {
11130
13126
  for (const parent of parents) {
11131
13127
  const parentId = String(parent["$id"]?.value ?? "");
11132
13128
  const parentRevision = getRevision(parent);
11133
- const tableRows = getMutableTableRows(parent, subtableCode);
11134
- for (let i = 0; i < tableRows.length; i++) {
11135
- const row = tableRows[i];
13129
+ const tableRows2 = getMutableTableRows(parent, subtableCode);
13130
+ for (let i = 0; i < tableRows2.length; i++) {
13131
+ const row = tableRows2[i];
11136
13132
  const flat = {
11137
13133
  _pid: parentId,
11138
13134
  _rid: row.id ?? "",
@@ -11256,6 +13252,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
11256
13252
  cacheContext
11257
13253
  );
11258
13254
  const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
13255
+ if (isConstantFalseWhere(stmt.where)) return { type: "REORDER", reorderedParentCount: 0 };
11259
13256
  const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
11260
13257
  field.code,
11261
13258
  field.semantics ?? resolveFieldSemantics(field)
@@ -11337,8 +13334,9 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11337
13334
  if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
11338
13335
  const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
11339
13336
  const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
11340
- const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
11341
- const { rows, columns } = selectResult;
13337
+ const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
13338
+ const { rows, columns } = sourceTable;
13339
+ assertNoImportRowErrors(sourceTable);
11342
13340
  if (columns.length !== stmt.fields.length) {
11343
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" : "";
11344
13342
  throw new Error(
@@ -11352,18 +13350,29 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
11352
13350
  }
11353
13351
  const toInsert = [];
11354
13352
  const toUpdate = [];
11355
- const records = rows.map((row) => {
13353
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13354
+ const records = rows.map((row, rowIndex) => {
11356
13355
  const record = {};
11357
13356
  stmt.fields.forEach((field, i) => {
11358
- 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)) };
11359
13360
  });
11360
13361
  return record;
11361
13362
  });
11362
- assertValidDmlRecords(records, stmt.fields, fieldInfos, numberPrecision);
11363
- const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
13363
+ records.forEach((record) => assertValidDmlRecords([record], stmt.fields.filter((field) => field in record), fieldInfos, numberPrecision));
11364
13364
  const rowKeyValues = records.map(
11365
13365
  (record) => stmt.keyFields.map((key) => String(record[key]?.value ?? ""))
11366
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
+ }
11367
13376
  const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
11368
13377
  records.forEach((record, rowIdx) => {
11369
13378
  const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
@@ -11412,10 +13421,10 @@ async function executeDescribe(stmt, client, cacheContext) {
11412
13421
  }));
11413
13422
  return { type: "SELECT", rows, columns, rowCount: rows.length };
11414
13423
  }
11415
- function parseSql(sql) {
13424
+ function parseSql(sql, enableImport = false) {
11416
13425
  try {
11417
13426
  const tokens = new Lexer(sql).tokenize();
11418
- const stmt = new Parser(tokens).parse();
13427
+ const stmt = new Parser(tokens, { import: enableImport }).parse();
11419
13428
  validateKlikeStatement(stmt);
11420
13429
  return stmt;
11421
13430
  } catch (e) {
@@ -11482,6 +13491,8 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCa
11482
13491
  }));
11483
13492
  break;
11484
13493
  }
13494
+ case "BOOLEAN":
13495
+ break;
11485
13496
  }
11486
13497
  }
11487
13498
  async function resolveSetSubqueries(assignments, client, options, cacheContext) {
@@ -11519,9 +13530,11 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
11519
13530
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
11520
13531
  return cache;
11521
13532
  }
13533
+ var validateExplainInfo = /* @__PURE__ */ new WeakMap();
11522
13534
  async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
11523
13535
  const fieldApps = /* @__PURE__ */ new Set();
11524
13536
  const processStatusApps = /* @__PURE__ */ new Set();
13537
+ const numberPrecisionApps = /* @__PURE__ */ new Set();
11525
13538
  const tracedClient = {
11526
13539
  ...client,
11527
13540
  getFields: async (appId) => {
@@ -11531,6 +13544,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
11531
13544
  getProcessStatuses: async (appId) => {
11532
13545
  processStatusApps.add(appId);
11533
13546
  return client.getProcessStatuses(appId);
13547
+ },
13548
+ getNumberPrecision: async (appId) => {
13549
+ numberPrecisionApps.add(appId);
13550
+ return client.getNumberPrecision(appId);
11534
13551
  }
11535
13552
  };
11536
13553
  const capabilities = /* @__PURE__ */ new Map();
@@ -11579,6 +13596,51 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
11579
13596
  }));
11580
13597
  }
11581
13598
  }
13599
+ } else if (typed["type"] === "VALIDATE") {
13600
+ const validate = node;
13601
+ fieldApps.add(validate.appId);
13602
+ const fields = await getFieldsCached(validate.appId, tracedClient, cacheContext);
13603
+ const infoByCode = new Map(fields.map((field) => [field.code, field]));
13604
+ const targets = resolveExistingValidationTargets(validate, fields);
13605
+ const checks = collectCheckFieldRefs(validate.checkGroups ?? []);
13606
+ const whereFields = collectValidateWhereFields(validate.where);
13607
+ for (const ref of checks) {
13608
+ if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
13609
+ throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${validate.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
13610
+ }
13611
+ }
13612
+ for (const field of whereFields) {
13613
+ if (field !== "$id" && !infoByCode.has(field)) {
13614
+ throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${validate.appId}.`);
13615
+ }
13616
+ }
13617
+ const types = new Map(fields.map((field) => [field.code, field.fieldType]));
13618
+ types.set("$id", "RECORD_NUMBER");
13619
+ assertCheckComparisonTypes(validate, types);
13620
+ const capability = classifyWhereCapability(validate.where, (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0));
13621
+ if (capability.capability === "UNSUPPORTED") {
13622
+ throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
13623
+ }
13624
+ const fieldTypes = new Map(fields.map((field) => [field.code, field.fieldType]));
13625
+ const fieldOptions = new Map(fields.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
13626
+ const prefilter = validate.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? validate.where : extractSafePushdownLeaves(validate.where, {
13627
+ allowUnqualifiedFields: true,
13628
+ fieldTypes,
13629
+ fieldOptions,
13630
+ allowKlike: false
13631
+ });
13632
+ const needsPrecision = targets.some((field) => field.fieldType === "NUMBER");
13633
+ if (needsPrecision) {
13634
+ numberPrecisionApps.add(validate.appId);
13635
+ await getNumberPrecisionCached(validate.appId, tracedClient, cacheContext);
13636
+ }
13637
+ validateExplainInfo.set(validate, {
13638
+ targetFields: targets.map((field) => field.code),
13639
+ fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((field) => field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
13640
+ capability,
13641
+ prefilter,
13642
+ numberPrecision: needsPrecision
13643
+ });
11582
13644
  } else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
11583
13645
  fieldApps.add(node.appId);
11584
13646
  await assertDmlWhereCapability(
@@ -11609,23 +13671,24 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
11609
13671
  }));
11610
13672
  }
11611
13673
  }
11612
- return { capabilities, orderPlans, fieldApps, processStatusApps };
13674
+ return { capabilities, orderPlans, fieldApps, processStatusApps, numberPrecisionApps };
11613
13675
  }
11614
13676
  function explainMetadataLines(analysis) {
11615
13677
  return [
11616
13678
  ...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
11617
- ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
13679
+ ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`),
13680
+ ...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
11618
13681
  ];
11619
13682
  }
11620
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2) {
11621
- 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);
11622
13685
  const analysis = analyzeBatch(statements);
11623
13686
  validateDeclaredBatchVariables(statements, injectedVariables);
11624
13687
  const variables = /* @__PURE__ */ new Map();
11625
13688
  const plans = [];
11626
13689
  for (let i = 0; i < statements.length; i++) {
11627
13690
  const stmt = statements[i];
11628
- const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
13691
+ const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
11629
13692
  validateKlikeStatement(planStmt);
11630
13693
  const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords);
11631
13694
  const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
@@ -11641,7 +13704,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
11641
13704
  plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
11642
13705
  });
11643
13706
  if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
11644
- variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
13707
+ variables.set(stmt.name, stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? { type: "array", elements: stmt.expr.elements.map((element) => ({ type: "string", value: element.value })) } : { type: "string", value: `@${stmt.name}` });
11645
13708
  }
11646
13709
  }
11647
13710
  return { statementCount: statements.length, statements: plans };
@@ -11776,8 +13839,103 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
11776
13839
  if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
11777
13840
  if (query.type === "DELETE") return buildDeletePlan(query, label);
11778
13841
  if (query.type === "REORDER") return buildReorderPlan(query, label);
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
+ }
11779
13916
  return buildSelectPlan(query, label, capabilities, orderPlans);
11780
13917
  }
13918
+ function buildValidatePlan(stmt, label) {
13919
+ const info = validateExplainInfo.get(stmt);
13920
+ const lines = [];
13921
+ if (label) lines.push(label);
13922
+ lines.push(`VALIDATE APP${stmt.appId}`);
13923
+ lines.push(" operation: read-only existing-record constraint audit (writesKintone=false)");
13924
+ lines.push(" fetch API: GET records via offset + $id keyset paging (Cursor API unused)");
13925
+ lines.push(" complete input: required (onLimit=truncate disabled)");
13926
+ if (!info) {
13927
+ lines.push(" metadata: form definition required; number precision required for NUMBER targets");
13928
+ return lines;
13929
+ }
13930
+ lines.push(` WHERE capability: ${info.capability.capability}`);
13931
+ lines.push(` kintone query: ${info.prefilter === null ? "(\u5168\u4EF6\u53D6\u5F97)" : whereToKintone(info.prefilter)}`);
13932
+ lines.push(` audit fields: ${info.targetFields.length === 0 ? "(\u306A\u3057)" : info.targetFields.join(", ")}`);
13933
+ lines.push(` fetch fields: ${info.fetchFields.join(", ")}`);
13934
+ lines.push(` number precision: ${info.numberPrecision ? "required" : "not required"}`);
13935
+ lines.push(" local checks: original WHERE re-evaluation + built-in constraints + CHECK groups");
13936
+ lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
13937
+ return lines;
13938
+ }
11781
13939
  function buildSelectPlan(stmt, label, capabilities, orderPlans) {
11782
13940
  const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
11783
13941
  const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
@@ -11789,6 +13947,12 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
11789
13947
  const lines = [];
11790
13948
  if (label) lines.push(label);
11791
13949
  lines.push(` mode: ${mode}`);
13950
+ if (isConstantFalseWhere(stmt.where)) {
13951
+ lines.push(" predicate: constant false");
13952
+ lines.push(" records API access: none");
13953
+ lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
13954
+ return lines;
13955
+ }
11792
13956
  if (orderPlan) {
11793
13957
  lines.push(` order plan: ${orderPlan.kind}`);
11794
13958
  if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
@@ -11936,6 +14100,8 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
11936
14100
  break;
11937
14101
  case "NULL_CHECK":
11938
14102
  break;
14103
+ case "BOOLEAN":
14104
+ break;
11939
14105
  }
11940
14106
  };
11941
14107
  visitWhere(stmt.where);
@@ -11988,7 +14154,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
11988
14154
  } else {
11989
14155
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
11990
14156
  }
11991
- lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
14157
+ lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
11992
14158
  const setTypes = [];
11993
14159
  if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
11994
14160
  if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
@@ -12019,7 +14185,7 @@ function buildDeletePlan(stmt, label) {
12019
14185
  lines.push(` [DELETE]`);
12020
14186
  lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
12021
14187
  lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
12022
- lines.push(` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
14188
+ lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
12023
14189
  return lines;
12024
14190
  }
12025
14191
  function buildUpsertPlan(stmt, label) {
@@ -12059,7 +14225,7 @@ function buildReorderPlan(stmt, label) {
12059
14225
  ` table: ${target}`,
12060
14226
  ` scope: ${scope}`,
12061
14227
  ` by: ${byStr}`,
12062
- ` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
14228
+ isConstantFalseWhere(stmt.where) ? ` api: metadata validation only (records API access: none)` : ` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
12063
14229
  ];
12064
14230
  if (!stmt.all && stmt.where) {
12065
14231
  lines.splice(5, 0, ` where: ${safeWhereToKintone(stmt.where)}`);
@@ -12071,6 +14237,7 @@ function formatOrderByItem(item) {
12071
14237
  return `${key} ${item.direction}`;
12072
14238
  }
12073
14239
  function safeWhereToKintone(where) {
14240
+ if (where.type === "BOOLEAN") return where.value ? "TRUE" : "FALSE (constant)";
12074
14241
  try {
12075
14242
  return whereToKintone(where);
12076
14243
  } catch {
@@ -12199,15 +14366,15 @@ var OperationCancelledError = class extends Error {
12199
14366
  };
12200
14367
 
12201
14368
  // src/core/sql.ts
12202
- function parseSqlStatement(sql) {
14369
+ function parseSqlStatement(sql, capabilities = {}) {
12203
14370
  const tokens = new Lexer(sql).tokenize();
12204
- const stmt = new Parser(tokens).parse();
14371
+ const stmt = new Parser(tokens, capabilities).parse();
12205
14372
  validateKlikeStatement(stmt);
12206
14373
  return stmt;
12207
14374
  }
12208
- function parseSqlStatements(sql) {
14375
+ function parseSqlStatements(sql, capabilities = {}) {
12209
14376
  const tokens = new Lexer(sql).tokenize();
12210
- const statements = new Parser(tokens).parseStatements();
14377
+ const statements = new Parser(tokens, capabilities).parseStatements();
12211
14378
  statements.forEach(validateKlikeStatement);
12212
14379
  return statements;
12213
14380
  }
@@ -12374,7 +14541,8 @@ function buildBatchEnvelope(batch, options = {}) {
12374
14541
  validRows: s.result.validRows,
12375
14542
  invalidRows: s.result.invalidRows,
12376
14543
  errorCount: s.result.errorCount,
12377
- ...s.result.errTable ? { errTable: s.result.errTable } : {}
14544
+ ...s.result.errTable ? { errTable: s.result.errTable } : {},
14545
+ ...s.result.importDetail ? { importDetail: s.result.importDetail } : {}
12378
14546
  });
12379
14547
  } else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
12380
14548
  Object.assign(entry, toMutationSummary(s.result));
@@ -12677,7 +14845,7 @@ function clampInt(v, min, max) {
12677
14845
  function flattenFormFieldProperties(properties) {
12678
14846
  return flattenFields(properties, collectLookupCopyFields(properties));
12679
14847
  }
12680
- function flattenFields(properties, lookupCopyFields, inSubtable = false) {
14848
+ function flattenFields(properties, lookupCopyFields, inSubtable = false, subtableCode) {
12681
14849
  const out = [];
12682
14850
  for (const field of Object.values(properties)) {
12683
14851
  const optionOrder = toOptionOrderMap(field.options);
@@ -12695,11 +14863,12 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
12695
14863
  maxLength: normalizeConstraintValue(field.maxLength),
12696
14864
  defaultValue: field.defaultValue,
12697
14865
  inSubtable,
14866
+ ...subtableCode ? { subtableCode } : {},
12698
14867
  writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
12699
14868
  };
12700
14869
  info.semantics = resolveFieldSemantics(info);
12701
14870
  out.push(info);
12702
- 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));
12703
14872
  }
12704
14873
  return out;
12705
14874
  }
@@ -13683,6 +15852,17 @@ function restoreSqlContextError(err, sourceSql, context) {
13683
15852
  return err;
13684
15853
  }
13685
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
+
13686
15866
  // src/cli/index.ts
13687
15867
  var HELP_TEXT = `ksql - Execute SQL against kintone apps
13688
15868
 
@@ -13697,6 +15877,8 @@ Options:
13697
15877
  --console Start interactive console mode
13698
15878
  --dry-run Parse and show execution plan only
13699
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)
13700
15882
  --format <type> Output format: table | json | jsonl | csv | markdown | md
13701
15883
  (batch + json: prints one JSON envelope for the whole batch)
13702
15884
  --max-records <n> Max records to fetch (default: 500)
@@ -13744,6 +15926,15 @@ Options:
13744
15926
  -h, --help Show help
13745
15927
  -v, --version Show version
13746
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
+ }
13747
15938
  function parseArgs(argv) {
13748
15939
  const out = {
13749
15940
  help: false,
@@ -13795,7 +15986,9 @@ function parseArgs(argv) {
13795
15986
  arrayFormat: null,
13796
15987
  tableFormat: null,
13797
15988
  dateFormat: null,
13798
- attachmentFormat: null
15989
+ attachmentFormat: null,
15990
+ importCsv: /* @__PURE__ */ Object.create(null),
15991
+ importJson: /* @__PURE__ */ Object.create(null)
13799
15992
  };
13800
15993
  for (let i = 0; i < argv.length; i++) {
13801
15994
  const a = argv[i];
@@ -13877,6 +16070,28 @@ function parseArgs(argv) {
13877
16070
  i++;
13878
16071
  continue;
13879
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
+ }
13880
16095
  if (a === "-e" || a === "--execute") {
13881
16096
  out.executeSql = v ?? "";
13882
16097
  i++;
@@ -15024,21 +17239,22 @@ async function run() {
15024
17239
  `);
15025
17240
  return 2;
15026
17241
  }
17242
+ const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
15027
17243
  try {
15028
- const statements = parseSqlStatements(sql);
17244
+ const statements = parseSqlStatements(sql, { import: importEnabled });
15029
17245
  dryRunNeedsMetadata = statements.some(explainNeedsAppMetadata);
15030
17246
  if (statements.length > 1) {
15031
17247
  batchAnalysis = analyzeBatch(statements);
15032
17248
  isBatchSql = true;
15033
17249
  batchContainsDml = batchAnalysis.containsDml;
15034
17250
  } else {
15035
- const stmt = parseSqlStatement(sql);
17251
+ const stmt = parseSqlStatement(sql, { import: importEnabled });
15036
17252
  parsedStmt = stmt;
15037
17253
  stmtType = getStatementType(stmt);
15038
17254
  isDmlStatement = writesKintone(stmt);
15039
17255
  hasWhere = hasWhereClause(stmt);
15040
17256
  insertValuesCount = getInsertValuesCount(stmt);
15041
- const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
17257
+ const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || stmtType === "VALIDATE" || isDmlType(stmtType);
15042
17258
  if (!supported) {
15043
17259
  process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
15044
17260
  `);
@@ -15050,7 +17266,8 @@ async function run() {
15050
17266
  bindings: sqlDiagnosticContext.appBindingByMappedApp,
15051
17267
  rewriteSegments: sqlDiagnosticContext.rewriteSegments
15052
17268
  }) : err;
15053
- 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)}
15054
17271
  `);
15055
17272
  return 1;
15056
17273
  }
@@ -15088,10 +17305,11 @@ async function run() {
15088
17305
  const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
15089
17306
  const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
15090
17307
  const isValidationOnly = batchAnalysis?.containsValidationOnly === true || parsedStmt !== null && typeof parsedStmt === "object" && "validateOnly" in parsedStmt && parsedStmt.validateOnly === true;
15091
- const surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly;
17308
+ const isExistingRecordValidation = batchAnalysis?.statements.some((s) => s.statementType === "VALIDATE") === true || getStatementType(parsedStmt) === "VALIDATE";
17309
+ const surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly || isExistingRecordValidation;
15092
17310
  const effectiveOnLimit = surfaceForcesOnLimitError ? "error" : onLimit;
15093
17311
  if (surfaceForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
15094
- const reason = isDmlStatement || batchContainsDml ? "DML" : "VALIDATE ONLY";
17312
+ const reason = isDmlStatement || batchContainsDml ? "DML" : isExistingRecordValidation ? "VALIDATE" : "VALIDATE ONLY";
15095
17313
  process.stderr.write(`note: onLimit=truncate is ignored for ${reason} (forced to error)
15096
17314
  `);
15097
17315
  }
@@ -15406,7 +17624,8 @@ async function run() {
15406
17624
  args.variables,
15407
17625
  cacheContext,
15408
17626
  maxRecords,
15409
- cursorMaxActive
17627
+ cursorMaxActive,
17628
+ Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0
15410
17629
  );
15411
17630
  const out = [];
15412
17631
  const restoredStatements = sqlDiagnosticContext ? restoreSqlDiagnosticValue(plans.statements, sqlDiagnosticContext.appBindingByMappedApp) : plans.statements;
@@ -15445,10 +17664,30 @@ async function run() {
15445
17664
  }
15446
17665
  }
15447
17666
  }
15448
- 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) => {
15449
17673
  if (count > dmlMaxRows) {
15450
17674
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15451
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
+ }
15452
17691
  if (yes) return true;
15453
17692
  if (args.console) return true;
15454
17693
  const label = sql?.replace(/\s+/g, " ").trim() ?? operation;
@@ -15473,10 +17712,20 @@ query=${label}`);
15473
17712
  timeoutMs: timeout,
15474
17713
  cursorMaxActive,
15475
17714
  variables: args.variables,
15476
- confirm: batchContainsDml ? async (count, operation) => {
17715
+ enableImport: importEnabled,
17716
+ importSource,
17717
+ supportsImportConfirmDetail: true,
17718
+ confirm: batchContainsDml ? async (count, operation, context) => {
15477
17719
  if (count > dmlMaxRows) {
15478
17720
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed --dml-max-rows (${dmlMaxRows}).`);
15479
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
+ }
15480
17729
  return true;
15481
17730
  } : void 0
15482
17731
  });
@@ -15486,14 +17735,19 @@ query=${label}`);
15486
17735
  maxRecords,
15487
17736
  onLimitReached: onLimit,
15488
17737
  cacheContext,
15489
- cursorMaxActive
17738
+ cursorMaxActive,
17739
+ enableImport: importEnabled,
17740
+ importSource
15490
17741
  }) : await execute(sql, client, {
15491
17742
  maxRecords,
15492
17743
  fetchParallel,
15493
17744
  onLimitReached: effectiveOnLimit,
15494
17745
  confirm: isDmlStatement ? confirm : void 0,
15495
17746
  cacheContext,
15496
- cursorMaxActive
17747
+ cursorMaxActive,
17748
+ enableImport: importEnabled,
17749
+ importSource,
17750
+ supportsImportConfirmDetail: true
15497
17751
  });
15498
17752
  if (args.dryRun && sqlDiagnosticContext) {
15499
17753
  result = restoreSqlDiagnosticValue(result, sqlDiagnosticContext.appBindingByMappedApp);
@@ -15561,6 +17815,7 @@ if (isDirectCliRun()) {
15561
17815
  }
15562
17816
  // Annotate the CommonJS export names for ESM import in node:
15563
17817
  0 && (module.exports = {
17818
+ CLI_IMPORT_SOURCE_REQUIRED_MESSAGE,
15564
17819
  HELP_TEXT,
15565
17820
  buildBatchDmlConfirmMessage,
15566
17821
  buildBatchStatementSummary,
@@ -15577,5 +17832,6 @@ if (isDirectCliRun()) {
15577
17832
  parseTokenMap,
15578
17833
  runWithArgv,
15579
17834
  shouldExitOnEmpty,
17835
+ toCliImportError,
15580
17836
  writeBatchOutput
15581
17837
  });