@rex0220/kintone-sql-tools 3.5.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist-cli/ksql.js +1822 -54
- package/dist-mcp/ksql-mcp.js +1859 -97
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -1199,8 +1199,8 @@ var require_util = __commonJS({
|
|
|
1199
1199
|
})(Type || (exports2.Type = Type = {}));
|
|
1200
1200
|
function getErrorPath(dataProp, dataPropType, jsPropertySyntax) {
|
|
1201
1201
|
if (dataProp instanceof codegen_1.Name) {
|
|
1202
|
-
const
|
|
1203
|
-
return jsPropertySyntax ?
|
|
1202
|
+
const isNumber2 = dataPropType === Type.Num;
|
|
1203
|
+
return jsPropertySyntax ? isNumber2 ? (0, codegen_1._)`"[" + ${dataProp} + "]"` : (0, codegen_1._)`"['" + ${dataProp} + "']"` : isNumber2 ? (0, codegen_1._)`"/" + ${dataProp}` : (0, codegen_1._)`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")`;
|
|
1204
1204
|
}
|
|
1205
1205
|
return jsPropertySyntax ? (0, codegen_1.getProperty)(dataProp).toString() : "/" + escapeJsonPointer(dataProp);
|
|
1206
1206
|
}
|
|
@@ -7267,15 +7267,15 @@ var makeIssue = (params) => {
|
|
|
7267
7267
|
message: issueData.message
|
|
7268
7268
|
};
|
|
7269
7269
|
}
|
|
7270
|
-
let
|
|
7270
|
+
let errorMessage2 = "";
|
|
7271
7271
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
7272
7272
|
for (const map2 of maps) {
|
|
7273
|
-
|
|
7273
|
+
errorMessage2 = map2(fullIssue, { data, defaultError: errorMessage2 }).message;
|
|
7274
7274
|
}
|
|
7275
7275
|
return {
|
|
7276
7276
|
...issueData,
|
|
7277
7277
|
path: fullPath,
|
|
7278
|
-
message:
|
|
7278
|
+
message: errorMessage2
|
|
7279
7279
|
};
|
|
7280
7280
|
};
|
|
7281
7281
|
function addIssueToContext(ctx, issueData) {
|
|
@@ -27067,19 +27067,19 @@ var getRefs = (options) => {
|
|
|
27067
27067
|
};
|
|
27068
27068
|
|
|
27069
27069
|
// node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
27070
|
-
function addErrorMessage(res, key,
|
|
27070
|
+
function addErrorMessage(res, key, errorMessage2, refs) {
|
|
27071
27071
|
if (!refs?.errorMessages)
|
|
27072
27072
|
return;
|
|
27073
|
-
if (
|
|
27073
|
+
if (errorMessage2) {
|
|
27074
27074
|
res.errorMessage = {
|
|
27075
27075
|
...res.errorMessage,
|
|
27076
|
-
[key]:
|
|
27076
|
+
[key]: errorMessage2
|
|
27077
27077
|
};
|
|
27078
27078
|
}
|
|
27079
27079
|
}
|
|
27080
|
-
function setResponseValueAndErrors(res, key, value,
|
|
27080
|
+
function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
|
|
27081
27081
|
res[key] = value;
|
|
27082
|
-
addErrorMessage(res, key,
|
|
27082
|
+
addErrorMessage(res, key, errorMessage2, refs);
|
|
27083
27083
|
}
|
|
27084
27084
|
|
|
27085
27085
|
// node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
|
|
@@ -28390,8 +28390,8 @@ var Protocol = class {
|
|
|
28390
28390
|
if (queuedMessage.type === "response") {
|
|
28391
28391
|
resolver(message);
|
|
28392
28392
|
} else {
|
|
28393
|
-
const
|
|
28394
|
-
const error51 = new McpError(
|
|
28393
|
+
const errorMessage2 = message;
|
|
28394
|
+
const error51 = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
|
|
28395
28395
|
resolver(error51);
|
|
28396
28396
|
}
|
|
28397
28397
|
} else {
|
|
@@ -29691,23 +29691,23 @@ var Server = class extends Protocol {
|
|
|
29691
29691
|
const wrappedHandler = async (request, extra) => {
|
|
29692
29692
|
const validatedRequest = safeParse2(CallToolRequestSchema, request);
|
|
29693
29693
|
if (!validatedRequest.success) {
|
|
29694
|
-
const
|
|
29695
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${
|
|
29694
|
+
const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
29695
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
|
|
29696
29696
|
}
|
|
29697
29697
|
const { params } = validatedRequest.data;
|
|
29698
29698
|
const result = await Promise.resolve(handler(request, extra));
|
|
29699
29699
|
if (params.task) {
|
|
29700
29700
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
29701
29701
|
if (!taskValidationResult.success) {
|
|
29702
|
-
const
|
|
29703
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${
|
|
29702
|
+
const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
29703
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
|
|
29704
29704
|
}
|
|
29705
29705
|
return taskValidationResult.data;
|
|
29706
29706
|
}
|
|
29707
29707
|
const validationResult = safeParse2(CallToolResultSchema, result);
|
|
29708
29708
|
if (!validationResult.success) {
|
|
29709
|
-
const
|
|
29710
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${
|
|
29709
|
+
const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
29710
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
|
|
29711
29711
|
}
|
|
29712
29712
|
return validationResult.data;
|
|
29713
29713
|
};
|
|
@@ -30201,12 +30201,12 @@ var McpServer = class {
|
|
|
30201
30201
|
* @param errorMessage - The error message.
|
|
30202
30202
|
* @returns The tool error result.
|
|
30203
30203
|
*/
|
|
30204
|
-
createToolError(
|
|
30204
|
+
createToolError(errorMessage2) {
|
|
30205
30205
|
return {
|
|
30206
30206
|
content: [
|
|
30207
30207
|
{
|
|
30208
30208
|
type: "text",
|
|
30209
|
-
text:
|
|
30209
|
+
text: errorMessage2
|
|
30210
30210
|
}
|
|
30211
30211
|
],
|
|
30212
30212
|
isError: true
|
|
@@ -30224,8 +30224,8 @@ var McpServer = class {
|
|
|
30224
30224
|
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
30225
30225
|
if (!parseResult.success) {
|
|
30226
30226
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30227
|
-
const
|
|
30228
|
-
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${
|
|
30227
|
+
const errorMessage2 = getParseErrorMessage(error51);
|
|
30228
|
+
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage2}`);
|
|
30229
30229
|
}
|
|
30230
30230
|
return parseResult.data;
|
|
30231
30231
|
}
|
|
@@ -30249,8 +30249,8 @@ var McpServer = class {
|
|
|
30249
30249
|
const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
|
|
30250
30250
|
if (!parseResult.success) {
|
|
30251
30251
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30252
|
-
const
|
|
30253
|
-
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${
|
|
30252
|
+
const errorMessage2 = getParseErrorMessage(error51);
|
|
30253
|
+
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage2}`);
|
|
30254
30254
|
}
|
|
30255
30255
|
}
|
|
30256
30256
|
/**
|
|
@@ -30462,8 +30462,8 @@ var McpServer = class {
|
|
|
30462
30462
|
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
30463
30463
|
if (!parseResult.success) {
|
|
30464
30464
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30465
|
-
const
|
|
30466
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${
|
|
30465
|
+
const errorMessage2 = getParseErrorMessage(error51);
|
|
30466
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage2}`);
|
|
30467
30467
|
}
|
|
30468
30468
|
const args = parseResult.data;
|
|
30469
30469
|
const cb = prompt.callback;
|
|
@@ -31563,8 +31563,9 @@ var ParseError = class extends Error {
|
|
|
31563
31563
|
}
|
|
31564
31564
|
};
|
|
31565
31565
|
var Parser = class {
|
|
31566
|
-
constructor(tokens) {
|
|
31566
|
+
constructor(tokens, capabilities = {}) {
|
|
31567
31567
|
this.tokens = tokens;
|
|
31568
|
+
this.capabilities = capabilities;
|
|
31568
31569
|
this.allowUnaryPlusNumber = false;
|
|
31569
31570
|
this.scalarAllowsAggregateArgs = true;
|
|
31570
31571
|
this.scalarAllowsCase = true;
|
|
@@ -31657,6 +31658,12 @@ var Parser = class {
|
|
|
31657
31658
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
31658
31659
|
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
31659
31660
|
if (upper === "VALIDATE") return this.parseValidate();
|
|
31661
|
+
if (upper === "IMPORT") {
|
|
31662
|
+
if (!this.capabilities.import) {
|
|
31663
|
+
throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
|
|
31664
|
+
}
|
|
31665
|
+
return this.parseImport();
|
|
31666
|
+
}
|
|
31660
31667
|
break;
|
|
31661
31668
|
}
|
|
31662
31669
|
default:
|
|
@@ -31837,11 +31844,213 @@ var Parser = class {
|
|
|
31837
31844
|
query = this.parseReorder();
|
|
31838
31845
|
} else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
|
|
31839
31846
|
query = this.parseValidate();
|
|
31847
|
+
} else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "IMPORT") {
|
|
31848
|
+
if (!this.capabilities.import) {
|
|
31849
|
+
throw new ParseError("IMPORT is not supported (capability is disabled).", tok);
|
|
31850
|
+
}
|
|
31851
|
+
query = this.parseImport();
|
|
31840
31852
|
} else {
|
|
31841
31853
|
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
31842
31854
|
}
|
|
31843
31855
|
return { type: "EXPLAIN", query };
|
|
31844
31856
|
}
|
|
31857
|
+
parseImport() {
|
|
31858
|
+
this.advance();
|
|
31859
|
+
let writeMode;
|
|
31860
|
+
if (this.peek().kind === "UPDATE" /* UPDATE */) {
|
|
31861
|
+
this.advance();
|
|
31862
|
+
writeMode = "UPDATE_RECORD_NUMBER";
|
|
31863
|
+
}
|
|
31864
|
+
this.expect("INTO" /* INTO */);
|
|
31865
|
+
this.rejectTempTableDml();
|
|
31866
|
+
const target = this.parseIdentifier();
|
|
31867
|
+
const { appId, subtableCode } = extractTableRef(target, this.prev());
|
|
31868
|
+
if (subtableCode) throw new ParseError("IMPORT does not support subtables in Phase 1.", this.prev());
|
|
31869
|
+
this.expect("(" /* LPAREN */);
|
|
31870
|
+
const targets = [];
|
|
31871
|
+
const fields = [];
|
|
31872
|
+
const targetNames = /* @__PURE__ */ new Set();
|
|
31873
|
+
while (true) {
|
|
31874
|
+
const name = this.parseIdentifier();
|
|
31875
|
+
if (targetNames.has(name)) throw new ParseError(`IMPORT target ${name} is declared more than once.`, this.prev());
|
|
31876
|
+
targetNames.add(name);
|
|
31877
|
+
if (this.peek().kind === "(" /* LPAREN */) {
|
|
31878
|
+
this.advance();
|
|
31879
|
+
const children = this.parseIdentList();
|
|
31880
|
+
this.expect(")" /* RPAREN */);
|
|
31881
|
+
if (new Set(children).size !== children.length) {
|
|
31882
|
+
throw new ParseError(`IMPORT subtable ${name} contains duplicate child declarations.`, this.prev());
|
|
31883
|
+
}
|
|
31884
|
+
let rowIdSourceHeader;
|
|
31885
|
+
if (this.isSoftKeyword("ROW")) {
|
|
31886
|
+
this.advance();
|
|
31887
|
+
for (const word of ["ID", "SOURCE"]) {
|
|
31888
|
+
if (!this.isSoftKeyword(word)) throw new ParseError(`ROW must be followed by ID SOURCE <header>.`, this.peek());
|
|
31889
|
+
this.advance();
|
|
31890
|
+
}
|
|
31891
|
+
rowIdSourceHeader = this.parseIdentifier();
|
|
31892
|
+
}
|
|
31893
|
+
targets.push({ kind: "SUBTABLE", subtableCode: name, children, ...rowIdSourceHeader ? { rowIdSourceHeader } : {} });
|
|
31894
|
+
} else {
|
|
31895
|
+
fields.push(name);
|
|
31896
|
+
targets.push({ kind: "FIELD", field: name });
|
|
31897
|
+
}
|
|
31898
|
+
if (this.peek().kind !== "," /* COMMA */) break;
|
|
31899
|
+
this.advance();
|
|
31900
|
+
}
|
|
31901
|
+
this.expect(")" /* RPAREN */);
|
|
31902
|
+
this.expect("FROM" /* FROM */);
|
|
31903
|
+
if (!this.isSoftKeyword("CSV") && !this.isSoftKeyword("JSON")) throw new ParseError("IMPORT FROM requires CSV or JSON.", this.peek());
|
|
31904
|
+
const sourceKind = this.peek().value.toUpperCase();
|
|
31905
|
+
this.advance();
|
|
31906
|
+
const sourceName = this.parseIdentifier();
|
|
31907
|
+
let encoding;
|
|
31908
|
+
let hasHeader = true;
|
|
31909
|
+
let columns;
|
|
31910
|
+
if (this.isSoftKeyword("ENCODING")) {
|
|
31911
|
+
if (sourceKind === "JSON") throw new ParseError("JSON source is UTF-8 only; ENCODING is not allowed.", this.peek());
|
|
31912
|
+
this.advance();
|
|
31913
|
+
const value = this.parseIdentifier().toUpperCase();
|
|
31914
|
+
if (value !== "UTF8" && value !== "SJIS") throw new ParseError("ENCODING must be UTF8 or SJIS.", this.prev());
|
|
31915
|
+
encoding = value === "UTF8" ? "utf8" : "sjis";
|
|
31916
|
+
}
|
|
31917
|
+
if (this.peek().kind === "NOT" /* NOT */ && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
|
|
31918
|
+
if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
|
|
31919
|
+
this.advance();
|
|
31920
|
+
this.advance();
|
|
31921
|
+
hasHeader = false;
|
|
31922
|
+
} else if (this.isSoftKeyword("NO") && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "HEADER") {
|
|
31923
|
+
if (sourceKind === "JSON") throw new ParseError("NO HEADER is CSV-only.", this.peek());
|
|
31924
|
+
this.advance();
|
|
31925
|
+
this.advance();
|
|
31926
|
+
hasHeader = false;
|
|
31927
|
+
}
|
|
31928
|
+
if (this.isSoftKeyword("COLUMNS")) {
|
|
31929
|
+
if (sourceKind === "JSON") throw new ParseError("COLUMNS is CSV-only.", this.peek());
|
|
31930
|
+
if (hasHeader) throw new ParseError("COLUMNS requires NO HEADER.", this.peek());
|
|
31931
|
+
this.advance();
|
|
31932
|
+
this.expect("(" /* LPAREN */);
|
|
31933
|
+
columns = this.parseIdentList();
|
|
31934
|
+
this.expect(")" /* RPAREN */);
|
|
31935
|
+
}
|
|
31936
|
+
let projection;
|
|
31937
|
+
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
31938
|
+
if (sourceKind === "JSON") throw new ParseError("SELECT projection is CSV-only.", this.peek());
|
|
31939
|
+
projection = this.parseSelect();
|
|
31940
|
+
if (projection.from.cteName !== NO_FROM_CTE_NAME || projection.joins.length > 0) {
|
|
31941
|
+
throw new ParseError("IMPORT projection cannot use FROM or JOIN.", this.prev());
|
|
31942
|
+
}
|
|
31943
|
+
if (projection.where || projection.groupBy.length || projection.having || projection.orderBy.length || projection.limit !== null || projection.offset !== null) {
|
|
31944
|
+
throw new ParseError("IMPORT projection supports SELECT expressions only.", this.prev());
|
|
31945
|
+
}
|
|
31946
|
+
this.validateImportProjectionScope(projection, this.prev());
|
|
31947
|
+
if (targets.some((item) => item.kind === "SUBTABLE")) {
|
|
31948
|
+
throw new ParseError("IMPORT subtable sources cannot use SELECT projection.", this.prev());
|
|
31949
|
+
}
|
|
31950
|
+
if (projection.columns.length !== fields.length) {
|
|
31951
|
+
throw new ParseError(`IMPORT projection has ${projection.columns.length} columns; target has ${fields.length}.`, this.prev());
|
|
31952
|
+
}
|
|
31953
|
+
}
|
|
31954
|
+
let mappingMode = "POSITION";
|
|
31955
|
+
let ignoreUnknownColumns = false;
|
|
31956
|
+
if (this.peek().kind === "BY" /* BY */ || this.isSoftKeyword("BY")) {
|
|
31957
|
+
if (sourceKind === "JSON") throw new ParseError("BY NAME is CSV-only.", this.peek());
|
|
31958
|
+
this.advance();
|
|
31959
|
+
if (!this.isSoftKeyword("NAME")) throw new ParseError("BY must be followed by NAME in IMPORT.", this.peek());
|
|
31960
|
+
this.advance();
|
|
31961
|
+
if (!hasHeader) throw new ParseError("BY NAME requires HEADER.", this.prev());
|
|
31962
|
+
if (projection) throw new ParseError("BY NAME and SELECT projection are mutually exclusive.", this.prev());
|
|
31963
|
+
mappingMode = "BY_NAME";
|
|
31964
|
+
if (this.isSoftKeyword("IGNORE")) {
|
|
31965
|
+
this.advance();
|
|
31966
|
+
if (!this.isSoftKeyword("UNKNOWN")) throw new ParseError("IGNORE must be followed by UNKNOWN COLUMNS.", this.peek());
|
|
31967
|
+
this.advance();
|
|
31968
|
+
if (!this.isSoftKeyword("COLUMNS")) throw new ParseError("IGNORE UNKNOWN must be followed by COLUMNS.", this.peek());
|
|
31969
|
+
this.advance();
|
|
31970
|
+
ignoreUnknownColumns = true;
|
|
31971
|
+
}
|
|
31972
|
+
}
|
|
31973
|
+
let keyFields;
|
|
31974
|
+
let recordNumberSourceHeader;
|
|
31975
|
+
if (this.isSoftKeyword("MATCH")) {
|
|
31976
|
+
this.advance();
|
|
31977
|
+
for (const word of ["RECORD", "NUMBER", "SOURCE"]) {
|
|
31978
|
+
if (!this.isSoftKeyword(word)) throw new ParseError(`MATCH must be followed by RECORD NUMBER SOURCE <header>.`, this.peek());
|
|
31979
|
+
this.advance();
|
|
31980
|
+
}
|
|
31981
|
+
recordNumberSourceHeader = this.parseIdentifier();
|
|
31982
|
+
}
|
|
31983
|
+
if (this.peek().kind === "ON" /* ON */ && this.peekAt(1).kind === "DUPLICATE" /* DUPLICATE */) keyFields = this.parseOnDuplicate();
|
|
31984
|
+
let replaceSubtables;
|
|
31985
|
+
if (this.peek().kind === "REPLACE" /* REPLACE */ || this.isSoftKeyword("REPLACE")) {
|
|
31986
|
+
this.advance();
|
|
31987
|
+
if (!this.isSoftKeyword("SUBTABLES")) throw new ParseError("REPLACE must be followed by SUBTABLES (...).", this.peek());
|
|
31988
|
+
this.advance();
|
|
31989
|
+
this.expect("(" /* LPAREN */);
|
|
31990
|
+
replaceSubtables = this.parseIdentList();
|
|
31991
|
+
this.expect(")" /* RPAREN */);
|
|
31992
|
+
if (new Set(replaceSubtables).size !== replaceSubtables.length) throw new ParseError("REPLACE SUBTABLES contains duplicates.", this.prev());
|
|
31993
|
+
}
|
|
31994
|
+
const subtableTargets = targets.filter((item) => item.kind === "SUBTABLE");
|
|
31995
|
+
if (subtableTargets.length) {
|
|
31996
|
+
if (projection) throw new ParseError("IMPORT subtables cannot use SELECT projection.", this.prev());
|
|
31997
|
+
if (sourceKind === "JSON") {
|
|
31998
|
+
if (subtableTargets.some((item) => item.rowIdSourceHeader)) throw new ParseError("JSON subtable IMPORT does not accept ROW ID SOURCE.", this.prev());
|
|
31999
|
+
if (replaceSubtables) throw new ParseError("REPLACE SUBTABLES is CSV-only; JSON uses nested-array replacement semantics.", this.prev());
|
|
32000
|
+
} else {
|
|
32001
|
+
if (writeMode !== "UPDATE_RECORD_NUMBER" || !recordNumberSourceHeader) throw new ParseError("CSV subtable IMPORT requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.", this.prev());
|
|
32002
|
+
if (mappingMode !== "BY_NAME") throw new ParseError("CSV subtable IMPORT requires BY NAME.", this.prev());
|
|
32003
|
+
if (!replaceSubtables) throw new ParseError("CSV subtable IMPORT requires REPLACE SUBTABLES (...).", this.prev());
|
|
32004
|
+
const replacement = new Set(replaceSubtables);
|
|
32005
|
+
for (const item of subtableTargets) {
|
|
32006
|
+
if (!item.rowIdSourceHeader) throw new ParseError(`CSV subtable ${item.subtableCode} requires ROW ID SOURCE <header>.`, this.prev());
|
|
32007
|
+
if (!replacement.has(item.subtableCode)) throw new ParseError(`IMPORT declares child columns for non-replaced subtable ${item.subtableCode}.`, this.prev());
|
|
32008
|
+
}
|
|
32009
|
+
for (const code of replacement) {
|
|
32010
|
+
if (!subtableTargets.some((item) => item.subtableCode === code)) throw new ParseError(`REPLACE SUBTABLES target ${code} is not declared in INTO.`, this.prev());
|
|
32011
|
+
}
|
|
32012
|
+
}
|
|
32013
|
+
} else if (replaceSubtables) {
|
|
32014
|
+
throw new ParseError("REPLACE SUBTABLES requires subtable targets in INTO.", this.prev());
|
|
32015
|
+
}
|
|
32016
|
+
if (writeMode) {
|
|
32017
|
+
if (sourceKind !== "CSV") throw new ParseError("IMPORT UPDATE supports CSV only.", this.prev());
|
|
32018
|
+
if (mappingMode !== "BY_NAME") throw new ParseError("IMPORT UPDATE requires BY NAME.", this.prev());
|
|
32019
|
+
if (!recordNumberSourceHeader) throw new ParseError("IMPORT UPDATE requires MATCH RECORD NUMBER SOURCE <header>.", this.peek());
|
|
32020
|
+
if (keyFields) throw new ParseError("IMPORT UPDATE and ON DUPLICATE are mutually exclusive.", this.prev());
|
|
32021
|
+
} else if (recordNumberSourceHeader) {
|
|
32022
|
+
throw new ParseError("MATCH RECORD NUMBER SOURCE requires IMPORT UPDATE.", this.prev());
|
|
32023
|
+
}
|
|
32024
|
+
const checkGroups = this.parseCheckGroups();
|
|
32025
|
+
const control = this.parseDmlControlSuffix();
|
|
32026
|
+
return {
|
|
32027
|
+
type: "IMPORT",
|
|
32028
|
+
appId,
|
|
32029
|
+
fields,
|
|
32030
|
+
targets,
|
|
32031
|
+
source: sourceKind === "JSON" ? { kind: "JSON", sourceName } : { kind: "CSV", sourceName, encoding, hasHeader, mappingMode, ignoreUnknownColumns, ...columns ? { columns } : {}, ...projection ? { projection } : {} },
|
|
32032
|
+
...writeMode ? { writeMode, recordNumberSourceHeader } : {},
|
|
32033
|
+
...replaceSubtables ? { replaceSubtables } : {},
|
|
32034
|
+
...keyFields ? { keyFields } : {},
|
|
32035
|
+
...checkGroups,
|
|
32036
|
+
...control
|
|
32037
|
+
};
|
|
32038
|
+
}
|
|
32039
|
+
validateImportProjectionScope(node, token) {
|
|
32040
|
+
if (Array.isArray(node)) {
|
|
32041
|
+
node.forEach((item) => this.validateImportProjectionScope(item, token));
|
|
32042
|
+
return;
|
|
32043
|
+
}
|
|
32044
|
+
if (node === null || typeof node !== "object") return;
|
|
32045
|
+
const value = node;
|
|
32046
|
+
if (value.type === "SCALAR_SUBQUERY" || value.type === "SCALAR_SUBQUERY_COL") {
|
|
32047
|
+
throw new ParseError("IMPORT projection cannot use subqueries.", token);
|
|
32048
|
+
}
|
|
32049
|
+
if (typeof value.tableAlias === "string") {
|
|
32050
|
+
throw new ParseError("IMPORT projection cannot use qualified column references.", token);
|
|
32051
|
+
}
|
|
32052
|
+
Object.values(value).forEach((item) => this.validateImportProjectionScope(item, token));
|
|
32053
|
+
}
|
|
31845
32054
|
/** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
|
|
31846
32055
|
parseValidate() {
|
|
31847
32056
|
const validateTok = this.advance();
|
|
@@ -34006,7 +34215,7 @@ function getStatementType(stmt) {
|
|
|
34006
34215
|
return typeof obj.type === "string" ? obj.type : "UNKNOWN";
|
|
34007
34216
|
}
|
|
34008
34217
|
function isDmlType(type) {
|
|
34009
|
-
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
34218
|
+
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER" || type === "IMPORT";
|
|
34010
34219
|
}
|
|
34011
34220
|
function isReadOnlyType(type) {
|
|
34012
34221
|
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";
|
|
@@ -35491,7 +35700,7 @@ function analyzeBatch(statements) {
|
|
|
35491
35700
|
dependsOn.add(at);
|
|
35492
35701
|
}
|
|
35493
35702
|
if (validationTable) {
|
|
35494
|
-
const payloadFields = stmt.type === "VALIDATE" ? ["$id", "$err_field", "$err_code", "$err_message", "$err_value"] : stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
35703
|
+
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 : [];
|
|
35495
35704
|
const signature = JSON.stringify(payloadFields);
|
|
35496
35705
|
const at = defined.get(validationTable);
|
|
35497
35706
|
if (at === void 0) {
|
|
@@ -38453,9 +38662,15 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
38453
38662
|
candidate.record ??= {};
|
|
38454
38663
|
const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
|
|
38455
38664
|
for (const code of targetFields) {
|
|
38665
|
+
if (!candidate.payload.has(code)) continue;
|
|
38456
38666
|
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
|
|
38457
38667
|
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
38458
|
-
else
|
|
38668
|
+
else {
|
|
38669
|
+
const original = candidate.payload.get(code);
|
|
38670
|
+
const type = infoByCode.get(code).fieldType;
|
|
38671
|
+
const preserveCodes = ["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"].includes(type) && Array.isArray(original) && original.every((item) => typeof item === "object" && item !== null && "code" in item);
|
|
38672
|
+
candidate.record[code] = { value: preserveCodes ? original : result.value };
|
|
38673
|
+
}
|
|
38459
38674
|
}
|
|
38460
38675
|
if (validateMissingCreateFields && candidate.mode === "create") {
|
|
38461
38676
|
for (const info of fieldInfos) {
|
|
@@ -38717,6 +38932,862 @@ function unsupported(code, field, fieldType, operator) {
|
|
|
38717
38932
|
return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
|
|
38718
38933
|
}
|
|
38719
38934
|
|
|
38935
|
+
// src/import/sourceLoader.ts
|
|
38936
|
+
var IMPORT_MAX_BYTES = 10 * 1024 * 1024;
|
|
38937
|
+
var ImportSourceError = class extends Error {
|
|
38938
|
+
constructor(message) {
|
|
38939
|
+
super(`ImportSourceError: ${message}`);
|
|
38940
|
+
this.name = "ImportSourceError";
|
|
38941
|
+
}
|
|
38942
|
+
};
|
|
38943
|
+
function resolveImportSource(name, resolver) {
|
|
38944
|
+
if (!resolver) throw new ImportSourceError("IMPORT source capability is not available.");
|
|
38945
|
+
const handle = resolver(name);
|
|
38946
|
+
if (!handle) throw new ImportSourceError(`source "${name}" is not supplied.`);
|
|
38947
|
+
return handle;
|
|
38948
|
+
}
|
|
38949
|
+
async function loadImportSource(handle, cache) {
|
|
38950
|
+
let pending = cache.get(handle);
|
|
38951
|
+
if (!pending) {
|
|
38952
|
+
pending = handle.load().then((payload) => {
|
|
38953
|
+
if (!(payload.bytes instanceof Uint8Array)) throw new ImportSourceError("loader must return Uint8Array bytes.");
|
|
38954
|
+
if (payload.bytes.byteLength > IMPORT_MAX_BYTES) {
|
|
38955
|
+
throw new ImportSourceError(`source exceeds the ${IMPORT_MAX_BYTES} byte limit.`);
|
|
38956
|
+
}
|
|
38957
|
+
return payload;
|
|
38958
|
+
});
|
|
38959
|
+
cache.set(handle, pending);
|
|
38960
|
+
}
|
|
38961
|
+
return pending;
|
|
38962
|
+
}
|
|
38963
|
+
|
|
38964
|
+
// src/import/csvDecoder.ts
|
|
38965
|
+
function decodeImportText(bytes, encoding) {
|
|
38966
|
+
try {
|
|
38967
|
+
return new TextDecoder(encoding === "sjis" ? "shift_jis" : "utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
|
|
38968
|
+
} catch {
|
|
38969
|
+
throw new ImportSourceError(`invalid ${encoding.toUpperCase()} byte sequence.`);
|
|
38970
|
+
}
|
|
38971
|
+
}
|
|
38972
|
+
function parseRfc4180(text) {
|
|
38973
|
+
const records = [];
|
|
38974
|
+
let record2 = [];
|
|
38975
|
+
let cell = "";
|
|
38976
|
+
let quoted = false;
|
|
38977
|
+
let afterQuote = false;
|
|
38978
|
+
let i = 0;
|
|
38979
|
+
const finishCell = () => {
|
|
38980
|
+
record2.push(cell);
|
|
38981
|
+
cell = "";
|
|
38982
|
+
afterQuote = false;
|
|
38983
|
+
};
|
|
38984
|
+
const finishRecord = () => {
|
|
38985
|
+
finishCell();
|
|
38986
|
+
records.push(record2);
|
|
38987
|
+
record2 = [];
|
|
38988
|
+
};
|
|
38989
|
+
while (i < text.length) {
|
|
38990
|
+
const ch = text[i];
|
|
38991
|
+
if (quoted) {
|
|
38992
|
+
if (ch === '"') {
|
|
38993
|
+
if (text[i + 1] === '"') {
|
|
38994
|
+
cell += '"';
|
|
38995
|
+
i += 2;
|
|
38996
|
+
continue;
|
|
38997
|
+
}
|
|
38998
|
+
quoted = false;
|
|
38999
|
+
afterQuote = true;
|
|
39000
|
+
i++;
|
|
39001
|
+
continue;
|
|
39002
|
+
}
|
|
39003
|
+
cell += ch;
|
|
39004
|
+
i++;
|
|
39005
|
+
continue;
|
|
39006
|
+
}
|
|
39007
|
+
if (afterQuote && ch !== "," && ch !== "\r" && ch !== "\n") {
|
|
39008
|
+
throw new ImportSourceError(`unexpected character after closing quote at offset ${i}.`);
|
|
39009
|
+
}
|
|
39010
|
+
if (ch === '"') {
|
|
39011
|
+
if (cell.length !== 0) throw new ImportSourceError(`quote in unquoted cell at offset ${i}.`);
|
|
39012
|
+
quoted = true;
|
|
39013
|
+
i++;
|
|
39014
|
+
continue;
|
|
39015
|
+
}
|
|
39016
|
+
if (ch === ",") {
|
|
39017
|
+
finishCell();
|
|
39018
|
+
i++;
|
|
39019
|
+
continue;
|
|
39020
|
+
}
|
|
39021
|
+
if (ch === "\r" || ch === "\n") {
|
|
39022
|
+
if (ch === "\r" && text[i + 1] === "\n") i++;
|
|
39023
|
+
finishRecord();
|
|
39024
|
+
i++;
|
|
39025
|
+
continue;
|
|
39026
|
+
}
|
|
39027
|
+
cell += ch;
|
|
39028
|
+
i++;
|
|
39029
|
+
}
|
|
39030
|
+
if (quoted) throw new ImportSourceError("unterminated quoted cell.");
|
|
39031
|
+
if (cell.length > 0 || record2.length > 0 || afterQuote) finishRecord();
|
|
39032
|
+
return records;
|
|
39033
|
+
}
|
|
39034
|
+
function assertColumns(columns) {
|
|
39035
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39036
|
+
columns.forEach((column, index) => {
|
|
39037
|
+
if (column === "") throw new ImportSourceError(`CSV column ${index + 1} has an empty name.`);
|
|
39038
|
+
if (seen.has(column)) throw new ImportSourceError(`CSV column name "${column}" is duplicated.`);
|
|
39039
|
+
seen.add(column);
|
|
39040
|
+
});
|
|
39041
|
+
}
|
|
39042
|
+
function decodeCsv(bytes, options) {
|
|
39043
|
+
const records = parseRfc4180(decodeImportText(bytes, options.encoding));
|
|
39044
|
+
let columns;
|
|
39045
|
+
let rows;
|
|
39046
|
+
if (options.hasHeader) {
|
|
39047
|
+
columns = records[0] ?? [];
|
|
39048
|
+
rows = records.slice(1);
|
|
39049
|
+
} else {
|
|
39050
|
+
rows = records;
|
|
39051
|
+
columns = options.columns ? [...options.columns] : Array.from({ length: rows[0]?.length ?? 0 }, (_, i) => `c${i + 1}`);
|
|
39052
|
+
}
|
|
39053
|
+
assertColumns(columns);
|
|
39054
|
+
if (rows.length === 0) throw new ImportSourceError("CSV has no data rows.");
|
|
39055
|
+
rows.forEach((row, i) => {
|
|
39056
|
+
if (row.length !== columns.length) {
|
|
39057
|
+
throw new ImportSourceError(`CSV row ${i + (options.hasHeader ? 2 : 1)} has ${row.length} cells; expected ${columns.length}.`);
|
|
39058
|
+
}
|
|
39059
|
+
});
|
|
39060
|
+
return { columns, rows };
|
|
39061
|
+
}
|
|
39062
|
+
|
|
39063
|
+
// src/import/convertImportCsvValue.ts
|
|
39064
|
+
var LF_MULTI_TYPES = /* @__PURE__ */ new Set([
|
|
39065
|
+
"CHECK_BOX",
|
|
39066
|
+
"MULTI_SELECT",
|
|
39067
|
+
"USER_SELECT",
|
|
39068
|
+
"ORGANIZATION_SELECT",
|
|
39069
|
+
"GROUP_SELECT"
|
|
39070
|
+
]);
|
|
39071
|
+
var USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
39072
|
+
var ImportCsvValueError = class extends Error {
|
|
39073
|
+
constructor() {
|
|
39074
|
+
super("multiple-value CSV cell contains an empty LF-delimited item");
|
|
39075
|
+
this.code = "ERR_IMPORT_MULTI_EMPTY_ITEM";
|
|
39076
|
+
this.name = "ImportCsvValueError";
|
|
39077
|
+
}
|
|
39078
|
+
};
|
|
39079
|
+
function convertImportCsvValue(raw, type, options) {
|
|
39080
|
+
void options;
|
|
39081
|
+
if (!LF_MULTI_TYPES.has(type ?? "")) return raw;
|
|
39082
|
+
if (raw === "") return [];
|
|
39083
|
+
const items = raw.split(/\r\n|\n/);
|
|
39084
|
+
if (items.some((item) => item === "")) throw new ImportCsvValueError();
|
|
39085
|
+
return USER_TYPES2.has(type ?? "") ? items.map((code) => ({ code })) : items;
|
|
39086
|
+
}
|
|
39087
|
+
|
|
39088
|
+
// src/import/jsonTokenizer.ts
|
|
39089
|
+
function fail(message, offset, line, column) {
|
|
39090
|
+
throw new ImportSourceError(`JSON ${message} (offset=${offset}, line=${line}, column=${column}).`);
|
|
39091
|
+
}
|
|
39092
|
+
function decodeUtf8Json(bytes) {
|
|
39093
|
+
try {
|
|
39094
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
39095
|
+
} catch {
|
|
39096
|
+
throw new ImportSourceError("JSON source is not valid UTF-8.");
|
|
39097
|
+
}
|
|
39098
|
+
}
|
|
39099
|
+
function tokenizeJson(text) {
|
|
39100
|
+
const tokens = [];
|
|
39101
|
+
let i = 0, line = 1, column = 1;
|
|
39102
|
+
const advance = () => {
|
|
39103
|
+
const ch = text[i++];
|
|
39104
|
+
if (ch === "\n") {
|
|
39105
|
+
line++;
|
|
39106
|
+
column = 1;
|
|
39107
|
+
} else column++;
|
|
39108
|
+
return ch;
|
|
39109
|
+
};
|
|
39110
|
+
const position = () => ({ offset: i, line, column });
|
|
39111
|
+
while (i < text.length) {
|
|
39112
|
+
const ch = text[i];
|
|
39113
|
+
if (ch === " " || ch === " " || ch === "\r" || ch === "\n") {
|
|
39114
|
+
advance();
|
|
39115
|
+
continue;
|
|
39116
|
+
}
|
|
39117
|
+
const start = position();
|
|
39118
|
+
if ("{}[]:,".includes(ch)) {
|
|
39119
|
+
advance();
|
|
39120
|
+
tokens.push({ kind: "punct", value: ch, ...start });
|
|
39121
|
+
continue;
|
|
39122
|
+
}
|
|
39123
|
+
if (ch === '"') {
|
|
39124
|
+
advance();
|
|
39125
|
+
let value = "";
|
|
39126
|
+
let closed = false;
|
|
39127
|
+
while (i < text.length) {
|
|
39128
|
+
const c = advance();
|
|
39129
|
+
if (c === '"') {
|
|
39130
|
+
closed = true;
|
|
39131
|
+
break;
|
|
39132
|
+
}
|
|
39133
|
+
if (c.charCodeAt(0) < 32) fail("string contains an unescaped control character", start.offset, start.line, start.column);
|
|
39134
|
+
if (c !== "\\") {
|
|
39135
|
+
value += c;
|
|
39136
|
+
continue;
|
|
39137
|
+
}
|
|
39138
|
+
if (i >= text.length) fail("string has an unterminated escape", start.offset, start.line, start.column);
|
|
39139
|
+
const esc2 = advance();
|
|
39140
|
+
const simple = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
|
|
39141
|
+
if (esc2 in simple) {
|
|
39142
|
+
value += simple[esc2];
|
|
39143
|
+
continue;
|
|
39144
|
+
}
|
|
39145
|
+
if (esc2 !== "u") fail(`has invalid escape \\${esc2}`, i - 2, line, Math.max(1, column - 2));
|
|
39146
|
+
const hex3 = text.slice(i, i + 4);
|
|
39147
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex3)) fail("has invalid unicode escape", i, line, column);
|
|
39148
|
+
for (let n = 0; n < 4; n++) advance();
|
|
39149
|
+
const code = Number.parseInt(hex3, 16);
|
|
39150
|
+
if (code >= 55296 && code <= 56319) {
|
|
39151
|
+
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);
|
|
39152
|
+
advance();
|
|
39153
|
+
advance();
|
|
39154
|
+
const lowHex = text.slice(i, i + 4);
|
|
39155
|
+
for (let n = 0; n < 4; n++) advance();
|
|
39156
|
+
const low = Number.parseInt(lowHex, 16);
|
|
39157
|
+
if (low < 56320 || low > 57343) fail("has an invalid surrogate pair", i - 4, line, Math.max(1, column - 4));
|
|
39158
|
+
value += String.fromCodePoint(65536 + (code - 55296 << 10) + low - 56320);
|
|
39159
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
39160
|
+
fail("has an unpaired low surrogate", i - 4, line, Math.max(1, column - 4));
|
|
39161
|
+
} else value += String.fromCharCode(code);
|
|
39162
|
+
}
|
|
39163
|
+
if (!closed) fail("string is unterminated", start.offset, start.line, start.column);
|
|
39164
|
+
tokens.push({ kind: "string", value, ...start });
|
|
39165
|
+
continue;
|
|
39166
|
+
}
|
|
39167
|
+
const rest = text.slice(i);
|
|
39168
|
+
const number4 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest)?.[0];
|
|
39169
|
+
if (number4) {
|
|
39170
|
+
for (let n = 0; n < number4.length; n++) advance();
|
|
39171
|
+
tokens.push({ kind: "number", lexeme: number4, ...start });
|
|
39172
|
+
continue;
|
|
39173
|
+
}
|
|
39174
|
+
const literal2 = /^(true|false|null)/.exec(rest)?.[0];
|
|
39175
|
+
if (literal2) {
|
|
39176
|
+
for (let n = 0; n < literal2.length; n++) advance();
|
|
39177
|
+
tokens.push({ kind: "literal", value: literal2 === "true" ? true : literal2 === "false" ? false : null, ...start });
|
|
39178
|
+
continue;
|
|
39179
|
+
}
|
|
39180
|
+
fail(`has an unexpected token ${JSON.stringify(ch)}`, start.offset, start.line, start.column);
|
|
39181
|
+
}
|
|
39182
|
+
tokens.push({ kind: "eof", offset: i, line, column });
|
|
39183
|
+
return tokens;
|
|
39184
|
+
}
|
|
39185
|
+
|
|
39186
|
+
// src/import/jsonDecoder.ts
|
|
39187
|
+
function describe3(token) {
|
|
39188
|
+
return token.kind === "eof" ? "end of input" : token.kind === "punct" ? token.value : token.kind;
|
|
39189
|
+
}
|
|
39190
|
+
function decodeJsonRecords(bytes) {
|
|
39191
|
+
if (bytes.byteLength === 0) throw new ImportSourceError("JSON source is empty.");
|
|
39192
|
+
const tokens = tokenizeJson(decodeUtf8Json(bytes));
|
|
39193
|
+
let index = 0;
|
|
39194
|
+
const fail3 = (message, token = tokens[index]) => {
|
|
39195
|
+
throw new ImportSourceError(`JSON ${message} (offset=${token.offset}, line=${token.line}, column=${token.column}).`);
|
|
39196
|
+
};
|
|
39197
|
+
const isPunct = (token, value) => token.kind === "punct" && token.value === value;
|
|
39198
|
+
const punct = (value) => {
|
|
39199
|
+
const token = tokens[index];
|
|
39200
|
+
if (token.kind !== "punct" || token.value !== value) fail3(`expected ${value}; found ${describe3(token)}`, token);
|
|
39201
|
+
index++;
|
|
39202
|
+
};
|
|
39203
|
+
const parseValue = () => {
|
|
39204
|
+
const token = tokens[index++];
|
|
39205
|
+
if (token.kind === "string") return token.value;
|
|
39206
|
+
if (token.kind === "number") return { kind: "number", lexeme: token.lexeme };
|
|
39207
|
+
if (token.kind === "literal") return token.value;
|
|
39208
|
+
if (token.kind === "punct" && token.value === "{") {
|
|
39209
|
+
const object3 = /* @__PURE__ */ new Map();
|
|
39210
|
+
if (isPunct(tokens[index], "}")) {
|
|
39211
|
+
index++;
|
|
39212
|
+
return object3;
|
|
39213
|
+
}
|
|
39214
|
+
while (true) {
|
|
39215
|
+
const key = tokens[index++];
|
|
39216
|
+
if (key.kind !== "string") return fail3(`object key must be a string; found ${describe3(key)}`, key);
|
|
39217
|
+
const keyValue = key.value;
|
|
39218
|
+
if (object3.has(keyValue)) fail3(`duplicate key ${JSON.stringify(keyValue)}`, key);
|
|
39219
|
+
punct(":");
|
|
39220
|
+
object3.set(keyValue, parseValue());
|
|
39221
|
+
const separator = tokens[index];
|
|
39222
|
+
if (isPunct(separator, "}")) {
|
|
39223
|
+
index++;
|
|
39224
|
+
break;
|
|
39225
|
+
}
|
|
39226
|
+
punct(",");
|
|
39227
|
+
}
|
|
39228
|
+
return object3;
|
|
39229
|
+
}
|
|
39230
|
+
if (token.kind === "punct" && token.value === "[") {
|
|
39231
|
+
const array2 = [];
|
|
39232
|
+
if (isPunct(tokens[index], "]")) {
|
|
39233
|
+
index++;
|
|
39234
|
+
return array2;
|
|
39235
|
+
}
|
|
39236
|
+
while (true) {
|
|
39237
|
+
array2.push(parseValue());
|
|
39238
|
+
const separator = tokens[index];
|
|
39239
|
+
if (isPunct(separator, "]")) {
|
|
39240
|
+
index++;
|
|
39241
|
+
break;
|
|
39242
|
+
}
|
|
39243
|
+
punct(",");
|
|
39244
|
+
}
|
|
39245
|
+
return array2;
|
|
39246
|
+
}
|
|
39247
|
+
return fail3(`expected a value; found ${describe3(token)}`, token);
|
|
39248
|
+
};
|
|
39249
|
+
const root = parseValue();
|
|
39250
|
+
if (tokens[index].kind !== "eof") fail3(`has trailing data; found ${describe3(tokens[index])}`);
|
|
39251
|
+
const records = root instanceof Map ? [root] : Array.isArray(root) ? root : fail3("root must be an object or array.", tokens[0]);
|
|
39252
|
+
if (records.length === 0) throw new ImportSourceError("JSON source contains no records.");
|
|
39253
|
+
records.forEach((record2, i) => {
|
|
39254
|
+
if (!(record2 instanceof Map)) throw new ImportSourceError(`JSON record ${i + 1} must be an object.`);
|
|
39255
|
+
});
|
|
39256
|
+
return records;
|
|
39257
|
+
}
|
|
39258
|
+
|
|
39259
|
+
// src/import/jsonMaterializer.ts
|
|
39260
|
+
var STRING_ARRAY_TYPES = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
39261
|
+
var CODE_ARRAY_TYPES = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
39262
|
+
function fail2(row, field, message) {
|
|
39263
|
+
throw new ImportSourceError(`JSON field validation failed (row=${row}, field=${field}): ${message}`);
|
|
39264
|
+
}
|
|
39265
|
+
function isNumber(value) {
|
|
39266
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map) && value.kind === "number";
|
|
39267
|
+
}
|
|
39268
|
+
function materializeValue(value, target, row) {
|
|
39269
|
+
if (value === null) return "";
|
|
39270
|
+
if (typeof value === "string") return value;
|
|
39271
|
+
if (typeof value === "boolean") fail2(row, target.code, "boolean is not accepted.");
|
|
39272
|
+
if (isNumber(value)) {
|
|
39273
|
+
if (target.fieldType === "NUMBER") fail2(row, target.code, "precision target requires a JSON string.");
|
|
39274
|
+
if (!/^-?(?:0|[1-9]\d*)$/.test(value.lexeme) || value.lexeme === "-0") {
|
|
39275
|
+
fail2(row, target.code, `JSON number ${value.lexeme} must be a non-negative-zero safe integer lexeme.`);
|
|
39276
|
+
}
|
|
39277
|
+
const number4 = Number(value.lexeme);
|
|
39278
|
+
if (!Number.isSafeInteger(number4)) fail2(row, target.code, `JSON number ${value.lexeme} is outside the safe integer range.`);
|
|
39279
|
+
return String(number4);
|
|
39280
|
+
}
|
|
39281
|
+
if (value instanceof Map) fail2(row, target.code, "object is not accepted for a flat field.");
|
|
39282
|
+
if (!Array.isArray(value)) fail2(row, target.code, "unsupported value type.");
|
|
39283
|
+
if (!STRING_ARRAY_TYPES.has(target.fieldType) && !CODE_ARRAY_TYPES.has(target.fieldType)) {
|
|
39284
|
+
fail2(row, target.code, "array is accepted only for multi-value fields.");
|
|
39285
|
+
}
|
|
39286
|
+
const strings = value.map((entry) => {
|
|
39287
|
+
if (typeof entry !== "string") fail2(row, target.code, "array elements must be strings.");
|
|
39288
|
+
return entry;
|
|
39289
|
+
});
|
|
39290
|
+
if (new Set(strings).size !== strings.length) fail2(row, target.code, "array elements must not contain duplicates.");
|
|
39291
|
+
return CODE_ARRAY_TYPES.has(target.fieldType) ? JSON.stringify(strings.map((code) => ({ code }))) : JSON.stringify(strings);
|
|
39292
|
+
}
|
|
39293
|
+
function materializeJsonDmlSource(_source, payload, targets, maxRows) {
|
|
39294
|
+
if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
|
|
39295
|
+
const records = decodeJsonRecords(payload.bytes);
|
|
39296
|
+
if (records.length > maxRows) throw new ImportSourceError(`source rows (${records.length}) exceed maxRecords (${maxRows}).`);
|
|
39297
|
+
const targetByCode = new Map(targets.map((target) => [target.code, target]));
|
|
39298
|
+
if (targetByCode.size !== targets.length) throw new ImportSourceError("JSON target fields contain duplicates.");
|
|
39299
|
+
const rows = [];
|
|
39300
|
+
const importPresence = [];
|
|
39301
|
+
records.forEach((record2, index) => {
|
|
39302
|
+
for (const key of record2.keys()) {
|
|
39303
|
+
if (!targetByCode.has(key)) fail2(index + 1, key, "unknown key (not declared in INTO).");
|
|
39304
|
+
}
|
|
39305
|
+
const row = {};
|
|
39306
|
+
const present = /* @__PURE__ */ new Set();
|
|
39307
|
+
for (const target of targets) {
|
|
39308
|
+
if (!record2.has(target.code)) continue;
|
|
39309
|
+
present.add(target.code);
|
|
39310
|
+
row[target.code] = materializeValue(record2.get(target.code), target, index + 1);
|
|
39311
|
+
}
|
|
39312
|
+
rows.push(row);
|
|
39313
|
+
importPresence.push(present);
|
|
39314
|
+
});
|
|
39315
|
+
return {
|
|
39316
|
+
rows,
|
|
39317
|
+
columns: targets.map((target) => target.code),
|
|
39318
|
+
columnMeta: new Map(targets.map((target) => [target.code, { fieldType: target.fieldType }])),
|
|
39319
|
+
importPresence
|
|
39320
|
+
};
|
|
39321
|
+
}
|
|
39322
|
+
|
|
39323
|
+
// src/import/materializeDmlSource.ts
|
|
39324
|
+
function materializeCsvDmlSource(source, payload, maxRows, targetCodes, fieldInfos, recordNumberSourceHeader) {
|
|
39325
|
+
const decoded = decodeCsv(payload.bytes, {
|
|
39326
|
+
encoding: source.encoding ?? payload.encoding ?? "utf8",
|
|
39327
|
+
hasHeader: source.hasHeader,
|
|
39328
|
+
columns: source.columns
|
|
39329
|
+
});
|
|
39330
|
+
if (decoded.rows.length > maxRows) {
|
|
39331
|
+
throw new ImportSourceError(`source rows (${decoded.rows.length}) exceed maxRecords (${maxRows}).`);
|
|
39332
|
+
}
|
|
39333
|
+
if (source.mappingMode === "BY_NAME") {
|
|
39334
|
+
if (!targetCodes || !fieldInfos) throw new Error("InternalError: BY NAME requires destination form metadata.");
|
|
39335
|
+
if (new Set(targetCodes).size !== targetCodes.length) {
|
|
39336
|
+
throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: a BY NAME header cannot be consumed more than once.");
|
|
39337
|
+
}
|
|
39338
|
+
const indexes = new Map(decoded.columns.map((column, index) => [column, index]));
|
|
39339
|
+
for (const code of targetCodes) {
|
|
39340
|
+
if (!indexes.has(code)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${code}" is missing.`);
|
|
39341
|
+
}
|
|
39342
|
+
const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
|
|
39343
|
+
const targetSet = new Set(targetCodes);
|
|
39344
|
+
if (recordNumberSourceHeader && targetSet.has(recordNumberSourceHeader)) {
|
|
39345
|
+
throw new ImportSourceError("ERR_IMPORT_HEADER_REUSED: record-number source header is lookup-only and cannot be a write target.");
|
|
39346
|
+
}
|
|
39347
|
+
if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) {
|
|
39348
|
+
throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
|
|
39349
|
+
}
|
|
39350
|
+
const ignoredKnownColumns = [];
|
|
39351
|
+
const ignoredUnknownColumns = [];
|
|
39352
|
+
const nonEmpty = (index) => decoded.rows.filter((row) => row[index] !== "").length;
|
|
39353
|
+
const reasonFor = (info) => {
|
|
39354
|
+
if (info.fieldType === "FILE") return "FILE attachment is outside flat IMPORT scope";
|
|
39355
|
+
if (info.inSubtable || info.fieldType === "SUBTABLE") return "subtable field is not writable in Phase 3";
|
|
39356
|
+
if (info.writable === false) return `non-writable ${info.fieldType} field`;
|
|
39357
|
+
return `known export-only ${info.fieldType} field`;
|
|
39358
|
+
};
|
|
39359
|
+
for (const [index, column] of decoded.columns.entries()) {
|
|
39360
|
+
if (targetSet.has(column) || column === recordNumberSourceHeader) continue;
|
|
39361
|
+
const info = infoByCode.get(column);
|
|
39362
|
+
if (info) ignoredKnownColumns.push({ column, reason: reasonFor(info), nonEmptyCells: nonEmpty(index) });
|
|
39363
|
+
else if (!source.ignoreUnknownColumns) throw new ImportSourceError(`ERR_IMPORT_UNKNOWN_COLUMN: unknown CSV header "${column}".`);
|
|
39364
|
+
else ignoredUnknownColumns.push({ column, reason: "unknown column ignored by explicit policy", nonEmptyCells: nonEmpty(index) });
|
|
39365
|
+
}
|
|
39366
|
+
for (const code of targetCodes) {
|
|
39367
|
+
const info = infoByCode.get(code);
|
|
39368
|
+
if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
39369
|
+
if (info.inSubtable || info.writable === false || info.fieldType === "FILE" || info.fieldType === "SUBTABLE") {
|
|
39370
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
39371
|
+
}
|
|
39372
|
+
}
|
|
39373
|
+
const importRowErrors = [];
|
|
39374
|
+
const rows2 = decoded.rows.map((values) => {
|
|
39375
|
+
const errors = [];
|
|
39376
|
+
const row = {};
|
|
39377
|
+
for (const code of targetCodes) {
|
|
39378
|
+
const raw = values[indexes.get(code)];
|
|
39379
|
+
try {
|
|
39380
|
+
row[code] = convertImportCsvValue(raw, infoByCode.get(code)?.fieldType, { cliKintone: true });
|
|
39381
|
+
} catch (error51) {
|
|
39382
|
+
if (!(error51 instanceof ImportCsvValueError)) throw error51;
|
|
39383
|
+
row[code] = raw;
|
|
39384
|
+
errors.push({ field: code, code: error51.code, message: error51.message });
|
|
39385
|
+
}
|
|
39386
|
+
}
|
|
39387
|
+
importRowErrors.push(errors);
|
|
39388
|
+
return row;
|
|
39389
|
+
});
|
|
39390
|
+
return {
|
|
39391
|
+
rows: rows2,
|
|
39392
|
+
columns: [...targetCodes],
|
|
39393
|
+
columnMeta: new Map(targetCodes.map((code) => [code, { fieldType: infoByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }])),
|
|
39394
|
+
importRowErrors,
|
|
39395
|
+
...recordNumberSourceHeader ? { recordNumberSourceValues: decoded.rows.map((row) => row[indexes.get(recordNumberSourceHeader)]) } : {},
|
|
39396
|
+
importAudit: { mapping: "BY_NAME", writtenColumns: [...targetCodes], ignoredKnownColumns, ignoredUnknownColumns }
|
|
39397
|
+
};
|
|
39398
|
+
}
|
|
39399
|
+
const rows = decoded.rows.map((values) => Object.fromEntries(decoded.columns.map((column, i) => [column, values[i]])));
|
|
39400
|
+
return {
|
|
39401
|
+
rows,
|
|
39402
|
+
columns: decoded.columns,
|
|
39403
|
+
// CSV cells are decoded as strings; projections such as CAST may replace this metadata downstream.
|
|
39404
|
+
columnMeta: new Map(decoded.columns.map((column) => [column, { fieldType: "SINGLE_LINE_TEXT" }]))
|
|
39405
|
+
};
|
|
39406
|
+
}
|
|
39407
|
+
|
|
39408
|
+
// src/import/importRecordsMaterializer.ts
|
|
39409
|
+
var sourceFail = (parentRow, code, message) => {
|
|
39410
|
+
throw new ImportSourceError(`JSON subtable validation failed (parentRow=${parentRow}, field=${code}): ${message}`);
|
|
39411
|
+
};
|
|
39412
|
+
function materializeJsonImportRecords(_source, payload, targets, maxParents, maxChildRows = maxParents) {
|
|
39413
|
+
if (payload.encoding && payload.encoding !== "utf8") throw new ImportSourceError("JSON source is UTF-8 only.");
|
|
39414
|
+
const decoded = decodeJsonRecords(payload.bytes);
|
|
39415
|
+
if (decoded.length > maxParents) throw new ImportSourceError(`source parent rows (${decoded.length}) exceed maxRecords (${maxParents}).`);
|
|
39416
|
+
const targetByCode = new Map(targets.map((target) => [target.kind === "FIELD" ? target.field : target.subtableCode, target]));
|
|
39417
|
+
if (targetByCode.size !== targets.length) throw new ImportSourceError("IMPORT targets contain duplicates.");
|
|
39418
|
+
let childTotal = 0;
|
|
39419
|
+
return {
|
|
39420
|
+
records: decoded.map((record2, index) => {
|
|
39421
|
+
const parentRow = index + 1;
|
|
39422
|
+
for (const code of record2.keys()) if (!targetByCode.has(code)) sourceFail(parentRow, code, "unknown key (not declared in INTO).");
|
|
39423
|
+
const top = /* @__PURE__ */ new Map();
|
|
39424
|
+
const subtables = /* @__PURE__ */ new Map();
|
|
39425
|
+
const replacementTables = /* @__PURE__ */ new Set();
|
|
39426
|
+
for (const target of targets) {
|
|
39427
|
+
const code = target.kind === "FIELD" ? target.field : target.subtableCode;
|
|
39428
|
+
if (!record2.has(code)) continue;
|
|
39429
|
+
const value = record2.get(code);
|
|
39430
|
+
if (target.kind === "FIELD") {
|
|
39431
|
+
if (value instanceof Map) sourceFail(parentRow, code, "object is not accepted for a top-level field.");
|
|
39432
|
+
top.set(code, value);
|
|
39433
|
+
continue;
|
|
39434
|
+
}
|
|
39435
|
+
if (!Array.isArray(value)) sourceFail(parentRow, code, "subtable value must be an array.");
|
|
39436
|
+
replacementTables.add(code);
|
|
39437
|
+
const children = new Set(target.children);
|
|
39438
|
+
const rows = value.map((entry, childIndex) => {
|
|
39439
|
+
if (!(entry instanceof Map)) sourceFail(parentRow, code, `childRow=${childIndex + 1} must be an object.`);
|
|
39440
|
+
const child = entry;
|
|
39441
|
+
for (const childCode of child.keys()) {
|
|
39442
|
+
if (!children.has(childCode)) sourceFail(parentRow, childCode, `unknown child key in subtable ${code} at childRow=${childIndex + 1}.`);
|
|
39443
|
+
}
|
|
39444
|
+
childTotal++;
|
|
39445
|
+
if (childTotal > maxChildRows) throw new ImportSourceError(`source child rows (${childTotal}) exceed limit (${maxChildRows}).`);
|
|
39446
|
+
return { childRowNumber: childIndex + 1, values: child };
|
|
39447
|
+
});
|
|
39448
|
+
subtables.set(code, rows);
|
|
39449
|
+
}
|
|
39450
|
+
return { rowNumber: parentRow, top, subtables, replacementTables };
|
|
39451
|
+
})
|
|
39452
|
+
};
|
|
39453
|
+
}
|
|
39454
|
+
function materializeCliKintoneCsvImportRecords(source, payload, targets, replacementTables, maxParents, recordNumberSourceHeader) {
|
|
39455
|
+
const decoded = decodeCsv(payload.bytes, {
|
|
39456
|
+
encoding: source.encoding ?? payload.encoding ?? "utf8",
|
|
39457
|
+
hasHeader: source.hasHeader,
|
|
39458
|
+
columns: source.columns
|
|
39459
|
+
});
|
|
39460
|
+
if (!source.hasHeader || decoded.columns[0] !== "*") throw new ImportSourceError('ERR_IMPORT_MARKER: first CSV header must be "*".');
|
|
39461
|
+
const indexes = new Map(decoded.columns.map((code, index) => [code, index]));
|
|
39462
|
+
if (recordNumberSourceHeader && !indexes.has(recordNumberSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${recordNumberSourceHeader}" is missing.`);
|
|
39463
|
+
const fields = targets.filter((target) => target.kind === "FIELD");
|
|
39464
|
+
const tables = targets.filter((target) => target.kind === "SUBTABLE");
|
|
39465
|
+
for (const field of fields) if (!indexes.has(field.field)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required header "${field.field}" is missing.`);
|
|
39466
|
+
for (const table of tables) {
|
|
39467
|
+
if (!table.rowIdSourceHeader || !indexes.has(table.rowIdSourceHeader)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: row-ID header for ${table.subtableCode} is missing.`);
|
|
39468
|
+
for (const child of table.children) if (!indexes.has(child)) throw new ImportSourceError(`ERR_IMPORT_MISSING_COLUMN: required child header "${child}" is missing.`);
|
|
39469
|
+
}
|
|
39470
|
+
const records = [];
|
|
39471
|
+
let current;
|
|
39472
|
+
decoded.rows.forEach((cells, physicalIndex) => {
|
|
39473
|
+
const sourceRowNumber = physicalIndex + 2;
|
|
39474
|
+
const marker = cells[0];
|
|
39475
|
+
if (marker !== "" && marker !== "*") throw new ImportSourceError(`ERR_IMPORT_MARKER: invalid marker ${JSON.stringify(marker)} at source row ${sourceRowNumber}.`);
|
|
39476
|
+
if (marker === "*") {
|
|
39477
|
+
if (records.length >= maxParents) throw new ImportSourceError(`source parent rows exceed maxRecords (${maxParents}).`);
|
|
39478
|
+
current = {
|
|
39479
|
+
rowNumber: records.length + 1,
|
|
39480
|
+
markerRowNumber: sourceRowNumber,
|
|
39481
|
+
top: new Map(fields.map((field) => [field.field, cells[indexes.get(field.field)]])),
|
|
39482
|
+
subtables: new Map(tables.map((table) => [table.subtableCode, []])),
|
|
39483
|
+
replacementTables: new Set(replacementTables),
|
|
39484
|
+
...recordNumberSourceHeader ? { recordNumberSourceValue: cells[indexes.get(recordNumberSourceHeader)] } : {}
|
|
39485
|
+
};
|
|
39486
|
+
records.push(current);
|
|
39487
|
+
} else if (!current) {
|
|
39488
|
+
throw new ImportSourceError(`ERR_IMPORT_MARKER: first data row must start a parent (source row ${sourceRowNumber}).`);
|
|
39489
|
+
} else {
|
|
39490
|
+
for (const field of fields) {
|
|
39491
|
+
const continuationValue = cells[indexes.get(field.field)];
|
|
39492
|
+
if (continuationValue !== "" && continuationValue !== current.top.get(field.field)) {
|
|
39493
|
+
throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${field.field} at source row ${sourceRowNumber}.`);
|
|
39494
|
+
}
|
|
39495
|
+
}
|
|
39496
|
+
if (recordNumberSourceHeader) {
|
|
39497
|
+
const continuationValue = cells[indexes.get(recordNumberSourceHeader)];
|
|
39498
|
+
if (continuationValue !== "" && continuationValue !== current.recordNumberSourceValue) {
|
|
39499
|
+
throw new ImportSourceError(`ERR_IMPORT_PARENT_VALUE_ON_CONTINUATION: ${recordNumberSourceHeader} at source row ${sourceRowNumber}.`);
|
|
39500
|
+
}
|
|
39501
|
+
}
|
|
39502
|
+
}
|
|
39503
|
+
for (const table of tables) {
|
|
39504
|
+
const rowId = cells[indexes.get(table.rowIdSourceHeader)];
|
|
39505
|
+
const values = new Map(table.children.map((child) => [child, cells[indexes.get(child)]]));
|
|
39506
|
+
if (rowId === "" && [...values.values()].every((value) => value === "")) continue;
|
|
39507
|
+
const rows = current.subtables.get(table.subtableCode);
|
|
39508
|
+
rows.push({ childRowNumber: rows.length + 1, sourceRowNumber, ...rowId ? { rowId } : {}, values });
|
|
39509
|
+
}
|
|
39510
|
+
});
|
|
39511
|
+
if (records.length === 0) throw new ImportSourceError("CSV has no parent rows.");
|
|
39512
|
+
return { records };
|
|
39513
|
+
}
|
|
39514
|
+
|
|
39515
|
+
// src/import/importRecordValidation.ts
|
|
39516
|
+
var USER_TYPES3 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
39517
|
+
var UNSUPPORTED_CHILD_TYPES = /* @__PURE__ */ new Set(["SUBTABLE", "FILE", "CALC", "RECORD_NUMBER", "CREATOR", "CREATED_TIME", "MODIFIER", "UPDATED_TIME", "STATUS", "STATUS_ASSIGNEE", "CATEGORY", "REFERENCE_TABLE"]);
|
|
39518
|
+
function assertImportRejectLimit(prepared, rejectLimit) {
|
|
39519
|
+
if (rejectLimit != null && prepared.invalidParentRows.size > rejectLimit) {
|
|
39520
|
+
throw new Error(`RejectLimitExceededError: rejected parents (${prepared.invalidParentRows.size}) exceed REJECT LIMIT (${rejectLimit}).`);
|
|
39521
|
+
}
|
|
39522
|
+
}
|
|
39523
|
+
function prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation) {
|
|
39524
|
+
const topInfos = new Map(fieldInfos.filter((f) => !f.inSubtable).map((f) => [f.code, f]));
|
|
39525
|
+
const scoped = /* @__PURE__ */ new Map();
|
|
39526
|
+
for (const info of fieldInfos) if (info.inSubtable && info.subtableCode) {
|
|
39527
|
+
let children = scoped.get(info.subtableCode);
|
|
39528
|
+
if (!children) scoped.set(info.subtableCode, children = /* @__PURE__ */ new Map());
|
|
39529
|
+
children.set(info.code, info);
|
|
39530
|
+
}
|
|
39531
|
+
const targetTop = targets.filter((t) => t.kind === "FIELD");
|
|
39532
|
+
const targetTables = targets.filter((t) => t.kind === "SUBTABLE");
|
|
39533
|
+
for (const target of targetTop) assertWritable(target.field, topInfos.get(target.field), void 0);
|
|
39534
|
+
for (const target of targetTables) {
|
|
39535
|
+
const table = topInfos.get(target.subtableCode);
|
|
39536
|
+
if (!table || table.fieldType !== "SUBTABLE") throw new Error(`ArgumentError: IMPORT subtable ${target.subtableCode} does not exist.`);
|
|
39537
|
+
const children = scoped.get(target.subtableCode) ?? /* @__PURE__ */ new Map();
|
|
39538
|
+
for (const child of target.children) assertWritable(child, children.get(child), target.subtableCode);
|
|
39539
|
+
}
|
|
39540
|
+
const tableCounts = new Map(targetTables.map((t) => [t.subtableCode, { parentsPresent: 0, childRows: 0, validChildRows: 0, invalidChildRows: 0 }]));
|
|
39541
|
+
const parents = materialized.records.map((record2) => validateParent(record2, targetTop, targetTables, topInfos, scoped, numberPrecision, operation, tableCounts));
|
|
39542
|
+
const errors = parents.flatMap((parent) => [...parent.errors]);
|
|
39543
|
+
return { parents, errors, invalidParentRows: new Set(parents.filter((p) => !p.valid).map((p) => p.parentRow)), tableCounts };
|
|
39544
|
+
}
|
|
39545
|
+
function validateParent(source, topTargets, tableTargets, topInfos, scoped, precision, operation, tableCounts) {
|
|
39546
|
+
const errors = [];
|
|
39547
|
+
const top = {};
|
|
39548
|
+
for (const target of topTargets) {
|
|
39549
|
+
if (!source.top.has(target.field)) continue;
|
|
39550
|
+
validateValue(source.top.get(target.field), topInfos.get(target.field), precision, top, target.field, errors, location(source, operation, target.field));
|
|
39551
|
+
}
|
|
39552
|
+
const createValidationOnly = {};
|
|
39553
|
+
if (operation === "INSERT") for (const info of topInfos.values()) {
|
|
39554
|
+
if (info.fieldType === "SUBTABLE" || info.writable === false || source.top.has(info.code)) continue;
|
|
39555
|
+
validateMissing(info, precision, createValidationOnly, errors, location(source, operation, info.code));
|
|
39556
|
+
}
|
|
39557
|
+
const subtables = /* @__PURE__ */ new Map();
|
|
39558
|
+
for (const target of tableTargets) {
|
|
39559
|
+
if (!source.subtables.has(target.subtableCode)) continue;
|
|
39560
|
+
const count = tableCounts.get(target.subtableCode);
|
|
39561
|
+
count.parentsPresent++;
|
|
39562
|
+
const preparedRows = [];
|
|
39563
|
+
for (const child of source.subtables.get(target.subtableCode)) {
|
|
39564
|
+
count.childRows++;
|
|
39565
|
+
const before = errors.length;
|
|
39566
|
+
const record2 = {};
|
|
39567
|
+
const infos = scoped.get(target.subtableCode);
|
|
39568
|
+
for (const code of target.children) {
|
|
39569
|
+
const info = infos.get(code);
|
|
39570
|
+
const loc = location(source, operation, code, target.subtableCode, child.childRowNumber, child.sourceRowNumber ?? source.markerRowNumber);
|
|
39571
|
+
if (child.values.has(code)) validateValue(child.values.get(code), info, precision, record2, code, errors, loc);
|
|
39572
|
+
else validateMissing(info, precision, record2, errors, loc);
|
|
39573
|
+
}
|
|
39574
|
+
if (errors.length === before) {
|
|
39575
|
+
count.validChildRows++;
|
|
39576
|
+
preparedRows.push(record2);
|
|
39577
|
+
} else count.invalidChildRows++;
|
|
39578
|
+
}
|
|
39579
|
+
subtables.set(target.subtableCode, preparedRows);
|
|
39580
|
+
}
|
|
39581
|
+
return { parentRow: source.rowNumber, valid: errors.length === 0, top, subtables, replacementTables: source.replacementTables, errors };
|
|
39582
|
+
}
|
|
39583
|
+
function assertWritable(code, info, table) {
|
|
39584
|
+
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.`);
|
|
39585
|
+
if (info.writable === false || table && UNSUPPORTED_CHILD_TYPES.has(info.fieldType)) {
|
|
39586
|
+
throw new Error(`ArgumentError: IMPORT ${table ? `child ${table}.${code}` : `field ${code}`} is not writable (${info.fieldType}).`);
|
|
39587
|
+
}
|
|
39588
|
+
}
|
|
39589
|
+
function validateMissing(info, precision, record2, errors, loc) {
|
|
39590
|
+
const raw = isEmptyDmlValue(info.defaultValue) ? "" : info.defaultValue;
|
|
39591
|
+
validateValue(raw, info, precision, record2, info.code, errors, loc, !isEmptyDmlValue(info.defaultValue));
|
|
39592
|
+
}
|
|
39593
|
+
function validateValue(raw, info, precision, record2, code, errors, loc, isDefault = false) {
|
|
39594
|
+
const normalizedRaw = decodeRaw(raw);
|
|
39595
|
+
const result = validateAndNormalizeDmlValue(normalizedRaw, info, precision);
|
|
39596
|
+
if (!result.ok) errors.push({ ...loc, code: result.code, message: isDefault ? `\u65E2\u5B9A\u5024: ${result.message}` : result.message });
|
|
39597
|
+
else record2[code] = { value: preserveUserCodes(normalizedRaw, info) ? normalizedRaw : result.value };
|
|
39598
|
+
}
|
|
39599
|
+
function decodeRaw(raw) {
|
|
39600
|
+
if (isJsonNumber(raw)) return raw.lexeme;
|
|
39601
|
+
if (Array.isArray(raw)) return raw.map((value) => value instanceof Map ? value : isJsonNumber(value) ? value.lexeme : value);
|
|
39602
|
+
return raw;
|
|
39603
|
+
}
|
|
39604
|
+
function isJsonNumber(raw) {
|
|
39605
|
+
return typeof raw === "object" && raw !== null && raw.kind === "number";
|
|
39606
|
+
}
|
|
39607
|
+
function preserveUserCodes(raw, info) {
|
|
39608
|
+
return USER_TYPES3.has(info.fieldType) && Array.isArray(raw) && raw.every((v) => typeof v === "object" && v !== null && "code" in v);
|
|
39609
|
+
}
|
|
39610
|
+
function location(source, operation, field, subtable, subrow, sourceRow) {
|
|
39611
|
+
const physicalRow = sourceRow ?? source.markerRowNumber;
|
|
39612
|
+
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 };
|
|
39613
|
+
}
|
|
39614
|
+
|
|
39615
|
+
// src/import/importErrors.ts
|
|
39616
|
+
var IMPORT_VALIDATION_META_COLUMNS = [
|
|
39617
|
+
"$err_statement",
|
|
39618
|
+
"$err_operation",
|
|
39619
|
+
"$err_row",
|
|
39620
|
+
"$err_field",
|
|
39621
|
+
"$err_subtable",
|
|
39622
|
+
"$err_subrow",
|
|
39623
|
+
"$err_source_row",
|
|
39624
|
+
"$err_code",
|
|
39625
|
+
"$err_message"
|
|
39626
|
+
];
|
|
39627
|
+
function materializeImportValidationErrors(errors, payloadFields, statementNumber = 1) {
|
|
39628
|
+
return errors.map((error51) => {
|
|
39629
|
+
const row = {};
|
|
39630
|
+
for (const field of payloadFields) row[field] = error51.sourceValues.get(field) == null ? "" : render(error51.sourceValues.get(field));
|
|
39631
|
+
row["$err_statement"] = String(statementNumber);
|
|
39632
|
+
row["$err_operation"] = error51.operation;
|
|
39633
|
+
row["$err_row"] = String(error51.parentRow);
|
|
39634
|
+
row["$err_field"] = error51.field;
|
|
39635
|
+
row["$err_subtable"] = error51.subtable ?? "";
|
|
39636
|
+
row["$err_subrow"] = error51.subrow == null ? "" : String(error51.subrow);
|
|
39637
|
+
row["$err_source_row"] = error51.sourceRow == null ? null : String(error51.sourceRow);
|
|
39638
|
+
row["$err_code"] = error51.code;
|
|
39639
|
+
row["$err_message"] = error51.message;
|
|
39640
|
+
return row;
|
|
39641
|
+
});
|
|
39642
|
+
}
|
|
39643
|
+
function render(value) {
|
|
39644
|
+
if (value === null || value === void 0) return "";
|
|
39645
|
+
if (typeof value === "object" && value !== null && "kind" in value && "lexeme" in value && value.kind === "number") {
|
|
39646
|
+
return String(value.lexeme);
|
|
39647
|
+
}
|
|
39648
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
39649
|
+
return String(value);
|
|
39650
|
+
}
|
|
39651
|
+
|
|
39652
|
+
// src/import/subtablePayload.ts
|
|
39653
|
+
function buildImportRecordPayload(top, subtables, rowIdMode) {
|
|
39654
|
+
const record2 = {};
|
|
39655
|
+
for (const [code, value] of top) record2[code] = { value };
|
|
39656
|
+
for (const [tableCode, sourceRows] of subtables) {
|
|
39657
|
+
record2[tableCode] = {
|
|
39658
|
+
value: sourceRows.map((sourceRow) => ({
|
|
39659
|
+
...rowIdMode === "PRESERVE" && sourceRow.rowId ? { id: sourceRow.rowId } : {},
|
|
39660
|
+
value: Object.fromEntries([...sourceRow.values].map(([childCode, value]) => [childCode, { value }]))
|
|
39661
|
+
}))
|
|
39662
|
+
};
|
|
39663
|
+
}
|
|
39664
|
+
return record2;
|
|
39665
|
+
}
|
|
39666
|
+
function buildJsonImportRecordPayload(top, subtables) {
|
|
39667
|
+
return buildImportRecordPayload(top, subtables, "DROP");
|
|
39668
|
+
}
|
|
39669
|
+
|
|
39670
|
+
// src/import/jsonSubtableWritePlan.ts
|
|
39671
|
+
function assertJsonImportHasNoRowIds(materialized) {
|
|
39672
|
+
for (const parent of materialized.records) for (const [table, rows] of parent.subtables) {
|
|
39673
|
+
for (const row of rows) {
|
|
39674
|
+
if (row.rowId !== void 0 || row.values.has("_rid") || row.values.has("id")) {
|
|
39675
|
+
throw new Error(`ArgumentError: JSON IMPORT subtable ${table} does not accept _rid/id; rows are always newly numbered.`);
|
|
39676
|
+
}
|
|
39677
|
+
}
|
|
39678
|
+
}
|
|
39679
|
+
}
|
|
39680
|
+
function buildJsonSubtableWritePlan(parents, targetIds, existingById) {
|
|
39681
|
+
return parents.map((parent, index) => {
|
|
39682
|
+
const targetId = targetIds[index];
|
|
39683
|
+
const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
|
|
39684
|
+
if (targetId !== void 0 && !existing) throw new Error(`InternalError: IMPORT UPSERT target APP record ${targetId} was not loaded.`);
|
|
39685
|
+
const tables = [...parent.subtables].map(([table, input]) => {
|
|
39686
|
+
const raw = existing?.record[table]?.value;
|
|
39687
|
+
const existingRows = Array.isArray(raw) ? raw.length : 0;
|
|
39688
|
+
return { table, existingRows, inputRows: input.length, addRows: input.length, deleteRows: existingRows };
|
|
39689
|
+
});
|
|
39690
|
+
return {
|
|
39691
|
+
parentRow: parent.parentRow,
|
|
39692
|
+
mode: targetId === void 0 ? "INSERT" : "UPDATE",
|
|
39693
|
+
...targetId === void 0 ? {} : { targetId, revision: existing?.revision },
|
|
39694
|
+
top: parent.top,
|
|
39695
|
+
subtables: parent.subtables,
|
|
39696
|
+
tables
|
|
39697
|
+
};
|
|
39698
|
+
});
|
|
39699
|
+
}
|
|
39700
|
+
|
|
39701
|
+
// src/import/subtableReplacementPlan.ts
|
|
39702
|
+
function tableRows(record2, table) {
|
|
39703
|
+
const raw = record2[table]?.value;
|
|
39704
|
+
return Array.isArray(raw) ? raw : [];
|
|
39705
|
+
}
|
|
39706
|
+
function assertNoDuplicateCsvSubtableRowIds(records) {
|
|
39707
|
+
const seen = /* @__PURE__ */ new Map();
|
|
39708
|
+
for (const parent of records) for (const [table, rows] of parent.subtables) for (const row of rows) {
|
|
39709
|
+
if (!row.rowId) continue;
|
|
39710
|
+
const key = `${table}\0${row.rowId}`;
|
|
39711
|
+
if (seen.has(key)) throw new Error(`ERR_SUBTABLE_ROW_ID_DUP_SOURCE: duplicate row ID ${row.rowId} in ${table}`);
|
|
39712
|
+
seen.set(key, parent.rowNumber);
|
|
39713
|
+
}
|
|
39714
|
+
}
|
|
39715
|
+
function buildCsvSubtableReplacementPlan(sources, prepared, targetIds, existingById, ownership) {
|
|
39716
|
+
return prepared.map((parent, index) => {
|
|
39717
|
+
const source = sources[index];
|
|
39718
|
+
const targetId = targetIds[index];
|
|
39719
|
+
const existing = targetId === void 0 ? void 0 : existingById.get(targetId);
|
|
39720
|
+
const errors = [...parent.errors];
|
|
39721
|
+
if (!existing || targetId === void 0) return { parentRow: parent.parentRow, targetId: targetId ?? 0, valid: false, top: parent.top, subtables: /* @__PURE__ */ new Map(), tables: [], errors };
|
|
39722
|
+
const subtables = /* @__PURE__ */ new Map();
|
|
39723
|
+
const tables = [];
|
|
39724
|
+
for (const table of parent.replacementTables) {
|
|
39725
|
+
const current = tableRows(existing.record, table);
|
|
39726
|
+
const currentIds = new Set(current.map((row) => row.id).filter((id) => !!id));
|
|
39727
|
+
const input = source.subtables.get(table) ?? [];
|
|
39728
|
+
const normalized = parent.subtables.get(table) ?? [];
|
|
39729
|
+
let updateRows = 0, addRows = 0, rowIdNotFound = 0;
|
|
39730
|
+
const payloadRows = input.map((row, rowIndex) => {
|
|
39731
|
+
const normalizedRecord = normalized[rowIndex] ?? {};
|
|
39732
|
+
if (row.rowId && currentIds.has(row.rowId)) {
|
|
39733
|
+
updateRows++;
|
|
39734
|
+
return { rowId: row.rowId, record: normalizedRecord };
|
|
39735
|
+
}
|
|
39736
|
+
if (row.rowId) {
|
|
39737
|
+
const owners = ownership.get(row.rowId) ?? [];
|
|
39738
|
+
if (owners.some((owner) => owner.parentId !== targetId || owner.table !== table)) errors.push({
|
|
39739
|
+
operation: "UPDATE",
|
|
39740
|
+
parentRow: parent.parentRow,
|
|
39741
|
+
field: row.rowId,
|
|
39742
|
+
subtable: table,
|
|
39743
|
+
subrow: row.childRowNumber,
|
|
39744
|
+
sourceRow: row.sourceRowNumber,
|
|
39745
|
+
code: "ERR_IMPORT_FIELD_OWNERSHIP",
|
|
39746
|
+
message: `rowIdOwnedElsewhere: ${row.rowId}`,
|
|
39747
|
+
sourceValues: row.values
|
|
39748
|
+
});
|
|
39749
|
+
rowIdNotFound++;
|
|
39750
|
+
}
|
|
39751
|
+
addRows++;
|
|
39752
|
+
return { record: normalizedRecord };
|
|
39753
|
+
});
|
|
39754
|
+
subtables.set(table, payloadRows);
|
|
39755
|
+
tables.push({ table, existingRows: current.length, inputRows: input.length, updateRows, addRows, deleteRows: current.length - updateRows, rowIdNotFound });
|
|
39756
|
+
}
|
|
39757
|
+
return { parentRow: parent.parentRow, targetId, ...existing.revision === void 0 ? {} : { revision: existing.revision }, valid: errors.length === 0, top: parent.top, subtables, tables, errors };
|
|
39758
|
+
});
|
|
39759
|
+
}
|
|
39760
|
+
|
|
39761
|
+
// src/import/importProjection.ts
|
|
39762
|
+
var IMPORT_PROJECTION_SOURCE = "#__import_source";
|
|
39763
|
+
function bindImportProjection(projection) {
|
|
39764
|
+
return { ...projection, from: { appId: 0, alias: null, cteName: IMPORT_PROJECTION_SOURCE } };
|
|
39765
|
+
}
|
|
39766
|
+
|
|
39767
|
+
// src/import/recordNumberUpdate.ts
|
|
39768
|
+
function normalizeImportRecordNumber(raw) {
|
|
39769
|
+
return /^[0-9]+$/.test(raw) ? raw.replace(/^0+(?=\d)/, "") : null;
|
|
39770
|
+
}
|
|
39771
|
+
function preflightImportRecordNumbers(values, header) {
|
|
39772
|
+
const normalized = values.map(normalizeImportRecordNumber);
|
|
39773
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39774
|
+
for (const key of normalized) {
|
|
39775
|
+
if (key === null) continue;
|
|
39776
|
+
if (seen.has(key)) {
|
|
39777
|
+
throw new Error("ERR_RECORD_NUMBER_DUP_SOURCE: source contains a duplicate record number");
|
|
39778
|
+
}
|
|
39779
|
+
seen.add(key);
|
|
39780
|
+
}
|
|
39781
|
+
return {
|
|
39782
|
+
normalized,
|
|
39783
|
+
errors: normalized.map((key) => key === null ? [{
|
|
39784
|
+
field: header,
|
|
39785
|
+
code: "ERR_RECORD_NUMBER_INVALID",
|
|
39786
|
+
message: `${header} must be a non-empty ASCII decimal record number`
|
|
39787
|
+
}] : [])
|
|
39788
|
+
};
|
|
39789
|
+
}
|
|
39790
|
+
|
|
38720
39791
|
// src/execute.ts
|
|
38721
39792
|
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";
|
|
38722
39793
|
var SearchAbortedError = class extends Error {
|
|
@@ -38727,6 +39798,7 @@ var SearchAbortedError = class extends Error {
|
|
|
38727
39798
|
};
|
|
38728
39799
|
var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
|
|
38729
39800
|
var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
|
|
39801
|
+
var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
|
|
38730
39802
|
var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
|
|
38731
39803
|
var nextDefaultCacheContextId = 1;
|
|
38732
39804
|
function resolveCacheContext(client, explicit) {
|
|
@@ -38741,7 +39813,7 @@ function resolveCacheContext(client, explicit) {
|
|
|
38741
39813
|
async function execute(sql, client, options = {}) {
|
|
38742
39814
|
const startedAt = Date.now();
|
|
38743
39815
|
const cacheContext = resolveCacheContext(client, options.cacheContext);
|
|
38744
|
-
const stmt = parseSql(sql);
|
|
39816
|
+
const stmt = parseSql(sql, options.enableImport === true);
|
|
38745
39817
|
const metrics = createEmptyMetrics();
|
|
38746
39818
|
const countedClient = wrapClientWithMetrics(client, metrics);
|
|
38747
39819
|
const collector = { aborted: false };
|
|
@@ -38921,6 +39993,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38921
39993
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
38922
39994
|
}
|
|
38923
39995
|
validateKlikeStatement(stmt);
|
|
39996
|
+
if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
|
|
38924
39997
|
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
38925
39998
|
if (stmt.validationErrorTable) {
|
|
38926
39999
|
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
@@ -39143,7 +40216,7 @@ var BatchTimeoutError = class extends Error {
|
|
|
39143
40216
|
}
|
|
39144
40217
|
};
|
|
39145
40218
|
async function executeBatch(sql, client, options = {}) {
|
|
39146
|
-
const statements = parseSqlBatch(sql);
|
|
40219
|
+
const statements = parseSqlBatch(sql, options.enableImport === true);
|
|
39147
40220
|
const analysis = analyzeBatch(statements);
|
|
39148
40221
|
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
39149
40222
|
const batchOptions = { ...options, variables: injectedVariables };
|
|
@@ -39196,11 +40269,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
39196
40269
|
const userConfirm = batchOptions.confirm;
|
|
39197
40270
|
const stmtOptions = userConfirm ? {
|
|
39198
40271
|
...batchOptions,
|
|
39199
|
-
confirm: (count, operation) => userConfirm(count, operation, {
|
|
40272
|
+
confirm: (count, operation, detailContext) => userConfirm(count, operation, {
|
|
39200
40273
|
statementIndex: i,
|
|
39201
40274
|
statementCount: statements.length,
|
|
39202
40275
|
statementType: info.statementType,
|
|
39203
|
-
targetAppId: info.targetAppId
|
|
40276
|
+
targetAppId: info.targetAppId,
|
|
40277
|
+
...detailContext?.importDetail ? { importDetail: detailContext.importDetail } : {}
|
|
39204
40278
|
})
|
|
39205
40279
|
} : batchOptions;
|
|
39206
40280
|
const searchAbortCollector = { aborted: false };
|
|
@@ -39314,6 +40388,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
39314
40388
|
}
|
|
39315
40389
|
return { result };
|
|
39316
40390
|
}
|
|
40391
|
+
if (resolvedStmt.type === "IMPORT") {
|
|
40392
|
+
return { result: await executeImport(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
40393
|
+
}
|
|
39317
40394
|
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
39318
40395
|
const result = await executeDmlValidation(
|
|
39319
40396
|
resolvedStmt,
|
|
@@ -39475,9 +40552,9 @@ function safeJsonStringify(v) {
|
|
|
39475
40552
|
return String(v);
|
|
39476
40553
|
}
|
|
39477
40554
|
}
|
|
39478
|
-
function parseSqlBatch(sql) {
|
|
40555
|
+
function parseSqlBatch(sql, enableImport = false) {
|
|
39479
40556
|
const tokens = new Lexer(sql).tokenize();
|
|
39480
|
-
return new Parser(tokens).parseStatements();
|
|
40557
|
+
return new Parser(tokens, { import: enableImport }).parseStatements();
|
|
39481
40558
|
}
|
|
39482
40559
|
function evaluateScalarExpr(expr) {
|
|
39483
40560
|
switch (expr.type) {
|
|
@@ -40546,8 +41623,14 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
|
|
|
40546
41623
|
}
|
|
40547
41624
|
} else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
|
|
40548
41625
|
meta3 = syntheticColumnMeta("number");
|
|
40549
|
-
} else if (column.type === "LITERAL_COL"
|
|
41626
|
+
} else if (column.type === "LITERAL_COL") {
|
|
40550
41627
|
meta3 = syntheticColumnMeta("string");
|
|
41628
|
+
} else if (column.type === "SCALAR_VALUE_COL") {
|
|
41629
|
+
const expr = column.expr;
|
|
41630
|
+
if (expr.type === "STRING_FUNC") meta3 = stringFunctionColumnMeta(expr);
|
|
41631
|
+
else if (expr.type === "NUMBER" || expr.type === "SCALAR_ARITH") meta3 = syntheticColumnMeta("number");
|
|
41632
|
+
else if (expr.type === "FIELD") meta3 = resolveField2(expr);
|
|
41633
|
+
else meta3 = syntheticColumnMeta("string");
|
|
40551
41634
|
} else if (column.type === "STRFUNC_COL") {
|
|
40552
41635
|
meta3 = stringFunctionColumnMeta(column.expr);
|
|
40553
41636
|
} else if (column.type === "WINDOW_COL") {
|
|
@@ -41361,9 +42444,10 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
41361
42444
|
return sortKinds;
|
|
41362
42445
|
}
|
|
41363
42446
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
41364
|
-
|
|
42447
|
+
if (typeof raw !== "string") return raw;
|
|
42448
|
+
const USER_TYPES4 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
41365
42449
|
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
41366
|
-
if (
|
|
42450
|
+
if (USER_TYPES4.has(dstFieldType ?? "")) {
|
|
41367
42451
|
if (raw === "") return [];
|
|
41368
42452
|
try {
|
|
41369
42453
|
const parsed = JSON.parse(raw);
|
|
@@ -41428,11 +42512,13 @@ function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecisio
|
|
|
41428
42512
|
records.forEach((record2, rowIndex) => {
|
|
41429
42513
|
for (const code of targetFields) {
|
|
41430
42514
|
const info = infoByCode.get(code);
|
|
41431
|
-
const
|
|
42515
|
+
const original = record2[code]?.value ?? "";
|
|
42516
|
+
const result = validateAndNormalizeDmlValue(original, info, numberPrecision);
|
|
41432
42517
|
if (!result.ok) {
|
|
41433
42518
|
throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
|
|
41434
42519
|
}
|
|
41435
|
-
|
|
42520
|
+
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);
|
|
42521
|
+
record2[code] = { value: preserveCodes ? original : result.value };
|
|
41436
42522
|
}
|
|
41437
42523
|
});
|
|
41438
42524
|
}
|
|
@@ -41592,6 +42678,8 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41592
42678
|
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
41593
42679
|
let rows;
|
|
41594
42680
|
let sourceRows;
|
|
42681
|
+
let sourcePresence;
|
|
42682
|
+
let sourceRowErrors;
|
|
41595
42683
|
let evaluationTypes;
|
|
41596
42684
|
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
41597
42685
|
assertInsertCheckRefs(stmt, stmt.fields);
|
|
@@ -41601,7 +42689,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41601
42689
|
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
41602
42690
|
));
|
|
41603
42691
|
} else {
|
|
41604
|
-
const selectResult =
|
|
42692
|
+
const selectResult = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, tempTables, [...infoByCode.values()]);
|
|
41605
42693
|
const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
|
|
41606
42694
|
if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
|
|
41607
42695
|
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`);
|
|
@@ -41611,7 +42699,9 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41611
42699
|
}
|
|
41612
42700
|
assertInsertCheckRefs(stmt, selectResult.columns);
|
|
41613
42701
|
sourceRows = selectResult.rows;
|
|
41614
|
-
|
|
42702
|
+
sourcePresence = selectResult.importPresence;
|
|
42703
|
+
sourceRowErrors = selectResult.importRowErrors;
|
|
42704
|
+
const meta3 = selectResult.columnMeta;
|
|
41615
42705
|
evaluationTypes = new Map(selectResult.columns.map((column) => {
|
|
41616
42706
|
const columnMeta = meta3?.get(column);
|
|
41617
42707
|
const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
|
|
@@ -41624,8 +42714,10 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41624
42714
|
rowNumber: index + 1,
|
|
41625
42715
|
operation,
|
|
41626
42716
|
mode: "create",
|
|
41627
|
-
payload: new Map(stmt.fields.
|
|
41628
|
-
|
|
42717
|
+
payload: new Map(stmt.fields.flatMap(
|
|
42718
|
+
(field, i) => sourcePresence && !sourcePresence[index]?.has(field) ? [] : [[field, values[i]]]
|
|
42719
|
+
)),
|
|
42720
|
+
preErrors: [...sourceRowErrors?.[index] ?? []],
|
|
41629
42721
|
record: {},
|
|
41630
42722
|
evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
|
|
41631
42723
|
stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
|
|
@@ -41638,13 +42730,17 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41638
42730
|
}
|
|
41639
42731
|
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
41640
42732
|
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
41641
|
-
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
41642
42733
|
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
41643
42734
|
const keyCounts = /* @__PURE__ */ new Map();
|
|
41644
42735
|
for (const parts of rowKeys) {
|
|
41645
42736
|
const key = upsertNormalizedKey(parts, numeric);
|
|
41646
42737
|
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
41647
42738
|
}
|
|
42739
|
+
const isImport = importSourceByDmlStatement.has(stmt);
|
|
42740
|
+
if (isImport && [...keyCounts.values()].some((count) => count > 1)) {
|
|
42741
|
+
throw new Error("ERR_KEY_DUP_SOURCE: UPSERT \u30BD\u30FC\u30B9\u5185\u3067\u30AD\u30FC\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059");
|
|
42742
|
+
}
|
|
42743
|
+
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
41648
42744
|
candidates.forEach((candidate, index) => {
|
|
41649
42745
|
const parts = rowKeys[index];
|
|
41650
42746
|
const targetId = lookupUpsertTarget(targets, parts);
|
|
@@ -41653,7 +42749,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41653
42749
|
stmt.keyFields.forEach((key, keyIndex) => {
|
|
41654
42750
|
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` });
|
|
41655
42751
|
});
|
|
41656
|
-
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
42752
|
+
if (!isImport && (keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
41657
42753
|
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" });
|
|
41658
42754
|
}
|
|
41659
42755
|
});
|
|
@@ -42013,12 +43109,508 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
42013
43109
|
insertedCount: createdIds.flat().length
|
|
42014
43110
|
};
|
|
42015
43111
|
}
|
|
43112
|
+
function importPlaceholderSelect() {
|
|
43113
|
+
return {
|
|
43114
|
+
type: "SELECT",
|
|
43115
|
+
distinct: false,
|
|
43116
|
+
columns: [],
|
|
43117
|
+
from: { appId: 0, alias: null, cteName: NO_FROM_CTE_NAME },
|
|
43118
|
+
joins: [],
|
|
43119
|
+
where: null,
|
|
43120
|
+
groupBy: [],
|
|
43121
|
+
having: null,
|
|
43122
|
+
orderMode: "CANONICAL",
|
|
43123
|
+
orderBy: [],
|
|
43124
|
+
limit: null,
|
|
43125
|
+
offset: null
|
|
43126
|
+
};
|
|
43127
|
+
}
|
|
43128
|
+
async function executeImport(stmt, client, options, cacheContext, tempTables) {
|
|
43129
|
+
if (!options.enableImport) throw new Error("UnsupportedError: IMPORT capability is disabled.");
|
|
43130
|
+
const handle = resolveImportSource(stmt.source.sourceName, options.importSource);
|
|
43131
|
+
if (stmt.targets?.some((target) => target.kind === "SUBTABLE")) {
|
|
43132
|
+
if (!stmt.validateOnly && !options.supportsImportConfirmDetail) {
|
|
43133
|
+
throw new Error("UnsupportedError: IMPORT subtable mutation requires a surface that displays parent/table replacement and deletion detail; use VALIDATE ONLY/EXPLAIN.");
|
|
43134
|
+
}
|
|
43135
|
+
if (stmt.source.kind === "CSV") {
|
|
43136
|
+
if (stmt.writeMode !== "UPDATE_RECORD_NUMBER" || !stmt.recordNumberSourceHeader) throw new Error("ArgumentError: CSV subtable replacement requires IMPORT UPDATE and MATCH RECORD NUMBER SOURCE.");
|
|
43137
|
+
if (!stmt.replaceSubtables?.length) throw new Error("ArgumentError: CSV subtable replacement requires REPLACE SUBTABLES (...).");
|
|
43138
|
+
const declared = new Set(stmt.targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode));
|
|
43139
|
+
if (stmt.replaceSubtables.some((table) => !declared.has(table))) throw new Error("ArgumentError: REPLACE SUBTABLES contains a table not declared in INTO.");
|
|
43140
|
+
for (const target of stmt.targets.filter((target2) => target2.kind === "SUBTABLE")) {
|
|
43141
|
+
if (!target.rowIdSourceHeader || !stmt.replaceSubtables.includes(target.subtableCode)) throw new Error(`ArgumentError: CSV subtable ${target.subtableCode} requires ROW ID SOURCE and REPLACE SUBTABLES declaration.`);
|
|
43142
|
+
}
|
|
43143
|
+
}
|
|
43144
|
+
if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
43145
|
+
const targets = stmt.targets;
|
|
43146
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
43147
|
+
const targetCodes = targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children);
|
|
43148
|
+
const numberPrecision = fieldInfos.some((info) => targetCodes.includes(info.code) && info.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
|
|
43149
|
+
const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
|
|
43150
|
+
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);
|
|
43151
|
+
const operation = stmt.source.kind === "CSV" ? "UPDATE" : stmt.keyFields ? "UPSERT" : "INSERT";
|
|
43152
|
+
const prepared = prepareImportRecords(materialized, targets, fieldInfos, numberPrecision, operation);
|
|
43153
|
+
if (stmt.source.kind === "CSV") return executeCsvSubtableReplacement(stmt, materialized, prepared, fieldInfos, client, options, tempTables);
|
|
43154
|
+
assertImportRejectLimit(prepared, stmt.rejectLimit);
|
|
43155
|
+
const payloadFields = [...new Set(targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
|
|
43156
|
+
const errors = materializeImportValidationErrors(prepared.errors, payloadFields);
|
|
43157
|
+
const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
|
|
43158
|
+
const invalidRows = prepared.invalidParentRows.size;
|
|
43159
|
+
const detail = {
|
|
43160
|
+
preflight: "ACTUAL_DATA",
|
|
43161
|
+
parents: { total: prepared.parents.length, valid: prepared.parents.length - invalidRows, invalid: invalidRows, mutationCandidates: prepared.parents.filter((parent) => parent.valid).length },
|
|
43162
|
+
tables: Object.fromEntries(prepared.tableCounts),
|
|
43163
|
+
writesKintone: false
|
|
43164
|
+
};
|
|
43165
|
+
const result2 = {
|
|
43166
|
+
type: "VALIDATION",
|
|
43167
|
+
operation,
|
|
43168
|
+
validatedRows: prepared.parents.length,
|
|
43169
|
+
validRows: prepared.parents.length - invalidRows,
|
|
43170
|
+
invalidRows,
|
|
43171
|
+
errorCount: errors.length,
|
|
43172
|
+
columns,
|
|
43173
|
+
errors,
|
|
43174
|
+
importDetail: detail,
|
|
43175
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {}
|
|
43176
|
+
};
|
|
43177
|
+
if (stmt.validationErrorTable && tempTables) appendValidationErrors(
|
|
43178
|
+
tempTables,
|
|
43179
|
+
stmt.validationErrorTable,
|
|
43180
|
+
columns,
|
|
43181
|
+
errors,
|
|
43182
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
43183
|
+
/* @__PURE__ */ new Map()
|
|
43184
|
+
);
|
|
43185
|
+
if (stmt.validateOnly) return result2;
|
|
43186
|
+
assertJsonImportHasNoRowIds(materialized);
|
|
43187
|
+
if (prepared.errors.length > 0 && !stmt.onErrorSkip) {
|
|
43188
|
+
const first = prepared.errors[0];
|
|
43189
|
+
throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
|
|
43190
|
+
}
|
|
43191
|
+
if (stmt.onErrorSkip) {
|
|
43192
|
+
if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
|
|
43193
|
+
appendValidationErrors(
|
|
43194
|
+
tempTables,
|
|
43195
|
+
stmt.errorTable,
|
|
43196
|
+
columns,
|
|
43197
|
+
errors,
|
|
43198
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
43199
|
+
/* @__PURE__ */ new Map()
|
|
43200
|
+
);
|
|
43201
|
+
}
|
|
43202
|
+
const validParents = prepared.parents.filter((parent) => parent.valid);
|
|
43203
|
+
const fieldTypes = new Map(fieldInfos.map((info) => [info.code, info.fieldType]));
|
|
43204
|
+
const targetIds = validParents.map(() => void 0);
|
|
43205
|
+
if (stmt.keyFields) {
|
|
43206
|
+
for (const key of stmt.keyFields) if (!stmt.fields.includes(key)) {
|
|
43207
|
+
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`);
|
|
43208
|
+
}
|
|
43209
|
+
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
43210
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
43211
|
+
const rowKeys = validParents.map((parent) => stmt.keyFields.map((key) => String(parent.top[key]?.value ?? "")));
|
|
43212
|
+
for (const parts of rowKeys) {
|
|
43213
|
+
const normalized = upsertNormalizedKey(parts, numeric);
|
|
43214
|
+
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");
|
|
43215
|
+
sourceKeys.add(normalized);
|
|
43216
|
+
}
|
|
43217
|
+
const targetsIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
43218
|
+
rowKeys.forEach((parts, index) => {
|
|
43219
|
+
targetIds[index] = lookupUpsertTarget(targetsIndex, parts);
|
|
43220
|
+
});
|
|
43221
|
+
}
|
|
43222
|
+
const tableCodes = targets.filter((target) => target.kind === "SUBTABLE").map((target) => target.subtableCode);
|
|
43223
|
+
const existingById = /* @__PURE__ */ new Map();
|
|
43224
|
+
const updateIds = targetIds.filter((id) => id !== void 0);
|
|
43225
|
+
for (const chunk2 of splitChunks([...new Set(updateIds)], 100)) {
|
|
43226
|
+
const response = await client.getRecords({ app: stmt.appId, query: `$id in (${chunk2.join(",")}) limit 500`, fields: ["$id", "$revision", ...tableCodes] });
|
|
43227
|
+
for (const record2 of response.records) {
|
|
43228
|
+
const id = Number(record2["$id"]?.value);
|
|
43229
|
+
const revision = Number(record2["$revision"]?.value);
|
|
43230
|
+
if (Number.isFinite(id)) existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record: record2 });
|
|
43231
|
+
}
|
|
43232
|
+
}
|
|
43233
|
+
const writePlan = buildJsonSubtableWritePlan(validParents, targetIds, existingById);
|
|
43234
|
+
const importDetail = {
|
|
43235
|
+
kind: "IMPORT_JSON_SUBTABLE",
|
|
43236
|
+
rowIdPolicy: "DROP_AND_RENUMBER_ALL",
|
|
43237
|
+
parentsToWrite: writePlan.length,
|
|
43238
|
+
insertedParents: writePlan.filter((parent) => parent.mode === "INSERT").length,
|
|
43239
|
+
updatedParents: writePlan.filter((parent) => parent.mode === "UPDATE").length,
|
|
43240
|
+
hasDeletes: writePlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
|
|
43241
|
+
parents: writePlan.map((parent) => ({ parentRow: parent.parentRow, mode: parent.mode, ...parent.targetId === void 0 ? {} : { targetId: parent.targetId }, tables: parent.tables }))
|
|
43242
|
+
};
|
|
43243
|
+
if (writePlan.length > 0) {
|
|
43244
|
+
if (!options.confirm) throw new Error("UnsupportedError: JSON IMPORT subtable mutation requires explicit confirmation detail approval.");
|
|
43245
|
+
const ok = await options.confirm(writePlan.length, stmt.keyFields ? "UPDATE" : "INSERT", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
|
|
43246
|
+
if (!ok) throw new OperationCancelledError(stmt.keyFields ? "UPDATE" : "INSERT", writePlan.length);
|
|
43247
|
+
}
|
|
43248
|
+
const toScalarMap = (record2) => new Map(
|
|
43249
|
+
Object.entries(record2).map(([code, field]) => [code, field.value])
|
|
43250
|
+
);
|
|
43251
|
+
const payloadFor = (parent) => buildJsonImportRecordPayload(
|
|
43252
|
+
toScalarMap(parent.top),
|
|
43253
|
+
new Map([...parent.subtables].map(([table, rows]) => [table, rows.map((row) => ({ values: toScalarMap(row) }))]))
|
|
43254
|
+
);
|
|
43255
|
+
const inserts = writePlan.filter((parent) => parent.mode === "INSERT");
|
|
43256
|
+
const updates = writePlan.filter((parent) => parent.mode === "UPDATE");
|
|
43257
|
+
const createdIds = [];
|
|
43258
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
43259
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map(payloadFor) });
|
|
43260
|
+
createdIds.push(response.ids);
|
|
43261
|
+
}
|
|
43262
|
+
for (let i = 0; i < updates.length; i += 100) await client.putRecords({
|
|
43263
|
+
app: stmt.appId,
|
|
43264
|
+
records: updates.slice(i, i + 100).map((parent) => ({ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record: payloadFor(parent) }))
|
|
43265
|
+
});
|
|
43266
|
+
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 };
|
|
43267
|
+
}
|
|
43268
|
+
if (stmt.writeMode === "UPDATE_RECORD_NUMBER") {
|
|
43269
|
+
return executeImportRecordNumberUpdate(
|
|
43270
|
+
stmt,
|
|
43271
|
+
handle,
|
|
43272
|
+
client,
|
|
43273
|
+
options,
|
|
43274
|
+
cacheContext,
|
|
43275
|
+
tempTables
|
|
43276
|
+
);
|
|
43277
|
+
}
|
|
43278
|
+
const common = {
|
|
43279
|
+
appId: stmt.appId,
|
|
43280
|
+
fields: stmt.fields,
|
|
43281
|
+
select: importPlaceholderSelect(),
|
|
43282
|
+
validateOnly: stmt.validateOnly,
|
|
43283
|
+
validationErrorTable: stmt.validationErrorTable,
|
|
43284
|
+
onErrorSkip: stmt.onErrorSkip,
|
|
43285
|
+
errorTable: stmt.errorTable,
|
|
43286
|
+
rejectLimit: stmt.rejectLimit,
|
|
43287
|
+
checkGroups: stmt.checkGroups
|
|
43288
|
+
};
|
|
43289
|
+
const generated = stmt.keyFields ? { type: "UPSERT_SELECT", ...common, keyFields: stmt.keyFields } : { type: "INSERT_SELECT", ...common };
|
|
43290
|
+
const executionSource = { source: stmt.source, handle, cache: /* @__PURE__ */ new Map() };
|
|
43291
|
+
importSourceByDmlStatement.set(generated, executionSource);
|
|
43292
|
+
const withAudit = (result2) => {
|
|
43293
|
+
if (executionSource.audit) Object.assign(result2, { importAudit: executionSource.audit });
|
|
43294
|
+
return result2;
|
|
43295
|
+
};
|
|
43296
|
+
if (generated.validateOnly) {
|
|
43297
|
+
if (generated.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
43298
|
+
const result2 = await executeDmlValidation(generated, client, { ...options, onLimitReached: "error" }, cacheContext, tempTables, 1);
|
|
43299
|
+
if (generated.validationErrorTable && tempTables) {
|
|
43300
|
+
appendValidationErrors(
|
|
43301
|
+
tempTables,
|
|
43302
|
+
generated.validationErrorTable,
|
|
43303
|
+
result2.columns,
|
|
43304
|
+
result2.errors,
|
|
43305
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
43306
|
+
materializedMetaByValidationResult.get(result2) ?? /* @__PURE__ */ new Map()
|
|
43307
|
+
);
|
|
43308
|
+
}
|
|
43309
|
+
return withAudit(result2);
|
|
43310
|
+
}
|
|
43311
|
+
if (generated.onErrorSkip) {
|
|
43312
|
+
if (!tempTables) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
43313
|
+
const result2 = await (generated.type === "UPSERT_SELECT" ? executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1) : executeOnErrorSkip(generated, client, options, cacheContext, tempTables, 1));
|
|
43314
|
+
return withAudit(result2);
|
|
43315
|
+
}
|
|
43316
|
+
const result = await (generated.type === "UPSERT_SELECT" ? executeUpsertSelect(generated, client, options, cacheContext, tempTables) : executeInsertSelect(generated, client, options, cacheContext, tempTables));
|
|
43317
|
+
return withAudit(result);
|
|
43318
|
+
}
|
|
43319
|
+
async function executeCsvSubtableReplacement(stmt, materialized, preparedBase, fieldInfos, client, options, tempTables) {
|
|
43320
|
+
if (!stmt.recordNumberSourceHeader || !stmt.replaceSubtables?.length) throw new Error("InternalError: incomplete CSV subtable replacement AST.");
|
|
43321
|
+
assertNoDuplicateCsvSubtableRowIds(materialized.records);
|
|
43322
|
+
const rawKeys = materialized.records.map((record2) => record2.recordNumberSourceValue ?? "");
|
|
43323
|
+
const keyPlan = preflightImportRecordNumbers(rawKeys, stmt.recordNumberSourceHeader);
|
|
43324
|
+
const tableCodes = [...stmt.replaceSubtables];
|
|
43325
|
+
const ownershipTableCodes = [...new Set(fieldInfos.filter((info) => !info.inSubtable && info.fieldType === "SUBTABLE").map((info) => info.code))];
|
|
43326
|
+
const allRecords = await fetchAll(client.getRecords, stmt.appId, "", ["$id", "$revision", ...ownershipTableCodes], {
|
|
43327
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
43328
|
+
parallel: options.fetchParallel ?? 1,
|
|
43329
|
+
onLimit: "error"
|
|
43330
|
+
});
|
|
43331
|
+
const existingById = /* @__PURE__ */ new Map();
|
|
43332
|
+
const ownership = /* @__PURE__ */ new Map();
|
|
43333
|
+
for (const record2 of allRecords) {
|
|
43334
|
+
const id = Number(record2["$id"]?.value);
|
|
43335
|
+
const revision = Number(record2["$revision"]?.value);
|
|
43336
|
+
if (!Number.isFinite(id)) continue;
|
|
43337
|
+
existingById.set(id, { id, ...Number.isFinite(revision) ? { revision } : {}, record: record2 });
|
|
43338
|
+
for (const table of ownershipTableCodes) {
|
|
43339
|
+
const rows = record2[table]?.value;
|
|
43340
|
+
if (!Array.isArray(rows)) continue;
|
|
43341
|
+
for (const row of rows) if (row.id) {
|
|
43342
|
+
const owners = ownership.get(row.id) ?? [];
|
|
43343
|
+
owners.push({ parentId: id, table });
|
|
43344
|
+
ownership.set(row.id, owners);
|
|
43345
|
+
}
|
|
43346
|
+
}
|
|
43347
|
+
}
|
|
43348
|
+
const targetIds = keyPlan.normalized.map((key) => key === null ? void 0 : Number(key));
|
|
43349
|
+
const parents = preparedBase.parents.map((parent, index) => {
|
|
43350
|
+
const errors2 = [...parent.errors];
|
|
43351
|
+
for (const error51 of keyPlan.errors[index]) errors2.push({
|
|
43352
|
+
operation: "UPDATE",
|
|
43353
|
+
parentRow: parent.parentRow,
|
|
43354
|
+
field: error51.field,
|
|
43355
|
+
code: error51.code,
|
|
43356
|
+
message: error51.message,
|
|
43357
|
+
sourceValues: materialized.records[index].top
|
|
43358
|
+
});
|
|
43359
|
+
const targetId = targetIds[index];
|
|
43360
|
+
if (targetId !== void 0 && !existingById.has(targetId)) errors2.push({
|
|
43361
|
+
operation: "UPDATE",
|
|
43362
|
+
parentRow: parent.parentRow,
|
|
43363
|
+
field: stmt.recordNumberSourceHeader,
|
|
43364
|
+
code: "ERR_RECORD_NUMBER_NOT_FOUND",
|
|
43365
|
+
message: `record number ${targetId} does not exist in APP${stmt.appId}`,
|
|
43366
|
+
sourceValues: materialized.records[index].top
|
|
43367
|
+
});
|
|
43368
|
+
return { ...parent, valid: errors2.length === 0, errors: errors2 };
|
|
43369
|
+
});
|
|
43370
|
+
const initialPlan = buildCsvSubtableReplacementPlan(materialized.records, parents, targetIds, existingById, ownership);
|
|
43371
|
+
const planErrors = initialPlan.flatMap((parent) => [...parent.errors]);
|
|
43372
|
+
const invalidParentRows = new Set(initialPlan.filter((parent) => !parent.valid).map((parent) => parent.parentRow));
|
|
43373
|
+
const prepared = { ...preparedBase, parents, errors: planErrors, invalidParentRows };
|
|
43374
|
+
assertImportRejectLimit(prepared, stmt.rejectLimit);
|
|
43375
|
+
const validPlan = initialPlan.filter((parent) => parent.valid);
|
|
43376
|
+
const allTables = initialPlan.flatMap((parent) => parent.tables);
|
|
43377
|
+
const sum = (table, key) => allTables.filter((item) => item.table === table).reduce((n, item) => n + Number(item[key]), 0);
|
|
43378
|
+
const tableDetail = Object.fromEntries(tableCodes.map((table) => [table, {
|
|
43379
|
+
existingRows: sum(table, "existingRows"),
|
|
43380
|
+
inputRows: sum(table, "inputRows"),
|
|
43381
|
+
updateRows: sum(table, "updateRows"),
|
|
43382
|
+
addRows: sum(table, "addRows"),
|
|
43383
|
+
deleteRows: sum(table, "deleteRows"),
|
|
43384
|
+
rowIdNotFound: sum(table, "rowIdNotFound")
|
|
43385
|
+
}]));
|
|
43386
|
+
const importDetail = {
|
|
43387
|
+
kind: "IMPORT_CSV_SUBTABLE_REPLACE",
|
|
43388
|
+
rowIdPolicy: "PRESERVE_EXISTING",
|
|
43389
|
+
parentsToWrite: validPlan.length,
|
|
43390
|
+
insertedParents: 0,
|
|
43391
|
+
updatedParents: validPlan.length,
|
|
43392
|
+
hasDeletes: validPlan.some((parent) => parent.tables.some((table) => table.deleteRows > 0)),
|
|
43393
|
+
totalDeleteRows: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.deleteRows, 0),
|
|
43394
|
+
rowIdNotFound: validPlan.flatMap((parent) => parent.tables).reduce((n, table) => n + table.rowIdNotFound, 0),
|
|
43395
|
+
invalidParents: invalidParentRows.size,
|
|
43396
|
+
parents: validPlan.map((parent) => ({ parentRow: parent.parentRow, mode: "UPDATE", targetId: parent.targetId, tables: parent.tables }))
|
|
43397
|
+
};
|
|
43398
|
+
const payloadFields = [...new Set(stmt.targets.flatMap((target) => target.kind === "FIELD" ? [target.field] : target.children))];
|
|
43399
|
+
const errors = materializeImportValidationErrors(planErrors, payloadFields);
|
|
43400
|
+
const columns = [...payloadFields, ...IMPORT_VALIDATION_META_COLUMNS];
|
|
43401
|
+
if (stmt.validateOnly) {
|
|
43402
|
+
if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
43403
|
+
if (stmt.validationErrorTable && tempTables) appendValidationErrors(tempTables, stmt.validationErrorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
|
|
43404
|
+
return {
|
|
43405
|
+
type: "VALIDATION",
|
|
43406
|
+
operation: "UPDATE",
|
|
43407
|
+
validatedRows: parents.length,
|
|
43408
|
+
validRows: parents.length - invalidParentRows.size,
|
|
43409
|
+
invalidRows: invalidParentRows.size,
|
|
43410
|
+
errorCount: errors.length,
|
|
43411
|
+
columns,
|
|
43412
|
+
errors,
|
|
43413
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : {},
|
|
43414
|
+
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 }
|
|
43415
|
+
};
|
|
43416
|
+
}
|
|
43417
|
+
if (planErrors.length && !stmt.onErrorSkip) {
|
|
43418
|
+
const first = planErrors[0];
|
|
43419
|
+
throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${first.parentRow}, field=${first.field})`);
|
|
43420
|
+
}
|
|
43421
|
+
if (stmt.onErrorSkip) {
|
|
43422
|
+
if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch and INTO error table.");
|
|
43423
|
+
appendValidationErrors(tempTables, stmt.errorTable, columns, errors, options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS, /* @__PURE__ */ new Map());
|
|
43424
|
+
}
|
|
43425
|
+
if (validPlan.length) {
|
|
43426
|
+
if (!options.supportsImportConfirmDetail || !options.confirm) throw new Error("UnsupportedError: CSV subtable replacement requires explicit rendered detail approval.");
|
|
43427
|
+
const ok = await options.confirm(validPlan.length, "UPDATE", { statementIndex: 0, statementCount: 1, statementType: "IMPORT", targetAppId: stmt.appId, importDetail });
|
|
43428
|
+
if (!ok) throw new OperationCancelledError("UPDATE", validPlan.length);
|
|
43429
|
+
}
|
|
43430
|
+
const scalarMap = (record2) => new Map(Object.entries(record2).map(([code, field]) => [code, field.value]));
|
|
43431
|
+
for (const parent of validPlan) {
|
|
43432
|
+
const record2 = 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");
|
|
43433
|
+
await client.putRecords({ app: stmt.appId, records: [{ id: parent.targetId, ...parent.revision === void 0 ? {} : { revision: parent.revision }, record: record2 }] });
|
|
43434
|
+
}
|
|
43435
|
+
return { type: "UPDATE", updatedCount: validPlan.length, affectedRows: validPlan.length, skippedRows: invalidParentRows.size, rejectLimit: stmt.rejectLimit, ...stmt.errorTable ? { errTable: stmt.errorTable } : {}, importDetail };
|
|
43436
|
+
}
|
|
43437
|
+
async function executeImportRecordNumberUpdate(stmt, handle, client, options, cacheContext, tempTables) {
|
|
43438
|
+
if (stmt.source.kind !== "CSV" || stmt.source.mappingMode !== "BY_NAME" || !stmt.recordNumberSourceHeader) {
|
|
43439
|
+
throw new Error("InternalError: invalid IMPORT UPDATE AST.");
|
|
43440
|
+
}
|
|
43441
|
+
if (new Set(stmt.fields).size !== stmt.fields.length) throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
43442
|
+
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
43443
|
+
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
43444
|
+
const payload = await loadImportSource(handle, /* @__PURE__ */ new Map());
|
|
43445
|
+
const sourceTable = materializeCsvDmlSource(
|
|
43446
|
+
stmt.source,
|
|
43447
|
+
payload,
|
|
43448
|
+
options.maxRecords ?? 1e4,
|
|
43449
|
+
stmt.fields,
|
|
43450
|
+
fieldInfos,
|
|
43451
|
+
stmt.recordNumberSourceHeader
|
|
43452
|
+
);
|
|
43453
|
+
const keyValues = sourceTable.recordNumberSourceValues;
|
|
43454
|
+
if (!keyValues) throw new Error("InternalError: record-number source values were not materialized.");
|
|
43455
|
+
const keyPlan = preflightImportRecordNumbers(keyValues, stmt.recordNumberSourceHeader);
|
|
43456
|
+
const matchedIds = /* @__PURE__ */ new Set();
|
|
43457
|
+
const lookupKeys = [...new Set(keyPlan.normalized.filter((key) => key !== null))];
|
|
43458
|
+
for (let i = 0; i < lookupKeys.length; i += 100) {
|
|
43459
|
+
const chunk2 = lookupKeys.slice(i, i + 100);
|
|
43460
|
+
const response = await client.getRecords({
|
|
43461
|
+
app: stmt.appId,
|
|
43462
|
+
query: `$id in (${chunk2.join(",")}) limit 500`,
|
|
43463
|
+
fields: ["$id"]
|
|
43464
|
+
});
|
|
43465
|
+
for (const record2 of response.records) {
|
|
43466
|
+
const id = record2["$id"]?.value;
|
|
43467
|
+
if (typeof id === "string" && id !== "") matchedIds.add(id.replace(/^0+(?=\d)/, ""));
|
|
43468
|
+
}
|
|
43469
|
+
}
|
|
43470
|
+
const infoByCode = new Map(fieldInfos.map((info) => [info.code, info]));
|
|
43471
|
+
const evaluationTypes = new Map(stmt.fields.map((field) => [field, infoByCode.get(field)?.fieldType ?? "SINGLE_LINE_TEXT"]));
|
|
43472
|
+
const candidates = sourceTable.rows.map((row, index) => {
|
|
43473
|
+
const key = keyPlan.normalized[index];
|
|
43474
|
+
const preErrors = [
|
|
43475
|
+
...sourceTable.importRowErrors?.[index] ?? [],
|
|
43476
|
+
...keyPlan.errors[index]
|
|
43477
|
+
];
|
|
43478
|
+
if (key !== null && !matchedIds.has(key)) preErrors.push({
|
|
43479
|
+
field: stmt.recordNumberSourceHeader,
|
|
43480
|
+
code: "ERR_RECORD_NUMBER_NOT_FOUND",
|
|
43481
|
+
message: `record number ${key} does not exist in APP${stmt.appId}`
|
|
43482
|
+
});
|
|
43483
|
+
return {
|
|
43484
|
+
rowNumber: index + 1,
|
|
43485
|
+
operation: "UPDATE",
|
|
43486
|
+
mode: "update",
|
|
43487
|
+
...key !== null && matchedIds.has(key) ? { targetId: Number(key) } : {},
|
|
43488
|
+
payload: new Map([
|
|
43489
|
+
[stmt.recordNumberSourceHeader, keyValues[index]],
|
|
43490
|
+
...stmt.fields.map((field) => [field, row[field] ?? ""])
|
|
43491
|
+
]),
|
|
43492
|
+
preErrors,
|
|
43493
|
+
record: {},
|
|
43494
|
+
evaluationRow: row,
|
|
43495
|
+
evaluationFieldTypes: evaluationTypes
|
|
43496
|
+
};
|
|
43497
|
+
});
|
|
43498
|
+
const diagnosticFields = [stmt.recordNumberSourceHeader, ...stmt.fields];
|
|
43499
|
+
const validation = validateDmlCandidates(
|
|
43500
|
+
candidates,
|
|
43501
|
+
"UPDATE",
|
|
43502
|
+
diagnosticFields,
|
|
43503
|
+
stmt.fields,
|
|
43504
|
+
fieldInfos,
|
|
43505
|
+
1,
|
|
43506
|
+
numberPrecision,
|
|
43507
|
+
stmt.checkGroups ?? [],
|
|
43508
|
+
false
|
|
43509
|
+
);
|
|
43510
|
+
const columns = [...diagnosticFields, ...VALIDATION_META_COLUMNS];
|
|
43511
|
+
const validationResult = {
|
|
43512
|
+
type: "VALIDATION",
|
|
43513
|
+
operation: "UPDATE",
|
|
43514
|
+
validatedRows: candidates.length,
|
|
43515
|
+
validRows: candidates.length - validation.invalidRows,
|
|
43516
|
+
invalidRows: validation.invalidRows,
|
|
43517
|
+
errorCount: validation.errors.length,
|
|
43518
|
+
columns,
|
|
43519
|
+
errors: validation.errors,
|
|
43520
|
+
...stmt.validationErrorTable ?? stmt.errorTable ? { errTable: stmt.validationErrorTable ?? stmt.errorTable } : {}
|
|
43521
|
+
};
|
|
43522
|
+
Object.assign(validationResult, { importAudit: sourceTable.importAudit });
|
|
43523
|
+
if (stmt.validateOnly) {
|
|
43524
|
+
if (stmt.validationErrorTable && !tempTables) throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
43525
|
+
if (stmt.validationErrorTable && tempTables) appendValidationErrors(
|
|
43526
|
+
tempTables,
|
|
43527
|
+
stmt.validationErrorTable,
|
|
43528
|
+
columns,
|
|
43529
|
+
validation.errors,
|
|
43530
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
43531
|
+
/* @__PURE__ */ new Map()
|
|
43532
|
+
);
|
|
43533
|
+
return validationResult;
|
|
43534
|
+
}
|
|
43535
|
+
if (!stmt.onErrorSkip && validation.invalidRows > 0) {
|
|
43536
|
+
const first = validation.errors[0];
|
|
43537
|
+
throw new Error(`DmlValidationError: ${first.$err_code} ${first.$err_message} (row=${first.$err_row}, field=${first.$err_field})`);
|
|
43538
|
+
}
|
|
43539
|
+
if (stmt.onErrorSkip) {
|
|
43540
|
+
if (!tempTables || !stmt.errorTable) throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
43541
|
+
appendValidationErrors(
|
|
43542
|
+
tempTables,
|
|
43543
|
+
stmt.errorTable,
|
|
43544
|
+
columns,
|
|
43545
|
+
validation.errors,
|
|
43546
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
43547
|
+
/* @__PURE__ */ new Map()
|
|
43548
|
+
);
|
|
43549
|
+
if (stmt.rejectLimit != null && validation.invalidRows > stmt.rejectLimit) {
|
|
43550
|
+
throw new RejectLimitExceededError(
|
|
43551
|
+
`rejected rows (${validation.invalidRows}) exceed REJECT LIMIT (${stmt.rejectLimit}).`,
|
|
43552
|
+
validationResult
|
|
43553
|
+
);
|
|
43554
|
+
}
|
|
43555
|
+
}
|
|
43556
|
+
const valid = candidates.filter((candidate) => !validation.invalidRowNumbers.has(candidate.rowNumber));
|
|
43557
|
+
if (options.confirm) {
|
|
43558
|
+
const ok = await options.confirm(valid.length, "UPDATE");
|
|
43559
|
+
if (!ok) throw new OperationCancelledError("UPDATE", valid.length);
|
|
43560
|
+
}
|
|
43561
|
+
const updates = valid.map((candidate) => ({ id: candidate.targetId, record: candidate.record }));
|
|
43562
|
+
for (let i = 0; i < updates.length; i += 100) {
|
|
43563
|
+
await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
43564
|
+
}
|
|
43565
|
+
const result = {
|
|
43566
|
+
type: "UPDATE",
|
|
43567
|
+
updatedCount: updates.length,
|
|
43568
|
+
...stmt.onErrorSkip ? {
|
|
43569
|
+
affectedRows: updates.length,
|
|
43570
|
+
skippedRows: validation.invalidRows,
|
|
43571
|
+
rejectLimit: stmt.rejectLimit ?? null,
|
|
43572
|
+
errTable: stmt.errorTable
|
|
43573
|
+
} : {}
|
|
43574
|
+
};
|
|
43575
|
+
Object.assign(result, { insertedCount: 0, importAudit: sourceTable.importAudit });
|
|
43576
|
+
return result;
|
|
43577
|
+
}
|
|
43578
|
+
async function materializeDmlSource(stmt, client, options, cacheContext, tempTables, targetFields) {
|
|
43579
|
+
const imported = importSourceByDmlStatement.get(stmt);
|
|
43580
|
+
if (!imported) {
|
|
43581
|
+
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);
|
|
43582
|
+
return { rows: selected2.rows, columns: selected2.columns, columnMeta: materializedMetaBySelectResult.get(selected2) };
|
|
43583
|
+
}
|
|
43584
|
+
const payload = await loadImportSource(imported.handle, imported.cache);
|
|
43585
|
+
const rowLimit = options.maxRecords ?? 1e4;
|
|
43586
|
+
const targetByCode = new Map((targetFields ?? []).map((info) => [info.code, info]));
|
|
43587
|
+
const jsonTargets = stmt.fields.map((code) => ({ code, fieldType: targetByCode.get(code)?.fieldType ?? "SINGLE_LINE_TEXT" }));
|
|
43588
|
+
const raw = imported.source.kind === "JSON" ? materializeJsonDmlSource(imported.source, payload, jsonTargets, rowLimit) : materializeCsvDmlSource(imported.source, payload, rowLimit, stmt.fields, targetFields);
|
|
43589
|
+
imported.audit = raw.importAudit;
|
|
43590
|
+
if (imported.source.kind === "JSON") return raw;
|
|
43591
|
+
if (!imported.source.projection) return raw;
|
|
43592
|
+
const projection = bindImportProjection(imported.source.projection);
|
|
43593
|
+
const tables = new Map(tempTables ?? []);
|
|
43594
|
+
tables.set(IMPORT_PROJECTION_SOURCE, raw);
|
|
43595
|
+
const selected = await executeQueryWithCte(projection, client, { ...options, onLimitReached: "error" }, tables, cacheContext, true);
|
|
43596
|
+
return { rows: selected.rows, columns: selected.columns, columnMeta: materializedMetaBySelectResult.get(selected) };
|
|
43597
|
+
}
|
|
43598
|
+
var dmlSourceMaterializer = { materialize: materializeDmlSource };
|
|
43599
|
+
function assertNoImportRowErrors(table) {
|
|
43600
|
+
for (let rowIndex = 0; rowIndex < (table.importRowErrors?.length ?? 0); rowIndex++) {
|
|
43601
|
+
const first = table.importRowErrors?.[rowIndex]?.[0];
|
|
43602
|
+
if (first) {
|
|
43603
|
+
throw new Error(`DmlValidationError: ${first.code} ${first.message} (row=${rowIndex + 1}, field=${first.field})`);
|
|
43604
|
+
}
|
|
43605
|
+
}
|
|
43606
|
+
}
|
|
42016
43607
|
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
42017
43608
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
42018
43609
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
42019
43610
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
42020
|
-
const
|
|
42021
|
-
const { rows, columns } =
|
|
43611
|
+
const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
|
|
43612
|
+
const { rows, columns } = sourceTable;
|
|
43613
|
+
assertNoImportRowErrors(sourceTable);
|
|
42022
43614
|
if (columns.length !== stmt.fields.length) {
|
|
42023
43615
|
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" : "";
|
|
42024
43616
|
throw new Error(
|
|
@@ -42030,15 +43622,16 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
42030
43622
|
if (!ok) throw new OperationCancelledError("INSERT", rows.length);
|
|
42031
43623
|
}
|
|
42032
43624
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
42033
|
-
const allRecords = rows.map((row) => {
|
|
43625
|
+
const allRecords = rows.map((row, rowIndex) => {
|
|
42034
43626
|
const record2 = {};
|
|
42035
43627
|
stmt.fields.forEach((field, i) => {
|
|
43628
|
+
if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
|
|
42036
43629
|
const raw = row[columns[i]] ?? "";
|
|
42037
43630
|
record2[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
|
|
42038
43631
|
});
|
|
42039
43632
|
return record2;
|
|
42040
43633
|
});
|
|
42041
|
-
assertValidDmlRecords(
|
|
43634
|
+
allRecords.forEach((record2) => assertValidDmlRecords([record2], stmt.fields.filter((field) => field in record2), fieldInfos, numberPrecision));
|
|
42042
43635
|
const createdIds = [];
|
|
42043
43636
|
for (let i = 0; i < allRecords.length; i += 100) {
|
|
42044
43637
|
const batch = allRecords.slice(i, i + 100);
|
|
@@ -42431,9 +44024,9 @@ function expandRowsForSubtableDml(parents, subtableCode) {
|
|
|
42431
44024
|
for (const parent of parents) {
|
|
42432
44025
|
const parentId = String(parent["$id"]?.value ?? "");
|
|
42433
44026
|
const parentRevision = getRevision(parent);
|
|
42434
|
-
const
|
|
42435
|
-
for (let i = 0; i <
|
|
42436
|
-
const row =
|
|
44027
|
+
const tableRows2 = getMutableTableRows(parent, subtableCode);
|
|
44028
|
+
for (let i = 0; i < tableRows2.length; i++) {
|
|
44029
|
+
const row = tableRows2[i];
|
|
42437
44030
|
const flat = {
|
|
42438
44031
|
_pid: parentId,
|
|
42439
44032
|
_rid: row.id ?? "",
|
|
@@ -42639,8 +44232,9 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
42639
44232
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
42640
44233
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
42641
44234
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
42642
|
-
const
|
|
42643
|
-
const { rows, columns } =
|
|
44235
|
+
const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
|
|
44236
|
+
const { rows, columns } = sourceTable;
|
|
44237
|
+
assertNoImportRowErrors(sourceTable);
|
|
42644
44238
|
if (columns.length !== stmt.fields.length) {
|
|
42645
44239
|
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" : "";
|
|
42646
44240
|
throw new Error(
|
|
@@ -42654,18 +44248,29 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
42654
44248
|
}
|
|
42655
44249
|
const toInsert = [];
|
|
42656
44250
|
const toUpdate = [];
|
|
42657
|
-
const
|
|
44251
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
44252
|
+
const records = rows.map((row, rowIndex) => {
|
|
42658
44253
|
const record2 = {};
|
|
42659
44254
|
stmt.fields.forEach((field, i) => {
|
|
42660
|
-
|
|
44255
|
+
if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
|
|
44256
|
+
const raw = row[columns[i]] ?? "";
|
|
44257
|
+
record2[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
|
|
42661
44258
|
});
|
|
42662
44259
|
return record2;
|
|
42663
44260
|
});
|
|
42664
|
-
assertValidDmlRecords(
|
|
42665
|
-
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
44261
|
+
records.forEach((record2) => assertValidDmlRecords([record2], stmt.fields.filter((field) => field in record2), fieldInfos, numberPrecision));
|
|
42666
44262
|
const rowKeyValues = records.map(
|
|
42667
44263
|
(record2) => stmt.keyFields.map((key) => String(record2[key]?.value ?? ""))
|
|
42668
44264
|
);
|
|
44265
|
+
if (importSourceByDmlStatement.has(stmt)) {
|
|
44266
|
+
const numericKey = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
44267
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
44268
|
+
for (const parts of rowKeyValues) {
|
|
44269
|
+
const normalized = upsertNormalizedKey(parts, numericKey);
|
|
44270
|
+
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");
|
|
44271
|
+
sourceKeys.add(normalized);
|
|
44272
|
+
}
|
|
44273
|
+
}
|
|
42669
44274
|
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
42670
44275
|
records.forEach((record2, rowIdx) => {
|
|
42671
44276
|
const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
|
|
@@ -42714,10 +44319,10 @@ async function executeDescribe(stmt, client, cacheContext) {
|
|
|
42714
44319
|
}));
|
|
42715
44320
|
return { type: "SELECT", rows, columns, rowCount: rows.length };
|
|
42716
44321
|
}
|
|
42717
|
-
function parseSql(sql) {
|
|
44322
|
+
function parseSql(sql, enableImport = false) {
|
|
42718
44323
|
try {
|
|
42719
44324
|
const tokens = new Lexer(sql).tokenize();
|
|
42720
|
-
const stmt = new Parser(tokens).parse();
|
|
44325
|
+
const stmt = new Parser(tokens, { import: enableImport }).parse();
|
|
42721
44326
|
validateKlikeStatement(stmt);
|
|
42722
44327
|
return stmt;
|
|
42723
44328
|
} catch (e) {
|
|
@@ -42973,8 +44578,8 @@ function explainMetadataLines(analysis) {
|
|
|
42973
44578
|
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
42974
44579
|
];
|
|
42975
44580
|
}
|
|
42976
|
-
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2) {
|
|
42977
|
-
const statements = parseSqlBatch(sql);
|
|
44581
|
+
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2, enableImport = false) {
|
|
44582
|
+
const statements = parseSqlBatch(sql, enableImport);
|
|
42978
44583
|
const analysis = analyzeBatch(statements);
|
|
42979
44584
|
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
42980
44585
|
const variables = /* @__PURE__ */ new Map();
|
|
@@ -43133,6 +44738,79 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
|
|
|
43133
44738
|
if (query.type === "DELETE") return buildDeletePlan(query, label);
|
|
43134
44739
|
if (query.type === "REORDER") return buildReorderPlan(query, label);
|
|
43135
44740
|
if (query.type === "VALIDATE") return buildValidatePlan(query, label);
|
|
44741
|
+
if (query.type === "IMPORT") {
|
|
44742
|
+
if (query.writeMode === "UPDATE_RECORD_NUMBER") {
|
|
44743
|
+
const csvTables = query.targets?.filter((target) => target.kind === "SUBTABLE") ?? [];
|
|
44744
|
+
return [
|
|
44745
|
+
...label ? [label] : [],
|
|
44746
|
+
`IMPORT UPDATE INTO APP${query.appId}`,
|
|
44747
|
+
` writeMode: UPDATE_RECORD_NUMBER`,
|
|
44748
|
+
` source: CSV ${query.source.sourceName}`,
|
|
44749
|
+
` keyHeader: ${query.recordNumberSourceHeader}`,
|
|
44750
|
+
` mapping: BY_NAME`,
|
|
44751
|
+
` parentRows: requires source load`,
|
|
44752
|
+
` duplicate: preflight before lookup/write`,
|
|
44753
|
+
` matched: requires lookup`,
|
|
44754
|
+
` unmatched: requires lookup`,
|
|
44755
|
+
` invalid: requires source load`,
|
|
44756
|
+
` requiresLookup:true`,
|
|
44757
|
+
` inserted: 0`,
|
|
44758
|
+
` keyInPayload: false`,
|
|
44759
|
+
...csvTables.length ? [
|
|
44760
|
+
` replaceSubtables: ${query.replaceSubtables?.join(", ") ?? "ERROR: required"}`,
|
|
44761
|
+
` subtableRowIdPolicy: PRESERVE existing; empty/unknown add without id`,
|
|
44762
|
+
` rowIdOwnership: owned elsewhere invalidates the parent`,
|
|
44763
|
+
` replacementDiff: existing/input/update/add/delete/rowIdNotFound requires actual-data preflight`,
|
|
44764
|
+
` confirmPolicy: highest warning "\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u5168\u7F6E\u63DB\u30FBN\u884C\u524A\u9664" plus per-table detail (including delete=0)`
|
|
44765
|
+
] : [],
|
|
44766
|
+
` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
|
|
44767
|
+
` gate: enabled for this parse`,
|
|
44768
|
+
` writesKintone: ${query.validateOnly ? "false" : "true"}`
|
|
44769
|
+
];
|
|
44770
|
+
}
|
|
44771
|
+
const mode = query.keyFields ? "UPSERT" : "INSERT";
|
|
44772
|
+
const hasSubtables = query.targets?.some((target) => target.kind === "SUBTABLE") === true;
|
|
44773
|
+
return [
|
|
44774
|
+
...label ? [label] : [],
|
|
44775
|
+
`IMPORT ${mode} INTO APP${query.appId}`,
|
|
44776
|
+
` source: ${query.source.kind} ${query.source.sourceName}`,
|
|
44777
|
+
` sourceFormat: ${query.source.kind}`,
|
|
44778
|
+
` encoding: ${query.source.kind === "JSON" ? "UTF8 only" : query.source.encoding ?? "UTF8 (or loader metadata)"}`,
|
|
44779
|
+
` mapping: ${query.source.kind === "JSON" ? "BY NAME (INTO order)" : query.source.projection ? "SELECT expressions" : query.source.mappingMode}`,
|
|
44780
|
+
...query.source.kind === "JSON" ? [
|
|
44781
|
+
` duplicateKeyPolicy: reject`,
|
|
44782
|
+
` numberLexemePolicy: preserve; JSON number accepts safe integer only`,
|
|
44783
|
+
` precisionTargetsRequireString: true`,
|
|
44784
|
+
` unknownKeyPolicy: reject`,
|
|
44785
|
+
` presenceAware: true`,
|
|
44786
|
+
...hasSubtables ? [
|
|
44787
|
+
` subtableRowIdPolicy: reject _rid/id; DROP IDs and renumber every input row`,
|
|
44788
|
+
` subtableUpdatePolicy: present table replaces all rows; missing table is preserved; [] deletes all rows`,
|
|
44789
|
+
` confirmPolicy: parent/table existing/input/add/delete detail required; delete is highest warning`
|
|
44790
|
+
] : []
|
|
44791
|
+
] : [
|
|
44792
|
+
` header: ${query.source.hasHeader ? "HEADER" : "NO HEADER"}`,
|
|
44793
|
+
...query.source.mappingMode === "BY_NAME" ? [
|
|
44794
|
+
` writtenColumns: ${query.fields.join(", ")}`,
|
|
44795
|
+
` knownExportColumns: audit and ignore with reason/non-empty count`,
|
|
44796
|
+
` unknownColumnPolicy: ${query.source.ignoreUnknownColumns ? "ignore with audit/non-empty count" : "ERR_IMPORT_UNKNOWN_COLUMN"}`,
|
|
44797
|
+
` multipleValueDelimiter: LF (CRLF or LF)`,
|
|
44798
|
+
` sourceValueMode: string-preserving`,
|
|
44799
|
+
` roundTripNumericGuarantee: exact CSV lexeme passes strict decimal validation`,
|
|
44800
|
+
` FILE: audit-ignore unless named in INTO (analyze error)`
|
|
44801
|
+
] : []
|
|
44802
|
+
],
|
|
44803
|
+
` sourceLimit: 10485760 bytes / ${query.fields.length} target columns`,
|
|
44804
|
+
` key: ${query.keyFields?.join(", ") ?? "none"}`,
|
|
44805
|
+
` checks: ${query.checkGroups?.length ?? 0}`,
|
|
44806
|
+
` disposition: ${query.validateOnly ? "VALIDATE ONLY" : query.onErrorSkip ? `ON ERROR SKIP INTO ${query.errorTable}` : "fail-fast"}`,
|
|
44807
|
+
` gate: enabled for this parse`,
|
|
44808
|
+
` preflight: ${query.validateOnly && hasSubtables ? "requires actual source load at execution; this EXPLAIN is static" : "requires load"}`,
|
|
44809
|
+
...hasSubtables ? [query.source.kind === "JSON" ? ` Phase5C: JSON mutation requires detail-capable confirmation surface` : ` Phase5D: CSV mutation requires detail-capable confirmation surface`] : [],
|
|
44810
|
+
` writesKintone: ${query.validateOnly ? "false" : "true"}`,
|
|
44811
|
+
` duplicateKey: preflight before lookup/write (requires load)`
|
|
44812
|
+
];
|
|
44813
|
+
}
|
|
43136
44814
|
return buildSelectPlan(query, label, capabilities, orderPlans);
|
|
43137
44815
|
}
|
|
43138
44816
|
function buildValidatePlan(stmt, label) {
|
|
@@ -43586,15 +45264,15 @@ var OperationCancelledError = class extends Error {
|
|
|
43586
45264
|
};
|
|
43587
45265
|
|
|
43588
45266
|
// src/core/sql.ts
|
|
43589
|
-
function parseSqlStatement(sql) {
|
|
45267
|
+
function parseSqlStatement(sql, capabilities = {}) {
|
|
43590
45268
|
const tokens = new Lexer(sql).tokenize();
|
|
43591
|
-
const stmt = new Parser(tokens).parse();
|
|
45269
|
+
const stmt = new Parser(tokens, capabilities).parse();
|
|
43592
45270
|
validateKlikeStatement(stmt);
|
|
43593
45271
|
return stmt;
|
|
43594
45272
|
}
|
|
43595
|
-
function parseSqlStatements(sql) {
|
|
45273
|
+
function parseSqlStatements(sql, capabilities = {}) {
|
|
43596
45274
|
const tokens = new Lexer(sql).tokenize();
|
|
43597
|
-
const statements = new Parser(tokens).parseStatements();
|
|
45275
|
+
const statements = new Parser(tokens, capabilities).parseStatements();
|
|
43598
45276
|
statements.forEach(validateKlikeStatement);
|
|
43599
45277
|
return statements;
|
|
43600
45278
|
}
|
|
@@ -43677,7 +45355,8 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
43677
45355
|
validRows: s.result.validRows,
|
|
43678
45356
|
invalidRows: s.result.invalidRows,
|
|
43679
45357
|
errorCount: s.result.errorCount,
|
|
43680
|
-
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
45358
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {},
|
|
45359
|
+
...s.result.importDetail ? { importDetail: s.result.importDetail } : {}
|
|
43681
45360
|
});
|
|
43682
45361
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
43683
45362
|
Object.assign(entry, toMutationSummary(s.result));
|
|
@@ -43753,6 +45432,17 @@ function restoreSqlContextError(err, sourceSql, context) {
|
|
|
43753
45432
|
return err;
|
|
43754
45433
|
}
|
|
43755
45434
|
|
|
45435
|
+
// src/import/importGateError.ts
|
|
45436
|
+
var IMPORT_CAPABILITY_GATE_MARKER = "capability is disabled";
|
|
45437
|
+
function errorMessage(error51) {
|
|
45438
|
+
if (error51 instanceof Error) return error51.message;
|
|
45439
|
+
if (typeof error51 === "string") return error51;
|
|
45440
|
+
return null;
|
|
45441
|
+
}
|
|
45442
|
+
function isImportCapabilityGateError(error51) {
|
|
45443
|
+
return errorMessage(error51)?.includes(IMPORT_CAPABILITY_GATE_MARKER) === true;
|
|
45444
|
+
}
|
|
45445
|
+
|
|
43756
45446
|
// src/node/config.ts
|
|
43757
45447
|
var import_fs = require("fs");
|
|
43758
45448
|
var LOGICAL_APP_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
@@ -44057,7 +45747,7 @@ function clampInt(v, min, max) {
|
|
|
44057
45747
|
function flattenFormFieldProperties(properties) {
|
|
44058
45748
|
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
44059
45749
|
}
|
|
44060
|
-
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
45750
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false, subtableCode) {
|
|
44061
45751
|
const out = [];
|
|
44062
45752
|
for (const field of Object.values(properties)) {
|
|
44063
45753
|
const optionOrder = toOptionOrderMap(field.options);
|
|
@@ -44075,11 +45765,12 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
|
44075
45765
|
maxLength: normalizeConstraintValue(field.maxLength),
|
|
44076
45766
|
defaultValue: field.defaultValue,
|
|
44077
45767
|
inSubtable,
|
|
45768
|
+
...subtableCode ? { subtableCode } : {},
|
|
44078
45769
|
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
44079
45770
|
};
|
|
44080
45771
|
info.semantics = resolveFieldSemantics(info);
|
|
44081
45772
|
out.push(info);
|
|
44082
|
-
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
|
|
45773
|
+
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true, field.type === "SUBTABLE" ? field.code : subtableCode));
|
|
44083
45774
|
}
|
|
44084
45775
|
return out;
|
|
44085
45776
|
}
|
|
@@ -45359,18 +47050,55 @@ function requireSingleStatement(validation, toolName) {
|
|
|
45359
47050
|
}
|
|
45360
47051
|
var DEFAULT_MAX_RECORDS = 500;
|
|
45361
47052
|
var DEFAULT_ON_LIMIT = "error";
|
|
47053
|
+
var MCP_IMPORT_SOURCE_REQUIRED_MESSAGE = "IMPORT \u306B\u306F importSources\uFF08inline CSV/JSON\uFF09\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
|
|
47054
|
+
function importCapability(input) {
|
|
47055
|
+
const sources = input.importSources;
|
|
47056
|
+
if (!sources || sources.length === 0) return {};
|
|
47057
|
+
const byName = /* @__PURE__ */ new Map();
|
|
47058
|
+
for (const source of sources) {
|
|
47059
|
+
if (byName.has(source.name)) throw new Error(`ArgumentError: duplicate import source name: ${source.name}`);
|
|
47060
|
+
let bytes;
|
|
47061
|
+
if (source.text !== void 0 && source.base64 === void 0) {
|
|
47062
|
+
bytes = new TextEncoder().encode(source.text);
|
|
47063
|
+
} else if (source.base64 !== void 0 && source.text === void 0) {
|
|
47064
|
+
const normalized = source.base64.replace(/\s/g, "");
|
|
47065
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(normalized)) {
|
|
47066
|
+
throw new Error(`ArgumentError: invalid base64 for import source: ${source.name}`);
|
|
47067
|
+
}
|
|
47068
|
+
bytes = new Uint8Array(Buffer.from(normalized, "base64"));
|
|
47069
|
+
} else {
|
|
47070
|
+
throw new Error(`ArgumentError: import source ${source.name} requires exactly one of text or base64.`);
|
|
47071
|
+
}
|
|
47072
|
+
byName.set(source.name, { bytes, encoding: source.encoding });
|
|
47073
|
+
}
|
|
47074
|
+
return {
|
|
47075
|
+
enableImport: true,
|
|
47076
|
+
importSource: (name) => {
|
|
47077
|
+
const source = byName.get(name);
|
|
47078
|
+
return source ? { load: async () => source } : void 0;
|
|
47079
|
+
}
|
|
47080
|
+
};
|
|
47081
|
+
}
|
|
47082
|
+
function toMcpImportError(error51, importEnabled) {
|
|
47083
|
+
if (importEnabled || !isImportCapabilityGateError(error51)) return error51;
|
|
47084
|
+
if (error51 instanceof Error) {
|
|
47085
|
+
error51.message = MCP_IMPORT_SOURCE_REQUIRED_MESSAGE;
|
|
47086
|
+
return error51;
|
|
47087
|
+
}
|
|
47088
|
+
return MCP_IMPORT_SOURCE_REQUIRED_MESSAGE;
|
|
47089
|
+
}
|
|
45362
47090
|
function noOpClient() {
|
|
45363
|
-
const
|
|
47091
|
+
const fail3 = async () => {
|
|
45364
47092
|
throw new Error("No-op client should not be called.");
|
|
45365
47093
|
};
|
|
45366
47094
|
return {
|
|
45367
|
-
getRecords:
|
|
45368
|
-
openCursor:
|
|
45369
|
-
postRecords:
|
|
45370
|
-
putRecords:
|
|
45371
|
-
deleteRecords:
|
|
45372
|
-
getApps:
|
|
45373
|
-
getFields:
|
|
47095
|
+
getRecords: fail3,
|
|
47096
|
+
openCursor: fail3,
|
|
47097
|
+
postRecords: fail3,
|
|
47098
|
+
putRecords: fail3,
|
|
47099
|
+
deleteRecords: fail3,
|
|
47100
|
+
getApps: fail3,
|
|
47101
|
+
getFields: fail3,
|
|
45374
47102
|
async getProcessStatuses() {
|
|
45375
47103
|
return { enable: false, states: [] };
|
|
45376
47104
|
},
|
|
@@ -45450,7 +47178,8 @@ function toDmlValidationPayload(result) {
|
|
|
45450
47178
|
errorCount: result.errorCount,
|
|
45451
47179
|
columns: result.columns,
|
|
45452
47180
|
errors: result.errors,
|
|
45453
|
-
...result.errTable ? { errTable: result.errTable } : {}
|
|
47181
|
+
...result.errTable ? { errTable: result.errTable } : {},
|
|
47182
|
+
...result.importDetail ? { importDetail: result.importDetail } : {}
|
|
45454
47183
|
};
|
|
45455
47184
|
}
|
|
45456
47185
|
function toMutationPayload(result) {
|
|
@@ -45571,12 +47300,14 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45571
47300
|
const validationContexts = /* @__PURE__ */ new WeakMap();
|
|
45572
47301
|
async function validate(input) {
|
|
45573
47302
|
const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
|
|
47303
|
+
const importOptions = importCapability(input);
|
|
45574
47304
|
let analysis;
|
|
45575
47305
|
try {
|
|
45576
|
-
const statements = parseSqlStatements(normalized.normalizedSql);
|
|
47306
|
+
const statements = parseSqlStatements(normalized.normalizedSql, { import: importOptions.enableImport });
|
|
45577
47307
|
analysis = analyzeBatch(statements);
|
|
45578
47308
|
} catch (err) {
|
|
45579
|
-
|
|
47309
|
+
const restored = restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
|
|
47310
|
+
throw toMcpImportError(restored, importOptions.enableImport === true);
|
|
45580
47311
|
}
|
|
45581
47312
|
const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => toValidationBinding(mappedAppId, binding));
|
|
45582
47313
|
const statementValidations = analysis.statements.map((s2) => ({
|
|
@@ -45634,12 +47365,14 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45634
47365
|
}
|
|
45635
47366
|
async function explain(input) {
|
|
45636
47367
|
const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
|
|
47368
|
+
const importOptions = importCapability(input);
|
|
45637
47369
|
const appBindings = toExplainBindings(normalized.appBindingByMappedApp);
|
|
45638
47370
|
let statements;
|
|
45639
47371
|
try {
|
|
45640
|
-
statements = parseSqlStatements(normalized.normalizedSql);
|
|
47372
|
+
statements = parseSqlStatements(normalized.normalizedSql, { import: importOptions.enableImport });
|
|
45641
47373
|
} catch (err) {
|
|
45642
|
-
|
|
47374
|
+
const restored = restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
|
|
47375
|
+
throw toMcpImportError(restored, importOptions.enableImport === true);
|
|
45643
47376
|
}
|
|
45644
47377
|
const needsAppMetadata = normalized.appBindingByMappedApp.size > 0 && statements.some(explainNeedsAppMetadata);
|
|
45645
47378
|
const runtime = needsAppMetadata ? await createRuntime(serverOptions, {
|
|
@@ -45659,7 +47392,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45659
47392
|
void 0,
|
|
45660
47393
|
explainCacheContext,
|
|
45661
47394
|
runtime?.maxRecords ?? input.maxRecords,
|
|
45662
|
-
runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47395
|
+
runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47396
|
+
importOptions.enableImport
|
|
45663
47397
|
);
|
|
45664
47398
|
return {
|
|
45665
47399
|
ok: true,
|
|
@@ -45672,7 +47406,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45672
47406
|
const result = await executeSql(explainSql(explainSourceSql), explainClient, {
|
|
45673
47407
|
cacheContext: explainCacheContext,
|
|
45674
47408
|
maxRecords: runtime?.maxRecords ?? input.maxRecords,
|
|
45675
|
-
cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47409
|
+
cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47410
|
+
...importOptions
|
|
45676
47411
|
});
|
|
45677
47412
|
if (result.type !== "SELECT") {
|
|
45678
47413
|
throw new Error(`ArgumentError: EXPLAIN returned unexpected result type ${result.type}.`);
|
|
@@ -45684,6 +47419,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45684
47419
|
}
|
|
45685
47420
|
async function query(input, validated) {
|
|
45686
47421
|
const validation = validated ?? await validate(input);
|
|
47422
|
+
const importOptions = importCapability(input);
|
|
45687
47423
|
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
45688
47424
|
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
45689
47425
|
}
|
|
@@ -45716,20 +47452,22 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45716
47452
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
45717
47453
|
timeoutMs: runtime2.timeout,
|
|
45718
47454
|
cursorMaxActive: runtime2.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
45719
|
-
variables: input.variables
|
|
47455
|
+
variables: input.variables,
|
|
47456
|
+
...importOptions
|
|
45720
47457
|
});
|
|
45721
47458
|
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
45722
47459
|
}
|
|
45723
47460
|
if (!validation.isReadOnly) {
|
|
45724
47461
|
throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
|
|
45725
47462
|
}
|
|
45726
|
-
const stmt = parseSqlStatement(validation.normalizedSql);
|
|
47463
|
+
const stmt = parseSqlStatement(validation.normalizedSql, { import: importOptions.enableImport });
|
|
45727
47464
|
const noAppApiNeeded = isNoFromSelectStatement(stmt);
|
|
45728
47465
|
if (noAppApiNeeded) {
|
|
45729
47466
|
const result2 = await executeSql(validation.normalizedSql, noOpClient(), {
|
|
45730
47467
|
maxRecords: input.maxRecords ?? DEFAULT_MAX_RECORDS,
|
|
45731
47468
|
onLimitReached: input.onLimit ?? DEFAULT_ON_LIMIT,
|
|
45732
|
-
cacheContext: validation.cacheContext
|
|
47469
|
+
cacheContext: validation.cacheContext,
|
|
47470
|
+
...importOptions
|
|
45733
47471
|
});
|
|
45734
47472
|
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
45735
47473
|
if (result2.type === "VALIDATION") return toDmlValidationPayload(result2);
|
|
@@ -45753,7 +47491,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45753
47491
|
fetchParallel: runtime.fetchParallel,
|
|
45754
47492
|
onLimitReached: runtime.onLimit,
|
|
45755
47493
|
cacheContext: runtime.cacheContext,
|
|
45756
|
-
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47494
|
+
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47495
|
+
...importOptions
|
|
45757
47496
|
});
|
|
45758
47497
|
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
45759
47498
|
if (result.type === "VALIDATION") return toDmlValidationPayload(result);
|
|
@@ -45763,6 +47502,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45763
47502
|
return toSelectPayload(result);
|
|
45764
47503
|
}
|
|
45765
47504
|
async function mutateBatch(input, validation, dmlMaxRows) {
|
|
47505
|
+
const importOptions = importCapability(input);
|
|
45766
47506
|
if (!validation.containsDml) {
|
|
45767
47507
|
throw new Error("ArgumentError: batch contains no DML statements. Use ksql_query.");
|
|
45768
47508
|
}
|
|
@@ -45812,6 +47552,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45812
47552
|
timeoutMs: runtime.timeout,
|
|
45813
47553
|
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
45814
47554
|
variables: input.variables,
|
|
47555
|
+
...importOptions,
|
|
45815
47556
|
confirm: async (count, operation) => {
|
|
45816
47557
|
if (count > dmlMaxRows) {
|
|
45817
47558
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
@@ -45840,6 +47581,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45840
47581
|
}
|
|
45841
47582
|
async function mutate(input, validated) {
|
|
45842
47583
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
47584
|
+
const importOptions = importCapability(input);
|
|
45843
47585
|
const validation = validated ?? await validate(input);
|
|
45844
47586
|
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
45845
47587
|
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
@@ -45882,7 +47624,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45882
47624
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
45883
47625
|
}
|
|
45884
47626
|
return true;
|
|
45885
|
-
}
|
|
47627
|
+
},
|
|
47628
|
+
...importOptions
|
|
45886
47629
|
});
|
|
45887
47630
|
} catch (err) {
|
|
45888
47631
|
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
@@ -46052,15 +47795,27 @@ var timeout = external_exports.number().int().positive().describe("Request timeo
|
|
|
46052
47795
|
var cursorMaxActive = external_exports.number().int().min(1).max(5).describe("Maximum active Cursor API handles per kintone host in this process (1-5, default 2). Later calls update the host limit; lowering it keeps existing cursors and delays new ones until active usage falls below the new limit. Create/Get are never automatically retried; capacity waits up to 30 seconds.").optional();
|
|
46053
47796
|
var savedQueryName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).describe("Saved query name (alphanumeric, '_' and '-', up to 64 chars).");
|
|
46054
47797
|
var savedQueryTags = external_exports.array(external_exports.string().min(1)).describe("Tags for organizing saved queries.").optional();
|
|
47798
|
+
var importSources = external_exports.array(external_exports.object({
|
|
47799
|
+
name: external_exports.string().min(1).describe("Source name referenced after FROM CSV."),
|
|
47800
|
+
text: external_exports.string().describe("Inline CSV text. Mutually exclusive with base64.").optional(),
|
|
47801
|
+
base64: external_exports.string().describe("Inline CSV bytes encoded as base64. Mutually exclusive with text.").optional(),
|
|
47802
|
+
encoding: external_exports.enum(["utf8", "sjis"]).describe("Optional source encoding metadata; SQL ENCODING takes precedence.").optional()
|
|
47803
|
+
}).superRefine((source, ctx) => {
|
|
47804
|
+
if (source.text === void 0 === (source.base64 === void 0)) {
|
|
47805
|
+
ctx.addIssue({ code: "custom", message: "Exactly one of text or base64 is required." });
|
|
47806
|
+
}
|
|
47807
|
+
})).max(16).describe("IMPORT CSV/JSON named inline sources (maximum 16). Nested subtable VALIDATE ONLY/EXPLAIN is supported, but mutation is fail-closed because MCP cannot interactively display and approve parent/table delete detail. JSON drops child IDs and renumbers; cli-kintone CSV preserves matching IDs and requires REPLACE SUBTABLES. Paths are not accepted; each source is limited to 10 MiB.").optional();
|
|
46055
47808
|
var validateInputSchema = external_exports.object({
|
|
46056
47809
|
sql: external_exports.string().min(1).describe("kSQL text to validate. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
|
|
46057
|
-
profile
|
|
47810
|
+
profile,
|
|
47811
|
+
importSources
|
|
46058
47812
|
});
|
|
46059
47813
|
var explainInputSchema = external_exports.object({
|
|
46060
47814
|
sql: external_exports.string().min(1).describe("kSQL text to explain. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
|
|
46061
47815
|
profile,
|
|
46062
47816
|
maxRecords,
|
|
46063
|
-
cursorMaxActive
|
|
47817
|
+
cursorMaxActive,
|
|
47818
|
+
importSources
|
|
46064
47819
|
});
|
|
46065
47820
|
var queryInputSchema = external_exports.object({
|
|
46066
47821
|
sql: external_exports.string().min(1).describe("Read-only kSQL text. May contain multiple ;-separated statements (batch) with temp tables, e.g. CREATE TEMP TABLE #t AS SELECT ...; SELECT ... FROM #t;"),
|
|
@@ -46071,6 +47826,7 @@ var queryInputSchema = external_exports.object({
|
|
|
46071
47826
|
tempTableMaxRows,
|
|
46072
47827
|
timeout,
|
|
46073
47828
|
cursorMaxActive,
|
|
47829
|
+
importSources,
|
|
46074
47830
|
continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
|
|
46075
47831
|
maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional(),
|
|
46076
47832
|
variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
|
|
@@ -46085,6 +47841,7 @@ var mutateInputSchema = external_exports.object({
|
|
|
46085
47841
|
tempTableMaxRows,
|
|
46086
47842
|
timeout,
|
|
46087
47843
|
cursorMaxActive,
|
|
47844
|
+
importSources,
|
|
46088
47845
|
dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional(),
|
|
46089
47846
|
variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
|
|
46090
47847
|
});
|
|
@@ -46173,9 +47930,14 @@ Options:
|
|
|
46173
47930
|
--config <path> Config file path (default: ./ksql.config.json or KSQL_CONFIG)
|
|
46174
47931
|
--profile <name> Default profile name
|
|
46175
47932
|
-h, --help Show help
|
|
47933
|
+
|
|
47934
|
+
IMPORT CSV/JSON is call-scoped and disabled by default. Supply named
|
|
47935
|
+
inline importSources (text or base64 bytes) to enable it; filesystem paths are not accepted.
|
|
47936
|
+
Nested JSON/CSV subtable mutation is fail-closed on MCP: use VALIDATE ONLY/EXPLAIN.
|
|
47937
|
+
JSON child IDs are rejected and replacement renumbers all rows.
|
|
46176
47938
|
`);
|
|
46177
47939
|
}
|
|
46178
|
-
var SERVER_VERSION = true ? "3.
|
|
47940
|
+
var SERVER_VERSION = true ? "3.6.1" : "0.0.0-dev";
|
|
46179
47941
|
function createServer(args) {
|
|
46180
47942
|
const server = new McpServer({
|
|
46181
47943
|
name: "ksql-mcp",
|
|
@@ -46187,12 +47949,12 @@ function createServer(args) {
|
|
|
46187
47949
|
});
|
|
46188
47950
|
server.registerTool("ksql_validate", {
|
|
46189
47951
|
title: "Validate kSQL",
|
|
46190
|
-
description: "Parse and validate kSQL without calling kintone APIs. Use this before executing generated SQL.",
|
|
47952
|
+
description: "Parse and validate kSQL without calling kintone APIs. Use this before executing generated SQL. IMPORT CSV/JSON is enabled only when named inline importSources are supplied.",
|
|
46191
47953
|
inputSchema: validateInputShape
|
|
46192
47954
|
}, tools.validateTool);
|
|
46193
47955
|
server.registerTool("ksql_explain", {
|
|
46194
47956
|
title: "Explain kSQL",
|
|
46195
|
-
description: "Return the schema-aware kSQL execution plan. Reads form metadata and, when needed, process status metadata; never reads or writes records.",
|
|
47957
|
+
description: "Return the schema-aware kSQL execution plan. Reads form metadata and, when needed, process status metadata; never reads or writes records. IMPORT CSV/JSON is enabled only when named inline importSources are supplied.",
|
|
46196
47958
|
inputSchema: explainInputShape
|
|
46197
47959
|
}, tools.explainTool);
|
|
46198
47960
|
server.registerTool("ksql_query", {
|
|
@@ -46201,7 +47963,7 @@ function createServer(args) {
|
|
|
46201
47963
|
inputSchema: queryInputShape
|
|
46202
47964
|
}, tools.queryTool);
|
|
46203
47965
|
server.registerTool("ksql_mutate", {
|
|
46204
|
-
title: "Run mutating kSQL",
|
|
47966
|
+
title: "Run mutating kSQL (IMPORT CSV/JSON via importSources)",
|
|
46205
47967
|
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. ON ERROR SKIP INTO #err optionally isolates local Tier-0 validation failures and writes only valid rows; REJECT LIMIT stops with zero writes while returning diagnostics. NUMBER targets use the destination app numberPrecision settings for integer-digit validation in normal, validation-only, and skip paths; settings failures are fail-closed. Excess fractional digits pass through for kintone to round automatically. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. UPDATE ... FROM supports copying scalar fields from an app or temp table by matching target $id or a single-line-text/number business key to one source key. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: source SELECT, ON ERROR SKIP candidates, and UPDATE ... FROM app reads use the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows by default (adjustable via tempTableMaxRows).",
|
|
46206
47968
|
inputSchema: mutateInputShape
|
|
46207
47969
|
}, tools.mutateTool);
|