@rex0220/kintone-sql-tools 3.4.0 → 3.5.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 +528 -40
- package/dist-mcp/ksql-mcp.js +529 -42
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -31656,13 +31656,14 @@ var Parser = class {
|
|
|
31656
31656
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
31657
31657
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
31658
31658
|
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
31659
|
+
if (upper === "VALIDATE") return this.parseValidate();
|
|
31659
31660
|
break;
|
|
31660
31661
|
}
|
|
31661
31662
|
default:
|
|
31662
31663
|
break;
|
|
31663
31664
|
}
|
|
31664
31665
|
throw new ParseError(
|
|
31665
|
-
"SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31666
|
+
"SELECT / INSERT / UPDATE / DELETE / REORDER / VALIDATE / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / SET / DECLARE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
|
|
31666
31667
|
tok
|
|
31667
31668
|
);
|
|
31668
31669
|
}
|
|
@@ -31673,7 +31674,7 @@ var Parser = class {
|
|
|
31673
31674
|
this.expect("SET" /* SET */);
|
|
31674
31675
|
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
31675
31676
|
this.expect("=" /* EQ */);
|
|
31676
|
-
const expr = this.parseScalarExpr("SET", true);
|
|
31677
|
+
const expr = this.peek().kind === "[" /* LBRACKET */ ? this.parseArrayLiteral() : this.parseScalarExpr("SET", true);
|
|
31677
31678
|
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
31678
31679
|
}
|
|
31679
31680
|
parseDeclareVariable() {
|
|
@@ -31834,11 +31835,63 @@ var Parser = class {
|
|
|
31834
31835
|
query = this.parseDelete();
|
|
31835
31836
|
} else if (tok.kind === "REORDER" /* REORDER */) {
|
|
31836
31837
|
query = this.parseReorder();
|
|
31838
|
+
} else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
|
|
31839
|
+
query = this.parseValidate();
|
|
31837
31840
|
} else {
|
|
31838
|
-
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
31841
|
+
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
31839
31842
|
}
|
|
31840
31843
|
return { type: "EXPLAIN", query };
|
|
31841
31844
|
}
|
|
31845
|
+
/** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
|
|
31846
|
+
parseValidate() {
|
|
31847
|
+
const validateTok = this.advance();
|
|
31848
|
+
const name = this.parseIdentifier();
|
|
31849
|
+
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
31850
|
+
if (subtableCode) {
|
|
31851
|
+
throw new ParseError("VALIDATE \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB\u4EEE\u60F3\u30C6\u30FC\u30D6\u30EB\u3092\u5BFE\u8C61\u306B\u3067\u304D\u307E\u305B\u3093", this.prev());
|
|
31852
|
+
}
|
|
31853
|
+
let fields;
|
|
31854
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
31855
|
+
fields = this.parseIdentList();
|
|
31856
|
+
this.expect(")" /* RPAREN */);
|
|
31857
|
+
}
|
|
31858
|
+
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
31859
|
+
const checks = this.parseCheckGroups();
|
|
31860
|
+
let errorTable;
|
|
31861
|
+
if (this.consume("INTO" /* INTO */)) {
|
|
31862
|
+
const tableTok = this.peek();
|
|
31863
|
+
if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
|
|
31864
|
+
throw new ParseError("VALIDATE INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tableTok);
|
|
31865
|
+
}
|
|
31866
|
+
errorTable = this.parseTableName();
|
|
31867
|
+
}
|
|
31868
|
+
const stmt = { type: "VALIDATE", appId, fields, where, ...checks, ...errorTable ? { errorTable } : {} };
|
|
31869
|
+
this.assertValidateExpressions(stmt, validateTok);
|
|
31870
|
+
return stmt;
|
|
31871
|
+
}
|
|
31872
|
+
/** v1 VALIDATE is single-app/local: subqueries and qualified references are rejected. */
|
|
31873
|
+
assertValidateExpressions(stmt, tok) {
|
|
31874
|
+
const visit = (node) => {
|
|
31875
|
+
if (Array.isArray(node)) {
|
|
31876
|
+
node.forEach(visit);
|
|
31877
|
+
return;
|
|
31878
|
+
}
|
|
31879
|
+
if (node === null || typeof node !== "object") return;
|
|
31880
|
+
const obj = node;
|
|
31881
|
+
if (obj.type === "EXISTS" || obj.type === "SUBQUERY_IN_LIST" || obj.type === "SCALAR_SUBQUERY") {
|
|
31882
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
31883
|
+
}
|
|
31884
|
+
if (obj.type === "FIELD" && obj.tableAlias !== null && obj.tableAlias !== void 0) {
|
|
31885
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
31886
|
+
}
|
|
31887
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string" && obj.field.includes(".")) {
|
|
31888
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u3067\u306F\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
31889
|
+
}
|
|
31890
|
+
Object.values(obj).forEach(visit);
|
|
31891
|
+
};
|
|
31892
|
+
visit(stmt.where);
|
|
31893
|
+
visit(stmt.checkGroups);
|
|
31894
|
+
}
|
|
31842
31895
|
// ----------------------------------------------------------
|
|
31843
31896
|
// ASSERT
|
|
31844
31897
|
//
|
|
@@ -32124,6 +32177,17 @@ var Parser = class {
|
|
|
32124
32177
|
const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
|
|
32125
32178
|
return { type: "SCALAR_VALUE_COL", expr, alias: alias2 };
|
|
32126
32179
|
}
|
|
32180
|
+
if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
|
|
32181
|
+
const variable = this.advance();
|
|
32182
|
+
if (!this.consume("AS" /* AS */)) {
|
|
32183
|
+
throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
32184
|
+
}
|
|
32185
|
+
return {
|
|
32186
|
+
type: "VARIABLE_COL",
|
|
32187
|
+
name: variable.value.slice(1).toLowerCase(),
|
|
32188
|
+
alias: this.parseAliasName()
|
|
32189
|
+
};
|
|
32190
|
+
}
|
|
32127
32191
|
const windowFunc = this.tryWindowFunc();
|
|
32128
32192
|
if (windowFunc !== null) {
|
|
32129
32193
|
return this.parseWindowColumn(windowFunc);
|
|
@@ -32939,9 +33003,7 @@ var Parser = class {
|
|
|
32939
33003
|
}
|
|
32940
33004
|
if (this.consume("NOT" /* NOT */)) {
|
|
32941
33005
|
if (this.consume("IN" /* IN */)) {
|
|
32942
|
-
this.
|
|
32943
|
-
const right2 = this.parseInListOrSubquery();
|
|
32944
|
-
this.expect(")" /* RPAREN */);
|
|
33006
|
+
const right2 = this.parseInRight();
|
|
32945
33007
|
return { type: "BINARY", op: "NOT_IN", left: field, right: right2 };
|
|
32946
33008
|
}
|
|
32947
33009
|
if (this.consume("LIKE" /* LIKE */)) {
|
|
@@ -32958,9 +33020,7 @@ var Parser = class {
|
|
|
32958
33020
|
);
|
|
32959
33021
|
}
|
|
32960
33022
|
if (this.consume("IN" /* IN */)) {
|
|
32961
|
-
this.
|
|
32962
|
-
const right2 = this.parseInListOrSubquery();
|
|
32963
|
-
this.expect(")" /* RPAREN */);
|
|
33023
|
+
const right2 = this.parseInRight();
|
|
32964
33024
|
return { type: "BINARY", op: "IN", left: field, right: right2 };
|
|
32965
33025
|
}
|
|
32966
33026
|
if (this.consume("KLIKE" /* KLIKE */)) {
|
|
@@ -33127,6 +33187,18 @@ var Parser = class {
|
|
|
33127
33187
|
);
|
|
33128
33188
|
}
|
|
33129
33189
|
// IN (...) — 値リストまたはサブクエリ
|
|
33190
|
+
parseInRight() {
|
|
33191
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
33192
|
+
const right = this.parseInListOrSubquery();
|
|
33193
|
+
this.expect(")" /* RPAREN */);
|
|
33194
|
+
return right;
|
|
33195
|
+
}
|
|
33196
|
+
const variable = this.expect(
|
|
33197
|
+
"VARIABLE" /* VARIABLE */,
|
|
33198
|
+
"IN / NOT IN \u306E\u5F8C\u306B\u306F (\u5024\u30EA\u30B9\u30C8\u307E\u305F\u306F SELECT) \u304B\u914D\u5217\u5909\u6570\u304C\u5FC5\u8981\u3067\u3059"
|
|
33199
|
+
);
|
|
33200
|
+
return { type: "VARIABLE_IN_LIST", name: variable.value.slice(1).toLowerCase() };
|
|
33201
|
+
}
|
|
33130
33202
|
parseInListOrSubquery() {
|
|
33131
33203
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
33132
33204
|
const query = this.parseSelect();
|
|
@@ -33937,7 +34009,7 @@ function isDmlType(type) {
|
|
|
33937
34009
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
33938
34010
|
}
|
|
33939
34011
|
function isReadOnlyType(type) {
|
|
33940
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
34012
|
+
return type === "SELECT" || type === "VALIDATE" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "SET_VARIABLE" || type === "DECLARE_VARIABLE" || type === "ASSERT";
|
|
33941
34013
|
}
|
|
33942
34014
|
function writesKintone(stmt) {
|
|
33943
34015
|
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
@@ -33948,6 +34020,8 @@ function isReadOnlyStatement(stmt) {
|
|
|
33948
34020
|
function requiresCompleteInput(stmt) {
|
|
33949
34021
|
if (isDmlType(stmt.type)) return true;
|
|
33950
34022
|
switch (stmt.type) {
|
|
34023
|
+
case "VALIDATE":
|
|
34024
|
+
return true;
|
|
33951
34025
|
case "SELECT":
|
|
33952
34026
|
return selectRequiresCompleteInput(stmt);
|
|
33953
34027
|
case "UNION":
|
|
@@ -33988,6 +34062,7 @@ function whereRequiresCompleteInput(where) {
|
|
|
33988
34062
|
case "EXISTS":
|
|
33989
34063
|
return selectRequiresCompleteInput(where.query);
|
|
33990
34064
|
case "NULL_CHECK":
|
|
34065
|
+
case "BOOLEAN":
|
|
33991
34066
|
return false;
|
|
33992
34067
|
}
|
|
33993
34068
|
}
|
|
@@ -34011,6 +34086,8 @@ function getInsertValuesCount(stmt) {
|
|
|
34011
34086
|
// src/engine/pushDownNot.ts
|
|
34012
34087
|
function pushDownNot(expr) {
|
|
34013
34088
|
switch (expr.type) {
|
|
34089
|
+
case "BOOLEAN":
|
|
34090
|
+
return { type: "BOOLEAN", value: !expr.value };
|
|
34014
34091
|
case "BINARY": {
|
|
34015
34092
|
const negated = negateOp(expr.op);
|
|
34016
34093
|
if (negated === null) {
|
|
@@ -34090,6 +34167,7 @@ function whereHasLike(where) {
|
|
|
34090
34167
|
case "BINARY":
|
|
34091
34168
|
case "NULL_CHECK":
|
|
34092
34169
|
case "EXISTS":
|
|
34170
|
+
case "BOOLEAN":
|
|
34093
34171
|
return false;
|
|
34094
34172
|
}
|
|
34095
34173
|
}
|
|
@@ -34105,6 +34183,7 @@ function whereHasKlike(where) {
|
|
|
34105
34183
|
case "BINARY":
|
|
34106
34184
|
case "NULL_CHECK":
|
|
34107
34185
|
case "EXISTS":
|
|
34186
|
+
case "BOOLEAN":
|
|
34108
34187
|
return false;
|
|
34109
34188
|
}
|
|
34110
34189
|
}
|
|
@@ -34124,6 +34203,8 @@ function whereToKintone(expr) {
|
|
|
34124
34203
|
return convertGroup(expr);
|
|
34125
34204
|
case "EXISTS":
|
|
34126
34205
|
throw new KintoneQueryError("EXISTS \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
|
|
34206
|
+
case "BOOLEAN":
|
|
34207
|
+
throw new KintoneQueryError("internal error: BOOLEAN predicate reached kintone query conversion");
|
|
34127
34208
|
}
|
|
34128
34209
|
}
|
|
34129
34210
|
function convertBinary(expr) {
|
|
@@ -34202,6 +34283,8 @@ function convertValue(value, op) {
|
|
|
34202
34283
|
switch (value.type) {
|
|
34203
34284
|
case "VARIABLE":
|
|
34204
34285
|
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
34286
|
+
case "VARIABLE_IN_LIST":
|
|
34287
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
34205
34288
|
case "STRING":
|
|
34206
34289
|
return convertString(value);
|
|
34207
34290
|
case "NUMBER":
|
|
@@ -34281,6 +34364,8 @@ function resolveSelectMode(stmt) {
|
|
|
34281
34364
|
function whereRequiresJsEval(where) {
|
|
34282
34365
|
if (where === null) return false;
|
|
34283
34366
|
switch (where.type) {
|
|
34367
|
+
case "BOOLEAN":
|
|
34368
|
+
return true;
|
|
34284
34369
|
case "BINARY":
|
|
34285
34370
|
return isFunc(where.left) || where.right.type === "ARITH_VALUE" || where.right.type === "CASE_VALUE" || where.right.type === "SUBQUERY_IN_LIST" || where.right.type === "SCALAR_SUBQUERY" || isLike(where);
|
|
34286
34371
|
case "NULL_CHECK":
|
|
@@ -34675,6 +34760,7 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
34675
34760
|
walkWhere(where.expr, phase);
|
|
34676
34761
|
return;
|
|
34677
34762
|
case "EXISTS":
|
|
34763
|
+
case "BOOLEAN":
|
|
34678
34764
|
return;
|
|
34679
34765
|
}
|
|
34680
34766
|
};
|
|
@@ -34713,6 +34799,8 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
34713
34799
|
break;
|
|
34714
34800
|
case "LITERAL_COL":
|
|
34715
34801
|
break;
|
|
34802
|
+
case "VARIABLE_COL":
|
|
34803
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
34716
34804
|
case "AGGREGATE":
|
|
34717
34805
|
if (col.arg.type !== "WILDCARD") walkArith(col.arg, "select");
|
|
34718
34806
|
break;
|
|
@@ -34888,6 +34976,7 @@ function stripCteAlias(where, alias) {
|
|
|
34888
34976
|
case "GROUP":
|
|
34889
34977
|
return { ...where, expr: stripCteAlias(where.expr, alias) };
|
|
34890
34978
|
case "EXISTS":
|
|
34979
|
+
case "BOOLEAN":
|
|
34891
34980
|
return where;
|
|
34892
34981
|
}
|
|
34893
34982
|
}
|
|
@@ -34925,6 +35014,7 @@ function extractAndLeaves(where, accept) {
|
|
|
34925
35014
|
case "NULL_CHECK":
|
|
34926
35015
|
case "NOT":
|
|
34927
35016
|
case "EXISTS":
|
|
35017
|
+
case "BOOLEAN":
|
|
34928
35018
|
return null;
|
|
34929
35019
|
}
|
|
34930
35020
|
}
|
|
@@ -35063,6 +35153,7 @@ function collectKlikes(where, out) {
|
|
|
35063
35153
|
case "BINARY":
|
|
35064
35154
|
case "NULL_CHECK":
|
|
35065
35155
|
case "EXISTS":
|
|
35156
|
+
case "BOOLEAN":
|
|
35066
35157
|
return;
|
|
35067
35158
|
}
|
|
35068
35159
|
}
|
|
@@ -35119,6 +35210,11 @@ function validateStatement(stmt) {
|
|
|
35119
35210
|
);
|
|
35120
35211
|
}
|
|
35121
35212
|
return;
|
|
35213
|
+
case "VALIDATE":
|
|
35214
|
+
if (containsKlike(stmt)) {
|
|
35215
|
+
throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F VALIDATE \u306E WHERE / CHECK \u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
35216
|
+
}
|
|
35217
|
+
return;
|
|
35122
35218
|
case "SHOW_APPS":
|
|
35123
35219
|
case "DESCRIBE":
|
|
35124
35220
|
case "DROP_TEMP_TABLE":
|
|
@@ -35206,6 +35302,8 @@ function isDescendantOf(root, target) {
|
|
|
35206
35302
|
case "NULL_CHECK":
|
|
35207
35303
|
case "EXISTS":
|
|
35208
35304
|
return false;
|
|
35305
|
+
case "BOOLEAN":
|
|
35306
|
+
return false;
|
|
35209
35307
|
}
|
|
35210
35308
|
}
|
|
35211
35309
|
function walkWithoutNestedSelects(node, visitWhere) {
|
|
@@ -35267,8 +35365,12 @@ function collectVariableRefs(node, refs) {
|
|
|
35267
35365
|
}
|
|
35268
35366
|
if (node !== null && typeof node === "object") {
|
|
35269
35367
|
const obj = node;
|
|
35270
|
-
|
|
35271
|
-
|
|
35368
|
+
const type = obj["type"];
|
|
35369
|
+
if ((type === "VARIABLE" || type === "VARIABLE_COL" || type === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") {
|
|
35370
|
+
refs.push({
|
|
35371
|
+
name: obj["name"],
|
|
35372
|
+
kind: type === "VARIABLE" ? "scalar" : type === "VARIABLE_COL" ? "select-column" : "array-in-list"
|
|
35373
|
+
});
|
|
35272
35374
|
return;
|
|
35273
35375
|
}
|
|
35274
35376
|
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
@@ -35309,9 +35411,9 @@ function analyzeBatch(statements) {
|
|
|
35309
35411
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
35310
35412
|
const variableOrder = [];
|
|
35311
35413
|
statements.forEach((stmt, index) => {
|
|
35312
|
-
const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
35414
|
+
const validationTable = stmt.type === "VALIDATE" && stmt.errorTable ? stmt.errorTable : "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
35313
35415
|
if (statements.length === 1 && validationTable) {
|
|
35314
|
-
const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
35416
|
+
const message = stmt.type === "VALIDATE" ? "ArgumentError: VALIDATE INTO requires a batch." : "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
35315
35417
|
throw new BatchAnalysisError(message, index);
|
|
35316
35418
|
}
|
|
35317
35419
|
const statementType = getStatementType(stmt);
|
|
@@ -35320,23 +35422,43 @@ function analyzeBatch(statements) {
|
|
|
35320
35422
|
const refs = /* @__PURE__ */ new Set();
|
|
35321
35423
|
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
35322
35424
|
const dependsOn = /* @__PURE__ */ new Set();
|
|
35323
|
-
const variableRefs =
|
|
35425
|
+
const variableRefs = [];
|
|
35324
35426
|
collectVariableRefs(stmt, variableRefs);
|
|
35325
|
-
|
|
35326
|
-
|
|
35427
|
+
const referencedThisStatement = /* @__PURE__ */ new Set();
|
|
35428
|
+
for (const use of variableRefs) {
|
|
35429
|
+
const def = variableDefs.get(use.name);
|
|
35327
35430
|
if (def === void 0) {
|
|
35328
35431
|
throw new BatchAnalysisError(
|
|
35329
|
-
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
35432
|
+
`ParseError: variable @${use.name} is not defined before statement ${index + 1}.`,
|
|
35433
|
+
index
|
|
35434
|
+
);
|
|
35435
|
+
}
|
|
35436
|
+
if (def.kind === "scalar" && use.kind === "array-in-list") {
|
|
35437
|
+
throw new BatchAnalysisError(
|
|
35438
|
+
`ParseError: scalar variable @${use.name} cannot be used as IN @${use.name}; use IN (@${use.name}) instead.`,
|
|
35330
35439
|
index
|
|
35331
35440
|
);
|
|
35332
35441
|
}
|
|
35333
|
-
def.
|
|
35442
|
+
if (def.kind === "array" && use.kind !== "array-in-list") {
|
|
35443
|
+
throw new BatchAnalysisError(
|
|
35444
|
+
`ParseError: array variable @${use.name} can only be used as IN @${use.name}.`,
|
|
35445
|
+
index
|
|
35446
|
+
);
|
|
35447
|
+
}
|
|
35448
|
+
if (!referencedThisStatement.has(use.name)) {
|
|
35449
|
+
def.referencedBy.push(index);
|
|
35450
|
+
referencedThisStatement.add(use.name);
|
|
35451
|
+
}
|
|
35334
35452
|
}
|
|
35335
35453
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
35336
35454
|
if (variableDefs.has(stmt.name)) {
|
|
35337
35455
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
35338
35456
|
}
|
|
35339
|
-
variableDefs.set(stmt.name, {
|
|
35457
|
+
variableDefs.set(stmt.name, {
|
|
35458
|
+
index,
|
|
35459
|
+
kind: stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? "array" : "scalar",
|
|
35460
|
+
referencedBy: []
|
|
35461
|
+
});
|
|
35340
35462
|
variableOrder.push(stmt.name);
|
|
35341
35463
|
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
35342
35464
|
throw new BatchAnalysisError(
|
|
@@ -35369,7 +35491,7 @@ function analyzeBatch(statements) {
|
|
|
35369
35491
|
dependsOn.add(at);
|
|
35370
35492
|
}
|
|
35371
35493
|
if (validationTable) {
|
|
35372
|
-
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
35494
|
+
const payloadFields = stmt.type === "VALIDATE" ? ["$id", "$err_field", "$err_code", "$err_message", "$err_value"] : stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
35373
35495
|
const signature = JSON.stringify(payloadFields);
|
|
35374
35496
|
const at = defined.get(validationTable);
|
|
35375
35497
|
if (at === void 0) {
|
|
@@ -35444,6 +35566,7 @@ function analyzeBatch(statements) {
|
|
|
35444
35566
|
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
35445
35567
|
const variables = variableOrder.map((name) => ({
|
|
35446
35568
|
name,
|
|
35569
|
+
kind: variableDefs.get(name).kind,
|
|
35447
35570
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
35448
35571
|
}));
|
|
35449
35572
|
return {
|
|
@@ -35720,6 +35843,7 @@ function whereNeedsFieldMetadata(where) {
|
|
|
35720
35843
|
case "GROUP":
|
|
35721
35844
|
return whereNeedsFieldMetadata(where.expr);
|
|
35722
35845
|
case "EXISTS":
|
|
35846
|
+
case "BOOLEAN":
|
|
35723
35847
|
return false;
|
|
35724
35848
|
}
|
|
35725
35849
|
}
|
|
@@ -35744,6 +35868,7 @@ function explainNeedsAppMetadata(statement) {
|
|
|
35744
35868
|
seen.add(node);
|
|
35745
35869
|
if (Array.isArray(node)) return node.some(visit);
|
|
35746
35870
|
const item = node;
|
|
35871
|
+
if (item["type"] === "VALIDATE") return true;
|
|
35747
35872
|
if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
|
|
35748
35873
|
return true;
|
|
35749
35874
|
}
|
|
@@ -36234,6 +36359,8 @@ function resolveFieldRef(row, field) {
|
|
|
36234
36359
|
// src/engine/evalWhere.ts
|
|
36235
36360
|
function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
|
|
36236
36361
|
switch (expr.type) {
|
|
36362
|
+
case "BOOLEAN":
|
|
36363
|
+
return expr.value;
|
|
36237
36364
|
case "BINARY":
|
|
36238
36365
|
return evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
|
|
36239
36366
|
case "NULL_CHECK":
|
|
@@ -36404,6 +36531,8 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
36404
36531
|
switch (value.type) {
|
|
36405
36532
|
case "VARIABLE":
|
|
36406
36533
|
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
36534
|
+
case "VARIABLE_IN_LIST":
|
|
36535
|
+
throw new Error(`ParseError: unresolved batch array variable @${value.name}.`);
|
|
36407
36536
|
case "STRING":
|
|
36408
36537
|
return value.value;
|
|
36409
36538
|
case "NUMBER":
|
|
@@ -36719,6 +36848,9 @@ function collectConditionFields(expr, out) {
|
|
|
36719
36848
|
case "GROUP":
|
|
36720
36849
|
collectConditionFields(expr.expr, out);
|
|
36721
36850
|
break;
|
|
36851
|
+
case "EXISTS":
|
|
36852
|
+
case "BOOLEAN":
|
|
36853
|
+
break;
|
|
36722
36854
|
}
|
|
36723
36855
|
}
|
|
36724
36856
|
function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
@@ -36934,6 +37066,8 @@ function convertDmlSqlValue(value, fieldType) {
|
|
|
36934
37066
|
switch (value.type) {
|
|
36935
37067
|
case "VARIABLE":
|
|
36936
37068
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
37069
|
+
case "VARIABLE_IN_LIST":
|
|
37070
|
+
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
36937
37071
|
case "STRING":
|
|
36938
37072
|
return convertString2(value.value, fieldType);
|
|
36939
37073
|
case "NUMBER":
|
|
@@ -37769,6 +37903,8 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
|
|
|
37769
37903
|
const out = {};
|
|
37770
37904
|
for (const [colIdx, col] of columns.entries()) {
|
|
37771
37905
|
switch (col.type) {
|
|
37906
|
+
case "VARIABLE_COL":
|
|
37907
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
37772
37908
|
case "WILDCARD":
|
|
37773
37909
|
Object.assign(out, stripParentShortcutColumns(row));
|
|
37774
37910
|
break;
|
|
@@ -37872,6 +38008,8 @@ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
|
|
|
37872
38008
|
}
|
|
37873
38009
|
function computeOutputKey(col, colIdx, defaultFieldKeys) {
|
|
37874
38010
|
switch (col.type) {
|
|
38011
|
+
case "VARIABLE_COL":
|
|
38012
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
37875
38013
|
case "FIELD":
|
|
37876
38014
|
return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
|
|
37877
38015
|
case "LITERAL_COL":
|
|
@@ -38386,6 +38524,11 @@ function renderValidationValue(value) {
|
|
|
38386
38524
|
return String(value);
|
|
38387
38525
|
}
|
|
38388
38526
|
|
|
38527
|
+
// src/core/existingRecordValidation.ts
|
|
38528
|
+
function renderExistingValidationValue(raw, fieldType) {
|
|
38529
|
+
return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
|
|
38530
|
+
}
|
|
38531
|
+
|
|
38389
38532
|
// src/core/optimization/whereCapability.ts
|
|
38390
38533
|
var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
|
|
38391
38534
|
var EQUALITY_IN = ["=", "!=", "in", "not in"];
|
|
@@ -38460,6 +38603,8 @@ function classifyWhereCapability(where, resolveField2) {
|
|
|
38460
38603
|
}
|
|
38461
38604
|
function classifyNode(where, resolveField2) {
|
|
38462
38605
|
switch (where.type) {
|
|
38606
|
+
case "BOOLEAN":
|
|
38607
|
+
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
38463
38608
|
case "BINARY":
|
|
38464
38609
|
return classifyBinary(where.op, where.left, where.right.type, resolveField2);
|
|
38465
38610
|
case "NULL_CHECK":
|
|
@@ -38786,6 +38931,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38786
38931
|
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
38787
38932
|
}
|
|
38788
38933
|
switch (stmt.type) {
|
|
38934
|
+
case "VALIDATE":
|
|
38935
|
+
return executeExistingRecordValidation(stmt, client, options, cacheContext);
|
|
38789
38936
|
case "SELECT":
|
|
38790
38937
|
return executeSelect(stmt, client, options, cacheContext);
|
|
38791
38938
|
case "UNION":
|
|
@@ -38831,6 +38978,144 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
38831
38978
|
return executeAssert(stmt, client, options, cacheContext);
|
|
38832
38979
|
}
|
|
38833
38980
|
}
|
|
38981
|
+
var EXISTING_VALIDATION_COLUMNS = ["$id", "$err_field", "$err_code", "$err_message", "$err_value"];
|
|
38982
|
+
function hasAuditableConstraint(field) {
|
|
38983
|
+
return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
|
|
38984
|
+
}
|
|
38985
|
+
function resolveExistingValidationTargets(stmt, fieldInfos) {
|
|
38986
|
+
const byCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
38987
|
+
const auditable = (field) => !field.inSubtable && (field.fieldType === "NUMBER" || hasAuditableConstraint(field));
|
|
38988
|
+
if (stmt.fields === void 0) return fieldInfos.filter(auditable);
|
|
38989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38990
|
+
return stmt.fields.map((code) => {
|
|
38991
|
+
if (seen.has(code)) throw new Error(`ArgumentError: VALIDATE field ${code} is duplicated.`);
|
|
38992
|
+
seen.add(code);
|
|
38993
|
+
if (code === "$id") throw new Error("ArgumentError: VALIDATE cannot audit system field $id.");
|
|
38994
|
+
const info = byCode.get(code);
|
|
38995
|
+
if (!info) throw new Error(`ArgumentError: VALIDATE field ${code} does not exist.`);
|
|
38996
|
+
if (info.inSubtable) throw new Error(`ArgumentError: VALIDATE field ${code} is a subtable child field.`);
|
|
38997
|
+
if (!auditable(info)) throw new Error(`ArgumentError: VALIDATE field ${code} has no auditable constraint.`);
|
|
38998
|
+
return info;
|
|
38999
|
+
});
|
|
39000
|
+
}
|
|
39001
|
+
function collectValidateWhereFields(where) {
|
|
39002
|
+
const fields = [];
|
|
39003
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39004
|
+
const add = (field) => {
|
|
39005
|
+
if (!seen.has(field)) {
|
|
39006
|
+
seen.add(field);
|
|
39007
|
+
fields.push(field);
|
|
39008
|
+
}
|
|
39009
|
+
};
|
|
39010
|
+
const visit = (node) => {
|
|
39011
|
+
if (Array.isArray(node)) {
|
|
39012
|
+
node.forEach(visit);
|
|
39013
|
+
return;
|
|
39014
|
+
}
|
|
39015
|
+
if (node === null || typeof node !== "object") return;
|
|
39016
|
+
const obj = node;
|
|
39017
|
+
if (obj.type === "FIELD" && typeof obj.field === "string") add(obj.field);
|
|
39018
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string") add(obj.field);
|
|
39019
|
+
Object.values(obj).forEach(visit);
|
|
39020
|
+
};
|
|
39021
|
+
visit(where);
|
|
39022
|
+
return fields;
|
|
39023
|
+
}
|
|
39024
|
+
function existingValidationColumnMeta() {
|
|
39025
|
+
return new Map(EXISTING_VALIDATION_COLUMNS.map((column) => [column, {
|
|
39026
|
+
fieldType: column === "$id" ? "KSQL_NUMBER" : "KSQL_STRING",
|
|
39027
|
+
sortKind: column === "$id" ? "number" : "string",
|
|
39028
|
+
semantics: syntheticSemantics(column === "$id" ? "number" : "string")
|
|
39029
|
+
}]));
|
|
39030
|
+
}
|
|
39031
|
+
async function executeExistingRecordValidation(stmt, client, options, cacheContext) {
|
|
39032
|
+
if (stmt.errorTable) throw new Error("ArgumentError: VALIDATE INTO requires a batch.");
|
|
39033
|
+
return executeExistingRecordValidationCore(stmt, client, options, cacheContext);
|
|
39034
|
+
}
|
|
39035
|
+
async function executeExistingRecordValidationCore(stmt, client, options, cacheContext) {
|
|
39036
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
39037
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
39038
|
+
const targets = resolveExistingValidationTargets(stmt, fieldInfos);
|
|
39039
|
+
const checkGroups = stmt.checkGroups ?? [];
|
|
39040
|
+
const checkRefs2 = collectCheckFieldRefs(checkGroups);
|
|
39041
|
+
for (const ref of checkRefs2) {
|
|
39042
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
39043
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${stmt.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
39044
|
+
}
|
|
39045
|
+
}
|
|
39046
|
+
const evaluationTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
39047
|
+
evaluationTypes.set("$id", "RECORD_NUMBER");
|
|
39048
|
+
assertCheckComparisonTypes(stmt, evaluationTypes);
|
|
39049
|
+
const whereFields = collectValidateWhereFields(stmt.where);
|
|
39050
|
+
const requiredFields = [.../* @__PURE__ */ new Set([
|
|
39051
|
+
"$id",
|
|
39052
|
+
...targets.map((field) => field.code),
|
|
39053
|
+
...whereFields,
|
|
39054
|
+
...checkRefs2.map((ref) => ref.field)
|
|
39055
|
+
])];
|
|
39056
|
+
for (const field of whereFields) {
|
|
39057
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
39058
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${stmt.appId}.`);
|
|
39059
|
+
}
|
|
39060
|
+
}
|
|
39061
|
+
const numberPrecision = targets.some((field) => field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
|
|
39062
|
+
const semantics = (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0);
|
|
39063
|
+
const capability = classifyWhereCapability(stmt.where, semantics);
|
|
39064
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
39065
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
39066
|
+
}
|
|
39067
|
+
const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
39068
|
+
const fieldOptions = new Map(fieldInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
39069
|
+
const prefilter = stmt.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? stmt.where : extractSafePushdownLeaves(stmt.where, {
|
|
39070
|
+
allowUnqualifiedFields: true,
|
|
39071
|
+
fieldTypes,
|
|
39072
|
+
fieldOptions,
|
|
39073
|
+
allowKlike: false
|
|
39074
|
+
});
|
|
39075
|
+
const query = prefilter === null ? "" : whereToKintone(prefilter);
|
|
39076
|
+
const records = await fetchAll(client.getRecords, stmt.appId, query, requiredFields, {
|
|
39077
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
39078
|
+
parallel: options.fetchParallel ?? 1,
|
|
39079
|
+
onLimit: "error"
|
|
39080
|
+
});
|
|
39081
|
+
const validationRows = records.map((record2) => ({
|
|
39082
|
+
id: String(record2["$id"]?.value ?? ""),
|
|
39083
|
+
record: record2,
|
|
39084
|
+
flat: flatten(record2, null)
|
|
39085
|
+
})).filter((row) => stmt.where === null || evalWhere(stmt.where, row.flat, (field) => evaluationTypes.get(field.field)));
|
|
39086
|
+
const rows = [];
|
|
39087
|
+
for (const row of validationRows) {
|
|
39088
|
+
for (const field of targets) {
|
|
39089
|
+
const raw = row.record[field.code]?.value;
|
|
39090
|
+
const validation = validateAndNormalizeDmlValue(raw, field, numberPrecision);
|
|
39091
|
+
if (validation.ok) continue;
|
|
39092
|
+
rows.push({
|
|
39093
|
+
"$id": row.id,
|
|
39094
|
+
"$err_field": field.code,
|
|
39095
|
+
"$err_code": validation.code,
|
|
39096
|
+
"$err_message": validation.message,
|
|
39097
|
+
"$err_value": renderExistingValidationValue(raw, field.fieldType)
|
|
39098
|
+
});
|
|
39099
|
+
}
|
|
39100
|
+
for (const check2 of evaluateCustomChecks(checkGroups, row.flat, (field) => evaluationTypes.get(field.field))) {
|
|
39101
|
+
rows.push({
|
|
39102
|
+
"$id": row.id,
|
|
39103
|
+
"$err_field": "",
|
|
39104
|
+
"$err_code": "ERR_CHECK",
|
|
39105
|
+
"$err_message": check2.message,
|
|
39106
|
+
"$err_value": ""
|
|
39107
|
+
});
|
|
39108
|
+
}
|
|
39109
|
+
}
|
|
39110
|
+
const result = {
|
|
39111
|
+
type: "SELECT",
|
|
39112
|
+
columns: [...EXISTING_VALIDATION_COLUMNS],
|
|
39113
|
+
rows,
|
|
39114
|
+
rowCount: rows.length
|
|
39115
|
+
};
|
|
39116
|
+
materializedMetaBySelectResult.set(result, existingValidationColumnMeta());
|
|
39117
|
+
return result;
|
|
39118
|
+
}
|
|
38834
39119
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
38835
39120
|
function appendValidationErrors(tempTables, name, columns, rows, maxRows, columnMeta) {
|
|
38836
39121
|
const current = tempTables.get(name);
|
|
@@ -38964,9 +39249,14 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
38964
39249
|
}
|
|
38965
39250
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
38966
39251
|
if (stmt.type === "SET_VARIABLE") {
|
|
38967
|
-
const resolvedStmt2 =
|
|
39252
|
+
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
38968
39253
|
validateKlikeStatement(resolvedStmt2);
|
|
38969
|
-
if (resolvedStmt2.expr.type === "
|
|
39254
|
+
if (resolvedStmt2.expr.type === "ARRAY") {
|
|
39255
|
+
variables.set(stmt.name, {
|
|
39256
|
+
type: "array",
|
|
39257
|
+
elements: resolvedStmt2.expr.elements.map((element) => ({ type: "string", value: element.value }))
|
|
39258
|
+
});
|
|
39259
|
+
} else if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
38970
39260
|
try {
|
|
38971
39261
|
const value = await evaluateScalarSubquery(
|
|
38972
39262
|
resolvedStmt2.expr.query,
|
|
@@ -39003,8 +39293,27 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
39003
39293
|
}
|
|
39004
39294
|
return {};
|
|
39005
39295
|
}
|
|
39006
|
-
const resolvedStmt =
|
|
39296
|
+
const resolvedStmt = resolveBatchVariableReferences(stmt, variables);
|
|
39007
39297
|
validateKlikeStatement(resolvedStmt);
|
|
39298
|
+
if (resolvedStmt.type === "VALIDATE") {
|
|
39299
|
+
const result = await executeExistingRecordValidationCore(
|
|
39300
|
+
resolvedStmt,
|
|
39301
|
+
client,
|
|
39302
|
+
{ ...options, onLimitReached: "error" },
|
|
39303
|
+
cacheContext
|
|
39304
|
+
);
|
|
39305
|
+
if (resolvedStmt.errorTable) {
|
|
39306
|
+
appendValidationErrors(
|
|
39307
|
+
tempTables,
|
|
39308
|
+
resolvedStmt.errorTable,
|
|
39309
|
+
result.columns,
|
|
39310
|
+
result.rows,
|
|
39311
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
39312
|
+
materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta()
|
|
39313
|
+
);
|
|
39314
|
+
}
|
|
39315
|
+
return { result };
|
|
39316
|
+
}
|
|
39008
39317
|
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
39009
39318
|
const result = await executeDmlValidation(
|
|
39010
39319
|
resolvedStmt,
|
|
@@ -39189,9 +39498,9 @@ function evaluateScalarExpr(expr) {
|
|
|
39189
39498
|
}
|
|
39190
39499
|
}
|
|
39191
39500
|
}
|
|
39192
|
-
function
|
|
39501
|
+
function resolveBatchVariableReferences(node, variables) {
|
|
39193
39502
|
if (Array.isArray(node)) {
|
|
39194
|
-
return node.map((v) =>
|
|
39503
|
+
return node.map((v) => resolveBatchVariableReferences(v, variables));
|
|
39195
39504
|
}
|
|
39196
39505
|
if (node !== null && typeof node === "object") {
|
|
39197
39506
|
const obj = node;
|
|
@@ -39200,14 +39509,72 @@ function resolveVariableRefs(node, variables) {
|
|
|
39200
39509
|
if (value === void 0) {
|
|
39201
39510
|
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
39202
39511
|
}
|
|
39512
|
+
if (value.type === "array") {
|
|
39513
|
+
throw new Error(`ParseError: array variable @${obj["name"]} can only be used as IN @${obj["name"]}.`);
|
|
39514
|
+
}
|
|
39203
39515
|
return value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
|
|
39204
39516
|
}
|
|
39205
|
-
|
|
39206
|
-
|
|
39517
|
+
if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
|
|
39518
|
+
const value = variables.get(obj["name"]);
|
|
39519
|
+
if (value === void 0) throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
39520
|
+
if (value.type === "array") throw new Error(`ParseError: array variable @${obj["name"]} cannot be used as a SELECT column.`);
|
|
39521
|
+
return value.type === "number" ? { type: "ARITH_COL", expr: { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) }, alias: obj["alias"] } : { type: "LITERAL_COL", value: value.value, alias: obj["alias"] };
|
|
39522
|
+
}
|
|
39523
|
+
if (obj["type"] === "VARIABLE_IN_LIST") return obj;
|
|
39524
|
+
const resolved = Object.fromEntries(
|
|
39525
|
+
Object.entries(obj).map(([key, value]) => [key, resolveBatchVariableReferences(value, variables)])
|
|
39207
39526
|
);
|
|
39527
|
+
if (resolved["type"] === "BINARY") {
|
|
39528
|
+
const right = resolved["right"];
|
|
39529
|
+
if (right?.["type"] === "VARIABLE_IN_LIST" && typeof right["name"] === "string") {
|
|
39530
|
+
const value = variables.get(right["name"]);
|
|
39531
|
+
if (value === void 0) throw new Error(`ParseError: variable @${right["name"]} is not defined in this batch.`);
|
|
39532
|
+
if (value.type !== "array") {
|
|
39533
|
+
throw new Error(`ParseError: scalar variable @${right["name"]} cannot be used as IN @${right["name"]}; use IN (@${right["name"]}) instead.`);
|
|
39534
|
+
}
|
|
39535
|
+
if (value.elements.length === 0) {
|
|
39536
|
+
return { type: "BOOLEAN", value: resolved["op"] === "NOT_IN" };
|
|
39537
|
+
}
|
|
39538
|
+
resolved["right"] = {
|
|
39539
|
+
type: "IN_LIST",
|
|
39540
|
+
values: value.elements.map((element) => ({ type: "STRING", value: element.value }))
|
|
39541
|
+
};
|
|
39542
|
+
}
|
|
39543
|
+
}
|
|
39544
|
+
const simplified = simplifyBooleanWhere(resolved);
|
|
39545
|
+
if (simplified["type"] === "SELECT" && isBooleanNode(simplified["where"], true)) {
|
|
39546
|
+
simplified["where"] = null;
|
|
39547
|
+
}
|
|
39548
|
+
if ((simplified["type"] === "UPDATE" || simplified["type"] === "DELETE" || simplified["type"] === "REORDER") && isBooleanNode(simplified["where"], true)) {
|
|
39549
|
+
throw new Error("ArgumentError: empty-array simplification makes the target WHERE always true; use an explicit safe target condition.");
|
|
39550
|
+
}
|
|
39551
|
+
return simplified;
|
|
39208
39552
|
}
|
|
39209
39553
|
return node;
|
|
39210
39554
|
}
|
|
39555
|
+
function isBooleanNode(value, expected) {
|
|
39556
|
+
return value !== null && typeof value === "object" && value.type === "BOOLEAN" && (expected === void 0 || value.value === expected);
|
|
39557
|
+
}
|
|
39558
|
+
function simplifyBooleanWhere(obj) {
|
|
39559
|
+
if (obj["type"] === "NOT" && isBooleanNode(obj["expr"])) {
|
|
39560
|
+
return { type: "BOOLEAN", value: !obj["expr"].value };
|
|
39561
|
+
}
|
|
39562
|
+
if (obj["type"] === "GROUP" && isBooleanNode(obj["expr"])) return obj["expr"];
|
|
39563
|
+
if (obj["type"] === "LOGICAL") {
|
|
39564
|
+
const left = obj["left"];
|
|
39565
|
+
const right = obj["right"];
|
|
39566
|
+
if (obj["op"] === "AND") {
|
|
39567
|
+
if (isBooleanNode(left, false) || isBooleanNode(right, false)) return { type: "BOOLEAN", value: false };
|
|
39568
|
+
if (isBooleanNode(left, true)) return right;
|
|
39569
|
+
if (isBooleanNode(right, true)) return left;
|
|
39570
|
+
} else if (obj["op"] === "OR") {
|
|
39571
|
+
if (isBooleanNode(left, true) || isBooleanNode(right, true)) return { type: "BOOLEAN", value: true };
|
|
39572
|
+
if (isBooleanNode(left, false)) return right;
|
|
39573
|
+
if (isBooleanNode(right, false)) return left;
|
|
39574
|
+
}
|
|
39575
|
+
}
|
|
39576
|
+
return obj;
|
|
39577
|
+
}
|
|
39211
39578
|
function findVariableRef(node) {
|
|
39212
39579
|
if (Array.isArray(node)) {
|
|
39213
39580
|
for (const value of node) {
|
|
@@ -39218,7 +39585,7 @@ function findVariableRef(node) {
|
|
|
39218
39585
|
}
|
|
39219
39586
|
if (node !== null && typeof node === "object") {
|
|
39220
39587
|
const obj = node;
|
|
39221
|
-
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
|
|
39588
|
+
if ((obj["type"] === "VARIABLE" || obj["type"] === "VARIABLE_COL" || obj["type"] === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") return obj["name"];
|
|
39222
39589
|
for (const value of Object.values(obj)) {
|
|
39223
39590
|
const found = findVariableRef(value);
|
|
39224
39591
|
if (found !== null) return found;
|
|
@@ -39461,6 +39828,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
|
|
|
39461
39828
|
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
|
|
39462
39829
|
const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
|
|
39463
39830
|
const byCode = new Map(fields.map((field) => [field.code, field]));
|
|
39831
|
+
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) return;
|
|
39464
39832
|
const result = classifyWhereCapability(stmt.where, (field) => {
|
|
39465
39833
|
if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
|
|
39466
39834
|
const info = byCode.get(field.field);
|
|
@@ -39533,6 +39901,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
39533
39901
|
}
|
|
39534
39902
|
return result;
|
|
39535
39903
|
}
|
|
39904
|
+
function isConstantFalseWhere(where) {
|
|
39905
|
+
return where?.type === "BOOLEAN" && where.value === false;
|
|
39906
|
+
}
|
|
39536
39907
|
function isNoFromSelect(stmt) {
|
|
39537
39908
|
return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
|
|
39538
39909
|
}
|
|
@@ -39564,6 +39935,8 @@ function stringFuncHasFieldRef(expr) {
|
|
|
39564
39935
|
function validateNoFromColumns(stmt) {
|
|
39565
39936
|
for (const col of stmt.columns) {
|
|
39566
39937
|
switch (col.type) {
|
|
39938
|
+
case "VARIABLE_COL":
|
|
39939
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
39567
39940
|
case "LITERAL_COL":
|
|
39568
39941
|
break;
|
|
39569
39942
|
case "ARITH_COL":
|
|
@@ -39799,6 +40172,7 @@ function collectTypedInFieldRefs(expr, out) {
|
|
|
39799
40172
|
return;
|
|
39800
40173
|
case "NULL_CHECK":
|
|
39801
40174
|
case "EXISTS":
|
|
40175
|
+
case "BOOLEAN":
|
|
39802
40176
|
return;
|
|
39803
40177
|
}
|
|
39804
40178
|
}
|
|
@@ -40261,7 +40635,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
40261
40635
|
validateKlikePushdownPlan(pushdownPlan);
|
|
40262
40636
|
const mainPushDown = pushdownPlan.mainCondition;
|
|
40263
40637
|
const tableConditions = pushdownPlan.joinConditions;
|
|
40264
|
-
const
|
|
40638
|
+
const constantFalse = isConstantFalseWhere(stmt.where);
|
|
40639
|
+
const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
|
|
40265
40640
|
stmt,
|
|
40266
40641
|
stmt.from,
|
|
40267
40642
|
client,
|
|
@@ -40276,6 +40651,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
40276
40651
|
const parallelJoins = [];
|
|
40277
40652
|
const onOptJoins = [];
|
|
40278
40653
|
for (const join of stmt.joins) {
|
|
40654
|
+
if (constantFalse) {
|
|
40655
|
+
parallelJoins.push({ join, promise: Promise.resolve([]) });
|
|
40656
|
+
continue;
|
|
40657
|
+
}
|
|
40279
40658
|
const jCond = join.table.alias ? tableConditions.get(join.table.alias) ?? null : null;
|
|
40280
40659
|
if (jCond !== null) {
|
|
40281
40660
|
parallelJoins.push({
|
|
@@ -41673,6 +42052,26 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
41673
42052
|
};
|
|
41674
42053
|
}
|
|
41675
42054
|
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
42055
|
+
if (stmt.checkGroups?.length && isConstantFalseWhere(stmt.where)) {
|
|
42056
|
+
const fieldInfos2 = await loadWritableTopLevelDmlFields(
|
|
42057
|
+
stmt.appId,
|
|
42058
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
42059
|
+
client,
|
|
42060
|
+
cacheContext
|
|
42061
|
+
);
|
|
42062
|
+
await loadNumberPrecisionForTargets(
|
|
42063
|
+
stmt.appId,
|
|
42064
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
42065
|
+
fieldInfos2,
|
|
42066
|
+
client,
|
|
42067
|
+
cacheContext
|
|
42068
|
+
);
|
|
42069
|
+
const fieldTypes2 = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
42070
|
+
assertUpdateCheckRefs(stmt, fieldTypes2);
|
|
42071
|
+
assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes2, stmt.appId));
|
|
42072
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
42073
|
+
return { type: "UPDATE", updatedCount: 0 };
|
|
42074
|
+
}
|
|
41676
42075
|
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, tempTables);
|
|
41677
42076
|
if (stmt.subtableCode) {
|
|
41678
42077
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
@@ -41693,6 +42092,7 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
41693
42092
|
cacheContext
|
|
41694
42093
|
);
|
|
41695
42094
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
42095
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "UPDATE", updatedCount: 0 };
|
|
41696
42096
|
if (stmt.from != null) {
|
|
41697
42097
|
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
41698
42098
|
}
|
|
@@ -41774,6 +42174,7 @@ function collectUpdateFromTargetFields(stmt) {
|
|
|
41774
42174
|
}
|
|
41775
42175
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
41776
42176
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
42177
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "DELETE", deletedCount: 0 };
|
|
41777
42178
|
if (stmt.subtableCode) {
|
|
41778
42179
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
41779
42180
|
}
|
|
@@ -42156,6 +42557,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
|
|
|
42156
42557
|
cacheContext
|
|
42157
42558
|
);
|
|
42158
42559
|
const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
42560
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "REORDER", reorderedParentCount: 0 };
|
|
42159
42561
|
const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
|
|
42160
42562
|
field.code,
|
|
42161
42563
|
field.semantics ?? resolveFieldSemantics(field)
|
|
@@ -42382,6 +42784,8 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCa
|
|
|
42382
42784
|
}));
|
|
42383
42785
|
break;
|
|
42384
42786
|
}
|
|
42787
|
+
case "BOOLEAN":
|
|
42788
|
+
break;
|
|
42385
42789
|
}
|
|
42386
42790
|
}
|
|
42387
42791
|
async function resolveSetSubqueries(assignments, client, options, cacheContext) {
|
|
@@ -42419,9 +42823,11 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
42419
42823
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
42420
42824
|
return cache;
|
|
42421
42825
|
}
|
|
42826
|
+
var validateExplainInfo = /* @__PURE__ */ new WeakMap();
|
|
42422
42827
|
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords2 = 1e4) {
|
|
42423
42828
|
const fieldApps = /* @__PURE__ */ new Set();
|
|
42424
42829
|
const processStatusApps = /* @__PURE__ */ new Set();
|
|
42830
|
+
const numberPrecisionApps = /* @__PURE__ */ new Set();
|
|
42425
42831
|
const tracedClient = {
|
|
42426
42832
|
...client,
|
|
42427
42833
|
getFields: async (appId) => {
|
|
@@ -42431,6 +42837,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42431
42837
|
getProcessStatuses: async (appId) => {
|
|
42432
42838
|
processStatusApps.add(appId);
|
|
42433
42839
|
return client.getProcessStatuses(appId);
|
|
42840
|
+
},
|
|
42841
|
+
getNumberPrecision: async (appId) => {
|
|
42842
|
+
numberPrecisionApps.add(appId);
|
|
42843
|
+
return client.getNumberPrecision(appId);
|
|
42434
42844
|
}
|
|
42435
42845
|
};
|
|
42436
42846
|
const capabilities = /* @__PURE__ */ new Map();
|
|
@@ -42479,6 +42889,51 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42479
42889
|
}));
|
|
42480
42890
|
}
|
|
42481
42891
|
}
|
|
42892
|
+
} else if (typed["type"] === "VALIDATE") {
|
|
42893
|
+
const validate = node;
|
|
42894
|
+
fieldApps.add(validate.appId);
|
|
42895
|
+
const fields = await getFieldsCached(validate.appId, tracedClient, cacheContext);
|
|
42896
|
+
const infoByCode = new Map(fields.map((field) => [field.code, field]));
|
|
42897
|
+
const targets = resolveExistingValidationTargets(validate, fields);
|
|
42898
|
+
const checks = collectCheckFieldRefs(validate.checkGroups ?? []);
|
|
42899
|
+
const whereFields = collectValidateWhereFields(validate.where);
|
|
42900
|
+
for (const ref of checks) {
|
|
42901
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
42902
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${validate.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
42903
|
+
}
|
|
42904
|
+
}
|
|
42905
|
+
for (const field of whereFields) {
|
|
42906
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
42907
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${validate.appId}.`);
|
|
42908
|
+
}
|
|
42909
|
+
}
|
|
42910
|
+
const types = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
42911
|
+
types.set("$id", "RECORD_NUMBER");
|
|
42912
|
+
assertCheckComparisonTypes(validate, types);
|
|
42913
|
+
const capability = classifyWhereCapability(validate.where, (field) => field.field === "$id" ? resolveFieldSemantics({ fieldType: "__ID__" }) : infoByCode.get(field.field)?.semantics ?? (infoByCode.has(field.field) ? resolveFieldSemantics(infoByCode.get(field.field)) : void 0));
|
|
42914
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
42915
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
42916
|
+
}
|
|
42917
|
+
const fieldTypes = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
42918
|
+
const fieldOptions = new Map(fields.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
42919
|
+
const prefilter = validate.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? validate.where : extractSafePushdownLeaves(validate.where, {
|
|
42920
|
+
allowUnqualifiedFields: true,
|
|
42921
|
+
fieldTypes,
|
|
42922
|
+
fieldOptions,
|
|
42923
|
+
allowKlike: false
|
|
42924
|
+
});
|
|
42925
|
+
const needsPrecision = targets.some((field) => field.fieldType === "NUMBER");
|
|
42926
|
+
if (needsPrecision) {
|
|
42927
|
+
numberPrecisionApps.add(validate.appId);
|
|
42928
|
+
await getNumberPrecisionCached(validate.appId, tracedClient, cacheContext);
|
|
42929
|
+
}
|
|
42930
|
+
validateExplainInfo.set(validate, {
|
|
42931
|
+
targetFields: targets.map((field) => field.code),
|
|
42932
|
+
fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((field) => field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
|
|
42933
|
+
capability,
|
|
42934
|
+
prefilter,
|
|
42935
|
+
numberPrecision: needsPrecision
|
|
42936
|
+
});
|
|
42482
42937
|
} else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
|
|
42483
42938
|
fieldApps.add(node.appId);
|
|
42484
42939
|
await assertDmlWhereCapability(
|
|
@@ -42509,12 +42964,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
42509
42964
|
}));
|
|
42510
42965
|
}
|
|
42511
42966
|
}
|
|
42512
|
-
return { capabilities, orderPlans, fieldApps, processStatusApps };
|
|
42967
|
+
return { capabilities, orderPlans, fieldApps, processStatusApps, numberPrecisionApps };
|
|
42513
42968
|
}
|
|
42514
42969
|
function explainMetadataLines(analysis) {
|
|
42515
42970
|
return [
|
|
42516
42971
|
...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
|
|
42517
|
-
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
|
|
42972
|
+
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`),
|
|
42973
|
+
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
42518
42974
|
];
|
|
42519
42975
|
}
|
|
42520
42976
|
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2) {
|
|
@@ -42525,7 +42981,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
42525
42981
|
const plans = [];
|
|
42526
42982
|
for (let i = 0; i < statements.length; i++) {
|
|
42527
42983
|
const stmt = statements[i];
|
|
42528
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr:
|
|
42984
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
|
|
42529
42985
|
validateKlikeStatement(planStmt);
|
|
42530
42986
|
const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords2);
|
|
42531
42987
|
const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
|
|
@@ -42541,7 +42997,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
42541
42997
|
plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
|
|
42542
42998
|
});
|
|
42543
42999
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
42544
|
-
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
43000
|
+
variables.set(stmt.name, stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? { type: "array", elements: stmt.expr.elements.map((element) => ({ type: "string", value: element.value })) } : { type: "string", value: `@${stmt.name}` });
|
|
42545
43001
|
}
|
|
42546
43002
|
}
|
|
42547
43003
|
return { statementCount: statements.length, statements: plans };
|
|
@@ -42676,8 +43132,30 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
|
|
|
42676
43132
|
if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
|
|
42677
43133
|
if (query.type === "DELETE") return buildDeletePlan(query, label);
|
|
42678
43134
|
if (query.type === "REORDER") return buildReorderPlan(query, label);
|
|
43135
|
+
if (query.type === "VALIDATE") return buildValidatePlan(query, label);
|
|
42679
43136
|
return buildSelectPlan(query, label, capabilities, orderPlans);
|
|
42680
43137
|
}
|
|
43138
|
+
function buildValidatePlan(stmt, label) {
|
|
43139
|
+
const info = validateExplainInfo.get(stmt);
|
|
43140
|
+
const lines = [];
|
|
43141
|
+
if (label) lines.push(label);
|
|
43142
|
+
lines.push(`VALIDATE APP${stmt.appId}`);
|
|
43143
|
+
lines.push(" operation: read-only existing-record constraint audit (writesKintone=false)");
|
|
43144
|
+
lines.push(" fetch API: GET records via offset + $id keyset paging (Cursor API unused)");
|
|
43145
|
+
lines.push(" complete input: required (onLimit=truncate disabled)");
|
|
43146
|
+
if (!info) {
|
|
43147
|
+
lines.push(" metadata: form definition required; number precision required for NUMBER targets");
|
|
43148
|
+
return lines;
|
|
43149
|
+
}
|
|
43150
|
+
lines.push(` WHERE capability: ${info.capability.capability}`);
|
|
43151
|
+
lines.push(` kintone query: ${info.prefilter === null ? "(\u5168\u4EF6\u53D6\u5F97)" : whereToKintone(info.prefilter)}`);
|
|
43152
|
+
lines.push(` audit fields: ${info.targetFields.length === 0 ? "(\u306A\u3057)" : info.targetFields.join(", ")}`);
|
|
43153
|
+
lines.push(` fetch fields: ${info.fetchFields.join(", ")}`);
|
|
43154
|
+
lines.push(` number precision: ${info.numberPrecision ? "required" : "not required"}`);
|
|
43155
|
+
lines.push(" local checks: original WHERE re-evaluation + built-in constraints + CHECK groups");
|
|
43156
|
+
lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
|
|
43157
|
+
return lines;
|
|
43158
|
+
}
|
|
42681
43159
|
function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
42682
43160
|
const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
42683
43161
|
const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
@@ -42689,6 +43167,12 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
|
42689
43167
|
const lines = [];
|
|
42690
43168
|
if (label) lines.push(label);
|
|
42691
43169
|
lines.push(` mode: ${mode}`);
|
|
43170
|
+
if (isConstantFalseWhere(stmt.where)) {
|
|
43171
|
+
lines.push(" predicate: constant false");
|
|
43172
|
+
lines.push(" records API access: none");
|
|
43173
|
+
lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
|
|
43174
|
+
return lines;
|
|
43175
|
+
}
|
|
42692
43176
|
if (orderPlan) {
|
|
42693
43177
|
lines.push(` order plan: ${orderPlan.kind}`);
|
|
42694
43178
|
if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
|
|
@@ -42836,6 +43320,8 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
|
|
|
42836
43320
|
break;
|
|
42837
43321
|
case "NULL_CHECK":
|
|
42838
43322
|
break;
|
|
43323
|
+
case "BOOLEAN":
|
|
43324
|
+
break;
|
|
42839
43325
|
}
|
|
42840
43326
|
};
|
|
42841
43327
|
visitWhere(stmt.where);
|
|
@@ -42888,7 +43374,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
|
|
|
42888
43374
|
} else {
|
|
42889
43375
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
42890
43376
|
}
|
|
42891
|
-
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
43377
|
+
lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
42892
43378
|
const setTypes = [];
|
|
42893
43379
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
42894
43380
|
if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
|
|
@@ -42919,7 +43405,7 @@ function buildDeletePlan(stmt, label) {
|
|
|
42919
43405
|
lines.push(` [DELETE]`);
|
|
42920
43406
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
42921
43407
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
42922
|
-
lines.push(` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
|
|
43408
|
+
lines.push(isConstantFalseWhere(stmt.where) ? " api: metadata validation only (records API access: none)" : ` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
|
|
42923
43409
|
return lines;
|
|
42924
43410
|
}
|
|
42925
43411
|
function buildUpsertPlan(stmt, label) {
|
|
@@ -42959,7 +43445,7 @@ function buildReorderPlan(stmt, label) {
|
|
|
42959
43445
|
` table: ${target}`,
|
|
42960
43446
|
` scope: ${scope}`,
|
|
42961
43447
|
` by: ${byStr}`,
|
|
42962
|
-
` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
|
|
43448
|
+
isConstantFalseWhere(stmt.where) ? ` api: metadata validation only (records API access: none)` : ` api: GET /k/v1/records.json\uFF08\u884C ID \u53D6\u5F97\uFF09\u2192 PUT /k/v1/records.json\uFF08id \u914D\u5217\u306E\u307F\u9001\u4FE1\uFF09`
|
|
42963
43449
|
];
|
|
42964
43450
|
if (!stmt.all && stmt.where) {
|
|
42965
43451
|
lines.splice(5, 0, ` where: ${safeWhereToKintone(stmt.where)}`);
|
|
@@ -42971,6 +43457,7 @@ function formatOrderByItem(item) {
|
|
|
42971
43457
|
return `${key} ${item.direction}`;
|
|
42972
43458
|
}
|
|
42973
43459
|
function safeWhereToKintone(where) {
|
|
43460
|
+
if (where.type === "BOOLEAN") return where.value ? "TRUE" : "FALSE (constant)";
|
|
42974
43461
|
try {
|
|
42975
43462
|
return whereToKintone(where);
|
|
42976
43463
|
} catch {
|
|
@@ -45210,7 +45697,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45210
45697
|
profile: input.profile,
|
|
45211
45698
|
maxRecords: input.maxRecords,
|
|
45212
45699
|
fetchParallel: input.fetchParallel,
|
|
45213
|
-
onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
|
|
45700
|
+
onLimit: validation.containsValidationOnly || validation.statements.some((s) => s.statementType === "VALIDATE") ? "error" : input.onLimit,
|
|
45214
45701
|
timeout: input.timeout,
|
|
45215
45702
|
tempTableMaxRows: input.tempTableMaxRows,
|
|
45216
45703
|
cursorMaxActive: input.cursorMaxActive
|
|
@@ -45257,7 +45744,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45257
45744
|
profile: input.profile,
|
|
45258
45745
|
maxRecords: input.maxRecords,
|
|
45259
45746
|
fetchParallel: input.fetchParallel,
|
|
45260
|
-
onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
|
|
45747
|
+
onLimit: validation.containsValidationOnly || validation.statements.some((s) => s.statementType === "VALIDATE") ? "error" : input.onLimit,
|
|
45261
45748
|
timeout: input.timeout,
|
|
45262
45749
|
cursorMaxActive: input.cursorMaxActive
|
|
45263
45750
|
});
|
|
@@ -45559,7 +46046,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
45559
46046
|
var profile = external_exports.string().min(1).describe("kintone connection profile name from ksql.config.json (default: the server's default profile).").optional();
|
|
45560
46047
|
var maxRecords = external_exports.number().int().positive().describe("Maximum records fetched per SELECT (default 500).").optional();
|
|
45561
46048
|
var fetchParallel = external_exports.number().int().min(1).max(10).describe("Number of parallel kintone record-fetch requests (1-10).").optional();
|
|
45562
|
-
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always
|
|
46049
|
+
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. Leading VALIDATE and DML VALIDATE ONLY always override 'truncate' to 'error'.").optional();
|
|
45563
46050
|
var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
|
|
45564
46051
|
var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
|
|
45565
46052
|
var cursorMaxActive = external_exports.number().int().min(1).max(5).describe("Maximum active Cursor API handles per kintone host in this process (1-5, default 2). Later calls update the host limit; lowering it keeps existing cursors and delays new ones until active usage falls below the new limit. Create/Get are never automatically retried; capacity waits up to 30 seconds.").optional();
|
|
@@ -45688,7 +46175,7 @@ Options:
|
|
|
45688
46175
|
-h, --help Show help
|
|
45689
46176
|
`);
|
|
45690
46177
|
}
|
|
45691
|
-
var SERVER_VERSION = true ? "3.
|
|
46178
|
+
var SERVER_VERSION = true ? "3.5.0" : "0.0.0-dev";
|
|
45692
46179
|
function createServer(args) {
|
|
45693
46180
|
const server = new McpServer({
|
|
45694
46181
|
name: "ksql-mcp",
|
|
@@ -45710,7 +46197,7 @@ function createServer(args) {
|
|
|
45710
46197
|
}, tools.explainTool);
|
|
45711
46198
|
server.registerTool("ksql_query", {
|
|
45712
46199
|
title: "Run read-only kSQL",
|
|
45713
|
-
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT, and INSERT/UPSERT/UPDATE ... VALIDATE ONLY. ASSERT failure always stops the batch. Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always
|
|
46200
|
+
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT, leading VALIDATE app existing-record audits, and INSERT/UPSERT/UPDATE ... VALIDATE ONLY. ASSERT failure always stops the batch. Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE and VALIDATE ONLY always treat onLimit=truncate as error and perform zero write API calls. Existing-record VALIDATE applies built-in form constraints plus optional CHECK groups and can materialize its fixed five diagnostic columns with INTO #err in a batch. NUMBER targets use the app numberPrecision settings for integer-digit validation and fail closed if settings cannot be read. Excess fractional digits pass through for kintone to round automatically. Supports multi-statement batches with temp tables, including VALIDATE ONLY INTO #err for later SELECT. Mutating DML is rejected.",
|
|
45714
46201
|
inputSchema: queryInputShape
|
|
45715
46202
|
}, tools.queryTool);
|
|
45716
46203
|
server.registerTool("ksql_mutate", {
|