@rex0220/kintone-sql-tools 3.4.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist-cli/ksql.js +2349 -93
- package/dist-mcp/ksql-mcp.js +2386 -137
- 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;
|
|
@@ -31656,13 +31657,20 @@ var Parser = class {
|
|
|
31656
31657
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
31657
31658
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
31658
31659
|
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
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
|
+
}
|
|
31659
31667
|
break;
|
|
31660
31668
|
}
|
|
31661
31669
|
default:
|
|
31662
31670
|
break;
|
|
31663
31671
|
}
|
|
31664
31672
|
throw new ParseError(
|
|
31665
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31673
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / VALIDATE / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31666
31674
|
tok
|
|
31667
31675
|
);
|
|
31668
31676
|
}
|
|
@@ -31673,7 +31681,7 @@ var Parser = class {
|
|
|
31673
31681
|
this.expect("SET" /* SET */);
|
|
31674
31682
|
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
31675
31683
|
this.expect("=" /* EQ */);
|
|
31676
|
-
const expr = this.parseScalarExpr("SET", true);
|
|
31684
|
+
const expr = this.peek().kind === "[" /* LBRACKET */ ? this.parseArrayLiteral() : this.parseScalarExpr("SET", true);
|
|
31677
31685
|
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
31678
31686
|
}
|
|
31679
31687
|
parseDeclareVariable() {
|
|
@@ -31834,11 +31842,265 @@ var Parser = class {
|
|
|
31834
31842
|
query = this.parseDelete();
|
|
31835
31843
|
} else if (tok.kind === "REORDER" /* REORDER */) {
|
|
31836
31844
|
query = this.parseReorder();
|
|
31845
|
+
} else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
|
|
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();
|
|
31837
31852
|
} else {
|
|
31838
|
-
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
31853
|
+
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
31839
31854
|
}
|
|
31840
31855
|
return { type: "EXPLAIN", query };
|
|
31841
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
|
+
}
|
|
32054
|
+
/** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
|
|
32055
|
+
parseValidate() {
|
|
32056
|
+
const validateTok = this.advance();
|
|
32057
|
+
const name = this.parseIdentifier();
|
|
32058
|
+
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
32059
|
+
if (subtableCode) {
|
|
32060
|
+
throw new ParseError("VALIDATE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3092\u5BFE\u8C61\u306B\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
32061
|
+
}
|
|
32062
|
+
let fields;
|
|
32063
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
32064
|
+
fields = this.parseIdentList();
|
|
32065
|
+
this.expect(")" /* RPAREN */);
|
|
32066
|
+
}
|
|
32067
|
+
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
32068
|
+
const checks = this.parseCheckGroups();
|
|
32069
|
+
let errorTable;
|
|
32070
|
+
if (this.consume("INTO" /* INTO */)) {
|
|
32071
|
+
const tableTok = this.peek();
|
|
32072
|
+
if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
|
|
32073
|
+
throw new ParseError("VALIDATE INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tableTok);
|
|
32074
|
+
}
|
|
32075
|
+
errorTable = this.parseTableName();
|
|
32076
|
+
}
|
|
32077
|
+
const stmt = { type: "VALIDATE", appId, fields, where, ...checks, ...errorTable ? { errorTable } : {} };
|
|
32078
|
+
this.assertValidateExpressions(stmt, validateTok);
|
|
32079
|
+
return stmt;
|
|
32080
|
+
}
|
|
32081
|
+
/** v1 VALIDATE is single-app/local: subqueries and qualified references are rejected. */
|
|
32082
|
+
assertValidateExpressions(stmt, tok) {
|
|
32083
|
+
const visit = (node) => {
|
|
32084
|
+
if (Array.isArray(node)) {
|
|
32085
|
+
node.forEach(visit);
|
|
32086
|
+
return;
|
|
32087
|
+
}
|
|
32088
|
+
if (node === null || typeof node !== "object") return;
|
|
32089
|
+
const obj = node;
|
|
32090
|
+
if (obj.type === "EXISTS" || obj.type === "SUBQUERY_IN_LIST" || obj.type === "SCALAR_SUBQUERY") {
|
|
32091
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
32092
|
+
}
|
|
32093
|
+
if (obj.type === "FIELD" && obj.tableAlias !== null && obj.tableAlias !== void 0) {
|
|
32094
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
32095
|
+
}
|
|
32096
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string" && obj.field.includes(".")) {
|
|
32097
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
32098
|
+
}
|
|
32099
|
+
Object.values(obj).forEach(visit);
|
|
32100
|
+
};
|
|
32101
|
+
visit(stmt.where);
|
|
32102
|
+
visit(stmt.checkGroups);
|
|
32103
|
+
}
|
|
31842
32104
|
// ----------------------------------------------------------
|
|
31843
32105
|
// ASSERT
|
|
31844
32106
|
//
|
|
@@ -32124,6 +32386,17 @@ var Parser = class {
|
|
|
32124
32386
|
const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
|
|
32125
32387
|
return { type: "SCALAR_VALUE_COL", expr, alias: alias2 };
|
|
32126
32388
|
}
|
|
32389
|
+
if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
|
|
32390
|
+
const variable = this.advance();
|
|
32391
|
+
if (!this.consume("AS" /* AS */)) {
|
|
32392
|
+
throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
32393
|
+
}
|
|
32394
|
+
return {
|
|
32395
|
+
type: "VARIABLE_COL",
|
|
32396
|
+
name: variable.value.slice(1).toLowerCase(),
|
|
32397
|
+
alias: this.parseAliasName()
|
|
32398
|
+
};
|
|
32399
|
+
}
|
|
32127
32400
|
const windowFunc = this.tryWindowFunc();
|
|
32128
32401
|
if (windowFunc !== null) {
|
|
32129
32402
|
return this.parseWindowColumn(windowFunc);
|
|
@@ -32939,9 +33212,7 @@ var Parser = class {
|
|
|
32939
33212
|
}
|
|
32940
33213
|
if (this.consume("NOT" /* NOT */)) {
|
|
32941
33214
|
if (this.consume("IN" /* IN */)) {
|
|
32942
|
-
this.
|
|
32943
|
-
const right2 = this.parseInListOrSubquery();
|
|
32944
|
-
this.expect(")" /* RPAREN */);
|
|
33215
|
+
const right2 = this.parseInRight();
|
|
32945
33216
|
return { type: "BINARY", op: "NOT_IN", left: field, right: right2 };
|
|
32946
33217
|
}
|
|
32947
33218
|
if (this.consume("LIKE" /* LIKE */)) {
|
|
@@ -32958,9 +33229,7 @@ var Parser = class {
|
|
|
32958
33229
|
);
|
|
32959
33230
|
}
|
|
32960
33231
|
if (this.consume("IN" /* IN */)) {
|
|
32961
|
-
this.
|
|
32962
|
-
const right2 = this.parseInListOrSubquery();
|
|
32963
|
-
this.expect(")" /* RPAREN */);
|
|
33232
|
+
const right2 = this.parseInRight();
|
|
32964
33233
|
return { type: "BINARY", op: "IN", left: field, right: right2 };
|
|
32965
33234
|
}
|
|
32966
33235
|
if (this.consume("KLIKE" /* KLIKE */)) {
|
|
@@ -33127,6 +33396,18 @@ var Parser = class {
|
|
|
33127
33396
|
);
|
|
33128
33397
|
}
|
|
33129
33398
|
// IN (...) — 値リストまたはサブクエリ
|
|
33399
|
+
parseInRight() {
|
|
33400
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
33401
|
+
const right = this.parseInListOrSubquery();
|
|
33402
|
+
this.expect(")" /* RPAREN */);
|
|
33403
|
+
return right;
|
|
33404
|
+
}
|
|
33405
|
+
const variable = this.expect(
|
|
33406
|
+
"VARIABLE" /* VARIABLE */,
|
|
33407
|
+
"IN / NOT IN \u306E\u5F8C\u306B\u306F (\u5024\u30EA\u30B9\u30C8\u307E\u305F\u306F SELECT) \u304B\u914D\u5217\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059"
|
|
33408
|
+
);
|
|
33409
|
+
return { type: "VARIABLE_IN_LIST", name: variable.value.slice(1).toLowerCase() };
|
|
33410
|
+
}
|
|
33130
33411
|
parseInListOrSubquery() {
|
|
33131
33412
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
33132
33413
|
const query = this.parseSelect();
|
|
@@ -33934,10 +34215,10 @@ function getStatementType(stmt) {
|
|
|
33934
34215
|
return typeof obj.type === "string" ? obj.type : "UNKNOWN";
|
|
33935
34216
|
}
|
|
33936
34217
|
function isDmlType(type) {
|
|
33937
|
-
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";
|
|
33938
34219
|
}
|
|
33939
34220
|
function isReadOnlyType(type) {
|
|
33940
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
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";
|
|
33941
34222
|
}
|
|
33942
34223
|
function writesKintone(stmt) {
|
|
33943
34224
|
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
@@ -33948,6 +34229,8 @@ function isReadOnlyStatement(stmt) {
|
|
|
33948
34229
|
function requiresCompleteInput(stmt) {
|
|
33949
34230
|
if (isDmlType(stmt.type)) return true;
|
|
33950
34231
|
switch (stmt.type) {
|
|
34232
|
+
case "VALIDATE":
|
|
34233
|
+
return true;
|
|
33951
34234
|
case "SELECT":
|
|
33952
34235
|
return selectRequiresCompleteInput(stmt);
|
|
33953
34236
|
case "UNION":
|
|
@@ -33988,6 +34271,7 @@ function whereRequiresCompleteInput(where) {
|
|
|
33988
34271
|
case "EXISTS":
|
|
33989
34272
|
return selectRequiresCompleteInput(where.query);
|
|
33990
34273
|
case "NULL_CHECK":
|
|
34274
|
+
case "BOOLEAN":
|
|
33991
34275
|
return false;
|
|
33992
34276
|
}
|
|
33993
34277
|
}
|
|
@@ -34011,6 +34295,8 @@ function getInsertValuesCount(stmt) {
|
|
|
34011
34295
|
// src/engine/pushDownNot.ts
|
|
34012
34296
|
function pushDownNot(expr) {
|
|
34013
34297
|
switch (expr.type) {
|
|
34298
|
+
case "BOOLEAN":
|
|
34299
|
+
return { type: "BOOLEAN", value: !expr.value };
|
|
34014
34300
|
case "BINARY": {
|
|
34015
34301
|
const negated = negateOp(expr.op);
|
|
34016
34302
|
if (negated === null) {
|
|
@@ -34090,6 +34376,7 @@ function whereHasLike(where) {
|
|
|
34090
34376
|
case "BINARY":
|
|
34091
34377
|
case "NULL_CHECK":
|
|
34092
34378
|
case "EXISTS":
|
|
34379
|
+
case "BOOLEAN":
|
|
34093
34380
|
return false;
|
|
34094
34381
|
}
|
|
34095
34382
|
}
|
|
@@ -34105,6 +34392,7 @@ function whereHasKlike(where) {
|
|
|
34105
34392
|
case "BINARY":
|
|
34106
34393
|
case "NULL_CHECK":
|
|
34107
34394
|
case "EXISTS":
|
|
34395
|
+
case "BOOLEAN":
|
|
34108
34396
|
return false;
|
|
34109
34397
|
}
|
|
34110
34398
|
}
|
|
@@ -34124,6 +34412,8 @@ function whereToKintone(expr) {
|
|
|
34124
34412
|
return convertGroup(expr);
|
|
34125
34413
|
case "EXISTS":
|
|
34126
34414
|
throw new KintoneQueryError("EXISTS \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
|
|
34415
|
+
case "BOOLEAN":
|
|
34416
|
+
throw new KintoneQueryError("internal error: BOOLEAN predicate reached kintone query conversion");
|
|
34127
34417
|
}
|
|
34128
34418
|
}
|
|
34129
34419
|
function convertBinary(expr) {
|
|
@@ -34202,6 +34492,8 @@ function convertValue(value, op) {
|
|
|
34202
34492
|
switch (value.type) {
|
|
34203
34493
|
case "VARIABLE":
|
|
34204
34494
|
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
34495
|
+
case "VARIABLE_IN_LIST":
|
|
34496
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
34205
34497
|
case "STRING":
|
|
34206
34498
|
return convertString(value);
|
|
34207
34499
|
case "NUMBER":
|
|
@@ -34281,6 +34573,8 @@ function resolveSelectMode(stmt) {
|
|
|
34281
34573
|
function whereRequiresJsEval(where) {
|
|
34282
34574
|
if (where === null) return false;
|
|
34283
34575
|
switch (where.type) {
|
|
34576
|
+
case "BOOLEAN":
|
|
34577
|
+
return true;
|
|
34284
34578
|
case "BINARY":
|
|
34285
34579
|
return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY" || isLike(where);
|
|
34286
34580
|
case "NULL_CHECK":
|
|
@@ -34675,6 +34969,7 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
34675
34969
|
walkWhere(where.expr, phase);
|
|
34676
34970
|
return;
|
|
34677
34971
|
case "EXISTS":
|
|
34972
|
+
case "BOOLEAN":
|
|
34678
34973
|
return;
|
|
34679
34974
|
}
|
|
34680
34975
|
};
|
|
@@ -34713,6 +35008,8 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
34713
35008
|
break;
|
|
34714
35009
|
case "LITERAL_COL":
|
|
34715
35010
|
break;
|
|
35011
|
+
case "VARIABLE_COL":
|
|
35012
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
34716
35013
|
case "AGGREGATE":
|
|
34717
35014
|
if (col.arg.type !== "WILDCARD") walkArith(col.arg, "select");
|
|
34718
35015
|
break;
|
|
@@ -34888,6 +35185,7 @@ function stripCteAlias(where, alias) {
|
|
|
34888
35185
|
case "GROUP":
|
|
34889
35186
|
return { ...where, expr: stripCteAlias(where.expr, alias) };
|
|
34890
35187
|
case "EXISTS":
|
|
35188
|
+
case "BOOLEAN":
|
|
34891
35189
|
return where;
|
|
34892
35190
|
}
|
|
34893
35191
|
}
|
|
@@ -34925,6 +35223,7 @@ function extractAndLeaves(where, accept) {
|
|
|
34925
35223
|
case "NULL_CHECK":
|
|
34926
35224
|
case "NOT":
|
|
34927
35225
|
case "EXISTS":
|
|
35226
|
+
case "BOOLEAN":
|
|
34928
35227
|
return null;
|
|
34929
35228
|
}
|
|
34930
35229
|
}
|
|
@@ -35063,6 +35362,7 @@ function collectKlikes(where, out) {
|
|
|
35063
35362
|
case "BINARY":
|
|
35064
35363
|
case "NULL_CHECK":
|
|
35065
35364
|
case "EXISTS":
|
|
35365
|
+
case "BOOLEAN":
|
|
35066
35366
|
return;
|
|
35067
35367
|
}
|
|
35068
35368
|
}
|
|
@@ -35119,6 +35419,11 @@ function validateStatement(stmt) {
|
|
|
35119
35419
|
);
|
|
35120
35420
|
}
|
|
35121
35421
|
return;
|
|
35422
|
+
case "VALIDATE":
|
|
35423
|
+
if (containsKlike(stmt)) {
|
|
35424
|
+
throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F VALIDATE \u306E WHERE / CHECK \u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
35425
|
+
}
|
|
35426
|
+
return;
|
|
35122
35427
|
case "SHOW_APPS":
|
|
35123
35428
|
case "DESCRIBE":
|
|
35124
35429
|
case "DROP_TEMP_TABLE":
|
|
@@ -35206,6 +35511,8 @@ function isDescendantOf(root, target) {
|
|
|
35206
35511
|
case "NULL_CHECK":
|
|
35207
35512
|
case "EXISTS":
|
|
35208
35513
|
return false;
|
|
35514
|
+
case "BOOLEAN":
|
|
35515
|
+
return false;
|
|
35209
35516
|
}
|
|
35210
35517
|
}
|
|
35211
35518
|
function walkWithoutNestedSelects(node, visitWhere) {
|
|
@@ -35267,8 +35574,12 @@ function collectVariableRefs(node, refs) {
|
|
|
35267
35574
|
}
|
|
35268
35575
|
if (node !== null && typeof node === "object") {
|
|
35269
35576
|
const obj = node;
|
|
35270
|
-
|
|
35271
|
-
|
|
35577
|
+
const type = obj["type"];
|
|
35578
|
+
if ((type === "VARIABLE" || type === "VARIABLE_COL" || type === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") {
|
|
35579
|
+
refs.push({
|
|
35580
|
+
name: obj["name"],
|
|
35581
|
+
kind: type === "VARIABLE" ? "scalar" : type === "VARIABLE_COL" ? "select-column" : "array-in-list"
|
|
35582
|
+
});
|
|
35272
35583
|
return;
|
|
35273
35584
|
}
|
|
35274
35585
|
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
@@ -35309,9 +35620,9 @@ function analyzeBatch(statements) {
|
|
|
35309
35620
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
35310
35621
|
const variableOrder = [];
|
|
35311
35622
|
statements.forEach((stmt, index) => {
|
|
35312
|
-
const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
35623
|
+
const validationTable = stmt.type === "VALIDATE" && stmt.errorTable ? stmt.errorTable : "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
35313
35624
|
if (statements.length === 1 && validationTable) {
|
|
35314
|
-
const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
35625
|
+
const message = stmt.type === "VALIDATE" ? "ArgumentError: VALIDATE INTO requires a batch." : "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
35315
35626
|
throw new BatchAnalysisError(message, index);
|
|
35316
35627
|
}
|
|
35317
35628
|
const statementType = getStatementType(stmt);
|
|
@@ -35320,23 +35631,43 @@ function analyzeBatch(statements) {
|
|
|
35320
35631
|
const refs = /* @__PURE__ */ new Set();
|
|
35321
35632
|
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
35322
35633
|
const dependsOn = /* @__PURE__ */ new Set();
|
|
35323
|
-
const variableRefs =
|
|
35634
|
+
const variableRefs = [];
|
|
35324
35635
|
collectVariableRefs(stmt, variableRefs);
|
|
35325
|
-
|
|
35326
|
-
|
|
35636
|
+
const referencedThisStatement = /* @__PURE__ */ new Set();
|
|
35637
|
+
for (const use of variableRefs) {
|
|
35638
|
+
const def = variableDefs.get(use.name);
|
|
35327
35639
|
if (def === void 0) {
|
|
35328
35640
|
throw new BatchAnalysisError(
|
|
35329
|
-
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
35641
|
+
`ParseError: variable @${use.name} is not defined before statement ${index + 1}.`,
|
|
35642
|
+
index
|
|
35643
|
+
);
|
|
35644
|
+
}
|
|
35645
|
+
if (def.kind === "scalar" && use.kind === "array-in-list") {
|
|
35646
|
+
throw new BatchAnalysisError(
|
|
35647
|
+
`ParseError: scalar variable @${use.name} cannot be used as IN @${use.name}; use IN (@${use.name}) instead.`,
|
|
35648
|
+
index
|
|
35649
|
+
);
|
|
35650
|
+
}
|
|
35651
|
+
if (def.kind === "array" && use.kind !== "array-in-list") {
|
|
35652
|
+
throw new BatchAnalysisError(
|
|
35653
|
+
`ParseError: array variable @${use.name} can only be used as IN @${use.name}.`,
|
|
35330
35654
|
index
|
|
35331
35655
|
);
|
|
35332
35656
|
}
|
|
35333
|
-
|
|
35657
|
+
if (!referencedThisStatement.has(use.name)) {
|
|
35658
|
+
def.referencedBy.push(index);
|
|
35659
|
+
referencedThisStatement.add(use.name);
|
|
35660
|
+
}
|
|
35334
35661
|
}
|
|
35335
35662
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
35336
35663
|
if (variableDefs.has(stmt.name)) {
|
|
35337
35664
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
35338
35665
|
}
|
|
35339
|
-
variableDefs.set(stmt.name, {
|
|
35666
|
+
variableDefs.set(stmt.name, {
|
|
35667
|
+
index,
|
|
35668
|
+
kind: stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? "array" : "scalar",
|
|
35669
|
+
referencedBy: []
|
|
35670
|
+
});
|
|
35340
35671
|
variableOrder.push(stmt.name);
|
|
35341
35672
|
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
35342
35673
|
throw new BatchAnalysisError(
|
|
@@ -35369,7 +35700,7 @@ function analyzeBatch(statements) {
|
|
|
35369
35700
|
dependsOn.add(at);
|
|
35370
35701
|
}
|
|
35371
35702
|
if (validationTable) {
|
|
35372
|
-
const payloadFields = 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 : [];
|
|
35373
35704
|
const signature = JSON.stringify(payloadFields);
|
|
35374
35705
|
const at = defined.get(validationTable);
|
|
35375
35706
|
if (at === void 0) {
|
|
@@ -35444,6 +35775,7 @@ function analyzeBatch(statements) {
|
|
|
35444
35775
|
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
35445
35776
|
const variables = variableOrder.map((name) => ({
|
|
35446
35777
|
name,
|
|
35778
|
+
kind: variableDefs.get(name).kind,
|
|
35447
35779
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
35448
35780
|
}));
|
|
35449
35781
|
return {
|
|
@@ -35720,6 +36052,7 @@ function whereNeedsFieldMetadata(where) {
|
|
|
35720
36052
|
case "GROUP":
|
|
35721
36053
|
return whereNeedsFieldMetadata(where.expr);
|
|
35722
36054
|
case "EXISTS":
|
|
36055
|
+
case "BOOLEAN":
|
|
35723
36056
|
return false;
|
|
35724
36057
|
}
|
|
35725
36058
|
}
|
|
@@ -35744,6 +36077,7 @@ function explainNeedsAppMetadata(statement) {
|
|
|
35744
36077
|
seen.add(node);
|
|
35745
36078
|
if (Array.isArray(node)) return node.some(visit);
|
|
35746
36079
|
const item = node;
|
|
36080
|
+
if (item["type"] === "VALIDATE") return true;
|
|
35747
36081
|
if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
|
|
35748
36082
|
return true;
|
|
35749
36083
|
}
|
|
@@ -36234,6 +36568,8 @@ function resolveFieldRef(row, field) {
|
|
|
36234
36568
|
// src/engine/evalWhere.ts
|
|
36235
36569
|
function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
|
|
36236
36570
|
switch (expr.type) {
|
|
36571
|
+
case "BOOLEAN":
|
|
36572
|
+
return expr.value;
|
|
36237
36573
|
case "BINARY":
|
|
36238
36574
|
return evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
|
|
36239
36575
|
case "NULL_CHECK":
|
|
@@ -36404,6 +36740,8 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
36404
36740
|
switch (value.type) {
|
|
36405
36741
|
case "VARIABLE":
|
|
36406
36742
|
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
36743
|
+
case "VARIABLE_IN_LIST":
|
|
36744
|
+
throw new Error(`ParseError: unresolved batch array variable @${value.name}.`);
|
|
36407
36745
|
case "STRING":
|
|
36408
36746
|
return value.value;
|
|
36409
36747
|
case "NUMBER":
|
|
@@ -36719,6 +37057,9 @@ function collectConditionFields(expr, out) {
|
|
|
36719
37057
|
case "GROUP":
|
|
36720
37058
|
collectConditionFields(expr.expr, out);
|
|
36721
37059
|
break;
|
|
37060
|
+
case "EXISTS":
|
|
37061
|
+
case "BOOLEAN":
|
|
37062
|
+
break;
|
|
36722
37063
|
}
|
|
36723
37064
|
}
|
|
36724
37065
|
function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
@@ -36934,6 +37275,8 @@ function convertDmlSqlValue(value, fieldType) {
|
|
|
36934
37275
|
switch (value.type) {
|
|
36935
37276
|
case "VARIABLE":
|
|
36936
37277
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
37278
|
+
case "VARIABLE_IN_LIST":
|
|
37279
|
+
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
36937
37280
|
case "STRING":
|
|
36938
37281
|
return convertString2(value.value, fieldType);
|
|
36939
37282
|
case "NUMBER":
|
|
@@ -37769,6 +38112,8 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
|
|
|
37769
38112
|
const out = {};
|
|
37770
38113
|
for (const [colIdx, col] of columns.entries()) {
|
|
37771
38114
|
switch (col.type) {
|
|
38115
|
+
case "VARIABLE_COL":
|
|
38116
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
37772
38117
|
case "WILDCARD":
|
|
37773
38118
|
Object.assign(out, stripParentShortcutColumns(row));
|
|
37774
38119
|
break;
|
|
@@ -37872,6 +38217,8 @@ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
|
|
|
37872
38217
|
}
|
|
37873
38218
|
function computeOutputKey(col, colIdx, defaultFieldKeys) {
|
|
37874
38219
|
switch (col.type) {
|
|
38220
|
+
case "VARIABLE_COL":
|
|
38221
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
37875
38222
|
case "FIELD":
|
|
37876
38223
|
return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
|
|
37877
38224
|
case "LITERAL_COL":
|
|
@@ -38315,9 +38662,15 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
38315
38662
|
candidate.record ??= {};
|
|
38316
38663
|
const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
|
|
38317
38664
|
for (const code of targetFields) {
|
|
38665
|
+
if (!candidate.payload.has(code)) continue;
|
|
38318
38666
|
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
|
|
38319
38667
|
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
38320
|
-
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
|
+
}
|
|
38321
38674
|
}
|
|
38322
38675
|
if (validateMissingCreateFields && candidate.mode === "create") {
|
|
38323
38676
|
for (const info of fieldInfos) {
|
|
@@ -38386,6 +38739,11 @@ function renderValidationValue(value) {
|
|
|
38386
38739
|
return String(value);
|
|
38387
38740
|
}
|
|
38388
38741
|
|
|
38742
|
+
// src/core/existingRecordValidation.ts
|
|
38743
|
+
function renderExistingValidationValue(raw, fieldType) {
|
|
38744
|
+
return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
|
|
38745
|
+
}
|
|
38746
|
+
|
|
38389
38747
|
// src/core/optimization/whereCapability.ts
|
|
38390
38748
|
var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
|
|
38391
38749
|
var EQUALITY_IN = ["=", "!=", "in", "not in"];
|
|
@@ -38460,6 +38818,8 @@ function classifyWhereCapability(where, resolveField2) {
|
|
|
38460
38818
|
}
|
|
38461
38819
|
function classifyNode(where, resolveField2) {
|
|
38462
38820
|
switch (where.type) {
|
|
38821
|
+
case "BOOLEAN":
|
|
38822
|
+
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
38463
38823
|
case "BINARY":
|
|
38464
38824
|
return classifyBinary(where.op, where.left, where.right.type, resolveField2);
|
|
38465
38825
|
case "NULL_CHECK":
|
|
@@ -38572,6 +38932,862 @@ function unsupported(code, field, fieldType, operator) {
|
|
|
38572
38932
|
return { capability: "UNSUPPORTED", reasons: [{ code, field, fieldType, operator }] };
|
|
38573
38933
|
}
|
|
38574
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
|
+
|
|
38575
39791
|
// src/execute.ts
|
|
38576
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";
|
|
38577
39793
|
var SearchAbortedError = class extends Error {
|
|
@@ -38582,6 +39798,7 @@ var SearchAbortedError = class extends Error {
|
|
|
38582
39798
|
};
|
|
38583
39799
|
var materializedMetaBySelectResult = /* @__PURE__ */ new WeakMap();
|
|
38584
39800
|
var materializedMetaByValidationResult = /* @__PURE__ */ new WeakMap();
|
|
39801
|
+
var importSourceByDmlStatement = /* @__PURE__ */ new WeakMap();
|
|
38585
39802
|
var defaultCacheContextByClient = /* @__PURE__ */ new WeakMap();
|
|
38586
39803
|
var nextDefaultCacheContextId = 1;
|
|
38587
39804
|
function resolveCacheContext(client, explicit) {
|
|
@@ -38596,7 +39813,7 @@ function resolveCacheContext(client, explicit) {
|
|
|
38596
39813
|
async function execute(sql, client, options = {}) {
|
|
38597
39814
|
const startedAt = Date.now();
|
|
38598
39815
|
const cacheContext = resolveCacheContext(client, options.cacheContext);
|
|
38599
|
-
const stmt = parseSql(sql);
|
|
39816
|
+
const stmt = parseSql(sql, options.enableImport === true);
|
|
38600
39817
|
const metrics = createEmptyMetrics();
|
|
38601
39818
|
const countedClient = wrapClientWithMetrics(client, metrics);
|
|
38602
39819
|
const collector = { aborted: false };
|
|
@@ -38776,6 +39993,7 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38776
39993
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
38777
39994
|
}
|
|
38778
39995
|
validateKlikeStatement(stmt);
|
|
39996
|
+
if (stmt.type === "IMPORT") return executeImport(stmt, client, options, cacheContext);
|
|
38779
39997
|
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
38780
39998
|
if (stmt.validationErrorTable) {
|
|
38781
39999
|
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
@@ -38786,6 +40004,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38786
40004
|
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
38787
40005
|
}
|
|
38788
40006
|
switch (stmt.type) {
|
|
40007
|
+
case "VALIDATE":
|
|
40008
|
+
return executeExistingRecordValidation(stmt, client, options, cacheContext);
|
|
38789
40009
|
case "SELECT":
|
|
38790
40010
|
return executeSelect(stmt, client, options, cacheContext);
|
|
38791
40011
|
case "UNION":
|
|
@@ -38831,6 +40051,144 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38831
40051
|
return executeAssert(stmt, client, options, cacheContext);
|
|
38832
40052
|
}
|
|
38833
40053
|
}
|
|
40054
|
+
var EXISTING_VALIDATION_COLUMNS = ["$id", "$err_field", "$err_code", "$err_message", "$err_value"];
|
|
40055
|
+
function hasAuditableConstraint(field) {
|
|
40056
|
+
return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
|
|
40057
|
+
}
|
|
40058
|
+
function resolveExistingValidationTargets(stmt, fieldInfos) {
|
|
40059
|
+
const byCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
40060
|
+
const auditable = (field) => !field.inSubtable && (field.fieldType === "NUMBER" || hasAuditableConstraint(field));
|
|
40061
|
+
if (stmt.fields === void 0) return fieldInfos.filter(auditable);
|
|
40062
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40063
|
+
return stmt.fields.map((code) => {
|
|
40064
|
+
if (seen.has(code)) throw new Error(`ArgumentError: VALIDATE field ${code} is duplicated.`);
|
|
40065
|
+
seen.add(code);
|
|
40066
|
+
if (code === "$id") throw new Error("ArgumentError: VALIDATE cannot audit system field $id.");
|
|
40067
|
+
const info = byCode.get(code);
|
|
40068
|
+
if (!info) throw new Error(`ArgumentError: VALIDATE field ${code} does not exist.`);
|
|
40069
|
+
if (info.inSubtable) throw new Error(`ArgumentError: VALIDATE field ${code} is a subtable child field.`);
|
|
40070
|
+
if (!auditable(info)) throw new Error(`ArgumentError: VALIDATE field ${code} has no auditable constraint.`);
|
|
40071
|
+
return info;
|
|
40072
|
+
});
|
|
40073
|
+
}
|
|
40074
|
+
function collectValidateWhereFields(where) {
|
|
40075
|
+
const fields = [];
|
|
40076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40077
|
+
const add = (field) => {
|
|
40078
|
+
if (!seen.has(field)) {
|
|
40079
|
+
seen.add(field);
|
|
40080
|
+
fields.push(field);
|
|
40081
|
+
}
|
|
40082
|
+
};
|
|
40083
|
+
const visit = (node) => {
|
|
40084
|
+
if (Array.isArray(node)) {
|
|
40085
|
+
node.forEach(visit);
|
|
40086
|
+
return;
|
|
40087
|
+
}
|
|
40088
|
+
if (node === null || typeof node !== "object") return;
|
|
40089
|
+
const obj = node;
|
|
40090
|
+
if (obj.type === "FIELD" && typeof obj.field === "string") add(obj.field);
|
|
40091
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string") add(obj.field);
|
|
40092
|
+
Object.values(obj).forEach(visit);
|
|
40093
|
+
};
|
|
40094
|
+
visit(where);
|
|
40095
|
+
return fields;
|
|
40096
|
+
}
|
|
40097
|
+
function existingValidationColumnMeta() {
|
|
40098
|
+
return new Map(EXISTING_VALIDATION_COLUMNS.map((column) => [column, {
|
|
40099
|
+
fieldType: column === "$id" ? "KSQL_NUMBER" : "KSQL_STRING",
|
|
40100
|
+
sortKind: column === "$id" ? "number" : "string",
|
|
40101
|
+
semantics: syntheticSemantics(column === "$id" ? "number" : "string")
|
|
40102
|
+
}]));
|
|
40103
|
+
}
|
|
40104
|
+
async function executeExistingRecordValidation(stmt, client, options, cacheContext) {
|
|
40105
|
+
if (stmt.errorTable) throw new Error("ArgumentError: VALIDATE INTO requires a batch.");
|
|
40106
|
+
return executeExistingRecordValidationCore(stmt, client, options, cacheContext);
|
|
40107
|
+
}
|
|
40108
|
+
async function executeExistingRecordValidationCore(stmt, client, options, cacheContext) {
|
|
40109
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
40110
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
40111
|
+
const targets = resolveExistingValidationTargets(stmt, fieldInfos);
|
|
40112
|
+
const checkGroups = stmt.checkGroups ?? [];
|
|
40113
|
+
const checkRefs2 = collectCheckFieldRefs(checkGroups);
|
|
40114
|
+
for (const ref of checkRefs2) {
|
|
40115
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
40116
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${stmt.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
40117
|
+
}
|
|
40118
|
+
}
|
|
40119
|
+
const evaluationTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
40120
|
+
evaluationTypes.set("$id", "RECORD_NUMBER");
|
|
40121
|
+
assertCheckComparisonTypes(stmt, evaluationTypes);
|
|
40122
|
+
const whereFields = collectValidateWhereFields(stmt.where);
|
|
40123
|
+
const requiredFields = [.../* @__PURE__ */ new Set([
|
|
40124
|
+
"$id",
|
|
40125
|
+
...targets.map((field) => field.code),
|
|
40126
|
+
...whereFields,
|
|
40127
|
+
...checkRefs2.map((ref) => ref.field)
|
|
40128
|
+
])];
|
|
40129
|
+
for (const field of whereFields) {
|
|
40130
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
40131
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${stmt.appId}.`);
|
|
40132
|
+
}
|
|
40133
|
+
}
|
|
40134
|
+
const numberPrecision = targets.some((field) => field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
|
|
40135
|
+
const semantics = (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0);
|
|
40136
|
+
const capability = classifyWhereCapability(stmt.where, semantics);
|
|
40137
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
40138
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
40139
|
+
}
|
|
40140
|
+
const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
40141
|
+
const fieldOptions = new Map(fieldInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
40142
|
+
const prefilter = stmt.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? stmt.where : extractSafePushdownLeaves(stmt.where, {
|
|
40143
|
+
allowUnqualifiedFields: true,
|
|
40144
|
+
fieldTypes,
|
|
40145
|
+
fieldOptions,
|
|
40146
|
+
allowKlike: false
|
|
40147
|
+
});
|
|
40148
|
+
const query = prefilter === null ? "" : whereToKintone(prefilter);
|
|
40149
|
+
const records = await fetchAll(client.getRecords, stmt.appId, query, requiredFields, {
|
|
40150
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
40151
|
+
parallel: options.fetchParallel ?? 1,
|
|
40152
|
+
onLimit: "error"
|
|
40153
|
+
});
|
|
40154
|
+
const validationRows = records.map((record2) => ({
|
|
40155
|
+
id: String(record2["$id"]?.value ?? ""),
|
|
40156
|
+
record: record2,
|
|
40157
|
+
flat: flatten(record2, null)
|
|
40158
|
+
})).filter((row) => stmt.where === null || evalWhere(stmt.where, row.flat, (field) => evaluationTypes.get(field.field)));
|
|
40159
|
+
const rows = [];
|
|
40160
|
+
for (const row of validationRows) {
|
|
40161
|
+
for (const field of targets) {
|
|
40162
|
+
const raw = row.record[field.code]?.value;
|
|
40163
|
+
const validation = validateAndNormalizeDmlValue(raw, field, numberPrecision);
|
|
40164
|
+
if (validation.ok) continue;
|
|
40165
|
+
rows.push({
|
|
40166
|
+
"$id": row.id,
|
|
40167
|
+
"$err_field": field.code,
|
|
40168
|
+
"$err_code": validation.code,
|
|
40169
|
+
"$err_message": validation.message,
|
|
40170
|
+
"$err_value": renderExistingValidationValue(raw, field.fieldType)
|
|
40171
|
+
});
|
|
40172
|
+
}
|
|
40173
|
+
for (const check2 of evaluateCustomChecks(checkGroups, row.flat, (field) => evaluationTypes.get(field.field))) {
|
|
40174
|
+
rows.push({
|
|
40175
|
+
"$id": row.id,
|
|
40176
|
+
"$err_field": "",
|
|
40177
|
+
"$err_code": "ERR_CHECK",
|
|
40178
|
+
"$err_message": check2.message,
|
|
40179
|
+
"$err_value": ""
|
|
40180
|
+
});
|
|
40181
|
+
}
|
|
40182
|
+
}
|
|
40183
|
+
const result = {
|
|
40184
|
+
type: "SELECT",
|
|
40185
|
+
columns: [...EXISTING_VALIDATION_COLUMNS],
|
|
40186
|
+
rows,
|
|
40187
|
+
rowCount: rows.length
|
|
40188
|
+
};
|
|
40189
|
+
materializedMetaBySelectResult.set(result, existingValidationColumnMeta());
|
|
40190
|
+
return result;
|
|
40191
|
+
}
|
|
38834
40192
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
38835
40193
|
function appendValidationErrors(tempTables, name, columns, rows, maxRows, columnMeta) {
|
|
38836
40194
|
const current = tempTables.get(name);
|
|
@@ -38858,7 +40216,7 @@ var BatchTimeoutError = class extends Error {
|
|
|
38858
40216
|
}
|
|
38859
40217
|
};
|
|
38860
40218
|
async function executeBatch(sql, client, options = {}) {
|
|
38861
|
-
const statements = parseSqlBatch(sql);
|
|
40219
|
+
const statements = parseSqlBatch(sql, options.enableImport === true);
|
|
38862
40220
|
const analysis = analyzeBatch(statements);
|
|
38863
40221
|
const injectedVariables = validateDeclaredBatchVariables(statements, options.variables);
|
|
38864
40222
|
const batchOptions = { ...options, variables: injectedVariables };
|
|
@@ -38911,11 +40269,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
38911
40269
|
const userConfirm = batchOptions.confirm;
|
|
38912
40270
|
const stmtOptions = userConfirm ? {
|
|
38913
40271
|
...batchOptions,
|
|
38914
|
-
confirm: (count, operation) => userConfirm(count, operation, {
|
|
40272
|
+
confirm: (count, operation, detailContext) => userConfirm(count, operation, {
|
|
38915
40273
|
statementIndex: i,
|
|
38916
40274
|
statementCount: statements.length,
|
|
38917
40275
|
statementType: info.statementType,
|
|
38918
|
-
targetAppId: info.targetAppId
|
|
40276
|
+
targetAppId: info.targetAppId,
|
|
40277
|
+
...detailContext?.importDetail ? { importDetail: detailContext.importDetail } : {}
|
|
38919
40278
|
})
|
|
38920
40279
|
} : batchOptions;
|
|
38921
40280
|
const searchAbortCollector = { aborted: false };
|
|
@@ -38964,9 +40323,14 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
38964
40323
|
}
|
|
38965
40324
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
38966
40325
|
if (stmt.type === "SET_VARIABLE") {
|
|
38967
|
-
const resolvedStmt2 =
|
|
40326
|
+
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
38968
40327
|
validateKlikeStatement(resolvedStmt2);
|
|
38969
|
-
if (resolvedStmt2.expr.type === "
|
|
40328
|
+
if (resolvedStmt2.expr.type === "ARRAY") {
|
|
40329
|
+
variables.set(stmt.name, {
|
|
40330
|
+
type: "array",
|
|
40331
|
+
elements: resolvedStmt2.expr.elements.map((element) => ({ type: "string", value: element.value }))
|
|
40332
|
+
});
|
|
40333
|
+
} else if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
38970
40334
|
try {
|
|
38971
40335
|
const value = await evaluateScalarSubquery(
|
|
38972
40336
|
resolvedStmt2.expr.query,
|
|
@@ -39003,8 +40367,30 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
39003
40367
|
}
|
|
39004
40368
|
return {};
|
|
39005
40369
|
}
|
|
39006
|
-
const resolvedStmt =
|
|
40370
|
+
const resolvedStmt = resolveBatchVariableReferences(stmt, variables);
|
|
39007
40371
|
validateKlikeStatement(resolvedStmt);
|
|
40372
|
+
if (resolvedStmt.type === "VALIDATE") {
|
|
40373
|
+
const result = await executeExistingRecordValidationCore(
|
|
40374
|
+
resolvedStmt,
|
|
40375
|
+
client,
|
|
40376
|
+
{ ...options, onLimitReached: "error" },
|
|
40377
|
+
cacheContext
|
|
40378
|
+
);
|
|
40379
|
+
if (resolvedStmt.errorTable) {
|
|
40380
|
+
appendValidationErrors(
|
|
40381
|
+
tempTables,
|
|
40382
|
+
resolvedStmt.errorTable,
|
|
40383
|
+
result.columns,
|
|
40384
|
+
result.rows,
|
|
40385
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
40386
|
+
materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta()
|
|
40387
|
+
);
|
|
40388
|
+
}
|
|
40389
|
+
return { result };
|
|
40390
|
+
}
|
|
40391
|
+
if (resolvedStmt.type === "IMPORT") {
|
|
40392
|
+
return { result: await executeImport(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
40393
|
+
}
|
|
39008
40394
|
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
39009
40395
|
const result = await executeDmlValidation(
|
|
39010
40396
|
resolvedStmt,
|
|
@@ -39166,9 +40552,9 @@ function safeJsonStringify(v) {
|
|
|
39166
40552
|
return String(v);
|
|
39167
40553
|
}
|
|
39168
40554
|
}
|
|
39169
|
-
function parseSqlBatch(sql) {
|
|
40555
|
+
function parseSqlBatch(sql, enableImport = false) {
|
|
39170
40556
|
const tokens = new Lexer(sql).tokenize();
|
|
39171
|
-
return new Parser(tokens).parseStatements();
|
|
40557
|
+
return new Parser(tokens, { import: enableImport }).parseStatements();
|
|
39172
40558
|
}
|
|
39173
40559
|
function evaluateScalarExpr(expr) {
|
|
39174
40560
|
switch (expr.type) {
|
|
@@ -39189,9 +40575,9 @@ function evaluateScalarExpr(expr) {
|
|
|
39189
40575
|
}
|
|
39190
40576
|
}
|
|
39191
40577
|
}
|
|
39192
|
-
function
|
|
40578
|
+
function resolveBatchVariableReferences(node, variables) {
|
|
39193
40579
|
if (Array.isArray(node)) {
|
|
39194
|
-
return node.map((v) =>
|
|
40580
|
+
return node.map((v) => resolveBatchVariableReferences(v, variables));
|
|
39195
40581
|
}
|
|
39196
40582
|
if (node !== null && typeof node === "object") {
|
|
39197
40583
|
const obj = node;
|
|
@@ -39200,14 +40586,72 @@ function resolveVariableRefs(node, variables) {
|
|
|
39200
40586
|
if (value === void 0) {
|
|
39201
40587
|
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
39202
40588
|
}
|
|
40589
|
+
if (value.type === "array") {
|
|
40590
|
+
throw new Error(`ParseError: array variable @${obj["name"]} can only be used as IN @${obj["name"]}.`);
|
|
40591
|
+
}
|
|
39203
40592
|
return value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
|
|
39204
40593
|
}
|
|
39205
|
-
|
|
39206
|
-
|
|
40594
|
+
if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
|
|
40595
|
+
const value = variables.get(obj["name"]);
|
|
40596
|
+
if (value === void 0) throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
40597
|
+
if (value.type === "array") throw new Error(`ParseError: array variable @${obj["name"]} cannot be used as a SELECT column.`);
|
|
40598
|
+
return value.type === "number" ? { type: "ARITH_COL", expr: { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) }, alias: obj["alias"] } : { type: "LITERAL_COL", value: value.value, alias: obj["alias"] };
|
|
40599
|
+
}
|
|
40600
|
+
if (obj["type"] === "VARIABLE_IN_LIST") return obj;
|
|
40601
|
+
const resolved = Object.fromEntries(
|
|
40602
|
+
Object.entries(obj).map(([key, value]) => [key, resolveBatchVariableReferences(value, variables)])
|
|
39207
40603
|
);
|
|
40604
|
+
if (resolved["type"] === "BINARY") {
|
|
40605
|
+
const right = resolved["right"];
|
|
40606
|
+
if (right?.["type"] === "VARIABLE_IN_LIST" && typeof right["name"] === "string") {
|
|
40607
|
+
const value = variables.get(right["name"]);
|
|
40608
|
+
if (value === void 0) throw new Error(`ParseError: variable @${right["name"]} is not defined in this batch.`);
|
|
40609
|
+
if (value.type !== "array") {
|
|
40610
|
+
throw new Error(`ParseError: scalar variable @${right["name"]} cannot be used as IN @${right["name"]}; use IN (@${right["name"]}) instead.`);
|
|
40611
|
+
}
|
|
40612
|
+
if (value.elements.length === 0) {
|
|
40613
|
+
return { type: "BOOLEAN", value: resolved["op"] === "NOT_IN" };
|
|
40614
|
+
}
|
|
40615
|
+
resolved["right"] = {
|
|
40616
|
+
type: "IN_LIST",
|
|
40617
|
+
values: value.elements.map((element) => ({ type: "STRING", value: element.value }))
|
|
40618
|
+
};
|
|
40619
|
+
}
|
|
40620
|
+
}
|
|
40621
|
+
const simplified = simplifyBooleanWhere(resolved);
|
|
40622
|
+
if (simplified["type"] === "SELECT" && isBooleanNode(simplified["where"], true)) {
|
|
40623
|
+
simplified["where"] = null;
|
|
40624
|
+
}
|
|
40625
|
+
if ((simplified["type"] === "UPDATE" || simplified["type"] === "DELETE" || simplified["type"] === "REORDER") && isBooleanNode(simplified["where"], true)) {
|
|
40626
|
+
throw new Error("ArgumentError: empty-array simplification makes the target WHERE always true; use an explicit safe target condition.");
|
|
40627
|
+
}
|
|
40628
|
+
return simplified;
|
|
39208
40629
|
}
|
|
39209
40630
|
return node;
|
|
39210
40631
|
}
|
|
40632
|
+
function isBooleanNode(value, expected) {
|
|
40633
|
+
return value !== null && typeof value === "object" && value.type === "BOOLEAN" && (expected === void 0 || value.value === expected);
|
|
40634
|
+
}
|
|
40635
|
+
function simplifyBooleanWhere(obj) {
|
|
40636
|
+
if (obj["type"] === "NOT" && isBooleanNode(obj["expr"])) {
|
|
40637
|
+
return { type: "BOOLEAN", value: !obj["expr"].value };
|
|
40638
|
+
}
|
|
40639
|
+
if (obj["type"] === "GROUP" && isBooleanNode(obj["expr"])) return obj["expr"];
|
|
40640
|
+
if (obj["type"] === "LOGICAL") {
|
|
40641
|
+
const left = obj["left"];
|
|
40642
|
+
const right = obj["right"];
|
|
40643
|
+
if (obj["op"] === "AND") {
|
|
40644
|
+
if (isBooleanNode(left, false) || isBooleanNode(right, false)) return { type: "BOOLEAN", value: false };
|
|
40645
|
+
if (isBooleanNode(left, true)) return right;
|
|
40646
|
+
if (isBooleanNode(right, true)) return left;
|
|
40647
|
+
} else if (obj["op"] === "OR") {
|
|
40648
|
+
if (isBooleanNode(left, true) || isBooleanNode(right, true)) return { type: "BOOLEAN", value: true };
|
|
40649
|
+
if (isBooleanNode(left, false)) return right;
|
|
40650
|
+
if (isBooleanNode(right, false)) return left;
|
|
40651
|
+
}
|
|
40652
|
+
}
|
|
40653
|
+
return obj;
|
|
40654
|
+
}
|
|
39211
40655
|
function findVariableRef(node) {
|
|
39212
40656
|
if (Array.isArray(node)) {
|
|
39213
40657
|
for (const value of node) {
|
|
@@ -39218,7 +40662,7 @@ function findVariableRef(node) {
|
|
|
39218
40662
|
}
|
|
39219
40663
|
if (node !== null && typeof node === "object") {
|
|
39220
40664
|
const obj = node;
|
|
39221
|
-
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
|
|
40665
|
+
if ((obj["type"] === "VARIABLE" || obj["type"] === "VARIABLE_COL" || obj["type"] === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") return obj["name"];
|
|
39222
40666
|
for (const value of Object.values(obj)) {
|
|
39223
40667
|
const found = findVariableRef(value);
|
|
39224
40668
|
if (found !== null) return found;
|
|
@@ -39461,6 +40905,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
|
|
|
39461
40905
|
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
|
|
39462
40906
|
const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
|
|
39463
40907
|
const byCode = new Map(fields.map((field) => [field.code, field]));
|
|
40908
|
+
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) return;
|
|
39464
40909
|
const result = classifyWhereCapability(stmt.where, (field) => {
|
|
39465
40910
|
if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
|
|
39466
40911
|
const info = byCode.get(field.field);
|
|
@@ -39533,6 +40978,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
39533
40978
|
}
|
|
39534
40979
|
return result;
|
|
39535
40980
|
}
|
|
40981
|
+
function isConstantFalseWhere(where) {
|
|
40982
|
+
return where?.type === "BOOLEAN" && where.value === false;
|
|
40983
|
+
}
|
|
39536
40984
|
function isNoFromSelect(stmt) {
|
|
39537
40985
|
return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
|
|
39538
40986
|
}
|
|
@@ -39564,6 +41012,8 @@ function stringFuncHasFieldRef(expr) {
|
|
|
39564
41012
|
function validateNoFromColumns(stmt) {
|
|
39565
41013
|
for (const col of stmt.columns) {
|
|
39566
41014
|
switch (col.type) {
|
|
41015
|
+
case "VARIABLE_COL":
|
|
41016
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
39567
41017
|
case "LITERAL_COL":
|
|
39568
41018
|
break;
|
|
39569
41019
|
case "ARITH_COL":
|
|
@@ -39799,6 +41249,7 @@ function collectTypedInFieldRefs(expr, out) {
|
|
|
39799
41249
|
return;
|
|
39800
41250
|
case "NULL_CHECK":
|
|
39801
41251
|
case "EXISTS":
|
|
41252
|
+
case "BOOLEAN":
|
|
39802
41253
|
return;
|
|
39803
41254
|
}
|
|
39804
41255
|
}
|
|
@@ -40172,8 +41623,14 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
|
|
|
40172
41623
|
}
|
|
40173
41624
|
} else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
|
|
40174
41625
|
meta3 = syntheticColumnMeta("number");
|
|
40175
|
-
} else if (column.type === "LITERAL_COL"
|
|
41626
|
+
} else if (column.type === "LITERAL_COL") {
|
|
40176
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");
|
|
40177
41634
|
} else if (column.type === "STRFUNC_COL") {
|
|
40178
41635
|
meta3 = stringFunctionColumnMeta(column.expr);
|
|
40179
41636
|
} else if (column.type === "WINDOW_COL") {
|
|
@@ -40261,7 +41718,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
40261
41718
|
validateKlikePushdownPlan(pushdownPlan);
|
|
40262
41719
|
const mainPushDown = pushdownPlan.mainCondition;
|
|
40263
41720
|
const tableConditions = pushdownPlan.joinConditions;
|
|
40264
|
-
const
|
|
41721
|
+
const constantFalse = isConstantFalseWhere(stmt.where);
|
|
41722
|
+
const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
|
|
40265
41723
|
stmt,
|
|
40266
41724
|
stmt.from,
|
|
40267
41725
|
client,
|
|
@@ -40276,6 +41734,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
40276
41734
|
const parallelJoins = [];
|
|
40277
41735
|
const onOptJoins = [];
|
|
40278
41736
|
for (const join of stmt.joins) {
|
|
41737
|
+
if (constantFalse) {
|
|
41738
|
+
parallelJoins.push({ join, promise: Promise.resolve([]) });
|
|
41739
|
+
continue;
|
|
41740
|
+
}
|
|
40279
41741
|
const jCond = join.table.alias ? tableConditions.get(join.table.alias) ?? null : null;
|
|
40280
41742
|
if (jCond !== null) {
|
|
40281
41743
|
parallelJoins.push({
|
|
@@ -40982,9 +42444,10 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
40982
42444
|
return sortKinds;
|
|
40983
42445
|
}
|
|
40984
42446
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
40985
|
-
|
|
42447
|
+
if (typeof raw !== "string") return raw;
|
|
42448
|
+
const USER_TYPES4 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
40986
42449
|
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
40987
|
-
if (
|
|
42450
|
+
if (USER_TYPES4.has(dstFieldType ?? "")) {
|
|
40988
42451
|
if (raw === "") return [];
|
|
40989
42452
|
try {
|
|
40990
42453
|
const parsed = JSON.parse(raw);
|
|
@@ -41049,11 +42512,13 @@ function assertValidDmlRecords(records, targetFields, fieldInfos, numberPrecisio
|
|
|
41049
42512
|
records.forEach((record2, rowIndex) => {
|
|
41050
42513
|
for (const code of targetFields) {
|
|
41051
42514
|
const info = infoByCode.get(code);
|
|
41052
|
-
const
|
|
42515
|
+
const original = record2[code]?.value ?? "";
|
|
42516
|
+
const result = validateAndNormalizeDmlValue(original, info, numberPrecision);
|
|
41053
42517
|
if (!result.ok) {
|
|
41054
42518
|
throw new Error(`DmlValidationError: ${result.code} ${result.message} (row=${rowIndex + 1}, field=${code})`);
|
|
41055
42519
|
}
|
|
41056
|
-
|
|
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 };
|
|
41057
42522
|
}
|
|
41058
42523
|
});
|
|
41059
42524
|
}
|
|
@@ -41213,6 +42678,8 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41213
42678
|
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
41214
42679
|
let rows;
|
|
41215
42680
|
let sourceRows;
|
|
42681
|
+
let sourcePresence;
|
|
42682
|
+
let sourceRowErrors;
|
|
41216
42683
|
let evaluationTypes;
|
|
41217
42684
|
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
41218
42685
|
assertInsertCheckRefs(stmt, stmt.fields);
|
|
@@ -41222,7 +42689,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41222
42689
|
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
41223
42690
|
));
|
|
41224
42691
|
} else {
|
|
41225
|
-
const selectResult =
|
|
42692
|
+
const selectResult = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, tempTables, [...infoByCode.values()]);
|
|
41226
42693
|
const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
|
|
41227
42694
|
if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
|
|
41228
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`);
|
|
@@ -41232,7 +42699,9 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41232
42699
|
}
|
|
41233
42700
|
assertInsertCheckRefs(stmt, selectResult.columns);
|
|
41234
42701
|
sourceRows = selectResult.rows;
|
|
41235
|
-
|
|
42702
|
+
sourcePresence = selectResult.importPresence;
|
|
42703
|
+
sourceRowErrors = selectResult.importRowErrors;
|
|
42704
|
+
const meta3 = selectResult.columnMeta;
|
|
41236
42705
|
evaluationTypes = new Map(selectResult.columns.map((column) => {
|
|
41237
42706
|
const columnMeta = meta3?.get(column);
|
|
41238
42707
|
const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
|
|
@@ -41245,8 +42714,10 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41245
42714
|
rowNumber: index + 1,
|
|
41246
42715
|
operation,
|
|
41247
42716
|
mode: "create",
|
|
41248
|
-
payload: new Map(stmt.fields.
|
|
41249
|
-
|
|
42717
|
+
payload: new Map(stmt.fields.flatMap(
|
|
42718
|
+
(field, i) => sourcePresence && !sourcePresence[index]?.has(field) ? [] : [[field, values[i]]]
|
|
42719
|
+
)),
|
|
42720
|
+
preErrors: [...sourceRowErrors?.[index] ?? []],
|
|
41250
42721
|
record: {},
|
|
41251
42722
|
evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
|
|
41252
42723
|
stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
|
|
@@ -41259,13 +42730,17 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41259
42730
|
}
|
|
41260
42731
|
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
41261
42732
|
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
41262
|
-
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
41263
42733
|
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
41264
42734
|
const keyCounts = /* @__PURE__ */ new Map();
|
|
41265
42735
|
for (const parts of rowKeys) {
|
|
41266
42736
|
const key = upsertNormalizedKey(parts, numeric);
|
|
41267
42737
|
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
41268
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);
|
|
41269
42744
|
candidates.forEach((candidate, index) => {
|
|
41270
42745
|
const parts = rowKeys[index];
|
|
41271
42746
|
const targetId = lookupUpsertTarget(targets, parts);
|
|
@@ -41274,7 +42749,7 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
41274
42749
|
stmt.keyFields.forEach((key, keyIndex) => {
|
|
41275
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` });
|
|
41276
42751
|
});
|
|
41277
|
-
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
42752
|
+
if (!isImport && (keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
41278
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" });
|
|
41279
42754
|
}
|
|
41280
42755
|
});
|
|
@@ -41634,12 +43109,508 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
41634
43109
|
insertedCount: createdIds.flat().length
|
|
41635
43110
|
};
|
|
41636
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
|
+
}
|
|
41637
43607
|
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
41638
43608
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
41639
43609
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
41640
43610
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
41641
|
-
const
|
|
41642
|
-
const { rows, columns } =
|
|
43611
|
+
const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
|
|
43612
|
+
const { rows, columns } = sourceTable;
|
|
43613
|
+
assertNoImportRowErrors(sourceTable);
|
|
41643
43614
|
if (columns.length !== stmt.fields.length) {
|
|
41644
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" : "";
|
|
41645
43616
|
throw new Error(
|
|
@@ -41651,15 +43622,16 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
41651
43622
|
if (!ok) throw new OperationCancelledError("INSERT", rows.length);
|
|
41652
43623
|
}
|
|
41653
43624
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
41654
|
-
const allRecords = rows.map((row) => {
|
|
43625
|
+
const allRecords = rows.map((row, rowIndex) => {
|
|
41655
43626
|
const record2 = {};
|
|
41656
43627
|
stmt.fields.forEach((field, i) => {
|
|
43628
|
+
if (sourceTable.importPresence && !sourceTable.importPresence[rowIndex]?.has(field)) return;
|
|
41657
43629
|
const raw = row[columns[i]] ?? "";
|
|
41658
43630
|
record2[field] = { value: convertProcessRowValue(raw, fieldTypes.get(field)) };
|
|
41659
43631
|
});
|
|
41660
43632
|
return record2;
|
|
41661
43633
|
});
|
|
41662
|
-
assertValidDmlRecords(
|
|
43634
|
+
allRecords.forEach((record2) => assertValidDmlRecords([record2], stmt.fields.filter((field) => field in record2), fieldInfos, numberPrecision));
|
|
41663
43635
|
const createdIds = [];
|
|
41664
43636
|
for (let i = 0; i < allRecords.length; i += 100) {
|
|
41665
43637
|
const batch = allRecords.slice(i, i + 100);
|
|
@@ -41673,6 +43645,26 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
41673
43645
|
};
|
|
41674
43646
|
}
|
|
41675
43647
|
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
43648
|
+
if (stmt.checkGroups?.length && isConstantFalseWhere(stmt.where)) {
|
|
43649
|
+
const fieldInfos2 = await loadWritableTopLevelDmlFields(
|
|
43650
|
+
stmt.appId,
|
|
43651
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
43652
|
+
client,
|
|
43653
|
+
cacheContext
|
|
43654
|
+
);
|
|
43655
|
+
await loadNumberPrecisionForTargets(
|
|
43656
|
+
stmt.appId,
|
|
43657
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
43658
|
+
fieldInfos2,
|
|
43659
|
+
client,
|
|
43660
|
+
cacheContext
|
|
43661
|
+
);
|
|
43662
|
+
const fieldTypes2 = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
43663
|
+
assertUpdateCheckRefs(stmt, fieldTypes2);
|
|
43664
|
+
assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes2, stmt.appId));
|
|
43665
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
43666
|
+
return { type: "UPDATE", updatedCount: 0 };
|
|
43667
|
+
}
|
|
41676
43668
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, tempTables);
|
|
41677
43669
|
if (stmt.subtableCode) {
|
|
41678
43670
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
@@ -41693,6 +43685,7 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
41693
43685
|
cacheContext
|
|
41694
43686
|
);
|
|
41695
43687
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
43688
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "UPDATE", updatedCount: 0 };
|
|
41696
43689
|
if (stmt.from != null) {
|
|
41697
43690
|
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
41698
43691
|
}
|
|
@@ -41774,6 +43767,7 @@ function collectUpdateFromTargetFields(stmt) {
|
|
|
41774
43767
|
}
|
|
41775
43768
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
41776
43769
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
43770
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "DELETE", deletedCount: 0 };
|
|
41777
43771
|
if (stmt.subtableCode) {
|
|
41778
43772
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
41779
43773
|
}
|
|
@@ -42030,9 +44024,9 @@ function expandRowsForSubtableDml(parents, subtableCode) {
|
|
|
42030
44024
|
for (const parent of parents) {
|
|
42031
44025
|
const parentId = String(parent["$id"]?.value ?? "");
|
|
42032
44026
|
const parentRevision = getRevision(parent);
|
|
42033
|
-
const
|
|
42034
|
-
for (let i = 0; i <
|
|
42035
|
-
const row =
|
|
44027
|
+
const tableRows2 = getMutableTableRows(parent, subtableCode);
|
|
44028
|
+
for (let i = 0; i < tableRows2.length; i++) {
|
|
44029
|
+
const row = tableRows2[i];
|
|
42036
44030
|
const flat = {
|
|
42037
44031
|
_pid: parentId,
|
|
42038
44032
|
_rid: row.id ?? "",
|
|
@@ -42156,6 +44150,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
|
|
|
42156
44150
|
cacheContext
|
|
42157
44151
|
);
|
|
42158
44152
|
const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
44153
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "REORDER", reorderedParentCount: 0 };
|
|
42159
44154
|
const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
|
|
42160
44155
|
field.code,
|
|
42161
44156
|
field.semantics ?? resolveFieldSemantics(field)
|
|
@@ -42237,8 +44232,9 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
42237
44232
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
42238
44233
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
42239
44234
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
42240
|
-
const
|
|
42241
|
-
const { rows, columns } =
|
|
44235
|
+
const sourceTable = await dmlSourceMaterializer.materialize(stmt, client, options, cacheContext, cteCache, fieldInfos);
|
|
44236
|
+
const { rows, columns } = sourceTable;
|
|
44237
|
+
assertNoImportRowErrors(sourceTable);
|
|
42242
44238
|
if (columns.length !== stmt.fields.length) {
|
|
42243
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" : "";
|
|
42244
44240
|
throw new Error(
|
|
@@ -42252,18 +44248,29 @@ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
42252
44248
|
}
|
|
42253
44249
|
const toInsert = [];
|
|
42254
44250
|
const toUpdate = [];
|
|
42255
|
-
const
|
|
44251
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
44252
|
+
const records = rows.map((row, rowIndex) => {
|
|
42256
44253
|
const record2 = {};
|
|
42257
44254
|
stmt.fields.forEach((field, i) => {
|
|
42258
|
-
|
|
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)) };
|
|
42259
44258
|
});
|
|
42260
44259
|
return record2;
|
|
42261
44260
|
});
|
|
42262
|
-
assertValidDmlRecords(
|
|
42263
|
-
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
44261
|
+
records.forEach((record2) => assertValidDmlRecords([record2], stmt.fields.filter((field) => field in record2), fieldInfos, numberPrecision));
|
|
42264
44262
|
const rowKeyValues = records.map(
|
|
42265
44263
|
(record2) => stmt.keyFields.map((key) => String(record2[key]?.value ?? ""))
|
|
42266
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
|
+
}
|
|
42267
44274
|
const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
|
|
42268
44275
|
records.forEach((record2, rowIdx) => {
|
|
42269
44276
|
const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
|
|
@@ -42312,10 +44319,10 @@ async function executeDescribe(stmt, client, cacheContext) {
|
|
|
42312
44319
|
}));
|
|
42313
44320
|
return { type: "SELECT", rows, columns, rowCount: rows.length };
|
|
42314
44321
|
}
|
|
42315
|
-
function parseSql(sql) {
|
|
44322
|
+
function parseSql(sql, enableImport = false) {
|
|
42316
44323
|
try {
|
|
42317
44324
|
const tokens = new Lexer(sql).tokenize();
|
|
42318
|
-
const stmt = new Parser(tokens).parse();
|
|
44325
|
+
const stmt = new Parser(tokens, { import: enableImport }).parse();
|
|
42319
44326
|
validateKlikeStatement(stmt);
|
|
42320
44327
|
return stmt;
|
|
42321
44328
|
} catch (e) {
|
|
@@ -42382,6 +44389,8 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCa
|
|
|
42382
44389
|
}));
|
|
42383
44390
|
break;
|
|
42384
44391
|
}
|
|
44392
|
+
case "BOOLEAN":
|
|
44393
|
+
break;
|
|
42385
44394
|
}
|
|
42386
44395
|
}
|
|
42387
44396
|
async function resolveSetSubqueries(assignments, client, options, cacheContext) {
|
|
@@ -42419,9 +44428,11 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
42419
44428
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
42420
44429
|
return cache;
|
|
42421
44430
|
}
|
|
44431
|
+
var validateExplainInfo = /* @__PURE__ */ new WeakMap();
|
|
42422
44432
|
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords2 = 1e4) {
|
|
42423
44433
|
const fieldApps = /* @__PURE__ */ new Set();
|
|
42424
44434
|
const processStatusApps = /* @__PURE__ */ new Set();
|
|
44435
|
+
const numberPrecisionApps = /* @__PURE__ */ new Set();
|
|
42425
44436
|
const tracedClient = {
|
|
42426
44437
|
...client,
|
|
42427
44438
|
getFields: async (appId) => {
|
|
@@ -42431,6 +44442,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42431
44442
|
getProcessStatuses: async (appId) => {
|
|
42432
44443
|
processStatusApps.add(appId);
|
|
42433
44444
|
return client.getProcessStatuses(appId);
|
|
44445
|
+
},
|
|
44446
|
+
getNumberPrecision: async (appId) => {
|
|
44447
|
+
numberPrecisionApps.add(appId);
|
|
44448
|
+
return client.getNumberPrecision(appId);
|
|
42434
44449
|
}
|
|
42435
44450
|
};
|
|
42436
44451
|
const capabilities = /* @__PURE__ */ new Map();
|
|
@@ -42479,6 +44494,51 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42479
44494
|
}));
|
|
42480
44495
|
}
|
|
42481
44496
|
}
|
|
44497
|
+
} else if (typed["type"] === "VALIDATE") {
|
|
44498
|
+
const validate = node;
|
|
44499
|
+
fieldApps.add(validate.appId);
|
|
44500
|
+
const fields = await getFieldsCached(validate.appId, tracedClient, cacheContext);
|
|
44501
|
+
const infoByCode = new Map(fields.map((field) => [field.code, field]));
|
|
44502
|
+
const targets = resolveExistingValidationTargets(validate, fields);
|
|
44503
|
+
const checks = collectCheckFieldRefs(validate.checkGroups ?? []);
|
|
44504
|
+
const whereFields = collectValidateWhereFields(validate.where);
|
|
44505
|
+
for (const ref of checks) {
|
|
44506
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
44507
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${validate.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
44508
|
+
}
|
|
44509
|
+
}
|
|
44510
|
+
for (const field of whereFields) {
|
|
44511
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
44512
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${validate.appId}.`);
|
|
44513
|
+
}
|
|
44514
|
+
}
|
|
44515
|
+
const types = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
44516
|
+
types.set("$id", "RECORD_NUMBER");
|
|
44517
|
+
assertCheckComparisonTypes(validate, types);
|
|
44518
|
+
const capability = classifyWhereCapability(validate.where, (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0));
|
|
44519
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
44520
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
44521
|
+
}
|
|
44522
|
+
const fieldTypes = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
44523
|
+
const fieldOptions = new Map(fields.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
44524
|
+
const prefilter = validate.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? validate.where : extractSafePushdownLeaves(validate.where, {
|
|
44525
|
+
allowUnqualifiedFields: true,
|
|
44526
|
+
fieldTypes,
|
|
44527
|
+
fieldOptions,
|
|
44528
|
+
allowKlike: false
|
|
44529
|
+
});
|
|
44530
|
+
const needsPrecision = targets.some((field) => field.fieldType === "NUMBER");
|
|
44531
|
+
if (needsPrecision) {
|
|
44532
|
+
numberPrecisionApps.add(validate.appId);
|
|
44533
|
+
await getNumberPrecisionCached(validate.appId, tracedClient, cacheContext);
|
|
44534
|
+
}
|
|
44535
|
+
validateExplainInfo.set(validate, {
|
|
44536
|
+
targetFields: targets.map((field) => field.code),
|
|
44537
|
+
fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((field) => field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
|
|
44538
|
+
capability,
|
|
44539
|
+
prefilter,
|
|
44540
|
+
numberPrecision: needsPrecision
|
|
44541
|
+
});
|
|
42482
44542
|
} else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
|
|
42483
44543
|
fieldApps.add(node.appId);
|
|
42484
44544
|
await assertDmlWhereCapability(
|
|
@@ -42509,23 +44569,24 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42509
44569
|
}));
|
|
42510
44570
|
}
|
|
42511
44571
|
}
|
|
42512
|
-
return { capabilities, orderPlans, fieldApps, processStatusApps };
|
|
44572
|
+
return { capabilities, orderPlans, fieldApps, processStatusApps, numberPrecisionApps };
|
|
42513
44573
|
}
|
|
42514
44574
|
function explainMetadataLines(analysis) {
|
|
42515
44575
|
return [
|
|
42516
44576
|
...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
|
|
42517
|
-
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
|
|
44577
|
+
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`),
|
|
44578
|
+
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
42518
44579
|
];
|
|
42519
44580
|
}
|
|
42520
|
-
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2) {
|
|
42521
|
-
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);
|
|
42522
44583
|
const analysis = analyzeBatch(statements);
|
|
42523
44584
|
validateDeclaredBatchVariables(statements, injectedVariables);
|
|
42524
44585
|
const variables = /* @__PURE__ */ new Map();
|
|
42525
44586
|
const plans = [];
|
|
42526
44587
|
for (let i = 0; i < statements.length; i++) {
|
|
42527
44588
|
const stmt = statements[i];
|
|
42528
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr:
|
|
44589
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
|
|
42529
44590
|
validateKlikeStatement(planStmt);
|
|
42530
44591
|
const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords2);
|
|
42531
44592
|
const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
|
|
@@ -42541,7 +44602,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
42541
44602
|
plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
|
|
42542
44603
|
});
|
|
42543
44604
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
42544
|
-
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
44605
|
+
variables.set(stmt.name, stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? { type: "array", elements: stmt.expr.elements.map((element) => ({ type: "string", value: element.value })) } : { type: "string", value: `@${stmt.name}` });
|
|
42545
44606
|
}
|
|
42546
44607
|
}
|
|
42547
44608
|
return { statementCount: statements.length, statements: plans };
|
|
@@ -42676,8 +44737,103 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
|
|
|
42676
44737
|
if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
|
|
42677
44738
|
if (query.type === "DELETE") return buildDeletePlan(query, label);
|
|
42678
44739
|
if (query.type === "REORDER") return buildReorderPlan(query, label);
|
|
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
|
+
}
|
|
42679
44814
|
return buildSelectPlan(query, label, capabilities, orderPlans);
|
|
42680
44815
|
}
|
|
44816
|
+
function buildValidatePlan(stmt, label) {
|
|
44817
|
+
const info = validateExplainInfo.get(stmt);
|
|
44818
|
+
const lines = [];
|
|
44819
|
+
if (label) lines.push(label);
|
|
44820
|
+
lines.push(`VALIDATE APP${stmt.appId}`);
|
|
44821
|
+
lines.push(" operation: read-only existing-record constraint audit (writesKintone=false)");
|
|
44822
|
+
lines.push(" fetch API: GET records via offset + $id keyset paging (Cursor API unused)");
|
|
44823
|
+
lines.push(" complete input: required (onLimit=truncate disabled)");
|
|
44824
|
+
if (!info) {
|
|
44825
|
+
lines.push(" metadata: form definition required; number precision required for NUMBER targets");
|
|
44826
|
+
return lines;
|
|
44827
|
+
}
|
|
44828
|
+
lines.push(` WHERE capability: ${info.capability.capability}`);
|
|
44829
|
+
lines.push(` kintone query: ${info.prefilter === null ? "(\u5168\u4EF6\u53D6\u5F97)" : whereToKintone(info.prefilter)}`);
|
|
44830
|
+
lines.push(` audit fields: ${info.targetFields.length === 0 ? "(\u306A\u3057)" : info.targetFields.join(", ")}`);
|
|
44831
|
+
lines.push(` fetch fields: ${info.fetchFields.join(", ")}`);
|
|
44832
|
+
lines.push(` number precision: ${info.numberPrecision ? "required" : "not required"}`);
|
|
44833
|
+
lines.push(" local checks: original WHERE re-evaluation + built-in constraints + CHECK groups");
|
|
44834
|
+
lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
|
|
44835
|
+
return lines;
|
|
44836
|
+
}
|
|
42681
44837
|
function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
42682
44838
|
const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
42683
44839
|
const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
@@ -42689,6 +44845,12 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
|
42689
44845
|
const lines = [];
|
|
42690
44846
|
if (label) lines.push(label);
|
|
42691
44847
|
lines.push(` mode: ${mode}`);
|
|
44848
|
+
if (isConstantFalseWhere(stmt.where)) {
|
|
44849
|
+
lines.push(" predicate: constant false");
|
|
44850
|
+
lines.push(" records API access: none");
|
|
44851
|
+
lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
|
|
44852
|
+
return lines;
|
|
44853
|
+
}
|
|
42692
44854
|
if (orderPlan) {
|
|
42693
44855
|
lines.push(` order plan: ${orderPlan.kind}`);
|
|
42694
44856
|
if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
|
|
@@ -42836,6 +44998,8 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
|
|
|
42836
44998
|
break;
|
|
42837
44999
|
case "NULL_CHECK":
|
|
42838
45000
|
break;
|
|
45001
|
+
case "BOOLEAN":
|
|
45002
|
+
break;
|
|
42839
45003
|
}
|
|
42840
45004
|
};
|
|
42841
45005
|
visitWhere(stmt.where);
|
|
@@ -42888,7 +45052,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
|
|
|
42888
45052
|
} else {
|
|
42889
45053
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
42890
45054
|
}
|
|
42891
|
-
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
45055
|
+
lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
42892
45056
|
const setTypes = [];
|
|
42893
45057
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
42894
45058
|
if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
|
|
@@ -42919,7 +45083,7 @@ function buildDeletePlan(stmt, label) {
|
|
|
42919
45083
|
lines.push(` [DELETE]`);
|
|
42920
45084
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
42921
45085
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
42922
|
-
lines.push(` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
|
|
45086
|
+
lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
|
|
42923
45087
|
return lines;
|
|
42924
45088
|
}
|
|
42925
45089
|
function buildUpsertPlan(stmt, label) {
|
|
@@ -42959,7 +45123,7 @@ function buildReorderPlan(stmt, label) {
|
|
|
42959
45123
|
` table: ${target}`,
|
|
42960
45124
|
` scope: ${scope}`,
|
|
42961
45125
|
` by: ${byStr}`,
|
|
42962
|
-
` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
|
|
45126
|
+
isConstantFalseWhere(stmt.where) ? ` api: metadata validation only (records API access: none)` : ` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
|
|
42963
45127
|
];
|
|
42964
45128
|
if (!stmt.all && stmt.where) {
|
|
42965
45129
|
lines.splice(5, 0, ` where: ${safeWhereToKintone(stmt.where)}`);
|
|
@@ -42971,6 +45135,7 @@ function formatOrderByItem(item) {
|
|
|
42971
45135
|
return `${key} ${item.direction}`;
|
|
42972
45136
|
}
|
|
42973
45137
|
function safeWhereToKintone(where) {
|
|
45138
|
+
if (where.type === "BOOLEAN") return where.value ? "TRUE" : "FALSE (constant)";
|
|
42974
45139
|
try {
|
|
42975
45140
|
return whereToKintone(where);
|
|
42976
45141
|
} catch {
|
|
@@ -43099,15 +45264,15 @@ var OperationCancelledError = class extends Error {
|
|
|
43099
45264
|
};
|
|
43100
45265
|
|
|
43101
45266
|
// src/core/sql.ts
|
|
43102
|
-
function parseSqlStatement(sql) {
|
|
45267
|
+
function parseSqlStatement(sql, capabilities = {}) {
|
|
43103
45268
|
const tokens = new Lexer(sql).tokenize();
|
|
43104
|
-
const stmt = new Parser(tokens).parse();
|
|
45269
|
+
const stmt = new Parser(tokens, capabilities).parse();
|
|
43105
45270
|
validateKlikeStatement(stmt);
|
|
43106
45271
|
return stmt;
|
|
43107
45272
|
}
|
|
43108
|
-
function parseSqlStatements(sql) {
|
|
45273
|
+
function parseSqlStatements(sql, capabilities = {}) {
|
|
43109
45274
|
const tokens = new Lexer(sql).tokenize();
|
|
43110
|
-
const statements = new Parser(tokens).parseStatements();
|
|
45275
|
+
const statements = new Parser(tokens, capabilities).parseStatements();
|
|
43111
45276
|
statements.forEach(validateKlikeStatement);
|
|
43112
45277
|
return statements;
|
|
43113
45278
|
}
|
|
@@ -43190,7 +45355,8 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
43190
45355
|
validRows: s.result.validRows,
|
|
43191
45356
|
invalidRows: s.result.invalidRows,
|
|
43192
45357
|
errorCount: s.result.errorCount,
|
|
43193
|
-
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
45358
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {},
|
|
45359
|
+
...s.result.importDetail ? { importDetail: s.result.importDetail } : {}
|
|
43194
45360
|
});
|
|
43195
45361
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
43196
45362
|
Object.assign(entry, toMutationSummary(s.result));
|
|
@@ -43266,6 +45432,17 @@ function restoreSqlContextError(err, sourceSql, context) {
|
|
|
43266
45432
|
return err;
|
|
43267
45433
|
}
|
|
43268
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
|
+
|
|
43269
45446
|
// src/node/config.ts
|
|
43270
45447
|
var import_fs = require("fs");
|
|
43271
45448
|
var LOGICAL_APP_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
@@ -43570,7 +45747,7 @@ function clampInt(v, min, max) {
|
|
|
43570
45747
|
function flattenFormFieldProperties(properties) {
|
|
43571
45748
|
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
43572
45749
|
}
|
|
43573
|
-
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
45750
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false, subtableCode) {
|
|
43574
45751
|
const out = [];
|
|
43575
45752
|
for (const field of Object.values(properties)) {
|
|
43576
45753
|
const optionOrder = toOptionOrderMap(field.options);
|
|
@@ -43588,11 +45765,12 @@ function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
|
43588
45765
|
maxLength: normalizeConstraintValue(field.maxLength),
|
|
43589
45766
|
defaultValue: field.defaultValue,
|
|
43590
45767
|
inSubtable,
|
|
45768
|
+
...subtableCode ? { subtableCode } : {},
|
|
43591
45769
|
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
43592
45770
|
};
|
|
43593
45771
|
info.semantics = resolveFieldSemantics(info);
|
|
43594
45772
|
out.push(info);
|
|
43595
|
-
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));
|
|
43596
45774
|
}
|
|
43597
45775
|
return out;
|
|
43598
45776
|
}
|
|
@@ -44872,18 +47050,55 @@ function requireSingleStatement(validation, toolName) {
|
|
|
44872
47050
|
}
|
|
44873
47051
|
var DEFAULT_MAX_RECORDS = 500;
|
|
44874
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
|
+
}
|
|
44875
47090
|
function noOpClient() {
|
|
44876
|
-
const
|
|
47091
|
+
const fail3 = async () => {
|
|
44877
47092
|
throw new Error("No-op client should not be called.");
|
|
44878
47093
|
};
|
|
44879
47094
|
return {
|
|
44880
|
-
getRecords:
|
|
44881
|
-
openCursor:
|
|
44882
|
-
postRecords:
|
|
44883
|
-
putRecords:
|
|
44884
|
-
deleteRecords:
|
|
44885
|
-
getApps:
|
|
44886
|
-
getFields:
|
|
47095
|
+
getRecords: fail3,
|
|
47096
|
+
openCursor: fail3,
|
|
47097
|
+
postRecords: fail3,
|
|
47098
|
+
putRecords: fail3,
|
|
47099
|
+
deleteRecords: fail3,
|
|
47100
|
+
getApps: fail3,
|
|
47101
|
+
getFields: fail3,
|
|
44887
47102
|
async getProcessStatuses() {
|
|
44888
47103
|
return { enable: false, states: [] };
|
|
44889
47104
|
},
|
|
@@ -44963,7 +47178,8 @@ function toDmlValidationPayload(result) {
|
|
|
44963
47178
|
errorCount: result.errorCount,
|
|
44964
47179
|
columns: result.columns,
|
|
44965
47180
|
errors: result.errors,
|
|
44966
|
-
...result.errTable ? { errTable: result.errTable } : {}
|
|
47181
|
+
...result.errTable ? { errTable: result.errTable } : {},
|
|
47182
|
+
...result.importDetail ? { importDetail: result.importDetail } : {}
|
|
44967
47183
|
};
|
|
44968
47184
|
}
|
|
44969
47185
|
function toMutationPayload(result) {
|
|
@@ -45084,12 +47300,14 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45084
47300
|
const validationContexts = /* @__PURE__ */ new WeakMap();
|
|
45085
47301
|
async function validate(input) {
|
|
45086
47302
|
const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
|
|
47303
|
+
const importOptions = importCapability(input);
|
|
45087
47304
|
let analysis;
|
|
45088
47305
|
try {
|
|
45089
|
-
const statements = parseSqlStatements(normalized.normalizedSql);
|
|
47306
|
+
const statements = parseSqlStatements(normalized.normalizedSql, { import: importOptions.enableImport });
|
|
45090
47307
|
analysis = analyzeBatch(statements);
|
|
45091
47308
|
} catch (err) {
|
|
45092
|
-
|
|
47309
|
+
const restored = restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
|
|
47310
|
+
throw toMcpImportError(restored, importOptions.enableImport === true);
|
|
45093
47311
|
}
|
|
45094
47312
|
const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => toValidationBinding(mappedAppId, binding));
|
|
45095
47313
|
const statementValidations = analysis.statements.map((s2) => ({
|
|
@@ -45147,12 +47365,14 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45147
47365
|
}
|
|
45148
47366
|
async function explain(input) {
|
|
45149
47367
|
const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
|
|
47368
|
+
const importOptions = importCapability(input);
|
|
45150
47369
|
const appBindings = toExplainBindings(normalized.appBindingByMappedApp);
|
|
45151
47370
|
let statements;
|
|
45152
47371
|
try {
|
|
45153
|
-
statements = parseSqlStatements(normalized.normalizedSql);
|
|
47372
|
+
statements = parseSqlStatements(normalized.normalizedSql, { import: importOptions.enableImport });
|
|
45154
47373
|
} catch (err) {
|
|
45155
|
-
|
|
47374
|
+
const restored = restoreSqlContextError(err, normalized.sourceSql, normalized.sqlContext);
|
|
47375
|
+
throw toMcpImportError(restored, importOptions.enableImport === true);
|
|
45156
47376
|
}
|
|
45157
47377
|
const needsAppMetadata = normalized.appBindingByMappedApp.size > 0 && statements.some(explainNeedsAppMetadata);
|
|
45158
47378
|
const runtime = needsAppMetadata ? await createRuntime(serverOptions, {
|
|
@@ -45172,7 +47392,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45172
47392
|
void 0,
|
|
45173
47393
|
explainCacheContext,
|
|
45174
47394
|
runtime?.maxRecords ?? input.maxRecords,
|
|
45175
|
-
runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47395
|
+
runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47396
|
+
importOptions.enableImport
|
|
45176
47397
|
);
|
|
45177
47398
|
return {
|
|
45178
47399
|
ok: true,
|
|
@@ -45185,7 +47406,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45185
47406
|
const result = await executeSql(explainSql(explainSourceSql), explainClient, {
|
|
45186
47407
|
cacheContext: explainCacheContext,
|
|
45187
47408
|
maxRecords: runtime?.maxRecords ?? input.maxRecords,
|
|
45188
|
-
cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47409
|
+
cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47410
|
+
...importOptions
|
|
45189
47411
|
});
|
|
45190
47412
|
if (result.type !== "SELECT") {
|
|
45191
47413
|
throw new Error(`ArgumentError: EXPLAIN returned unexpected result type ${result.type}.`);
|
|
@@ -45197,6 +47419,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45197
47419
|
}
|
|
45198
47420
|
async function query(input, validated) {
|
|
45199
47421
|
const validation = validated ?? await validate(input);
|
|
47422
|
+
const importOptions = importCapability(input);
|
|
45200
47423
|
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
45201
47424
|
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
45202
47425
|
}
|
|
@@ -45210,7 +47433,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45210
47433
|
profile: input.profile,
|
|
45211
47434
|
maxRecords: input.maxRecords,
|
|
45212
47435
|
fetchParallel: input.fetchParallel,
|
|
45213
|
-
onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
|
|
47436
|
+
onLimit: validation.containsValidationOnly || validation.statements.some((s) => s.statementType === "VALIDATE") ? "error" : input.onLimit,
|
|
45214
47437
|
timeout: input.timeout,
|
|
45215
47438
|
tempTableMaxRows: input.tempTableMaxRows,
|
|
45216
47439
|
cursorMaxActive: input.cursorMaxActive
|
|
@@ -45229,20 +47452,22 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45229
47452
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
45230
47453
|
timeoutMs: runtime2.timeout,
|
|
45231
47454
|
cursorMaxActive: runtime2.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
45232
|
-
variables: input.variables
|
|
47455
|
+
variables: input.variables,
|
|
47456
|
+
...importOptions
|
|
45233
47457
|
});
|
|
45234
47458
|
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
45235
47459
|
}
|
|
45236
47460
|
if (!validation.isReadOnly) {
|
|
45237
47461
|
throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
|
|
45238
47462
|
}
|
|
45239
|
-
const stmt = parseSqlStatement(validation.normalizedSql);
|
|
47463
|
+
const stmt = parseSqlStatement(validation.normalizedSql, { import: importOptions.enableImport });
|
|
45240
47464
|
const noAppApiNeeded = isNoFromSelectStatement(stmt);
|
|
45241
47465
|
if (noAppApiNeeded) {
|
|
45242
47466
|
const result2 = await executeSql(validation.normalizedSql, noOpClient(), {
|
|
45243
47467
|
maxRecords: input.maxRecords ?? DEFAULT_MAX_RECORDS,
|
|
45244
47468
|
onLimitReached: input.onLimit ?? DEFAULT_ON_LIMIT,
|
|
45245
|
-
cacheContext: validation.cacheContext
|
|
47469
|
+
cacheContext: validation.cacheContext,
|
|
47470
|
+
...importOptions
|
|
45246
47471
|
});
|
|
45247
47472
|
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
45248
47473
|
if (result2.type === "VALIDATION") return toDmlValidationPayload(result2);
|
|
@@ -45257,7 +47482,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45257
47482
|
profile: input.profile,
|
|
45258
47483
|
maxRecords: input.maxRecords,
|
|
45259
47484
|
fetchParallel: input.fetchParallel,
|
|
45260
|
-
onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
|
|
47485
|
+
onLimit: validation.containsValidationOnly || validation.statements.some((s) => s.statementType === "VALIDATE") ? "error" : input.onLimit,
|
|
45261
47486
|
timeout: input.timeout,
|
|
45262
47487
|
cursorMaxActive: input.cursorMaxActive
|
|
45263
47488
|
});
|
|
@@ -45266,7 +47491,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45266
47491
|
fetchParallel: runtime.fetchParallel,
|
|
45267
47492
|
onLimitReached: runtime.onLimit,
|
|
45268
47493
|
cacheContext: runtime.cacheContext,
|
|
45269
|
-
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2
|
|
47494
|
+
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
47495
|
+
...importOptions
|
|
45270
47496
|
});
|
|
45271
47497
|
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
45272
47498
|
if (result.type === "VALIDATION") return toDmlValidationPayload(result);
|
|
@@ -45276,6 +47502,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45276
47502
|
return toSelectPayload(result);
|
|
45277
47503
|
}
|
|
45278
47504
|
async function mutateBatch(input, validation, dmlMaxRows) {
|
|
47505
|
+
const importOptions = importCapability(input);
|
|
45279
47506
|
if (!validation.containsDml) {
|
|
45280
47507
|
throw new Error("ArgumentError: batch contains no DML statements. Use ksql_query.");
|
|
45281
47508
|
}
|
|
@@ -45325,6 +47552,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45325
47552
|
timeoutMs: runtime.timeout,
|
|
45326
47553
|
cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
|
|
45327
47554
|
variables: input.variables,
|
|
47555
|
+
...importOptions,
|
|
45328
47556
|
confirm: async (count, operation) => {
|
|
45329
47557
|
if (count > dmlMaxRows) {
|
|
45330
47558
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
@@ -45353,6 +47581,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45353
47581
|
}
|
|
45354
47582
|
async function mutate(input, validated) {
|
|
45355
47583
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
47584
|
+
const importOptions = importCapability(input);
|
|
45356
47585
|
const validation = validated ?? await validate(input);
|
|
45357
47586
|
if (!validation.batch && input.variables && Object.keys(input.variables).length > 0) {
|
|
45358
47587
|
throw new Error("ArgumentError: variables require a batch containing DECLARE.");
|
|
@@ -45395,7 +47624,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45395
47624
|
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
45396
47625
|
}
|
|
45397
47626
|
return true;
|
|
45398
|
-
}
|
|
47627
|
+
},
|
|
47628
|
+
...importOptions
|
|
45399
47629
|
});
|
|
45400
47630
|
} catch (err) {
|
|
45401
47631
|
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
@@ -45559,21 +47789,33 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45559
47789
|
var profile = external_exports.string().min(1).describe("kintone connection profile name from ksql.config.json (default: the server's default profile).").optional();
|
|
45560
47790
|
var maxRecords = external_exports.number().int().positive().describe("Maximum records fetched per SELECT (default 500).").optional();
|
|
45561
47791
|
var fetchParallel = external_exports.number().int().min(1).max(10).describe("Number of parallel kintone record-fetch requests (1-10).").optional();
|
|
45562
|
-
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always
|
|
47792
|
+
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. Leading VALIDATE and DML VALIDATE ONLY always override 'truncate' to 'error'.").optional();
|
|
45563
47793
|
var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
|
|
45564
47794
|
var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
|
|
45565
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();
|
|
45566
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).");
|
|
45567
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();
|
|
45568
47808
|
var validateInputSchema = external_exports.object({
|
|
45569
47809
|
sql: external_exports.string().min(1).describe("kSQL text to validate. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
|
|
45570
|
-
profile
|
|
47810
|
+
profile,
|
|
47811
|
+
importSources
|
|
45571
47812
|
});
|
|
45572
47813
|
var explainInputSchema = external_exports.object({
|
|
45573
47814
|
sql: external_exports.string().min(1).describe("kSQL text to explain. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
|
|
45574
47815
|
profile,
|
|
45575
47816
|
maxRecords,
|
|
45576
|
-
cursorMaxActive
|
|
47817
|
+
cursorMaxActive,
|
|
47818
|
+
importSources
|
|
45577
47819
|
});
|
|
45578
47820
|
var queryInputSchema = external_exports.object({
|
|
45579
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;"),
|
|
@@ -45584,6 +47826,7 @@ var queryInputSchema = external_exports.object({
|
|
|
45584
47826
|
tempTableMaxRows,
|
|
45585
47827
|
timeout,
|
|
45586
47828
|
cursorMaxActive,
|
|
47829
|
+
importSources,
|
|
45587
47830
|
continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
|
|
45588
47831
|
maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional(),
|
|
45589
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()
|
|
@@ -45598,6 +47841,7 @@ var mutateInputSchema = external_exports.object({
|
|
|
45598
47841
|
tempTableMaxRows,
|
|
45599
47842
|
timeout,
|
|
45600
47843
|
cursorMaxActive,
|
|
47844
|
+
importSources,
|
|
45601
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(),
|
|
45602
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()
|
|
45603
47847
|
});
|
|
@@ -45686,9 +47930,14 @@ Options:
|
|
|
45686
47930
|
--config <path> Config file path (default: ./ksql.config.json or KSQL_CONFIG)
|
|
45687
47931
|
--profile <name> Default profile name
|
|
45688
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.
|
|
45689
47938
|
`);
|
|
45690
47939
|
}
|
|
45691
|
-
var SERVER_VERSION = true ? "3.
|
|
47940
|
+
var SERVER_VERSION = true ? "3.6.1" : "0.0.0-dev";
|
|
45692
47941
|
function createServer(args) {
|
|
45693
47942
|
const server = new McpServer({
|
|
45694
47943
|
name: "ksql-mcp",
|
|
@@ -45700,21 +47949,21 @@ function createServer(args) {
|
|
|
45700
47949
|
});
|
|
45701
47950
|
server.registerTool("ksql_validate", {
|
|
45702
47951
|
title: "Validate kSQL",
|
|
45703
|
-
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.",
|
|
45704
47953
|
inputSchema: validateInputShape
|
|
45705
47954
|
}, tools.validateTool);
|
|
45706
47955
|
server.registerTool("ksql_explain", {
|
|
45707
47956
|
title: "Explain kSQL",
|
|
45708
|
-
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.",
|
|
45709
47958
|
inputSchema: explainInputShape
|
|
45710
47959
|
}, tools.explainTool);
|
|
45711
47960
|
server.registerTool("ksql_query", {
|
|
45712
47961
|
title: "Run read-only kSQL",
|
|
45713
|
-
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT, and INSERT/UPSERT/UPDATE ... VALIDATE ONLY. ASSERT failure always stops the batch. Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always
|
|
47962
|
+
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT, leading VALIDATE app existing-record audits, and INSERT/UPSERT/UPDATE ... VALIDATE ONLY. ASSERT failure always stops the batch. Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE and VALIDATE ONLY always treat onLimit=truncate as error and perform zero write API calls. Existing-record VALIDATE applies built-in form constraints plus optional CHECK groups and can materialize its fixed five diagnostic columns with INTO #err in a batch. NUMBER targets use the app numberPrecision settings for integer-digit validation and fail closed if settings cannot be read. Excess fractional digits pass through for kintone to round automatically. Supports multi-statement batches with temp tables, including VALIDATE ONLY INTO #err for later SELECT. Mutating DML is rejected.",
|
|
45714
47963
|
inputSchema: queryInputShape
|
|
45715
47964
|
}, tools.queryTool);
|
|
45716
47965
|
server.registerTool("ksql_mutate", {
|
|
45717
|
-
title: "Run mutating kSQL",
|
|
47966
|
+
title: "Run mutating kSQL (IMPORT CSV/JSON via importSources)",
|
|
45718
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).",
|
|
45719
47968
|
inputSchema: mutateInputShape
|
|
45720
47969
|
}, tools.mutateTool);
|