@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-mcp/ksql-mcp.js
CHANGED
|
@@ -32416,6 +32416,7 @@ var Parser = class {
|
|
|
32416
32416
|
tryParseImplicitAlias() {
|
|
32417
32417
|
const k = this.peek().kind;
|
|
32418
32418
|
if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
|
|
32419
|
+
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "VALIDATE" && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "ONLY") return null;
|
|
32419
32420
|
return this.parseTableAliasName();
|
|
32420
32421
|
}
|
|
32421
32422
|
return null;
|
|
@@ -32862,7 +32863,8 @@ var Parser = class {
|
|
|
32862
32863
|
if (subtableCode) {
|
|
32863
32864
|
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());
|
|
32864
32865
|
}
|
|
32865
|
-
|
|
32866
|
+
const validation2 = this.parseDmlControlSuffix();
|
|
32867
|
+
return { type: "INSERT_SELECT", appId, fields, select, ...validation2 };
|
|
32866
32868
|
}
|
|
32867
32869
|
this.expect("VALUES" /* VALUES */);
|
|
32868
32870
|
const values = [];
|
|
@@ -32872,7 +32874,11 @@ var Parser = class {
|
|
|
32872
32874
|
this.expect(")" /* RPAREN */);
|
|
32873
32875
|
values.push(row);
|
|
32874
32876
|
} while (this.consume("," /* COMMA */));
|
|
32875
|
-
|
|
32877
|
+
const validation = this.parseDmlControlSuffix();
|
|
32878
|
+
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
32879
|
+
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());
|
|
32880
|
+
}
|
|
32881
|
+
return subtableCode ? { type: "INSERT", appId, subtableCode, fields, values, ...validation } : { type: "INSERT", appId, fields, values, ...validation };
|
|
32876
32882
|
}
|
|
32877
32883
|
parseUpsert() {
|
|
32878
32884
|
this.expect("UPSERT" /* UPSERT */);
|
|
@@ -32889,7 +32895,8 @@ var Parser = class {
|
|
|
32889
32895
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
32890
32896
|
const select = this.parseSelect();
|
|
32891
32897
|
const keyFields2 = this.parseOnDuplicate();
|
|
32892
|
-
|
|
32898
|
+
const validation2 = this.parseDmlControlSuffix();
|
|
32899
|
+
return { type: "UPSERT_SELECT", appId, fields, select, keyFields: keyFields2, ...validation2 };
|
|
32893
32900
|
}
|
|
32894
32901
|
this.expect("VALUES" /* VALUES */);
|
|
32895
32902
|
const values = [];
|
|
@@ -32899,7 +32906,8 @@ var Parser = class {
|
|
|
32899
32906
|
this.expect(")" /* RPAREN */);
|
|
32900
32907
|
} while (this.consume("," /* COMMA */));
|
|
32901
32908
|
const keyFields = this.parseOnDuplicate();
|
|
32902
|
-
|
|
32909
|
+
const validation = this.parseDmlControlSuffix();
|
|
32910
|
+
return { type: "UPSERT", appId, fields, values, keyFields, ...validation };
|
|
32903
32911
|
}
|
|
32904
32912
|
parseOnDuplicate() {
|
|
32905
32913
|
this.expectKeyword("ON" /* ON */, "UPSERT \u306B\u306F ON DUPLICATE (\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9) \u304C\u5FC5\u8981\u3067\u3059");
|
|
@@ -32979,10 +32987,14 @@ var Parser = class {
|
|
|
32979
32987
|
if (!table.alias) {
|
|
32980
32988
|
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
32981
32989
|
}
|
|
32990
|
+
if (table.alias.toLowerCase() === `app${appId}`.toLowerCase()) {
|
|
32991
|
+
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());
|
|
32992
|
+
}
|
|
32982
32993
|
from = {
|
|
32983
32994
|
appId: table.appId,
|
|
32984
32995
|
cteName: table.cteName,
|
|
32985
32996
|
alias: table.alias,
|
|
32997
|
+
targetJoinField: "",
|
|
32986
32998
|
joinKeyField: "",
|
|
32987
32999
|
targetFilter: null
|
|
32988
33000
|
};
|
|
@@ -33001,6 +33013,7 @@ var Parser = class {
|
|
|
33001
33013
|
}
|
|
33002
33014
|
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
33003
33015
|
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
33016
|
+
from.targetJoinField = decomposed.targetJoinField;
|
|
33004
33017
|
from.joinKeyField = decomposed.joinKeyField;
|
|
33005
33018
|
from.targetFilter = decomposed.targetFilter;
|
|
33006
33019
|
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
@@ -33009,8 +33022,66 @@ var Parser = class {
|
|
|
33009
33022
|
whereTok
|
|
33010
33023
|
);
|
|
33011
33024
|
}
|
|
33012
|
-
|
|
33013
|
-
|
|
33025
|
+
const validation = this.parseDmlControlSuffix();
|
|
33026
|
+
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
33027
|
+
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());
|
|
33028
|
+
}
|
|
33029
|
+
if (from !== null) return { type: "UPDATE", appId, assignments, where, from, ...validation };
|
|
33030
|
+
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where, ...validation } : { type: "UPDATE", appId, assignments, where, ...validation };
|
|
33031
|
+
}
|
|
33032
|
+
/** DML末尾の VALIDATE ONLY または ON ERROR SKIP。各語はsoft keyword。 */
|
|
33033
|
+
parseDmlControlSuffix() {
|
|
33034
|
+
if (this.peek().kind === "ON" /* ON */) return this.parseOnErrorSkipSuffix();
|
|
33035
|
+
if (this.isSoftKeyword("REJECT")) {
|
|
33036
|
+
throw new ParseError("REJECT LIMIT \u306B\u306F ON ERROR SKIP INTO \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
33037
|
+
}
|
|
33038
|
+
if (!this.isSoftKeyword("VALIDATE")) return {};
|
|
33039
|
+
const validateTok = this.advance();
|
|
33040
|
+
if (!this.isSoftKeyword("ONLY")) {
|
|
33041
|
+
throw new ParseError("VALIDATE \u306E\u5F8C\u306B\u306F ONLY \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
33042
|
+
}
|
|
33043
|
+
this.advance();
|
|
33044
|
+
let validationErrorTable = null;
|
|
33045
|
+
if (this.consume("INTO" /* INTO */)) {
|
|
33046
|
+
const tok = this.peek();
|
|
33047
|
+
if (tok.kind !== "IDENT" /* IDENT */ || !tok.value.startsWith("#")) {
|
|
33048
|
+
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);
|
|
33049
|
+
}
|
|
33050
|
+
validationErrorTable = this.parseTableName();
|
|
33051
|
+
}
|
|
33052
|
+
if (this.peek().kind === "ON" /* ON */ || this.isSoftKeyword("REJECT")) {
|
|
33053
|
+
throw new ParseError("VALIDATE ONLY \u3068 ON ERROR / REJECT LIMIT \u306F\u4F75\u8A18\u3067\u304D\u307E\u305B\u3093", validateTok);
|
|
33054
|
+
}
|
|
33055
|
+
return { validateOnly: true, validationErrorTable };
|
|
33056
|
+
}
|
|
33057
|
+
parseOnErrorSkipSuffix() {
|
|
33058
|
+
const onTok = this.advance();
|
|
33059
|
+
if (!this.isSoftKeyword("ERROR")) throw new ParseError("ON \u306E\u5F8C\u306B\u306F ERROR \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
33060
|
+
this.advance();
|
|
33061
|
+
if (!this.isSoftKeyword("SKIP")) throw new ParseError("ON ERROR \u306E\u5F8C\u306B\u306F SKIP \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
33062
|
+
this.advance();
|
|
33063
|
+
this.expect("INTO" /* INTO */, "ON ERROR SKIP \u306B\u306F INTO #\u4E00\u6642\u30C6\u30FC\u30D6\u30EB \u304C\u5FC5\u8981\u3067\u3059");
|
|
33064
|
+
const tableTok = this.peek();
|
|
33065
|
+
if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
|
|
33066
|
+
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);
|
|
33067
|
+
}
|
|
33068
|
+
const errorTable = this.parseTableName();
|
|
33069
|
+
let rejectLimit = null;
|
|
33070
|
+
if (this.isSoftKeyword("REJECT")) {
|
|
33071
|
+
this.advance();
|
|
33072
|
+
this.expect("LIMIT" /* LIMIT */, "REJECT \u306E\u5F8C\u306B\u306F LIMIT \u304C\u5FC5\u8981\u3067\u3059");
|
|
33073
|
+
const tok = this.expect("NUMBER" /* NUMBER */, "REJECT LIMIT \u306B\u306F 0 \u4EE5\u4E0A\u306E\u6574\u6570\u304C\u5FC5\u8981\u3067\u3059");
|
|
33074
|
+
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);
|
|
33075
|
+
rejectLimit = Number(tok.value);
|
|
33076
|
+
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);
|
|
33077
|
+
}
|
|
33078
|
+
if (this.isSoftKeyword("REJECT") || this.isSoftKeyword("VALIDATE") || this.peek().kind === "ON" /* ON */) {
|
|
33079
|
+
throw new ParseError("ON ERROR SKIP \u306E\u53E5\u304C\u91CD\u8907\u307E\u305F\u306F\u7AF6\u5408\u3057\u3066\u3044\u307E\u3059", onTok);
|
|
33080
|
+
}
|
|
33081
|
+
return { onErrorSkip: true, errorTable, rejectLimit };
|
|
33082
|
+
}
|
|
33083
|
+
isSoftKeyword(value) {
|
|
33084
|
+
return this.peek().kind === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === value;
|
|
33014
33085
|
}
|
|
33015
33086
|
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
33016
33087
|
for (const assignment of assignments) {
|
|
@@ -33035,11 +33106,11 @@ var Parser = class {
|
|
|
33035
33106
|
const leaves = this.flattenTopLevelAnd(where);
|
|
33036
33107
|
const joins = [];
|
|
33037
33108
|
leaves.forEach((leaf, index) => {
|
|
33038
|
-
const
|
|
33039
|
-
if (
|
|
33109
|
+
const matched = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
33110
|
+
if (matched !== null) joins.push({ index, ...matched });
|
|
33040
33111
|
});
|
|
33041
33112
|
if (joins.length !== 1) {
|
|
33042
|
-
throw new ParseError("UPDATE ... FROM \u306E WHERE \u306B\u306F target
|
|
33113
|
+
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);
|
|
33043
33114
|
}
|
|
33044
33115
|
const join = joins[0];
|
|
33045
33116
|
for (let i = 0; i < leaves.length; i++) {
|
|
@@ -33055,7 +33126,7 @@ var Parser = class {
|
|
|
33055
33126
|
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
33056
33127
|
null
|
|
33057
33128
|
);
|
|
33058
|
-
return { joinKeyField: join.sourceField, targetFilter };
|
|
33129
|
+
return { targetJoinField: join.targetField, joinKeyField: join.sourceField, targetFilter };
|
|
33059
33130
|
}
|
|
33060
33131
|
flattenTopLevelAnd(expr) {
|
|
33061
33132
|
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
@@ -33069,16 +33140,20 @@ var Parser = class {
|
|
|
33069
33140
|
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
33070
33141
|
if (right === null) return null;
|
|
33071
33142
|
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
33072
|
-
if (this.
|
|
33073
|
-
|
|
33143
|
+
if (this.isTargetRef(left, targetAppId) && this.isSourceRef(right, sourceAlias)) {
|
|
33144
|
+
return { targetField: left.field, sourceField: right.field };
|
|
33145
|
+
}
|
|
33146
|
+
if (this.isSourceRef(left, sourceAlias) && this.isTargetRef(right, targetAppId)) {
|
|
33147
|
+
return { targetField: right.field, sourceField: left.field };
|
|
33148
|
+
}
|
|
33074
33149
|
return null;
|
|
33075
33150
|
}
|
|
33076
33151
|
splitQualifiedField(field) {
|
|
33077
33152
|
const dot = field.indexOf(".");
|
|
33078
33153
|
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
33079
33154
|
}
|
|
33080
|
-
|
|
33081
|
-
return ref.
|
|
33155
|
+
isTargetRef(ref, appId) {
|
|
33156
|
+
return ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase();
|
|
33082
33157
|
}
|
|
33083
33158
|
isSourceRef(ref, alias) {
|
|
33084
33159
|
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
@@ -33405,6 +33480,15 @@ function isDmlType(type) {
|
|
|
33405
33480
|
function isReadOnlyType(type) {
|
|
33406
33481
|
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";
|
|
33407
33482
|
}
|
|
33483
|
+
function writesKintone(stmt) {
|
|
33484
|
+
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
33485
|
+
}
|
|
33486
|
+
function isReadOnlyStatement(stmt) {
|
|
33487
|
+
return !writesKintone(stmt) && (isReadOnlyType(stmt.type) || isDmlType(stmt.type));
|
|
33488
|
+
}
|
|
33489
|
+
function requiresCompleteInput(stmt) {
|
|
33490
|
+
return isDmlType(stmt.type);
|
|
33491
|
+
}
|
|
33408
33492
|
function hasWhereClause(stmt) {
|
|
33409
33493
|
if (!stmt || typeof stmt !== "object") return false;
|
|
33410
33494
|
const obj = stmt;
|
|
@@ -34632,11 +34716,17 @@ function analyzeBatch(statements) {
|
|
|
34632
34716
|
}
|
|
34633
34717
|
}
|
|
34634
34718
|
const defined = /* @__PURE__ */ new Map();
|
|
34719
|
+
const validationSchemas = /* @__PURE__ */ new Map();
|
|
34635
34720
|
const createdOrder = [];
|
|
34636
34721
|
const results = [];
|
|
34637
34722
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
34638
34723
|
const variableOrder = [];
|
|
34639
34724
|
statements.forEach((stmt, index) => {
|
|
34725
|
+
const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
34726
|
+
if (statements.length === 1 && validationTable) {
|
|
34727
|
+
const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
34728
|
+
throw new BatchAnalysisError(message, index);
|
|
34729
|
+
}
|
|
34640
34730
|
const statementType = getStatementType(stmt);
|
|
34641
34731
|
const created = [];
|
|
34642
34732
|
const dropped = [];
|
|
@@ -34691,6 +34781,28 @@ function analyzeBatch(statements) {
|
|
|
34691
34781
|
}
|
|
34692
34782
|
dependsOn.add(at);
|
|
34693
34783
|
}
|
|
34784
|
+
if (validationTable) {
|
|
34785
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
34786
|
+
const signature = JSON.stringify(payloadFields);
|
|
34787
|
+
const at = defined.get(validationTable);
|
|
34788
|
+
if (at === void 0) {
|
|
34789
|
+
defined.set(validationTable, index);
|
|
34790
|
+
validationSchemas.set(validationTable, signature);
|
|
34791
|
+
createdOrder.push(validationTable);
|
|
34792
|
+
created.push(validationTable);
|
|
34793
|
+
if (defined.size > MAX_TEMP_TABLES) {
|
|
34794
|
+
throw new BatchAnalysisError(`ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`, index);
|
|
34795
|
+
}
|
|
34796
|
+
} else {
|
|
34797
|
+
if (validationSchemas.get(validationTable) !== signature) {
|
|
34798
|
+
throw new BatchAnalysisError(
|
|
34799
|
+
`ParseError: validation error table ${validationTable} has a different payload schema.`,
|
|
34800
|
+
index
|
|
34801
|
+
);
|
|
34802
|
+
}
|
|
34803
|
+
dependsOn.add(at);
|
|
34804
|
+
}
|
|
34805
|
+
}
|
|
34694
34806
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
34695
34807
|
if (defined.has(stmt.name)) {
|
|
34696
34808
|
throw new BatchAnalysisError(
|
|
@@ -34723,8 +34835,8 @@ function analyzeBatch(statements) {
|
|
|
34723
34835
|
results.push({
|
|
34724
34836
|
index,
|
|
34725
34837
|
statementType,
|
|
34726
|
-
isDml:
|
|
34727
|
-
isReadOnly:
|
|
34838
|
+
isDml: writesKintone(stmt),
|
|
34839
|
+
isReadOnly: isReadOnlyStatement(stmt),
|
|
34728
34840
|
hasWhere: hasWhereClause(stmt),
|
|
34729
34841
|
insertValuesCount: getInsertValuesCount(stmt),
|
|
34730
34842
|
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
@@ -34734,10 +34846,15 @@ function analyzeBatch(statements) {
|
|
|
34734
34846
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
34735
34847
|
tempOnlySource,
|
|
34736
34848
|
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null,
|
|
34737
|
-
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null
|
|
34849
|
+
isUpdateFrom: stmt.type === "UPDATE" && stmt.from != null,
|
|
34850
|
+
isValidationOnly: "validateOnly" in stmt && stmt.validateOnly === true,
|
|
34851
|
+
isOnErrorSkip: "onErrorSkip" in stmt && stmt.onErrorSkip === true,
|
|
34852
|
+
requiresCompleteInput: requiresCompleteInput(stmt)
|
|
34738
34853
|
});
|
|
34739
34854
|
});
|
|
34740
34855
|
const containsDml = results.some((r) => r.isDml);
|
|
34856
|
+
const containsValidationOnly = results.some((r) => r.isValidationOnly);
|
|
34857
|
+
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
34741
34858
|
const variables = variableOrder.map((name) => ({
|
|
34742
34859
|
name,
|
|
34743
34860
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
@@ -34746,6 +34863,8 @@ function analyzeBatch(statements) {
|
|
|
34746
34863
|
statementCount: statements.length,
|
|
34747
34864
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
34748
34865
|
containsDml,
|
|
34866
|
+
containsValidationOnly,
|
|
34867
|
+
requiresCompleteInput: needsCompleteInput,
|
|
34749
34868
|
tempTables: createdOrder,
|
|
34750
34869
|
variables,
|
|
34751
34870
|
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
@@ -35555,6 +35674,18 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
35555
35674
|
return "";
|
|
35556
35675
|
}
|
|
35557
35676
|
function toKintoneValue(value, fieldType) {
|
|
35677
|
+
const result = normalizeDmlSqlValue(value, fieldType);
|
|
35678
|
+
if (!result.ok) throw new DmlConvertError(result.message);
|
|
35679
|
+
return result.value;
|
|
35680
|
+
}
|
|
35681
|
+
function normalizeDmlSqlValue(value, fieldType) {
|
|
35682
|
+
try {
|
|
35683
|
+
return { ok: true, value: convertDmlSqlValue(value, fieldType) };
|
|
35684
|
+
} catch (e) {
|
|
35685
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
35686
|
+
}
|
|
35687
|
+
}
|
|
35688
|
+
function convertDmlSqlValue(value, fieldType) {
|
|
35558
35689
|
switch (value.type) {
|
|
35559
35690
|
case "VARIABLE":
|
|
35560
35691
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
@@ -35859,7 +35990,7 @@ function hasAggregateColumns(columns) {
|
|
|
35859
35990
|
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr)
|
|
35860
35991
|
);
|
|
35861
35992
|
}
|
|
35862
|
-
function applyGroupBy(rows, groupByKeys, columns) {
|
|
35993
|
+
function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
|
|
35863
35994
|
const groups = /* @__PURE__ */ new Map();
|
|
35864
35995
|
for (const row of rows) {
|
|
35865
35996
|
const key = groupByKeys.map((k) => evalGroupByKey(k, row)).join("\0");
|
|
@@ -35883,15 +36014,15 @@ function applyGroupBy(rows, groupByKeys, columns) {
|
|
|
35883
36014
|
for (const col of columns) {
|
|
35884
36015
|
if (col.type === "AGGREGATE") {
|
|
35885
36016
|
const syntheticKey = aggregateSyntheticName2(col.func, col.distinct, col.arg);
|
|
35886
|
-
const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows));
|
|
36017
|
+
const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows, resolveAggSortKind));
|
|
35887
36018
|
outRow[col.alias ?? syntheticKey] = value;
|
|
35888
36019
|
if (col.alias) outRow[syntheticKey] = value;
|
|
35889
36020
|
} else if (col.type === "ARITH_AGG_COL") {
|
|
35890
36021
|
const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
|
|
35891
|
-
outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows));
|
|
36022
|
+
outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows, resolveAggSortKind));
|
|
35892
36023
|
} else if (col.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(col.expr)) {
|
|
35893
36024
|
const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
|
|
35894
|
-
const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows);
|
|
36025
|
+
const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
|
|
35895
36026
|
outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
|
|
35896
36027
|
}
|
|
35897
36028
|
}
|
|
@@ -35904,7 +36035,7 @@ function evalGroupByKey(key, row) {
|
|
|
35904
36035
|
if (key.type === "FUNC_KEY") return evalStringFunc(key.expr, row);
|
|
35905
36036
|
return String(evalArithExpr(key.expr, row));
|
|
35906
36037
|
}
|
|
35907
|
-
function evalAggregate(func, distinct, arg, rows) {
|
|
36038
|
+
function evalAggregate(func, distinct, arg, rows, resolveAggSortKind) {
|
|
35908
36039
|
if (arg.type === "WILDCARD") {
|
|
35909
36040
|
return func === "COUNT" ? rows.length : 0;
|
|
35910
36041
|
}
|
|
@@ -35924,6 +36055,11 @@ function evalAggregate(func, distinct, arg, rows) {
|
|
|
35924
36055
|
}
|
|
35925
36056
|
const eff = distinct ? [...new Set(strValues)] : strValues;
|
|
35926
36057
|
if (func === "COUNT") return eff.length;
|
|
36058
|
+
const sortKind = (func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" ? resolveAggSortKind?.(toAggregateFieldRef(arg.field)) : void 0;
|
|
36059
|
+
if (sortKind === "string") {
|
|
36060
|
+
if (eff.length === 0) return "";
|
|
36061
|
+
return func === "MAX" ? maxStringOf(eff) : minStringOf(eff);
|
|
36062
|
+
}
|
|
35927
36063
|
const nums = eff.map(Number);
|
|
35928
36064
|
switch (func) {
|
|
35929
36065
|
case "SUM":
|
|
@@ -35937,6 +36073,20 @@ function evalAggregate(func, distinct, arg, rows) {
|
|
|
35937
36073
|
return nums.length === 0 ? 0 : minOf(nums);
|
|
35938
36074
|
}
|
|
35939
36075
|
}
|
|
36076
|
+
function toAggregateFieldRef(field) {
|
|
36077
|
+
const dot = field.indexOf(".");
|
|
36078
|
+
return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
|
|
36079
|
+
}
|
|
36080
|
+
function maxStringOf(values) {
|
|
36081
|
+
let value = values[0];
|
|
36082
|
+
for (const candidate of values) if (candidate > value) value = candidate;
|
|
36083
|
+
return value;
|
|
36084
|
+
}
|
|
36085
|
+
function minStringOf(values) {
|
|
36086
|
+
let value = values[0];
|
|
36087
|
+
for (const candidate of values) if (candidate < value) value = candidate;
|
|
36088
|
+
return value;
|
|
36089
|
+
}
|
|
35940
36090
|
function maxOf(nums) {
|
|
35941
36091
|
let m = nums[0];
|
|
35942
36092
|
for (const n of nums) if (n > m) m = n;
|
|
@@ -35947,11 +36097,11 @@ function minOf(nums) {
|
|
|
35947
36097
|
for (const n of nums) if (n < m) m = n;
|
|
35948
36098
|
return m;
|
|
35949
36099
|
}
|
|
35950
|
-
function evalAggArithExpr(node, rows) {
|
|
36100
|
+
function evalAggArithExpr(node, rows, resolveAggSortKind) {
|
|
35951
36101
|
if (node.type === "NUMBER") return node.value;
|
|
35952
|
-
if (node.type === "AGG_REF") return evalAggregate(node.func, node.distinct, node.arg, rows);
|
|
35953
|
-
const l = evalAggArithExpr(node.left, rows);
|
|
35954
|
-
const r = evalAggArithExpr(node.right, rows);
|
|
36102
|
+
if (node.type === "AGG_REF") return Number(evalAggregate(node.func, node.distinct, node.arg, rows, resolveAggSortKind));
|
|
36103
|
+
const l = evalAggArithExpr(node.left, rows, resolveAggSortKind);
|
|
36104
|
+
const r = evalAggArithExpr(node.right, rows, resolveAggSortKind);
|
|
35955
36105
|
switch (node.op) {
|
|
35956
36106
|
case "+":
|
|
35957
36107
|
return l + r;
|
|
@@ -36296,26 +36446,24 @@ function hasAggregateInStringFuncArg(arg) {
|
|
|
36296
36446
|
function hasAggregateInStringFuncExpr2(expr) {
|
|
36297
36447
|
return expr.args.some((arg) => hasAggregateInStringFuncArg(arg));
|
|
36298
36448
|
}
|
|
36299
|
-
function resolveAggInStringFuncArg(arg, rows) {
|
|
36449
|
+
function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
|
|
36300
36450
|
if (arg.type === "AGG_REF") {
|
|
36301
|
-
|
|
36302
|
-
|
|
36303
|
-
value: evalAggregate(arg.func, arg.distinct, arg.arg, rows)
|
|
36304
|
-
};
|
|
36451
|
+
const value = evalAggregate(arg.func, arg.distinct, arg.arg, rows, resolveAggSortKind);
|
|
36452
|
+
return typeof value === "number" ? { type: "NUMBER", value } : { type: "STRING", value };
|
|
36305
36453
|
}
|
|
36306
36454
|
if (arg.type === "AGG_ARITH") {
|
|
36307
|
-
return { type: "NUMBER", value: evalAggArithExpr(arg, rows) };
|
|
36455
|
+
return { type: "NUMBER", value: evalAggArithExpr(arg, rows, resolveAggSortKind) };
|
|
36308
36456
|
}
|
|
36309
36457
|
if (arg.type === "STRING_FUNC") {
|
|
36310
|
-
return resolveAggInStringFuncExpr(arg, rows);
|
|
36458
|
+
return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
|
|
36311
36459
|
}
|
|
36312
36460
|
return arg;
|
|
36313
36461
|
}
|
|
36314
|
-
function resolveAggInStringFuncExpr(expr, rows) {
|
|
36462
|
+
function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
|
|
36315
36463
|
return {
|
|
36316
36464
|
type: "STRING_FUNC",
|
|
36317
36465
|
func: expr.func,
|
|
36318
|
-
args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows))
|
|
36466
|
+
args: expr.args.map((arg) => resolveAggInStringFuncArg(arg, rows, resolveAggSortKind))
|
|
36319
36467
|
};
|
|
36320
36468
|
}
|
|
36321
36469
|
function runFullScan(input) {
|
|
@@ -36327,6 +36475,7 @@ function runFullScan(input) {
|
|
|
36327
36475
|
sortKinds,
|
|
36328
36476
|
fieldTypeResolver,
|
|
36329
36477
|
havingFieldTypeResolver,
|
|
36478
|
+
aggregateSortKindResolver,
|
|
36330
36479
|
appliedKlikes,
|
|
36331
36480
|
sourceColumns
|
|
36332
36481
|
} = input;
|
|
@@ -36342,7 +36491,7 @@ function runFullScan(input) {
|
|
|
36342
36491
|
}
|
|
36343
36492
|
rows = applyFilter(rows, stmt.where, fieldTypeResolver, appliedKlikes);
|
|
36344
36493
|
if (stmt.groupBy.length > 0 || hasAggregateColumns(stmt.columns)) {
|
|
36345
|
-
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns);
|
|
36494
|
+
rows = applyGroupBy(rows, stmt.groupBy, stmt.columns, aggregateSortKindResolver);
|
|
36346
36495
|
}
|
|
36347
36496
|
rows = applyHaving(rows, stmt.having, havingFieldTypeResolver);
|
|
36348
36497
|
if (stmt.distinct) {
|
|
@@ -36395,6 +36544,228 @@ function toFlatString(value) {
|
|
|
36395
36544
|
}
|
|
36396
36545
|
}
|
|
36397
36546
|
|
|
36547
|
+
// src/core/dmlValidation.ts
|
|
36548
|
+
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
36549
|
+
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
36550
|
+
function validateAndNormalizeDmlValue(raw, field) {
|
|
36551
|
+
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
36552
|
+
const original = rawScalarText(raw);
|
|
36553
|
+
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
36554
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
36555
|
+
}
|
|
36556
|
+
}
|
|
36557
|
+
let value;
|
|
36558
|
+
try {
|
|
36559
|
+
value = normalizeRaw(raw, field.fieldType);
|
|
36560
|
+
} catch (e) {
|
|
36561
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
36562
|
+
return { ok: false, code: typeCode(field.fieldType), message };
|
|
36563
|
+
}
|
|
36564
|
+
if (field.required && isEmpty(value)) {
|
|
36565
|
+
return { ok: false, code: "ERR_REQUIRED", message: `${field.code} \u306F\u5FC5\u9808\u3067\u3059` };
|
|
36566
|
+
}
|
|
36567
|
+
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
36568
|
+
const text = String(value);
|
|
36569
|
+
if (!isFiniteDecimal(text)) {
|
|
36570
|
+
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36571
|
+
}
|
|
36572
|
+
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
36573
|
+
return { ok: false, code: "ERR_RANGE_MIN", message: `${field.code} \u306F ${field.minValue} \u4EE5\u4E0A\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36574
|
+
}
|
|
36575
|
+
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
36576
|
+
return { ok: false, code: "ERR_RANGE_MAX", message: `${field.code} \u306F ${field.maxValue} \u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36577
|
+
}
|
|
36578
|
+
}
|
|
36579
|
+
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
36580
|
+
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
36581
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
36582
|
+
}
|
|
36583
|
+
}
|
|
36584
|
+
if (typeof value === "string") {
|
|
36585
|
+
const length = value.length;
|
|
36586
|
+
const min = field.minLength == null ? null : Number(field.minLength);
|
|
36587
|
+
const max = field.maxLength == null ? null : Number(field.maxLength);
|
|
36588
|
+
if (Number.isFinite(min) && length < min) {
|
|
36589
|
+
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` };
|
|
36590
|
+
}
|
|
36591
|
+
if (Number.isFinite(max) && length > max) {
|
|
36592
|
+
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` };
|
|
36593
|
+
}
|
|
36594
|
+
}
|
|
36595
|
+
if (CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
|
|
36596
|
+
const selected = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
36597
|
+
if (selected.some((choice) => !(choice in field.optionOrder))) {
|
|
36598
|
+
return { ok: false, code: "ERR_CHOICE_INVALID", message: `${field.code} \u306B\u5B9A\u7FA9\u5916\u306E\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u3059` };
|
|
36599
|
+
}
|
|
36600
|
+
}
|
|
36601
|
+
return { ok: true, value };
|
|
36602
|
+
}
|
|
36603
|
+
function rawScalarText(raw) {
|
|
36604
|
+
if (raw == null) return "";
|
|
36605
|
+
if (isSqlValue(raw) && (raw.type === "STRING" || raw.type === "NUMBER")) return String(raw.value);
|
|
36606
|
+
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
36607
|
+
}
|
|
36608
|
+
function isValidTemporalInput(value, type) {
|
|
36609
|
+
if (type === "DATE") return isValidTemporal(value.replace(/\//g, "-"), "DATE");
|
|
36610
|
+
if (type === "TIME") return isValidTemporal(value, "TIME");
|
|
36611
|
+
let normalized = value.replace(/\//g, "-").replace(" ", "T");
|
|
36612
|
+
if (/T\d{2}:\d{2}$/.test(normalized)) normalized += ":00";
|
|
36613
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(normalized)) {
|
|
36614
|
+
return isValidTemporal(normalized.slice(0, 10), "DATE") && isValidTemporal(normalized.slice(11), "TIME");
|
|
36615
|
+
}
|
|
36616
|
+
return isValidTemporal(normalized, "DATETIME");
|
|
36617
|
+
}
|
|
36618
|
+
function normalizeRaw(raw, fieldType) {
|
|
36619
|
+
if (isSqlValue(raw)) {
|
|
36620
|
+
const normalized = normalizeDmlSqlValue(raw, fieldType);
|
|
36621
|
+
if (!normalized.ok) throw new Error(normalized.message);
|
|
36622
|
+
return normalized.value;
|
|
36623
|
+
}
|
|
36624
|
+
if (Array.isArray(raw)) return raw.map((v) => typeof v === "object" && v !== null && "code" in v ? String(v.code) : String(v));
|
|
36625
|
+
const text = raw == null ? "" : String(raw);
|
|
36626
|
+
if (ARRAY_TYPES2.has(fieldType)) {
|
|
36627
|
+
if (text === "") return [];
|
|
36628
|
+
try {
|
|
36629
|
+
const parsed = JSON.parse(text);
|
|
36630
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
36631
|
+
} catch {
|
|
36632
|
+
}
|
|
36633
|
+
return text.split(",").map((v) => v.trim());
|
|
36634
|
+
}
|
|
36635
|
+
return text;
|
|
36636
|
+
}
|
|
36637
|
+
function isSqlValue(value) {
|
|
36638
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
36639
|
+
}
|
|
36640
|
+
function isEmptyDmlValue(value) {
|
|
36641
|
+
if (value == null || value === "") return true;
|
|
36642
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
36643
|
+
if (isSqlValue(value)) {
|
|
36644
|
+
if (value.type === "STRING") return value.value === "";
|
|
36645
|
+
if (value.type === "ARRAY") return value.elements.length === 0;
|
|
36646
|
+
}
|
|
36647
|
+
return false;
|
|
36648
|
+
}
|
|
36649
|
+
function isEmpty(value) {
|
|
36650
|
+
return value === "" || Array.isArray(value) && value.length === 0;
|
|
36651
|
+
}
|
|
36652
|
+
function typeCode(type) {
|
|
36653
|
+
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
36654
|
+
}
|
|
36655
|
+
function isFiniteDecimal(value) {
|
|
36656
|
+
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
36657
|
+
}
|
|
36658
|
+
function compareDecimal(left, right) {
|
|
36659
|
+
const normalize = (input) => {
|
|
36660
|
+
let s = input.trim();
|
|
36661
|
+
let sign = 1;
|
|
36662
|
+
if (s.startsWith("-")) {
|
|
36663
|
+
sign = -1;
|
|
36664
|
+
s = s.slice(1);
|
|
36665
|
+
} else if (s.startsWith("+")) s = s.slice(1);
|
|
36666
|
+
let [whole, fraction = ""] = s.split(".");
|
|
36667
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
36668
|
+
fraction = fraction.replace(/0+$/, "");
|
|
36669
|
+
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
36670
|
+
return { sign, whole, fraction };
|
|
36671
|
+
};
|
|
36672
|
+
const a = normalize(left);
|
|
36673
|
+
const b = normalize(right);
|
|
36674
|
+
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
36675
|
+
const direction = a.sign;
|
|
36676
|
+
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
36677
|
+
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
36678
|
+
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
36679
|
+
const af = a.fraction.padEnd(width, "0");
|
|
36680
|
+
const bf = b.fraction.padEnd(width, "0");
|
|
36681
|
+
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
36682
|
+
}
|
|
36683
|
+
function isValidTemporal(value, type) {
|
|
36684
|
+
if (type === "TIME") {
|
|
36685
|
+
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
36686
|
+
return m2 !== null && Number(m2[1]) <= 23 && Number(m2[2]) <= 59 && Number(m2[3] ?? 0) <= 59;
|
|
36687
|
+
}
|
|
36688
|
+
const datePart = type === "DATE" ? value : value.slice(0, 10);
|
|
36689
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart);
|
|
36690
|
+
if (!m) return false;
|
|
36691
|
+
const year = Number(m[1]);
|
|
36692
|
+
const month = Number(m[2]);
|
|
36693
|
+
const day = Number(m[3]);
|
|
36694
|
+
const date5 = new Date(Date.UTC(year, month - 1, day));
|
|
36695
|
+
if (date5.getUTCFullYear() !== year || date5.getUTCMonth() !== month - 1 || date5.getUTCDate() !== day) return false;
|
|
36696
|
+
if (type === "DATE") return true;
|
|
36697
|
+
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");
|
|
36698
|
+
}
|
|
36699
|
+
|
|
36700
|
+
// src/core/dmlValidationCandidates.ts
|
|
36701
|
+
var VALIDATION_META_COLUMNS = [
|
|
36702
|
+
"$err_statement",
|
|
36703
|
+
"$err_operation",
|
|
36704
|
+
"$err_row",
|
|
36705
|
+
"$err_field",
|
|
36706
|
+
"$err_code",
|
|
36707
|
+
"$err_message"
|
|
36708
|
+
];
|
|
36709
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
36710
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
36711
|
+
const errors = [];
|
|
36712
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
36713
|
+
for (const candidate of candidates) {
|
|
36714
|
+
candidate.record ??= {};
|
|
36715
|
+
const rowErrors = [...candidate.preErrors];
|
|
36716
|
+
for (const code of targetFields) {
|
|
36717
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
36718
|
+
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
36719
|
+
else candidate.record[code] = { value: result.value };
|
|
36720
|
+
}
|
|
36721
|
+
if (candidate.mode === "create") {
|
|
36722
|
+
for (const info of fieldInfos) {
|
|
36723
|
+
if (info.inSubtable) continue;
|
|
36724
|
+
if (candidate.payload.has(info.code)) continue;
|
|
36725
|
+
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
36726
|
+
if (!emptyDefault) {
|
|
36727
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
36728
|
+
if (!defaultResult.ok) rowErrors.push({
|
|
36729
|
+
field: info.code,
|
|
36730
|
+
code: defaultResult.code,
|
|
36731
|
+
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
36732
|
+
});
|
|
36733
|
+
} else {
|
|
36734
|
+
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
36735
|
+
if (!emptyResult.ok) {
|
|
36736
|
+
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
36737
|
+
} else if (info.required) {
|
|
36738
|
+
rowErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
|
|
36739
|
+
}
|
|
36740
|
+
}
|
|
36741
|
+
}
|
|
36742
|
+
}
|
|
36743
|
+
if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
|
|
36744
|
+
for (const error51 of rowErrors) {
|
|
36745
|
+
const row = {};
|
|
36746
|
+
for (const field of payloadFields) row[field] = renderValidationValue(candidate.payload.get(field));
|
|
36747
|
+
row["$err_statement"] = String(statementNumber);
|
|
36748
|
+
row["$err_operation"] = operation;
|
|
36749
|
+
row["$err_row"] = String(candidate.rowNumber);
|
|
36750
|
+
row["$err_field"] = error51.field;
|
|
36751
|
+
row["$err_code"] = error51.code;
|
|
36752
|
+
row["$err_message"] = error51.message;
|
|
36753
|
+
errors.push(row);
|
|
36754
|
+
}
|
|
36755
|
+
}
|
|
36756
|
+
return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
|
|
36757
|
+
}
|
|
36758
|
+
function renderValidationValue(value) {
|
|
36759
|
+
if (value == null) return "";
|
|
36760
|
+
if (typeof value === "object" && "type" in value) {
|
|
36761
|
+
const sql = value;
|
|
36762
|
+
if (sql.type === "STRING" || sql.type === "NUMBER") return String(sql.value ?? "");
|
|
36763
|
+
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
36764
|
+
}
|
|
36765
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
36766
|
+
return String(value);
|
|
36767
|
+
}
|
|
36768
|
+
|
|
36398
36769
|
// src/execute.ts
|
|
36399
36770
|
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";
|
|
36400
36771
|
var SearchAbortedError = class extends Error {
|
|
@@ -36498,6 +36869,15 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
36498
36869
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
36499
36870
|
}
|
|
36500
36871
|
validateKlikeStatement(stmt);
|
|
36872
|
+
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
36873
|
+
if (stmt.validationErrorTable) {
|
|
36874
|
+
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
36875
|
+
}
|
|
36876
|
+
return executeDmlValidation(stmt, client, { ...options, onLimitReached: "error" }, cacheContext, void 0, 1);
|
|
36877
|
+
}
|
|
36878
|
+
if ("onErrorSkip" in stmt && stmt.onErrorSkip === true) {
|
|
36879
|
+
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
36880
|
+
}
|
|
36501
36881
|
switch (stmt.type) {
|
|
36502
36882
|
case "SELECT":
|
|
36503
36883
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -36539,6 +36919,17 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
36539
36919
|
}
|
|
36540
36920
|
}
|
|
36541
36921
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
36922
|
+
function appendValidationErrors(tempTables, name, columns, rows, maxRows) {
|
|
36923
|
+
const current = tempTables.get(name);
|
|
36924
|
+
if (current && (current.columns.length !== columns.length || current.columns.some((c, i) => c !== columns[i]))) {
|
|
36925
|
+
throw new Error(`ArgumentError: validation error table ${name} has a different schema.`);
|
|
36926
|
+
}
|
|
36927
|
+
const existingRows = current?.rows ?? [];
|
|
36928
|
+
if (existingRows.length + rows.length > maxRows) {
|
|
36929
|
+
throw new Error(`ArgumentError: temp table ${name} exceeds max rows (${maxRows}).`);
|
|
36930
|
+
}
|
|
36931
|
+
tempTables.set(name, { columns: [...columns], rows: [...existingRows, ...rows] });
|
|
36932
|
+
}
|
|
36542
36933
|
var BatchTimeoutError = class extends Error {
|
|
36543
36934
|
constructor() {
|
|
36544
36935
|
super("TimeoutError: batch timeout exceeded.");
|
|
@@ -36620,7 +37011,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
36620
37011
|
}
|
|
36621
37012
|
results.push({ ...base, status: "success", ...outcome });
|
|
36622
37013
|
} catch (e) {
|
|
36623
|
-
results.push({
|
|
37014
|
+
results.push({
|
|
37015
|
+
...base,
|
|
37016
|
+
status: "error",
|
|
37017
|
+
error: toBatchStatementError(e),
|
|
37018
|
+
...e instanceof RejectLimitExceededError ? { result: e.diagnostic } : {}
|
|
37019
|
+
});
|
|
36624
37020
|
failed.add(i);
|
|
36625
37021
|
if (e instanceof BatchTimeoutError) {
|
|
36626
37022
|
aborted2 = "timeout";
|
|
@@ -36679,6 +37075,38 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
36679
37075
|
}
|
|
36680
37076
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
36681
37077
|
validateKlikeStatement(resolvedStmt);
|
|
37078
|
+
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
37079
|
+
const result = await executeDmlValidation(
|
|
37080
|
+
resolvedStmt,
|
|
37081
|
+
client,
|
|
37082
|
+
{ ...options, onLimitReached: "error" },
|
|
37083
|
+
cacheContext,
|
|
37084
|
+
tempTables,
|
|
37085
|
+
info.index + 1
|
|
37086
|
+
);
|
|
37087
|
+
if (resolvedStmt.validationErrorTable) {
|
|
37088
|
+
appendValidationErrors(
|
|
37089
|
+
tempTables,
|
|
37090
|
+
resolvedStmt.validationErrorTable,
|
|
37091
|
+
result.columns,
|
|
37092
|
+
result.errors,
|
|
37093
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
37094
|
+
);
|
|
37095
|
+
}
|
|
37096
|
+
return { result };
|
|
37097
|
+
}
|
|
37098
|
+
if ("onErrorSkip" in resolvedStmt && resolvedStmt.onErrorSkip === true) {
|
|
37099
|
+
return {
|
|
37100
|
+
result: await executeOnErrorSkip(
|
|
37101
|
+
resolvedStmt,
|
|
37102
|
+
client,
|
|
37103
|
+
{ ...options, onLimitReached: "error" },
|
|
37104
|
+
cacheContext,
|
|
37105
|
+
tempTables,
|
|
37106
|
+
info.index + 1
|
|
37107
|
+
)
|
|
37108
|
+
};
|
|
37109
|
+
}
|
|
36682
37110
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
36683
37111
|
const materializeOptions = {
|
|
36684
37112
|
...options,
|
|
@@ -37212,6 +37640,116 @@ async function loadTypedInFieldTypes(stmt, client, cacheContext) {
|
|
|
37212
37640
|
const entries = await Promise.all([...appIds].map(async (appId) => [appId, await getFieldTypeMap(appId, client, cacheContext)]));
|
|
37213
37641
|
return new Map(entries);
|
|
37214
37642
|
}
|
|
37643
|
+
function aggregateFieldRef(field) {
|
|
37644
|
+
const dot = field.indexOf(".");
|
|
37645
|
+
return dot > 0 ? { type: "FIELD", tableAlias: field.slice(0, dot), field: field.slice(dot + 1) } : { type: "FIELD", tableAlias: null, field };
|
|
37646
|
+
}
|
|
37647
|
+
function collectAggregateRef(func, arg, out) {
|
|
37648
|
+
if ((func === "MIN" || func === "MAX") && arg.type === "FIELD_REF" && arg.field) {
|
|
37649
|
+
out.push(aggregateFieldRef(arg.field));
|
|
37650
|
+
}
|
|
37651
|
+
}
|
|
37652
|
+
function collectAggregateOperandRefs(node, out) {
|
|
37653
|
+
if (node.type === "AGG_REF") {
|
|
37654
|
+
collectAggregateRef(node.func, node.arg, out);
|
|
37655
|
+
return;
|
|
37656
|
+
}
|
|
37657
|
+
if (node.type === "AGG_ARITH") {
|
|
37658
|
+
collectAggregateOperandRefs(node.left, out);
|
|
37659
|
+
collectAggregateOperandRefs(node.right, out);
|
|
37660
|
+
}
|
|
37661
|
+
}
|
|
37662
|
+
function collectStringFuncAggregateRefs(expr, out) {
|
|
37663
|
+
for (const arg of expr.args) {
|
|
37664
|
+
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
37665
|
+
collectAggregateOperandRefs(arg, out);
|
|
37666
|
+
} else if (arg.type === "STRING_FUNC") {
|
|
37667
|
+
collectStringFuncAggregateRefs(arg, out);
|
|
37668
|
+
}
|
|
37669
|
+
}
|
|
37670
|
+
}
|
|
37671
|
+
function collectSelectAggregateSortRefs(columns) {
|
|
37672
|
+
const refs = [];
|
|
37673
|
+
for (const column of columns) {
|
|
37674
|
+
if (column.type === "AGGREGATE") {
|
|
37675
|
+
collectAggregateRef(column.func, column.arg, refs);
|
|
37676
|
+
} else if (column.type === "ARITH_AGG_COL") {
|
|
37677
|
+
collectAggregateOperandRefs(column.expr, refs);
|
|
37678
|
+
} else if (column.type === "STRFUNC_COL") {
|
|
37679
|
+
collectStringFuncAggregateRefs(column.expr, refs);
|
|
37680
|
+
}
|
|
37681
|
+
}
|
|
37682
|
+
return refs;
|
|
37683
|
+
}
|
|
37684
|
+
var AGGREGATE_STRING_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
37685
|
+
"SINGLE_LINE_TEXT",
|
|
37686
|
+
"MULTI_LINE_TEXT",
|
|
37687
|
+
"RICH_TEXT",
|
|
37688
|
+
"LINK",
|
|
37689
|
+
"DROP_DOWN",
|
|
37690
|
+
"RADIO_BUTTON",
|
|
37691
|
+
"STATUS",
|
|
37692
|
+
"DATE",
|
|
37693
|
+
"TIME",
|
|
37694
|
+
"DATETIME",
|
|
37695
|
+
"CREATED_TIME",
|
|
37696
|
+
"UPDATED_TIME"
|
|
37697
|
+
]);
|
|
37698
|
+
function aggregateSortKind(info) {
|
|
37699
|
+
if (info.sortKind !== void 0) return info.sortKind;
|
|
37700
|
+
if (info.fieldType === "NUMBER" || info.fieldType === "RECORD_NUMBER") return "number";
|
|
37701
|
+
return AGGREGATE_STRING_FIELD_TYPES.has(info.fieldType) ? "string" : void 0;
|
|
37702
|
+
}
|
|
37703
|
+
async function loadAggregateSortKindResolver(stmt, client, cacheContext) {
|
|
37704
|
+
const refs = collectSelectAggregateSortRefs(stmt.columns);
|
|
37705
|
+
if (refs.length === 0) return void 0;
|
|
37706
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
37707
|
+
const physicalTables = physicalSelectTables(stmt);
|
|
37708
|
+
for (const ref of refs) {
|
|
37709
|
+
if (ref.tableAlias !== null) {
|
|
37710
|
+
if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
37711
|
+
appIds.add(stmt.from.appId);
|
|
37712
|
+
continue;
|
|
37713
|
+
}
|
|
37714
|
+
const table = findTableForAlias(stmt, ref.tableAlias);
|
|
37715
|
+
if (table && table.cteName === null) appIds.add(table.appId);
|
|
37716
|
+
} else if (stmt.joins.length === 0) {
|
|
37717
|
+
if (stmt.from.cteName === null) appIds.add(stmt.from.appId);
|
|
37718
|
+
} else {
|
|
37719
|
+
if ([stmt.from, ...stmt.joins.map((join) => join.table)].some((table) => table.cteName !== null)) continue;
|
|
37720
|
+
for (const table of physicalTables) appIds.add(table.appId);
|
|
37721
|
+
}
|
|
37722
|
+
}
|
|
37723
|
+
if (appIds.size === 0) return void 0;
|
|
37724
|
+
const fieldInfosByApp = new Map(
|
|
37725
|
+
await Promise.all([...appIds].map(async (appId) => {
|
|
37726
|
+
const infos = await getFieldsCached(appId, client, cacheContext);
|
|
37727
|
+
return [appId, new Map(infos.map((info) => [info.code, info]))];
|
|
37728
|
+
}))
|
|
37729
|
+
);
|
|
37730
|
+
const tables = [stmt.from, ...stmt.joins.map((join) => join.table)];
|
|
37731
|
+
return (ref) => {
|
|
37732
|
+
let info;
|
|
37733
|
+
if (ref.tableAlias !== null) {
|
|
37734
|
+
if (ref.tableAlias === "_p" && stmt.from.subtableCode && stmt.from.cteName === null) {
|
|
37735
|
+
info = fieldInfosByApp.get(stmt.from.appId)?.get(ref.field);
|
|
37736
|
+
} else {
|
|
37737
|
+
const table = tables.find((candidate) => candidate.alias === ref.tableAlias);
|
|
37738
|
+
if (!table || table.cteName !== null) return void 0;
|
|
37739
|
+
info = fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field));
|
|
37740
|
+
}
|
|
37741
|
+
} else if (stmt.joins.length === 0) {
|
|
37742
|
+
if (stmt.from.cteName !== null) return void 0;
|
|
37743
|
+
info = fieldInfosByApp.get(stmt.from.appId)?.get(fieldCodeForTypeLookup(stmt.from, ref.field));
|
|
37744
|
+
} else {
|
|
37745
|
+
if (tables.some((table) => table.cteName !== null)) return void 0;
|
|
37746
|
+
const matches = physicalTables.map((table) => fieldInfosByApp.get(table.appId)?.get(fieldCodeForTypeLookup(table, ref.field))).filter((candidate) => candidate !== void 0);
|
|
37747
|
+
if (matches.length !== 1) return void 0;
|
|
37748
|
+
info = matches[0];
|
|
37749
|
+
}
|
|
37750
|
+
return info ? aggregateSortKind(info) : void 0;
|
|
37751
|
+
};
|
|
37752
|
+
}
|
|
37215
37753
|
function fieldCodeForTypeLookup(table, field) {
|
|
37216
37754
|
if (table.subtableCode && field.startsWith("_p.")) return field.slice(3);
|
|
37217
37755
|
return field;
|
|
@@ -37257,9 +37795,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
37257
37795
|
resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
|
|
37258
37796
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
|
|
37259
37797
|
]);
|
|
37260
|
-
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
37798
|
+
const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
|
|
37261
37799
|
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
37262
|
-
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
37800
|
+
loadTypedInFieldTypes(stmt, client, cacheContext),
|
|
37801
|
+
loadAggregateSortKindResolver(stmt, client, cacheContext)
|
|
37263
37802
|
]);
|
|
37264
37803
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
37265
37804
|
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
@@ -37347,6 +37886,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
37347
37886
|
sortKinds,
|
|
37348
37887
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
37349
37888
|
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
37889
|
+
aggregateSortKindResolver,
|
|
37350
37890
|
appliedKlikes: pushdownPlan.appliedKlikes
|
|
37351
37891
|
});
|
|
37352
37892
|
return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
|
|
@@ -37430,9 +37970,10 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
37430
37970
|
resolveSubqueries(stmt.having, client, options, cacheContext, cteCache),
|
|
37431
37971
|
resolveSelectCaseSubqueries(stmt, client, options, cacheContext, cteCache)
|
|
37432
37972
|
]);
|
|
37433
|
-
const [pushdownMeta, typedInFieldTypes] = await Promise.all([
|
|
37973
|
+
const [pushdownMeta, typedInFieldTypes, aggregateSortKindResolver] = await Promise.all([
|
|
37434
37974
|
loadTypedPushdownMeta(stmt, client, cacheContext),
|
|
37435
|
-
loadTypedInFieldTypes(stmt, client, cacheContext)
|
|
37975
|
+
loadTypedInFieldTypes(stmt, client, cacheContext),
|
|
37976
|
+
loadAggregateSortKindResolver(stmt, client, cacheContext)
|
|
37436
37977
|
]);
|
|
37437
37978
|
const fieldTypeResolvers = buildSelectFieldTypeResolvers(stmt, typedInFieldTypes);
|
|
37438
37979
|
const pushdownPlan = buildKlikePushdownPlan(stmt, pushdownMeta);
|
|
@@ -37504,6 +38045,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
|
|
|
37504
38045
|
sortKinds,
|
|
37505
38046
|
fieldTypeResolver: fieldTypeResolvers.row,
|
|
37506
38047
|
havingFieldTypeResolver: fieldTypeResolvers.having,
|
|
38048
|
+
aggregateSortKindResolver,
|
|
37507
38049
|
appliedKlikes: pushdownPlan.appliedKlikes,
|
|
37508
38050
|
sourceColumns
|
|
37509
38051
|
});
|
|
@@ -37827,7 +38369,7 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
37827
38369
|
}
|
|
37828
38370
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
37829
38371
|
const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
37830
|
-
const
|
|
38372
|
+
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
37831
38373
|
if (USER_TYPES2.has(dstFieldType ?? "")) {
|
|
37832
38374
|
if (raw === "") return [];
|
|
37833
38375
|
try {
|
|
@@ -37839,7 +38381,7 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
37839
38381
|
}
|
|
37840
38382
|
return raw.split(",").map((c) => ({ code: c.trim() }));
|
|
37841
38383
|
}
|
|
37842
|
-
if (
|
|
38384
|
+
if (ARRAY_TYPES3.has(dstFieldType ?? "")) {
|
|
37843
38385
|
if (raw === "") return [];
|
|
37844
38386
|
try {
|
|
37845
38387
|
const parsed = JSON.parse(raw);
|
|
@@ -37850,6 +38392,396 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
37850
38392
|
}
|
|
37851
38393
|
return raw;
|
|
37852
38394
|
}
|
|
38395
|
+
var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
38396
|
+
"CALC",
|
|
38397
|
+
"RECORD_NUMBER",
|
|
38398
|
+
"CREATOR",
|
|
38399
|
+
"CREATED_TIME",
|
|
38400
|
+
"MODIFIER",
|
|
38401
|
+
"UPDATED_TIME",
|
|
38402
|
+
"STATUS",
|
|
38403
|
+
"STATUS_ASSIGNEE",
|
|
38404
|
+
"CATEGORY",
|
|
38405
|
+
"REFERENCE_TABLE"
|
|
38406
|
+
]);
|
|
38407
|
+
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38408
|
+
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
38409
|
+
}
|
|
38410
|
+
var RejectLimitExceededError = class extends Error {
|
|
38411
|
+
constructor(message, diagnostic) {
|
|
38412
|
+
super(`RejectLimitExceededError: ${message}`);
|
|
38413
|
+
this.diagnostic = diagnostic;
|
|
38414
|
+
this.name = "RejectLimitExceededError";
|
|
38415
|
+
}
|
|
38416
|
+
};
|
|
38417
|
+
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38418
|
+
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
38419
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
38420
|
+
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
38421
|
+
throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
38422
|
+
}
|
|
38423
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
38424
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
38425
|
+
const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
|
|
38426
|
+
for (const code of targetFields) {
|
|
38427
|
+
const info = infoByCode.get(code);
|
|
38428
|
+
if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
38429
|
+
if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
|
|
38430
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
38431
|
+
}
|
|
38432
|
+
}
|
|
38433
|
+
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
38434
|
+
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
38435
|
+
candidates,
|
|
38436
|
+
operation,
|
|
38437
|
+
payloadFields,
|
|
38438
|
+
targetFields,
|
|
38439
|
+
fieldInfos,
|
|
38440
|
+
statementNumber
|
|
38441
|
+
);
|
|
38442
|
+
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
38443
|
+
const result = {
|
|
38444
|
+
type: "VALIDATION",
|
|
38445
|
+
operation,
|
|
38446
|
+
validatedRows: candidates.length,
|
|
38447
|
+
validRows: candidates.length - invalidRows,
|
|
38448
|
+
invalidRows,
|
|
38449
|
+
errorCount: errors.length,
|
|
38450
|
+
columns,
|
|
38451
|
+
errors,
|
|
38452
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : stmt.onErrorSkip && stmt.errorTable ? { errTable: stmt.errorTable } : {}
|
|
38453
|
+
};
|
|
38454
|
+
return { result, candidates, invalidRowNumbers };
|
|
38455
|
+
}
|
|
38456
|
+
async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38457
|
+
const prepared = await prepareDmlValidation(
|
|
38458
|
+
stmt,
|
|
38459
|
+
client,
|
|
38460
|
+
options,
|
|
38461
|
+
cacheContext,
|
|
38462
|
+
tempTables,
|
|
38463
|
+
statementNumber
|
|
38464
|
+
);
|
|
38465
|
+
const errTable = stmt.errorTable;
|
|
38466
|
+
if (!errTable) throw new Error("ArgumentError: ON ERROR SKIP requires INTO #error_table.");
|
|
38467
|
+
appendValidationErrors(
|
|
38468
|
+
tempTables,
|
|
38469
|
+
errTable,
|
|
38470
|
+
prepared.result.columns,
|
|
38471
|
+
prepared.result.errors,
|
|
38472
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
38473
|
+
);
|
|
38474
|
+
const rejectLimit = stmt.rejectLimit ?? null;
|
|
38475
|
+
if (rejectLimit !== null && prepared.result.invalidRows > rejectLimit) {
|
|
38476
|
+
throw new RejectLimitExceededError(
|
|
38477
|
+
`rejected rows (${prepared.result.invalidRows}) exceed REJECT LIMIT (${rejectLimit}).`,
|
|
38478
|
+
prepared.result
|
|
38479
|
+
);
|
|
38480
|
+
}
|
|
38481
|
+
const valid = prepared.candidates.filter((candidate) => !prepared.invalidRowNumbers.has(candidate.rowNumber));
|
|
38482
|
+
if (options.confirm) {
|
|
38483
|
+
const operation = stmt.type.startsWith("INSERT") ? "INSERT" : "UPDATE";
|
|
38484
|
+
const ok = await options.confirm(valid.length, operation);
|
|
38485
|
+
if (!ok) throw new OperationCancelledError(operation, valid.length);
|
|
38486
|
+
}
|
|
38487
|
+
const common = {
|
|
38488
|
+
affectedRows: valid.length,
|
|
38489
|
+
skippedRows: prepared.result.invalidRows,
|
|
38490
|
+
rejectLimit,
|
|
38491
|
+
errTable
|
|
38492
|
+
};
|
|
38493
|
+
if (stmt.type === "INSERT" || stmt.type === "INSERT_SELECT") {
|
|
38494
|
+
const createdIds = [];
|
|
38495
|
+
for (let i = 0; i < valid.length; i += 100) {
|
|
38496
|
+
const response = await client.postRecords({ app: stmt.appId, records: valid.slice(i, i + 100).map((c) => c.record) });
|
|
38497
|
+
createdIds.push(response.ids);
|
|
38498
|
+
}
|
|
38499
|
+
return { type: "INSERT", createdIds, insertedCount: createdIds.flat().length, ...common };
|
|
38500
|
+
}
|
|
38501
|
+
if (stmt.type === "UPDATE") {
|
|
38502
|
+
const updates2 = valid.map((candidate) => {
|
|
38503
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPDATE candidate has no targetId.");
|
|
38504
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
38505
|
+
});
|
|
38506
|
+
for (let i = 0; i < updates2.length; i += 100) {
|
|
38507
|
+
await client.putRecords({ app: stmt.appId, records: updates2.slice(i, i + 100) });
|
|
38508
|
+
}
|
|
38509
|
+
return { type: "UPDATE", updatedCount: updates2.length, ...common };
|
|
38510
|
+
}
|
|
38511
|
+
const inserts = valid.filter((candidate) => candidate.mode === "create");
|
|
38512
|
+
const updates = valid.filter((candidate) => candidate.mode === "update").map((candidate) => {
|
|
38513
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPSERT candidate has no targetId.");
|
|
38514
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
38515
|
+
});
|
|
38516
|
+
let insertedCount = 0;
|
|
38517
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
38518
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map((c) => c.record) });
|
|
38519
|
+
insertedCount += response.ids.length;
|
|
38520
|
+
}
|
|
38521
|
+
for (let i = 0; i < updates.length; i += 100) {
|
|
38522
|
+
await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
38523
|
+
}
|
|
38524
|
+
return { type: "UPSERT", insertedCount, updatedCount: updates.length, ...common };
|
|
38525
|
+
}
|
|
38526
|
+
async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
|
|
38527
|
+
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
38528
|
+
let rows;
|
|
38529
|
+
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
38530
|
+
rows = stmt.values.map((row) => row.map(
|
|
38531
|
+
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
38532
|
+
));
|
|
38533
|
+
} else {
|
|
38534
|
+
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);
|
|
38535
|
+
if (selectResult.columns.length !== stmt.fields.length) {
|
|
38536
|
+
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`);
|
|
38537
|
+
}
|
|
38538
|
+
rows = selectResult.rows.map((row) => selectResult.columns.map((column) => row[column] ?? ""));
|
|
38539
|
+
}
|
|
38540
|
+
const candidates = rows.map((values, index) => ({
|
|
38541
|
+
rowNumber: index + 1,
|
|
38542
|
+
operation,
|
|
38543
|
+
mode: "create",
|
|
38544
|
+
payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
|
|
38545
|
+
preErrors: [],
|
|
38546
|
+
record: {}
|
|
38547
|
+
}));
|
|
38548
|
+
if (stmt.type !== "UPSERT" && stmt.type !== "UPSERT_SELECT") return candidates;
|
|
38549
|
+
for (const key of stmt.keyFields) {
|
|
38550
|
+
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`);
|
|
38551
|
+
}
|
|
38552
|
+
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
38553
|
+
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
38554
|
+
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
38555
|
+
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
38556
|
+
const keyCounts = /* @__PURE__ */ new Map();
|
|
38557
|
+
for (const parts of rowKeys) {
|
|
38558
|
+
const key = upsertNormalizedKey(parts, numeric);
|
|
38559
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
38560
|
+
}
|
|
38561
|
+
candidates.forEach((candidate, index) => {
|
|
38562
|
+
const parts = rowKeys[index];
|
|
38563
|
+
const targetId = lookupUpsertTarget(targets, parts);
|
|
38564
|
+
candidate.mode = targetId === void 0 ? "create" : "update";
|
|
38565
|
+
if (targetId !== void 0) candidate.targetId = targetId;
|
|
38566
|
+
stmt.keyFields.forEach((key, keyIndex) => {
|
|
38567
|
+
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` });
|
|
38568
|
+
});
|
|
38569
|
+
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
38570
|
+
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" });
|
|
38571
|
+
}
|
|
38572
|
+
});
|
|
38573
|
+
return candidates;
|
|
38574
|
+
}
|
|
38575
|
+
async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
|
|
38576
|
+
if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
38577
|
+
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
38578
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38579
|
+
let records;
|
|
38580
|
+
if (hasArithAssignment(stmt)) {
|
|
38581
|
+
const getParams = updateToGetQueryForArith(stmt);
|
|
38582
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
|
|
38583
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
38584
|
+
parallel: options.fetchParallel ?? 1,
|
|
38585
|
+
onLimit: "error"
|
|
38586
|
+
});
|
|
38587
|
+
records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
|
|
38588
|
+
} else {
|
|
38589
|
+
const getParams = updateToGetQuery(stmt);
|
|
38590
|
+
const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
|
|
38591
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
38592
|
+
parallel: options.fetchParallel ?? 1
|
|
38593
|
+
});
|
|
38594
|
+
records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
|
|
38595
|
+
}
|
|
38596
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
38597
|
+
rowNumber: index + 1,
|
|
38598
|
+
operation: "UPDATE",
|
|
38599
|
+
mode: "update",
|
|
38600
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
38601
|
+
preErrors: [],
|
|
38602
|
+
record: entry.record,
|
|
38603
|
+
targetId: entry.id
|
|
38604
|
+
}));
|
|
38605
|
+
}
|
|
38606
|
+
async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
|
|
38607
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
38608
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38609
|
+
const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
|
|
38610
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
38611
|
+
rowNumber: index + 1,
|
|
38612
|
+
operation: "UPDATE",
|
|
38613
|
+
mode: "update",
|
|
38614
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
38615
|
+
preErrors: [],
|
|
38616
|
+
record: entry.record,
|
|
38617
|
+
targetId: entry.id
|
|
38618
|
+
}));
|
|
38619
|
+
}
|
|
38620
|
+
var UPDATE_FROM_KEY_CHUNK_SIZE = UPSERT_IN_CHUNK_SIZE;
|
|
38621
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
38622
|
+
"CHECK_BOX",
|
|
38623
|
+
"MULTI_SELECT",
|
|
38624
|
+
"USER_SELECT",
|
|
38625
|
+
"ORGANIZATION_SELECT",
|
|
38626
|
+
"GROUP_SELECT",
|
|
38627
|
+
"FILE"
|
|
38628
|
+
]);
|
|
38629
|
+
async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
|
|
38630
|
+
const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
|
|
38631
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
38632
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
38633
|
+
const sourceRows = await loadUpdateFromSourceRows(
|
|
38634
|
+
from,
|
|
38635
|
+
requiredSourceFields,
|
|
38636
|
+
sourceFields,
|
|
38637
|
+
client,
|
|
38638
|
+
options,
|
|
38639
|
+
cacheContext,
|
|
38640
|
+
tempTables
|
|
38641
|
+
);
|
|
38642
|
+
const sourceByKey = /* @__PURE__ */ new Map();
|
|
38643
|
+
for (const row of sourceRows) {
|
|
38644
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
38645
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
38646
|
+
}
|
|
38647
|
+
const key = normalizeUpdateFromJoinKey(row[from.joinKeyField], joinKind, "source");
|
|
38648
|
+
if (sourceByKey.has(key)) {
|
|
38649
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
38650
|
+
}
|
|
38651
|
+
sourceByKey.set(key, row);
|
|
38652
|
+
}
|
|
38653
|
+
if (sourceByKey.size === 0) return [];
|
|
38654
|
+
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
38655
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
38656
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
38657
|
+
const targetRecords = [];
|
|
38658
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
38659
|
+
let fetchedTargetCount = 0;
|
|
38660
|
+
for (const keys of splitChunks([...sourceByKey.keys()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
38661
|
+
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
38662
|
+
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
38663
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
38664
|
+
client.getRecords,
|
|
38665
|
+
stmt.appId,
|
|
38666
|
+
query,
|
|
38667
|
+
targetFields,
|
|
38668
|
+
{ maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38669
|
+
);
|
|
38670
|
+
fetchedTargetCount += resolved.records.length;
|
|
38671
|
+
if (fetchedTargetCount > maxRecords2) {
|
|
38672
|
+
throw new FetchAllLimitError(
|
|
38673
|
+
`\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650\uFF08${maxRecords2} \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`
|
|
38674
|
+
);
|
|
38675
|
+
}
|
|
38676
|
+
for (const record2 of resolved.records) {
|
|
38677
|
+
const id = record2["$id"]?.value;
|
|
38678
|
+
if (typeof id !== "string" || id === "") {
|
|
38679
|
+
throw new Error("ArgumentError: UPDATE ... FROM target record does not contain a valid $id.");
|
|
38680
|
+
}
|
|
38681
|
+
if (seenTargetIds.has(id)) continue;
|
|
38682
|
+
seenTargetIds.add(id);
|
|
38683
|
+
targetRecords.push(record2);
|
|
38684
|
+
}
|
|
38685
|
+
}
|
|
38686
|
+
const matched = [];
|
|
38687
|
+
for (const target of targetRecords) {
|
|
38688
|
+
const raw = target[from.targetJoinField]?.value;
|
|
38689
|
+
const key = normalizeUpdateFromJoinKey(raw, joinKind, "target");
|
|
38690
|
+
if (key === null) continue;
|
|
38691
|
+
const source = sourceByKey.get(key);
|
|
38692
|
+
if (source !== void 0) matched.push({ target, source });
|
|
38693
|
+
}
|
|
38694
|
+
return matched;
|
|
38695
|
+
}
|
|
38696
|
+
async function resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext) {
|
|
38697
|
+
if (from.targetJoinField === "$id") return "id";
|
|
38698
|
+
const info = (await getFieldsCached(stmt.appId, client, cacheContext)).find((field) => field.code === from.targetJoinField);
|
|
38699
|
+
if (!info) {
|
|
38700
|
+
throw new Error(`ArgumentError: UPDATE ... FROM target column ${from.targetJoinField} does not exist.`);
|
|
38701
|
+
}
|
|
38702
|
+
if (info.inSubtable || info.writable === false || info.fieldType !== "SINGLE_LINE_TEXT" && info.fieldType !== "NUMBER") {
|
|
38703
|
+
throw new Error(
|
|
38704
|
+
`ArgumentError: UPDATE ... FROM does not support target join field type ${info.fieldType} (${from.targetJoinField}).`
|
|
38705
|
+
);
|
|
38706
|
+
}
|
|
38707
|
+
return info.fieldType === "NUMBER" ? "number" : "string";
|
|
38708
|
+
}
|
|
38709
|
+
async function loadUpdateFromSourceRows(from, requiredSourceFields, sourceValueFields, client, options, cacheContext, tempTables) {
|
|
38710
|
+
if (from.cteName !== null) {
|
|
38711
|
+
const table = tempTables?.get(from.cteName);
|
|
38712
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
38713
|
+
for (const field of requiredSourceFields) {
|
|
38714
|
+
if (!table.columns.includes(field)) {
|
|
38715
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
38716
|
+
}
|
|
38717
|
+
}
|
|
38718
|
+
return table.rows;
|
|
38719
|
+
}
|
|
38720
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
38721
|
+
const joinType = from.joinKeyField === "$id" ? "RECORD_NUMBER" : sourceTypes.get(from.joinKeyField);
|
|
38722
|
+
if (joinType === void 0) {
|
|
38723
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
38724
|
+
}
|
|
38725
|
+
if (from.joinKeyField !== "$id" && joinType !== "SINGLE_LINE_TEXT" && joinType !== "NUMBER") {
|
|
38726
|
+
throw new Error(
|
|
38727
|
+
`ArgumentError: UPDATE ... FROM does not support source join field type ${joinType} (${from.joinKeyField}).`
|
|
38728
|
+
);
|
|
38729
|
+
}
|
|
38730
|
+
for (const field of sourceValueFields) {
|
|
38731
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
38732
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
38733
|
+
}
|
|
38734
|
+
const type = field === "$id" ? "RECORD_NUMBER" : sourceTypes.get(field);
|
|
38735
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
38736
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
38737
|
+
}
|
|
38738
|
+
}
|
|
38739
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
38740
|
+
client.getRecords,
|
|
38741
|
+
from.appId,
|
|
38742
|
+
"",
|
|
38743
|
+
requiredSourceFields,
|
|
38744
|
+
{ maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38745
|
+
);
|
|
38746
|
+
return resolved.records.map((record2) => flatten(record2, null));
|
|
38747
|
+
}
|
|
38748
|
+
function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
38749
|
+
if (typeof raw !== "string") {
|
|
38750
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a scalar string: ${String(raw)}`);
|
|
38751
|
+
}
|
|
38752
|
+
if (kind === "string") {
|
|
38753
|
+
if (raw === "") {
|
|
38754
|
+
if (side === "target") return null;
|
|
38755
|
+
throw new Error("ArgumentError: UPDATE ... FROM source key must not be empty.");
|
|
38756
|
+
}
|
|
38757
|
+
return raw;
|
|
38758
|
+
}
|
|
38759
|
+
if (kind === "number" && side === "target" && raw === "") return null;
|
|
38760
|
+
if (kind === "id") {
|
|
38761
|
+
const text2 = raw.trim();
|
|
38762
|
+
const id = Number(text2);
|
|
38763
|
+
if (text2 === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
38764
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
38765
|
+
}
|
|
38766
|
+
return String(id);
|
|
38767
|
+
}
|
|
38768
|
+
const text = raw.trim();
|
|
38769
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
|
|
38770
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
38771
|
+
}
|
|
38772
|
+
let unsigned = text;
|
|
38773
|
+
let negative = false;
|
|
38774
|
+
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
38775
|
+
negative = unsigned[0] === "-";
|
|
38776
|
+
unsigned = unsigned.slice(1);
|
|
38777
|
+
}
|
|
38778
|
+
let [whole, fraction = ""] = unsigned.split(".");
|
|
38779
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
38780
|
+
fraction = fraction.replace(/0+$/, "");
|
|
38781
|
+
const zero = /^0*$/.test(whole) && fraction === "";
|
|
38782
|
+
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
38783
|
+
return negative && !zero ? `-${canonical}` : canonical;
|
|
38784
|
+
}
|
|
37853
38785
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
37854
38786
|
if (stmt.subtableCode) {
|
|
37855
38787
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
@@ -37949,98 +38881,20 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
37949
38881
|
}
|
|
37950
38882
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
37951
38883
|
}
|
|
37952
|
-
var UPDATE_FROM_ID_CHUNK_SIZE = 50;
|
|
37953
|
-
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
37954
|
-
"CHECK_BOX",
|
|
37955
|
-
"MULTI_SELECT",
|
|
37956
|
-
"USER_SELECT",
|
|
37957
|
-
"ORGANIZATION_SELECT",
|
|
37958
|
-
"GROUP_SELECT",
|
|
37959
|
-
"FILE"
|
|
37960
|
-
]);
|
|
37961
38884
|
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
37962
|
-
const
|
|
37963
|
-
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
37964
|
-
let sourceRows;
|
|
37965
|
-
if (from.cteName !== null) {
|
|
37966
|
-
const table = tempTables?.get(from.cteName);
|
|
37967
|
-
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
37968
|
-
for (const field of requiredSourceFields) {
|
|
37969
|
-
if (!table.columns.includes(field)) {
|
|
37970
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
37971
|
-
}
|
|
37972
|
-
}
|
|
37973
|
-
sourceRows = table.rows;
|
|
37974
|
-
} else {
|
|
37975
|
-
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
37976
|
-
for (const field of requiredSourceFields) {
|
|
37977
|
-
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
37978
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
37979
|
-
}
|
|
37980
|
-
const type = sourceTypes.get(field);
|
|
37981
|
-
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
37982
|
-
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
37983
|
-
}
|
|
37984
|
-
}
|
|
37985
|
-
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
37986
|
-
const resolved = await fetchRecordsForSharedPlan(
|
|
37987
|
-
client.getRecords,
|
|
37988
|
-
from.appId,
|
|
37989
|
-
"",
|
|
37990
|
-
requiredSourceFields,
|
|
37991
|
-
{ maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
37992
|
-
);
|
|
37993
|
-
sourceRows = resolved.records.map((record2) => flatten(record2, null));
|
|
37994
|
-
}
|
|
37995
|
-
const sourceById = /* @__PURE__ */ new Map();
|
|
37996
|
-
for (const row of sourceRows) {
|
|
37997
|
-
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
37998
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
37999
|
-
}
|
|
38000
|
-
const raw = row[from.joinKeyField];
|
|
38001
|
-
const text = typeof raw === "string" ? raw.trim() : "";
|
|
38002
|
-
const id = Number(text);
|
|
38003
|
-
if (text === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
38004
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source key must be a positive safe integer: ${String(raw)}`);
|
|
38005
|
-
}
|
|
38006
|
-
if (sourceById.has(id)) {
|
|
38007
|
-
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for target $id ${id}.`);
|
|
38008
|
-
}
|
|
38009
|
-
sourceById.set(id, row);
|
|
38010
|
-
}
|
|
38011
|
-
const targetIds = [...sourceById.keys()];
|
|
38012
|
-
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
38013
|
-
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
38014
|
-
const targetRecords = [];
|
|
38015
|
-
for (const ids of splitChunks(targetIds, UPDATE_FROM_ID_CHUNK_SIZE)) {
|
|
38016
|
-
const idQuery = `$id in (${ids.map((id) => sqlQuote(String(id))).join(",")})`;
|
|
38017
|
-
const query = filterQuery ? `(${idQuery}) and (${filterQuery})` : idQuery;
|
|
38018
|
-
const resolved = await fetchRecordsForSharedPlan(
|
|
38019
|
-
client.getRecords,
|
|
38020
|
-
stmt.appId,
|
|
38021
|
-
query,
|
|
38022
|
-
targetFields,
|
|
38023
|
-
{ maxRecords: Math.max(ids.length, 1), parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38024
|
-
);
|
|
38025
|
-
targetRecords.push(...resolved.records);
|
|
38026
|
-
}
|
|
38885
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
38027
38886
|
if (options.confirm) {
|
|
38028
|
-
const ok = await options.confirm(
|
|
38029
|
-
if (!ok) throw new OperationCancelledError("UPDATE",
|
|
38887
|
+
const ok = await options.confirm(matched.length, "UPDATE");
|
|
38888
|
+
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
38030
38889
|
}
|
|
38031
38890
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38032
|
-
const matched = targetRecords.map((target) => {
|
|
38033
|
-
const id = Number(target["$id"]?.value);
|
|
38034
|
-
const source = sourceById.get(id);
|
|
38035
|
-
if (!source) throw new Error(`ArgumentError: UPDATE ... FROM could not resolve source row for target $id ${id}.`);
|
|
38036
|
-
return { target, source };
|
|
38037
|
-
});
|
|
38038
38891
|
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
38039
38892
|
for (const batch of batches) await client.putRecords(batch);
|
|
38040
|
-
return { type: "UPDATE", updatedCount:
|
|
38893
|
+
return { type: "UPDATE", updatedCount: matched.length };
|
|
38041
38894
|
}
|
|
38042
38895
|
function collectUpdateFromTargetFields(stmt) {
|
|
38043
38896
|
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
38897
|
+
if (stmt.from) fields.add(stmt.from.targetJoinField);
|
|
38044
38898
|
const visit = (node) => {
|
|
38045
38899
|
if (Array.isArray(node)) {
|
|
38046
38900
|
node.forEach(visit);
|
|
@@ -38986,7 +39840,7 @@ function buildUpdatePlan(stmt, label) {
|
|
|
38986
39840
|
if (stmt.from) {
|
|
38987
39841
|
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
38988
39842
|
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
38989
|
-
lines.push(` join: APP${stmt.appId}.$
|
|
39843
|
+
lines.push(` join: APP${stmt.appId}.${stmt.from.targetJoinField} = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
38990
39844
|
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
38991
39845
|
} else {
|
|
38992
39846
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
@@ -39143,12 +39997,32 @@ function parseSqlStatements(sql) {
|
|
|
39143
39997
|
// src/output/batchEnvelope.ts
|
|
39144
39998
|
function toMutationSummary(result) {
|
|
39145
39999
|
if (result.type === "INSERT") {
|
|
39146
|
-
return {
|
|
40000
|
+
return {
|
|
40001
|
+
insertedCount: result.insertedCount,
|
|
40002
|
+
createdIds: result.createdIds,
|
|
40003
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
40004
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
40005
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
40006
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40007
|
+
};
|
|
39147
40008
|
}
|
|
39148
|
-
if (result.type === "UPDATE") return {
|
|
40009
|
+
if (result.type === "UPDATE") return {
|
|
40010
|
+
updatedCount: result.updatedCount,
|
|
40011
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
40012
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
40013
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
40014
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40015
|
+
};
|
|
39149
40016
|
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
39150
40017
|
if (result.type === "UPSERT") {
|
|
39151
|
-
return {
|
|
40018
|
+
return {
|
|
40019
|
+
insertedCount: result.insertedCount,
|
|
40020
|
+
updatedCount: result.updatedCount,
|
|
40021
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
40022
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
40023
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
40024
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40025
|
+
};
|
|
39152
40026
|
}
|
|
39153
40027
|
return { reorderedParentCount: result.reorderedParentCount };
|
|
39154
40028
|
}
|
|
@@ -39175,11 +40049,31 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
39175
40049
|
}
|
|
39176
40050
|
entry.resultIndex = results.length;
|
|
39177
40051
|
results.push({
|
|
40052
|
+
type: "SELECT",
|
|
39178
40053
|
columns: s.result.columns,
|
|
39179
40054
|
rows: s.result.rows,
|
|
39180
40055
|
rowCount: s.result.rowCount,
|
|
39181
40056
|
warnings: s.result.warnings ?? []
|
|
39182
40057
|
});
|
|
40058
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
40059
|
+
totalRows += s.result.errorCount;
|
|
40060
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
40061
|
+
throw new Error(`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`);
|
|
40062
|
+
}
|
|
40063
|
+
entry.resultIndex = results.length;
|
|
40064
|
+
results.push({
|
|
40065
|
+
type: "VALIDATION",
|
|
40066
|
+
columns: s.result.columns,
|
|
40067
|
+
rows: s.result.errors,
|
|
40068
|
+
rowCount: s.result.errorCount,
|
|
40069
|
+
warnings: [],
|
|
40070
|
+
operation: s.result.operation,
|
|
40071
|
+
validatedRows: s.result.validatedRows,
|
|
40072
|
+
validRows: s.result.validRows,
|
|
40073
|
+
invalidRows: s.result.invalidRows,
|
|
40074
|
+
errorCount: s.result.errorCount,
|
|
40075
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
40076
|
+
});
|
|
39183
40077
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
39184
40078
|
Object.assign(entry, toMutationSummary(s.result));
|
|
39185
40079
|
}
|
|
@@ -39537,6 +40431,9 @@ function clampInt(v, min, max) {
|
|
|
39537
40431
|
|
|
39538
40432
|
// src/core/formFieldInfo.ts
|
|
39539
40433
|
function flattenFormFieldProperties(properties) {
|
|
40434
|
+
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
40435
|
+
}
|
|
40436
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
39540
40437
|
const out = [];
|
|
39541
40438
|
for (const field of Object.values(properties)) {
|
|
39542
40439
|
out.push({
|
|
@@ -39544,12 +40441,49 @@ function flattenFormFieldProperties(properties) {
|
|
|
39544
40441
|
label: field.label,
|
|
39545
40442
|
fieldType: field.type,
|
|
39546
40443
|
optionOrder: toOptionOrderMap(field.options),
|
|
39547
|
-
sortKind: detectSortKind(field.type, field.format)
|
|
40444
|
+
sortKind: detectSortKind(field.type, field.format),
|
|
40445
|
+
required: field.required,
|
|
40446
|
+
minValue: normalizeConstraintValue(field.minValue),
|
|
40447
|
+
maxValue: normalizeConstraintValue(field.maxValue),
|
|
40448
|
+
minLength: normalizeConstraintValue(field.minLength),
|
|
40449
|
+
maxLength: normalizeConstraintValue(field.maxLength),
|
|
40450
|
+
defaultValue: field.defaultValue,
|
|
40451
|
+
inSubtable,
|
|
40452
|
+
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
39548
40453
|
});
|
|
39549
|
-
if (field.fields) out.push(...
|
|
40454
|
+
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
|
|
39550
40455
|
}
|
|
39551
40456
|
return out;
|
|
39552
40457
|
}
|
|
40458
|
+
var NON_WRITABLE_FIELD_TYPES2 = /* @__PURE__ */ new Set([
|
|
40459
|
+
"CALC",
|
|
40460
|
+
"RECORD_NUMBER",
|
|
40461
|
+
"CREATOR",
|
|
40462
|
+
"CREATED_TIME",
|
|
40463
|
+
"MODIFIER",
|
|
40464
|
+
"UPDATED_TIME",
|
|
40465
|
+
"STATUS",
|
|
40466
|
+
"STATUS_ASSIGNEE",
|
|
40467
|
+
"CATEGORY",
|
|
40468
|
+
"REFERENCE_TABLE",
|
|
40469
|
+
"SUBTABLE"
|
|
40470
|
+
]);
|
|
40471
|
+
function collectLookupCopyFields(properties) {
|
|
40472
|
+
const result = /* @__PURE__ */ new Set();
|
|
40473
|
+
const visit = (fields) => {
|
|
40474
|
+
for (const field of Object.values(fields)) {
|
|
40475
|
+
for (const mapping of field.lookup?.fieldMappings ?? []) {
|
|
40476
|
+
if (mapping.field) result.add(mapping.field);
|
|
40477
|
+
}
|
|
40478
|
+
if (field.fields) visit(field.fields);
|
|
40479
|
+
}
|
|
40480
|
+
};
|
|
40481
|
+
visit(properties);
|
|
40482
|
+
return result;
|
|
40483
|
+
}
|
|
40484
|
+
function normalizeConstraintValue(value) {
|
|
40485
|
+
return value == null || value === "" ? void 0 : value;
|
|
40486
|
+
}
|
|
39553
40487
|
function toOptionOrderMap(options) {
|
|
39554
40488
|
if (!options || typeof options !== "object") return void 0;
|
|
39555
40489
|
const order = {};
|
|
@@ -40573,20 +41507,42 @@ function toAssertPayload(result) {
|
|
|
40573
41507
|
condition: result.condition
|
|
40574
41508
|
};
|
|
40575
41509
|
}
|
|
41510
|
+
function toDmlValidationPayload(result) {
|
|
41511
|
+
return {
|
|
41512
|
+
ok: true,
|
|
41513
|
+
type: result.type,
|
|
41514
|
+
operation: result.operation,
|
|
41515
|
+
validatedRows: result.validatedRows,
|
|
41516
|
+
validRows: result.validRows,
|
|
41517
|
+
invalidRows: result.invalidRows,
|
|
41518
|
+
errorCount: result.errorCount,
|
|
41519
|
+
columns: result.columns,
|
|
41520
|
+
errors: result.errors,
|
|
41521
|
+
...result.errTable ? { errTable: result.errTable } : {}
|
|
41522
|
+
};
|
|
41523
|
+
}
|
|
40576
41524
|
function toMutationPayload(result) {
|
|
40577
41525
|
if (result.type === "INSERT") {
|
|
40578
41526
|
return {
|
|
40579
41527
|
ok: true,
|
|
40580
41528
|
type: result.type,
|
|
40581
41529
|
insertedCount: result.insertedCount,
|
|
40582
|
-
createdIds: result.createdIds
|
|
41530
|
+
createdIds: result.createdIds,
|
|
41531
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41532
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41533
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41534
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40583
41535
|
};
|
|
40584
41536
|
}
|
|
40585
41537
|
if (result.type === "UPDATE") {
|
|
40586
41538
|
return {
|
|
40587
41539
|
ok: true,
|
|
40588
41540
|
type: result.type,
|
|
40589
|
-
updatedCount: result.updatedCount
|
|
41541
|
+
updatedCount: result.updatedCount,
|
|
41542
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41543
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41544
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41545
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40590
41546
|
};
|
|
40591
41547
|
}
|
|
40592
41548
|
if (result.type === "DELETE") {
|
|
@@ -40601,7 +41557,11 @@ function toMutationPayload(result) {
|
|
|
40601
41557
|
ok: true,
|
|
40602
41558
|
type: result.type,
|
|
40603
41559
|
insertedCount: result.insertedCount,
|
|
40604
|
-
updatedCount: result.updatedCount
|
|
41560
|
+
updatedCount: result.updatedCount,
|
|
41561
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41562
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41563
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41564
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40605
41565
|
};
|
|
40606
41566
|
}
|
|
40607
41567
|
return {
|
|
@@ -40637,7 +41597,7 @@ function requireDmlApproval(input, toolName, suffix = "") {
|
|
|
40637
41597
|
}
|
|
40638
41598
|
function containsSelectBasedDml(statements) {
|
|
40639
41599
|
return statements.some(
|
|
40640
|
-
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT" || s.isUpdateFrom === true
|
|
41600
|
+
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT" || s.isUpdateFrom === true || s.isOnErrorSkip === true
|
|
40641
41601
|
);
|
|
40642
41602
|
}
|
|
40643
41603
|
function resolveMutateRuntimeMaxRecords(statements, dmlMaxRows) {
|
|
@@ -40700,13 +41660,18 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40700
41660
|
tempTablesDropped: s2.tempTablesDropped,
|
|
40701
41661
|
tempOnlySource: s2.tempOnlySource,
|
|
40702
41662
|
targetAppId: s2.targetAppId,
|
|
40703
|
-
isUpdateFrom: s2.isUpdateFrom
|
|
41663
|
+
isUpdateFrom: s2.isUpdateFrom,
|
|
41664
|
+
isValidationOnly: s2.isValidationOnly,
|
|
41665
|
+
isOnErrorSkip: s2.isOnErrorSkip,
|
|
41666
|
+
requiresCompleteInput: s2.requiresCompleteInput
|
|
40704
41667
|
}));
|
|
40705
41668
|
const common = {
|
|
40706
41669
|
ok: true,
|
|
40707
41670
|
statementCount: analysis.statementCount,
|
|
40708
41671
|
isReadOnlyBatch: analysis.isReadOnlyBatch,
|
|
40709
41672
|
containsDml: analysis.containsDml,
|
|
41673
|
+
containsValidationOnly: analysis.containsValidationOnly,
|
|
41674
|
+
requiresCompleteInput: analysis.requiresCompleteInput,
|
|
40710
41675
|
tempTables: analysis.tempTables,
|
|
40711
41676
|
canRunWithQueryTool: analysis.isReadOnlyBatch,
|
|
40712
41677
|
requiresMutationTool: analysis.containsDml,
|
|
@@ -40780,7 +41745,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40780
41745
|
profile: input.profile,
|
|
40781
41746
|
maxRecords: input.maxRecords,
|
|
40782
41747
|
fetchParallel: input.fetchParallel,
|
|
40783
|
-
onLimit: input.onLimit,
|
|
41748
|
+
onLimit: validation.requiresCompleteInput ? "error" : input.onLimit,
|
|
40784
41749
|
timeout: input.timeout,
|
|
40785
41750
|
tempTableMaxRows: input.tempTableMaxRows
|
|
40786
41751
|
});
|
|
@@ -40813,6 +41778,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40813
41778
|
cacheContext: validation.cacheContext
|
|
40814
41779
|
});
|
|
40815
41780
|
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
41781
|
+
if (result2.type === "VALIDATION") return toDmlValidationPayload(result2);
|
|
40816
41782
|
if (result2.type !== "SELECT") {
|
|
40817
41783
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result2.type}.`);
|
|
40818
41784
|
}
|
|
@@ -40824,7 +41790,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40824
41790
|
profile: input.profile,
|
|
40825
41791
|
maxRecords: input.maxRecords,
|
|
40826
41792
|
fetchParallel: input.fetchParallel,
|
|
40827
|
-
onLimit: input.onLimit,
|
|
41793
|
+
onLimit: validation.requiresCompleteInput ? "error" : input.onLimit,
|
|
40828
41794
|
timeout: input.timeout
|
|
40829
41795
|
});
|
|
40830
41796
|
const result = await executeSql(runtime.sql, runtime.client, {
|
|
@@ -40834,6 +41800,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40834
41800
|
cacheContext: runtime.cacheContext
|
|
40835
41801
|
});
|
|
40836
41802
|
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
41803
|
+
if (result.type === "VALIDATION") return toDmlValidationPayload(result);
|
|
40837
41804
|
if (result.type !== "SELECT") {
|
|
40838
41805
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result.type}.`);
|
|
40839
41806
|
}
|
|
@@ -40850,12 +41817,12 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40850
41817
|
if ((s.statementType === "UPDATE" || s.statementType === "DELETE") && !s.hasWhere) {
|
|
40851
41818
|
throw new Error(`ArgumentError: ${s.statementType} without WHERE is blocked by ksql_mutate.${at}`);
|
|
40852
41819
|
}
|
|
40853
|
-
if (s.insertValuesCount !== null && s.insertValuesCount > dmlMaxRows) {
|
|
41820
|
+
if (!s.isOnErrorSkip && s.insertValuesCount !== null && s.insertValuesCount > dmlMaxRows) {
|
|
40854
41821
|
throw new Error(
|
|
40855
41822
|
`ArgumentError: INSERT rows (${s.insertValuesCount}) exceed dmlMaxRows (${dmlMaxRows}).${at}`
|
|
40856
41823
|
);
|
|
40857
41824
|
}
|
|
40858
|
-
staticInsertTotal += s.insertValuesCount ?? 0;
|
|
41825
|
+
if (!s.isOnErrorSkip) staticInsertTotal += s.insertValuesCount ?? 0;
|
|
40859
41826
|
}
|
|
40860
41827
|
const dmlTotalMaxRows = input.dmlTotalMaxRows;
|
|
40861
41828
|
if (dmlTotalMaxRows !== void 0 && staticInsertTotal > dmlTotalMaxRows) {
|
|
@@ -40960,7 +41927,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40960
41927
|
} catch (err) {
|
|
40961
41928
|
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
40962
41929
|
}
|
|
40963
|
-
if (result.type === "SELECT" || result.type === "ASSERT") {
|
|
41930
|
+
if (result.type === "SELECT" || result.type === "ASSERT" || result.type === "VALIDATION") {
|
|
40964
41931
|
throw new Error(`ArgumentError: ksql_mutate returned unexpected result type ${result.type}.`);
|
|
40965
41932
|
}
|
|
40966
41933
|
return toMutationPayload(result);
|
|
@@ -41119,7 +42086,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
41119
42086
|
var profile = external_exports.string().min(1).describe("kintone connection profile name from ksql.config.json (default: the server's default profile).").optional();
|
|
41120
42087
|
var maxRecords = external_exports.number().int().positive().describe("Maximum records fetched per SELECT (default 500).").optional();
|
|
41121
42088
|
var fetchParallel = external_exports.number().int().min(1).max(10).describe("Number of parallel kintone record-fetch requests (1-10).").optional();
|
|
41122
|
-
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error').").optional();
|
|
42089
|
+
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). VALIDATE ONLY always requires complete input and therefore overrides 'truncate' to 'error'.").optional();
|
|
41123
42090
|
var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
|
|
41124
42091
|
var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
|
|
41125
42092
|
var savedQueryName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).describe("Saved query name (alphanumeric, '_' and '-', up to 64 chars).");
|
|
@@ -41243,7 +42210,7 @@ Options:
|
|
|
41243
42210
|
-h, --help Show help
|
|
41244
42211
|
`);
|
|
41245
42212
|
}
|
|
41246
|
-
var SERVER_VERSION = true ? "2.
|
|
42213
|
+
var SERVER_VERSION = true ? "2.14.0" : "0.0.0-dev";
|
|
41247
42214
|
function createServer(args) {
|
|
41248
42215
|
const server = new McpServer({
|
|
41249
42216
|
name: "ksql-mcp",
|
|
@@ -41265,12 +42232,12 @@ function createServer(args) {
|
|
|
41265
42232
|
}, tools.explainTool);
|
|
41266
42233
|
server.registerTool("ksql_query", {
|
|
41267
42234
|
title: "Run read-only kSQL",
|
|
41268
|
-
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT.
|
|
42235
|
+
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT, and INSERT/UPSERT/UPDATE ... VALIDATE ONLY. ASSERT failure always stops the batch. VALIDATE ONLY performs local Tier-0 validation with zero write API calls; it always requires complete input, so onLimit=truncate is ignored and treated as error. Supports multi-statement batches with temp tables, including VALIDATE ONLY INTO #err for later SELECT. Mutating DML is rejected.",
|
|
41269
42236
|
inputSchema: queryInputShape
|
|
41270
42237
|
}, tools.queryTool);
|
|
41271
42238
|
server.registerTool("ksql_mutate", {
|
|
41272
42239
|
title: "Run mutating kSQL",
|
|
41273
|
-
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. UPDATE ... FROM supports copying scalar fields from an app or temp table by matching target $id to one source key. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: source SELECT and UPDATE ... FROM app reads use the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows by default (adjustable via tempTableMaxRows).",
|
|
42240
|
+
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. ON ERROR SKIP INTO #err optionally isolates local Tier-0 validation failures and writes only valid rows; REJECT LIMIT stops with zero writes while returning diagnostics. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. UPDATE ... FROM supports copying scalar fields from an app or temp table by matching target $id or a single-line-text/number business key to one source key. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: source SELECT, ON ERROR SKIP candidates, and UPDATE ... FROM app reads use the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows by default (adjustable via tempTableMaxRows).",
|
|
41274
42241
|
inputSchema: mutateInputShape
|
|
41275
42242
|
}, tools.mutateTool);
|
|
41276
42243
|
server.registerTool("ksql_describe_app", {
|