@rex0220/kintone-sql-tools 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist-cli/ksql.js +1180 -24
- package/dist-mcp/ksql-mcp.js +1191 -36
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-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");
|
|
@@ -32967,6 +32975,30 @@ var Parser = class {
|
|
|
32967
32975
|
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
32968
32976
|
this.expect("SET" /* SET */);
|
|
32969
32977
|
const assignments = this.parseAssignments();
|
|
32978
|
+
let from = null;
|
|
32979
|
+
if (this.consume("FROM" /* FROM */)) {
|
|
32980
|
+
const table = this.parseTableRef();
|
|
32981
|
+
if (table.subtableCode) {
|
|
32982
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u306F\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
32983
|
+
}
|
|
32984
|
+
if (table.cteName !== null && !table.cteName.startsWith("#")) {
|
|
32985
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306F #temp \u307E\u305F\u306F APP<n> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08CTE \u306F\u975E\u5BFE\u5FDC\uFF09", this.prev());
|
|
32986
|
+
}
|
|
32987
|
+
if (!table.alias) {
|
|
32988
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u306B\u306F\u30A8\u30A4\u30EA\u30A2\u30B9\u304C\u5FC5\u8981\u3067\u3059", this.prev());
|
|
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
|
+
}
|
|
32993
|
+
from = {
|
|
32994
|
+
appId: table.appId,
|
|
32995
|
+
cteName: table.cteName,
|
|
32996
|
+
alias: table.alias,
|
|
32997
|
+
targetJoinField: "",
|
|
32998
|
+
joinKeyField: "",
|
|
32999
|
+
targetFilter: null
|
|
33000
|
+
};
|
|
33001
|
+
}
|
|
32970
33002
|
const whereTok = this.peek();
|
|
32971
33003
|
if (!this.consume("WHERE" /* WHERE */)) {
|
|
32972
33004
|
throw new ParseError(
|
|
@@ -32975,7 +33007,191 @@ var Parser = class {
|
|
|
32975
33007
|
);
|
|
32976
33008
|
}
|
|
32977
33009
|
const where = this.parseWhereExpr();
|
|
32978
|
-
|
|
33010
|
+
if (from !== null) {
|
|
33011
|
+
if (subtableCode) {
|
|
33012
|
+
throw new ParseError("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE ... FROM \u306F\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093", whereTok);
|
|
33013
|
+
}
|
|
33014
|
+
this.validateUpdateFromAssignments(assignments, from.alias, whereTok);
|
|
33015
|
+
const decomposed = this.decomposeUpdateFromWhere(where, appId, from.alias, whereTok);
|
|
33016
|
+
from.targetJoinField = decomposed.targetJoinField;
|
|
33017
|
+
from.joinKeyField = decomposed.joinKeyField;
|
|
33018
|
+
from.targetFilter = decomposed.targetFilter;
|
|
33019
|
+
} else if (assignments.some((a) => a.value.type === "SOURCE_FIELD")) {
|
|
33020
|
+
throw new ParseError(
|
|
33021
|
+
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
33022
|
+
whereTok
|
|
33023
|
+
);
|
|
33024
|
+
}
|
|
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;
|
|
33085
|
+
}
|
|
33086
|
+
validateUpdateFromAssignments(assignments, sourceAlias, tok) {
|
|
33087
|
+
for (const assignment of assignments) {
|
|
33088
|
+
if (assignment.value.type === "SOURCE_FIELD") {
|
|
33089
|
+
if (assignment.value.alias.toLowerCase() !== sourceAlias.toLowerCase()) {
|
|
33090
|
+
throw new ParseError(`UPDATE ... FROM \u306E SET \u53C2\u7167\u306F\u30BD\u30FC\u30B9 alias ${sourceAlias} \u3067\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`, tok);
|
|
33091
|
+
}
|
|
33092
|
+
continue;
|
|
33093
|
+
}
|
|
33094
|
+
if (this.nodeContainsQualifiedField(assignment.value, sourceAlias)) {
|
|
33095
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217\u306F SET \u306E\u76F4\u63A5\u5024\u3068\u3057\u3066\u306E\u307F\u53C2\u7167\u3067\u304D\u307E\u3059", tok);
|
|
33096
|
+
}
|
|
33097
|
+
if (assignment.value.type === "SCALAR_SUBQUERY") {
|
|
33098
|
+
throw new ParseError("UPDATE ... FROM \u306E SET \u3067\u306F\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
33099
|
+
}
|
|
33100
|
+
if (this.nodeContainsAnyQualifier(assignment.value)) {
|
|
33101
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u5F0F\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u4FEE\u98FE\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044", tok);
|
|
33102
|
+
}
|
|
33103
|
+
}
|
|
33104
|
+
}
|
|
33105
|
+
decomposeUpdateFromWhere(where, targetAppId, sourceAlias, tok) {
|
|
33106
|
+
const leaves = this.flattenTopLevelAnd(where);
|
|
33107
|
+
const joins = [];
|
|
33108
|
+
leaves.forEach((leaf, index) => {
|
|
33109
|
+
const matched = this.matchUpdateFromJoin(leaf, targetAppId, sourceAlias);
|
|
33110
|
+
if (matched !== null) joins.push({ index, ...matched });
|
|
33111
|
+
});
|
|
33112
|
+
if (joins.length !== 1) {
|
|
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);
|
|
33114
|
+
}
|
|
33115
|
+
const join = joins[0];
|
|
33116
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
33117
|
+
if (i !== join.index && this.nodeContainsQualifiedField(leaves[i], sourceAlias)) {
|
|
33118
|
+
throw new ParseError("UPDATE ... FROM \u306E\u30BD\u30FC\u30B9 alias \u306F\u7D50\u5408\u7B49\u5024\u4EE5\u5916\u306E WHERE \u6761\u4EF6\u3067\u306F\u53C2\u7167\u3067\u304D\u307E\u305B\u3093", tok);
|
|
33119
|
+
}
|
|
33120
|
+
if (i !== join.index && this.nodeContainsForeignQualifier(leaves[i], targetAppId)) {
|
|
33121
|
+
throw new ParseError(`UPDATE ... FROM \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30EB\u30BF\u306F APP${targetAppId} \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53C2\u7167\u3067\u304D\u307E\u3059`, tok);
|
|
33122
|
+
}
|
|
33123
|
+
}
|
|
33124
|
+
const filters = leaves.filter((_, index) => index !== join.index);
|
|
33125
|
+
const targetFilter = filters.reduce(
|
|
33126
|
+
(acc, expr) => acc === null ? expr : { type: "LOGICAL", op: "AND", left: acc, right: expr },
|
|
33127
|
+
null
|
|
33128
|
+
);
|
|
33129
|
+
return { targetJoinField: join.targetField, joinKeyField: join.sourceField, targetFilter };
|
|
33130
|
+
}
|
|
33131
|
+
flattenTopLevelAnd(expr) {
|
|
33132
|
+
if (expr.type === "GROUP") return this.flattenTopLevelAnd(expr.expr);
|
|
33133
|
+
if (expr.type === "LOGICAL" && expr.op === "AND") {
|
|
33134
|
+
return [...this.flattenTopLevelAnd(expr.left), ...this.flattenTopLevelAnd(expr.right)];
|
|
33135
|
+
}
|
|
33136
|
+
return [expr];
|
|
33137
|
+
}
|
|
33138
|
+
matchUpdateFromJoin(expr, targetAppId, sourceAlias) {
|
|
33139
|
+
if (expr.type !== "BINARY" || expr.op !== "=" || expr.left.type !== "FIELD") return null;
|
|
33140
|
+
const right = expr.right.type === "ARITH_VALUE" && expr.right.expr.type === "FIELD_REF" ? this.splitQualifiedField(expr.right.expr.field) : null;
|
|
33141
|
+
if (right === null) return null;
|
|
33142
|
+
const left = { alias: expr.left.tableAlias, field: expr.left.field };
|
|
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
|
+
}
|
|
33149
|
+
return null;
|
|
33150
|
+
}
|
|
33151
|
+
splitQualifiedField(field) {
|
|
33152
|
+
const dot = field.indexOf(".");
|
|
33153
|
+
return dot < 0 ? { alias: null, field } : { alias: field.slice(0, dot), field: field.slice(dot + 1) };
|
|
33154
|
+
}
|
|
33155
|
+
isTargetRef(ref, appId) {
|
|
33156
|
+
return ref.alias === null || ref.alias.toLowerCase() === `app${appId}`.toLowerCase();
|
|
33157
|
+
}
|
|
33158
|
+
isSourceRef(ref, alias) {
|
|
33159
|
+
return ref.alias?.toLowerCase() === alias.toLowerCase();
|
|
33160
|
+
}
|
|
33161
|
+
nodeContainsQualifiedField(node, alias) {
|
|
33162
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
33163
|
+
if (node === null || typeof node !== "object") return false;
|
|
33164
|
+
const obj = node;
|
|
33165
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string" && obj["tableAlias"].toLowerCase() === alias.toLowerCase()) return true;
|
|
33166
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33167
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
33168
|
+
if (this.isSourceRef(ref, alias)) return true;
|
|
33169
|
+
}
|
|
33170
|
+
return Object.values(obj).some((v) => this.nodeContainsQualifiedField(v, alias));
|
|
33171
|
+
}
|
|
33172
|
+
nodeContainsForeignQualifier(node, targetAppId) {
|
|
33173
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
33174
|
+
if (node === null || typeof node !== "object") return false;
|
|
33175
|
+
const obj = node;
|
|
33176
|
+
const expected = `app${targetAppId}`.toLowerCase();
|
|
33177
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") {
|
|
33178
|
+
return obj["tableAlias"].toLowerCase() !== expected;
|
|
33179
|
+
}
|
|
33180
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33181
|
+
const ref = this.splitQualifiedField(obj["field"]);
|
|
33182
|
+
if (ref.alias !== null) return ref.alias.toLowerCase() !== expected;
|
|
33183
|
+
}
|
|
33184
|
+
return Object.values(obj).some((v) => this.nodeContainsForeignQualifier(v, targetAppId));
|
|
33185
|
+
}
|
|
33186
|
+
nodeContainsAnyQualifier(node) {
|
|
33187
|
+
if (Array.isArray(node)) return node.some((v) => this.nodeContainsAnyQualifier(v));
|
|
33188
|
+
if (node === null || typeof node !== "object") return false;
|
|
33189
|
+
const obj = node;
|
|
33190
|
+
if (obj["type"] === "FIELD" && typeof obj["tableAlias"] === "string") return true;
|
|
33191
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") {
|
|
33192
|
+
if (this.splitQualifiedField(obj["field"]).alias !== null) return true;
|
|
33193
|
+
}
|
|
33194
|
+
return Object.values(obj).some((v) => this.nodeContainsAnyQualifier(v));
|
|
32979
33195
|
}
|
|
32980
33196
|
parseAssignments() {
|
|
32981
33197
|
const assignments = [];
|
|
@@ -33011,6 +33227,12 @@ var Parser = class {
|
|
|
33011
33227
|
const node = this.parseArithAddSub();
|
|
33012
33228
|
if (node.type === "NUMBER") return node;
|
|
33013
33229
|
if (node.type === "ARITH") return node;
|
|
33230
|
+
if (node.type === "FIELD_REF") {
|
|
33231
|
+
const dot = node.field.indexOf(".");
|
|
33232
|
+
if (dot > 0 && dot < node.field.length - 1) {
|
|
33233
|
+
return { type: "SOURCE_FIELD", alias: node.field.slice(0, dot), field: node.field.slice(dot + 1) };
|
|
33234
|
+
}
|
|
33235
|
+
}
|
|
33014
33236
|
throw new ParseError(
|
|
33015
33237
|
"SET \u306E\u5024\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306E\u307F\u306F\u4E0D\u53EF\uFF09",
|
|
33016
33238
|
tok
|
|
@@ -33258,6 +33480,15 @@ function isDmlType(type) {
|
|
|
33258
33480
|
function isReadOnlyType(type) {
|
|
33259
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";
|
|
33260
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
|
+
}
|
|
33261
33492
|
function hasWhereClause(stmt) {
|
|
33262
33493
|
if (!stmt || typeof stmt !== "object") return false;
|
|
33263
33494
|
const obj = stmt;
|
|
@@ -34485,11 +34716,17 @@ function analyzeBatch(statements) {
|
|
|
34485
34716
|
}
|
|
34486
34717
|
}
|
|
34487
34718
|
const defined = /* @__PURE__ */ new Map();
|
|
34719
|
+
const validationSchemas = /* @__PURE__ */ new Map();
|
|
34488
34720
|
const createdOrder = [];
|
|
34489
34721
|
const results = [];
|
|
34490
34722
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
34491
34723
|
const variableOrder = [];
|
|
34492
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
|
+
}
|
|
34493
34730
|
const statementType = getStatementType(stmt);
|
|
34494
34731
|
const created = [];
|
|
34495
34732
|
const dropped = [];
|
|
@@ -34544,6 +34781,28 @@ function analyzeBatch(statements) {
|
|
|
34544
34781
|
}
|
|
34545
34782
|
dependsOn.add(at);
|
|
34546
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
|
+
}
|
|
34547
34806
|
if (stmt.type === "CREATE_TEMP_TABLE") {
|
|
34548
34807
|
if (defined.has(stmt.name)) {
|
|
34549
34808
|
throw new BatchAnalysisError(
|
|
@@ -34576,8 +34835,8 @@ function analyzeBatch(statements) {
|
|
|
34576
34835
|
results.push({
|
|
34577
34836
|
index,
|
|
34578
34837
|
statementType,
|
|
34579
|
-
isDml:
|
|
34580
|
-
isReadOnly:
|
|
34838
|
+
isDml: writesKintone(stmt),
|
|
34839
|
+
isReadOnly: isReadOnlyStatement(stmt),
|
|
34581
34840
|
hasWhere: hasWhereClause(stmt),
|
|
34582
34841
|
insertValuesCount: getInsertValuesCount(stmt),
|
|
34583
34842
|
appIds: [...stmtAppIds].sort((a, b) => a - b),
|
|
@@ -34586,10 +34845,16 @@ function analyzeBatch(statements) {
|
|
|
34586
34845
|
tempTablesDropped: dropped,
|
|
34587
34846
|
dependsOn: [...dependsOn].sort((a, b) => a - b),
|
|
34588
34847
|
tempOnlySource,
|
|
34589
|
-
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
|
|
34848
|
+
targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : 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)
|
|
34590
34853
|
});
|
|
34591
34854
|
});
|
|
34592
34855
|
const containsDml = results.some((r) => r.isDml);
|
|
34856
|
+
const containsValidationOnly = results.some((r) => r.isValidationOnly);
|
|
34857
|
+
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
34593
34858
|
const variables = variableOrder.map((name) => ({
|
|
34594
34859
|
name,
|
|
34595
34860
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
@@ -34598,6 +34863,8 @@ function analyzeBatch(statements) {
|
|
|
34598
34863
|
statementCount: statements.length,
|
|
34599
34864
|
isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
|
|
34600
34865
|
containsDml,
|
|
34866
|
+
containsValidationOnly,
|
|
34867
|
+
requiresCompleteInput: needsCompleteInput,
|
|
34601
34868
|
tempTables: createdOrder,
|
|
34602
34869
|
variables,
|
|
34603
34870
|
warnings: variables.filter((v) => v.referencedBy.length === 0).map((v) => `variable @${v.name} is never used.`),
|
|
@@ -35131,7 +35398,7 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
35131
35398
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
35132
35399
|
const record2 = {};
|
|
35133
35400
|
for (const { field, value } of assignments) {
|
|
35134
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE") continue;
|
|
35401
|
+
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "SOURCE_FIELD") continue;
|
|
35135
35402
|
record2[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
35136
35403
|
}
|
|
35137
35404
|
return record2;
|
|
@@ -35235,6 +35502,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
35235
35502
|
record2[field] = { value: String(evalArith(value, raw)) };
|
|
35236
35503
|
} else if (value.type === "CASE_VALUE") {
|
|
35237
35504
|
record2[field] = { value: evalCaseWhenValue(value.expr, row, fieldTypes.get(field)) };
|
|
35505
|
+
} else if (value.type === "SOURCE_FIELD") {
|
|
35506
|
+
throw new DmlConvertError("SOURCE_FIELD \u306F UPDATE ... FROM \u5C02\u7528\u3067\u3059");
|
|
35238
35507
|
} else {
|
|
35239
35508
|
record2[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
35240
35509
|
}
|
|
@@ -35246,6 +35515,51 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
35246
35515
|
records: batch
|
|
35247
35516
|
}));
|
|
35248
35517
|
}
|
|
35518
|
+
var UPDATE_FROM_UNSUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
35519
|
+
"CHECK_BOX",
|
|
35520
|
+
"MULTI_SELECT",
|
|
35521
|
+
"USER_SELECT",
|
|
35522
|
+
"ORGANIZATION_SELECT",
|
|
35523
|
+
"GROUP_SELECT",
|
|
35524
|
+
"FILE"
|
|
35525
|
+
]);
|
|
35526
|
+
function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
35527
|
+
const updateRecords = matched.map(({ target, source }) => {
|
|
35528
|
+
const id = Number(target["$id"]?.value);
|
|
35529
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
35530
|
+
throw new DmlConvertError("UPDATE ... FROM \u306E\u5BFE\u8C61\u30EC\u30B3\u30FC\u30C9\u756A\u53F7\u304C\u4E0D\u6B63\u3067\u3059");
|
|
35531
|
+
}
|
|
35532
|
+
const targetRow = kintoneRecordToProcessRow(target);
|
|
35533
|
+
const record2 = {};
|
|
35534
|
+
for (const { field, value } of stmt.assignments) {
|
|
35535
|
+
const fieldType = fieldTypes.get(field);
|
|
35536
|
+
if (value.type === "SOURCE_FIELD") {
|
|
35537
|
+
if (UPDATE_FROM_UNSUPPORTED_TYPES.has(fieldType ?? "")) {
|
|
35538
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F ${fieldType} \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9: ${field}\uFF09`);
|
|
35539
|
+
}
|
|
35540
|
+
if (!Object.prototype.hasOwnProperty.call(source, value.field)) {
|
|
35541
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E\u30BD\u30FC\u30B9\u5217 ${value.field} \u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
35542
|
+
}
|
|
35543
|
+
const raw = source[value.field];
|
|
35544
|
+
if (typeof raw !== "string") {
|
|
35545
|
+
throw new DmlConvertError(`UPDATE ... FROM \u306E SOURCE_FIELD \u306F\u30B9\u30AB\u30E9\u30FC\u5024\u306E\u307F\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059\uFF08\u5217: ${value.field}\uFF09`);
|
|
35546
|
+
}
|
|
35547
|
+
if ((fieldType === "NUMBER" || fieldType === "CALC") && raw !== "" && !Number.isFinite(Number(raw))) {
|
|
35548
|
+
throw new DmlConvertError(`\u6570\u5024\u30D5\u30A3\u30FC\u30EB\u30C9 ${field} \u306B\u5909\u63DB\u3067\u304D\u306A\u3044\u5024\u3067\u3059: ${raw}`);
|
|
35549
|
+
}
|
|
35550
|
+
record2[field] = { value: toKintoneValue({ type: "STRING", value: raw }, fieldType) };
|
|
35551
|
+
} else if (value.type === "ARITH") {
|
|
35552
|
+
record2[field] = { value: String(evalArith(value, target)) };
|
|
35553
|
+
} else if (value.type === "CASE_VALUE") {
|
|
35554
|
+
record2[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
|
|
35555
|
+
} else {
|
|
35556
|
+
record2[field] = { value: toKintoneValue(value, fieldType) };
|
|
35557
|
+
}
|
|
35558
|
+
}
|
|
35559
|
+
return { id, record: record2 };
|
|
35560
|
+
});
|
|
35561
|
+
return chunk(updateRecords, 100).map((records) => ({ app: stmt.appId, records }));
|
|
35562
|
+
}
|
|
35249
35563
|
function kintoneRecordToProcessRow(raw) {
|
|
35250
35564
|
return Object.fromEntries(
|
|
35251
35565
|
Object.entries(raw).map(([k, v]) => [
|
|
@@ -35360,6 +35674,18 @@ function evalCaseWhenValue(expr, row, fieldType) {
|
|
|
35360
35674
|
return "";
|
|
35361
35675
|
}
|
|
35362
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) {
|
|
35363
35689
|
switch (value.type) {
|
|
35364
35690
|
case "VARIABLE":
|
|
35365
35691
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
@@ -36200,6 +36526,228 @@ function toFlatString(value) {
|
|
|
36200
36526
|
}
|
|
36201
36527
|
}
|
|
36202
36528
|
|
|
36529
|
+
// src/core/dmlValidation.ts
|
|
36530
|
+
var ARRAY_TYPES2 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
36531
|
+
var CHOICE_TYPES = /* @__PURE__ */ new Set(["DROP_DOWN", "RADIO_BUTTON", "CHECK_BOX", "MULTI_SELECT"]);
|
|
36532
|
+
function validateAndNormalizeDmlValue(raw, field) {
|
|
36533
|
+
if (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME") {
|
|
36534
|
+
const original = rawScalarText(raw);
|
|
36535
|
+
if (original !== "" && !isValidTemporalInput(original, field.fieldType)) {
|
|
36536
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
36537
|
+
}
|
|
36538
|
+
}
|
|
36539
|
+
let value;
|
|
36540
|
+
try {
|
|
36541
|
+
value = normalizeRaw(raw, field.fieldType);
|
|
36542
|
+
} catch (e) {
|
|
36543
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
36544
|
+
return { ok: false, code: typeCode(field.fieldType), message };
|
|
36545
|
+
}
|
|
36546
|
+
if (field.required && isEmpty(value)) {
|
|
36547
|
+
return { ok: false, code: "ERR_REQUIRED", message: `${field.code} \u306F\u5FC5\u9808\u3067\u3059` };
|
|
36548
|
+
}
|
|
36549
|
+
if (!isEmpty(value) && field.fieldType === "NUMBER") {
|
|
36550
|
+
const text = String(value);
|
|
36551
|
+
if (!isFiniteDecimal(text)) {
|
|
36552
|
+
return { ok: false, code: "ERR_TYPE_NUMBER", message: `${field.code} \u306F\u6570\u5024\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36553
|
+
}
|
|
36554
|
+
if (field.minValue != null && compareDecimal(text, field.minValue) < 0) {
|
|
36555
|
+
return { ok: false, code: "ERR_RANGE_MIN", message: `${field.code} \u306F ${field.minValue} \u4EE5\u4E0A\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36556
|
+
}
|
|
36557
|
+
if (field.maxValue != null && compareDecimal(text, field.maxValue) > 0) {
|
|
36558
|
+
return { ok: false, code: "ERR_RANGE_MAX", message: `${field.code} \u306F ${field.maxValue} \u4EE5\u4E0B\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044` };
|
|
36559
|
+
}
|
|
36560
|
+
}
|
|
36561
|
+
if (!isEmpty(value) && (field.fieldType === "DATE" || field.fieldType === "TIME" || field.fieldType === "DATETIME")) {
|
|
36562
|
+
if (!isValidTemporal(String(value), field.fieldType)) {
|
|
36563
|
+
return { ok: false, code: "ERR_TYPE_DATE", message: `${field.code} \u306E\u65E5\u4ED8\u30FB\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059` };
|
|
36564
|
+
}
|
|
36565
|
+
}
|
|
36566
|
+
if (typeof value === "string") {
|
|
36567
|
+
const length = value.length;
|
|
36568
|
+
const min = field.minLength == null ? null : Number(field.minLength);
|
|
36569
|
+
const max = field.maxLength == null ? null : Number(field.maxLength);
|
|
36570
|
+
if (Number.isFinite(min) && length < min) {
|
|
36571
|
+
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` };
|
|
36572
|
+
}
|
|
36573
|
+
if (Number.isFinite(max) && length > max) {
|
|
36574
|
+
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` };
|
|
36575
|
+
}
|
|
36576
|
+
}
|
|
36577
|
+
if (CHOICE_TYPES.has(field.fieldType) && field.optionOrder) {
|
|
36578
|
+
const selected = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
36579
|
+
if (selected.some((choice) => !(choice in field.optionOrder))) {
|
|
36580
|
+
return { ok: false, code: "ERR_CHOICE_INVALID", message: `${field.code} \u306B\u5B9A\u7FA9\u5916\u306E\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u3059` };
|
|
36581
|
+
}
|
|
36582
|
+
}
|
|
36583
|
+
return { ok: true, value };
|
|
36584
|
+
}
|
|
36585
|
+
function rawScalarText(raw) {
|
|
36586
|
+
if (raw == null) return "";
|
|
36587
|
+
if (isSqlValue(raw) && (raw.type === "STRING" || raw.type === "NUMBER")) return String(raw.value);
|
|
36588
|
+
return typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
|
|
36589
|
+
}
|
|
36590
|
+
function isValidTemporalInput(value, type) {
|
|
36591
|
+
if (type === "DATE") return isValidTemporal(value.replace(/\//g, "-"), "DATE");
|
|
36592
|
+
if (type === "TIME") return isValidTemporal(value, "TIME");
|
|
36593
|
+
let normalized = value.replace(/\//g, "-").replace(" ", "T");
|
|
36594
|
+
if (/T\d{2}:\d{2}$/.test(normalized)) normalized += ":00";
|
|
36595
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(normalized)) {
|
|
36596
|
+
return isValidTemporal(normalized.slice(0, 10), "DATE") && isValidTemporal(normalized.slice(11), "TIME");
|
|
36597
|
+
}
|
|
36598
|
+
return isValidTemporal(normalized, "DATETIME");
|
|
36599
|
+
}
|
|
36600
|
+
function normalizeRaw(raw, fieldType) {
|
|
36601
|
+
if (isSqlValue(raw)) {
|
|
36602
|
+
const normalized = normalizeDmlSqlValue(raw, fieldType);
|
|
36603
|
+
if (!normalized.ok) throw new Error(normalized.message);
|
|
36604
|
+
return normalized.value;
|
|
36605
|
+
}
|
|
36606
|
+
if (Array.isArray(raw)) return raw.map((v) => typeof v === "object" && v !== null && "code" in v ? String(v.code) : String(v));
|
|
36607
|
+
const text = raw == null ? "" : String(raw);
|
|
36608
|
+
if (ARRAY_TYPES2.has(fieldType)) {
|
|
36609
|
+
if (text === "") return [];
|
|
36610
|
+
try {
|
|
36611
|
+
const parsed = JSON.parse(text);
|
|
36612
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
36613
|
+
} catch {
|
|
36614
|
+
}
|
|
36615
|
+
return text.split(",").map((v) => v.trim());
|
|
36616
|
+
}
|
|
36617
|
+
return text;
|
|
36618
|
+
}
|
|
36619
|
+
function isSqlValue(value) {
|
|
36620
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
36621
|
+
}
|
|
36622
|
+
function isEmptyDmlValue(value) {
|
|
36623
|
+
if (value == null || value === "") return true;
|
|
36624
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
36625
|
+
if (isSqlValue(value)) {
|
|
36626
|
+
if (value.type === "STRING") return value.value === "";
|
|
36627
|
+
if (value.type === "ARRAY") return value.elements.length === 0;
|
|
36628
|
+
}
|
|
36629
|
+
return false;
|
|
36630
|
+
}
|
|
36631
|
+
function isEmpty(value) {
|
|
36632
|
+
return value === "" || Array.isArray(value) && value.length === 0;
|
|
36633
|
+
}
|
|
36634
|
+
function typeCode(type) {
|
|
36635
|
+
return type === "NUMBER" ? "ERR_TYPE_NUMBER" : "ERR_TYPE_DATE";
|
|
36636
|
+
}
|
|
36637
|
+
function isFiniteDecimal(value) {
|
|
36638
|
+
return /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim());
|
|
36639
|
+
}
|
|
36640
|
+
function compareDecimal(left, right) {
|
|
36641
|
+
const normalize = (input) => {
|
|
36642
|
+
let s = input.trim();
|
|
36643
|
+
let sign = 1;
|
|
36644
|
+
if (s.startsWith("-")) {
|
|
36645
|
+
sign = -1;
|
|
36646
|
+
s = s.slice(1);
|
|
36647
|
+
} else if (s.startsWith("+")) s = s.slice(1);
|
|
36648
|
+
let [whole, fraction = ""] = s.split(".");
|
|
36649
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
36650
|
+
fraction = fraction.replace(/0+$/, "");
|
|
36651
|
+
if (/^0*$/.test(whole) && fraction === "") sign = 1;
|
|
36652
|
+
return { sign, whole, fraction };
|
|
36653
|
+
};
|
|
36654
|
+
const a = normalize(left);
|
|
36655
|
+
const b = normalize(right);
|
|
36656
|
+
if (a.sign !== b.sign) return a.sign < b.sign ? -1 : 1;
|
|
36657
|
+
const direction = a.sign;
|
|
36658
|
+
if (a.whole.length !== b.whole.length) return a.whole.length < b.whole.length ? -direction : direction;
|
|
36659
|
+
if (a.whole !== b.whole) return a.whole < b.whole ? -direction : direction;
|
|
36660
|
+
const width = Math.max(a.fraction.length, b.fraction.length);
|
|
36661
|
+
const af = a.fraction.padEnd(width, "0");
|
|
36662
|
+
const bf = b.fraction.padEnd(width, "0");
|
|
36663
|
+
return af === bf ? 0 : af < bf ? -direction : direction;
|
|
36664
|
+
}
|
|
36665
|
+
function isValidTemporal(value, type) {
|
|
36666
|
+
if (type === "TIME") {
|
|
36667
|
+
const m2 = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
36668
|
+
return m2 !== null && Number(m2[1]) <= 23 && Number(m2[2]) <= 59 && Number(m2[3] ?? 0) <= 59;
|
|
36669
|
+
}
|
|
36670
|
+
const datePart = type === "DATE" ? value : value.slice(0, 10);
|
|
36671
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart);
|
|
36672
|
+
if (!m) return false;
|
|
36673
|
+
const year = Number(m[1]);
|
|
36674
|
+
const month = Number(m[2]);
|
|
36675
|
+
const day = Number(m[3]);
|
|
36676
|
+
const date5 = new Date(Date.UTC(year, month - 1, day));
|
|
36677
|
+
if (date5.getUTCFullYear() !== year || date5.getUTCMonth() !== month - 1 || date5.getUTCDate() !== day) return false;
|
|
36678
|
+
if (type === "DATE") return true;
|
|
36679
|
+
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");
|
|
36680
|
+
}
|
|
36681
|
+
|
|
36682
|
+
// src/core/dmlValidationCandidates.ts
|
|
36683
|
+
var VALIDATION_META_COLUMNS = [
|
|
36684
|
+
"$err_statement",
|
|
36685
|
+
"$err_operation",
|
|
36686
|
+
"$err_row",
|
|
36687
|
+
"$err_field",
|
|
36688
|
+
"$err_code",
|
|
36689
|
+
"$err_message"
|
|
36690
|
+
];
|
|
36691
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber) {
|
|
36692
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
36693
|
+
const errors = [];
|
|
36694
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
36695
|
+
for (const candidate of candidates) {
|
|
36696
|
+
candidate.record ??= {};
|
|
36697
|
+
const rowErrors = [...candidate.preErrors];
|
|
36698
|
+
for (const code of targetFields) {
|
|
36699
|
+
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code));
|
|
36700
|
+
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
36701
|
+
else candidate.record[code] = { value: result.value };
|
|
36702
|
+
}
|
|
36703
|
+
if (candidate.mode === "create") {
|
|
36704
|
+
for (const info of fieldInfos) {
|
|
36705
|
+
if (info.inSubtable) continue;
|
|
36706
|
+
if (candidate.payload.has(info.code)) continue;
|
|
36707
|
+
const emptyDefault = isEmptyDmlValue(info.defaultValue);
|
|
36708
|
+
if (!emptyDefault) {
|
|
36709
|
+
const defaultResult = validateAndNormalizeDmlValue(info.defaultValue, info);
|
|
36710
|
+
if (!defaultResult.ok) rowErrors.push({
|
|
36711
|
+
field: info.code,
|
|
36712
|
+
code: defaultResult.code,
|
|
36713
|
+
message: `\u65E2\u5B9A\u5024: ${defaultResult.message}`
|
|
36714
|
+
});
|
|
36715
|
+
} else {
|
|
36716
|
+
const emptyResult = validateAndNormalizeDmlValue("", info);
|
|
36717
|
+
if (!emptyResult.ok) {
|
|
36718
|
+
rowErrors.push({ field: info.code, code: emptyResult.code, message: emptyResult.message });
|
|
36719
|
+
} else if (info.required) {
|
|
36720
|
+
rowErrors.push({ field: info.code, code: "ERR_REQUIRED", message: `${info.code} \u306F\u5FC5\u9808\u3067\u3059` });
|
|
36721
|
+
}
|
|
36722
|
+
}
|
|
36723
|
+
}
|
|
36724
|
+
}
|
|
36725
|
+
if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
|
|
36726
|
+
for (const error51 of rowErrors) {
|
|
36727
|
+
const row = {};
|
|
36728
|
+
for (const field of payloadFields) row[field] = renderValidationValue(candidate.payload.get(field));
|
|
36729
|
+
row["$err_statement"] = String(statementNumber);
|
|
36730
|
+
row["$err_operation"] = operation;
|
|
36731
|
+
row["$err_row"] = String(candidate.rowNumber);
|
|
36732
|
+
row["$err_field"] = error51.field;
|
|
36733
|
+
row["$err_code"] = error51.code;
|
|
36734
|
+
row["$err_message"] = error51.message;
|
|
36735
|
+
errors.push(row);
|
|
36736
|
+
}
|
|
36737
|
+
}
|
|
36738
|
+
return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
|
|
36739
|
+
}
|
|
36740
|
+
function renderValidationValue(value) {
|
|
36741
|
+
if (value == null) return "";
|
|
36742
|
+
if (typeof value === "object" && "type" in value) {
|
|
36743
|
+
const sql = value;
|
|
36744
|
+
if (sql.type === "STRING" || sql.type === "NUMBER") return String(sql.value ?? "");
|
|
36745
|
+
if (sql.type === "ARRAY") return JSON.stringify(sql.elements?.map((e) => e.value) ?? []);
|
|
36746
|
+
}
|
|
36747
|
+
if (Array.isArray(value)) return JSON.stringify(value);
|
|
36748
|
+
return String(value);
|
|
36749
|
+
}
|
|
36750
|
+
|
|
36203
36751
|
// src/execute.ts
|
|
36204
36752
|
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";
|
|
36205
36753
|
var SearchAbortedError = class extends Error {
|
|
@@ -36303,6 +36851,15 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
36303
36851
|
throw new Error(`ParseError: variable @${unresolved} is not defined in a batch.`);
|
|
36304
36852
|
}
|
|
36305
36853
|
validateKlikeStatement(stmt);
|
|
36854
|
+
if ("validateOnly" in stmt && stmt.validateOnly === true) {
|
|
36855
|
+
if (stmt.validationErrorTable) {
|
|
36856
|
+
throw new Error("ArgumentError: VALIDATE ONLY INTO requires a batch.");
|
|
36857
|
+
}
|
|
36858
|
+
return executeDmlValidation(stmt, client, { ...options, onLimitReached: "error" }, cacheContext, void 0, 1);
|
|
36859
|
+
}
|
|
36860
|
+
if ("onErrorSkip" in stmt && stmt.onErrorSkip === true) {
|
|
36861
|
+
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
36862
|
+
}
|
|
36306
36863
|
switch (stmt.type) {
|
|
36307
36864
|
case "SELECT":
|
|
36308
36865
|
return executeSelect(stmt, client, options, cacheContext);
|
|
@@ -36344,6 +36901,17 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
36344
36901
|
}
|
|
36345
36902
|
}
|
|
36346
36903
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
36904
|
+
function appendValidationErrors(tempTables, name, columns, rows, maxRows) {
|
|
36905
|
+
const current = tempTables.get(name);
|
|
36906
|
+
if (current && (current.columns.length !== columns.length || current.columns.some((c, i) => c !== columns[i]))) {
|
|
36907
|
+
throw new Error(`ArgumentError: validation error table ${name} has a different schema.`);
|
|
36908
|
+
}
|
|
36909
|
+
const existingRows = current?.rows ?? [];
|
|
36910
|
+
if (existingRows.length + rows.length > maxRows) {
|
|
36911
|
+
throw new Error(`ArgumentError: temp table ${name} exceeds max rows (${maxRows}).`);
|
|
36912
|
+
}
|
|
36913
|
+
tempTables.set(name, { columns: [...columns], rows: [...existingRows, ...rows] });
|
|
36914
|
+
}
|
|
36347
36915
|
var BatchTimeoutError = class extends Error {
|
|
36348
36916
|
constructor() {
|
|
36349
36917
|
super("TimeoutError: batch timeout exceeded.");
|
|
@@ -36360,6 +36928,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
36360
36928
|
for (const s of analysis.statements) {
|
|
36361
36929
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
36362
36930
|
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
36931
|
+
const parsed = statements[s.index];
|
|
36932
|
+
if (parsed?.type === "UPDATE" && parsed.from?.cteName != null) continue;
|
|
36363
36933
|
throw new BatchAnalysisError(
|
|
36364
36934
|
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
36365
36935
|
s.index
|
|
@@ -36423,7 +36993,12 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
36423
36993
|
}
|
|
36424
36994
|
results.push({ ...base, status: "success", ...outcome });
|
|
36425
36995
|
} catch (e) {
|
|
36426
|
-
results.push({
|
|
36996
|
+
results.push({
|
|
36997
|
+
...base,
|
|
36998
|
+
status: "error",
|
|
36999
|
+
error: toBatchStatementError(e),
|
|
37000
|
+
...e instanceof RejectLimitExceededError ? { result: e.diagnostic } : {}
|
|
37001
|
+
});
|
|
36427
37002
|
failed.add(i);
|
|
36428
37003
|
if (e instanceof BatchTimeoutError) {
|
|
36429
37004
|
aborted2 = "timeout";
|
|
@@ -36482,6 +37057,38 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
36482
37057
|
}
|
|
36483
37058
|
const resolvedStmt = resolveVariableRefs(stmt, variables);
|
|
36484
37059
|
validateKlikeStatement(resolvedStmt);
|
|
37060
|
+
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
37061
|
+
const result = await executeDmlValidation(
|
|
37062
|
+
resolvedStmt,
|
|
37063
|
+
client,
|
|
37064
|
+
{ ...options, onLimitReached: "error" },
|
|
37065
|
+
cacheContext,
|
|
37066
|
+
tempTables,
|
|
37067
|
+
info.index + 1
|
|
37068
|
+
);
|
|
37069
|
+
if (resolvedStmt.validationErrorTable) {
|
|
37070
|
+
appendValidationErrors(
|
|
37071
|
+
tempTables,
|
|
37072
|
+
resolvedStmt.validationErrorTable,
|
|
37073
|
+
result.columns,
|
|
37074
|
+
result.errors,
|
|
37075
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
37076
|
+
);
|
|
37077
|
+
}
|
|
37078
|
+
return { result };
|
|
37079
|
+
}
|
|
37080
|
+
if ("onErrorSkip" in resolvedStmt && resolvedStmt.onErrorSkip === true) {
|
|
37081
|
+
return {
|
|
37082
|
+
result: await executeOnErrorSkip(
|
|
37083
|
+
resolvedStmt,
|
|
37084
|
+
client,
|
|
37085
|
+
{ ...options, onLimitReached: "error" },
|
|
37086
|
+
cacheContext,
|
|
37087
|
+
tempTables,
|
|
37088
|
+
info.index + 1
|
|
37089
|
+
)
|
|
37090
|
+
};
|
|
37091
|
+
}
|
|
36485
37092
|
if (resolvedStmt.type === "CREATE_TEMP_TABLE") {
|
|
36486
37093
|
const materializeOptions = {
|
|
36487
37094
|
...options,
|
|
@@ -36516,6 +37123,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
36516
37123
|
if (resolvedStmt.type === "UPSERT_SELECT") {
|
|
36517
37124
|
return { result: await executeUpsertSelect(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
36518
37125
|
}
|
|
37126
|
+
if (resolvedStmt.type === "UPDATE" && resolvedStmt.from?.cteName != null) {
|
|
37127
|
+
return { result: await executeUpdate(resolvedStmt, client, options, cacheContext, tempTables) };
|
|
37128
|
+
}
|
|
36519
37129
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
36520
37130
|
}
|
|
36521
37131
|
return { result: await executeParsedStatement(resolvedStmt, client, options, cacheContext) };
|
|
@@ -37627,7 +38237,7 @@ async function buildSortKindsForSelect(stmt, client, cacheContext) {
|
|
|
37627
38237
|
}
|
|
37628
38238
|
function convertProcessRowValue(raw, dstFieldType) {
|
|
37629
38239
|
const USER_TYPES2 = /* @__PURE__ */ new Set(["USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT"]);
|
|
37630
|
-
const
|
|
38240
|
+
const ARRAY_TYPES3 = /* @__PURE__ */ new Set(["CHECK_BOX", "MULTI_SELECT"]);
|
|
37631
38241
|
if (USER_TYPES2.has(dstFieldType ?? "")) {
|
|
37632
38242
|
if (raw === "") return [];
|
|
37633
38243
|
try {
|
|
@@ -37639,7 +38249,7 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
37639
38249
|
}
|
|
37640
38250
|
return raw.split(",").map((c) => ({ code: c.trim() }));
|
|
37641
38251
|
}
|
|
37642
|
-
if (
|
|
38252
|
+
if (ARRAY_TYPES3.has(dstFieldType ?? "")) {
|
|
37643
38253
|
if (raw === "") return [];
|
|
37644
38254
|
try {
|
|
37645
38255
|
const parsed = JSON.parse(raw);
|
|
@@ -37650,6 +38260,396 @@ function convertProcessRowValue(raw, dstFieldType) {
|
|
|
37650
38260
|
}
|
|
37651
38261
|
return raw;
|
|
37652
38262
|
}
|
|
38263
|
+
var NON_WRITABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
38264
|
+
"CALC",
|
|
38265
|
+
"RECORD_NUMBER",
|
|
38266
|
+
"CREATOR",
|
|
38267
|
+
"CREATED_TIME",
|
|
38268
|
+
"MODIFIER",
|
|
38269
|
+
"UPDATED_TIME",
|
|
38270
|
+
"STATUS",
|
|
38271
|
+
"STATUS_ASSIGNEE",
|
|
38272
|
+
"CATEGORY",
|
|
38273
|
+
"REFERENCE_TABLE"
|
|
38274
|
+
]);
|
|
38275
|
+
async function executeDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38276
|
+
return (await prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber)).result;
|
|
38277
|
+
}
|
|
38278
|
+
var RejectLimitExceededError = class extends Error {
|
|
38279
|
+
constructor(message, diagnostic) {
|
|
38280
|
+
super(`RejectLimitExceededError: ${message}`);
|
|
38281
|
+
this.diagnostic = diagnostic;
|
|
38282
|
+
this.name = "RejectLimitExceededError";
|
|
38283
|
+
}
|
|
38284
|
+
};
|
|
38285
|
+
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38286
|
+
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
38287
|
+
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
38288
|
+
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
38289
|
+
throw new Error("ArgumentError: DML target fields contain duplicates.");
|
|
38290
|
+
}
|
|
38291
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
38292
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
38293
|
+
const targetFields = stmt.type === "UPDATE" ? stmt.assignments.map((a) => a.field) : stmt.fields;
|
|
38294
|
+
for (const code of targetFields) {
|
|
38295
|
+
const info = infoByCode.get(code);
|
|
38296
|
+
if (!info) throw new Error(`ArgumentError: DML target field ${code} does not exist.`);
|
|
38297
|
+
if (info.writable === false || NON_WRITABLE_FIELD_TYPES.has(info.fieldType)) {
|
|
38298
|
+
throw new Error(`ArgumentError: DML target field ${code} is not writable (${info.fieldType}).`);
|
|
38299
|
+
}
|
|
38300
|
+
}
|
|
38301
|
+
const candidates = await materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode);
|
|
38302
|
+
const { errors, invalidRows, invalidRowNumbers } = validateDmlCandidates(
|
|
38303
|
+
candidates,
|
|
38304
|
+
operation,
|
|
38305
|
+
payloadFields,
|
|
38306
|
+
targetFields,
|
|
38307
|
+
fieldInfos,
|
|
38308
|
+
statementNumber
|
|
38309
|
+
);
|
|
38310
|
+
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
38311
|
+
const result = {
|
|
38312
|
+
type: "VALIDATION",
|
|
38313
|
+
operation,
|
|
38314
|
+
validatedRows: candidates.length,
|
|
38315
|
+
validRows: candidates.length - invalidRows,
|
|
38316
|
+
invalidRows,
|
|
38317
|
+
errorCount: errors.length,
|
|
38318
|
+
columns,
|
|
38319
|
+
errors,
|
|
38320
|
+
...stmt.validationErrorTable ? { errTable: stmt.validationErrorTable } : stmt.onErrorSkip && stmt.errorTable ? { errTable: stmt.errorTable } : {}
|
|
38321
|
+
};
|
|
38322
|
+
return { result, candidates, invalidRowNumbers };
|
|
38323
|
+
}
|
|
38324
|
+
async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
38325
|
+
const prepared = await prepareDmlValidation(
|
|
38326
|
+
stmt,
|
|
38327
|
+
client,
|
|
38328
|
+
options,
|
|
38329
|
+
cacheContext,
|
|
38330
|
+
tempTables,
|
|
38331
|
+
statementNumber
|
|
38332
|
+
);
|
|
38333
|
+
const errTable = stmt.errorTable;
|
|
38334
|
+
if (!errTable) throw new Error("ArgumentError: ON ERROR SKIP requires INTO #error_table.");
|
|
38335
|
+
appendValidationErrors(
|
|
38336
|
+
tempTables,
|
|
38337
|
+
errTable,
|
|
38338
|
+
prepared.result.columns,
|
|
38339
|
+
prepared.result.errors,
|
|
38340
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS
|
|
38341
|
+
);
|
|
38342
|
+
const rejectLimit = stmt.rejectLimit ?? null;
|
|
38343
|
+
if (rejectLimit !== null && prepared.result.invalidRows > rejectLimit) {
|
|
38344
|
+
throw new RejectLimitExceededError(
|
|
38345
|
+
`rejected rows (${prepared.result.invalidRows}) exceed REJECT LIMIT (${rejectLimit}).`,
|
|
38346
|
+
prepared.result
|
|
38347
|
+
);
|
|
38348
|
+
}
|
|
38349
|
+
const valid = prepared.candidates.filter((candidate) => !prepared.invalidRowNumbers.has(candidate.rowNumber));
|
|
38350
|
+
if (options.confirm) {
|
|
38351
|
+
const operation = stmt.type.startsWith("INSERT") ? "INSERT" : "UPDATE";
|
|
38352
|
+
const ok = await options.confirm(valid.length, operation);
|
|
38353
|
+
if (!ok) throw new OperationCancelledError(operation, valid.length);
|
|
38354
|
+
}
|
|
38355
|
+
const common = {
|
|
38356
|
+
affectedRows: valid.length,
|
|
38357
|
+
skippedRows: prepared.result.invalidRows,
|
|
38358
|
+
rejectLimit,
|
|
38359
|
+
errTable
|
|
38360
|
+
};
|
|
38361
|
+
if (stmt.type === "INSERT" || stmt.type === "INSERT_SELECT") {
|
|
38362
|
+
const createdIds = [];
|
|
38363
|
+
for (let i = 0; i < valid.length; i += 100) {
|
|
38364
|
+
const response = await client.postRecords({ app: stmt.appId, records: valid.slice(i, i + 100).map((c) => c.record) });
|
|
38365
|
+
createdIds.push(response.ids);
|
|
38366
|
+
}
|
|
38367
|
+
return { type: "INSERT", createdIds, insertedCount: createdIds.flat().length, ...common };
|
|
38368
|
+
}
|
|
38369
|
+
if (stmt.type === "UPDATE") {
|
|
38370
|
+
const updates2 = valid.map((candidate) => {
|
|
38371
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPDATE candidate has no targetId.");
|
|
38372
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
38373
|
+
});
|
|
38374
|
+
for (let i = 0; i < updates2.length; i += 100) {
|
|
38375
|
+
await client.putRecords({ app: stmt.appId, records: updates2.slice(i, i + 100) });
|
|
38376
|
+
}
|
|
38377
|
+
return { type: "UPDATE", updatedCount: updates2.length, ...common };
|
|
38378
|
+
}
|
|
38379
|
+
const inserts = valid.filter((candidate) => candidate.mode === "create");
|
|
38380
|
+
const updates = valid.filter((candidate) => candidate.mode === "update").map((candidate) => {
|
|
38381
|
+
if (candidate.targetId === void 0) throw new Error("InternalError: prepared UPSERT candidate has no targetId.");
|
|
38382
|
+
return { id: candidate.targetId, record: candidate.record };
|
|
38383
|
+
});
|
|
38384
|
+
let insertedCount = 0;
|
|
38385
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
38386
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map((c) => c.record) });
|
|
38387
|
+
insertedCount += response.ids.length;
|
|
38388
|
+
}
|
|
38389
|
+
for (let i = 0; i < updates.length; i += 100) {
|
|
38390
|
+
await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
38391
|
+
}
|
|
38392
|
+
return { type: "UPSERT", insertedCount, updatedCount: updates.length, ...common };
|
|
38393
|
+
}
|
|
38394
|
+
async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
|
|
38395
|
+
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
38396
|
+
let rows;
|
|
38397
|
+
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
38398
|
+
rows = stmt.values.map((row) => row.map(
|
|
38399
|
+
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
38400
|
+
));
|
|
38401
|
+
} else {
|
|
38402
|
+
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);
|
|
38403
|
+
if (selectResult.columns.length !== stmt.fields.length) {
|
|
38404
|
+
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`);
|
|
38405
|
+
}
|
|
38406
|
+
rows = selectResult.rows.map((row) => selectResult.columns.map((column) => row[column] ?? ""));
|
|
38407
|
+
}
|
|
38408
|
+
const candidates = rows.map((values, index) => ({
|
|
38409
|
+
rowNumber: index + 1,
|
|
38410
|
+
operation,
|
|
38411
|
+
mode: "create",
|
|
38412
|
+
payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
|
|
38413
|
+
preErrors: [],
|
|
38414
|
+
record: {}
|
|
38415
|
+
}));
|
|
38416
|
+
if (stmt.type !== "UPSERT" && stmt.type !== "UPSERT_SELECT") return candidates;
|
|
38417
|
+
for (const key of stmt.keyFields) {
|
|
38418
|
+
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`);
|
|
38419
|
+
}
|
|
38420
|
+
const fieldTypes = new Map([...infoByCode].map(([code, info]) => [code, info.fieldType]));
|
|
38421
|
+
const rowKeys = candidates.map((candidate) => stmt.keyFields.map((key) => renderValidationValue(candidate.payload.get(key))));
|
|
38422
|
+
const targets = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeys, client, options, fieldTypes);
|
|
38423
|
+
const numeric = stmt.keyFields.map((key) => fieldTypes.get(key) === "NUMBER");
|
|
38424
|
+
const keyCounts = /* @__PURE__ */ new Map();
|
|
38425
|
+
for (const parts of rowKeys) {
|
|
38426
|
+
const key = upsertNormalizedKey(parts, numeric);
|
|
38427
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
38428
|
+
}
|
|
38429
|
+
candidates.forEach((candidate, index) => {
|
|
38430
|
+
const parts = rowKeys[index];
|
|
38431
|
+
const targetId = lookupUpsertTarget(targets, parts);
|
|
38432
|
+
candidate.mode = targetId === void 0 ? "create" : "update";
|
|
38433
|
+
if (targetId !== void 0) candidate.targetId = targetId;
|
|
38434
|
+
stmt.keyFields.forEach((key, keyIndex) => {
|
|
38435
|
+
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` });
|
|
38436
|
+
});
|
|
38437
|
+
if ((keyCounts.get(upsertNormalizedKey(parts, numeric)) ?? 0) > 1) {
|
|
38438
|
+
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" });
|
|
38439
|
+
}
|
|
38440
|
+
});
|
|
38441
|
+
return candidates;
|
|
38442
|
+
}
|
|
38443
|
+
async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
|
|
38444
|
+
if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
38445
|
+
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
38446
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38447
|
+
let records;
|
|
38448
|
+
if (hasArithAssignment(stmt)) {
|
|
38449
|
+
const getParams = updateToGetQueryForArith(stmt);
|
|
38450
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
|
|
38451
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
38452
|
+
parallel: options.fetchParallel ?? 1,
|
|
38453
|
+
onLimit: "error"
|
|
38454
|
+
});
|
|
38455
|
+
records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
|
|
38456
|
+
} else {
|
|
38457
|
+
const getParams = updateToGetQuery(stmt);
|
|
38458
|
+
const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
|
|
38459
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
38460
|
+
parallel: options.fetchParallel ?? 1
|
|
38461
|
+
});
|
|
38462
|
+
records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
|
|
38463
|
+
}
|
|
38464
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
38465
|
+
rowNumber: index + 1,
|
|
38466
|
+
operation: "UPDATE",
|
|
38467
|
+
mode: "update",
|
|
38468
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
38469
|
+
preErrors: [],
|
|
38470
|
+
record: entry.record,
|
|
38471
|
+
targetId: entry.id
|
|
38472
|
+
}));
|
|
38473
|
+
}
|
|
38474
|
+
async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
|
|
38475
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
38476
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38477
|
+
const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
|
|
38478
|
+
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
38479
|
+
rowNumber: index + 1,
|
|
38480
|
+
operation: "UPDATE",
|
|
38481
|
+
mode: "update",
|
|
38482
|
+
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
38483
|
+
preErrors: [],
|
|
38484
|
+
record: entry.record,
|
|
38485
|
+
targetId: entry.id
|
|
38486
|
+
}));
|
|
38487
|
+
}
|
|
38488
|
+
var UPDATE_FROM_KEY_CHUNK_SIZE = UPSERT_IN_CHUNK_SIZE;
|
|
38489
|
+
var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
38490
|
+
"CHECK_BOX",
|
|
38491
|
+
"MULTI_SELECT",
|
|
38492
|
+
"USER_SELECT",
|
|
38493
|
+
"ORGANIZATION_SELECT",
|
|
38494
|
+
"GROUP_SELECT",
|
|
38495
|
+
"FILE"
|
|
38496
|
+
]);
|
|
38497
|
+
async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
|
|
38498
|
+
const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
|
|
38499
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
|
|
38500
|
+
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
38501
|
+
const sourceRows = await loadUpdateFromSourceRows(
|
|
38502
|
+
from,
|
|
38503
|
+
requiredSourceFields,
|
|
38504
|
+
sourceFields,
|
|
38505
|
+
client,
|
|
38506
|
+
options,
|
|
38507
|
+
cacheContext,
|
|
38508
|
+
tempTables
|
|
38509
|
+
);
|
|
38510
|
+
const sourceByKey = /* @__PURE__ */ new Map();
|
|
38511
|
+
for (const row of sourceRows) {
|
|
38512
|
+
if (!Object.prototype.hasOwnProperty.call(row, from.joinKeyField)) {
|
|
38513
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
38514
|
+
}
|
|
38515
|
+
const key = normalizeUpdateFromJoinKey(row[from.joinKeyField], joinKind, "source");
|
|
38516
|
+
if (sourceByKey.has(key)) {
|
|
38517
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source has multiple rows for normalized key ${key}.`);
|
|
38518
|
+
}
|
|
38519
|
+
sourceByKey.set(key, row);
|
|
38520
|
+
}
|
|
38521
|
+
if (sourceByKey.size === 0) return [];
|
|
38522
|
+
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
38523
|
+
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
38524
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
38525
|
+
const targetRecords = [];
|
|
38526
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
38527
|
+
let fetchedTargetCount = 0;
|
|
38528
|
+
for (const keys of splitChunks([...sourceByKey.keys()], UPDATE_FROM_KEY_CHUNK_SIZE)) {
|
|
38529
|
+
const keyQuery = `${from.targetJoinField} in (${keys.map(sqlQuote).join(",")})`;
|
|
38530
|
+
const query = filterQuery ? `(${keyQuery}) and (${filterQuery})` : keyQuery;
|
|
38531
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
38532
|
+
client.getRecords,
|
|
38533
|
+
stmt.appId,
|
|
38534
|
+
query,
|
|
38535
|
+
targetFields,
|
|
38536
|
+
{ maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38537
|
+
);
|
|
38538
|
+
fetchedTargetCount += resolved.records.length;
|
|
38539
|
+
if (fetchedTargetCount > maxRecords2) {
|
|
38540
|
+
throw new FetchAllLimitError(
|
|
38541
|
+
`\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`
|
|
38542
|
+
);
|
|
38543
|
+
}
|
|
38544
|
+
for (const record2 of resolved.records) {
|
|
38545
|
+
const id = record2["$id"]?.value;
|
|
38546
|
+
if (typeof id !== "string" || id === "") {
|
|
38547
|
+
throw new Error("ArgumentError: UPDATE ... FROM target record does not contain a valid $id.");
|
|
38548
|
+
}
|
|
38549
|
+
if (seenTargetIds.has(id)) continue;
|
|
38550
|
+
seenTargetIds.add(id);
|
|
38551
|
+
targetRecords.push(record2);
|
|
38552
|
+
}
|
|
38553
|
+
}
|
|
38554
|
+
const matched = [];
|
|
38555
|
+
for (const target of targetRecords) {
|
|
38556
|
+
const raw = target[from.targetJoinField]?.value;
|
|
38557
|
+
const key = normalizeUpdateFromJoinKey(raw, joinKind, "target");
|
|
38558
|
+
if (key === null) continue;
|
|
38559
|
+
const source = sourceByKey.get(key);
|
|
38560
|
+
if (source !== void 0) matched.push({ target, source });
|
|
38561
|
+
}
|
|
38562
|
+
return matched;
|
|
38563
|
+
}
|
|
38564
|
+
async function resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext) {
|
|
38565
|
+
if (from.targetJoinField === "$id") return "id";
|
|
38566
|
+
const info = (await getFieldsCached(stmt.appId, client, cacheContext)).find((field) => field.code === from.targetJoinField);
|
|
38567
|
+
if (!info) {
|
|
38568
|
+
throw new Error(`ArgumentError: UPDATE ... FROM target column ${from.targetJoinField} does not exist.`);
|
|
38569
|
+
}
|
|
38570
|
+
if (info.inSubtable || info.writable === false || info.fieldType !== "SINGLE_LINE_TEXT" && info.fieldType !== "NUMBER") {
|
|
38571
|
+
throw new Error(
|
|
38572
|
+
`ArgumentError: UPDATE ... FROM does not support target join field type ${info.fieldType} (${from.targetJoinField}).`
|
|
38573
|
+
);
|
|
38574
|
+
}
|
|
38575
|
+
return info.fieldType === "NUMBER" ? "number" : "string";
|
|
38576
|
+
}
|
|
38577
|
+
async function loadUpdateFromSourceRows(from, requiredSourceFields, sourceValueFields, client, options, cacheContext, tempTables) {
|
|
38578
|
+
if (from.cteName !== null) {
|
|
38579
|
+
const table = tempTables?.get(from.cteName);
|
|
38580
|
+
if (!table) throw new Error(`ArgumentError: temp table ${from.cteName} is not available.`);
|
|
38581
|
+
for (const field of requiredSourceFields) {
|
|
38582
|
+
if (!table.columns.includes(field)) {
|
|
38583
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
38584
|
+
}
|
|
38585
|
+
}
|
|
38586
|
+
return table.rows;
|
|
38587
|
+
}
|
|
38588
|
+
const sourceTypes = await getFieldTypeMap(from.appId, client, cacheContext);
|
|
38589
|
+
const joinType = from.joinKeyField === "$id" ? "RECORD_NUMBER" : sourceTypes.get(from.joinKeyField);
|
|
38590
|
+
if (joinType === void 0) {
|
|
38591
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${from.joinKeyField} does not exist.`);
|
|
38592
|
+
}
|
|
38593
|
+
if (from.joinKeyField !== "$id" && joinType !== "SINGLE_LINE_TEXT" && joinType !== "NUMBER") {
|
|
38594
|
+
throw new Error(
|
|
38595
|
+
`ArgumentError: UPDATE ... FROM does not support source join field type ${joinType} (${from.joinKeyField}).`
|
|
38596
|
+
);
|
|
38597
|
+
}
|
|
38598
|
+
for (const field of sourceValueFields) {
|
|
38599
|
+
if (field !== "$id" && !sourceTypes.has(field)) {
|
|
38600
|
+
throw new Error(`ArgumentError: UPDATE ... FROM source column ${field} does not exist.`);
|
|
38601
|
+
}
|
|
38602
|
+
const type = field === "$id" ? "RECORD_NUMBER" : sourceTypes.get(field);
|
|
38603
|
+
if (UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES.has(type ?? "")) {
|
|
38604
|
+
throw new Error(`ArgumentError: UPDATE ... FROM does not support source field type ${type} (${field}).`);
|
|
38605
|
+
}
|
|
38606
|
+
}
|
|
38607
|
+
const resolved = await fetchRecordsForSharedPlan(
|
|
38608
|
+
client.getRecords,
|
|
38609
|
+
from.appId,
|
|
38610
|
+
"",
|
|
38611
|
+
requiredSourceFields,
|
|
38612
|
+
{ maxRecords: options.maxRecords ?? 1e4, parallel: options.fetchParallel ?? 1, onLimit: "error" }
|
|
38613
|
+
);
|
|
38614
|
+
return resolved.records.map((record2) => flatten(record2, null));
|
|
38615
|
+
}
|
|
38616
|
+
function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
38617
|
+
if (typeof raw !== "string") {
|
|
38618
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a scalar string: ${String(raw)}`);
|
|
38619
|
+
}
|
|
38620
|
+
if (kind === "string") {
|
|
38621
|
+
if (raw === "") {
|
|
38622
|
+
if (side === "target") return null;
|
|
38623
|
+
throw new Error("ArgumentError: UPDATE ... FROM source key must not be empty.");
|
|
38624
|
+
}
|
|
38625
|
+
return raw;
|
|
38626
|
+
}
|
|
38627
|
+
if (kind === "number" && side === "target" && raw === "") return null;
|
|
38628
|
+
if (kind === "id") {
|
|
38629
|
+
const text2 = raw.trim();
|
|
38630
|
+
const id = Number(text2);
|
|
38631
|
+
if (text2 === "" || !Number.isSafeInteger(id) || id <= 0) {
|
|
38632
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a positive safe integer: ${raw}`);
|
|
38633
|
+
}
|
|
38634
|
+
return String(id);
|
|
38635
|
+
}
|
|
38636
|
+
const text = raw.trim();
|
|
38637
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) {
|
|
38638
|
+
throw new Error(`ArgumentError: UPDATE ... FROM ${side} key must be a finite decimal: ${raw}`);
|
|
38639
|
+
}
|
|
38640
|
+
let unsigned = text;
|
|
38641
|
+
let negative = false;
|
|
38642
|
+
if (unsigned.startsWith("-") || unsigned.startsWith("+")) {
|
|
38643
|
+
negative = unsigned[0] === "-";
|
|
38644
|
+
unsigned = unsigned.slice(1);
|
|
38645
|
+
}
|
|
38646
|
+
let [whole, fraction = ""] = unsigned.split(".");
|
|
38647
|
+
whole = (whole || "0").replace(/^0+(?=\d)/, "");
|
|
38648
|
+
fraction = fraction.replace(/0+$/, "");
|
|
38649
|
+
const zero = /^0*$/.test(whole) && fraction === "";
|
|
38650
|
+
const canonical = fraction === "" ? whole : `${whole}.${fraction}`;
|
|
38651
|
+
return negative && !zero ? `-${canonical}` : canonical;
|
|
38652
|
+
}
|
|
37653
38653
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
37654
38654
|
if (stmt.subtableCode) {
|
|
37655
38655
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
@@ -37701,10 +38701,13 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
37701
38701
|
insertedCount: createdIds.flat().length
|
|
37702
38702
|
};
|
|
37703
38703
|
}
|
|
37704
|
-
async function executeUpdate(stmt, client, options, cacheContext) {
|
|
38704
|
+
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
37705
38705
|
if (stmt.subtableCode) {
|
|
37706
38706
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
37707
38707
|
}
|
|
38708
|
+
if (stmt.from != null) {
|
|
38709
|
+
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
38710
|
+
}
|
|
37708
38711
|
const maxRecords2 = options.maxRecords ?? 1e4;
|
|
37709
38712
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
37710
38713
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
@@ -37746,6 +38749,35 @@ async function executeUpdate(stmt, client, options, cacheContext) {
|
|
|
37746
38749
|
}
|
|
37747
38750
|
return { type: "UPDATE", updatedCount: ids.length };
|
|
37748
38751
|
}
|
|
38752
|
+
async function executeUpdateFrom(stmt, from, client, options, cacheContext, tempTables) {
|
|
38753
|
+
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
38754
|
+
if (options.confirm) {
|
|
38755
|
+
const ok = await options.confirm(matched.length, "UPDATE");
|
|
38756
|
+
if (!ok) throw new OperationCancelledError("UPDATE", matched.length);
|
|
38757
|
+
}
|
|
38758
|
+
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
38759
|
+
const batches = updateFromToPutBatches(stmt, matched, fieldTypes);
|
|
38760
|
+
for (const batch of batches) await client.putRecords(batch);
|
|
38761
|
+
return { type: "UPDATE", updatedCount: matched.length };
|
|
38762
|
+
}
|
|
38763
|
+
function collectUpdateFromTargetFields(stmt) {
|
|
38764
|
+
const fields = /* @__PURE__ */ new Set(["$id"]);
|
|
38765
|
+
if (stmt.from) fields.add(stmt.from.targetJoinField);
|
|
38766
|
+
const visit = (node) => {
|
|
38767
|
+
if (Array.isArray(node)) {
|
|
38768
|
+
node.forEach(visit);
|
|
38769
|
+
return;
|
|
38770
|
+
}
|
|
38771
|
+
if (node === null || typeof node !== "object") return;
|
|
38772
|
+
const obj = node;
|
|
38773
|
+
if (obj["type"] === "FIELD_REF" && typeof obj["field"] === "string") fields.add(obj["field"]);
|
|
38774
|
+
for (const value of Object.values(obj)) visit(value);
|
|
38775
|
+
};
|
|
38776
|
+
for (const assignment of stmt.assignments) {
|
|
38777
|
+
if (assignment.value.type !== "SOURCE_FIELD") visit(assignment.value);
|
|
38778
|
+
}
|
|
38779
|
+
return [...fields];
|
|
38780
|
+
}
|
|
37749
38781
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
37750
38782
|
if (stmt.subtableCode) {
|
|
37751
38783
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
@@ -38671,9 +39703,16 @@ function buildUpdatePlan(stmt, label) {
|
|
|
38671
39703
|
const isSubq = stmt.assignments.some((a) => a.value.type === "SCALAR_SUBQUERY");
|
|
38672
39704
|
const lines = [];
|
|
38673
39705
|
if (label) lines.push(label);
|
|
38674
|
-
lines.push(` [UPDATE]`);
|
|
39706
|
+
lines.push(stmt.from ? ` [UPDATE FROM]` : ` [UPDATE]`);
|
|
38675
39707
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
38676
|
-
|
|
39708
|
+
if (stmt.from) {
|
|
39709
|
+
const source = stmt.from.cteName ?? `APP${stmt.from.appId}`;
|
|
39710
|
+
lines.push(` source: ${source} AS ${stmt.from.alias}`);
|
|
39711
|
+
lines.push(` join: APP${stmt.appId}.${stmt.from.targetJoinField} = ${stmt.from.alias}.${stmt.from.joinKeyField}`);
|
|
39712
|
+
lines.push(` target filter: ${stmt.from.targetFilter ? safeWhereToKintone(stmt.from.targetFilter) : "(none)"}`);
|
|
39713
|
+
} else {
|
|
39714
|
+
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
39715
|
+
}
|
|
38677
39716
|
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
38678
39717
|
const setTypes = [];
|
|
38679
39718
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
@@ -38786,6 +39825,7 @@ function formatAssignment(a) {
|
|
|
38786
39825
|
if (v.type === "ARITH") return `${a.field} = ${formatArithExprStr(v)}`;
|
|
38787
39826
|
if (v.type === "CASE_VALUE") return `${a.field} = CASE WHEN ...`;
|
|
38788
39827
|
if (v.type === "SCALAR_SUBQUERY") return `${a.field} = (SELECT ...)`;
|
|
39828
|
+
if (v.type === "SOURCE_FIELD") return `${a.field} = ${v.alias}.${v.field}`;
|
|
38789
39829
|
return `${a.field} = (${v.type})`;
|
|
38790
39830
|
}
|
|
38791
39831
|
function formatArithExprStr(expr) {
|
|
@@ -38825,12 +39865,32 @@ function parseSqlStatements(sql) {
|
|
|
38825
39865
|
// src/output/batchEnvelope.ts
|
|
38826
39866
|
function toMutationSummary(result) {
|
|
38827
39867
|
if (result.type === "INSERT") {
|
|
38828
|
-
return {
|
|
39868
|
+
return {
|
|
39869
|
+
insertedCount: result.insertedCount,
|
|
39870
|
+
createdIds: result.createdIds,
|
|
39871
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
39872
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
39873
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
39874
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
39875
|
+
};
|
|
38829
39876
|
}
|
|
38830
|
-
if (result.type === "UPDATE") return {
|
|
39877
|
+
if (result.type === "UPDATE") return {
|
|
39878
|
+
updatedCount: result.updatedCount,
|
|
39879
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
39880
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
39881
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
39882
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
39883
|
+
};
|
|
38831
39884
|
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
38832
39885
|
if (result.type === "UPSERT") {
|
|
38833
|
-
return {
|
|
39886
|
+
return {
|
|
39887
|
+
insertedCount: result.insertedCount,
|
|
39888
|
+
updatedCount: result.updatedCount,
|
|
39889
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
39890
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
39891
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
39892
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
39893
|
+
};
|
|
38834
39894
|
}
|
|
38835
39895
|
return { reorderedParentCount: result.reorderedParentCount };
|
|
38836
39896
|
}
|
|
@@ -38857,11 +39917,31 @@ function buildBatchEnvelope(batch, options = {}) {
|
|
|
38857
39917
|
}
|
|
38858
39918
|
entry.resultIndex = results.length;
|
|
38859
39919
|
results.push({
|
|
39920
|
+
type: "SELECT",
|
|
38860
39921
|
columns: s.result.columns,
|
|
38861
39922
|
rows: s.result.rows,
|
|
38862
39923
|
rowCount: s.result.rowCount,
|
|
38863
39924
|
warnings: s.result.warnings ?? []
|
|
38864
39925
|
});
|
|
39926
|
+
} else if (s.result?.type === "VALIDATION") {
|
|
39927
|
+
totalRows += s.result.errorCount;
|
|
39928
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
39929
|
+
throw new Error(`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`);
|
|
39930
|
+
}
|
|
39931
|
+
entry.resultIndex = results.length;
|
|
39932
|
+
results.push({
|
|
39933
|
+
type: "VALIDATION",
|
|
39934
|
+
columns: s.result.columns,
|
|
39935
|
+
rows: s.result.errors,
|
|
39936
|
+
rowCount: s.result.errorCount,
|
|
39937
|
+
warnings: [],
|
|
39938
|
+
operation: s.result.operation,
|
|
39939
|
+
validatedRows: s.result.validatedRows,
|
|
39940
|
+
validRows: s.result.validRows,
|
|
39941
|
+
invalidRows: s.result.invalidRows,
|
|
39942
|
+
errorCount: s.result.errorCount,
|
|
39943
|
+
...s.result.errTable ? { errTable: s.result.errTable } : {}
|
|
39944
|
+
});
|
|
38865
39945
|
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
38866
39946
|
Object.assign(entry, toMutationSummary(s.result));
|
|
38867
39947
|
}
|
|
@@ -39219,6 +40299,9 @@ function clampInt(v, min, max) {
|
|
|
39219
40299
|
|
|
39220
40300
|
// src/core/formFieldInfo.ts
|
|
39221
40301
|
function flattenFormFieldProperties(properties) {
|
|
40302
|
+
return flattenFields(properties, collectLookupCopyFields(properties));
|
|
40303
|
+
}
|
|
40304
|
+
function flattenFields(properties, lookupCopyFields, inSubtable = false) {
|
|
39222
40305
|
const out = [];
|
|
39223
40306
|
for (const field of Object.values(properties)) {
|
|
39224
40307
|
out.push({
|
|
@@ -39226,12 +40309,49 @@ function flattenFormFieldProperties(properties) {
|
|
|
39226
40309
|
label: field.label,
|
|
39227
40310
|
fieldType: field.type,
|
|
39228
40311
|
optionOrder: toOptionOrderMap(field.options),
|
|
39229
|
-
sortKind: detectSortKind(field.type, field.format)
|
|
40312
|
+
sortKind: detectSortKind(field.type, field.format),
|
|
40313
|
+
required: field.required,
|
|
40314
|
+
minValue: normalizeConstraintValue(field.minValue),
|
|
40315
|
+
maxValue: normalizeConstraintValue(field.maxValue),
|
|
40316
|
+
minLength: normalizeConstraintValue(field.minLength),
|
|
40317
|
+
maxLength: normalizeConstraintValue(field.maxLength),
|
|
40318
|
+
defaultValue: field.defaultValue,
|
|
40319
|
+
inSubtable,
|
|
40320
|
+
writable: !lookupCopyFields.has(field.code) && !NON_WRITABLE_FIELD_TYPES2.has(field.type)
|
|
39230
40321
|
});
|
|
39231
|
-
if (field.fields) out.push(...
|
|
40322
|
+
if (field.fields) out.push(...flattenFields(field.fields, lookupCopyFields, true));
|
|
39232
40323
|
}
|
|
39233
40324
|
return out;
|
|
39234
40325
|
}
|
|
40326
|
+
var NON_WRITABLE_FIELD_TYPES2 = /* @__PURE__ */ new Set([
|
|
40327
|
+
"CALC",
|
|
40328
|
+
"RECORD_NUMBER",
|
|
40329
|
+
"CREATOR",
|
|
40330
|
+
"CREATED_TIME",
|
|
40331
|
+
"MODIFIER",
|
|
40332
|
+
"UPDATED_TIME",
|
|
40333
|
+
"STATUS",
|
|
40334
|
+
"STATUS_ASSIGNEE",
|
|
40335
|
+
"CATEGORY",
|
|
40336
|
+
"REFERENCE_TABLE",
|
|
40337
|
+
"SUBTABLE"
|
|
40338
|
+
]);
|
|
40339
|
+
function collectLookupCopyFields(properties) {
|
|
40340
|
+
const result = /* @__PURE__ */ new Set();
|
|
40341
|
+
const visit = (fields) => {
|
|
40342
|
+
for (const field of Object.values(fields)) {
|
|
40343
|
+
for (const mapping of field.lookup?.fieldMappings ?? []) {
|
|
40344
|
+
if (mapping.field) result.add(mapping.field);
|
|
40345
|
+
}
|
|
40346
|
+
if (field.fields) visit(field.fields);
|
|
40347
|
+
}
|
|
40348
|
+
};
|
|
40349
|
+
visit(properties);
|
|
40350
|
+
return result;
|
|
40351
|
+
}
|
|
40352
|
+
function normalizeConstraintValue(value) {
|
|
40353
|
+
return value == null || value === "" ? void 0 : value;
|
|
40354
|
+
}
|
|
39235
40355
|
function toOptionOrderMap(options) {
|
|
39236
40356
|
if (!options || typeof options !== "object") return void 0;
|
|
39237
40357
|
const order = {};
|
|
@@ -40255,20 +41375,42 @@ function toAssertPayload(result) {
|
|
|
40255
41375
|
condition: result.condition
|
|
40256
41376
|
};
|
|
40257
41377
|
}
|
|
41378
|
+
function toDmlValidationPayload(result) {
|
|
41379
|
+
return {
|
|
41380
|
+
ok: true,
|
|
41381
|
+
type: result.type,
|
|
41382
|
+
operation: result.operation,
|
|
41383
|
+
validatedRows: result.validatedRows,
|
|
41384
|
+
validRows: result.validRows,
|
|
41385
|
+
invalidRows: result.invalidRows,
|
|
41386
|
+
errorCount: result.errorCount,
|
|
41387
|
+
columns: result.columns,
|
|
41388
|
+
errors: result.errors,
|
|
41389
|
+
...result.errTable ? { errTable: result.errTable } : {}
|
|
41390
|
+
};
|
|
41391
|
+
}
|
|
40258
41392
|
function toMutationPayload(result) {
|
|
40259
41393
|
if (result.type === "INSERT") {
|
|
40260
41394
|
return {
|
|
40261
41395
|
ok: true,
|
|
40262
41396
|
type: result.type,
|
|
40263
41397
|
insertedCount: result.insertedCount,
|
|
40264
|
-
createdIds: result.createdIds
|
|
41398
|
+
createdIds: result.createdIds,
|
|
41399
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41400
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41401
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41402
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40265
41403
|
};
|
|
40266
41404
|
}
|
|
40267
41405
|
if (result.type === "UPDATE") {
|
|
40268
41406
|
return {
|
|
40269
41407
|
ok: true,
|
|
40270
41408
|
type: result.type,
|
|
40271
|
-
updatedCount: result.updatedCount
|
|
41409
|
+
updatedCount: result.updatedCount,
|
|
41410
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41411
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41412
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41413
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40272
41414
|
};
|
|
40273
41415
|
}
|
|
40274
41416
|
if (result.type === "DELETE") {
|
|
@@ -40283,7 +41425,11 @@ function toMutationPayload(result) {
|
|
|
40283
41425
|
ok: true,
|
|
40284
41426
|
type: result.type,
|
|
40285
41427
|
insertedCount: result.insertedCount,
|
|
40286
|
-
updatedCount: result.updatedCount
|
|
41428
|
+
updatedCount: result.updatedCount,
|
|
41429
|
+
...result.affectedRows !== void 0 ? { affectedRows: result.affectedRows } : {},
|
|
41430
|
+
...result.skippedRows !== void 0 ? { skippedRows: result.skippedRows } : {},
|
|
41431
|
+
...result.rejectLimit !== void 0 ? { rejectLimit: result.rejectLimit } : {},
|
|
41432
|
+
...result.errTable !== void 0 ? { errTable: result.errTable } : {}
|
|
40287
41433
|
};
|
|
40288
41434
|
}
|
|
40289
41435
|
return {
|
|
@@ -40319,14 +41465,14 @@ function requireDmlApproval(input, toolName, suffix = "") {
|
|
|
40319
41465
|
}
|
|
40320
41466
|
function containsSelectBasedDml(statements) {
|
|
40321
41467
|
return statements.some(
|
|
40322
|
-
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT"
|
|
41468
|
+
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT" || s.isUpdateFrom === true || s.isOnErrorSkip === true
|
|
40323
41469
|
);
|
|
40324
41470
|
}
|
|
40325
41471
|
function resolveMutateRuntimeMaxRecords(statements, dmlMaxRows) {
|
|
40326
41472
|
return containsSelectBasedDml(statements) ? void 0 : dmlMaxRows + 1;
|
|
40327
41473
|
}
|
|
40328
41474
|
var READ_LIMIT_MESSAGE_FRAGMENT = "\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650";
|
|
40329
|
-
var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML \u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
|
|
41475
|
+
var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML\uFF08UPDATE \u2026 FROM \u3092\u542B\u3080\uFF09\u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
|
|
40330
41476
|
function appendSelectBasedDmlReadLimitHint(err) {
|
|
40331
41477
|
if (err instanceof Error && err.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) {
|
|
40332
41478
|
const hinted = new Error(`${err.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}`);
|
|
@@ -40381,13 +41527,19 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40381
41527
|
tempTablesReferenced: s2.tempTablesReferenced,
|
|
40382
41528
|
tempTablesDropped: s2.tempTablesDropped,
|
|
40383
41529
|
tempOnlySource: s2.tempOnlySource,
|
|
40384
|
-
targetAppId: s2.targetAppId
|
|
41530
|
+
targetAppId: s2.targetAppId,
|
|
41531
|
+
isUpdateFrom: s2.isUpdateFrom,
|
|
41532
|
+
isValidationOnly: s2.isValidationOnly,
|
|
41533
|
+
isOnErrorSkip: s2.isOnErrorSkip,
|
|
41534
|
+
requiresCompleteInput: s2.requiresCompleteInput
|
|
40385
41535
|
}));
|
|
40386
41536
|
const common = {
|
|
40387
41537
|
ok: true,
|
|
40388
41538
|
statementCount: analysis.statementCount,
|
|
40389
41539
|
isReadOnlyBatch: analysis.isReadOnlyBatch,
|
|
40390
41540
|
containsDml: analysis.containsDml,
|
|
41541
|
+
containsValidationOnly: analysis.containsValidationOnly,
|
|
41542
|
+
requiresCompleteInput: analysis.requiresCompleteInput,
|
|
40391
41543
|
tempTables: analysis.tempTables,
|
|
40392
41544
|
canRunWithQueryTool: analysis.isReadOnlyBatch,
|
|
40393
41545
|
requiresMutationTool: analysis.containsDml,
|
|
@@ -40461,7 +41613,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40461
41613
|
profile: input.profile,
|
|
40462
41614
|
maxRecords: input.maxRecords,
|
|
40463
41615
|
fetchParallel: input.fetchParallel,
|
|
40464
|
-
onLimit: input.onLimit,
|
|
41616
|
+
onLimit: validation.requiresCompleteInput ? "error" : input.onLimit,
|
|
40465
41617
|
timeout: input.timeout,
|
|
40466
41618
|
tempTableMaxRows: input.tempTableMaxRows
|
|
40467
41619
|
});
|
|
@@ -40494,6 +41646,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40494
41646
|
cacheContext: validation.cacheContext
|
|
40495
41647
|
});
|
|
40496
41648
|
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
41649
|
+
if (result2.type === "VALIDATION") return toDmlValidationPayload(result2);
|
|
40497
41650
|
if (result2.type !== "SELECT") {
|
|
40498
41651
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result2.type}.`);
|
|
40499
41652
|
}
|
|
@@ -40505,7 +41658,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40505
41658
|
profile: input.profile,
|
|
40506
41659
|
maxRecords: input.maxRecords,
|
|
40507
41660
|
fetchParallel: input.fetchParallel,
|
|
40508
|
-
onLimit: input.onLimit,
|
|
41661
|
+
onLimit: validation.requiresCompleteInput ? "error" : input.onLimit,
|
|
40509
41662
|
timeout: input.timeout
|
|
40510
41663
|
});
|
|
40511
41664
|
const result = await executeSql(runtime.sql, runtime.client, {
|
|
@@ -40515,6 +41668,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40515
41668
|
cacheContext: runtime.cacheContext
|
|
40516
41669
|
});
|
|
40517
41670
|
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
41671
|
+
if (result.type === "VALIDATION") return toDmlValidationPayload(result);
|
|
40518
41672
|
if (result.type !== "SELECT") {
|
|
40519
41673
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result.type}.`);
|
|
40520
41674
|
}
|
|
@@ -40531,12 +41685,12 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40531
41685
|
if ((s.statementType === "UPDATE" || s.statementType === "DELETE") && !s.hasWhere) {
|
|
40532
41686
|
throw new Error(`ArgumentError: ${s.statementType} without WHERE is blocked by ksql_mutate.${at}`);
|
|
40533
41687
|
}
|
|
40534
|
-
if (s.insertValuesCount !== null && s.insertValuesCount > dmlMaxRows) {
|
|
41688
|
+
if (!s.isOnErrorSkip && s.insertValuesCount !== null && s.insertValuesCount > dmlMaxRows) {
|
|
40535
41689
|
throw new Error(
|
|
40536
41690
|
`ArgumentError: INSERT rows (${s.insertValuesCount}) exceed dmlMaxRows (${dmlMaxRows}).${at}`
|
|
40537
41691
|
);
|
|
40538
41692
|
}
|
|
40539
|
-
staticInsertTotal += s.insertValuesCount ?? 0;
|
|
41693
|
+
if (!s.isOnErrorSkip) staticInsertTotal += s.insertValuesCount ?? 0;
|
|
40540
41694
|
}
|
|
40541
41695
|
const dmlTotalMaxRows = input.dmlTotalMaxRows;
|
|
40542
41696
|
if (dmlTotalMaxRows !== void 0 && staticInsertTotal > dmlTotalMaxRows) {
|
|
@@ -40584,7 +41738,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40584
41738
|
const payload = buildBatchEnvelope(batchResult);
|
|
40585
41739
|
if (selectBasedDml) {
|
|
40586
41740
|
for (const entry of payload.statements) {
|
|
40587
|
-
|
|
41741
|
+
const statement = validation.statements.find((s) => s.index === entry.index);
|
|
41742
|
+
if (entry.type !== "INSERT_SELECT" && entry.type !== "UPSERT_SELECT" && statement?.isUpdateFrom !== true) continue;
|
|
40588
41743
|
const error51 = entry.error;
|
|
40589
41744
|
if (typeof error51?.message !== "string") continue;
|
|
40590
41745
|
if (!error51.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) continue;
|
|
@@ -40640,7 +41795,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40640
41795
|
} catch (err) {
|
|
40641
41796
|
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
40642
41797
|
}
|
|
40643
|
-
if (result.type === "SELECT" || result.type === "ASSERT") {
|
|
41798
|
+
if (result.type === "SELECT" || result.type === "ASSERT" || result.type === "VALIDATION") {
|
|
40644
41799
|
throw new Error(`ArgumentError: ksql_mutate returned unexpected result type ${result.type}.`);
|
|
40645
41800
|
}
|
|
40646
41801
|
return toMutationPayload(result);
|
|
@@ -40799,7 +41954,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
40799
41954
|
var profile = external_exports.string().min(1).describe("kintone connection profile name from ksql.config.json (default: the server's default profile).").optional();
|
|
40800
41955
|
var maxRecords = external_exports.number().int().positive().describe("Maximum records fetched per SELECT (default 500).").optional();
|
|
40801
41956
|
var fetchParallel = external_exports.number().int().min(1).max(10).describe("Number of parallel kintone record-fetch requests (1-10).").optional();
|
|
40802
|
-
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error').").optional();
|
|
41957
|
+
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();
|
|
40803
41958
|
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();
|
|
40804
41959
|
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();
|
|
40805
41960
|
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).");
|
|
@@ -40923,7 +42078,7 @@ Options:
|
|
|
40923
42078
|
-h, --help Show help
|
|
40924
42079
|
`);
|
|
40925
42080
|
}
|
|
40926
|
-
var SERVER_VERSION = true ? "2.
|
|
42081
|
+
var SERVER_VERSION = true ? "2.13.0" : "0.0.0-dev";
|
|
40927
42082
|
function createServer(args) {
|
|
40928
42083
|
const server = new McpServer({
|
|
40929
42084
|
name: "ksql-mcp",
|
|
@@ -40945,12 +42100,12 @@ function createServer(args) {
|
|
|
40945
42100
|
}, tools.explainTool);
|
|
40946
42101
|
server.registerTool("ksql_query", {
|
|
40947
42102
|
title: "Run read-only kSQL",
|
|
40948
|
-
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT.
|
|
42103
|
+
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.",
|
|
40949
42104
|
inputSchema: queryInputShape
|
|
40950
42105
|
}, tools.queryTool);
|
|
40951
42106
|
server.registerTool("ksql_mutate", {
|
|
40952
42107
|
title: "Run mutating kSQL",
|
|
40953
|
-
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. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads:
|
|
42108
|
+
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).",
|
|
40954
42109
|
inputSchema: mutateInputShape
|
|
40955
42110
|
}, tools.mutateTool);
|
|
40956
42111
|
server.registerTool("ksql_describe_app", {
|