@rex0220/kintone-sql-tools 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/ksql.js +1106 -136
- package/dist-mcp/ksql-mcp.js +1113 -146
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
buildBatchStatementSummary: () => buildBatchStatementSummary,
|
|
27
27
|
buildOutput: () => buildOutput,
|
|
28
28
|
buildReplExecArgv: () => buildReplExecArgv,
|
|
29
|
+
buildValidationOutput: () => buildValidationOutput,
|
|
29
30
|
extractAppIds: () => extractAppIds,
|
|
30
31
|
normalizeAppKey: () => normalizeAppKey,
|
|
31
32
|
normalizeSqlAppProfiles: () => normalizeSqlAppProfiles,
|
|
@@ -1503,6 +1504,7 @@ var Parser = class {
|
|
|
1503
1504
|
tryParseImplicitAlias() {
|
|
1504
1505
|
const k = this.peek().kind;
|
|
1505
1506
|
if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
|
|
1507
|
+
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "VALIDATE" && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "ONLY") return null;
|
|
1506
1508
|
return this.parseTableAliasName();
|
|
1507
1509
|
}
|
|
1508
1510
|
return null;
|
|
@@ -1949,7 +1951,8 @@ var Parser = class {
|
|
|
1949
1951
|
if (subtableCode) {
|
|
1950
1952
|
throw new ParseError("INSERT INTO ... SELECT \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3067\u306F\u672A\u5BFE\u5FDC\u3067\u3059", this.prev());
|
|
1951
1953
|
}
|
|
1952
|
-
|
|
1954
|
+
const validation2 = this.parseDmlControlSuffix();
|
|
1955
|
+
return { type: "INSERT_SELECT", appId, fields, select, ...validation2 };
|
|
1953
1956
|
}
|
|
1954
1957
|
this.expect("VALUES" /* VALUES */);
|
|
1955
1958
|
const values = [];
|
|
@@ -1959,7 +1962,11 @@ var Parser = class {
|
|
|
1959
1962
|
this.expect(")" /* RPAREN */);
|
|
1960
1963
|
values.push(row);
|
|
1961
1964
|
} while (this.consume("," /* COMMA */));
|
|
1962
|
-
|
|
1965
|
+
const validation = this.parseDmlControlSuffix();
|
|
1966
|
+
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
1967
|
+
throw new ParseError("VALIDATE ONLY / ON ERROR SKIP \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB INSERT \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093", this.prev());
|
|
1968
|
+
}
|
|
1969
|
+
return subtableCode ? { type: "INSERT", appId, subtableCode, fields, values, ...validation } : { type: "INSERT", appId, fields, values, ...validation };
|
|
1963
1970
|
}
|
|
1964
1971
|
parseUpsert() {
|
|
1965
1972
|
this.expect("UPSERT" /* UPSERT */);
|
|
@@ -1976,7 +1983,8 @@ var Parser = class {
|
|
|
1976
1983
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
1977
1984
|
const select = this.parseSelect();
|
|
1978
1985
|
const keyFields2 = this.parseOnDuplicate();
|
|
1979
|
-
|
|
1986
|
+
const validation2 = this.parseDmlControlSuffix();
|
|
1987
|
+
return { type: "UPSERT_SELECT", appId, fields, select, keyFields: keyFields2, ...validation2 };
|
|
1980
1988
|
}
|
|
1981
1989
|
this.expect("VALUES" /* VALUES */);
|
|
1982
1990
|
const values = [];
|
|
@@ -1986,7 +1994,8 @@ var Parser = class {
|
|
|
1986
1994
|
this.expect(")" /* RPAREN */);
|
|
1987
1995
|
} while (this.consume("," /* COMMA */));
|
|
1988
1996
|
const keyFields = this.parseOnDuplicate();
|
|
1989
|
-
|
|
1997
|
+
const validation = this.parseDmlControlSuffix();
|
|
1998
|
+
return { type: "UPSERT", appId, fields, values, keyFields, ...validation };
|
|
1990
1999
|
}
|
|
1991
2000
|
parseOnDuplicate() {
|
|
1992
2001
|
this.expectKeyword("ON" /* ON */, "UPSERT \u306B\u306F ON DUPLICATE (\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9) \u304C\u5FC5\u8981\u3067\u3059");
|
|
@@ -2066,10 +2075,14 @@ var Parser = class {
|
|
|
2066
2075
|
if (!table.alias) {
|
|
2067
2076
|
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
2068
2077
|
}
|
|
2078
|
+
if (table.alias.toLowerCase() === `app${appId}`.toLowerCase()) {
|
|
2079
|
+
throw new ParseError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u66F4\u65B0\u5148 APP${appId} \u3068\u540C\u540D\u306B\u3067\u304D\u307E\u305B\u3093`, this.prev());
|
|
2080
|
+
}
|
|
2069
2081
|
from = {
|
|
2070
2082
|
appId: table.appId,
|
|
2071
2083
|
cteName: table.cteName,
|
|
2072
2084
|
alias: table.alias,
|
|
2085
|
+
targetJoinField: "",
|
|
2073
2086
|
joinKeyField: "",
|
|
2074
2087
|
targetFilter: null
|
|
2075
2088
|
};
|
|
@@ -2088,6 +2101,7 @@ var Parser = class {
|
|
|
2088
2101
|
}
|
|
2089
2102
|
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
2090
2103
|
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
2104
|
+
from.targetJoinField = decomposed.targetJoinField;
|
|
2091
2105
|
from.joinKeyField = decomposed.joinKeyField;
|
|
2092
2106
|
from.targetFilter = decomposed.targetFilter;
|
|
2093
2107
|
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
@@ -2096,8 +2110,66 @@ var Parser = class {
|
|
|
2096
2110
|
whereTok
|
|
2097
2111
|
);
|
|
2098
2112
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2113
|
+
const validation = this.parseDmlControlSuffix();
|
|
2114
|
+
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
2115
|
+
throw new ParseError("VALIDATE ONLY / ON ERROR SKIP \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093", this.prev());
|
|
2116
|
+
}
|
|
2117
|
+
if (from !== null) return { type: "UPDATE", appId, assignments, where, from, ...validation };
|
|
2118
|
+
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where, ...validation } : { type: "UPDATE", appId, assignments, where, ...validation };
|
|
2119
|
+
}
|
|
2120
|
+
/** DML末尾の VALIDATE ONLY または ON ERROR SKIP。各語はsoft keyword。 */
|
|
2121
|
+
parseDmlControlSuffix() {
|
|
2122
|
+
if (this.peek().kind === "ON" /* ON */) return this.parseOnErrorSkipSuffix();
|
|
2123
|
+
if (this.isSoftKeyword("REJECT")) {
|
|
2124
|
+
throw new ParseError("REJECT LIMIT \u306B\u306F ON ERROR SKIP INTO \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
2125
|
+
}
|
|
2126
|
+
if (!this.isSoftKeyword("VALIDATE")) return {};
|
|
2127
|
+
const validateTok = this.advance();
|
|
2128
|
+
if (!this.isSoftKeyword("ONLY")) {
|
|
2129
|
+
throw new ParseError("VALIDATE \u306E\u5F8C\u306B\u306F ONLY \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
2130
|
+
}
|
|
2131
|
+
this.advance();
|
|
2132
|
+
let validationErrorTable = null;
|
|
2133
|
+
if (this.consume("INTO" /* INTO */)) {
|
|
2134
|
+
const tok = this.peek();
|
|
2135
|
+
if (tok.kind !== "IDENT" /* IDENT */ || !tok.value.startsWith("#")) {
|
|
2136
|
+
throw new ParseError("VALIDATE ONLY INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
2137
|
+
}
|
|
2138
|
+
validationErrorTable = this.parseTableName();
|
|
2139
|
+
}
|
|
2140
|
+
if (this.peek().kind === "ON" /* ON */ || this.isSoftKeyword("REJECT")) {
|
|
2141
|
+
throw new ParseError("VALIDATE ONLY \u3068 ON ERROR / REJECT LIMIT \u306F\u4F75\u8A18\u3067\u304D\u307E\u305B\u3093", validateTok);
|
|
2142
|
+
}
|
|
2143
|
+
return { validateOnly: true, validationErrorTable };
|
|
2144
|
+
}
|
|
2145
|
+
parseOnErrorSkipSuffix() {
|
|
2146
|
+
const onTok = this.advance();
|
|
2147
|
+
if (!this.isSoftKeyword("ERROR")) throw new ParseError("ON \u306E\u5F8C\u306B\u306F ERROR \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
2148
|
+
this.advance();
|
|
2149
|
+
if (!this.isSoftKeyword("SKIP")) throw new ParseError("ON ERROR \u306E\u5F8C\u306B\u306F SKIP \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
2150
|
+
this.advance();
|
|
2151
|
+
this.expect("INTO" /* INTO */, "ON ERROR SKIP \u306B\u306F INTO #\u4E00\u6642\u30C6\u30FC\u30D6\u30EB \u304C\u5FC5\u8981\u3067\u3059");
|
|
2152
|
+
const tableTok = this.peek();
|
|
2153
|
+
if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
|
|
2154
|
+
throw new ParseError("ON ERROR SKIP INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tableTok);
|
|
2155
|
+
}
|
|
2156
|
+
const errorTable = this.parseTableName();
|
|
2157
|
+
let rejectLimit = null;
|
|
2158
|
+
if (this.isSoftKeyword("REJECT")) {
|
|
2159
|
+
this.advance();
|
|
2160
|
+
this.expect("LIMIT" /* LIMIT */, "REJECT \u306E\u5F8C\u306B\u306F LIMIT \u304C\u5FC5\u8981\u3067\u3059");
|
|
2161
|
+
const tok = this.expect("NUMBER" /* NUMBER */, "REJECT LIMIT \u306B\u306F 0 \u4EE5\u4E0A\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
2162
|
+
if (!/^\d+$/.test(tok.value)) throw new ParseError("REJECT LIMIT \u306F 0 \u4EE5\u4E0A\u306E\u5B89\u5168\u306A\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", tok);
|
|
2163
|
+
rejectLimit = Number(tok.value);
|
|
2164
|
+
if (!Number.isSafeInteger(rejectLimit)) throw new ParseError("REJECT LIMIT \u306F 0 \u4EE5\u4E0A\u306E\u5B89\u5168\u306A\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044", tok);
|
|
2165
|
+
}
|
|
2166
|
+
if (this.isSoftKeyword("REJECT") || this.isSoftKeyword("VALIDATE") || this.peek().kind === "ON" /* ON */) {
|
|
2167
|
+
throw new ParseError("ON ERROR SKIP \u306E\u53E5\u304C\u91CD\u8907\u307E\u305F\u306F\u7AF6\u5408\u3057\u3066\u3044\u307E\u3059", onTok);
|
|
2168
|
+
}
|
|
2169
|
+
return { onErrorSkip: true, errorTable, rejectLimit };
|
|
2170
|
+
}
|
|
2171
|
+
isSoftKeyword(value) {
|
|
2172
|
+
return this.peek().kind === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === value;
|
|
2101
2173
|
}
|
|
2102
2174
|
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
2103
2175
|
for (const assignment of assignments) {
|
|
@@ -2122,11 +2194,11 @@ var Parser = class {
|
|
|
2122
2194
|
const leaves = this.flattenTopLevelAnd(where);
|
|
2123
2195
|
const joins = [];
|
|
2124
2196
|
leaves.forEach((leaf, index) => {
|
|
2125
|
-
const
|
|
2126
|
-
if (
|
|
2197
|
+
const matched = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
2198
|
+
if (matched !== null) joins.push({ index, ...matched });
|
|
2127
2199
|
});
|
|
2128
2200
|
if (joins.length !== 1) {
|
|
2129
|
-
throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target
|
|
2201
|
+
throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target.key = source.key \u306E\u7D50\u5408\u7B49\u5024\u304C\u3061\u3087\u3046\u30691\u3064\u5FC5\u8981\u3067\u3059", tok);
|
|
2130
2202
|
}
|
|
2131
2203
|
const join2 = joins[0];
|
|
2132
2204
|
for (let i = 0; i < leaves.length; i++) {
|
|
@@ -2142,7 +2214,7 @@ var Parser = class {
|
|
|
2142
2214
|
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
2143
2215
|
null
|
|
2144
2216
|
);
|
|
2145
|
-
return { joinKeyField: join2.sourceField, targetFilter };
|
|
2217
|
+
return { targetJoinField: join2.targetField, joinKeyField: join2.sourceField, targetFilter };
|
|
2146
2218
|
}
|
|
2147
2219
|
flattenTopLevelAnd(expr) {
|
|
2148
2220
|
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
@@ -2156,16 +2228,20 @@ var Parser = class {
|
|
|
2156
2228
|
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
2157
2229
|
if (right === null) return null;
|
|
2158
2230
|
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
2159
|
-
if (this.
|
|
2160
|
-
|
|
2231
|
+
if (this.isTargetRef(left, targetAppId) && this.isSourceRef(right, sourceAlias)) {
|
|
2232
|
+
return { targetField: left.field, sourceField: right.field };
|
|
2233
|
+
}
|
|
2234
|
+
if (this.isSourceRef(left, sourceAlias) && this.isTargetRef(right, targetAppId)) {
|
|
2235
|
+
return { targetField: right.field, sourceField: left.field };
|
|
2236
|
+
}
|
|
2161
2237
|
return null;
|
|
2162
2238
|
}
|
|
2163
2239
|
splitQualifiedField(field) {
|
|
2164
2240
|
const dot = field.indexOf(".");
|
|
2165
2241
|
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
2166
2242
|
}
|
|
2167
|
-
|
|
2168
|
-
return ref.
|
|
2243
|
+
isTargetRef(ref, appId) {
|
|
2244
|
+
return ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase();
|
|
2169
2245
|
}
|
|
2170
2246
|
isSourceRef(ref, alias) {
|
|
2171
2247
|
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
@@ -2492,6 +2568,15 @@ function isDmlType(type) {
|
|
|
2492
2568
|
function isReadOnlyType(type) {
|
|
2493
2569
|
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";
|
|
2494
2570
|
}
|
|
2571
|
+
function writesKintone(stmt) {
|
|
2572
|
+
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
2573
|
+
}
|
|
2574
|
+
function isReadOnlyStatement(stmt) {
|
|
2575
|
+
return !writesKintone(stmt) && (isReadOnlyType(stmt.type) || isDmlType(stmt.type));
|
|
2576
|
+
}
|
|
2577
|
+
function requiresCompleteInput(stmt) {
|
|
2578
|
+
return isDmlType(stmt.type);
|
|
2579
|
+
}
|
|
2495
2580
|
function hasWhereClause(stmt) {
|
|
2496
2581
|
if (!stmt || typeof stmt !== "object") return false;
|
|
2497
2582
|
const obj = stmt;
|
|
@@ -3731,11 +3816,17 @@ function analyzeBatch(statements) {
|
|
|
3731
3816
|
}
|
|
3732
3817
|
}
|
|
3733
3818
|
const defined = /* @__PURE__ */ new Map();
|
|
3819
|
+
const validationSchemas = /* @__PURE__ */ new Map();
|
|
3734
3820
|
const createdOrder = [];
|
|
3735
3821
|
const results = [];
|
|
3736
3822
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
3737
3823
|
const variableOrder = [];
|
|
3738
3824
|
statements.forEach((stmt, index) => {
|
|
3825
|
+
const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
3826
|
+
if (statements.length === 1 && validationTable) {
|
|
3827
|
+
const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
3828
|
+
throw new BatchAnalysisError(message, index);
|
|
3829
|
+
}
|
|
3739
3830
|
const statementType = getStatementType(stmt);
|
|
3740
3831
|
const created = [];
|
|
3741
3832
|
const dropped = [];
|
|
@@ -3790,6 +3881,28 @@ function analyzeBatch(statements) {
|
|
|
3790
3881
|
}
|
|
3791
3882
|
dependsOn.add(at);
|
|
3792
3883
|
}
|
|
3884
|
+
if (validationTable) {
|
|
3885
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
3886
|
+
const signature = JSON.stringify(payloadFields);
|
|
3887
|
+
const at = defined.get(validationTable);
|
|
3888
|
+
if (at === void 0) {
|
|
3889
|
+
defined.set(validationTable, index);
|
|
3890
|
+
validationSchemas.set(validationTable, signature);
|
|
3891
|
+
createdOrder.push(validationTable);
|
|
3892
|
+
created.push(validationTable);
|
|
3893
|
+
if (defined.size > MAX_TEMP_TABLES) {
|
|
3894
|
+
throw new BatchAnalysisError(`ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`, index);
|
|
3895
|
+
}
|
|
3896
|
+
} else {
|
|
3897
|
+
if (validationSchemas.get(validationTable) !== signature) {
|
|
3898
|
+
throw new BatchAnalysisError(
|
|
3899
|
+
`ParseError: validation error table ${validationTable} has a different payload schema.`,
|
|
3900
|
+
index
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3903
|
+
dependsOn.add(at);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3793
3906
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
3794
3907
|
if (defined.has(stmt.name)) {
|
|
3795
3908
|
throw new BatchAnalysisError(
|
|
@@ -3822,8 +3935,8 @@ function analyzeBatch(statements) {
|
|
|
3822
3935
|
results.push({
|
|
3823
3936
|
index,
|
|
3824
3937
|
statementType,
|
|
3825
|
-
isDml:
|
|
3826
|
-
isReadOnly:
|
|
3938
|
+
isDml: writesKintone(stmt),
|
|
3939
|
+
isReadOnly: isReadOnlyStatement(stmt),
|
|
3827
3940
|
hasWhere: hasWhereClause(stmt),
|
|
3828
3941
|
insertValuesCount: getInsertValuesCount(stmt),
|
|
3829
3942
|
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
@@ -3833,10 +3946,15 @@ function analyzeBatch(statements) {
|
|
|
3833
3946
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
3834
3947
|
tempOnlySource,
|
|
3835
3948
|
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null,
|
|
3836
|
-
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null
|
|
3949
|
+
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null,
|
|
3950
|
+
isValidationOnly: "validateOnly" in stmt && stmt.validateOnly === true,
|
|
3951
|
+
isOnErrorSkip: "onErrorSkip" in stmt && stmt.onErrorSkip === true,
|
|
3952
|
+
requiresCompleteInput: requiresCompleteInput(stmt)
|
|
3837
3953
|
});
|
|
3838
3954
|
});
|
|
3839
3955
|
const containsDml = results.some((r) => r.isDml);
|
|
3956
|
+
const containsValidationOnly = results.some((r) => r.isValidationOnly);
|
|
3957
|
+
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
3840
3958
|
const variables = variableOrder.map((name) => ({
|
|
3841
3959
|
name,
|
|
3842
3960
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
@@ -3845,6 +3963,8 @@ function analyzeBatch(statements) {
|
|
|
3845
3963
|
statementCount: statements.length,
|
|
3846
3964
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
3847
3965
|
containsDml,
|
|
3966
|
+
containsValidationOnly,
|
|
3967
|
+
requiresCompleteInput: needsCompleteInput,
|
|
3848
3968
|
tempTables: createdOrder,
|
|
3849
3969
|
variables,
|
|
3850
3970
|
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
@@ -4654,6 +4774,18 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
4654
4774
|
return "";
|
|
4655
4775
|
}
|
|
4656
4776
|
function toKintoneValue(value, fieldType) {
|
|
4777
|
+
const result = normalizeDmlSqlValue(value, fieldType);
|
|
4778
|
+
if (!result.ok) throw new DmlConvertError(result.message);
|
|
4779
|
+
return result.value;
|
|
4780
|
+
}
|
|
4781
|
+
function normalizeDmlSqlValue(value, fieldType) {
|
|
4782
|
+
try {
|
|
4783
|
+
return { ok: true, value: convertDmlSqlValue(value, fieldType) };
|
|
4784
|
+
} catch (e) {
|
|
4785
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
function convertDmlSqlValue(value, fieldType) {
|
|
4657
4789
|
switch (value.type) {
|
|
4658
4790
|
case "VARIABLE":
|
|
4659
4791
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
@@ -4958,7 +5090,7 @@ function hasAggregateColumns(columns) {
|
|
|
4958
5090
|
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr)
|
|
4959
5091
|
);
|
|
4960
5092
|
}
|
|
4961
|
-
function applyGroupBy(rows, groupByKeys, columns) {
|
|
5093
|
+
function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
|
|
4962
5094
|
const groups = /* @__PURE__ */ new Map();
|
|
4963
5095
|
for (const row of rows) {
|
|
4964
5096
|
const key = groupByKeys.map((k) => evalGroupByKey(k, row)).join("\0");
|
|
@@ -4982,15 +5114,15 @@ function applyGroupBy(rows, groupByKeys, columns) {
|
|
|
4982
5114
|
for (const col of columns) {
|
|
4983
5115
|
if (col.type === "AGGREGATE") {
|
|
4984
5116
|
const syntheticKey = aggregateSyntheticName2(col.func, col.distinct, col.arg);
|
|
4985
|
-
const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows));
|
|
5117
|
+
const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows, resolveAggSortKind));
|
|
4986
5118
|
outRow[col.alias ?? syntheticKey] = value;
|
|
4987
5119
|
if (col.alias) outRow[syntheticKey] = value;
|
|
4988
5120
|
} else if (col.type === "ARITH_AGG_COL") {
|
|
4989
5121
|
const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
|
|
4990
|
-
outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows));
|
|
5122
|
+
outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
|
|
4991
5123
|
} else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
|
|
4992
5124
|
const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
|
|
4993
|
-
const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows);
|
|
5125
|
+
const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
|
|
4994
5126
|
outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
|
|
4995
5127
|
}
|
|
4996
5128
|
}
|
|
@@ -5003,7 +5135,7 @@ function evalGroupByKey(key, row) {
|
|
|
5003
5135
|
if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
|
|
5004
5136
|
return String(evalArithExpr(key.expr, row));
|
|
5005
5137
|
}
|
|
5006
|
-
function evalAggregate(func, distinct, arg, rows) {
|
|
5138
|
+
function evalAggregate(func, distinct, arg, rows, resolveAggSortKind) {
|
|
5007
5139
|
if (arg.type === "WILDCARD") {
|
|
5008
5140
|
return func === "COUNT" ? rows.length : 0;
|
|
5009
5141
|
}
|
|
@@ -5023,6 +5155,11 @@ function evalAggregate(func, distinct, arg, rows) {
|
|
|
5023
5155
|
}
|
|
5024
5156
|
const eff = distinct ? [...new Set(strValues)] : strValues;
|
|
5025
5157
|
if (func === "COUNT") return eff.length;
|
|
5158
|
+
const sortKind = (func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" ? resolveAggSortKind?.(toAggregateFieldRef(arg.field)) : void 0;
|
|
5159
|
+
if (sortKind === "string") {
|
|
5160
|
+
if (eff.length === 0) return "";
|
|
5161
|
+
return func === "MAX" ? maxStringOf(eff) : minStringOf(eff);
|
|
5162
|
+
}
|
|
5026
5163
|
const nums = eff.map(Number);
|
|
5027
5164
|
switch (func) {
|
|
5028
5165
|
case "SUM":
|
|
@@ -5036,6 +5173,20 @@ function evalAggregate(func, distinct, arg, rows) {
|
|
|
5036
5173
|
return nums.length === 0 ? 0 : minOf(nums);
|
|
5037
5174
|
}
|
|
5038
5175
|
}
|
|
5176
|
+
function toAggregateFieldRef(field) {
|
|
5177
|
+
const dot = field.indexOf(".");
|
|
5178
|
+
return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
|
|
5179
|
+
}
|
|
5180
|
+
function maxStringOf(values) {
|
|
5181
|
+
let value = values[0];
|
|
5182
|
+
for (const candidate of values) if (candidate > value) value = candidate;
|
|
5183
|
+
return value;
|
|
5184
|
+
}
|
|
5185
|
+
function minStringOf(values) {
|
|
5186
|
+
let value = values[0];
|
|
5187
|
+
for (const candidate of values) if (candidate < value) value = candidate;
|
|
5188
|
+
return value;
|
|
5189
|
+
}
|
|
5039
5190
|
function maxOf(nums) {
|
|
5040
5191
|
let m = nums[0];
|
|
5041
5192
|
for (const n of nums) if (n > m) m = n;
|
|
@@ -5046,11 +5197,11 @@ function minOf(nums) {
|
|
|
5046
5197
|
for (const n of nums) if (n < m) m = n;
|
|
5047
5198
|
return m;
|
|
5048
5199
|
}
|
|
5049
|
-
function evalAggArithExpr(node, rows) {
|
|
5200
|
+
function evalAggArithExpr(node, rows, resolveAggSortKind) {
|
|
5050
5201
|
if (node.type === "NUMBER") return node.value;
|
|
5051
|
-
if (node.type === "AGG_REF") return evalAggregate(node.func, node.distinct, node.arg, rows);
|
|
5052
|
-
const l = evalAggArithExpr(node.left, rows);
|
|
5053
|
-
const r = evalAggArithExpr(node.right, rows);
|
|
5202
|
+
if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, rows, resolveAggSortKind));
|
|
5203
|
+
const l = evalAggArithExpr(node.left, rows, resolveAggSortKind);
|
|
5204
|
+
const r = evalAggArithExpr(node.right, rows, resolveAggSortKind);
|
|
5054
5205
|
switch (node.op) {
|
|
5055
5206
|
case "+":
|
|
5056
5207
|
return l + r;
|
|
@@ -5395,26 +5546,24 @@ function hasAggregateInStringFuncArg(arg) {
|
|
|
5395
5546
|
function hasAggregateInStringFuncExpr2(expr) {
|
|
5396
5547
|
return expr.args.some((arg) => hasAggregateInStringFuncArg(arg));
|
|
5397
5548
|
}
|
|
5398
|
-
function resolveAggInStringFuncArg(arg, rows) {
|
|
5549
|
+
function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
|
|
5399
5550
|
if (arg.type === "AGG_REF") {
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
value: evalAggregate(arg.func, arg.distinct, arg.arg, rows)
|
|
5403
|
-
};
|
|
5551
|
+
const value = evalAggregate(arg.func, arg.distinct, arg.arg, rows, resolveAggSortKind);
|
|
5552
|
+
return typeof value === "number" ? { type: "NUMBER", value } : { type: "STRING", value };
|
|
5404
5553
|
}
|
|
5405
5554
|
if (arg.type === "AGG_ARITH") {
|
|
5406
|
-
return { type: "NUMBER", value: evalAggArithExpr(arg, rows) };
|
|
5555
|
+
return { type: "NUMBER", value: evalAggArithExpr(arg, rows, resolveAggSortKind) };
|
|
5407
5556
|
}
|
|
5408
5557
|
if (arg.type === "STRING_FUNC") {
|
|
5409
|
-
return resolveAggInStringFuncExpr(arg, rows);
|
|
5558
|
+
return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
|
|
5410
5559
|
}
|
|
5411
5560
|
return arg;
|
|
5412
5561
|
}
|
|
5413
|
-
function resolveAggInStringFuncExpr(expr, rows) {
|
|
5562
|
+
function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
|
|
5414
5563
|
return {
|
|
5415
5564
|
type: "STRING_FUNC",
|
|
5416
5565
|
func: expr.func,
|
|
5417
|
-
args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows))
|
|
5566
|
+
args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
|
|
5418
5567
|
};
|
|
5419
5568
|
}
|
|
5420
5569
|
function runFullScan(input) {
|
|
@@ -5426,6 +5575,7 @@ function runFullScan(input) {
|
|
|
5426
5575
|
sortKinds,
|
|
5427
5576
|
fieldTypeResolver,
|
|
5428
5577
|
havingFieldTypeResolver,
|
|
5578
|
+
aggregateSortKindResolver,
|
|
5429
5579
|
appliedKlikes,
|
|
5430
5580
|
sourceColumns
|
|
5431
5581
|
} = input;
|
|
@@ -5441,7 +5591,7 @@ function runFullScan(input) {
|
|
|
5441
5591
|
}
|
|
5442
5592
|
rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
|
|
5443
5593
|
if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
|
|
5444
|
-
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
|
|
5594
|
+
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns, aggregateSortKindResolver);
|
|
5445
5595
|
}
|
|
5446
5596
|
rows = applyHaving(rows, stmt.having, havingFieldTypeResolver);
|
|
5447
5597
|
if (stmt.distinct) {
|
|
@@ -5494,6 +5644,228 @@ function toFlatString(value) {
|
|
|
5494
5644
|
}
|
|
5495
5645
|
}
|
|
5496
5646
|
|
|
5647
|
+
// src/core/dmlValidation.ts
|
|
5648
|
+
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
5649
|
+
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
5650
|
+
function validateAndNormalizeDmlValue(raw, field) {
|
|
5651
|
+
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
5652
|
+
const original = rawScalarText(raw);
|
|
5653
|
+
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
5654
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
let value;
|
|
5658
|
+
try {
|
|
5659
|
+
value = normalizeRaw(raw, field.fieldType);
|
|
5660
|
+
} catch (e) {
|
|
5661
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
5662
|
+
return { ok: false, code: typeCode(field.fieldType), message };
|
|
5663
|
+
}
|
|
5664
|
+
if (field.required && isEmpty(value)) {
|
|
5665
|
+
return { ok: false, code: "ERR_REQUIRED", message: `${field.code} \u306F\u5FC5\u9808\u3067\u3059` };
|
|
5666
|
+
}
|
|
5667
|
+
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
5668
|
+
const text = String(value);
|
|
5669
|
+
if (!isFiniteDecimal(text)) {
|
|
5670
|
+
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5671
|
+
}
|
|
5672
|
+
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
5673
|
+
return { ok: false, code: "ERR_RANGE_MIN", message: `${field.code} \u306F ${field.minValue} \u4EE5\u4E0A\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5674
|
+
}
|
|
5675
|
+
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
5676
|
+
return { ok: false, code: "ERR_RANGE_MAX", message: `${field.code} \u306F ${field.maxValue} \u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
5680
|
+
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
5681
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
if (typeof value === "string") {
|
|
5685
|
+
const length = value.length;
|
|
5686
|
+
const min = field.minLength == null ? null : Number(field.minLength);
|
|
5687
|
+
const max = field.maxLength == null ? null : Number(field.maxLength);
|
|
5688
|
+
if (Number.isFinite(min) && length < min) {
|
|
5689
|
+
return { ok: false, code: "ERR_LENGTH_MIN", message: `${field.code} \u306F ${min} \u6587\u5B57\u4EE5\u4E0A\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5690
|
+
}
|
|
5691
|
+
if (Number.isFinite(max) && length > max) {
|
|
5692
|
+
return { ok: false, code: "ERR_LENGTH_MAX", message: `${field.code} \u306F ${max} \u6587\u5B57\u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
if (CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
|
|
5696
|
+
const selected = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
5697
|
+
if (selected.some((choice) => !(choice in field.optionOrder))) {
|
|
5698
|
+
return { ok: false, code: "ERR_CHOICE_INVALID", message: `${field.code} \u306B\u5B9A\u7FA9\u5916\u306E\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u3059` };
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
return { ok: true, value };
|
|
5702
|
+
}
|
|
5703
|
+
function rawScalarText(raw) {
|
|
5704
|
+
if (raw == null) return "";
|
|
5705
|
+
if (isSqlValue(raw) && (raw.type === "STRING" || raw.type === "NUMBER")) return String(raw.value);
|
|
5706
|
+
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
5707
|
+
}
|
|
5708
|
+
function isValidTemporalInput(value, type) {
|
|
5709
|
+
if (type === "DATE") return isValidTemporal(value.replace(/\//g, "-"), "DATE");
|
|
5710
|
+
if (type === "TIME") return isValidTemporal(value, "TIME");
|
|
5711
|
+
let normalized = value.replace(/\//g, "-").replace(" ", "T");
|
|
5712
|
+
if (/T\d{2}:\d{2}$/.test(normalized)) normalized += ":00";
|
|
5713
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(normalized)) {
|
|
5714
|
+
return isValidTemporal(normalized.slice(0, 10), "DATE") && isValidTemporal(normalized.slice(11), "TIME");
|
|
5715
|
+
}
|
|
5716
|
+
return isValidTemporal(normalized, "DATETIME");
|
|
5717
|
+
}
|
|
5718
|
+
function normalizeRaw(raw, fieldType) {
|
|
5719
|
+
if (isSqlValue(raw)) {
|
|
5720
|
+
const normalized = normalizeDmlSqlValue(raw, fieldType);
|
|
5721
|
+
if (!normalized.ok) throw new Error(normalized.message);
|
|
5722
|
+
return normalized.value;
|
|
5723
|
+
}
|
|
5724
|
+
if (Array.isArray(raw)) return raw.map((v) => typeof v === "object" && v !== null && "code" in v ? String(v.code) : String(v));
|
|
5725
|
+
const text = raw == null ? "" : String(raw);
|
|
5726
|
+
if (ARRAY_TYPES2.has(fieldType)) {
|
|
5727
|
+
if (text === "") return [];
|
|
5728
|
+
try {
|
|
5729
|
+
const parsed = JSON.parse(text);
|
|
5730
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
5731
|
+
} catch {
|
|
5732
|
+
}
|
|
5733
|
+
return text.split(",").map((v) => v.trim());
|
|
5734
|
+
}
|
|
5735
|
+
return text;
|
|
5736
|
+
}
|
|
5737
|
+
function isSqlValue(value) {
|
|
5738
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
5739
|
+
}
|
|
5740
|
+
function isEmptyDmlValue(value) {
|
|
5741
|
+
if (value == null || value === "") return true;
|
|
5742
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
5743
|
+
if (isSqlValue(value)) {
|
|
5744
|
+
if (value.type === "STRING") return value.value === "";
|
|
5745
|
+
if (value.type === "ARRAY") return value.elements.length === 0;
|
|
5746
|
+
}
|
|
5747
|
+
return false;
|
|
5748
|
+
}
|
|
5749
|
+
function isEmpty(value) {
|
|
5750
|
+
return value === "" || Array.isArray(value) && value.length === 0;
|
|
5751
|
+
}
|
|
5752
|
+
function typeCode(type) {
|
|
5753
|
+
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
5754
|
+
}
|
|
5755
|
+
function isFiniteDecimal(value) {
|
|
5756
|
+
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
5757
|
+
}
|
|
5758
|
+
function compareDecimal(left, right) {
|
|
5759
|
+
const normalize = (input) => {
|
|
5760
|
+
let s = input.trim();
|
|
5761
|
+
let sign = 1;
|
|
5762
|
+
if (s.startsWith("-")) {
|
|
5763
|
+
sign = -1;
|
|
5764
|
+
s = s.slice(1);
|
|
5765
|
+
} else if (s.startsWith("+")) s = s.slice(1);
|
|
5766
|
+
let [whole, fraction = ""] = s.split(".");
|
|
5767
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
5768
|
+
fraction = fraction.replace(/0+$/, "");
|
|
5769
|
+
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
5770
|
+
return { sign, whole, fraction };
|
|
5771
|
+
};
|
|
5772
|
+
const a = normalize(left);
|
|
5773
|
+
const b = normalize(right);
|
|
5774
|
+
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
5775
|
+
const direction = a.sign;
|
|
5776
|
+
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
5777
|
+
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
5778
|
+
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
5779
|
+
const af = a.fraction.padEnd(width, "0");
|
|
5780
|
+
const bf = b.fraction.padEnd(width, "0");
|
|
5781
|
+
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
5782
|
+
}
|
|
5783
|
+
function isValidTemporal(value, type) {
|
|
5784
|
+
if (type === "TIME") {
|
|
5785
|
+
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
5786
|
+
return m2 !== null && Number(m2[1]) <= 23 && Number(m2[2]) <= 59 && Number(m2[3] ?? 0) <= 59;
|
|
5787
|
+
}
|
|
5788
|
+
const datePart = type === "DATE" ? value : value.slice(0, 10);
|
|
5789
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart);
|
|
5790
|
+
if (!m) return false;
|
|
5791
|
+
const year = Number(m[1]);
|
|
5792
|
+
const month = Number(m[2]);
|
|
5793
|
+
const day = Number(m[3]);
|
|
5794
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
5795
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return false;
|
|
5796
|
+
if (type === "DATE") return true;
|
|
5797
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && isValidTemporal(value.slice(11, value.endsWith("Z") ? -1 : value.length - 6), "TIME");
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
// src/core/dmlValidationCandidates.ts
|
|
5801
|
+
var VALIDATION_META_COLUMNS = [
|
|
5802
|
+
"$err_statement",
|
|
5803
|
+
"$err_operation",
|
|
5804
|
+
"$err_row",
|
|
5805
|
+
"$err_field",
|
|
5806
|
+
"$err_code",
|
|
5807
|
+
"$err_message"
|
|
5808
|
+
];
|
|
5809
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
5810
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
5811
|
+
const errors = [];
|
|
5812
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
5813
|
+
for (const candidate of candidates) {
|
|
5814
|
+
candidate.record ??= {};
|
|
5815
|
+
const rowErrors = [...candidate.preErrors];
|
|
5816
|
+
for (const code of targetFields) {
|
|
5817
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
5818
|
+
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
5819
|
+
else candidate.record[code] = { value: result.value };
|
|
5820
|
+
}
|
|
5821
|
+
if (candidate.mode === "create") {
|
|
5822
|
+
for (const info of fieldInfos) {
|
|
5823
|
+
if (info.inSubtable) continue;
|
|
5824
|
+
if (candidate.payload.has(info.code)) continue;
|
|
5825
|
+
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
5826
|
+
if (!emptyDefault) {
|
|
5827
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
5828
|
+
if (!defaultResult.ok) rowErrors.push({
|
|
5829
|
+
field: info.code,
|
|
5830
|
+
code: defaultResult.code,
|
|
5831
|
+
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
5832
|
+
});
|
|
5833
|
+
} else {
|
|
5834
|
+
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
5835
|
+
if (!emptyResult.ok) {
|
|
5836
|
+
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
5837
|
+
} else if (info.required) {
|
|
5838
|
+
rowErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
|
|
5844
|
+
for (const error of rowErrors) {
|
|
5845
|
+
const row = {};
|
|
5846
|
+
for (const field of payloadFields) row[field] = renderValidationValue(candidate.payload.get(field));
|
|
5847
|
+
row["$err_statement"] = String(statementNumber);
|
|
5848
|
+
row["$err_operation"] = operation;
|
|
5849
|
+
row["$err_row"] = String(candidate.rowNumber);
|
|
5850
|
+
row["$err_field"] = error.field;
|
|
5851
|
+
row["$err_code"] = error.code;
|
|
5852
|
+
row["$err_message"] = error.message;
|
|
5853
|
+
errors.push(row);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
|
|
5857
|
+
}
|
|
5858
|
+
function renderValidationValue(value) {
|
|
5859
|
+
if (value == null) return "";
|
|
5860
|
+
if (typeof value === "object" && "type" in value) {
|
|
5861
|
+
const sql = value;
|
|
5862
|
+
if (sql.type === "STRING" || sql.type === "NUMBER") return String(sql.value ?? "");
|
|
5863
|
+
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
5864
|
+
}
|
|
5865
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
5866
|
+
return String(value);
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5497
5869
|
// src/execute.ts
|
|
5498
5870
|
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";
|
|
5499
5871
|
var SearchAbortedError = class extends Error {
|
|
@@ -5597,6 +5969,15 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
5597
5969
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
5598
5970
|
}
|
|
5599
5971
|
validateKlikeStatement(stmt);
|
|
5972
|
+
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
5973
|
+
if (stmt.validationErrorTable) {
|
|
5974
|
+
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
5975
|
+
}
|
|
5976
|
+
return executeDmlValidation(stmt, client, { ...options, onLimitReached: "error" }, cacheContext, void 0, 1);
|
|
5977
|
+
}
|
|
5978
|
+
if ("onErrorSkip" in stmt && stmt.onErrorSkip === true) {
|
|
5979
|
+
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
5980
|
+
}
|
|
5600
5981
|
switch (stmt.type) {
|
|
5601
5982
|
case "SELECT":
|
|
5602
5983
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -5638,6 +6019,17 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
5638
6019
|
}
|
|
5639
6020
|
}
|
|
5640
6021
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
6022
|
+
function appendValidationErrors(tempTables, name, columns, rows, maxRows) {
|
|
6023
|
+
const current = tempTables.get(name);
|
|
6024
|
+
if (current && (current.columns.length !== columns.length || current.columns.some((c, i) => c !== columns[i]))) {
|
|
6025
|
+
throw new Error(`ArgumentError: validation error table ${name} has a different schema.`);
|
|
6026
|
+
}
|
|
6027
|
+
const existingRows = current?.rows ?? [];
|
|
6028
|
+
if (existingRows.length + rows.length > maxRows) {
|
|
6029
|
+
throw new Error(`ArgumentError: temp table ${name} exceeds max rows (${maxRows}).`);
|
|
6030
|
+
}
|
|
6031
|
+
tempTables.set(name, { columns: [...columns], rows: [...existingRows, ...rows] });
|
|
6032
|
+
}
|
|
5641
6033
|
var BatchTimeoutError = class extends Error {
|
|
5642
6034
|
constructor() {
|
|
5643
6035
|
super("TimeoutError: batch timeout exceeded.");
|
|
@@ -5719,7 +6111,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5719
6111
|
}
|
|
5720
6112
|
results.push({ ...base, status: "success", ...outcome });
|
|
5721
6113
|
} catch (e) {
|
|
5722
|
-
results.push({
|
|
6114
|
+
results.push({
|
|
6115
|
+
...base,
|
|
6116
|
+
status: "error",
|
|
6117
|
+
error: toBatchStatementError(e),
|
|
6118
|
+
...e instanceof RejectLimitExceededError ? { result: e.diagnostic } : {}
|
|
6119
|
+
});
|
|
5723
6120
|
failed.add(i);
|
|
5724
6121
|
if (e instanceof BatchTimeoutError) {
|
|
5725
6122
|
aborted = "timeout";
|
|
@@ -5778,6 +6175,38 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
5778
6175
|
}
|
|
5779
6176
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
5780
6177
|
validateKlikeStatement(resolvedStmt);
|
|
6178
|
+
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
6179
|
+
const result = await executeDmlValidation(
|
|
6180
|
+
resolvedStmt,
|
|
6181
|
+
client,
|
|
6182
|
+
{ ...options, onLimitReached: "error" },
|
|
6183
|
+
cacheContext,
|
|
6184
|
+
tempTables,
|
|
6185
|
+
info.index + 1
|
|
6186
|
+
);
|
|
6187
|
+
if (resolvedStmt.validationErrorTable) {
|
|
6188
|
+
appendValidationErrors(
|
|
6189
|
+
tempTables,
|
|
6190
|
+
resolvedStmt.validationErrorTable,
|
|
6191
|
+
result.columns,
|
|
6192
|
+
result.errors,
|
|
6193
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
6194
|
+
);
|
|
6195
|
+
}
|
|
6196
|
+
return { result };
|
|
6197
|
+
}
|
|
6198
|
+
if ("onErrorSkip" in resolvedStmt && resolvedStmt.onErrorSkip === true) {
|
|
6199
|
+
return {
|
|
6200
|
+
result: await executeOnErrorSkip(
|
|
6201
|
+
resolvedStmt,
|
|
6202
|
+
client,
|
|
6203
|
+
{ ...options, onLimitReached: "error" },
|
|
6204
|
+
cacheContext,
|
|
6205
|
+
tempTables,
|
|
6206
|
+
info.index + 1
|
|
6207
|
+
)
|
|
6208
|
+
};
|
|
6209
|
+
}
|
|
5781
6210
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
5782
6211
|
const materializeOptions = {
|
|
5783
6212
|
...options,
|
|
@@ -6311,6 +6740,116 @@ async function loadTypedInFieldTypes(stmt, client, cacheContext) {
|
|
|
6311
6740
|
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
6312
6741
|
return new Map(entries);
|
|
6313
6742
|
}
|
|
6743
|
+
function aggregateFieldRef(field) {
|
|
6744
|
+
const dot = field.indexOf(".");
|
|
6745
|
+
return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
|
|
6746
|
+
}
|
|
6747
|
+
function collectAggregateRef(func, arg, out) {
|
|
6748
|
+
if ((func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" && arg.field) {
|
|
6749
|
+
out.push(aggregateFieldRef(arg.field));
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6752
|
+
function collectAggregateOperandRefs(node, out) {
|
|
6753
|
+
if (node.type === "AGG_REF") {
|
|
6754
|
+
collectAggregateRef(node.func, node.arg, out);
|
|
6755
|
+
return;
|
|
6756
|
+
}
|
|
6757
|
+
if (node.type === "AGG_ARITH") {
|
|
6758
|
+
collectAggregateOperandRefs(node.left, out);
|
|
6759
|
+
collectAggregateOperandRefs(node.right, out);
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
function collectStringFuncAggregateRefs(expr, out) {
|
|
6763
|
+
for (const arg of expr.args) {
|
|
6764
|
+
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
6765
|
+
collectAggregateOperandRefs(arg, out);
|
|
6766
|
+
} else if (arg.type === "STRING_FUNC") {
|
|
6767
|
+
collectStringFuncAggregateRefs(arg, out);
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
function collectSelectAggregateSortRefs(columns) {
|
|
6772
|
+
const refs = [];
|
|
6773
|
+
for (const column of columns) {
|
|
6774
|
+
if (column.type === "AGGREGATE") {
|
|
6775
|
+
collectAggregateRef(column.func, column.arg, refs);
|
|
6776
|
+
} else if (column.type === "ARITH_AGG_COL") {
|
|
6777
|
+
collectAggregateOperandRefs(column.expr, refs);
|
|
6778
|
+
} else if (column.type === "STRFUNC_COL") {
|
|
6779
|
+
collectStringFuncAggregateRefs(column.expr, refs);
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
return refs;
|
|
6783
|
+
}
|
|
6784
|
+
var AGGREGATE_STRING_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
6785
|
+
"SINGLE_LINE_TEXT",
|
|
6786
|
+
"MULTI_LINE_TEXT",
|
|
6787
|
+
"RICH_TEXT",
|
|
6788
|
+
"LINK",
|
|
6789
|
+
"DROP_DOWN",
|
|
6790
|
+
"RADIO_BUTTON",
|
|
6791
|
+
"STATUS",
|
|
6792
|
+
"DATE",
|
|
6793
|
+
"TIME",
|
|
6794
|
+
"DATETIME",
|
|
6795
|
+
"CREATED_TIME",
|
|
6796
|
+
"UPDATED_TIME"
|
|
6797
|
+
]);
|
|
6798
|
+
function aggregateSortKind(info) {
|
|
6799
|
+
if (info.sortKind !== void 0) return info.sortKind;
|
|
6800
|
+
if (info.fieldType === "NUMBER" || info.fieldType === "RECORD_NUMBER") return "number";
|
|
6801
|
+
return AGGREGATE_STRING_FIELD_TYPES.has(info.fieldType) ? "string" : void 0;
|
|
6802
|
+
}
|
|
6803
|
+
async function loadAggregateSortKindResolver(stmt, client, cacheContext) {
|
|
6804
|
+
const refs = collectSelectAggregateSortRefs(stmt.columns);
|
|
6805
|
+
if (refs.length === 0) return void 0;
|
|
6806
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
6807
|
+
const physicalTables = physicalSelectTables(stmt);
|
|
6808
|
+
for (const ref of refs) {
|
|
6809
|
+
if (ref.tableAlias !== null) {
|
|
6810
|
+
if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
6811
|
+
appIds.add(stmt.from.appId);
|
|
6812
|
+
continue;
|
|
6813
|
+
}
|
|
6814
|
+
const table = findTableForAlias(stmt, ref.tableAlias);
|
|
6815
|
+
if (table && table.cteName === null) appIds.add(table.appId);
|
|
6816
|
+
} else if (stmt.joins.length === 0) {
|
|
6817
|
+
if (stmt.from.cteName === null) appIds.add(stmt.from.appId);
|
|
6818
|
+
} else {
|
|
6819
|
+
if ([stmt.from, ...stmt.joins.map((join2) => join2.table)].some((table) => table.cteName !== null)) continue;
|
|
6820
|
+
for (const table of physicalTables) appIds.add(table.appId);
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
if (appIds.size === 0) return void 0;
|
|
6824
|
+
const fieldInfosByApp = new Map(
|
|
6825
|
+
await Promise.all([...appIds].map(async (appId) => {
|
|
6826
|
+
const infos = await getFieldsCached(appId, client, cacheContext);
|
|
6827
|
+
return [appId, new Map(infos.map((info) => [info.code, info]))];
|
|
6828
|
+
}))
|
|
6829
|
+
);
|
|
6830
|
+
const tables = [stmt.from, ...stmt.joins.map((join2) => join2.table)];
|
|
6831
|
+
return (ref) => {
|
|
6832
|
+
let info;
|
|
6833
|
+
if (ref.tableAlias !== null) {
|
|
6834
|
+
if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
6835
|
+
info = fieldInfosByApp.get(stmt.from.appId)?.get(ref.field);
|
|
6836
|
+
} else {
|
|
6837
|
+
const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
|
|
6838
|
+
if (!table || table.cteName !== null) return void 0;
|
|
6839
|
+
info = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
|
|
6840
|
+
}
|
|
6841
|
+
} else if (stmt.joins.length === 0) {
|
|
6842
|
+
if (stmt.from.cteName !== null) return void 0;
|
|
6843
|
+
info = fieldInfosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
|
|
6844
|
+
} else {
|
|
6845
|
+
if (tables.some((table) => table.cteName !== null)) return void 0;
|
|
6846
|
+
const matches = physicalTables.map((table) => fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field))).filter((candidate) => candidate !== void 0);
|
|
6847
|
+
if (matches.length !== 1) return void 0;
|
|
6848
|
+
info = matches[0];
|
|
6849
|
+
}
|
|
6850
|
+
return info ? aggregateSortKind(info) : void 0;
|
|
6851
|
+
};
|
|
6852
|
+
}
|
|
6314
6853
|
function fieldCodeForTypeLookup(table, field) {
|
|
6315
6854
|
if (table.subtableCode && field.startsWith("_p.")) return field.slice(3);
|
|
6316
6855
|
return field;
|
|
@@ -6356,9 +6895,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
6356
6895
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
6357
6896
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
6358
6897
|
]);
|
|
6359
|
-
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
6898
|
+
const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
|
|
6360
6899
|
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
6361
|
-
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
6900
|
+
loadTypedInFieldTypes(stmt, client, cacheContext),
|
|
6901
|
+
loadAggregateSortKindResolver(stmt, client, cacheContext)
|
|
6362
6902
|
]);
|
|
6363
6903
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
6364
6904
|
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
@@ -6446,6 +6986,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
6446
6986
|
sortKinds,
|
|
6447
6987
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
6448
6988
|
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
6989
|
+
aggregateSortKindResolver,
|
|
6449
6990
|
appliedKlikes: pushdownPlan.appliedKlikes
|
|
6450
6991
|
});
|
|
6451
6992
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
@@ -6529,9 +7070,10 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6529
7070
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
|
|
6530
7071
|
resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
|
|
6531
7072
|
]);
|
|
6532
|
-
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
7073
|
+
const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
|
|
6533
7074
|
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
6534
|
-
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
7075
|
+
loadTypedInFieldTypes(stmt, client, cacheContext),
|
|
7076
|
+
loadAggregateSortKindResolver(stmt, client, cacheContext)
|
|
6535
7077
|
]);
|
|
6536
7078
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
6537
7079
|
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
@@ -6603,6 +7145,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
6603
7145
|
sortKinds,
|
|
6604
7146
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
6605
7147
|
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
7148
|
+
aggregateSortKindResolver,
|
|
6606
7149
|
appliedKlikes: pushdownPlan.appliedKlikes,
|
|
6607
7150
|
sourceColumns
|
|
6608
7151
|
});
|
|
@@ -6926,7 +7469,7 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
6926
7469
|
}
|
|
6927
7470
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
6928
7471
|
const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
6929
|
-
const
|
|
7472
|
+
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
6930
7473
|
if (USER_TYPES2.has(dstFieldType ?? "")) {
|
|
6931
7474
|
if (raw === "") return [];
|
|
6932
7475
|
try {
|
|
@@ -6938,7 +7481,7 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
6938
7481
|
}
|
|
6939
7482
|
return raw.split(",").map((c) => ({ code: c.trim() }));
|
|
6940
7483
|
}
|
|
6941
|
-
if (
|
|
7484
|
+
if (ARRAY_TYPES3.has(dstFieldType ?? "")) {
|
|
6942
7485
|
if (raw === "") return [];
|
|
6943
7486
|
try {
|
|
6944
7487
|
const parsed = JSON.parse(raw);
|
|
@@ -6949,6 +7492,396 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
6949
7492
|
}
|
|
6950
7493
|
return raw;
|
|
6951
7494
|
}
|
|
7495
|
+
var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
7496
|
+
"CALC",
|
|
7497
|
+
"RECORD_NUMBER",
|
|
7498
|
+
"CREATOR",
|
|
7499
|
+
"CREATED_TIME",
|
|
7500
|
+
"MODIFIER",
|
|
7501
|
+
"UPDATED_TIME",
|
|
7502
|
+
"STATUS",
|
|
7503
|
+
"STATUS_ASSIGNEE",
|
|
7504
|
+
"CATEGORY",
|
|
7505
|
+
"REFERENCE_TABLE"
|
|
7506
|
+
]);
|
|
7507
|
+
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7508
|
+
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
7509
|
+
}
|
|
7510
|
+
var RejectLimitExceededError = class extends Error {
|
|
7511
|
+
constructor(message, diagnostic) {
|
|
7512
|
+
super(`RejectLimitExceededError: ${message}`);
|
|
7513
|
+
this.diagnostic = diagnostic;
|
|
7514
|
+
this.name = "RejectLimitExceededError";
|
|
7515
|
+
}
|
|
7516
|
+
};
|
|
7517
|
+
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7518
|
+
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
7519
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
7520
|
+
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
7521
|
+
throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
7522
|
+
}
|
|
7523
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
7524
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
7525
|
+
const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
|
|
7526
|
+
for (const code of targetFields) {
|
|
7527
|
+
const info = infoByCode.get(code);
|
|
7528
|
+
if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
7529
|
+
if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
|
|
7530
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
7534
|
+
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
7535
|
+
candidates,
|
|
7536
|
+
operation,
|
|
7537
|
+
payloadFields,
|
|
7538
|
+
targetFields,
|
|
7539
|
+
fieldInfos,
|
|
7540
|
+
statementNumber
|
|
7541
|
+
);
|
|
7542
|
+
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
7543
|
+
const result = {
|
|
7544
|
+
type: "VALIDATION",
|
|
7545
|
+
operation,
|
|
7546
|
+
validatedRows: candidates.length,
|
|
7547
|
+
validRows: candidates.length - invalidRows,
|
|
7548
|
+
invalidRows,
|
|
7549
|
+
errorCount: errors.length,
|
|
7550
|
+
columns,
|
|
7551
|
+
errors,
|
|
7552
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : stmt.onErrorSkip && stmt.errorTable ? { errTable: stmt.errorTable } : {}
|
|
7553
|
+
};
|
|
7554
|
+
return { result, candidates, invalidRowNumbers };
|
|
7555
|
+
}
|
|
7556
|
+
async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7557
|
+
const prepared = await prepareDmlValidation(
|
|
7558
|
+
stmt,
|
|
7559
|
+
client,
|
|
7560
|
+
options,
|
|
7561
|
+
cacheContext,
|
|
7562
|
+
tempTables,
|
|
7563
|
+
statementNumber
|
|
7564
|
+
);
|
|
7565
|
+
const errTable = stmt.errorTable;
|
|
7566
|
+
if (!errTable) throw new Error("ArgumentError: ON ERROR SKIP requires INTO #error_table.");
|
|
7567
|
+
appendValidationErrors(
|
|
7568
|
+
tempTables,
|
|
7569
|
+
errTable,
|
|
7570
|
+
prepared.result.columns,
|
|
7571
|
+
prepared.result.errors,
|
|
7572
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
7573
|
+
);
|
|
7574
|
+
const rejectLimit = stmt.rejectLimit ?? null;
|
|
7575
|
+
if (rejectLimit !== null && prepared.result.invalidRows > rejectLimit) {
|
|
7576
|
+
throw new RejectLimitExceededError(
|
|
7577
|
+
`rejected rows (${prepared.result.invalidRows}) exceed REJECT LIMIT (${rejectLimit}).`,
|
|
7578
|
+
prepared.result
|
|
7579
|
+
);
|
|
7580
|
+
}
|
|
7581
|
+
const valid = prepared.candidates.filter((candidate) => !prepared.invalidRowNumbers.has(candidate.rowNumber));
|
|
7582
|
+
if (options.confirm) {
|
|
7583
|
+
const operation = stmt.type.startsWith("INSERT") ? "INSERT" : "UPDATE";
|
|
7584
|
+
const ok = await options.confirm(valid.length, operation);
|
|
7585
|
+
if (!ok) throw new OperationCancelledError(operation, valid.length);
|
|
7586
|
+
}
|
|
7587
|
+
const common = {
|
|
7588
|
+
affectedRows: valid.length,
|
|
7589
|
+
skippedRows: prepared.result.invalidRows,
|
|
7590
|
+
rejectLimit,
|
|
7591
|
+
errTable
|
|
7592
|
+
};
|
|
7593
|
+
if (stmt.type === "INSERT" || stmt.type === "INSERT_SELECT") {
|
|
7594
|
+
const createdIds = [];
|
|
7595
|
+
for (let i = 0; i < valid.length; i += 100) {
|
|
7596
|
+
const response = await client.postRecords({ app: stmt.appId, records: valid.slice(i, i + 100).map((c) => c.record) });
|
|
7597
|
+
createdIds.push(response.ids);
|
|
7598
|
+
}
|
|
7599
|
+
return { type: "INSERT", createdIds, insertedCount: createdIds.flat().length, ...common };
|
|
7600
|
+
}
|
|
7601
|
+
if (stmt.type === "UPDATE") {
|
|
7602
|
+
const updates2 = valid.map((candidate) => {
|
|
7603
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPDATE candidate has no targetId.");
|
|
7604
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
7605
|
+
});
|
|
7606
|
+
for (let i = 0; i < updates2.length; i += 100) {
|
|
7607
|
+
await client.putRecords({ app: stmt.appId, records: updates2.slice(i, i + 100) });
|
|
7608
|
+
}
|
|
7609
|
+
return { type: "UPDATE", updatedCount: updates2.length, ...common };
|
|
7610
|
+
}
|
|
7611
|
+
const inserts = valid.filter((candidate) => candidate.mode === "create");
|
|
7612
|
+
const updates = valid.filter((candidate) => candidate.mode === "update").map((candidate) => {
|
|
7613
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPSERT candidate has no targetId.");
|
|
7614
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
7615
|
+
});
|
|
7616
|
+
let insertedCount = 0;
|
|
7617
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
7618
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map((c) => c.record) });
|
|
7619
|
+
insertedCount += response.ids.length;
|
|
7620
|
+
}
|
|
7621
|
+
for (let i = 0; i < updates.length; i += 100) {
|
|
7622
|
+
await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
7623
|
+
}
|
|
7624
|
+
return { type: "UPSERT", insertedCount, updatedCount: updates.length, ...common };
|
|
7625
|
+
}
|
|
7626
|
+
async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
|
|
7627
|
+
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
7628
|
+
let rows;
|
|
7629
|
+
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
7630
|
+
rows = stmt.values.map((row) => row.map(
|
|
7631
|
+
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
7632
|
+
));
|
|
7633
|
+
} else {
|
|
7634
|
+
const selectResult = tempTables && tempTables.size > 0 ? await executeQueryWithCte(stmt.select, client, { ...options, onLimitReached: "error" }, tempTables, cacheContext) : await executeSelect(stmt.select, client, { ...options, onLimitReached: "error" }, cacheContext);
|
|
7635
|
+
if (selectResult.columns.length !== stmt.fields.length) {
|
|
7636
|
+
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`);
|
|
7637
|
+
}
|
|
7638
|
+
rows = selectResult.rows.map((row) => selectResult.columns.map((column) => row[column] ?? ""));
|
|
7639
|
+
}
|
|
7640
|
+
const candidates = rows.map((values, index) => ({
|
|
7641
|
+
rowNumber: index + 1,
|
|
7642
|
+
operation,
|
|
7643
|
+
mode: "create",
|
|
7644
|
+
payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
|
|
7645
|
+
preErrors: [],
|
|
7646
|
+
record: {}
|
|
7647
|
+
}));
|
|
7648
|
+
if (stmt.type !== "UPSERT" && stmt.type !== "UPSERT_SELECT") return candidates;
|
|
7649
|
+
for (const key of stmt.keyFields) {
|
|
7650
|
+
if (!stmt.fields.includes(key)) 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`);
|
|
7651
|
+
}
|
|
7652
|
+
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
7653
|
+
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
7654
|
+
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
7655
|
+
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
7656
|
+
const keyCounts = /* @__PURE__ */ new Map();
|
|
7657
|
+
for (const parts of rowKeys) {
|
|
7658
|
+
const key = upsertNormalizedKey(parts, numeric);
|
|
7659
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
7660
|
+
}
|
|
7661
|
+
candidates.forEach((candidate, index) => {
|
|
7662
|
+
const parts = rowKeys[index];
|
|
7663
|
+
const targetId = lookupUpsertTarget(targets, parts);
|
|
7664
|
+
candidate.mode = targetId === void 0 ? "create" : "update";
|
|
7665
|
+
if (targetId !== void 0) candidate.targetId = targetId;
|
|
7666
|
+
stmt.keyFields.forEach((key, keyIndex) => {
|
|
7667
|
+
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` });
|
|
7668
|
+
});
|
|
7669
|
+
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
7670
|
+
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" });
|
|
7671
|
+
}
|
|
7672
|
+
});
|
|
7673
|
+
return candidates;
|
|
7674
|
+
}
|
|
7675
|
+
async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
|
|
7676
|
+
if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
7677
|
+
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
7678
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7679
|
+
let records;
|
|
7680
|
+
if (hasArithAssignment(stmt)) {
|
|
7681
|
+
const getParams = updateToGetQueryForArith(stmt);
|
|
7682
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
|
|
7683
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
7684
|
+
parallel: options.fetchParallel ?? 1,
|
|
7685
|
+
onLimit: "error"
|
|
7686
|
+
});
|
|
7687
|
+
records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
|
|
7688
|
+
} else {
|
|
7689
|
+
const getParams = updateToGetQuery(stmt);
|
|
7690
|
+
const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
|
|
7691
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
7692
|
+
parallel: options.fetchParallel ?? 1
|
|
7693
|
+
});
|
|
7694
|
+
records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
|
|
7695
|
+
}
|
|
7696
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
7697
|
+
rowNumber: index + 1,
|
|
7698
|
+
operation: "UPDATE",
|
|
7699
|
+
mode: "update",
|
|
7700
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
7701
|
+
preErrors: [],
|
|
7702
|
+
record: entry.record,
|
|
7703
|
+
targetId: entry.id
|
|
7704
|
+
}));
|
|
7705
|
+
}
|
|
7706
|
+
async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
|
|
7707
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
7708
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7709
|
+
const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
|
|
7710
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
7711
|
+
rowNumber: index + 1,
|
|
7712
|
+
operation: "UPDATE",
|
|
7713
|
+
mode: "update",
|
|
7714
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
7715
|
+
preErrors: [],
|
|
7716
|
+
record: entry.record,
|
|
7717
|
+
targetId: entry.id
|
|
7718
|
+
}));
|
|
7719
|
+
}
|
|
7720
|
+
var UPDATE_FROM_KEY_CHUNK_SIZE = UPSERT_IN_CHUNK_SIZE;
|
|
7721
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
7722
|
+
"CHECK_BOX",
|
|
7723
|
+
"MULTI_SELECT",
|
|
7724
|
+
"USER_SELECT",
|
|
7725
|
+
"ORGANIZATION_SELECT",
|
|
7726
|
+
"GROUP_SELECT",
|
|
7727
|
+
"FILE"
|
|
7728
|
+
]);
|
|
7729
|
+
async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
|
|
7730
|
+
const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
|
|
7731
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
7732
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
7733
|
+
const sourceRows = await loadUpdateFromSourceRows(
|
|
7734
|
+
from,
|
|
7735
|
+
requiredSourceFields,
|
|
7736
|
+
sourceFields,
|
|
7737
|
+
client,
|
|
7738
|
+
options,
|
|
7739
|
+
cacheContext,
|
|
7740
|
+
tempTables
|
|
7741
|
+
);
|
|
7742
|
+
const sourceByKey = /* @__PURE__ */ new Map();
|
|
7743
|
+
for (const row of sourceRows) {
|
|
7744
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
7745
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7746
|
+
}
|
|
7747
|
+
const key = normalizeUpdateFromJoinKey(row[from.joinKeyField], joinKind, "source");
|
|
7748
|
+
if (sourceByKey.has(key)) {
|
|
7749
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
7750
|
+
}
|
|
7751
|
+
sourceByKey.set(key, row);
|
|
7752
|
+
}
|
|
7753
|
+
if (sourceByKey.size === 0) return [];
|
|
7754
|
+
const maxRecords = options.maxRecords ?? 1e4;
|
|
7755
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
7756
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
7757
|
+
const targetRecords = [];
|
|
7758
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
7759
|
+
let fetchedTargetCount = 0;
|
|
7760
|
+
for (const keys of splitChunks([...sourceByKey.keys()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
7761
|
+
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
7762
|
+
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
7763
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7764
|
+
client.getRecords,
|
|
7765
|
+
stmt.appId,
|
|
7766
|
+
query,
|
|
7767
|
+
targetFields,
|
|
7768
|
+
{ maxRecords, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7769
|
+
);
|
|
7770
|
+
fetchedTargetCount += resolved.records.length;
|
|
7771
|
+
if (fetchedTargetCount > maxRecords) {
|
|
7772
|
+
throw new FetchAllLimitError(
|
|
7773
|
+
`\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650\uFF08${maxRecords} \u4EF6\uFF09\u3092\u8D85\u3048\u307E\u3057\u305F\u3002WHERE \u53E5\u3067\u7D5E\u308A\u8FBC\u3080\u304B\u3001maxRecords \u3092\u5F15\u304D\u4E0A\u3052\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
7774
|
+
);
|
|
7775
|
+
}
|
|
7776
|
+
for (const record of resolved.records) {
|
|
7777
|
+
const id = record["$id"]?.value;
|
|
7778
|
+
if (typeof id !== "string" || id === "") {
|
|
7779
|
+
throw new Error("ArgumentError: UPDATE ... FROM target record does not contain a valid $id.");
|
|
7780
|
+
}
|
|
7781
|
+
if (seenTargetIds.has(id)) continue;
|
|
7782
|
+
seenTargetIds.add(id);
|
|
7783
|
+
targetRecords.push(record);
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
const matched = [];
|
|
7787
|
+
for (const target of targetRecords) {
|
|
7788
|
+
const raw = target[from.targetJoinField]?.value;
|
|
7789
|
+
const key = normalizeUpdateFromJoinKey(raw, joinKind, "target");
|
|
7790
|
+
if (key === null) continue;
|
|
7791
|
+
const source = sourceByKey.get(key);
|
|
7792
|
+
if (source !== void 0) matched.push({ target, source });
|
|
7793
|
+
}
|
|
7794
|
+
return matched;
|
|
7795
|
+
}
|
|
7796
|
+
async function resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext) {
|
|
7797
|
+
if (from.targetJoinField === "$id") return "id";
|
|
7798
|
+
const info = (await getFieldsCached(stmt.appId, client, cacheContext)).find((field) => field.code === from.targetJoinField);
|
|
7799
|
+
if (!info) {
|
|
7800
|
+
throw new Error(`ArgumentError: UPDATE ... FROM target column ${from.targetJoinField} does not exist.`);
|
|
7801
|
+
}
|
|
7802
|
+
if (info.inSubtable || info.writable === false || info.fieldType !== "SINGLE_LINE_TEXT" && info.fieldType !== "NUMBER") {
|
|
7803
|
+
throw new Error(
|
|
7804
|
+
`ArgumentError: UPDATE ... FROM does not support target join field type ${info.fieldType} (${from.targetJoinField}).`
|
|
7805
|
+
);
|
|
7806
|
+
}
|
|
7807
|
+
return info.fieldType === "NUMBER" ? "number" : "string";
|
|
7808
|
+
}
|
|
7809
|
+
async function loadUpdateFromSourceRows(from, requiredSourceFields, sourceValueFields, client, options, cacheContext, tempTables) {
|
|
7810
|
+
if (from.cteName !== null) {
|
|
7811
|
+
const table = tempTables?.get(from.cteName);
|
|
7812
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
7813
|
+
for (const field of requiredSourceFields) {
|
|
7814
|
+
if (!table.columns.includes(field)) {
|
|
7815
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
return table.rows;
|
|
7819
|
+
}
|
|
7820
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
7821
|
+
const joinType = from.joinKeyField === "$id" ? "RECORD_NUMBER" : sourceTypes.get(from.joinKeyField);
|
|
7822
|
+
if (joinType === void 0) {
|
|
7823
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7824
|
+
}
|
|
7825
|
+
if (from.joinKeyField !== "$id" && joinType !== "SINGLE_LINE_TEXT" && joinType !== "NUMBER") {
|
|
7826
|
+
throw new Error(
|
|
7827
|
+
`ArgumentError: UPDATE ... FROM does not support source join field type ${joinType} (${from.joinKeyField}).`
|
|
7828
|
+
);
|
|
7829
|
+
}
|
|
7830
|
+
for (const field of sourceValueFields) {
|
|
7831
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
7832
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7833
|
+
}
|
|
7834
|
+
const type = field === "$id" ? "RECORD_NUMBER" : sourceTypes.get(field);
|
|
7835
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
7836
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7840
|
+
client.getRecords,
|
|
7841
|
+
from.appId,
|
|
7842
|
+
"",
|
|
7843
|
+
requiredSourceFields,
|
|
7844
|
+
{ maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7845
|
+
);
|
|
7846
|
+
return resolved.records.map((record) => flatten(record, null));
|
|
7847
|
+
}
|
|
7848
|
+
function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
7849
|
+
if (typeof raw !== "string") {
|
|
7850
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a scalar string: ${String(raw)}`);
|
|
7851
|
+
}
|
|
7852
|
+
if (kind === "string") {
|
|
7853
|
+
if (raw === "") {
|
|
7854
|
+
if (side === "target") return null;
|
|
7855
|
+
throw new Error("ArgumentError: UPDATE ... FROM source key must not be empty.");
|
|
7856
|
+
}
|
|
7857
|
+
return raw;
|
|
7858
|
+
}
|
|
7859
|
+
if (kind === "number" && side === "target" && raw === "") return null;
|
|
7860
|
+
if (kind === "id") {
|
|
7861
|
+
const text2 = raw.trim();
|
|
7862
|
+
const id = Number(text2);
|
|
7863
|
+
if (text2 === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
7864
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
7865
|
+
}
|
|
7866
|
+
return String(id);
|
|
7867
|
+
}
|
|
7868
|
+
const text = raw.trim();
|
|
7869
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
|
|
7870
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
7871
|
+
}
|
|
7872
|
+
let unsigned = text;
|
|
7873
|
+
let negative = false;
|
|
7874
|
+
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
7875
|
+
negative = unsigned[0] === "-";
|
|
7876
|
+
unsigned = unsigned.slice(1);
|
|
7877
|
+
}
|
|
7878
|
+
let [whole, fraction = ""] = unsigned.split(".");
|
|
7879
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
7880
|
+
fraction = fraction.replace(/0+$/, "");
|
|
7881
|
+
const zero = /^0*$/.test(whole) && fraction === "";
|
|
7882
|
+
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
7883
|
+
return negative && !zero ? `-${canonical}` : canonical;
|
|
7884
|
+
}
|
|
6952
7885
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
6953
7886
|
if (stmt.subtableCode) {
|
|
6954
7887
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
@@ -7048,98 +7981,20 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
7048
7981
|
}
|
|
7049
7982
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
7050
7983
|
}
|
|
7051
|
-
var UPDATE_FROM_ID_CHUNK_SIZE = 50;
|
|
7052
|
-
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
7053
|
-
"CHECK_BOX",
|
|
7054
|
-
"MULTI_SELECT",
|
|
7055
|
-
"USER_SELECT",
|
|
7056
|
-
"ORGANIZATION_SELECT",
|
|
7057
|
-
"GROUP_SELECT",
|
|
7058
|
-
"FILE"
|
|
7059
|
-
]);
|
|
7060
7984
|
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
7061
|
-
const
|
|
7062
|
-
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
7063
|
-
let sourceRows;
|
|
7064
|
-
if (from.cteName !== null) {
|
|
7065
|
-
const table = tempTables?.get(from.cteName);
|
|
7066
|
-
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
7067
|
-
for (const field of requiredSourceFields) {
|
|
7068
|
-
if (!table.columns.includes(field)) {
|
|
7069
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7070
|
-
}
|
|
7071
|
-
}
|
|
7072
|
-
sourceRows = table.rows;
|
|
7073
|
-
} else {
|
|
7074
|
-
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
7075
|
-
for (const field of requiredSourceFields) {
|
|
7076
|
-
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
7077
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7078
|
-
}
|
|
7079
|
-
const type = sourceTypes.get(field);
|
|
7080
|
-
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
7081
|
-
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
7082
|
-
}
|
|
7083
|
-
}
|
|
7084
|
-
const maxRecords = options.maxRecords ?? 1e4;
|
|
7085
|
-
const resolved = await fetchRecordsForSharedPlan(
|
|
7086
|
-
client.getRecords,
|
|
7087
|
-
from.appId,
|
|
7088
|
-
"",
|
|
7089
|
-
requiredSourceFields,
|
|
7090
|
-
{ maxRecords, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7091
|
-
);
|
|
7092
|
-
sourceRows = resolved.records.map((record) => flatten(record, null));
|
|
7093
|
-
}
|
|
7094
|
-
const sourceById = /* @__PURE__ */ new Map();
|
|
7095
|
-
for (const row of sourceRows) {
|
|
7096
|
-
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
7097
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7098
|
-
}
|
|
7099
|
-
const raw = row[from.joinKeyField];
|
|
7100
|
-
const text = typeof raw === "string" ? raw.trim() : "";
|
|
7101
|
-
const id = Number(text);
|
|
7102
|
-
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
7103
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source key must be a positive safe integer: ${String(raw)}`);
|
|
7104
|
-
}
|
|
7105
|
-
if (sourceById.has(id)) {
|
|
7106
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for target $id ${id}.`);
|
|
7107
|
-
}
|
|
7108
|
-
sourceById.set(id, row);
|
|
7109
|
-
}
|
|
7110
|
-
const targetIds = [...sourceById.keys()];
|
|
7111
|
-
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
7112
|
-
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
7113
|
-
const targetRecords = [];
|
|
7114
|
-
for (const ids of splitChunks(targetIds, UPDATE_FROM_ID_CHUNK_SIZE)) {
|
|
7115
|
-
const idQuery = `$id in (${ids.map((id) => sqlQuote(String(id))).join(",")})`;
|
|
7116
|
-
const query = filterQuery ? `(${idQuery}) and (${filterQuery})` : idQuery;
|
|
7117
|
-
const resolved = await fetchRecordsForSharedPlan(
|
|
7118
|
-
client.getRecords,
|
|
7119
|
-
stmt.appId,
|
|
7120
|
-
query,
|
|
7121
|
-
targetFields,
|
|
7122
|
-
{ maxRecords: Math.max(ids.length, 1), parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7123
|
-
);
|
|
7124
|
-
targetRecords.push(...resolved.records);
|
|
7125
|
-
}
|
|
7985
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
7126
7986
|
if (options.confirm) {
|
|
7127
|
-
const ok = await options.confirm(
|
|
7128
|
-
if (!ok) throw new OperationCancelledError("UPDATE",
|
|
7987
|
+
const ok = await options.confirm(matched.length, "UPDATE");
|
|
7988
|
+
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
7129
7989
|
}
|
|
7130
7990
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7131
|
-
const matched = targetRecords.map((target) => {
|
|
7132
|
-
const id = Number(target["$id"]?.value);
|
|
7133
|
-
const source = sourceById.get(id);
|
|
7134
|
-
if (!source) throw new Error(`ArgumentError: UPDATE ... FROM could not resolve source row for target $id ${id}.`);
|
|
7135
|
-
return { target, source };
|
|
7136
|
-
});
|
|
7137
7991
|
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
7138
7992
|
for (const batch of batches) await client.putRecords(batch);
|
|
7139
|
-
return { type: "UPDATE", updatedCount:
|
|
7993
|
+
return { type: "UPDATE", updatedCount: matched.length };
|
|
7140
7994
|
}
|
|
7141
7995
|
function collectUpdateFromTargetFields(stmt) {
|
|
7142
7996
|
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
7997
|
+
if (stmt.from) fields.add(stmt.from.targetJoinField);
|
|
7143
7998
|
const visit = (node) => {
|
|
7144
7999
|
if (Array.isArray(node)) {
|
|
7145
8000
|
node.forEach(visit);
|
|
@@ -8085,7 +8940,7 @@ function buildUpdatePlan(stmt, label) {
|
|
|
8085
8940
|
if (stmt.from) {
|
|
8086
8941
|
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
8087
8942
|
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
8088
|
-
lines.push(` join: APP${stmt.appId}.$
|
|
8943
|
+
lines.push(` join: APP${stmt.appId}.${stmt.from.targetJoinField} = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
8089
8944
|
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
8090
8945
|
} else {
|
|
8091
8946
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
@@ -8326,12 +9181,32 @@ function isSubtableRow(v) {
|
|
|
8326
9181
|
// src/output/batchEnvelope.ts
|
|
8327
9182
|
function toMutationSummary(result) {
|
|
8328
9183
|
if (result.type === "INSERT") {
|
|
8329
|
-
return {
|
|
9184
|
+
return {
|
|
9185
|
+
insertedCount: result.insertedCount,
|
|
9186
|
+
createdIds: result.createdIds,
|
|
9187
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9188
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9189
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9190
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9191
|
+
};
|
|
8330
9192
|
}
|
|
8331
|
-
if (result.type === "UPDATE") return {
|
|
9193
|
+
if (result.type === "UPDATE") return {
|
|
9194
|
+
updatedCount: result.updatedCount,
|
|
9195
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9196
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9197
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9198
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9199
|
+
};
|
|
8332
9200
|
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
8333
9201
|
if (result.type === "UPSERT") {
|
|
8334
|
-
return {
|
|
9202
|
+
return {
|
|
9203
|
+
insertedCount: result.insertedCount,
|
|
9204
|
+
updatedCount: result.updatedCount,
|
|
9205
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9206
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9207
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9208
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9209
|
+
};
|
|
8335
9210
|
}
|
|
8336
9211
|
return { reorderedParentCount: result.reorderedParentCount };
|
|
8337
9212
|
}
|
|
@@ -8358,11 +9233,31 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
8358
9233
|
}
|
|
8359
9234
|
entry.resultIndex = results.length;
|
|
8360
9235
|
results.push({
|
|
9236
|
+
type: "SELECT",
|
|
8361
9237
|
columns: s.result.columns,
|
|
8362
9238
|
rows: s.result.rows,
|
|
8363
9239
|
rowCount: s.result.rowCount,
|
|
8364
9240
|
warnings: s.result.warnings ?? []
|
|
8365
9241
|
});
|
|
9242
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
9243
|
+
totalRows += s.result.errorCount;
|
|
9244
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
9245
|
+
throw new Error(`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`);
|
|
9246
|
+
}
|
|
9247
|
+
entry.resultIndex = results.length;
|
|
9248
|
+
results.push({
|
|
9249
|
+
type: "VALIDATION",
|
|
9250
|
+
columns: s.result.columns,
|
|
9251
|
+
rows: s.result.errors,
|
|
9252
|
+
rowCount: s.result.errorCount,
|
|
9253
|
+
warnings: [],
|
|
9254
|
+
operation: s.result.operation,
|
|
9255
|
+
validatedRows: s.result.validatedRows,
|
|
9256
|
+
validRows: s.result.validRows,
|
|
9257
|
+
invalidRows: s.result.invalidRows,
|
|
9258
|
+
errorCount: s.result.errorCount,
|
|
9259
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
9260
|
+
});
|
|
8366
9261
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
8367
9262
|
Object.assign(entry, toMutationSummary(s.result));
|
|
8368
9263
|
}
|
|
@@ -8643,6 +9538,9 @@ function clampInt(v, min, max) {
|
|
|
8643
9538
|
|
|
8644
9539
|
// src/core/formFieldInfo.ts
|
|
8645
9540
|
function flattenFormFieldProperties(properties) {
|
|
9541
|
+
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
9542
|
+
}
|
|
9543
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
8646
9544
|
const out = [];
|
|
8647
9545
|
for (const field of Object.values(properties)) {
|
|
8648
9546
|
out.push({
|
|
@@ -8650,12 +9548,49 @@ function flattenFormFieldProperties(properties) {
|
|
|
8650
9548
|
label: field.label,
|
|
8651
9549
|
fieldType: field.type,
|
|
8652
9550
|
optionOrder: toOptionOrderMap(field.options),
|
|
8653
|
-
sortKind: detectSortKind(field.type, field.format)
|
|
9551
|
+
sortKind: detectSortKind(field.type, field.format),
|
|
9552
|
+
required: field.required,
|
|
9553
|
+
minValue: normalizeConstraintValue(field.minValue),
|
|
9554
|
+
maxValue: normalizeConstraintValue(field.maxValue),
|
|
9555
|
+
minLength: normalizeConstraintValue(field.minLength),
|
|
9556
|
+
maxLength: normalizeConstraintValue(field.maxLength),
|
|
9557
|
+
defaultValue: field.defaultValue,
|
|
9558
|
+
inSubtable,
|
|
9559
|
+
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
8654
9560
|
});
|
|
8655
|
-
if (field.fields) out.push(...
|
|
9561
|
+
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
|
|
8656
9562
|
}
|
|
8657
9563
|
return out;
|
|
8658
9564
|
}
|
|
9565
|
+
var NON_WRITABLE_FIELD_TYPES2 = /* @__PURE__ */ new Set([
|
|
9566
|
+
"CALC",
|
|
9567
|
+
"RECORD_NUMBER",
|
|
9568
|
+
"CREATOR",
|
|
9569
|
+
"CREATED_TIME",
|
|
9570
|
+
"MODIFIER",
|
|
9571
|
+
"UPDATED_TIME",
|
|
9572
|
+
"STATUS",
|
|
9573
|
+
"STATUS_ASSIGNEE",
|
|
9574
|
+
"CATEGORY",
|
|
9575
|
+
"REFERENCE_TABLE",
|
|
9576
|
+
"SUBTABLE"
|
|
9577
|
+
]);
|
|
9578
|
+
function collectLookupCopyFields(properties) {
|
|
9579
|
+
const result = /* @__PURE__ */ new Set();
|
|
9580
|
+
const visit = (fields) => {
|
|
9581
|
+
for (const field of Object.values(fields)) {
|
|
9582
|
+
for (const mapping of field.lookup?.fieldMappings ?? []) {
|
|
9583
|
+
if (mapping.field) result.add(mapping.field);
|
|
9584
|
+
}
|
|
9585
|
+
if (field.fields) visit(field.fields);
|
|
9586
|
+
}
|
|
9587
|
+
};
|
|
9588
|
+
visit(properties);
|
|
9589
|
+
return result;
|
|
9590
|
+
}
|
|
9591
|
+
function normalizeConstraintValue(value) {
|
|
9592
|
+
return value == null || value === "" ? void 0 : value;
|
|
9593
|
+
}
|
|
8659
9594
|
function toOptionOrderMap(options) {
|
|
8660
9595
|
if (!options || typeof options !== "object") return void 0;
|
|
8661
9596
|
const order = {};
|
|
@@ -9788,6 +10723,11 @@ function buildAssertOutput(result, format, pretty) {
|
|
|
9788
10723
|
if (format === "jsonl") return JSON.stringify(payload);
|
|
9789
10724
|
return `assertion ok: ${result.condition}`;
|
|
9790
10725
|
}
|
|
10726
|
+
function buildValidationOutput(result, format, noHeader, pretty, displayOptions) {
|
|
10727
|
+
if (format === "json") return JSON.stringify({ ok: true, ...result, metrics: void 0 }, null, pretty ? 2 : 0);
|
|
10728
|
+
if (format === "jsonl") return result.errors.map((row) => JSON.stringify(row)).join("\n");
|
|
10729
|
+
return buildOutput({ type: "SELECT", columns: result.columns, rows: result.errors, rowCount: result.errorCount }, format, noHeader, pretty, displayOptions);
|
|
10730
|
+
}
|
|
9791
10731
|
function buildMutationOutput(result, format, noHeader, pretty) {
|
|
9792
10732
|
const row = { type: result.type };
|
|
9793
10733
|
if (result.type === "INSERT") {
|
|
@@ -9802,6 +10742,12 @@ function buildMutationOutput(result, format, noHeader, pretty) {
|
|
|
9802
10742
|
} else if (result.type === "REORDER") {
|
|
9803
10743
|
row.reorderedParentCount = result.reorderedParentCount;
|
|
9804
10744
|
}
|
|
10745
|
+
if (result.type === "INSERT" || result.type === "UPDATE" || result.type === "UPSERT") {
|
|
10746
|
+
if (result.affectedRows !== void 0) row.affectedRows = result.affectedRows;
|
|
10747
|
+
if (result.skippedRows !== void 0) row.skippedRows = result.skippedRows;
|
|
10748
|
+
if (result.rejectLimit !== void 0) row.rejectLimit = result.rejectLimit;
|
|
10749
|
+
if (result.errTable !== void 0) row.errTable = result.errTable;
|
|
10750
|
+
}
|
|
9805
10751
|
if (format === "json") return JSON.stringify(row, null, pretty ? 2 : 0);
|
|
9806
10752
|
if (format === "jsonl") return JSON.stringify(row);
|
|
9807
10753
|
const cols = Object.keys(row);
|
|
@@ -9912,6 +10858,14 @@ function buildBatchStatementSummary(s) {
|
|
|
9912
10858
|
else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
|
|
9913
10859
|
else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
|
|
9914
10860
|
else if (r.type === "REORDER") parts.push(`reordered=${r.reorderedParentCount}`);
|
|
10861
|
+
else if (r.type === "VALIDATION") parts.push(`validated=${r.validatedRows} valid=${r.validRows} invalid=${r.invalidRows} errors=${r.errorCount}`);
|
|
10862
|
+
if ((r.type === "INSERT" || r.type === "UPDATE" || r.type === "UPSERT") && r.skippedRows !== void 0) {
|
|
10863
|
+
parts.push(`affected=${r.affectedRows} skipped=${r.skippedRows} errTable=${r.errTable}`);
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
if (s.status === "error" && s.result?.type === "VALIDATION") {
|
|
10867
|
+
const r = s.result;
|
|
10868
|
+
parts.push(`validated=${r.validatedRows} valid=${r.validRows} invalid=${r.invalidRows} errors=${r.errorCount}`);
|
|
9915
10869
|
}
|
|
9916
10870
|
if (s.status === "error" && s.error) parts.push(s.error.message);
|
|
9917
10871
|
if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
|
|
@@ -9945,6 +10899,8 @@ function buildBatchResultsOutput(batch, opts) {
|
|
|
9945
10899
|
for (const s of batch.statements) {
|
|
9946
10900
|
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
9947
10901
|
outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
10902
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
10903
|
+
outputs.push(buildValidationOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
9948
10904
|
}
|
|
9949
10905
|
}
|
|
9950
10906
|
return outputs.join("\n\n");
|
|
@@ -10607,6 +11563,7 @@ async function run() {
|
|
|
10607
11563
|
let isBatchSql = false;
|
|
10608
11564
|
let batchContainsDml = false;
|
|
10609
11565
|
let batchAnalysis = null;
|
|
11566
|
+
let needsCompleteInput = false;
|
|
10610
11567
|
if (args.diagRecordId === null) {
|
|
10611
11568
|
sql = args.executeSql;
|
|
10612
11569
|
if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
|
|
@@ -10637,14 +11594,16 @@ async function run() {
|
|
|
10637
11594
|
batchAnalysis = analyzeBatch(statements);
|
|
10638
11595
|
isBatchSql = true;
|
|
10639
11596
|
batchContainsDml = batchAnalysis.containsDml;
|
|
11597
|
+
needsCompleteInput = batchAnalysis.requiresCompleteInput;
|
|
10640
11598
|
} else {
|
|
10641
11599
|
const stmt = parseSqlStatement(sql);
|
|
10642
11600
|
parsedStmt = stmt;
|
|
10643
11601
|
stmtType = getStatementType(stmt);
|
|
10644
|
-
isDmlStatement =
|
|
11602
|
+
isDmlStatement = writesKintone(stmt);
|
|
11603
|
+
needsCompleteInput = requiresCompleteInput(stmt);
|
|
10645
11604
|
hasWhere = hasWhereClause(stmt);
|
|
10646
11605
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
10647
|
-
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" ||
|
|
11606
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
|
|
10648
11607
|
if (!supported) {
|
|
10649
11608
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
10650
11609
|
`);
|
|
@@ -10688,10 +11647,10 @@ async function run() {
|
|
|
10688
11647
|
const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
|
|
10689
11648
|
const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
|
|
10690
11649
|
const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
|
|
10691
|
-
const dmlForcesOnLimitError =
|
|
11650
|
+
const dmlForcesOnLimitError = needsCompleteInput;
|
|
10692
11651
|
const effectiveOnLimit = dmlForcesOnLimitError ? "error" : onLimit;
|
|
10693
11652
|
if (dmlForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
|
|
10694
|
-
process.stderr.write("note: onLimit=truncate is ignored for DML (forced to error)\n");
|
|
11653
|
+
process.stderr.write(isDmlStatement || batchContainsDml ? "note: onLimit=truncate is ignored for DML (forced to error)\n" : "note: onLimit=truncate is ignored for VALIDATE ONLY (forced to error)\n");
|
|
10695
11654
|
}
|
|
10696
11655
|
if (format === "markdown" && noHeader) {
|
|
10697
11656
|
process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
|
|
@@ -11074,6 +12033,16 @@ query=${label}`);
|
|
|
11074
12033
|
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
11075
12034
|
`, "utf-8");
|
|
11076
12035
|
else if (output2) process.stdout.write(`${output2}
|
|
12036
|
+
`);
|
|
12037
|
+
return 0;
|
|
12038
|
+
}
|
|
12039
|
+
if (result.type === "VALIDATION") {
|
|
12040
|
+
const output2 = buildValidationOutput(result, format, noHeader, pretty, displayOptions);
|
|
12041
|
+
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
12042
|
+
`, "utf-8");
|
|
12043
|
+
else if (output2) process.stdout.write(`${output2}
|
|
12044
|
+
`);
|
|
12045
|
+
if (!quiet) process.stderr.write(`validated=${result.validatedRows} valid=${result.validRows} invalid=${result.invalidRows} errors=${result.errorCount}
|
|
11077
12046
|
`);
|
|
11078
12047
|
return 0;
|
|
11079
12048
|
}
|
|
@@ -11127,6 +12096,7 @@ if (isDirectCliRun()) {
|
|
|
11127
12096
|
buildBatchStatementSummary,
|
|
11128
12097
|
buildOutput,
|
|
11129
12098
|
buildReplExecArgv,
|
|
12099
|
+
buildValidationOutput,
|
|
11130
12100
|
extractAppIds,
|
|
11131
12101
|
normalizeAppKey,
|
|
11132
12102
|
normalizeSqlAppProfiles,
|