@rex0220/kintone-sql-tools 2.11.0 → 2.13.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/README.md +1 -1
- package/dist-cli/ksql.js +1180 -24
- package/dist-mcp/ksql-mcp.js +1191 -36
- 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");
|
|
@@ -2054,6 +2063,30 @@ var Parser = class {
|
|
|
2054
2063
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
2055
2064
|
this.expect("SET" /* SET */);
|
|
2056
2065
|
const assignments = this.parseAssignments();
|
|
2066
|
+
let from = null;
|
|
2067
|
+
if (this.consume("FROM" /* FROM */)) {
|
|
2068
|
+
const table = this.parseTableRef();
|
|
2069
|
+
if (table.subtableCode) {
|
|
2070
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
2071
|
+
}
|
|
2072
|
+
if (table.cteName !== null && !table.cteName.startsWith("#")) {
|
|
2073
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306F #temp \u307E\u305F\u306F APP<n> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08CTE \u306F\u975E\u5BFE\u5FDC\uFF09", this.prev());
|
|
2074
|
+
}
|
|
2075
|
+
if (!table.alias) {
|
|
2076
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
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
|
+
}
|
|
2081
|
+
from = {
|
|
2082
|
+
appId: table.appId,
|
|
2083
|
+
cteName: table.cteName,
|
|
2084
|
+
alias: table.alias,
|
|
2085
|
+
targetJoinField: "",
|
|
2086
|
+
joinKeyField: "",
|
|
2087
|
+
targetFilter: null
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2057
2090
|
const whereTok = this.peek();
|
|
2058
2091
|
if (!this.consume("WHERE" /* WHERE */)) {
|
|
2059
2092
|
throw new ParseError(
|
|
@@ -2062,7 +2095,191 @@ var Parser = class {
|
|
|
2062
2095
|
);
|
|
2063
2096
|
}
|
|
2064
2097
|
const where = this.parseWhereExpr();
|
|
2065
|
-
|
|
2098
|
+
if (from !== null) {
|
|
2099
|
+
if (subtableCode) {
|
|
2100
|
+
throw new ParseError("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE ... FROM \u306F\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093", whereTok);
|
|
2101
|
+
}
|
|
2102
|
+
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
2103
|
+
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
2104
|
+
from.targetJoinField = decomposed.targetJoinField;
|
|
2105
|
+
from.joinKeyField = decomposed.joinKeyField;
|
|
2106
|
+
from.targetFilter = decomposed.targetFilter;
|
|
2107
|
+
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
2108
|
+
throw new ParseError(
|
|
2109
|
+
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
2110
|
+
whereTok
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
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;
|
|
2173
|
+
}
|
|
2174
|
+
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
2175
|
+
for (const assignment of assignments) {
|
|
2176
|
+
if (assignment.value.type === "SOURCE_FIELD") {
|
|
2177
|
+
if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
2178
|
+
throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
2179
|
+
}
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (this.nodeContainsQualifiedField(assignment.value, sourceAlias)) {
|
|
2183
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217\u306F SET \u306E\u76F4\u63A5\u5024\u3068\u3057\u3066\u306E\u307F\u53C2\u7167\u3067\u304D\u307E\u3059", tok);
|
|
2184
|
+
}
|
|
2185
|
+
if (assignment.value.type === "SCALAR_SUBQUERY") {
|
|
2186
|
+
throw new ParseError("UPDATE ... FROM \u306E SET \u3067\u306F\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
2187
|
+
}
|
|
2188
|
+
if (this.nodeContainsAnyQualifier(assignment.value)) {
|
|
2189
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u5F0F\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044", tok);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
decomposeUpdateFromWhere(where, targetAppId, sourceAlias, tok) {
|
|
2194
|
+
const leaves = this.flattenTopLevelAnd(where);
|
|
2195
|
+
const joins = [];
|
|
2196
|
+
leaves.forEach((leaf, index) => {
|
|
2197
|
+
const matched = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
2198
|
+
if (matched !== null) joins.push({ index, ...matched });
|
|
2199
|
+
});
|
|
2200
|
+
if (joins.length !== 1) {
|
|
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);
|
|
2202
|
+
}
|
|
2203
|
+
const join2 = joins[0];
|
|
2204
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
2205
|
+
if (i !== join2.index && this.nodeContainsQualifiedField(leaves[i], sourceAlias)) {
|
|
2206
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u7D50\u5408\u7B49\u5024\u4EE5\u5916\u306E WHERE \u6761\u4EF6\u3067\u306F\u53C2\u7167\u3067\u304D\u307E\u305B\u3093", tok);
|
|
2207
|
+
}
|
|
2208
|
+
if (i !== join2.index && this.nodeContainsForeignQualifier(leaves[i], targetAppId)) {
|
|
2209
|
+
throw new ParseError(`UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30EB\u30BF\u306F APP${targetAppId} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3067\u304D\u307E\u3059`, tok);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
const filters = leaves.filter((_, index) => index !== join2.index);
|
|
2213
|
+
const targetFilter = filters.reduce(
|
|
2214
|
+
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
2215
|
+
null
|
|
2216
|
+
);
|
|
2217
|
+
return { targetJoinField: join2.targetField, joinKeyField: join2.sourceField, targetFilter };
|
|
2218
|
+
}
|
|
2219
|
+
flattenTopLevelAnd(expr) {
|
|
2220
|
+
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
2221
|
+
if (expr.type === "LOGICAL" && expr.op === "AND") {
|
|
2222
|
+
return [...this.flattenTopLevelAnd(expr.left), ...this.flattenTopLevelAnd(expr.right)];
|
|
2223
|
+
}
|
|
2224
|
+
return [expr];
|
|
2225
|
+
}
|
|
2226
|
+
matchUpdateFromJoin(expr, targetAppId, sourceAlias) {
|
|
2227
|
+
if (expr.type !== "BINARY" || expr.op !== "=" || expr.left.type !== "FIELD") return null;
|
|
2228
|
+
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
2229
|
+
if (right === null) return null;
|
|
2230
|
+
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
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
|
+
}
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
splitQualifiedField(field) {
|
|
2240
|
+
const dot = field.indexOf(".");
|
|
2241
|
+
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
2242
|
+
}
|
|
2243
|
+
isTargetRef(ref, appId) {
|
|
2244
|
+
return ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase();
|
|
2245
|
+
}
|
|
2246
|
+
isSourceRef(ref, alias) {
|
|
2247
|
+
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
2248
|
+
}
|
|
2249
|
+
nodeContainsQualifiedField(node, alias) {
|
|
2250
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
2251
|
+
if (node === null || typeof node !== "object") return false;
|
|
2252
|
+
const obj = node;
|
|
2253
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string" && obj["tableAlias"].toLowerCase() === alias.toLowerCase()) return true;
|
|
2254
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2255
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
2256
|
+
if (this.isSourceRef(ref, alias)) return true;
|
|
2257
|
+
}
|
|
2258
|
+
return Object.values(obj).some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
2259
|
+
}
|
|
2260
|
+
nodeContainsForeignQualifier(node, targetAppId) {
|
|
2261
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
2262
|
+
if (node === null || typeof node !== "object") return false;
|
|
2263
|
+
const obj = node;
|
|
2264
|
+
const expected = `app${targetAppId}`.toLowerCase();
|
|
2265
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
|
|
2266
|
+
return obj["tableAlias"].toLowerCase() !== expected;
|
|
2267
|
+
}
|
|
2268
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2269
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
2270
|
+
if (ref.alias !== null) return ref.alias.toLowerCase() !== expected;
|
|
2271
|
+
}
|
|
2272
|
+
return Object.values(obj).some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
2273
|
+
}
|
|
2274
|
+
nodeContainsAnyQualifier(node) {
|
|
2275
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsAnyQualifier(v));
|
|
2276
|
+
if (node === null || typeof node !== "object") return false;
|
|
2277
|
+
const obj = node;
|
|
2278
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") return true;
|
|
2279
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
2280
|
+
if (this.splitQualifiedField(obj["field"]).alias !== null) return true;
|
|
2281
|
+
}
|
|
2282
|
+
return Object.values(obj).some((v) => this.nodeContainsAnyQualifier(v));
|
|
2066
2283
|
}
|
|
2067
2284
|
parseAssignments() {
|
|
2068
2285
|
const assignments = [];
|
|
@@ -2098,6 +2315,12 @@ var Parser = class {
|
|
|
2098
2315
|
const node = this.parseArithAddSub();
|
|
2099
2316
|
if (node.type === "NUMBER") return node;
|
|
2100
2317
|
if (node.type === "ARITH") return node;
|
|
2318
|
+
if (node.type === "FIELD_REF") {
|
|
2319
|
+
const dot = node.field.indexOf(".");
|
|
2320
|
+
if (dot > 0 && dot < node.field.length - 1) {
|
|
2321
|
+
return { type: "SOURCE_FIELD", alias: node.field.slice(0, dot), field: node.field.slice(dot + 1) };
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2101
2324
|
throw new ParseError(
|
|
2102
2325
|
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
2103
2326
|
tok
|
|
@@ -2345,6 +2568,15 @@ function isDmlType(type) {
|
|
|
2345
2568
|
function isReadOnlyType(type) {
|
|
2346
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";
|
|
2347
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
|
+
}
|
|
2348
2580
|
function hasWhereClause(stmt) {
|
|
2349
2581
|
if (!stmt || typeof stmt !== "object") return false;
|
|
2350
2582
|
const obj = stmt;
|
|
@@ -3584,11 +3816,17 @@ function analyzeBatch(statements) {
|
|
|
3584
3816
|
}
|
|
3585
3817
|
}
|
|
3586
3818
|
const defined = /* @__PURE__ */ new Map();
|
|
3819
|
+
const validationSchemas = /* @__PURE__ */ new Map();
|
|
3587
3820
|
const createdOrder = [];
|
|
3588
3821
|
const results = [];
|
|
3589
3822
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
3590
3823
|
const variableOrder = [];
|
|
3591
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
|
+
}
|
|
3592
3830
|
const statementType = getStatementType(stmt);
|
|
3593
3831
|
const created = [];
|
|
3594
3832
|
const dropped = [];
|
|
@@ -3643,6 +3881,28 @@ function analyzeBatch(statements) {
|
|
|
3643
3881
|
}
|
|
3644
3882
|
dependsOn.add(at);
|
|
3645
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
|
+
}
|
|
3646
3906
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
3647
3907
|
if (defined.has(stmt.name)) {
|
|
3648
3908
|
throw new BatchAnalysisError(
|
|
@@ -3675,8 +3935,8 @@ function analyzeBatch(statements) {
|
|
|
3675
3935
|
results.push({
|
|
3676
3936
|
index,
|
|
3677
3937
|
statementType,
|
|
3678
|
-
isDml:
|
|
3679
|
-
isReadOnly:
|
|
3938
|
+
isDml: writesKintone(stmt),
|
|
3939
|
+
isReadOnly: isReadOnlyStatement(stmt),
|
|
3680
3940
|
hasWhere: hasWhereClause(stmt),
|
|
3681
3941
|
insertValuesCount: getInsertValuesCount(stmt),
|
|
3682
3942
|
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
@@ -3685,10 +3945,16 @@ function analyzeBatch(statements) {
|
|
|
3685
3945
|
tempTablesDropped: dropped,
|
|
3686
3946
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
3687
3947
|
tempOnlySource,
|
|
3688
|
-
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
3948
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : 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)
|
|
3689
3953
|
});
|
|
3690
3954
|
});
|
|
3691
3955
|
const containsDml = results.some((r) => r.isDml);
|
|
3956
|
+
const containsValidationOnly = results.some((r) => r.isValidationOnly);
|
|
3957
|
+
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
3692
3958
|
const variables = variableOrder.map((name) => ({
|
|
3693
3959
|
name,
|
|
3694
3960
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
@@ -3697,6 +3963,8 @@ function analyzeBatch(statements) {
|
|
|
3697
3963
|
statementCount: statements.length,
|
|
3698
3964
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
3699
3965
|
containsDml,
|
|
3966
|
+
containsValidationOnly,
|
|
3967
|
+
requiresCompleteInput: needsCompleteInput,
|
|
3700
3968
|
tempTables: createdOrder,
|
|
3701
3969
|
variables,
|
|
3702
3970
|
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
@@ -4230,7 +4498,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
4230
4498
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
4231
4499
|
const record = {};
|
|
4232
4500
|
for (const { field, value } of assignments) {
|
|
4233
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE") continue;
|
|
4501
|
+
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
|
|
4234
4502
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
4235
4503
|
}
|
|
4236
4504
|
return record;
|
|
@@ -4334,6 +4602,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
4334
4602
|
record[field] = { value: String(evalArith(value, raw)) };
|
|
4335
4603
|
} else if (value.type === "CASE_VALUE") {
|
|
4336
4604
|
record[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
|
|
4605
|
+
} else if (value.type === "SOURCE_FIELD") {
|
|
4606
|
+
throw new DmlConvertError("SOURCE_FIELD \u306F UPDATE ... FROM \u5C02\u7528\u3067\u3059");
|
|
4337
4607
|
} else {
|
|
4338
4608
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
4339
4609
|
}
|
|
@@ -4345,6 +4615,51 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
4345
4615
|
records: batch
|
|
4346
4616
|
}));
|
|
4347
4617
|
}
|
|
4618
|
+
var UPDATE_FROM_UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
4619
|
+
"CHECK_BOX",
|
|
4620
|
+
"MULTI_SELECT",
|
|
4621
|
+
"USER_SELECT",
|
|
4622
|
+
"ORGANIZATION_SELECT",
|
|
4623
|
+
"GROUP_SELECT",
|
|
4624
|
+
"FILE"
|
|
4625
|
+
]);
|
|
4626
|
+
function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
4627
|
+
const updateRecords = matched.map(({ target, source }) => {
|
|
4628
|
+
const id = Number(target["$id"]?.value);
|
|
4629
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
4630
|
+
throw new DmlConvertError("UPDATE ... FROM \u306E\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u304C\u4E0D\u6B63\u3067\u3059");
|
|
4631
|
+
}
|
|
4632
|
+
const targetRow = kintoneRecordToProcessRow(target);
|
|
4633
|
+
const record = {};
|
|
4634
|
+
for (const { field, value } of stmt.assignments) {
|
|
4635
|
+
const fieldType = fieldTypes.get(field);
|
|
4636
|
+
if (value.type === "SOURCE_FIELD") {
|
|
4637
|
+
if (UPDATE_FROM_UNSUPPORTED_TYPES.has(fieldType ?? "")) {
|
|
4638
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F ${fieldType} \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9: ${field}\uFF09`);
|
|
4639
|
+
}
|
|
4640
|
+
if (!Object.prototype.hasOwnProperty.call(source, value.field)) {
|
|
4641
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217 ${value.field} \u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
4642
|
+
}
|
|
4643
|
+
const raw = source[value.field];
|
|
4644
|
+
if (typeof raw !== "string") {
|
|
4645
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F\u30B9\u30AB\u30E9\u30FC\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\uFF08\u5217: ${value.field}\uFF09`);
|
|
4646
|
+
}
|
|
4647
|
+
if ((fieldType === "NUMBER" || fieldType === "CALC") && raw !== "" && !Number.isFinite(Number(raw))) {
|
|
4648
|
+
throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
|
|
4649
|
+
}
|
|
4650
|
+
record[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
|
|
4651
|
+
} else if (value.type === "ARITH") {
|
|
4652
|
+
record[field] = { value: String(evalArith(value, target)) };
|
|
4653
|
+
} else if (value.type === "CASE_VALUE") {
|
|
4654
|
+
record[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
|
|
4655
|
+
} else {
|
|
4656
|
+
record[field] = { value: toKintoneValue(value, fieldType) };
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
return { id, record };
|
|
4660
|
+
});
|
|
4661
|
+
return chunk(updateRecords, 100).map((records) => ({ app: stmt.appId, records }));
|
|
4662
|
+
}
|
|
4348
4663
|
function kintoneRecordToProcessRow(raw) {
|
|
4349
4664
|
return Object.fromEntries(
|
|
4350
4665
|
Object.entries(raw).map(([k, v]) => [
|
|
@@ -4459,6 +4774,18 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
4459
4774
|
return "";
|
|
4460
4775
|
}
|
|
4461
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) {
|
|
4462
4789
|
switch (value.type) {
|
|
4463
4790
|
case "VARIABLE":
|
|
4464
4791
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
@@ -5299,6 +5626,228 @@ function toFlatString(value) {
|
|
|
5299
5626
|
}
|
|
5300
5627
|
}
|
|
5301
5628
|
|
|
5629
|
+
// src/core/dmlValidation.ts
|
|
5630
|
+
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
5631
|
+
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
5632
|
+
function validateAndNormalizeDmlValue(raw, field) {
|
|
5633
|
+
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
5634
|
+
const original = rawScalarText(raw);
|
|
5635
|
+
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
5636
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
let value;
|
|
5640
|
+
try {
|
|
5641
|
+
value = normalizeRaw(raw, field.fieldType);
|
|
5642
|
+
} catch (e) {
|
|
5643
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
5644
|
+
return { ok: false, code: typeCode(field.fieldType), message };
|
|
5645
|
+
}
|
|
5646
|
+
if (field.required && isEmpty(value)) {
|
|
5647
|
+
return { ok: false, code: "ERR_REQUIRED", message: `${field.code} \u306F\u5FC5\u9808\u3067\u3059` };
|
|
5648
|
+
}
|
|
5649
|
+
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
5650
|
+
const text = String(value);
|
|
5651
|
+
if (!isFiniteDecimal(text)) {
|
|
5652
|
+
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5653
|
+
}
|
|
5654
|
+
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
5655
|
+
return { ok: false, code: "ERR_RANGE_MIN", message: `${field.code} \u306F ${field.minValue} \u4EE5\u4E0A\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5656
|
+
}
|
|
5657
|
+
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
5658
|
+
return { ok: false, code: "ERR_RANGE_MAX", message: `${field.code} \u306F ${field.maxValue} \u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
5662
|
+
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
5663
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
if (typeof value === "string") {
|
|
5667
|
+
const length = value.length;
|
|
5668
|
+
const min = field.minLength == null ? null : Number(field.minLength);
|
|
5669
|
+
const max = field.maxLength == null ? null : Number(field.maxLength);
|
|
5670
|
+
if (Number.isFinite(min) && length < min) {
|
|
5671
|
+
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` };
|
|
5672
|
+
}
|
|
5673
|
+
if (Number.isFinite(max) && length > max) {
|
|
5674
|
+
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` };
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
if (CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
|
|
5678
|
+
const selected = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
5679
|
+
if (selected.some((choice) => !(choice in field.optionOrder))) {
|
|
5680
|
+
return { ok: false, code: "ERR_CHOICE_INVALID", message: `${field.code} \u306B\u5B9A\u7FA9\u5916\u306E\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u3059` };
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
return { ok: true, value };
|
|
5684
|
+
}
|
|
5685
|
+
function rawScalarText(raw) {
|
|
5686
|
+
if (raw == null) return "";
|
|
5687
|
+
if (isSqlValue(raw) && (raw.type === "STRING" || raw.type === "NUMBER")) return String(raw.value);
|
|
5688
|
+
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
5689
|
+
}
|
|
5690
|
+
function isValidTemporalInput(value, type) {
|
|
5691
|
+
if (type === "DATE") return isValidTemporal(value.replace(/\//g, "-"), "DATE");
|
|
5692
|
+
if (type === "TIME") return isValidTemporal(value, "TIME");
|
|
5693
|
+
let normalized = value.replace(/\//g, "-").replace(" ", "T");
|
|
5694
|
+
if (/T\d{2}:\d{2}$/.test(normalized)) normalized += ":00";
|
|
5695
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(normalized)) {
|
|
5696
|
+
return isValidTemporal(normalized.slice(0, 10), "DATE") && isValidTemporal(normalized.slice(11), "TIME");
|
|
5697
|
+
}
|
|
5698
|
+
return isValidTemporal(normalized, "DATETIME");
|
|
5699
|
+
}
|
|
5700
|
+
function normalizeRaw(raw, fieldType) {
|
|
5701
|
+
if (isSqlValue(raw)) {
|
|
5702
|
+
const normalized = normalizeDmlSqlValue(raw, fieldType);
|
|
5703
|
+
if (!normalized.ok) throw new Error(normalized.message);
|
|
5704
|
+
return normalized.value;
|
|
5705
|
+
}
|
|
5706
|
+
if (Array.isArray(raw)) return raw.map((v) => typeof v === "object" && v !== null && "code" in v ? String(v.code) : String(v));
|
|
5707
|
+
const text = raw == null ? "" : String(raw);
|
|
5708
|
+
if (ARRAY_TYPES2.has(fieldType)) {
|
|
5709
|
+
if (text === "") return [];
|
|
5710
|
+
try {
|
|
5711
|
+
const parsed = JSON.parse(text);
|
|
5712
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
5713
|
+
} catch {
|
|
5714
|
+
}
|
|
5715
|
+
return text.split(",").map((v) => v.trim());
|
|
5716
|
+
}
|
|
5717
|
+
return text;
|
|
5718
|
+
}
|
|
5719
|
+
function isSqlValue(value) {
|
|
5720
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
5721
|
+
}
|
|
5722
|
+
function isEmptyDmlValue(value) {
|
|
5723
|
+
if (value == null || value === "") return true;
|
|
5724
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
5725
|
+
if (isSqlValue(value)) {
|
|
5726
|
+
if (value.type === "STRING") return value.value === "";
|
|
5727
|
+
if (value.type === "ARRAY") return value.elements.length === 0;
|
|
5728
|
+
}
|
|
5729
|
+
return false;
|
|
5730
|
+
}
|
|
5731
|
+
function isEmpty(value) {
|
|
5732
|
+
return value === "" || Array.isArray(value) && value.length === 0;
|
|
5733
|
+
}
|
|
5734
|
+
function typeCode(type) {
|
|
5735
|
+
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
5736
|
+
}
|
|
5737
|
+
function isFiniteDecimal(value) {
|
|
5738
|
+
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
5739
|
+
}
|
|
5740
|
+
function compareDecimal(left, right) {
|
|
5741
|
+
const normalize = (input) => {
|
|
5742
|
+
let s = input.trim();
|
|
5743
|
+
let sign = 1;
|
|
5744
|
+
if (s.startsWith("-")) {
|
|
5745
|
+
sign = -1;
|
|
5746
|
+
s = s.slice(1);
|
|
5747
|
+
} else if (s.startsWith("+")) s = s.slice(1);
|
|
5748
|
+
let [whole, fraction = ""] = s.split(".");
|
|
5749
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
5750
|
+
fraction = fraction.replace(/0+$/, "");
|
|
5751
|
+
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
5752
|
+
return { sign, whole, fraction };
|
|
5753
|
+
};
|
|
5754
|
+
const a = normalize(left);
|
|
5755
|
+
const b = normalize(right);
|
|
5756
|
+
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
5757
|
+
const direction = a.sign;
|
|
5758
|
+
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
5759
|
+
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
5760
|
+
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
5761
|
+
const af = a.fraction.padEnd(width, "0");
|
|
5762
|
+
const bf = b.fraction.padEnd(width, "0");
|
|
5763
|
+
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
5764
|
+
}
|
|
5765
|
+
function isValidTemporal(value, type) {
|
|
5766
|
+
if (type === "TIME") {
|
|
5767
|
+
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
5768
|
+
return m2 !== null && Number(m2[1]) <= 23 && Number(m2[2]) <= 59 && Number(m2[3] ?? 0) <= 59;
|
|
5769
|
+
}
|
|
5770
|
+
const datePart = type === "DATE" ? value : value.slice(0, 10);
|
|
5771
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart);
|
|
5772
|
+
if (!m) return false;
|
|
5773
|
+
const year = Number(m[1]);
|
|
5774
|
+
const month = Number(m[2]);
|
|
5775
|
+
const day = Number(m[3]);
|
|
5776
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
5777
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return false;
|
|
5778
|
+
if (type === "DATE") return true;
|
|
5779
|
+
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");
|
|
5780
|
+
}
|
|
5781
|
+
|
|
5782
|
+
// src/core/dmlValidationCandidates.ts
|
|
5783
|
+
var VALIDATION_META_COLUMNS = [
|
|
5784
|
+
"$err_statement",
|
|
5785
|
+
"$err_operation",
|
|
5786
|
+
"$err_row",
|
|
5787
|
+
"$err_field",
|
|
5788
|
+
"$err_code",
|
|
5789
|
+
"$err_message"
|
|
5790
|
+
];
|
|
5791
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
5792
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
5793
|
+
const errors = [];
|
|
5794
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
5795
|
+
for (const candidate of candidates) {
|
|
5796
|
+
candidate.record ??= {};
|
|
5797
|
+
const rowErrors = [...candidate.preErrors];
|
|
5798
|
+
for (const code of targetFields) {
|
|
5799
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
5800
|
+
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
5801
|
+
else candidate.record[code] = { value: result.value };
|
|
5802
|
+
}
|
|
5803
|
+
if (candidate.mode === "create") {
|
|
5804
|
+
for (const info of fieldInfos) {
|
|
5805
|
+
if (info.inSubtable) continue;
|
|
5806
|
+
if (candidate.payload.has(info.code)) continue;
|
|
5807
|
+
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
5808
|
+
if (!emptyDefault) {
|
|
5809
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
5810
|
+
if (!defaultResult.ok) rowErrors.push({
|
|
5811
|
+
field: info.code,
|
|
5812
|
+
code: defaultResult.code,
|
|
5813
|
+
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
5814
|
+
});
|
|
5815
|
+
} else {
|
|
5816
|
+
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
5817
|
+
if (!emptyResult.ok) {
|
|
5818
|
+
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
5819
|
+
} else if (info.required) {
|
|
5820
|
+
rowErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
|
|
5826
|
+
for (const error of rowErrors) {
|
|
5827
|
+
const row = {};
|
|
5828
|
+
for (const field of payloadFields) row[field] = renderValidationValue(candidate.payload.get(field));
|
|
5829
|
+
row["$err_statement"] = String(statementNumber);
|
|
5830
|
+
row["$err_operation"] = operation;
|
|
5831
|
+
row["$err_row"] = String(candidate.rowNumber);
|
|
5832
|
+
row["$err_field"] = error.field;
|
|
5833
|
+
row["$err_code"] = error.code;
|
|
5834
|
+
row["$err_message"] = error.message;
|
|
5835
|
+
errors.push(row);
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
|
|
5839
|
+
}
|
|
5840
|
+
function renderValidationValue(value) {
|
|
5841
|
+
if (value == null) return "";
|
|
5842
|
+
if (typeof value === "object" && "type" in value) {
|
|
5843
|
+
const sql = value;
|
|
5844
|
+
if (sql.type === "STRING" || sql.type === "NUMBER") return String(sql.value ?? "");
|
|
5845
|
+
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
5846
|
+
}
|
|
5847
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
5848
|
+
return String(value);
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5302
5851
|
// src/execute.ts
|
|
5303
5852
|
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";
|
|
5304
5853
|
var SearchAbortedError = class extends Error {
|
|
@@ -5402,6 +5951,15 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
5402
5951
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
5403
5952
|
}
|
|
5404
5953
|
validateKlikeStatement(stmt);
|
|
5954
|
+
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
5955
|
+
if (stmt.validationErrorTable) {
|
|
5956
|
+
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
5957
|
+
}
|
|
5958
|
+
return executeDmlValidation(stmt, client, { ...options, onLimitReached: "error" }, cacheContext, void 0, 1);
|
|
5959
|
+
}
|
|
5960
|
+
if ("onErrorSkip" in stmt && stmt.onErrorSkip === true) {
|
|
5961
|
+
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
5962
|
+
}
|
|
5405
5963
|
switch (stmt.type) {
|
|
5406
5964
|
case "SELECT":
|
|
5407
5965
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -5443,6 +6001,17 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
5443
6001
|
}
|
|
5444
6002
|
}
|
|
5445
6003
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
6004
|
+
function appendValidationErrors(tempTables, name, columns, rows, maxRows) {
|
|
6005
|
+
const current = tempTables.get(name);
|
|
6006
|
+
if (current && (current.columns.length !== columns.length || current.columns.some((c, i) => c !== columns[i]))) {
|
|
6007
|
+
throw new Error(`ArgumentError: validation error table ${name} has a different schema.`);
|
|
6008
|
+
}
|
|
6009
|
+
const existingRows = current?.rows ?? [];
|
|
6010
|
+
if (existingRows.length + rows.length > maxRows) {
|
|
6011
|
+
throw new Error(`ArgumentError: temp table ${name} exceeds max rows (${maxRows}).`);
|
|
6012
|
+
}
|
|
6013
|
+
tempTables.set(name, { columns: [...columns], rows: [...existingRows, ...rows] });
|
|
6014
|
+
}
|
|
5446
6015
|
var BatchTimeoutError = class extends Error {
|
|
5447
6016
|
constructor() {
|
|
5448
6017
|
super("TimeoutError: batch timeout exceeded.");
|
|
@@ -5459,6 +6028,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5459
6028
|
for (const s of analysis.statements) {
|
|
5460
6029
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
5461
6030
|
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
6031
|
+
const parsed = statements[s.index];
|
|
6032
|
+
if (parsed?.type === "UPDATE" && parsed.from?.cteName != null) continue;
|
|
5462
6033
|
throw new BatchAnalysisError(
|
|
5463
6034
|
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
5464
6035
|
s.index
|
|
@@ -5522,7 +6093,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
5522
6093
|
}
|
|
5523
6094
|
results.push({ ...base, status: "success", ...outcome });
|
|
5524
6095
|
} catch (e) {
|
|
5525
|
-
results.push({
|
|
6096
|
+
results.push({
|
|
6097
|
+
...base,
|
|
6098
|
+
status: "error",
|
|
6099
|
+
error: toBatchStatementError(e),
|
|
6100
|
+
...e instanceof RejectLimitExceededError ? { result: e.diagnostic } : {}
|
|
6101
|
+
});
|
|
5526
6102
|
failed.add(i);
|
|
5527
6103
|
if (e instanceof BatchTimeoutError) {
|
|
5528
6104
|
aborted = "timeout";
|
|
@@ -5581,6 +6157,38 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
5581
6157
|
}
|
|
5582
6158
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
5583
6159
|
validateKlikeStatement(resolvedStmt);
|
|
6160
|
+
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
6161
|
+
const result = await executeDmlValidation(
|
|
6162
|
+
resolvedStmt,
|
|
6163
|
+
client,
|
|
6164
|
+
{ ...options, onLimitReached: "error" },
|
|
6165
|
+
cacheContext,
|
|
6166
|
+
tempTables,
|
|
6167
|
+
info.index + 1
|
|
6168
|
+
);
|
|
6169
|
+
if (resolvedStmt.validationErrorTable) {
|
|
6170
|
+
appendValidationErrors(
|
|
6171
|
+
tempTables,
|
|
6172
|
+
resolvedStmt.validationErrorTable,
|
|
6173
|
+
result.columns,
|
|
6174
|
+
result.errors,
|
|
6175
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
6176
|
+
);
|
|
6177
|
+
}
|
|
6178
|
+
return { result };
|
|
6179
|
+
}
|
|
6180
|
+
if ("onErrorSkip" in resolvedStmt && resolvedStmt.onErrorSkip === true) {
|
|
6181
|
+
return {
|
|
6182
|
+
result: await executeOnErrorSkip(
|
|
6183
|
+
resolvedStmt,
|
|
6184
|
+
client,
|
|
6185
|
+
{ ...options, onLimitReached: "error" },
|
|
6186
|
+
cacheContext,
|
|
6187
|
+
tempTables,
|
|
6188
|
+
info.index + 1
|
|
6189
|
+
)
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
5584
6192
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
5585
6193
|
const materializeOptions = {
|
|
5586
6194
|
...options,
|
|
@@ -5615,6 +6223,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
5615
6223
|
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
5616
6224
|
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
5617
6225
|
}
|
|
6226
|
+
if (resolvedStmt.type === "UPDATE" && resolvedStmt.from?.cteName != null) {
|
|
6227
|
+
return { result: await executeUpdate(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
6228
|
+
}
|
|
5618
6229
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
5619
6230
|
}
|
|
5620
6231
|
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
@@ -6726,7 +7337,7 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
6726
7337
|
}
|
|
6727
7338
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
6728
7339
|
const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
6729
|
-
const
|
|
7340
|
+
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
6730
7341
|
if (USER_TYPES2.has(dstFieldType ?? "")) {
|
|
6731
7342
|
if (raw === "") return [];
|
|
6732
7343
|
try {
|
|
@@ -6738,7 +7349,7 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
6738
7349
|
}
|
|
6739
7350
|
return raw.split(",").map((c) => ({ code: c.trim() }));
|
|
6740
7351
|
}
|
|
6741
|
-
if (
|
|
7352
|
+
if (ARRAY_TYPES3.has(dstFieldType ?? "")) {
|
|
6742
7353
|
if (raw === "") return [];
|
|
6743
7354
|
try {
|
|
6744
7355
|
const parsed = JSON.parse(raw);
|
|
@@ -6749,6 +7360,396 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
6749
7360
|
}
|
|
6750
7361
|
return raw;
|
|
6751
7362
|
}
|
|
7363
|
+
var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
7364
|
+
"CALC",
|
|
7365
|
+
"RECORD_NUMBER",
|
|
7366
|
+
"CREATOR",
|
|
7367
|
+
"CREATED_TIME",
|
|
7368
|
+
"MODIFIER",
|
|
7369
|
+
"UPDATED_TIME",
|
|
7370
|
+
"STATUS",
|
|
7371
|
+
"STATUS_ASSIGNEE",
|
|
7372
|
+
"CATEGORY",
|
|
7373
|
+
"REFERENCE_TABLE"
|
|
7374
|
+
]);
|
|
7375
|
+
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7376
|
+
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
7377
|
+
}
|
|
7378
|
+
var RejectLimitExceededError = class extends Error {
|
|
7379
|
+
constructor(message, diagnostic) {
|
|
7380
|
+
super(`RejectLimitExceededError: ${message}`);
|
|
7381
|
+
this.diagnostic = diagnostic;
|
|
7382
|
+
this.name = "RejectLimitExceededError";
|
|
7383
|
+
}
|
|
7384
|
+
};
|
|
7385
|
+
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7386
|
+
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
7387
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
7388
|
+
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
7389
|
+
throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
7390
|
+
}
|
|
7391
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
7392
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
7393
|
+
const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
|
|
7394
|
+
for (const code of targetFields) {
|
|
7395
|
+
const info = infoByCode.get(code);
|
|
7396
|
+
if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
7397
|
+
if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
|
|
7398
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
7402
|
+
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
7403
|
+
candidates,
|
|
7404
|
+
operation,
|
|
7405
|
+
payloadFields,
|
|
7406
|
+
targetFields,
|
|
7407
|
+
fieldInfos,
|
|
7408
|
+
statementNumber
|
|
7409
|
+
);
|
|
7410
|
+
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
7411
|
+
const result = {
|
|
7412
|
+
type: "VALIDATION",
|
|
7413
|
+
operation,
|
|
7414
|
+
validatedRows: candidates.length,
|
|
7415
|
+
validRows: candidates.length - invalidRows,
|
|
7416
|
+
invalidRows,
|
|
7417
|
+
errorCount: errors.length,
|
|
7418
|
+
columns,
|
|
7419
|
+
errors,
|
|
7420
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : stmt.onErrorSkip && stmt.errorTable ? { errTable: stmt.errorTable } : {}
|
|
7421
|
+
};
|
|
7422
|
+
return { result, candidates, invalidRowNumbers };
|
|
7423
|
+
}
|
|
7424
|
+
async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
7425
|
+
const prepared = await prepareDmlValidation(
|
|
7426
|
+
stmt,
|
|
7427
|
+
client,
|
|
7428
|
+
options,
|
|
7429
|
+
cacheContext,
|
|
7430
|
+
tempTables,
|
|
7431
|
+
statementNumber
|
|
7432
|
+
);
|
|
7433
|
+
const errTable = stmt.errorTable;
|
|
7434
|
+
if (!errTable) throw new Error("ArgumentError: ON ERROR SKIP requires INTO #error_table.");
|
|
7435
|
+
appendValidationErrors(
|
|
7436
|
+
tempTables,
|
|
7437
|
+
errTable,
|
|
7438
|
+
prepared.result.columns,
|
|
7439
|
+
prepared.result.errors,
|
|
7440
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
7441
|
+
);
|
|
7442
|
+
const rejectLimit = stmt.rejectLimit ?? null;
|
|
7443
|
+
if (rejectLimit !== null && prepared.result.invalidRows > rejectLimit) {
|
|
7444
|
+
throw new RejectLimitExceededError(
|
|
7445
|
+
`rejected rows (${prepared.result.invalidRows}) exceed REJECT LIMIT (${rejectLimit}).`,
|
|
7446
|
+
prepared.result
|
|
7447
|
+
);
|
|
7448
|
+
}
|
|
7449
|
+
const valid = prepared.candidates.filter((candidate) => !prepared.invalidRowNumbers.has(candidate.rowNumber));
|
|
7450
|
+
if (options.confirm) {
|
|
7451
|
+
const operation = stmt.type.startsWith("INSERT") ? "INSERT" : "UPDATE";
|
|
7452
|
+
const ok = await options.confirm(valid.length, operation);
|
|
7453
|
+
if (!ok) throw new OperationCancelledError(operation, valid.length);
|
|
7454
|
+
}
|
|
7455
|
+
const common = {
|
|
7456
|
+
affectedRows: valid.length,
|
|
7457
|
+
skippedRows: prepared.result.invalidRows,
|
|
7458
|
+
rejectLimit,
|
|
7459
|
+
errTable
|
|
7460
|
+
};
|
|
7461
|
+
if (stmt.type === "INSERT" || stmt.type === "INSERT_SELECT") {
|
|
7462
|
+
const createdIds = [];
|
|
7463
|
+
for (let i = 0; i < valid.length; i += 100) {
|
|
7464
|
+
const response = await client.postRecords({ app: stmt.appId, records: valid.slice(i, i + 100).map((c) => c.record) });
|
|
7465
|
+
createdIds.push(response.ids);
|
|
7466
|
+
}
|
|
7467
|
+
return { type: "INSERT", createdIds, insertedCount: createdIds.flat().length, ...common };
|
|
7468
|
+
}
|
|
7469
|
+
if (stmt.type === "UPDATE") {
|
|
7470
|
+
const updates2 = valid.map((candidate) => {
|
|
7471
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPDATE candidate has no targetId.");
|
|
7472
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
7473
|
+
});
|
|
7474
|
+
for (let i = 0; i < updates2.length; i += 100) {
|
|
7475
|
+
await client.putRecords({ app: stmt.appId, records: updates2.slice(i, i + 100) });
|
|
7476
|
+
}
|
|
7477
|
+
return { type: "UPDATE", updatedCount: updates2.length, ...common };
|
|
7478
|
+
}
|
|
7479
|
+
const inserts = valid.filter((candidate) => candidate.mode === "create");
|
|
7480
|
+
const updates = valid.filter((candidate) => candidate.mode === "update").map((candidate) => {
|
|
7481
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPSERT candidate has no targetId.");
|
|
7482
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
7483
|
+
});
|
|
7484
|
+
let insertedCount = 0;
|
|
7485
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
7486
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map((c) => c.record) });
|
|
7487
|
+
insertedCount += response.ids.length;
|
|
7488
|
+
}
|
|
7489
|
+
for (let i = 0; i < updates.length; i += 100) {
|
|
7490
|
+
await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
7491
|
+
}
|
|
7492
|
+
return { type: "UPSERT", insertedCount, updatedCount: updates.length, ...common };
|
|
7493
|
+
}
|
|
7494
|
+
async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
|
|
7495
|
+
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
7496
|
+
let rows;
|
|
7497
|
+
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
7498
|
+
rows = stmt.values.map((row) => row.map(
|
|
7499
|
+
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
7500
|
+
));
|
|
7501
|
+
} else {
|
|
7502
|
+
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);
|
|
7503
|
+
if (selectResult.columns.length !== stmt.fields.length) {
|
|
7504
|
+
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`);
|
|
7505
|
+
}
|
|
7506
|
+
rows = selectResult.rows.map((row) => selectResult.columns.map((column) => row[column] ?? ""));
|
|
7507
|
+
}
|
|
7508
|
+
const candidates = rows.map((values, index) => ({
|
|
7509
|
+
rowNumber: index + 1,
|
|
7510
|
+
operation,
|
|
7511
|
+
mode: "create",
|
|
7512
|
+
payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
|
|
7513
|
+
preErrors: [],
|
|
7514
|
+
record: {}
|
|
7515
|
+
}));
|
|
7516
|
+
if (stmt.type !== "UPSERT" && stmt.type !== "UPSERT_SELECT") return candidates;
|
|
7517
|
+
for (const key of stmt.keyFields) {
|
|
7518
|
+
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`);
|
|
7519
|
+
}
|
|
7520
|
+
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
7521
|
+
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
7522
|
+
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
7523
|
+
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
7524
|
+
const keyCounts = /* @__PURE__ */ new Map();
|
|
7525
|
+
for (const parts of rowKeys) {
|
|
7526
|
+
const key = upsertNormalizedKey(parts, numeric);
|
|
7527
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
7528
|
+
}
|
|
7529
|
+
candidates.forEach((candidate, index) => {
|
|
7530
|
+
const parts = rowKeys[index];
|
|
7531
|
+
const targetId = lookupUpsertTarget(targets, parts);
|
|
7532
|
+
candidate.mode = targetId === void 0 ? "create" : "update";
|
|
7533
|
+
if (targetId !== void 0) candidate.targetId = targetId;
|
|
7534
|
+
stmt.keyFields.forEach((key, keyIndex) => {
|
|
7535
|
+
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` });
|
|
7536
|
+
});
|
|
7537
|
+
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
7538
|
+
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" });
|
|
7539
|
+
}
|
|
7540
|
+
});
|
|
7541
|
+
return candidates;
|
|
7542
|
+
}
|
|
7543
|
+
async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
|
|
7544
|
+
if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
7545
|
+
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
7546
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7547
|
+
let records;
|
|
7548
|
+
if (hasArithAssignment(stmt)) {
|
|
7549
|
+
const getParams = updateToGetQueryForArith(stmt);
|
|
7550
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
|
|
7551
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
7552
|
+
parallel: options.fetchParallel ?? 1,
|
|
7553
|
+
onLimit: "error"
|
|
7554
|
+
});
|
|
7555
|
+
records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
|
|
7556
|
+
} else {
|
|
7557
|
+
const getParams = updateToGetQuery(stmt);
|
|
7558
|
+
const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
|
|
7559
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
7560
|
+
parallel: options.fetchParallel ?? 1
|
|
7561
|
+
});
|
|
7562
|
+
records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
|
|
7563
|
+
}
|
|
7564
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
7565
|
+
rowNumber: index + 1,
|
|
7566
|
+
operation: "UPDATE",
|
|
7567
|
+
mode: "update",
|
|
7568
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
7569
|
+
preErrors: [],
|
|
7570
|
+
record: entry.record,
|
|
7571
|
+
targetId: entry.id
|
|
7572
|
+
}));
|
|
7573
|
+
}
|
|
7574
|
+
async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
|
|
7575
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
7576
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7577
|
+
const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
|
|
7578
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
7579
|
+
rowNumber: index + 1,
|
|
7580
|
+
operation: "UPDATE",
|
|
7581
|
+
mode: "update",
|
|
7582
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
7583
|
+
preErrors: [],
|
|
7584
|
+
record: entry.record,
|
|
7585
|
+
targetId: entry.id
|
|
7586
|
+
}));
|
|
7587
|
+
}
|
|
7588
|
+
var UPDATE_FROM_KEY_CHUNK_SIZE = UPSERT_IN_CHUNK_SIZE;
|
|
7589
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
7590
|
+
"CHECK_BOX",
|
|
7591
|
+
"MULTI_SELECT",
|
|
7592
|
+
"USER_SELECT",
|
|
7593
|
+
"ORGANIZATION_SELECT",
|
|
7594
|
+
"GROUP_SELECT",
|
|
7595
|
+
"FILE"
|
|
7596
|
+
]);
|
|
7597
|
+
async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
|
|
7598
|
+
const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
|
|
7599
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
7600
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
7601
|
+
const sourceRows = await loadUpdateFromSourceRows(
|
|
7602
|
+
from,
|
|
7603
|
+
requiredSourceFields,
|
|
7604
|
+
sourceFields,
|
|
7605
|
+
client,
|
|
7606
|
+
options,
|
|
7607
|
+
cacheContext,
|
|
7608
|
+
tempTables
|
|
7609
|
+
);
|
|
7610
|
+
const sourceByKey = /* @__PURE__ */ new Map();
|
|
7611
|
+
for (const row of sourceRows) {
|
|
7612
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
7613
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7614
|
+
}
|
|
7615
|
+
const key = normalizeUpdateFromJoinKey(row[from.joinKeyField], joinKind, "source");
|
|
7616
|
+
if (sourceByKey.has(key)) {
|
|
7617
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
7618
|
+
}
|
|
7619
|
+
sourceByKey.set(key, row);
|
|
7620
|
+
}
|
|
7621
|
+
if (sourceByKey.size === 0) return [];
|
|
7622
|
+
const maxRecords = options.maxRecords ?? 1e4;
|
|
7623
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
7624
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
7625
|
+
const targetRecords = [];
|
|
7626
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
7627
|
+
let fetchedTargetCount = 0;
|
|
7628
|
+
for (const keys of splitChunks([...sourceByKey.keys()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
7629
|
+
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
7630
|
+
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
7631
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7632
|
+
client.getRecords,
|
|
7633
|
+
stmt.appId,
|
|
7634
|
+
query,
|
|
7635
|
+
targetFields,
|
|
7636
|
+
{ maxRecords, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7637
|
+
);
|
|
7638
|
+
fetchedTargetCount += resolved.records.length;
|
|
7639
|
+
if (fetchedTargetCount > maxRecords) {
|
|
7640
|
+
throw new FetchAllLimitError(
|
|
7641
|
+
`\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`
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
for (const record of resolved.records) {
|
|
7645
|
+
const id = record["$id"]?.value;
|
|
7646
|
+
if (typeof id !== "string" || id === "") {
|
|
7647
|
+
throw new Error("ArgumentError: UPDATE ... FROM target record does not contain a valid $id.");
|
|
7648
|
+
}
|
|
7649
|
+
if (seenTargetIds.has(id)) continue;
|
|
7650
|
+
seenTargetIds.add(id);
|
|
7651
|
+
targetRecords.push(record);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
const matched = [];
|
|
7655
|
+
for (const target of targetRecords) {
|
|
7656
|
+
const raw = target[from.targetJoinField]?.value;
|
|
7657
|
+
const key = normalizeUpdateFromJoinKey(raw, joinKind, "target");
|
|
7658
|
+
if (key === null) continue;
|
|
7659
|
+
const source = sourceByKey.get(key);
|
|
7660
|
+
if (source !== void 0) matched.push({ target, source });
|
|
7661
|
+
}
|
|
7662
|
+
return matched;
|
|
7663
|
+
}
|
|
7664
|
+
async function resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext) {
|
|
7665
|
+
if (from.targetJoinField === "$id") return "id";
|
|
7666
|
+
const info = (await getFieldsCached(stmt.appId, client, cacheContext)).find((field) => field.code === from.targetJoinField);
|
|
7667
|
+
if (!info) {
|
|
7668
|
+
throw new Error(`ArgumentError: UPDATE ... FROM target column ${from.targetJoinField} does not exist.`);
|
|
7669
|
+
}
|
|
7670
|
+
if (info.inSubtable || info.writable === false || info.fieldType !== "SINGLE_LINE_TEXT" && info.fieldType !== "NUMBER") {
|
|
7671
|
+
throw new Error(
|
|
7672
|
+
`ArgumentError: UPDATE ... FROM does not support target join field type ${info.fieldType} (${from.targetJoinField}).`
|
|
7673
|
+
);
|
|
7674
|
+
}
|
|
7675
|
+
return info.fieldType === "NUMBER" ? "number" : "string";
|
|
7676
|
+
}
|
|
7677
|
+
async function loadUpdateFromSourceRows(from, requiredSourceFields, sourceValueFields, client, options, cacheContext, tempTables) {
|
|
7678
|
+
if (from.cteName !== null) {
|
|
7679
|
+
const table = tempTables?.get(from.cteName);
|
|
7680
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
7681
|
+
for (const field of requiredSourceFields) {
|
|
7682
|
+
if (!table.columns.includes(field)) {
|
|
7683
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
return table.rows;
|
|
7687
|
+
}
|
|
7688
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
7689
|
+
const joinType = from.joinKeyField === "$id" ? "RECORD_NUMBER" : sourceTypes.get(from.joinKeyField);
|
|
7690
|
+
if (joinType === void 0) {
|
|
7691
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
7692
|
+
}
|
|
7693
|
+
if (from.joinKeyField !== "$id" && joinType !== "SINGLE_LINE_TEXT" && joinType !== "NUMBER") {
|
|
7694
|
+
throw new Error(
|
|
7695
|
+
`ArgumentError: UPDATE ... FROM does not support source join field type ${joinType} (${from.joinKeyField}).`
|
|
7696
|
+
);
|
|
7697
|
+
}
|
|
7698
|
+
for (const field of sourceValueFields) {
|
|
7699
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
7700
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
7701
|
+
}
|
|
7702
|
+
const type = field === "$id" ? "RECORD_NUMBER" : sourceTypes.get(field);
|
|
7703
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
7704
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
7705
|
+
}
|
|
7706
|
+
}
|
|
7707
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
7708
|
+
client.getRecords,
|
|
7709
|
+
from.appId,
|
|
7710
|
+
"",
|
|
7711
|
+
requiredSourceFields,
|
|
7712
|
+
{ maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
7713
|
+
);
|
|
7714
|
+
return resolved.records.map((record) => flatten(record, null));
|
|
7715
|
+
}
|
|
7716
|
+
function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
7717
|
+
if (typeof raw !== "string") {
|
|
7718
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a scalar string: ${String(raw)}`);
|
|
7719
|
+
}
|
|
7720
|
+
if (kind === "string") {
|
|
7721
|
+
if (raw === "") {
|
|
7722
|
+
if (side === "target") return null;
|
|
7723
|
+
throw new Error("ArgumentError: UPDATE ... FROM source key must not be empty.");
|
|
7724
|
+
}
|
|
7725
|
+
return raw;
|
|
7726
|
+
}
|
|
7727
|
+
if (kind === "number" && side === "target" && raw === "") return null;
|
|
7728
|
+
if (kind === "id") {
|
|
7729
|
+
const text2 = raw.trim();
|
|
7730
|
+
const id = Number(text2);
|
|
7731
|
+
if (text2 === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
7732
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
7733
|
+
}
|
|
7734
|
+
return String(id);
|
|
7735
|
+
}
|
|
7736
|
+
const text = raw.trim();
|
|
7737
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
|
|
7738
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
7739
|
+
}
|
|
7740
|
+
let unsigned = text;
|
|
7741
|
+
let negative = false;
|
|
7742
|
+
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
7743
|
+
negative = unsigned[0] === "-";
|
|
7744
|
+
unsigned = unsigned.slice(1);
|
|
7745
|
+
}
|
|
7746
|
+
let [whole, fraction = ""] = unsigned.split(".");
|
|
7747
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
7748
|
+
fraction = fraction.replace(/0+$/, "");
|
|
7749
|
+
const zero = /^0*$/.test(whole) && fraction === "";
|
|
7750
|
+
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
7751
|
+
return negative && !zero ? `-${canonical}` : canonical;
|
|
7752
|
+
}
|
|
6752
7753
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
6753
7754
|
if (stmt.subtableCode) {
|
|
6754
7755
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
@@ -6800,10 +7801,13 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
6800
7801
|
insertedCount: createdIds.flat().length
|
|
6801
7802
|
};
|
|
6802
7803
|
}
|
|
6803
|
-
async function executeUpdate(stmt, client, options, cacheContext) {
|
|
7804
|
+
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
6804
7805
|
if (stmt.subtableCode) {
|
|
6805
7806
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
6806
7807
|
}
|
|
7808
|
+
if (stmt.from != null) {
|
|
7809
|
+
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
7810
|
+
}
|
|
6807
7811
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
6808
7812
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
6809
7813
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -6845,6 +7849,35 @@ async function executeUpdate(stmt, client, options, cacheContext) {
|
|
|
6845
7849
|
}
|
|
6846
7850
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
6847
7851
|
}
|
|
7852
|
+
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
7853
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
7854
|
+
if (options.confirm) {
|
|
7855
|
+
const ok = await options.confirm(matched.length, "UPDATE");
|
|
7856
|
+
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
7857
|
+
}
|
|
7858
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
7859
|
+
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
7860
|
+
for (const batch of batches) await client.putRecords(batch);
|
|
7861
|
+
return { type: "UPDATE", updatedCount: matched.length };
|
|
7862
|
+
}
|
|
7863
|
+
function collectUpdateFromTargetFields(stmt) {
|
|
7864
|
+
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
7865
|
+
if (stmt.from) fields.add(stmt.from.targetJoinField);
|
|
7866
|
+
const visit = (node) => {
|
|
7867
|
+
if (Array.isArray(node)) {
|
|
7868
|
+
node.forEach(visit);
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
7871
|
+
if (node === null || typeof node !== "object") return;
|
|
7872
|
+
const obj = node;
|
|
7873
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") fields.add(obj["field"]);
|
|
7874
|
+
for (const value of Object.values(obj)) visit(value);
|
|
7875
|
+
};
|
|
7876
|
+
for (const assignment of stmt.assignments) {
|
|
7877
|
+
if (assignment.value.type !== "SOURCE_FIELD") visit(assignment.value);
|
|
7878
|
+
}
|
|
7879
|
+
return [...fields];
|
|
7880
|
+
}
|
|
6848
7881
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
6849
7882
|
if (stmt.subtableCode) {
|
|
6850
7883
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
@@ -7770,9 +8803,16 @@ function buildUpdatePlan(stmt, label) {
|
|
|
7770
8803
|
const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
|
|
7771
8804
|
const lines = [];
|
|
7772
8805
|
if (label) lines.push(label);
|
|
7773
|
-
lines.push(` [UPDATE]`);
|
|
8806
|
+
lines.push(stmt.from ? ` [UPDATE FROM]` : ` [UPDATE]`);
|
|
7774
8807
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
7775
|
-
|
|
8808
|
+
if (stmt.from) {
|
|
8809
|
+
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
8810
|
+
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
8811
|
+
lines.push(` join: APP${stmt.appId}.${stmt.from.targetJoinField} = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
8812
|
+
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
8813
|
+
} else {
|
|
8814
|
+
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
8815
|
+
}
|
|
7776
8816
|
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
7777
8817
|
const setTypes = [];
|
|
7778
8818
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
@@ -7885,6 +8925,7 @@ function formatAssignment(a) {
|
|
|
7885
8925
|
if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
|
|
7886
8926
|
if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
|
|
7887
8927
|
if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
|
|
8928
|
+
if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
|
|
7888
8929
|
return `${a.field} = (${v.type})`;
|
|
7889
8930
|
}
|
|
7890
8931
|
function formatArithExprStr(expr) {
|
|
@@ -8008,12 +9049,32 @@ function isSubtableRow(v) {
|
|
|
8008
9049
|
// src/output/batchEnvelope.ts
|
|
8009
9050
|
function toMutationSummary(result) {
|
|
8010
9051
|
if (result.type === "INSERT") {
|
|
8011
|
-
return {
|
|
9052
|
+
return {
|
|
9053
|
+
insertedCount: result.insertedCount,
|
|
9054
|
+
createdIds: result.createdIds,
|
|
9055
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9056
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9057
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9058
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9059
|
+
};
|
|
8012
9060
|
}
|
|
8013
|
-
if (result.type === "UPDATE") return {
|
|
9061
|
+
if (result.type === "UPDATE") return {
|
|
9062
|
+
updatedCount: result.updatedCount,
|
|
9063
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9064
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9065
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9066
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9067
|
+
};
|
|
8014
9068
|
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
8015
9069
|
if (result.type === "UPSERT") {
|
|
8016
|
-
return {
|
|
9070
|
+
return {
|
|
9071
|
+
insertedCount: result.insertedCount,
|
|
9072
|
+
updatedCount: result.updatedCount,
|
|
9073
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
9074
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
9075
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
9076
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
9077
|
+
};
|
|
8017
9078
|
}
|
|
8018
9079
|
return { reorderedParentCount: result.reorderedParentCount };
|
|
8019
9080
|
}
|
|
@@ -8040,11 +9101,31 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
8040
9101
|
}
|
|
8041
9102
|
entry.resultIndex = results.length;
|
|
8042
9103
|
results.push({
|
|
9104
|
+
type: "SELECT",
|
|
8043
9105
|
columns: s.result.columns,
|
|
8044
9106
|
rows: s.result.rows,
|
|
8045
9107
|
rowCount: s.result.rowCount,
|
|
8046
9108
|
warnings: s.result.warnings ?? []
|
|
8047
9109
|
});
|
|
9110
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
9111
|
+
totalRows += s.result.errorCount;
|
|
9112
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
9113
|
+
throw new Error(`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`);
|
|
9114
|
+
}
|
|
9115
|
+
entry.resultIndex = results.length;
|
|
9116
|
+
results.push({
|
|
9117
|
+
type: "VALIDATION",
|
|
9118
|
+
columns: s.result.columns,
|
|
9119
|
+
rows: s.result.errors,
|
|
9120
|
+
rowCount: s.result.errorCount,
|
|
9121
|
+
warnings: [],
|
|
9122
|
+
operation: s.result.operation,
|
|
9123
|
+
validatedRows: s.result.validatedRows,
|
|
9124
|
+
validRows: s.result.validRows,
|
|
9125
|
+
invalidRows: s.result.invalidRows,
|
|
9126
|
+
errorCount: s.result.errorCount,
|
|
9127
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
9128
|
+
});
|
|
8048
9129
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
8049
9130
|
Object.assign(entry, toMutationSummary(s.result));
|
|
8050
9131
|
}
|
|
@@ -8325,6 +9406,9 @@ function clampInt(v, min, max) {
|
|
|
8325
9406
|
|
|
8326
9407
|
// src/core/formFieldInfo.ts
|
|
8327
9408
|
function flattenFormFieldProperties(properties) {
|
|
9409
|
+
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
9410
|
+
}
|
|
9411
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
8328
9412
|
const out = [];
|
|
8329
9413
|
for (const field of Object.values(properties)) {
|
|
8330
9414
|
out.push({
|
|
@@ -8332,12 +9416,49 @@ function flattenFormFieldProperties(properties) {
|
|
|
8332
9416
|
label: field.label,
|
|
8333
9417
|
fieldType: field.type,
|
|
8334
9418
|
optionOrder: toOptionOrderMap(field.options),
|
|
8335
|
-
sortKind: detectSortKind(field.type, field.format)
|
|
9419
|
+
sortKind: detectSortKind(field.type, field.format),
|
|
9420
|
+
required: field.required,
|
|
9421
|
+
minValue: normalizeConstraintValue(field.minValue),
|
|
9422
|
+
maxValue: normalizeConstraintValue(field.maxValue),
|
|
9423
|
+
minLength: normalizeConstraintValue(field.minLength),
|
|
9424
|
+
maxLength: normalizeConstraintValue(field.maxLength),
|
|
9425
|
+
defaultValue: field.defaultValue,
|
|
9426
|
+
inSubtable,
|
|
9427
|
+
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
8336
9428
|
});
|
|
8337
|
-
if (field.fields) out.push(...
|
|
9429
|
+
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
|
|
8338
9430
|
}
|
|
8339
9431
|
return out;
|
|
8340
9432
|
}
|
|
9433
|
+
var NON_WRITABLE_FIELD_TYPES2 = /* @__PURE__ */ new Set([
|
|
9434
|
+
"CALC",
|
|
9435
|
+
"RECORD_NUMBER",
|
|
9436
|
+
"CREATOR",
|
|
9437
|
+
"CREATED_TIME",
|
|
9438
|
+
"MODIFIER",
|
|
9439
|
+
"UPDATED_TIME",
|
|
9440
|
+
"STATUS",
|
|
9441
|
+
"STATUS_ASSIGNEE",
|
|
9442
|
+
"CATEGORY",
|
|
9443
|
+
"REFERENCE_TABLE",
|
|
9444
|
+
"SUBTABLE"
|
|
9445
|
+
]);
|
|
9446
|
+
function collectLookupCopyFields(properties) {
|
|
9447
|
+
const result = /* @__PURE__ */ new Set();
|
|
9448
|
+
const visit = (fields) => {
|
|
9449
|
+
for (const field of Object.values(fields)) {
|
|
9450
|
+
for (const mapping of field.lookup?.fieldMappings ?? []) {
|
|
9451
|
+
if (mapping.field) result.add(mapping.field);
|
|
9452
|
+
}
|
|
9453
|
+
if (field.fields) visit(field.fields);
|
|
9454
|
+
}
|
|
9455
|
+
};
|
|
9456
|
+
visit(properties);
|
|
9457
|
+
return result;
|
|
9458
|
+
}
|
|
9459
|
+
function normalizeConstraintValue(value) {
|
|
9460
|
+
return value == null || value === "" ? void 0 : value;
|
|
9461
|
+
}
|
|
8341
9462
|
function toOptionOrderMap(options) {
|
|
8342
9463
|
if (!options || typeof options !== "object") return void 0;
|
|
8343
9464
|
const order = {};
|
|
@@ -9470,6 +10591,11 @@ function buildAssertOutput(result, format, pretty) {
|
|
|
9470
10591
|
if (format === "jsonl") return JSON.stringify(payload);
|
|
9471
10592
|
return `assertion ok: ${result.condition}`;
|
|
9472
10593
|
}
|
|
10594
|
+
function buildValidationOutput(result, format, noHeader, pretty, displayOptions) {
|
|
10595
|
+
if (format === "json") return JSON.stringify({ ok: true, ...result, metrics: void 0 }, null, pretty ? 2 : 0);
|
|
10596
|
+
if (format === "jsonl") return result.errors.map((row) => JSON.stringify(row)).join("\n");
|
|
10597
|
+
return buildOutput({ type: "SELECT", columns: result.columns, rows: result.errors, rowCount: result.errorCount }, format, noHeader, pretty, displayOptions);
|
|
10598
|
+
}
|
|
9473
10599
|
function buildMutationOutput(result, format, noHeader, pretty) {
|
|
9474
10600
|
const row = { type: result.type };
|
|
9475
10601
|
if (result.type === "INSERT") {
|
|
@@ -9484,6 +10610,12 @@ function buildMutationOutput(result, format, noHeader, pretty) {
|
|
|
9484
10610
|
} else if (result.type === "REORDER") {
|
|
9485
10611
|
row.reorderedParentCount = result.reorderedParentCount;
|
|
9486
10612
|
}
|
|
10613
|
+
if (result.type === "INSERT" || result.type === "UPDATE" || result.type === "UPSERT") {
|
|
10614
|
+
if (result.affectedRows !== void 0) row.affectedRows = result.affectedRows;
|
|
10615
|
+
if (result.skippedRows !== void 0) row.skippedRows = result.skippedRows;
|
|
10616
|
+
if (result.rejectLimit !== void 0) row.rejectLimit = result.rejectLimit;
|
|
10617
|
+
if (result.errTable !== void 0) row.errTable = result.errTable;
|
|
10618
|
+
}
|
|
9487
10619
|
if (format === "json") return JSON.stringify(row, null, pretty ? 2 : 0);
|
|
9488
10620
|
if (format === "jsonl") return JSON.stringify(row);
|
|
9489
10621
|
const cols = Object.keys(row);
|
|
@@ -9594,6 +10726,14 @@ function buildBatchStatementSummary(s) {
|
|
|
9594
10726
|
else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
|
|
9595
10727
|
else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
|
|
9596
10728
|
else if (r.type === "REORDER") parts.push(`reordered=${r.reorderedParentCount}`);
|
|
10729
|
+
else if (r.type === "VALIDATION") parts.push(`validated=${r.validatedRows} valid=${r.validRows} invalid=${r.invalidRows} errors=${r.errorCount}`);
|
|
10730
|
+
if ((r.type === "INSERT" || r.type === "UPDATE" || r.type === "UPSERT") && r.skippedRows !== void 0) {
|
|
10731
|
+
parts.push(`affected=${r.affectedRows} skipped=${r.skippedRows} errTable=${r.errTable}`);
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
if (s.status === "error" && s.result?.type === "VALIDATION") {
|
|
10735
|
+
const r = s.result;
|
|
10736
|
+
parts.push(`validated=${r.validatedRows} valid=${r.validRows} invalid=${r.invalidRows} errors=${r.errorCount}`);
|
|
9597
10737
|
}
|
|
9598
10738
|
if (s.status === "error" && s.error) parts.push(s.error.message);
|
|
9599
10739
|
if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
|
|
@@ -9627,6 +10767,8 @@ function buildBatchResultsOutput(batch, opts) {
|
|
|
9627
10767
|
for (const s of batch.statements) {
|
|
9628
10768
|
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
9629
10769
|
outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
10770
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
10771
|
+
outputs.push(buildValidationOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
9630
10772
|
}
|
|
9631
10773
|
}
|
|
9632
10774
|
return outputs.join("\n\n");
|
|
@@ -10289,6 +11431,7 @@ async function run() {
|
|
|
10289
11431
|
let isBatchSql = false;
|
|
10290
11432
|
let batchContainsDml = false;
|
|
10291
11433
|
let batchAnalysis = null;
|
|
11434
|
+
let needsCompleteInput = false;
|
|
10292
11435
|
if (args.diagRecordId === null) {
|
|
10293
11436
|
sql = args.executeSql;
|
|
10294
11437
|
if (!sql && args.filePath) sql = (0, import_fs2.readFileSync)(args.filePath, "utf-8");
|
|
@@ -10319,14 +11462,16 @@ async function run() {
|
|
|
10319
11462
|
batchAnalysis = analyzeBatch(statements);
|
|
10320
11463
|
isBatchSql = true;
|
|
10321
11464
|
batchContainsDml = batchAnalysis.containsDml;
|
|
11465
|
+
needsCompleteInput = batchAnalysis.requiresCompleteInput;
|
|
10322
11466
|
} else {
|
|
10323
11467
|
const stmt = parseSqlStatement(sql);
|
|
10324
11468
|
parsedStmt = stmt;
|
|
10325
11469
|
stmtType = getStatementType(stmt);
|
|
10326
|
-
isDmlStatement =
|
|
11470
|
+
isDmlStatement = writesKintone(stmt);
|
|
11471
|
+
needsCompleteInput = requiresCompleteInput(stmt);
|
|
10327
11472
|
hasWhere = hasWhereClause(stmt);
|
|
10328
11473
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
10329
|
-
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" ||
|
|
11474
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
|
|
10330
11475
|
if (!supported) {
|
|
10331
11476
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
10332
11477
|
`);
|
|
@@ -10370,10 +11515,10 @@ async function run() {
|
|
|
10370
11515
|
const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
|
|
10371
11516
|
const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
|
|
10372
11517
|
const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
|
|
10373
|
-
const dmlForcesOnLimitError =
|
|
11518
|
+
const dmlForcesOnLimitError = needsCompleteInput;
|
|
10374
11519
|
const effectiveOnLimit = dmlForcesOnLimitError ? "error" : onLimit;
|
|
10375
11520
|
if (dmlForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
|
|
10376
|
-
process.stderr.write("note: onLimit=truncate is ignored for DML (forced to error)\n");
|
|
11521
|
+
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");
|
|
10377
11522
|
}
|
|
10378
11523
|
if (format === "markdown" && noHeader) {
|
|
10379
11524
|
process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
|
|
@@ -10756,6 +11901,16 @@ query=${label}`);
|
|
|
10756
11901
|
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
10757
11902
|
`, "utf-8");
|
|
10758
11903
|
else if (output2) process.stdout.write(`${output2}
|
|
11904
|
+
`);
|
|
11905
|
+
return 0;
|
|
11906
|
+
}
|
|
11907
|
+
if (result.type === "VALIDATION") {
|
|
11908
|
+
const output2 = buildValidationOutput(result, format, noHeader, pretty, displayOptions);
|
|
11909
|
+
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
11910
|
+
`, "utf-8");
|
|
11911
|
+
else if (output2) process.stdout.write(`${output2}
|
|
11912
|
+
`);
|
|
11913
|
+
if (!quiet) process.stderr.write(`validated=${result.validatedRows} valid=${result.validRows} invalid=${result.invalidRows} errors=${result.errorCount}
|
|
10759
11914
|
`);
|
|
10760
11915
|
return 0;
|
|
10761
11916
|
}
|
|
@@ -10809,6 +11964,7 @@ if (isDirectCliRun()) {
|
|
|
10809
11964
|
buildBatchStatementSummary,
|
|
10810
11965
|
buildOutput,
|
|
10811
11966
|
buildReplExecArgv,
|
|
11967
|
+
buildValidationOutput,
|
|
10812
11968
|
extractAppIds,
|
|
10813
11969
|
normalizeAppKey,
|
|
10814
11970
|
normalizeSqlAppProfiles,
|