@rex0220/kintone-sql-tools 3.3.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 +1331 -153
- package/dist-mcp/ksql-mcp.js +1332 -155
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-cli/ksql.js
CHANGED
|
@@ -307,6 +307,10 @@ var Lexer = class {
|
|
|
307
307
|
this.pos += 2;
|
|
308
308
|
return this.makeToken("<=" /* LTE */, "<=", start);
|
|
309
309
|
}
|
|
310
|
+
if (ch === "|" && ch2 === "|") {
|
|
311
|
+
this.pos += 2;
|
|
312
|
+
return this.makeToken("||" /* CONCAT_OP */, "||", start);
|
|
313
|
+
}
|
|
310
314
|
switch (ch) {
|
|
311
315
|
case "=":
|
|
312
316
|
this.pos++;
|
|
@@ -650,6 +654,8 @@ var Parser = class {
|
|
|
650
654
|
constructor(tokens) {
|
|
651
655
|
this.tokens = tokens;
|
|
652
656
|
this.allowUnaryPlusNumber = false;
|
|
657
|
+
this.scalarAllowsAggregateArgs = true;
|
|
658
|
+
this.scalarAllowsCase = true;
|
|
653
659
|
this.pos = 0;
|
|
654
660
|
/** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
|
|
655
661
|
this.cteNames = /* @__PURE__ */ new Set();
|
|
@@ -738,13 +744,14 @@ var Parser = class {
|
|
|
738
744
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
739
745
|
if (upper === "DROP") return this.parseDropTempTable();
|
|
740
746
|
if (upper === "DECLARE") return this.parseDeclareVariable();
|
|
747
|
+
if (upper === "VALIDATE") return this.parseValidate();
|
|
741
748
|
break;
|
|
742
749
|
}
|
|
743
750
|
default:
|
|
744
751
|
break;
|
|
745
752
|
}
|
|
746
753
|
throw new ParseError(
|
|
747
|
-
"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",
|
|
754
|
+
"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",
|
|
748
755
|
tok
|
|
749
756
|
);
|
|
750
757
|
}
|
|
@@ -755,7 +762,7 @@ var Parser = class {
|
|
|
755
762
|
this.expect("SET" /* SET */);
|
|
756
763
|
const variable = this.expect("VARIABLE" /* VARIABLE */, "SET \u306E\u5F8C\u306B\u306F\u5909\u6570\u540D\uFF08\u4F8B: @name\uFF09\u304C\u5FC5\u8981\u3067\u3059");
|
|
757
764
|
this.expect("=" /* EQ */);
|
|
758
|
-
const expr = this.parseScalarExpr("SET", true);
|
|
765
|
+
const expr = this.peek().kind === "[" /* LBRACKET */ ? this.parseArrayLiteral() : this.parseScalarExpr("SET", true);
|
|
759
766
|
return { type: "SET_VARIABLE", name: variable.value.slice(1).toLowerCase(), expr };
|
|
760
767
|
}
|
|
761
768
|
parseDeclareVariable() {
|
|
@@ -818,17 +825,19 @@ var Parser = class {
|
|
|
818
825
|
}
|
|
819
826
|
rejectNonScalarExpr(node, tok, context) {
|
|
820
827
|
if (node.type === "STRING" || node.type === "NUMBER") return;
|
|
821
|
-
if (node.type === "FIELD_REF" || node.type === "AGG_REF") {
|
|
828
|
+
if (node.type === "FIELD_REF" || node.type === "FIELD" || node.type === "VARIABLE" || node.type === "AGG_REF") {
|
|
822
829
|
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u30FB\u96C6\u8A08\u95A2\u6570\u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
823
830
|
}
|
|
824
|
-
if (node.type === "ARITH" || node.type === "AGG_ARITH") {
|
|
831
|
+
if (node.type === "ARITH" || node.type === "SCALAR_ARITH" || node.type === "CONCAT_OP" || node.type === "AGG_ARITH") {
|
|
825
832
|
this.rejectNonScalarExpr(node.left, tok, context);
|
|
826
833
|
this.rejectNonScalarExpr(node.right, tok, context);
|
|
827
834
|
return;
|
|
828
835
|
}
|
|
829
836
|
if (node.type === "STRING_FUNC") {
|
|
830
837
|
for (const arg of node.args) this.rejectNonScalarExpr(arg, tok, context);
|
|
838
|
+
return;
|
|
831
839
|
}
|
|
840
|
+
throw new ParseError(`${context} \u306E\u53F3\u8FBA\u3067\u306F CASE \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`, tok);
|
|
832
841
|
}
|
|
833
842
|
// ----------------------------------------------------------
|
|
834
843
|
// CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
|
|
@@ -914,11 +923,63 @@ var Parser = class {
|
|
|
914
923
|
query = this.parseDelete();
|
|
915
924
|
} else if (tok.kind === "REORDER" /* REORDER */) {
|
|
916
925
|
query = this.parseReorder();
|
|
926
|
+
} else if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === "VALIDATE") {
|
|
927
|
+
query = this.parseValidate();
|
|
917
928
|
} else {
|
|
918
|
-
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
929
|
+
throw new ParseError("EXPLAIN \u306E\u5F8C\u306B\u306F SELECT / WITH / INSERT / UPSERT / UPDATE / DELETE / REORDER / VALIDATE \u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
919
930
|
}
|
|
920
931
|
return { type: "EXPLAIN", query };
|
|
921
932
|
}
|
|
933
|
+
/** VALIDATE APP100 [(fields)] [WHERE ...] [CHECK ...] [INTO #err]. */
|
|
934
|
+
parseValidate() {
|
|
935
|
+
const validateTok = this.advance();
|
|
936
|
+
const name = this.parseIdentifier();
|
|
937
|
+
const { appId, subtableCode } = extractTableRef(name, this.prev());
|
|
938
|
+
if (subtableCode) {
|
|
939
|
+
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());
|
|
940
|
+
}
|
|
941
|
+
let fields;
|
|
942
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
943
|
+
fields = this.parseIdentList();
|
|
944
|
+
this.expect(")" /* RPAREN */);
|
|
945
|
+
}
|
|
946
|
+
const where = this.consume("WHERE" /* WHERE */) ? this.parseWhereExpr() : null;
|
|
947
|
+
const checks = this.parseCheckGroups();
|
|
948
|
+
let errorTable;
|
|
949
|
+
if (this.consume("INTO" /* INTO */)) {
|
|
950
|
+
const tableTok = this.peek();
|
|
951
|
+
if (tableTok.kind !== "IDENT" /* IDENT */ || !tableTok.value.startsWith("#")) {
|
|
952
|
+
throw new ParseError("VALIDATE INTO \u306B\u306F # \u3067\u59CB\u307E\u308B\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059", tableTok);
|
|
953
|
+
}
|
|
954
|
+
errorTable = this.parseTableName();
|
|
955
|
+
}
|
|
956
|
+
const stmt = { type: "VALIDATE", appId, fields, where, ...checks, ...errorTable ? { errorTable } : {} };
|
|
957
|
+
this.assertValidateExpressions(stmt, validateTok);
|
|
958
|
+
return stmt;
|
|
959
|
+
}
|
|
960
|
+
/** v1 VALIDATE is single-app/local: subqueries and qualified references are rejected. */
|
|
961
|
+
assertValidateExpressions(stmt, tok) {
|
|
962
|
+
const visit = (node) => {
|
|
963
|
+
if (Array.isArray(node)) {
|
|
964
|
+
node.forEach(visit);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (node === null || typeof node !== "object") return;
|
|
968
|
+
const obj = node;
|
|
969
|
+
if (obj.type === "EXISTS" || obj.type === "SUBQUERY_IN_LIST" || obj.type === "SCALAR_SUBQUERY") {
|
|
970
|
+
throw new ParseError("VALIDATE \u306E WHERE / CHECK \u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
971
|
+
}
|
|
972
|
+
if (obj.type === "FIELD" && obj.tableAlias !== null && obj.tableAlias !== void 0) {
|
|
973
|
+
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);
|
|
974
|
+
}
|
|
975
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string" && obj.field.includes(".")) {
|
|
976
|
+
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);
|
|
977
|
+
}
|
|
978
|
+
Object.values(obj).forEach(visit);
|
|
979
|
+
};
|
|
980
|
+
visit(stmt.where);
|
|
981
|
+
visit(stmt.checkGroups);
|
|
982
|
+
}
|
|
922
983
|
// ----------------------------------------------------------
|
|
923
984
|
// ASSERT
|
|
924
985
|
//
|
|
@@ -1199,6 +1260,22 @@ var Parser = class {
|
|
|
1199
1260
|
if (this.consume("*" /* STAR */)) {
|
|
1200
1261
|
return { type: "WILDCARD" };
|
|
1201
1262
|
}
|
|
1263
|
+
if (this.hasTopLevelTokenBeforeValueEnd("||" /* CONCAT_OP */)) {
|
|
1264
|
+
const expr = this.parseScalarValueExpr({ allowAggregateArgs: true });
|
|
1265
|
+
const alias2 = this.consume("AS" /* AS */) ? this.parseAliasName() : null;
|
|
1266
|
+
return { type: "SCALAR_VALUE_COL", expr, alias: alias2 };
|
|
1267
|
+
}
|
|
1268
|
+
if (this.peek().kind === "VARIABLE" /* VARIABLE */) {
|
|
1269
|
+
const variable = this.advance();
|
|
1270
|
+
if (!this.consume("AS" /* AS */)) {
|
|
1271
|
+
throw new ParseError("SELECT \u5217\u306E\u30D0\u30C3\u30C1\u5909\u6570\u306B\u306F AS alias \u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
type: "VARIABLE_COL",
|
|
1275
|
+
name: variable.value.slice(1).toLowerCase(),
|
|
1276
|
+
alias: this.parseAliasName()
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1202
1279
|
const windowFunc = this.tryWindowFunc();
|
|
1203
1280
|
if (windowFunc !== null) {
|
|
1204
1281
|
return this.parseWindowColumn(windowFunc);
|
|
@@ -1314,13 +1391,25 @@ var Parser = class {
|
|
|
1314
1391
|
}
|
|
1315
1392
|
selectColumnHasAggregate(column) {
|
|
1316
1393
|
if (column.type === "AGGREGATE" || column.type === "ARITH_AGG_COL") return true;
|
|
1317
|
-
if (column.type
|
|
1318
|
-
|
|
1394
|
+
if (column.type === "STRFUNC_COL") return column.expr.args.some((arg) => this.stringFuncArgHasAggregate(arg));
|
|
1395
|
+
if (column.type === "SCALAR_VALUE_COL") return this.scalarValueHasAggregate(column.expr);
|
|
1396
|
+
return false;
|
|
1319
1397
|
}
|
|
1320
1398
|
stringFuncArgHasAggregate(arg) {
|
|
1321
1399
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
|
|
1322
|
-
|
|
1323
|
-
|
|
1400
|
+
return this.scalarValueHasAggregate(arg);
|
|
1401
|
+
}
|
|
1402
|
+
scalarValueHasAggregate(expr) {
|
|
1403
|
+
if (expr.type === "STRING_FUNC") return expr.args.some((arg) => this.stringFuncArgHasAggregate(arg));
|
|
1404
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
1405
|
+
return this.scalarValueHasAggregate(expr.left) || this.scalarValueHasAggregate(expr.right);
|
|
1406
|
+
}
|
|
1407
|
+
if (expr.type === "CASE_WHEN") {
|
|
1408
|
+
const results = [...expr.branches.map((b) => b.result), ...expr.elseResult ? [expr.elseResult] : []];
|
|
1409
|
+
return results.some((result) => {
|
|
1410
|
+
if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
|
|
1411
|
+
return this.scalarValueHasAggregate(result);
|
|
1412
|
+
});
|
|
1324
1413
|
}
|
|
1325
1414
|
return false;
|
|
1326
1415
|
}
|
|
@@ -1378,6 +1467,105 @@ var Parser = class {
|
|
|
1378
1467
|
throw new ParseError("\u96C6\u8A08\u7B97\u8853\u5F0F\u306B\u306F\u96C6\u8A08\u95A2\u6570\u307E\u305F\u306F\u6570\u5024\u304C\u5FC5\u8981\u3067\u3059", this.peek());
|
|
1379
1468
|
}
|
|
1380
1469
|
// ──────────────────────────────────────────────────
|
|
1470
|
+
// 汎用スカラー値式パーサー(B38)
|
|
1471
|
+
// ──────────────────────────────────────────────────
|
|
1472
|
+
/** 比較・述語・集約・サブクエリを含まない値式の公開入口。 */
|
|
1473
|
+
parseScalarValueExpr(options = {}) {
|
|
1474
|
+
const previousAggregateArgs = this.scalarAllowsAggregateArgs;
|
|
1475
|
+
const previousCase = this.scalarAllowsCase;
|
|
1476
|
+
this.scalarAllowsAggregateArgs = options.allowAggregateArgs === true;
|
|
1477
|
+
this.scalarAllowsCase = options.allowCase !== false;
|
|
1478
|
+
let expr;
|
|
1479
|
+
try {
|
|
1480
|
+
expr = this.parseScalarAddSubConcat(this.scalarAllowsCase);
|
|
1481
|
+
} finally {
|
|
1482
|
+
this.scalarAllowsAggregateArgs = previousAggregateArgs;
|
|
1483
|
+
this.scalarAllowsCase = previousCase;
|
|
1484
|
+
}
|
|
1485
|
+
const next = this.peek();
|
|
1486
|
+
if (next.kind === "IS" /* IS */ || next.kind === "=" /* EQ */ || next.kind === "!=" /* NEQ */ || next.kind === "<>" /* LT_GT */ || next.kind === ">" /* GT */ || next.kind === "<" /* LT */ || next.kind === ">=" /* GTE */ || next.kind === "<=" /* LTE */ || next.kind === "LIKE" /* LIKE */ || next.kind === "KLIKE" /* KLIKE */ || next.kind === "IN" /* IN */ || next.kind === "BETWEEN" /* BETWEEN */) throw new ParseError("\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u306B\u6BD4\u8F03\u30FB\u8FF0\u8A9E\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", next);
|
|
1487
|
+
return expr;
|
|
1488
|
+
}
|
|
1489
|
+
parseScalarAddSubConcat(allowCase) {
|
|
1490
|
+
let left = this.parseScalarMulDiv(allowCase);
|
|
1491
|
+
while (this.peek().kind === "+" /* PLUS */ || this.peek().kind === "-" /* MINUS */ || this.peek().kind === "||" /* CONCAT_OP */) {
|
|
1492
|
+
const token = this.advance();
|
|
1493
|
+
const right = this.parseScalarMulDiv(allowCase);
|
|
1494
|
+
left = token.kind === "||" /* CONCAT_OP */ ? { type: "CONCAT_OP", left, right } : { type: "SCALAR_ARITH", left, op: token.kind === "+" /* PLUS */ ? "+" : "-", right };
|
|
1495
|
+
}
|
|
1496
|
+
return left;
|
|
1497
|
+
}
|
|
1498
|
+
parseScalarMulDiv(allowCase) {
|
|
1499
|
+
let left = this.parseScalarPrimary(allowCase);
|
|
1500
|
+
while (this.peek().kind === "*" /* STAR */ || this.peek().kind === "/" /* SLASH */ || this.peek().kind === "%" /* PERCENT */) {
|
|
1501
|
+
const token = this.advance();
|
|
1502
|
+
const op = token.kind === "*" /* STAR */ ? "*" : token.kind === "/" /* SLASH */ ? "/" : "%";
|
|
1503
|
+
left = { type: "SCALAR_ARITH", left, op, right: this.parseScalarPrimary(allowCase) };
|
|
1504
|
+
}
|
|
1505
|
+
return left;
|
|
1506
|
+
}
|
|
1507
|
+
parseScalarPrimary(allowCase) {
|
|
1508
|
+
const tok = this.peek();
|
|
1509
|
+
if (tok.kind === "(" /* LPAREN */) {
|
|
1510
|
+
if (this.peekAt(1).kind === "SELECT" /* SELECT */) throw new ParseError("\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
1511
|
+
this.advance();
|
|
1512
|
+
const expr = this.parseScalarAddSubConcat(allowCase);
|
|
1513
|
+
this.expect(")" /* RPAREN */);
|
|
1514
|
+
return expr;
|
|
1515
|
+
}
|
|
1516
|
+
if (tok.kind === "+" /* PLUS */ || tok.kind === "-" /* MINUS */) {
|
|
1517
|
+
this.advance();
|
|
1518
|
+
if (this.peek().kind === "+" /* PLUS */ || this.peek().kind === "-" /* MINUS */) {
|
|
1519
|
+
throw new ParseError("\u5358\u9805\u7B26\u53F7\u3092\u91CD\u306D\u3066\u6307\u5B9A\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093", this.peek());
|
|
1520
|
+
}
|
|
1521
|
+
const operand = this.parseScalarPrimary(allowCase);
|
|
1522
|
+
if (operand.type === "NUMBER") {
|
|
1523
|
+
return makeNumberLiteral(`${tok.kind === "-" /* MINUS */ ? "-" : "+"}${numberLiteralText(operand)}`);
|
|
1524
|
+
}
|
|
1525
|
+
if (tok.kind === "+" /* PLUS */) throw new ParseError("\u5358\u9805 + \u306E\u76F4\u5F8C\u306B\u306F\u6570\u5024\u30EA\u30C6\u30E9\u30EB\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1526
|
+
return { type: "SCALAR_ARITH", left: makeNumberLiteral("0"), op: "-", right: operand };
|
|
1527
|
+
}
|
|
1528
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
1529
|
+
this.advance();
|
|
1530
|
+
return { type: "STRING", value: tok.value };
|
|
1531
|
+
}
|
|
1532
|
+
if (tok.kind === "NUMBER" /* NUMBER */) {
|
|
1533
|
+
this.advance();
|
|
1534
|
+
return makeNumberLiteral(tok.value);
|
|
1535
|
+
}
|
|
1536
|
+
if (tok.kind === "VARIABLE" /* VARIABLE */) {
|
|
1537
|
+
this.advance();
|
|
1538
|
+
return { type: "VARIABLE", name: tok.value.slice(1).toLowerCase() };
|
|
1539
|
+
}
|
|
1540
|
+
if (tok.kind === "CASE" /* CASE */) {
|
|
1541
|
+
if (!allowCase) throw new ParseError("\u3053\u306E\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u3067\u306F CASE \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
1542
|
+
return this.parseCaseWhenExpr();
|
|
1543
|
+
}
|
|
1544
|
+
if (this.tryAggregateFunc() !== null) throw new ParseError("\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u306B\u96C6\u7D04\u95A2\u6570\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
|
|
1545
|
+
if (this.tryStringFuncName() !== null) return this.parseStringFuncExpr();
|
|
1546
|
+
if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
|
|
1547
|
+
this.advance();
|
|
1548
|
+
if (this.consume("." /* DOT */)) return { type: "FIELD", tableAlias: tok.value, field: this.parseIdentifier() };
|
|
1549
|
+
return { type: "FIELD", tableAlias: null, field: tok.value };
|
|
1550
|
+
}
|
|
1551
|
+
throw new ParseError("\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u306E\u30AA\u30DA\u30E9\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
1552
|
+
}
|
|
1553
|
+
/** 現在の値の終端までに指定トークンがあるか(括弧内も対象)。 */
|
|
1554
|
+
hasTopLevelTokenBeforeValueEnd(target) {
|
|
1555
|
+
let depth = 0;
|
|
1556
|
+
for (let i = this.pos; i < this.tokens.length; i++) {
|
|
1557
|
+
const kind = this.tokens[i].kind;
|
|
1558
|
+
if (kind === "(" /* LPAREN */ || kind === "[" /* LBRACKET */) depth++;
|
|
1559
|
+
else if (kind === ")" /* RPAREN */ || kind === "]" /* RBRACKET */) {
|
|
1560
|
+
if (depth === 0) break;
|
|
1561
|
+
depth--;
|
|
1562
|
+
}
|
|
1563
|
+
if (kind === target) return true;
|
|
1564
|
+
if (depth === 0 && (kind === "," /* COMMA */ || kind === "AS" /* AS */ || kind === "FROM" /* FROM */ || kind === "WHERE" /* WHERE */ || kind === "WHEN" /* WHEN */ || kind === "THEN" /* THEN */ || kind === "ELSE" /* ELSE */ || kind === "END" /* END */ || kind === ";" /* SEMICOLON */ || kind === "EOF" /* EOF */)) break;
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
// ──────────────────────────────────────────────────
|
|
1381
1569
|
// 算術式パーサー(演算子優先順位: * / > + -)
|
|
1382
1570
|
//
|
|
1383
1571
|
// parseArithAddSub : + -(左結合・低優先度)
|
|
@@ -1499,12 +1687,15 @@ var Parser = class {
|
|
|
1499
1687
|
this.expect("END" /* END */);
|
|
1500
1688
|
return { type: "CASE_WHEN", branches, elseResult };
|
|
1501
1689
|
}
|
|
1502
|
-
/** THEN / ELSE
|
|
1690
|
+
/** THEN / ELSE の結果値。`||` を含む場合だけ新スカラー文法へ渡す。 */
|
|
1503
1691
|
parseCaseResult() {
|
|
1504
1692
|
const tok = this.peek();
|
|
1505
1693
|
if (tok.kind === "[" /* LBRACKET */) {
|
|
1506
1694
|
return this.parseArrayLiteral();
|
|
1507
1695
|
}
|
|
1696
|
+
if (this.hasTopLevelTokenBeforeValueEnd("||" /* CONCAT_OP */)) {
|
|
1697
|
+
return this.parseScalarValueExpr({ allowAggregateArgs: true });
|
|
1698
|
+
}
|
|
1508
1699
|
if (tok.kind === "STRING" /* STRING */) {
|
|
1509
1700
|
this.advance();
|
|
1510
1701
|
return { type: "STRING", value: tok.value };
|
|
@@ -1652,25 +1843,19 @@ var Parser = class {
|
|
|
1652
1843
|
}
|
|
1653
1844
|
return { type: "STRING", value: normalized };
|
|
1654
1845
|
}
|
|
1655
|
-
/** 文字列関数の引数:
|
|
1846
|
+
/** 文字列関数の引数: ScalarValueExpr / 集計算術式 */
|
|
1656
1847
|
parseStringFuncArg() {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
try {
|
|
1667
|
-
const left = this.parseAggPrimary();
|
|
1668
|
-
const expr = this.continueAggArith(left);
|
|
1669
|
-
if (this.hasAggregateOperand(expr)) return expr;
|
|
1670
|
-
} catch {
|
|
1848
|
+
if (this.scalarAllowsAggregateArgs) {
|
|
1849
|
+
const startPos = this.pos;
|
|
1850
|
+
try {
|
|
1851
|
+
const left = this.parseAggPrimary();
|
|
1852
|
+
const expr = this.continueAggArith(left);
|
|
1853
|
+
if (this.hasAggregateOperand(expr)) return expr;
|
|
1854
|
+
} catch {
|
|
1855
|
+
}
|
|
1856
|
+
this.pos = startPos;
|
|
1671
1857
|
}
|
|
1672
|
-
this.
|
|
1673
|
-
return this.parseArithAddSub();
|
|
1858
|
+
return this.parseScalarAddSubConcat(this.scalarAllowsCase);
|
|
1674
1859
|
}
|
|
1675
1860
|
hasAggregateOperand(node) {
|
|
1676
1861
|
if (node.type === "AGG_REF") return true;
|
|
@@ -1758,6 +1943,7 @@ var Parser = class {
|
|
|
1758
1943
|
const k = this.peek().kind;
|
|
1759
1944
|
if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
|
|
1760
1945
|
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "VALIDATE" && this.peekAt(1).kind === "IDENT" /* IDENT */ && this.peekAt(1).value.toUpperCase() === "ONLY") return null;
|
|
1946
|
+
if (k === "IDENT" /* IDENT */ && this.peek().value.toUpperCase() === "CHECK" && this.peekAt(1).kind === "WHEN" /* WHEN */) return null;
|
|
1761
1947
|
return this.parseTableAliasName();
|
|
1762
1948
|
}
|
|
1763
1949
|
return null;
|
|
@@ -1905,9 +2091,7 @@ var Parser = class {
|
|
|
1905
2091
|
}
|
|
1906
2092
|
if (this.consume("NOT" /* NOT */)) {
|
|
1907
2093
|
if (this.consume("IN" /* IN */)) {
|
|
1908
|
-
this.
|
|
1909
|
-
const right2 = this.parseInListOrSubquery();
|
|
1910
|
-
this.expect(")" /* RPAREN */);
|
|
2094
|
+
const right2 = this.parseInRight();
|
|
1911
2095
|
return { type: "BINARY", op: "NOT_IN", left: field, right: right2 };
|
|
1912
2096
|
}
|
|
1913
2097
|
if (this.consume("LIKE" /* LIKE */)) {
|
|
@@ -1924,9 +2108,7 @@ var Parser = class {
|
|
|
1924
2108
|
);
|
|
1925
2109
|
}
|
|
1926
2110
|
if (this.consume("IN" /* IN */)) {
|
|
1927
|
-
this.
|
|
1928
|
-
const right2 = this.parseInListOrSubquery();
|
|
1929
|
-
this.expect(")" /* RPAREN */);
|
|
2111
|
+
const right2 = this.parseInRight();
|
|
1930
2112
|
return { type: "BINARY", op: "IN", left: field, right: right2 };
|
|
1931
2113
|
}
|
|
1932
2114
|
if (this.consume("KLIKE" /* KLIKE */)) {
|
|
@@ -2093,6 +2275,18 @@ var Parser = class {
|
|
|
2093
2275
|
);
|
|
2094
2276
|
}
|
|
2095
2277
|
// IN (...) — 値リストまたはサブクエリ
|
|
2278
|
+
parseInRight() {
|
|
2279
|
+
if (this.consume("(" /* LPAREN */)) {
|
|
2280
|
+
const right = this.parseInListOrSubquery();
|
|
2281
|
+
this.expect(")" /* RPAREN */);
|
|
2282
|
+
return right;
|
|
2283
|
+
}
|
|
2284
|
+
const variable = this.expect(
|
|
2285
|
+
"VARIABLE" /* VARIABLE */,
|
|
2286
|
+
"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"
|
|
2287
|
+
);
|
|
2288
|
+
return { type: "VARIABLE_IN_LIST", name: variable.value.slice(1).toLowerCase() };
|
|
2289
|
+
}
|
|
2096
2290
|
parseInListOrSubquery() {
|
|
2097
2291
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
2098
2292
|
const query = this.parseSelect();
|
|
@@ -2223,8 +2417,9 @@ var Parser = class {
|
|
|
2223
2417
|
if (subtableCode) {
|
|
2224
2418
|
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());
|
|
2225
2419
|
}
|
|
2420
|
+
const checkGroups2 = this.parseCheckGroups();
|
|
2226
2421
|
const validation2 = this.parseDmlControlSuffix();
|
|
2227
|
-
return { type: "INSERT_SELECT", appId, fields, select, ...validation2 };
|
|
2422
|
+
return { type: "INSERT_SELECT", appId, fields, select, ...checkGroups2, ...validation2 };
|
|
2228
2423
|
}
|
|
2229
2424
|
this.expect("VALUES" /* VALUES */);
|
|
2230
2425
|
const values = [];
|
|
@@ -2234,11 +2429,15 @@ var Parser = class {
|
|
|
2234
2429
|
this.expect(")" /* RPAREN */);
|
|
2235
2430
|
values.push(row);
|
|
2236
2431
|
} while (this.consume("," /* COMMA */));
|
|
2432
|
+
const checkGroups = this.parseCheckGroups();
|
|
2237
2433
|
const validation = this.parseDmlControlSuffix();
|
|
2434
|
+
if (subtableCode && checkGroups.checkGroups) {
|
|
2435
|
+
throw new ParseError("CHECK \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB INSERT \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093", this.prev());
|
|
2436
|
+
}
|
|
2238
2437
|
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
2239
2438
|
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());
|
|
2240
2439
|
}
|
|
2241
|
-
return subtableCode ? { type: "INSERT", appId, subtableCode, fields, values, ...validation } : { type: "INSERT", appId, fields, values, ...validation };
|
|
2440
|
+
return subtableCode ? { type: "INSERT", appId, subtableCode, fields, values, ...checkGroups, ...validation } : { type: "INSERT", appId, fields, values, ...checkGroups, ...validation };
|
|
2242
2441
|
}
|
|
2243
2442
|
parseUpsert() {
|
|
2244
2443
|
this.expect("UPSERT" /* UPSERT */);
|
|
@@ -2255,8 +2454,9 @@ var Parser = class {
|
|
|
2255
2454
|
if (this.peek().kind === "SELECT" /* SELECT */) {
|
|
2256
2455
|
const select = this.parseSelect();
|
|
2257
2456
|
const keyFields2 = this.parseOnDuplicate();
|
|
2457
|
+
const checkGroups2 = this.parseCheckGroups();
|
|
2258
2458
|
const validation2 = this.parseDmlControlSuffix();
|
|
2259
|
-
return { type: "UPSERT_SELECT", appId, fields, select, keyFields: keyFields2, ...validation2 };
|
|
2459
|
+
return { type: "UPSERT_SELECT", appId, fields, select, keyFields: keyFields2, ...checkGroups2, ...validation2 };
|
|
2260
2460
|
}
|
|
2261
2461
|
this.expect("VALUES" /* VALUES */);
|
|
2262
2462
|
const values = [];
|
|
@@ -2266,8 +2466,9 @@ var Parser = class {
|
|
|
2266
2466
|
this.expect(")" /* RPAREN */);
|
|
2267
2467
|
} while (this.consume("," /* COMMA */));
|
|
2268
2468
|
const keyFields = this.parseOnDuplicate();
|
|
2469
|
+
const checkGroups = this.parseCheckGroups();
|
|
2269
2470
|
const validation = this.parseDmlControlSuffix();
|
|
2270
|
-
return { type: "UPSERT", appId, fields, values, keyFields, ...validation };
|
|
2471
|
+
return { type: "UPSERT", appId, fields, values, keyFields, ...checkGroups, ...validation };
|
|
2271
2472
|
}
|
|
2272
2473
|
parseOnDuplicate() {
|
|
2273
2474
|
this.expectKeyword("ON" /* ON */, "UPSERT \u306B\u306F ON DUPLICATE (\u30AD\u30FC\u30D5\u30A3\u30FC\u30EB\u30C9) \u304C\u5FC5\u8981\u3067\u3059");
|
|
@@ -2399,12 +2600,33 @@ var Parser = class {
|
|
|
2399
2600
|
whereTok
|
|
2400
2601
|
);
|
|
2401
2602
|
}
|
|
2603
|
+
const checkGroups = this.parseCheckGroups();
|
|
2402
2604
|
const validation = this.parseDmlControlSuffix();
|
|
2605
|
+
if (subtableCode && checkGroups.checkGroups) {
|
|
2606
|
+
throw new ParseError("CHECK \u306F\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB UPDATE \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093", this.prev());
|
|
2607
|
+
}
|
|
2403
2608
|
if (subtableCode && (validation.validateOnly || validation.onErrorSkip)) {
|
|
2404
2609
|
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());
|
|
2405
2610
|
}
|
|
2406
|
-
if (from !== null) return { type: "UPDATE", appId, assignments, where, from, ...validation };
|
|
2407
|
-
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where, ...validation } : { type: "UPDATE", appId, assignments, where, ...validation };
|
|
2611
|
+
if (from !== null) return { type: "UPDATE", appId, assignments, where, from, ...checkGroups, ...validation };
|
|
2612
|
+
return subtableCode ? { type: "UPDATE", appId, subtableCode, assignments, where, ...checkGroups, ...validation } : { type: "UPDATE", appId, assignments, where, ...checkGroups, ...validation };
|
|
2613
|
+
}
|
|
2614
|
+
/** CHECK WHEN ... THEN ... blocks. CHECK is a soft keyword. */
|
|
2615
|
+
parseCheckGroups() {
|
|
2616
|
+
const groups = [];
|
|
2617
|
+
while (this.isSoftKeyword("CHECK") && this.peekAt(1).kind === "WHEN" /* WHEN */) {
|
|
2618
|
+
const check = this.advance();
|
|
2619
|
+
const rules = [];
|
|
2620
|
+
while (this.consume("WHEN" /* WHEN */)) {
|
|
2621
|
+
const condition = this.parseWhereExpr();
|
|
2622
|
+
this.expect("THEN" /* THEN */, "CHECK WHEN \u306E\u6761\u4EF6\u306E\u5F8C\u306B\u306F THEN \u304C\u5FC5\u8981\u3067\u3059");
|
|
2623
|
+
const message = this.parseScalarValueExpr({ allowCase: false });
|
|
2624
|
+
rules.push({ condition, message });
|
|
2625
|
+
}
|
|
2626
|
+
if (rules.length === 0) throw new ParseError("CHECK \u306E\u5F8C\u306B\u306F WHEN \u304C\u6700\u4F4E 1 \u3064\u5FC5\u8981\u3067\u3059", check);
|
|
2627
|
+
groups.push({ rules });
|
|
2628
|
+
}
|
|
2629
|
+
return groups.length > 0 ? { checkGroups: groups } : {};
|
|
2408
2630
|
}
|
|
2409
2631
|
/** DML末尾の VALIDATE ONLY または ON ERROR SKIP。各語はsoft keyword。 */
|
|
2410
2632
|
parseDmlControlSuffix() {
|
|
@@ -2593,6 +2815,12 @@ var Parser = class {
|
|
|
2593
2815
|
*/
|
|
2594
2816
|
parseAssignmentValue() {
|
|
2595
2817
|
const tok = this.peek();
|
|
2818
|
+
if (this.hasTopLevelTokenBeforeValueEnd("||" /* CONCAT_OP */)) {
|
|
2819
|
+
const expr = this.parseScalarValueExpr();
|
|
2820
|
+
if (expr.type === "CONCAT_OP" || expr.type === "SCALAR_ARITH" || expr.type === "STRING_FUNC") return expr;
|
|
2821
|
+
if (expr.type === "CASE_WHEN") return { type: "CASE_VALUE", expr };
|
|
2822
|
+
throw new ParseError("SET \u306E\u5024\u306B\u306F\u9023\u7D50\u3092\u542B\u3080\u30B9\u30AB\u30E9\u30FC\u5024\u5F0F\u304C\u5FC5\u8981\u3067\u3059", tok);
|
|
2823
|
+
}
|
|
2596
2824
|
if (tok.kind === "VARIABLE" /* VARIABLE */) return this.parseSqlValue();
|
|
2597
2825
|
if (tok.kind === "STRING" /* STRING */) return this.parseSqlValue();
|
|
2598
2826
|
if (tok.kind === "TODAY" /* TODAY */ || tok.kind === "NOW" /* NOW */ || tok.kind === "LOGINUSER" /* LOGINUSER */) return this.parseSqlValue();
|
|
@@ -2869,7 +3097,7 @@ function isDmlType(type) {
|
|
|
2869
3097
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
2870
3098
|
}
|
|
2871
3099
|
function isReadOnlyType(type) {
|
|
2872
|
-
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";
|
|
3100
|
+
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";
|
|
2873
3101
|
}
|
|
2874
3102
|
function writesKintone(stmt) {
|
|
2875
3103
|
return isDmlType(stmt.type) && !("validateOnly" in stmt && stmt.validateOnly === true);
|
|
@@ -2880,6 +3108,8 @@ function isReadOnlyStatement(stmt) {
|
|
|
2880
3108
|
function requiresCompleteInput(stmt) {
|
|
2881
3109
|
if (isDmlType(stmt.type)) return true;
|
|
2882
3110
|
switch (stmt.type) {
|
|
3111
|
+
case "VALIDATE":
|
|
3112
|
+
return true;
|
|
2883
3113
|
case "SELECT":
|
|
2884
3114
|
return selectRequiresCompleteInput(stmt);
|
|
2885
3115
|
case "UNION":
|
|
@@ -2920,6 +3150,7 @@ function whereRequiresCompleteInput(where) {
|
|
|
2920
3150
|
case "EXISTS":
|
|
2921
3151
|
return selectRequiresCompleteInput(where.query);
|
|
2922
3152
|
case "NULL_CHECK":
|
|
3153
|
+
case "BOOLEAN":
|
|
2923
3154
|
return false;
|
|
2924
3155
|
}
|
|
2925
3156
|
}
|
|
@@ -2955,6 +3186,8 @@ function collectDmlTargetFields(stmt) {
|
|
|
2955
3186
|
// src/engine/pushDownNot.ts
|
|
2956
3187
|
function pushDownNot(expr) {
|
|
2957
3188
|
switch (expr.type) {
|
|
3189
|
+
case "BOOLEAN":
|
|
3190
|
+
return { type: "BOOLEAN", value: !expr.value };
|
|
2958
3191
|
case "BINARY": {
|
|
2959
3192
|
const negated = negateOp(expr.op);
|
|
2960
3193
|
if (negated === null) {
|
|
@@ -3034,6 +3267,7 @@ function whereHasLike(where) {
|
|
|
3034
3267
|
case "BINARY":
|
|
3035
3268
|
case "NULL_CHECK":
|
|
3036
3269
|
case "EXISTS":
|
|
3270
|
+
case "BOOLEAN":
|
|
3037
3271
|
return false;
|
|
3038
3272
|
}
|
|
3039
3273
|
}
|
|
@@ -3049,6 +3283,7 @@ function whereHasKlike(where) {
|
|
|
3049
3283
|
case "BINARY":
|
|
3050
3284
|
case "NULL_CHECK":
|
|
3051
3285
|
case "EXISTS":
|
|
3286
|
+
case "BOOLEAN":
|
|
3052
3287
|
return false;
|
|
3053
3288
|
}
|
|
3054
3289
|
}
|
|
@@ -3068,6 +3303,8 @@ function whereToKintone(expr) {
|
|
|
3068
3303
|
return convertGroup(expr);
|
|
3069
3304
|
case "EXISTS":
|
|
3070
3305
|
throw new KintoneQueryError("EXISTS \u306F kintone \u30AF\u30A8\u30EA\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093");
|
|
3306
|
+
case "BOOLEAN":
|
|
3307
|
+
throw new KintoneQueryError("internal error: BOOLEAN predicate reached kintone query conversion");
|
|
3071
3308
|
}
|
|
3072
3309
|
}
|
|
3073
3310
|
function convertBinary(expr) {
|
|
@@ -3146,6 +3383,8 @@ function convertValue(value, op) {
|
|
|
3146
3383
|
switch (value.type) {
|
|
3147
3384
|
case "VARIABLE":
|
|
3148
3385
|
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
3386
|
+
case "VARIABLE_IN_LIST":
|
|
3387
|
+
throw new KintoneQueryError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
3149
3388
|
case "STRING":
|
|
3150
3389
|
return convertString(value);
|
|
3151
3390
|
case "NUMBER":
|
|
@@ -3216,7 +3455,7 @@ function resolveSelectMode(stmt) {
|
|
|
3216
3455
|
if (stmt.distinct) return "FULL_SCAN";
|
|
3217
3456
|
if (hasWindowColumns(stmt.columns)) return "FULL_SCAN";
|
|
3218
3457
|
if (stmt.columns.some(
|
|
3219
|
-
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "SCALAR_SUBQUERY_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr(c.expr)
|
|
3458
|
+
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "SCALAR_SUBQUERY_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr(c.expr) || c.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate(c.expr)
|
|
3220
3459
|
)) return "FULL_SCAN";
|
|
3221
3460
|
if (whereRequiresJsEval(stmt.where)) return "FULL_SCAN";
|
|
3222
3461
|
if (stmt.orderBy.some((o) => o.key.type !== "FIELD_NAME")) return "FULL_SCAN";
|
|
@@ -3225,6 +3464,8 @@ function resolveSelectMode(stmt) {
|
|
|
3225
3464
|
function whereRequiresJsEval(where) {
|
|
3226
3465
|
if (where === null) return false;
|
|
3227
3466
|
switch (where.type) {
|
|
3467
|
+
case "BOOLEAN":
|
|
3468
|
+
return true;
|
|
3228
3469
|
case "BINARY":
|
|
3229
3470
|
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);
|
|
3230
3471
|
case "NULL_CHECK":
|
|
@@ -3311,6 +3552,8 @@ function extractFields(columns) {
|
|
|
3311
3552
|
collectArithNode(col.expr, fields);
|
|
3312
3553
|
} else if (col.type === "STRFUNC_COL") {
|
|
3313
3554
|
collectStringFuncFields(col.expr, fields);
|
|
3555
|
+
} else if (col.type === "SCALAR_VALUE_COL") {
|
|
3556
|
+
collectScalarValueFields(col.expr, fields);
|
|
3314
3557
|
}
|
|
3315
3558
|
}
|
|
3316
3559
|
return [...new Set(fields)];
|
|
@@ -3336,16 +3579,38 @@ function collectStringFuncFields(expr, out) {
|
|
|
3336
3579
|
}
|
|
3337
3580
|
}
|
|
3338
3581
|
function collectStringFuncArgFields(arg, out) {
|
|
3339
|
-
if (arg.type === "STRING") return;
|
|
3340
|
-
if (arg.type === "STRING_FUNC") {
|
|
3341
|
-
collectStringFuncFields(arg, out);
|
|
3342
|
-
return;
|
|
3343
|
-
}
|
|
3344
3582
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
3345
3583
|
collectAggOperandFields(arg, out);
|
|
3346
3584
|
return;
|
|
3347
3585
|
}
|
|
3348
|
-
|
|
3586
|
+
collectScalarValueFields(arg, out);
|
|
3587
|
+
}
|
|
3588
|
+
function collectScalarValueFields(expr, out) {
|
|
3589
|
+
if (expr.type === "FIELD") {
|
|
3590
|
+
out.push(normalizeSimpleFieldRef(expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field));
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
if (expr.type === "STRING_FUNC") {
|
|
3594
|
+
collectStringFuncFields(expr, out);
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
3598
|
+
collectScalarValueFields(expr.left, out);
|
|
3599
|
+
collectScalarValueFields(expr.right, out);
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
if (expr.type === "CASE_WHEN") {
|
|
3603
|
+
for (const branch of expr.branches) collectCaseResultScalarFields(branch.result, out);
|
|
3604
|
+
if (expr.elseResult) collectCaseResultScalarFields(expr.elseResult, out);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
function collectCaseResultScalarFields(result, out) {
|
|
3608
|
+
if (result.type === "ARRAY") return;
|
|
3609
|
+
if (result.type === "FIELD_REF" || result.type === "ARITH") {
|
|
3610
|
+
collectArithNode(result, out);
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3613
|
+
collectScalarValueFields(result, out);
|
|
3349
3614
|
}
|
|
3350
3615
|
function collectAggOperandFields(node, out) {
|
|
3351
3616
|
if (node.type === "AGG_REF") {
|
|
@@ -3360,10 +3625,23 @@ function collectAggOperandFields(node, out) {
|
|
|
3360
3625
|
function hasAggregateInStringFuncExpr(expr) {
|
|
3361
3626
|
return expr.args.some((arg) => {
|
|
3362
3627
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
|
|
3363
|
-
|
|
3364
|
-
return false;
|
|
3628
|
+
return scalarValueHasAggregate(arg);
|
|
3365
3629
|
});
|
|
3366
3630
|
}
|
|
3631
|
+
function scalarValueHasAggregate(expr) {
|
|
3632
|
+
if (expr.type === "STRING_FUNC") return hasAggregateInStringFuncExpr(expr);
|
|
3633
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
3634
|
+
return scalarValueHasAggregate(expr.left) || scalarValueHasAggregate(expr.right);
|
|
3635
|
+
}
|
|
3636
|
+
if (expr.type === "CASE_WHEN") {
|
|
3637
|
+
return expr.branches.some((b) => caseResultHasAggregate(b.result)) || expr.elseResult !== null && caseResultHasAggregate(expr.elseResult);
|
|
3638
|
+
}
|
|
3639
|
+
return false;
|
|
3640
|
+
}
|
|
3641
|
+
function caseResultHasAggregate(result) {
|
|
3642
|
+
if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
|
|
3643
|
+
return scalarValueHasAggregate(result);
|
|
3644
|
+
}
|
|
3367
3645
|
function collectRequiredFieldsByTable(stmt) {
|
|
3368
3646
|
const physicalTables = [stmt.from, ...stmt.joins.map((j) => j.table)].filter((t) => t.cteName === null);
|
|
3369
3647
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -3498,28 +3776,38 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
3498
3776
|
}
|
|
3499
3777
|
};
|
|
3500
3778
|
const walkStringArg = (arg, phase = "select") => {
|
|
3501
|
-
if (arg.type === "STRING") return;
|
|
3502
|
-
if (arg.type === "STRING_FUNC") {
|
|
3503
|
-
walkStringFunc(arg, phase);
|
|
3504
|
-
return;
|
|
3505
|
-
}
|
|
3506
3779
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
3507
3780
|
walkAgg(arg, phase);
|
|
3508
3781
|
return;
|
|
3509
3782
|
}
|
|
3510
|
-
|
|
3783
|
+
walkScalar(arg, phase);
|
|
3511
3784
|
};
|
|
3512
3785
|
const walkStringFunc = (expr, phase = "select") => {
|
|
3513
3786
|
for (const arg of expr.args) walkStringArg(arg, phase);
|
|
3514
3787
|
};
|
|
3788
|
+
const walkScalar = (expr, phase = "select") => {
|
|
3789
|
+
if (expr.type === "FIELD") {
|
|
3790
|
+
addFieldRef(expr.field, expr.tableAlias, phase);
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
if (expr.type === "STRING_FUNC") {
|
|
3794
|
+
walkStringFunc(expr, phase);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
3798
|
+
walkScalar(expr.left, phase);
|
|
3799
|
+
walkScalar(expr.right, phase);
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
if (expr.type === "CASE_WHEN") walkCase(expr, phase);
|
|
3803
|
+
};
|
|
3515
3804
|
const walkCaseResult = (result, phase = "select") => {
|
|
3516
|
-
if (result.type === "STRING") return;
|
|
3517
3805
|
if (result.type === "ARRAY") return;
|
|
3518
|
-
if (result.type === "
|
|
3519
|
-
|
|
3806
|
+
if (result.type === "FIELD_REF" || result.type === "ARITH") {
|
|
3807
|
+
walkArith(result, phase);
|
|
3520
3808
|
return;
|
|
3521
3809
|
}
|
|
3522
|
-
|
|
3810
|
+
walkScalar(result, phase);
|
|
3523
3811
|
};
|
|
3524
3812
|
const walkCase = (expr, phase = "select") => {
|
|
3525
3813
|
for (const b of expr.branches) {
|
|
@@ -3572,6 +3860,7 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
3572
3860
|
walkWhere(where.expr, phase);
|
|
3573
3861
|
return;
|
|
3574
3862
|
case "EXISTS":
|
|
3863
|
+
case "BOOLEAN":
|
|
3575
3864
|
return;
|
|
3576
3865
|
}
|
|
3577
3866
|
};
|
|
@@ -3610,6 +3899,8 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
3610
3899
|
break;
|
|
3611
3900
|
case "LITERAL_COL":
|
|
3612
3901
|
break;
|
|
3902
|
+
case "VARIABLE_COL":
|
|
3903
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
3613
3904
|
case "AGGREGATE":
|
|
3614
3905
|
if (col.arg.type !== "WILDCARD") walkArith(col.arg, "select");
|
|
3615
3906
|
break;
|
|
@@ -3625,6 +3916,9 @@ function collectRequiredFieldsByTable(stmt) {
|
|
|
3625
3916
|
case "STRFUNC_COL":
|
|
3626
3917
|
walkStringFunc(col.expr, "select");
|
|
3627
3918
|
break;
|
|
3919
|
+
case "SCALAR_VALUE_COL":
|
|
3920
|
+
walkScalar(col.expr, "select");
|
|
3921
|
+
break;
|
|
3628
3922
|
case "SCALAR_SUBQUERY_COL":
|
|
3629
3923
|
break;
|
|
3630
3924
|
case "WINDOW_COL":
|
|
@@ -3675,6 +3969,10 @@ function collectSelectOutputNames(columns) {
|
|
|
3675
3969
|
if (col.alias) names.add(col.alias);
|
|
3676
3970
|
continue;
|
|
3677
3971
|
}
|
|
3972
|
+
if (col.type === "SCALAR_VALUE_COL") {
|
|
3973
|
+
if (col.alias) names.add(col.alias);
|
|
3974
|
+
continue;
|
|
3975
|
+
}
|
|
3678
3976
|
if (col.type === "SCALAR_SUBQUERY_COL") {
|
|
3679
3977
|
names.add(col.alias ?? "(subquery)");
|
|
3680
3978
|
continue;
|
|
@@ -3697,14 +3995,32 @@ function arithNodeLabel(node) {
|
|
|
3697
3995
|
}
|
|
3698
3996
|
function stringFuncLabel(expr) {
|
|
3699
3997
|
const args = expr.args.map((a) => {
|
|
3700
|
-
if (a.type === "STRING") return `'${a.value}'`;
|
|
3701
|
-
if (a.type === "STRING_FUNC") return stringFuncLabel(a);
|
|
3702
3998
|
if (a.type === "AGG_REF") return aggregateSyntheticName(a.func, a.distinct, a.arg);
|
|
3703
3999
|
if (a.type === "AGG_ARITH") return "agg_arith";
|
|
3704
|
-
return
|
|
4000
|
+
return scalarValueLabel(a);
|
|
3705
4001
|
});
|
|
3706
4002
|
return `${expr.func}(${args.join(",")})`;
|
|
3707
4003
|
}
|
|
4004
|
+
function scalarValueLabel(expr) {
|
|
4005
|
+
switch (expr.type) {
|
|
4006
|
+
case "STRING":
|
|
4007
|
+
return `'${expr.value}'`;
|
|
4008
|
+
case "NUMBER":
|
|
4009
|
+
return numberLiteralText(expr);
|
|
4010
|
+
case "VARIABLE":
|
|
4011
|
+
return `@${expr.name}`;
|
|
4012
|
+
case "FIELD":
|
|
4013
|
+
return expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field;
|
|
4014
|
+
case "STRING_FUNC":
|
|
4015
|
+
return stringFuncLabel(expr);
|
|
4016
|
+
case "CASE_WHEN":
|
|
4017
|
+
return "case";
|
|
4018
|
+
case "SCALAR_ARITH":
|
|
4019
|
+
return `(${scalarValueLabel(expr.left)}${expr.op}${scalarValueLabel(expr.right)})`;
|
|
4020
|
+
case "CONCAT_OP":
|
|
4021
|
+
return `(${scalarValueLabel(expr.left)}||${scalarValueLabel(expr.right)})`;
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
3708
4024
|
function isAggregateSyntheticName(name) {
|
|
3709
4025
|
return /^(COUNT|SUM|AVG|MAX|MIN|GROUP_CONCAT)\(/i.test(name);
|
|
3710
4026
|
}
|
|
@@ -3760,6 +4076,7 @@ function stripCteAlias(where, alias) {
|
|
|
3760
4076
|
case "GROUP":
|
|
3761
4077
|
return { ...where, expr: stripCteAlias(where.expr, alias) };
|
|
3762
4078
|
case "EXISTS":
|
|
4079
|
+
case "BOOLEAN":
|
|
3763
4080
|
return where;
|
|
3764
4081
|
}
|
|
3765
4082
|
}
|
|
@@ -3797,6 +4114,7 @@ function extractAndLeaves(where, accept) {
|
|
|
3797
4114
|
case "NULL_CHECK":
|
|
3798
4115
|
case "NOT":
|
|
3799
4116
|
case "EXISTS":
|
|
4117
|
+
case "BOOLEAN":
|
|
3800
4118
|
return null;
|
|
3801
4119
|
}
|
|
3802
4120
|
}
|
|
@@ -3935,6 +4253,7 @@ function collectKlikes(where, out) {
|
|
|
3935
4253
|
case "BINARY":
|
|
3936
4254
|
case "NULL_CHECK":
|
|
3937
4255
|
case "EXISTS":
|
|
4256
|
+
case "BOOLEAN":
|
|
3938
4257
|
return;
|
|
3939
4258
|
}
|
|
3940
4259
|
}
|
|
@@ -3991,6 +4310,11 @@ function validateStatement(stmt) {
|
|
|
3991
4310
|
);
|
|
3992
4311
|
}
|
|
3993
4312
|
return;
|
|
4313
|
+
case "VALIDATE":
|
|
4314
|
+
if (containsKlike(stmt)) {
|
|
4315
|
+
throw new KlikeValidationError("KLIKE / NOT KLIKE \u306F VALIDATE \u306E WHERE / CHECK \u3067\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
4316
|
+
}
|
|
4317
|
+
return;
|
|
3994
4318
|
case "SHOW_APPS":
|
|
3995
4319
|
case "DESCRIBE":
|
|
3996
4320
|
case "DROP_TEMP_TABLE":
|
|
@@ -4078,6 +4402,8 @@ function isDescendantOf(root, target) {
|
|
|
4078
4402
|
case "NULL_CHECK":
|
|
4079
4403
|
case "EXISTS":
|
|
4080
4404
|
return false;
|
|
4405
|
+
case "BOOLEAN":
|
|
4406
|
+
return false;
|
|
4081
4407
|
}
|
|
4082
4408
|
}
|
|
4083
4409
|
function walkWithoutNestedSelects(node, visitWhere) {
|
|
@@ -4139,8 +4465,12 @@ function collectVariableRefs(node, refs) {
|
|
|
4139
4465
|
}
|
|
4140
4466
|
if (node !== null && typeof node === "object") {
|
|
4141
4467
|
const obj = node;
|
|
4142
|
-
|
|
4143
|
-
|
|
4468
|
+
const type = obj["type"];
|
|
4469
|
+
if ((type === "VARIABLE" || type === "VARIABLE_COL" || type === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") {
|
|
4470
|
+
refs.push({
|
|
4471
|
+
name: obj["name"],
|
|
4472
|
+
kind: type === "VARIABLE" ? "scalar" : type === "VARIABLE_COL" ? "select-column" : "array-in-list"
|
|
4473
|
+
});
|
|
4144
4474
|
return;
|
|
4145
4475
|
}
|
|
4146
4476
|
for (const v of Object.values(obj)) collectVariableRefs(v, refs);
|
|
@@ -4181,9 +4511,9 @@ function analyzeBatch(statements) {
|
|
|
4181
4511
|
const variableDefs = /* @__PURE__ */ new Map();
|
|
4182
4512
|
const variableOrder = [];
|
|
4183
4513
|
statements.forEach((stmt, index) => {
|
|
4184
|
-
const validationTable = "validationErrorTable" in stmt && stmt.validationErrorTable ? stmt.validationErrorTable : "onErrorSkip" in stmt && stmt.onErrorSkip ? stmt.errorTable ?? null : null;
|
|
4514
|
+
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;
|
|
4185
4515
|
if (statements.length === 1 && validationTable) {
|
|
4186
|
-
const message = "onErrorSkip" in stmt && stmt.onErrorSkip ? "ArgumentError: ON ERROR SKIP requires a batch." : "ArgumentError: VALIDATE ONLY INTO requires a batch.";
|
|
4516
|
+
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.";
|
|
4187
4517
|
throw new BatchAnalysisError(message, index);
|
|
4188
4518
|
}
|
|
4189
4519
|
const statementType = getStatementType(stmt);
|
|
@@ -4192,23 +4522,43 @@ function analyzeBatch(statements) {
|
|
|
4192
4522
|
const refs = /* @__PURE__ */ new Set();
|
|
4193
4523
|
const stmtAppIds = /* @__PURE__ */ new Set();
|
|
4194
4524
|
const dependsOn = /* @__PURE__ */ new Set();
|
|
4195
|
-
const variableRefs =
|
|
4525
|
+
const variableRefs = [];
|
|
4196
4526
|
collectVariableRefs(stmt, variableRefs);
|
|
4197
|
-
|
|
4198
|
-
|
|
4527
|
+
const referencedThisStatement = /* @__PURE__ */ new Set();
|
|
4528
|
+
for (const use of variableRefs) {
|
|
4529
|
+
const def = variableDefs.get(use.name);
|
|
4199
4530
|
if (def === void 0) {
|
|
4200
4531
|
throw new BatchAnalysisError(
|
|
4201
|
-
`ParseError: variable @${name} is not defined before statement ${index + 1}.`,
|
|
4532
|
+
`ParseError: variable @${use.name} is not defined before statement ${index + 1}.`,
|
|
4533
|
+
index
|
|
4534
|
+
);
|
|
4535
|
+
}
|
|
4536
|
+
if (def.kind === "scalar" && use.kind === "array-in-list") {
|
|
4537
|
+
throw new BatchAnalysisError(
|
|
4538
|
+
`ParseError: scalar variable @${use.name} cannot be used as IN @${use.name}; use IN (@${use.name}) instead.`,
|
|
4202
4539
|
index
|
|
4203
4540
|
);
|
|
4204
4541
|
}
|
|
4205
|
-
def.
|
|
4542
|
+
if (def.kind === "array" && use.kind !== "array-in-list") {
|
|
4543
|
+
throw new BatchAnalysisError(
|
|
4544
|
+
`ParseError: array variable @${use.name} can only be used as IN @${use.name}.`,
|
|
4545
|
+
index
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
if (!referencedThisStatement.has(use.name)) {
|
|
4549
|
+
def.referencedBy.push(index);
|
|
4550
|
+
referencedThisStatement.add(use.name);
|
|
4551
|
+
}
|
|
4206
4552
|
}
|
|
4207
4553
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
4208
4554
|
if (variableDefs.has(stmt.name)) {
|
|
4209
4555
|
throw new BatchAnalysisError(`ParseError: variable @${stmt.name} is already defined.`, index);
|
|
4210
4556
|
}
|
|
4211
|
-
variableDefs.set(stmt.name, {
|
|
4557
|
+
variableDefs.set(stmt.name, {
|
|
4558
|
+
index,
|
|
4559
|
+
kind: stmt.type === "SET_VARIABLE" && stmt.expr.type === "ARRAY" ? "array" : "scalar",
|
|
4560
|
+
referencedBy: []
|
|
4561
|
+
});
|
|
4212
4562
|
variableOrder.push(stmt.name);
|
|
4213
4563
|
if (variableOrder.length > MAX_BATCH_VARIABLES) {
|
|
4214
4564
|
throw new BatchAnalysisError(
|
|
@@ -4241,7 +4591,7 @@ function analyzeBatch(statements) {
|
|
|
4241
4591
|
dependsOn.add(at);
|
|
4242
4592
|
}
|
|
4243
4593
|
if (validationTable) {
|
|
4244
|
-
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : "fields" in stmt ? stmt.fields : [];
|
|
4594
|
+
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 : [];
|
|
4245
4595
|
const signature = JSON.stringify(payloadFields);
|
|
4246
4596
|
const at = defined.get(validationTable);
|
|
4247
4597
|
if (at === void 0) {
|
|
@@ -4316,6 +4666,7 @@ function analyzeBatch(statements) {
|
|
|
4316
4666
|
const needsCompleteInput = results.some((r) => r.requiresCompleteInput);
|
|
4317
4667
|
const variables = variableOrder.map((name) => ({
|
|
4318
4668
|
name,
|
|
4669
|
+
kind: variableDefs.get(name).kind,
|
|
4319
4670
|
referencedBy: [...variableDefs.get(name).referencedBy]
|
|
4320
4671
|
}));
|
|
4321
4672
|
return {
|
|
@@ -4592,6 +4943,7 @@ function whereNeedsFieldMetadata(where) {
|
|
|
4592
4943
|
case "GROUP":
|
|
4593
4944
|
return whereNeedsFieldMetadata(where.expr);
|
|
4594
4945
|
case "EXISTS":
|
|
4946
|
+
case "BOOLEAN":
|
|
4595
4947
|
return false;
|
|
4596
4948
|
}
|
|
4597
4949
|
}
|
|
@@ -4616,6 +4968,7 @@ function explainNeedsAppMetadata(statement) {
|
|
|
4616
4968
|
seen.add(node);
|
|
4617
4969
|
if (Array.isArray(node)) return node.some(visit);
|
|
4618
4970
|
const item = node;
|
|
4971
|
+
if (item["type"] === "VALIDATE") return true;
|
|
4619
4972
|
if (item["type"] === "SELECT" && selectNeedsOwnMetadata(node)) {
|
|
4620
4973
|
return true;
|
|
4621
4974
|
}
|
|
@@ -4647,6 +5000,45 @@ function evalArithExpr(expr, row) {
|
|
|
4647
5000
|
return r !== 0 ? l % r : NaN;
|
|
4648
5001
|
}
|
|
4649
5002
|
}
|
|
5003
|
+
function evalScalarValueExpr(expr, row) {
|
|
5004
|
+
switch (expr.type) {
|
|
5005
|
+
case "STRING":
|
|
5006
|
+
return expr.value;
|
|
5007
|
+
case "NUMBER":
|
|
5008
|
+
return expr.value;
|
|
5009
|
+
case "FIELD":
|
|
5010
|
+
return resolveFieldRef(row, expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field);
|
|
5011
|
+
case "VARIABLE":
|
|
5012
|
+
throw new Error(`ArgumentError: unresolved variable @${expr.name} reached scalar evaluator.`);
|
|
5013
|
+
case "STRING_FUNC":
|
|
5014
|
+
return evalStringFunc(expr, row);
|
|
5015
|
+
case "CASE_WHEN":
|
|
5016
|
+
return evalCaseWhen(expr, row);
|
|
5017
|
+
case "CONCAT_OP": {
|
|
5018
|
+
return evalStringFunc({
|
|
5019
|
+
type: "STRING_FUNC",
|
|
5020
|
+
func: "CONCAT",
|
|
5021
|
+
args: [expr.left, expr.right]
|
|
5022
|
+
}, row);
|
|
5023
|
+
}
|
|
5024
|
+
case "SCALAR_ARITH": {
|
|
5025
|
+
const left = Number(evalScalarValueExpr(expr.left, row));
|
|
5026
|
+
const right = Number(evalScalarValueExpr(expr.right, row));
|
|
5027
|
+
switch (expr.op) {
|
|
5028
|
+
case "+":
|
|
5029
|
+
return left + right;
|
|
5030
|
+
case "-":
|
|
5031
|
+
return left - right;
|
|
5032
|
+
case "*":
|
|
5033
|
+
return left * right;
|
|
5034
|
+
case "/":
|
|
5035
|
+
return right !== 0 ? left / right : NaN;
|
|
5036
|
+
case "%":
|
|
5037
|
+
return right !== 0 ? left % right : NaN;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
4650
5042
|
function applyRoundOp(op, num, digits) {
|
|
4651
5043
|
const factor = Math.pow(10, digits);
|
|
4652
5044
|
const raw = Math[op](num * factor) / factor;
|
|
@@ -5049,12 +5441,9 @@ function formatWithComma(num, digits) {
|
|
|
5049
5441
|
return decStr ? `${intFmt}.${decStr}` : intFmt;
|
|
5050
5442
|
}
|
|
5051
5443
|
function evalStringFuncArg(arg, row) {
|
|
5052
|
-
if (arg.type === "STRING") return arg.value;
|
|
5053
|
-
if (arg.type === "STRING_FUNC") return evalStringFunc(arg, row);
|
|
5054
|
-
if (arg.type === "FIELD_REF") return resolveFieldRef(row, arg.field);
|
|
5055
|
-
if (arg.type === "NUMBER") return numberLiteralText(arg);
|
|
5056
5444
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return "";
|
|
5057
|
-
|
|
5445
|
+
if (arg.type === "NUMBER") return numberLiteralText(arg);
|
|
5446
|
+
return String(evalScalarValueExpr(arg, row));
|
|
5058
5447
|
}
|
|
5059
5448
|
function resolveFieldRef(row, field) {
|
|
5060
5449
|
const direct = row[field];
|
|
@@ -5070,6 +5459,8 @@ function resolveFieldRef(row, field) {
|
|
|
5070
5459
|
// src/engine/evalWhere.ts
|
|
5071
5460
|
function evalWhere(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2) {
|
|
5072
5461
|
switch (expr.type) {
|
|
5462
|
+
case "BOOLEAN":
|
|
5463
|
+
return expr.value;
|
|
5073
5464
|
case "BINARY":
|
|
5074
5465
|
return evalBinary(expr, row, resolveFieldType, appliedKlikes, resolveFieldSemantics2);
|
|
5075
5466
|
case "NULL_CHECK":
|
|
@@ -5240,6 +5631,8 @@ function resolveValue(value, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
5240
5631
|
switch (value.type) {
|
|
5241
5632
|
case "VARIABLE":
|
|
5242
5633
|
throw new Error(`ParseError: unresolved batch variable @${value.name}.`);
|
|
5634
|
+
case "VARIABLE_IN_LIST":
|
|
5635
|
+
throw new Error(`ParseError: unresolved batch array variable @${value.name}.`);
|
|
5243
5636
|
case "STRING":
|
|
5244
5637
|
return value.value;
|
|
5245
5638
|
case "NUMBER":
|
|
@@ -5277,10 +5670,13 @@ function evalCaseWhen(expr, row, resolveFieldType, resolveFieldSemantics2) {
|
|
|
5277
5670
|
}
|
|
5278
5671
|
function evalCaseResult(result, row) {
|
|
5279
5672
|
if (result.type === "ARRAY") return result.elements.map((e) => e.value).join(",");
|
|
5280
|
-
if (result.type === "
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5673
|
+
if (result.type === "FIELD_REF") {
|
|
5674
|
+
return row[result.field] ?? "";
|
|
5675
|
+
}
|
|
5676
|
+
if (result.type === "ARITH") {
|
|
5677
|
+
return String(evalArithExpr(result, row));
|
|
5678
|
+
}
|
|
5679
|
+
return String(evalScalarValueExpr(result, row));
|
|
5284
5680
|
}
|
|
5285
5681
|
function resolveKintoneFunc(name) {
|
|
5286
5682
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -5324,6 +5720,63 @@ function matchLike(value, pattern) {
|
|
|
5324
5720
|
return regex.test(value);
|
|
5325
5721
|
}
|
|
5326
5722
|
|
|
5723
|
+
// src/core/dmlCustomCheck.ts
|
|
5724
|
+
function collectCheckFieldRefs(groups) {
|
|
5725
|
+
return collectRefs2(groups);
|
|
5726
|
+
}
|
|
5727
|
+
function collectCheckComparisonFieldRefs(groups) {
|
|
5728
|
+
return collectRefs2(groups.flatMap((group) => group.rules.map((rule) => rule.condition)));
|
|
5729
|
+
}
|
|
5730
|
+
function collectRefs2(root) {
|
|
5731
|
+
const refs = [];
|
|
5732
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5733
|
+
const visit = (node) => {
|
|
5734
|
+
if (Array.isArray(node)) {
|
|
5735
|
+
node.forEach(visit);
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
5738
|
+
if (node === null || typeof node !== "object") return;
|
|
5739
|
+
const obj = node;
|
|
5740
|
+
if (obj.type === "EXISTS" || obj.type === "SUBQUERY_IN_LIST" || obj.type === "SCALAR_SUBQUERY") {
|
|
5741
|
+
throw customCheckParseError("CHECK \u306B\u30B5\u30D6\u30AF\u30A8\u30EA\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
5742
|
+
}
|
|
5743
|
+
if (obj.type === "FIELD" && typeof obj.field === "string") {
|
|
5744
|
+
add(typeof obj.tableAlias === "string" ? obj.tableAlias : null, obj.field);
|
|
5745
|
+
} else if (obj.type === "FIELD_REF" && typeof obj.field === "string") {
|
|
5746
|
+
const dot = obj.field.indexOf(".");
|
|
5747
|
+
add(dot > 0 ? obj.field.slice(0, dot) : null, dot > 0 ? obj.field.slice(dot + 1) : obj.field);
|
|
5748
|
+
}
|
|
5749
|
+
for (const value of Object.values(obj)) visit(value);
|
|
5750
|
+
};
|
|
5751
|
+
const add = (tableAlias, field) => {
|
|
5752
|
+
if (/^(COUNT|SUM|AVG|MIN|MAX|GROUP_CONCAT)\(/i.test(field)) {
|
|
5753
|
+
throw customCheckParseError("CHECK \u306B\u96C6\u7D04\u95A2\u6570\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
5754
|
+
}
|
|
5755
|
+
const key = `${tableAlias ?? ""}\0${field}`;
|
|
5756
|
+
if (!seen.has(key)) {
|
|
5757
|
+
seen.add(key);
|
|
5758
|
+
refs.push({ tableAlias, field });
|
|
5759
|
+
}
|
|
5760
|
+
};
|
|
5761
|
+
visit(root);
|
|
5762
|
+
return refs;
|
|
5763
|
+
}
|
|
5764
|
+
function customCheckParseError(message) {
|
|
5765
|
+
return new ParseError(message, { kind: "EOF" /* EOF */, value: "CHECK", pos: 0 });
|
|
5766
|
+
}
|
|
5767
|
+
function evaluateCustomChecks(groups, row, resolveFieldType) {
|
|
5768
|
+
const errors = [];
|
|
5769
|
+
groups.forEach((group, groupIndex) => {
|
|
5770
|
+
for (const rule of group.rules) {
|
|
5771
|
+
if (!evalWhere(rule.condition, row, resolveFieldType)) continue;
|
|
5772
|
+
const value = evalScalarValueExpr(rule.message, row);
|
|
5773
|
+
errors.push({ groupIndex, message: value == null ? "" : String(value) });
|
|
5774
|
+
break;
|
|
5775
|
+
}
|
|
5776
|
+
});
|
|
5777
|
+
return errors;
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5327
5780
|
// src/converter/dmlToKintone.ts
|
|
5328
5781
|
function assertDmlWhereIsSafe(where) {
|
|
5329
5782
|
if (whereHasKlike(where)) {
|
|
@@ -5361,10 +5814,11 @@ function buildInsertRecord(fields, row, fieldTypes) {
|
|
|
5361
5814
|
}
|
|
5362
5815
|
function updateToGetQuery(stmt) {
|
|
5363
5816
|
assertDmlWhereIsSafe(stmt.where);
|
|
5817
|
+
const checkFields = collectUpdateCheckTargetFields(stmt);
|
|
5364
5818
|
return {
|
|
5365
5819
|
app: stmt.appId,
|
|
5366
5820
|
query: whereToKintone(stmt.where),
|
|
5367
|
-
fields: ["$id"],
|
|
5821
|
+
fields: ["$id", ...checkFields],
|
|
5368
5822
|
totalCount: false
|
|
5369
5823
|
};
|
|
5370
5824
|
}
|
|
@@ -5378,19 +5832,19 @@ function updateToPutBatches(stmt, ids, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
|
5378
5832
|
function buildUpdateRecord(assignments, fieldTypes) {
|
|
5379
5833
|
const record = {};
|
|
5380
5834
|
for (const { field, value } of assignments) {
|
|
5381
|
-
if (value.type === "ARITH" || value.type === "CASE_VALUE" || value.type === "STRING_FUNC" || value.type === "SOURCE_FIELD") continue;
|
|
5835
|
+
if (value.type === "ARITH" || value.type === "SCALAR_ARITH" || value.type === "CONCAT_OP" || value.type === "CASE_VALUE" || value.type === "STRING_FUNC" || value.type === "SOURCE_FIELD") continue;
|
|
5382
5836
|
record[field] = { value: toKintoneValue(value, fieldTypes.get(field)) };
|
|
5383
5837
|
}
|
|
5384
5838
|
return record;
|
|
5385
5839
|
}
|
|
5386
5840
|
function hasArithAssignment(stmt) {
|
|
5387
5841
|
return stmt.assignments.some(
|
|
5388
|
-
(a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE"
|
|
5842
|
+
(a) => a.value.type === "ARITH" || a.value.type === "SCALAR_ARITH" || a.value.type === "CONCAT_OP" || a.value.type === "CASE_VALUE"
|
|
5389
5843
|
);
|
|
5390
5844
|
}
|
|
5391
5845
|
function hasRowDependentAssignment(stmt) {
|
|
5392
5846
|
return stmt.assignments.some(
|
|
5393
|
-
(a) => a.value.type === "ARITH" || a.value.type === "CASE_VALUE" || a.value.type === "STRING_FUNC"
|
|
5847
|
+
(a) => a.value.type === "ARITH" || a.value.type === "SCALAR_ARITH" || a.value.type === "CONCAT_OP" || a.value.type === "CASE_VALUE" || a.value.type === "STRING_FUNC"
|
|
5394
5848
|
);
|
|
5395
5849
|
}
|
|
5396
5850
|
function updateToGetQueryForArith(stmt) {
|
|
@@ -5399,12 +5853,15 @@ function updateToGetQueryForArith(stmt) {
|
|
|
5399
5853
|
for (const { value } of stmt.assignments) {
|
|
5400
5854
|
if (value.type === "ARITH") {
|
|
5401
5855
|
collectArithFields2(value, refFields);
|
|
5856
|
+
} else if (value.type === "SCALAR_ARITH" || value.type === "CONCAT_OP") {
|
|
5857
|
+
collectScalarValueFields2(value, refFields);
|
|
5402
5858
|
} else if (value.type === "STRING_FUNC") {
|
|
5403
5859
|
collectStringFuncFields2(value, refFields);
|
|
5404
5860
|
} else if (value.type === "CASE_VALUE") {
|
|
5405
5861
|
collectCaseFields(value.expr, refFields);
|
|
5406
5862
|
}
|
|
5407
5863
|
}
|
|
5864
|
+
collectUpdateCheckTargetFields(stmt).forEach((field) => refFields.add(field));
|
|
5408
5865
|
return {
|
|
5409
5866
|
app: stmt.appId,
|
|
5410
5867
|
query: whereToKintone(stmt.where),
|
|
@@ -5425,16 +5882,27 @@ function collectStringFuncFields2(expr, out) {
|
|
|
5425
5882
|
for (const arg of expr.args) collectStringFuncArgFields2(arg, out);
|
|
5426
5883
|
}
|
|
5427
5884
|
function collectStringFuncArgFields2(arg, out) {
|
|
5428
|
-
if (arg.type === "STRING") return;
|
|
5429
|
-
if (arg.type === "STRING_FUNC") {
|
|
5430
|
-
collectStringFuncFields2(arg, out);
|
|
5431
|
-
return;
|
|
5432
|
-
}
|
|
5433
5885
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
5434
5886
|
collectAggOperandFields2(arg, out);
|
|
5435
5887
|
return;
|
|
5436
5888
|
}
|
|
5437
|
-
|
|
5889
|
+
collectScalarValueFields2(arg, out);
|
|
5890
|
+
}
|
|
5891
|
+
function collectScalarValueFields2(expr, out) {
|
|
5892
|
+
if (expr.type === "FIELD") {
|
|
5893
|
+
out.add(expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field);
|
|
5894
|
+
return;
|
|
5895
|
+
}
|
|
5896
|
+
if (expr.type === "STRING_FUNC") {
|
|
5897
|
+
collectStringFuncFields2(expr, out);
|
|
5898
|
+
return;
|
|
5899
|
+
}
|
|
5900
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
5901
|
+
collectScalarValueFields2(expr.left, out);
|
|
5902
|
+
collectScalarValueFields2(expr.right, out);
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
if (expr.type === "CASE_WHEN") collectCaseFields(expr, out);
|
|
5438
5906
|
}
|
|
5439
5907
|
function collectAggOperandFields2(node, out) {
|
|
5440
5908
|
if (node.type === "AGG_REF") {
|
|
@@ -5447,9 +5915,12 @@ function collectAggOperandFields2(node, out) {
|
|
|
5447
5915
|
}
|
|
5448
5916
|
}
|
|
5449
5917
|
function collectCaseResultFields(result, out) {
|
|
5450
|
-
if (result.type === "STRING") return;
|
|
5451
5918
|
if (result.type === "ARRAY") return;
|
|
5452
|
-
|
|
5919
|
+
if (result.type === "FIELD_REF" || result.type === "ARITH") {
|
|
5920
|
+
collectArithNode2(result, out);
|
|
5921
|
+
return;
|
|
5922
|
+
}
|
|
5923
|
+
collectScalarValueFields2(result, out);
|
|
5453
5924
|
}
|
|
5454
5925
|
function collectCaseFields(expr, out) {
|
|
5455
5926
|
for (const branch of expr.branches) {
|
|
@@ -5477,6 +5948,9 @@ function collectConditionFields(expr, out) {
|
|
|
5477
5948
|
case "GROUP":
|
|
5478
5949
|
collectConditionFields(expr.expr, out);
|
|
5479
5950
|
break;
|
|
5951
|
+
case "EXISTS":
|
|
5952
|
+
case "BOOLEAN":
|
|
5953
|
+
break;
|
|
5480
5954
|
}
|
|
5481
5955
|
}
|
|
5482
5956
|
function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new Map()) {
|
|
@@ -5487,6 +5961,8 @@ function updateToPutBatchesArith(stmt, records, fieldTypes = /* @__PURE__ */ new
|
|
|
5487
5961
|
for (const { field, value } of stmt.assignments) {
|
|
5488
5962
|
if (value.type === "ARITH") {
|
|
5489
5963
|
record[field] = { value: String(evalArith(value, raw)) };
|
|
5964
|
+
} else if (value.type === "SCALAR_ARITH" || value.type === "CONCAT_OP") {
|
|
5965
|
+
record[field] = { value: String(evalScalarValueExpr(value, row)) };
|
|
5490
5966
|
} else if (value.type === "STRING_FUNC") {
|
|
5491
5967
|
record[field] = { value: evalStringFunc(value, row) };
|
|
5492
5968
|
} else if (value.type === "CASE_VALUE") {
|
|
@@ -5541,6 +6017,8 @@ function updateFromToPutBatches(stmt, matched, fieldTypes = /* @__PURE__ */ new
|
|
|
5541
6017
|
throw new DmlConvertError("UPDATE ... FROM \u306E SET \u3067\u306F\u6587\u5B57\u5217\u95A2\u6570\u3092\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
|
|
5542
6018
|
} else if (value.type === "ARITH") {
|
|
5543
6019
|
record[field] = { value: String(evalArith(value, target)) };
|
|
6020
|
+
} else if (value.type === "SCALAR_ARITH" || value.type === "CONCAT_OP") {
|
|
6021
|
+
record[field] = { value: String(evalScalarValueExpr(value, targetRow)) };
|
|
5544
6022
|
} else if (value.type === "CASE_VALUE") {
|
|
5545
6023
|
record[field] = { value: evalCaseWhenValue(value.expr, targetRow, fieldType) };
|
|
5546
6024
|
} else {
|
|
@@ -5651,7 +6129,15 @@ function evalCaseResultValue(result, row, fieldType) {
|
|
|
5651
6129
|
if (result.type === "STRING_FUNC") {
|
|
5652
6130
|
return evalStringFunc(result, row);
|
|
5653
6131
|
}
|
|
5654
|
-
|
|
6132
|
+
if (result.type === "FIELD_REF" || result.type === "ARITH") {
|
|
6133
|
+
return String(evalArithExpr(result, row));
|
|
6134
|
+
}
|
|
6135
|
+
return String(evalScalarValueExpr(result, row));
|
|
6136
|
+
}
|
|
6137
|
+
function collectUpdateCheckTargetFields(stmt) {
|
|
6138
|
+
if (!stmt.checkGroups) return [];
|
|
6139
|
+
const targetAlias = `app${stmt.appId}`.toLowerCase();
|
|
6140
|
+
return [...new Set(collectCheckFieldRefs(stmt.checkGroups).filter((ref) => ref.tableAlias === null || ref.tableAlias.toLowerCase() === targetAlias).map((ref) => ref.field).filter((field) => field !== "$id"))];
|
|
5655
6141
|
}
|
|
5656
6142
|
function evalCaseWhenValue(expr, row, fieldType) {
|
|
5657
6143
|
for (const branch of expr.branches) {
|
|
@@ -5680,6 +6166,8 @@ function convertDmlSqlValue(value, fieldType) {
|
|
|
5680
6166
|
switch (value.type) {
|
|
5681
6167
|
case "VARIABLE":
|
|
5682
6168
|
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u30D0\u30C3\u30C1\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
6169
|
+
case "VARIABLE_IN_LIST":
|
|
6170
|
+
throw new DmlConvertError(`\u672A\u89E3\u6C7A\u306E\u914D\u5217\u5909\u6570 @${value.name} \u304C\u3042\u308A\u307E\u3059`);
|
|
5683
6171
|
case "STRING":
|
|
5684
6172
|
return convertString2(value.value, fieldType);
|
|
5685
6173
|
case "NUMBER":
|
|
@@ -6204,7 +6692,7 @@ function applyFilter(rows, where, resolveFieldType, appliedKlikes, resolveFieldS
|
|
|
6204
6692
|
}
|
|
6205
6693
|
function hasAggregateColumns(columns) {
|
|
6206
6694
|
return columns.some(
|
|
6207
|
-
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr)
|
|
6695
|
+
(c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL" || c.type === "STRFUNC_COL" && hasAggregateInStringFuncExpr2(c.expr) || c.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(c.expr)
|
|
6208
6696
|
);
|
|
6209
6697
|
}
|
|
6210
6698
|
function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
|
|
@@ -6241,6 +6729,10 @@ function applyGroupBy(rows, groupByKeys, columns, resolveAggSortKind) {
|
|
|
6241
6729
|
const outputKey = col.alias ?? stringFuncDefaultKey(col.expr);
|
|
6242
6730
|
const resolvedExpr = resolveAggInStringFuncExpr(col.expr, groupRows, resolveAggSortKind);
|
|
6243
6731
|
outRow[outputKey] = evalStringFunc(resolvedExpr, outRow);
|
|
6732
|
+
} else if (col.type === "SCALAR_VALUE_COL" && scalarValueHasAggregate2(col.expr)) {
|
|
6733
|
+
const outputKey = col.alias ?? scalarValueDefaultKey(col.expr);
|
|
6734
|
+
const resolvedExpr = resolveAggInScalarValue(col.expr, groupRows, resolveAggSortKind);
|
|
6735
|
+
outRow[outputKey] = String(evalScalarValueExpr(resolvedExpr, outRow));
|
|
6244
6736
|
}
|
|
6245
6737
|
}
|
|
6246
6738
|
result.push(outRow);
|
|
@@ -6511,6 +7003,8 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
|
|
|
6511
7003
|
const out = {};
|
|
6512
7004
|
for (const [colIdx, col] of columns.entries()) {
|
|
6513
7005
|
switch (col.type) {
|
|
7006
|
+
case "VARIABLE_COL":
|
|
7007
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
6514
7008
|
case "WILDCARD":
|
|
6515
7009
|
Object.assign(out, stripParentShortcutColumns(row));
|
|
6516
7010
|
break;
|
|
@@ -6571,6 +7065,13 @@ function project(rows, columns, scalarCache, resolveFieldType, sourceColumns, re
|
|
|
6571
7065
|
if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
|
|
6572
7066
|
break;
|
|
6573
7067
|
}
|
|
7068
|
+
case "SCALAR_VALUE_COL": {
|
|
7069
|
+
const key = outputKeys?.[colIdx] ?? col.alias ?? scalarValueDefaultKey(col.expr);
|
|
7070
|
+
const srcKey = scalarValueDefaultKey(col.expr);
|
|
7071
|
+
out[key] = scalarValueHasAggregate2(col.expr) ? row[col.alias ?? srcKey] ?? row[srcKey] ?? "" : String(evalScalarValueExpr(col.expr, row));
|
|
7072
|
+
if (outputKeys === null && rowIdx === 0) orderedKeys.push(key);
|
|
7073
|
+
break;
|
|
7074
|
+
}
|
|
6574
7075
|
case "SCALAR_SUBQUERY_COL": {
|
|
6575
7076
|
const key = outputKeys?.[colIdx] ?? col.alias ?? "(subquery)";
|
|
6576
7077
|
out[key] = scalarCache?.get(colIdx) ?? "";
|
|
@@ -6607,6 +7108,8 @@ function computeExplicitOutputKeys(columns, defaultFieldKeys) {
|
|
|
6607
7108
|
}
|
|
6608
7109
|
function computeOutputKey(col, colIdx, defaultFieldKeys) {
|
|
6609
7110
|
switch (col.type) {
|
|
7111
|
+
case "VARIABLE_COL":
|
|
7112
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
6610
7113
|
case "FIELD":
|
|
6611
7114
|
return col.alias ?? defaultFieldKeys.get(colIdx) ?? col.field;
|
|
6612
7115
|
case "LITERAL_COL":
|
|
@@ -6621,6 +7124,8 @@ function computeOutputKey(col, colIdx, defaultFieldKeys) {
|
|
|
6621
7124
|
return col.alias ?? "case";
|
|
6622
7125
|
case "STRFUNC_COL":
|
|
6623
7126
|
return col.alias ?? stringFuncDefaultKey(col.expr);
|
|
7127
|
+
case "SCALAR_VALUE_COL":
|
|
7128
|
+
return col.alias ?? scalarValueDefaultKey(col.expr);
|
|
6624
7129
|
case "SCALAR_SUBQUERY_COL":
|
|
6625
7130
|
return col.alias ?? "(subquery)";
|
|
6626
7131
|
case "WINDOW_COL":
|
|
@@ -6676,18 +7181,49 @@ function arithColDefaultKey(expr) {
|
|
|
6676
7181
|
}
|
|
6677
7182
|
function stringFuncDefaultKey(expr) {
|
|
6678
7183
|
const argStrs = expr.args.map((a) => {
|
|
6679
|
-
if (a.type === "STRING") return `'${a.value}'`;
|
|
6680
|
-
if (a.type === "STRING_FUNC") return stringFuncDefaultKey(a);
|
|
6681
7184
|
if (a.type === "AGG_REF" || a.type === "AGG_ARITH") return aggArithDefaultKey(a);
|
|
6682
|
-
return
|
|
7185
|
+
return scalarValueDefaultKey(a);
|
|
6683
7186
|
});
|
|
6684
7187
|
return `${expr.func}(${argStrs.join(",")})`;
|
|
6685
7188
|
}
|
|
7189
|
+
function scalarValueDefaultKey(expr) {
|
|
7190
|
+
switch (expr.type) {
|
|
7191
|
+
case "STRING":
|
|
7192
|
+
return `'${expr.value}'`;
|
|
7193
|
+
case "NUMBER":
|
|
7194
|
+
return numberLiteralText(expr);
|
|
7195
|
+
case "VARIABLE":
|
|
7196
|
+
return `@${expr.name}`;
|
|
7197
|
+
case "FIELD":
|
|
7198
|
+
return expr.tableAlias ? `${expr.tableAlias}.${expr.field}` : expr.field;
|
|
7199
|
+
case "STRING_FUNC":
|
|
7200
|
+
return stringFuncDefaultKey(expr);
|
|
7201
|
+
case "CASE_WHEN":
|
|
7202
|
+
return "case";
|
|
7203
|
+
case "SCALAR_ARITH":
|
|
7204
|
+
return `${scalarValueDefaultKey(expr.left)}${expr.op}${scalarValueDefaultKey(expr.right)}`;
|
|
7205
|
+
case "CONCAT_OP":
|
|
7206
|
+
return `${scalarValueDefaultKey(expr.left)}||${scalarValueDefaultKey(expr.right)}`;
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
6686
7209
|
function hasAggregateInStringFuncArg(arg) {
|
|
6687
7210
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
|
|
6688
|
-
|
|
7211
|
+
return scalarValueHasAggregate2(arg);
|
|
7212
|
+
}
|
|
7213
|
+
function scalarValueHasAggregate2(expr) {
|
|
7214
|
+
if (expr.type === "STRING_FUNC") return hasAggregateInStringFuncExpr2(expr);
|
|
7215
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
7216
|
+
return scalarValueHasAggregate2(expr.left) || scalarValueHasAggregate2(expr.right);
|
|
7217
|
+
}
|
|
7218
|
+
if (expr.type === "CASE_WHEN") {
|
|
7219
|
+
return expr.branches.some((branch) => caseResultHasAggregate2(branch.result)) || expr.elseResult !== null && caseResultHasAggregate2(expr.elseResult);
|
|
7220
|
+
}
|
|
6689
7221
|
return false;
|
|
6690
7222
|
}
|
|
7223
|
+
function caseResultHasAggregate2(result) {
|
|
7224
|
+
if (result.type === "ARRAY" || result.type === "FIELD_REF" || result.type === "ARITH") return false;
|
|
7225
|
+
return scalarValueHasAggregate2(result);
|
|
7226
|
+
}
|
|
6691
7227
|
function hasAggregateInStringFuncExpr2(expr) {
|
|
6692
7228
|
return expr.args.some((arg) => hasAggregateInStringFuncArg(arg));
|
|
6693
7229
|
}
|
|
@@ -6703,7 +7239,18 @@ function resolveAggInStringFuncArg(arg, rows, resolveAggSortKind) {
|
|
|
6703
7239
|
if (arg.type === "STRING_FUNC") {
|
|
6704
7240
|
return resolveAggInStringFuncExpr(arg, rows, resolveAggSortKind);
|
|
6705
7241
|
}
|
|
6706
|
-
return arg;
|
|
7242
|
+
return resolveAggInScalarValue(arg, rows, resolveAggSortKind);
|
|
7243
|
+
}
|
|
7244
|
+
function resolveAggInScalarValue(expr, rows, resolveAggSortKind) {
|
|
7245
|
+
if (expr.type === "STRING_FUNC") return resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind);
|
|
7246
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
7247
|
+
return {
|
|
7248
|
+
...expr,
|
|
7249
|
+
left: resolveAggInScalarValue(expr.left, rows, resolveAggSortKind),
|
|
7250
|
+
right: resolveAggInScalarValue(expr.right, rows, resolveAggSortKind)
|
|
7251
|
+
};
|
|
7252
|
+
}
|
|
7253
|
+
return expr;
|
|
6707
7254
|
}
|
|
6708
7255
|
function resolveAggInStringFuncExpr(expr, rows, resolveAggSortKind) {
|
|
6709
7256
|
return {
|
|
@@ -6724,7 +7271,7 @@ function deriveOutputOrderSemantics(columns) {
|
|
|
6724
7271
|
} else if (column.func === "GROUP_CONCAT") {
|
|
6725
7272
|
result.set(column.alias, syntheticSemantics("string"));
|
|
6726
7273
|
}
|
|
6727
|
-
} else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL") {
|
|
7274
|
+
} else if (column.type === "LITERAL_COL" || column.type === "CASE_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "SCALAR_VALUE_COL") {
|
|
6728
7275
|
result.set(column.alias, syntheticSemantics("string"));
|
|
6729
7276
|
} else if (column.type === "STRFUNC_COL") {
|
|
6730
7277
|
result.set(column.alias, syntheticSemantics(NUMERIC_ORDER_FUNCTIONS.has(column.expr.func) ? "number" : "string"));
|
|
@@ -6997,19 +7544,20 @@ var VALIDATION_META_COLUMNS = [
|
|
|
6997
7544
|
"$err_code",
|
|
6998
7545
|
"$err_message"
|
|
6999
7546
|
];
|
|
7000
|
-
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision) {
|
|
7547
|
+
function validateDmlCandidates(candidates, operation, payloadFields, targetFields, fieldInfos, statementNumber, numberPrecision, checkGroups = [], validateMissingCreateFields = true, includePreErrors = true) {
|
|
7001
7548
|
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
7002
7549
|
const errors = [];
|
|
7003
7550
|
const invalid = /* @__PURE__ */ new Set();
|
|
7551
|
+
let firstEvaluationError;
|
|
7004
7552
|
for (const candidate of candidates) {
|
|
7005
7553
|
candidate.record ??= {};
|
|
7006
|
-
const rowErrors = [...candidate.preErrors];
|
|
7554
|
+
const rowErrors = includePreErrors ? [...candidate.preErrors] : [];
|
|
7007
7555
|
for (const code of targetFields) {
|
|
7008
7556
|
const result = validateAndNormalizeDmlValue(candidate.payload.get(code), infoByCode.get(code), numberPrecision);
|
|
7009
7557
|
if (!result.ok) rowErrors.push({ field: code, code: result.code, message: result.message });
|
|
7010
7558
|
else candidate.record[code] = { value: result.value };
|
|
7011
7559
|
}
|
|
7012
|
-
if (candidate.mode === "create") {
|
|
7560
|
+
if (validateMissingCreateFields && candidate.mode === "create") {
|
|
7013
7561
|
for (const info of fieldInfos) {
|
|
7014
7562
|
if (info.inSubtable) continue;
|
|
7015
7563
|
if (candidate.payload.has(info.code)) continue;
|
|
@@ -7031,6 +7579,23 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
7031
7579
|
}
|
|
7032
7580
|
}
|
|
7033
7581
|
}
|
|
7582
|
+
if (checkGroups.length > 0) {
|
|
7583
|
+
const row = candidate.evaluationRow ?? Object.fromEntries(
|
|
7584
|
+
[...candidate.payload].map(([field, value]) => [field, renderValidationValue(value)])
|
|
7585
|
+
);
|
|
7586
|
+
const types = candidate.evaluationFieldTypes;
|
|
7587
|
+
const resolveType = (field) => {
|
|
7588
|
+
const qualified = field.tableAlias ? `${field.tableAlias}.${field.field}` : field.field;
|
|
7589
|
+
return types?.get(qualified) ?? types?.get(field.field);
|
|
7590
|
+
};
|
|
7591
|
+
try {
|
|
7592
|
+
for (const custom of evaluateCustomChecks(checkGroups, row, resolveType)) {
|
|
7593
|
+
rowErrors.push({ field: "", code: "ERR_CHECK", message: custom.message });
|
|
7594
|
+
}
|
|
7595
|
+
} catch (error) {
|
|
7596
|
+
firstEvaluationError ??= error;
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7034
7599
|
if (rowErrors.length > 0) invalid.add(candidate.rowNumber);
|
|
7035
7600
|
for (const error of rowErrors) {
|
|
7036
7601
|
const row = {};
|
|
@@ -7044,6 +7609,7 @@ function validateDmlCandidates(candidates, operation, payloadFields, targetField
|
|
|
7044
7609
|
errors.push(row);
|
|
7045
7610
|
}
|
|
7046
7611
|
}
|
|
7612
|
+
if (firstEvaluationError !== void 0) throw firstEvaluationError;
|
|
7047
7613
|
return { errors, invalidRows: invalid.size, invalidRowNumbers: invalid };
|
|
7048
7614
|
}
|
|
7049
7615
|
function renderValidationValue(value) {
|
|
@@ -7058,6 +7624,11 @@ function renderValidationValue(value) {
|
|
|
7058
7624
|
return String(value);
|
|
7059
7625
|
}
|
|
7060
7626
|
|
|
7627
|
+
// src/core/existingRecordValidation.ts
|
|
7628
|
+
function renderExistingValidationValue(raw, fieldType) {
|
|
7629
|
+
return isEmptyDmlValue(raw) ? "" : renderValidationValue(normalizeRaw(raw, fieldType));
|
|
7630
|
+
}
|
|
7631
|
+
|
|
7061
7632
|
// src/core/optimization/whereCapability.ts
|
|
7062
7633
|
var RANGE_AND_EQUALITY = ["=", "!=", ">", "<", ">=", "<="];
|
|
7063
7634
|
var EQUALITY_IN = ["=", "!=", "in", "not in"];
|
|
@@ -7132,6 +7703,8 @@ function classifyWhereCapability(where, resolveField2) {
|
|
|
7132
7703
|
}
|
|
7133
7704
|
function classifyNode(where, resolveField2) {
|
|
7134
7705
|
switch (where.type) {
|
|
7706
|
+
case "BOOLEAN":
|
|
7707
|
+
return { capability: "LOCAL_ONLY", reasons: [{ code: "WHERE_EXPRESSION_LOCAL_ONLY" }] };
|
|
7135
7708
|
case "BINARY":
|
|
7136
7709
|
return classifyBinary(where.op, where.left, where.right.type, resolveField2);
|
|
7137
7710
|
case "NULL_CHECK":
|
|
@@ -7458,6 +8031,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
7458
8031
|
throw new Error("ArgumentError: ON ERROR SKIP requires a batch.");
|
|
7459
8032
|
}
|
|
7460
8033
|
switch (stmt.type) {
|
|
8034
|
+
case "VALIDATE":
|
|
8035
|
+
return executeExistingRecordValidation(stmt, client, options, cacheContext);
|
|
7461
8036
|
case "SELECT":
|
|
7462
8037
|
return executeSelect(stmt, client, options, cacheContext);
|
|
7463
8038
|
case "UNION":
|
|
@@ -7503,6 +8078,144 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
7503
8078
|
return executeAssert(stmt, client, options, cacheContext);
|
|
7504
8079
|
}
|
|
7505
8080
|
}
|
|
8081
|
+
var EXISTING_VALIDATION_COLUMNS = ["$id", "$err_field", "$err_code", "$err_message", "$err_value"];
|
|
8082
|
+
function hasAuditableConstraint(field) {
|
|
8083
|
+
return field.required === true || field.minValue !== void 0 || field.maxValue !== void 0 || field.minLength !== void 0 || field.maxLength !== void 0 || field.optionOrder !== void 0;
|
|
8084
|
+
}
|
|
8085
|
+
function resolveExistingValidationTargets(stmt, fieldInfos) {
|
|
8086
|
+
const byCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
8087
|
+
const auditable = (field) => !field.inSubtable && (field.fieldType === "NUMBER" || hasAuditableConstraint(field));
|
|
8088
|
+
if (stmt.fields === void 0) return fieldInfos.filter(auditable);
|
|
8089
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8090
|
+
return stmt.fields.map((code) => {
|
|
8091
|
+
if (seen.has(code)) throw new Error(`ArgumentError: VALIDATE field ${code} is duplicated.`);
|
|
8092
|
+
seen.add(code);
|
|
8093
|
+
if (code === "$id") throw new Error("ArgumentError: VALIDATE cannot audit system field $id.");
|
|
8094
|
+
const info = byCode.get(code);
|
|
8095
|
+
if (!info) throw new Error(`ArgumentError: VALIDATE field ${code} does not exist.`);
|
|
8096
|
+
if (info.inSubtable) throw new Error(`ArgumentError: VALIDATE field ${code} is a subtable child field.`);
|
|
8097
|
+
if (!auditable(info)) throw new Error(`ArgumentError: VALIDATE field ${code} has no auditable constraint.`);
|
|
8098
|
+
return info;
|
|
8099
|
+
});
|
|
8100
|
+
}
|
|
8101
|
+
function collectValidateWhereFields(where) {
|
|
8102
|
+
const fields = [];
|
|
8103
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8104
|
+
const add = (field) => {
|
|
8105
|
+
if (!seen.has(field)) {
|
|
8106
|
+
seen.add(field);
|
|
8107
|
+
fields.push(field);
|
|
8108
|
+
}
|
|
8109
|
+
};
|
|
8110
|
+
const visit = (node) => {
|
|
8111
|
+
if (Array.isArray(node)) {
|
|
8112
|
+
node.forEach(visit);
|
|
8113
|
+
return;
|
|
8114
|
+
}
|
|
8115
|
+
if (node === null || typeof node !== "object") return;
|
|
8116
|
+
const obj = node;
|
|
8117
|
+
if (obj.type === "FIELD" && typeof obj.field === "string") add(obj.field);
|
|
8118
|
+
if (obj.type === "FIELD_REF" && typeof obj.field === "string") add(obj.field);
|
|
8119
|
+
Object.values(obj).forEach(visit);
|
|
8120
|
+
};
|
|
8121
|
+
visit(where);
|
|
8122
|
+
return fields;
|
|
8123
|
+
}
|
|
8124
|
+
function existingValidationColumnMeta() {
|
|
8125
|
+
return new Map(EXISTING_VALIDATION_COLUMNS.map((column) => [column, {
|
|
8126
|
+
fieldType: column === "$id" ? "KSQL_NUMBER" : "KSQL_STRING",
|
|
8127
|
+
sortKind: column === "$id" ? "number" : "string",
|
|
8128
|
+
semantics: syntheticSemantics(column === "$id" ? "number" : "string")
|
|
8129
|
+
}]));
|
|
8130
|
+
}
|
|
8131
|
+
async function executeExistingRecordValidation(stmt, client, options, cacheContext) {
|
|
8132
|
+
if (stmt.errorTable) throw new Error("ArgumentError: VALIDATE INTO requires a batch.");
|
|
8133
|
+
return executeExistingRecordValidationCore(stmt, client, options, cacheContext);
|
|
8134
|
+
}
|
|
8135
|
+
async function executeExistingRecordValidationCore(stmt, client, options, cacheContext) {
|
|
8136
|
+
const fieldInfos = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
8137
|
+
const infoByCode = new Map(fieldInfos.map((field) => [field.code, field]));
|
|
8138
|
+
const targets = resolveExistingValidationTargets(stmt, fieldInfos);
|
|
8139
|
+
const checkGroups = stmt.checkGroups ?? [];
|
|
8140
|
+
const checkRefs2 = collectCheckFieldRefs(checkGroups);
|
|
8141
|
+
for (const ref of checkRefs2) {
|
|
8142
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
8143
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${stmt.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
const evaluationTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
8147
|
+
evaluationTypes.set("$id", "RECORD_NUMBER");
|
|
8148
|
+
assertCheckComparisonTypes(stmt, evaluationTypes);
|
|
8149
|
+
const whereFields = collectValidateWhereFields(stmt.where);
|
|
8150
|
+
const requiredFields = [.../* @__PURE__ */ new Set([
|
|
8151
|
+
"$id",
|
|
8152
|
+
...targets.map((field) => field.code),
|
|
8153
|
+
...whereFields,
|
|
8154
|
+
...checkRefs2.map((ref) => ref.field)
|
|
8155
|
+
])];
|
|
8156
|
+
for (const field of whereFields) {
|
|
8157
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
8158
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${stmt.appId}.`);
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
const numberPrecision = targets.some((field) => field.fieldType === "NUMBER") ? await getNumberPrecisionCached(stmt.appId, client, cacheContext) : void 0;
|
|
8162
|
+
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);
|
|
8163
|
+
const capability = classifyWhereCapability(stmt.where, semantics);
|
|
8164
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
8165
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
8166
|
+
}
|
|
8167
|
+
const fieldTypes = new Map(fieldInfos.map((field) => [field.code, field.fieldType]));
|
|
8168
|
+
const fieldOptions = new Map(fieldInfos.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
8169
|
+
const prefilter = stmt.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? stmt.where : extractSafePushdownLeaves(stmt.where, {
|
|
8170
|
+
allowUnqualifiedFields: true,
|
|
8171
|
+
fieldTypes,
|
|
8172
|
+
fieldOptions,
|
|
8173
|
+
allowKlike: false
|
|
8174
|
+
});
|
|
8175
|
+
const query = prefilter === null ? "" : whereToKintone(prefilter);
|
|
8176
|
+
const records = await fetchAll(client.getRecords, stmt.appId, query, requiredFields, {
|
|
8177
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
8178
|
+
parallel: options.fetchParallel ?? 1,
|
|
8179
|
+
onLimit: "error"
|
|
8180
|
+
});
|
|
8181
|
+
const validationRows = records.map((record) => ({
|
|
8182
|
+
id: String(record["$id"]?.value ?? ""),
|
|
8183
|
+
record,
|
|
8184
|
+
flat: flatten(record, null)
|
|
8185
|
+
})).filter((row) => stmt.where === null || evalWhere(stmt.where, row.flat, (field) => evaluationTypes.get(field.field)));
|
|
8186
|
+
const rows = [];
|
|
8187
|
+
for (const row of validationRows) {
|
|
8188
|
+
for (const field of targets) {
|
|
8189
|
+
const raw = row.record[field.code]?.value;
|
|
8190
|
+
const validation = validateAndNormalizeDmlValue(raw, field, numberPrecision);
|
|
8191
|
+
if (validation.ok) continue;
|
|
8192
|
+
rows.push({
|
|
8193
|
+
"$id": row.id,
|
|
8194
|
+
"$err_field": field.code,
|
|
8195
|
+
"$err_code": validation.code,
|
|
8196
|
+
"$err_message": validation.message,
|
|
8197
|
+
"$err_value": renderExistingValidationValue(raw, field.fieldType)
|
|
8198
|
+
});
|
|
8199
|
+
}
|
|
8200
|
+
for (const check of evaluateCustomChecks(checkGroups, row.flat, (field) => evaluationTypes.get(field.field))) {
|
|
8201
|
+
rows.push({
|
|
8202
|
+
"$id": row.id,
|
|
8203
|
+
"$err_field": "",
|
|
8204
|
+
"$err_code": "ERR_CHECK",
|
|
8205
|
+
"$err_message": check.message,
|
|
8206
|
+
"$err_value": ""
|
|
8207
|
+
});
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
const result = {
|
|
8211
|
+
type: "SELECT",
|
|
8212
|
+
columns: [...EXISTING_VALIDATION_COLUMNS],
|
|
8213
|
+
rows,
|
|
8214
|
+
rowCount: rows.length
|
|
8215
|
+
};
|
|
8216
|
+
materializedMetaBySelectResult.set(result, existingValidationColumnMeta());
|
|
8217
|
+
return result;
|
|
8218
|
+
}
|
|
7506
8219
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
7507
8220
|
function appendValidationErrors(tempTables, name, columns, rows, maxRows, columnMeta) {
|
|
7508
8221
|
const current = tempTables.get(name);
|
|
@@ -7636,9 +8349,14 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
7636
8349
|
}
|
|
7637
8350
|
async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables, variables) {
|
|
7638
8351
|
if (stmt.type === "SET_VARIABLE") {
|
|
7639
|
-
const resolvedStmt2 =
|
|
8352
|
+
const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
|
|
7640
8353
|
validateKlikeStatement(resolvedStmt2);
|
|
7641
|
-
if (resolvedStmt2.expr.type === "
|
|
8354
|
+
if (resolvedStmt2.expr.type === "ARRAY") {
|
|
8355
|
+
variables.set(stmt.name, {
|
|
8356
|
+
type: "array",
|
|
8357
|
+
elements: resolvedStmt2.expr.elements.map((element) => ({ type: "string", value: element.value }))
|
|
8358
|
+
});
|
|
8359
|
+
} else if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
|
|
7642
8360
|
try {
|
|
7643
8361
|
const value = await evaluateScalarSubquery(
|
|
7644
8362
|
resolvedStmt2.expr.query,
|
|
@@ -7675,8 +8393,27 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
7675
8393
|
}
|
|
7676
8394
|
return {};
|
|
7677
8395
|
}
|
|
7678
|
-
const resolvedStmt =
|
|
8396
|
+
const resolvedStmt = resolveBatchVariableReferences(stmt, variables);
|
|
7679
8397
|
validateKlikeStatement(resolvedStmt);
|
|
8398
|
+
if (resolvedStmt.type === "VALIDATE") {
|
|
8399
|
+
const result = await executeExistingRecordValidationCore(
|
|
8400
|
+
resolvedStmt,
|
|
8401
|
+
client,
|
|
8402
|
+
{ ...options, onLimitReached: "error" },
|
|
8403
|
+
cacheContext
|
|
8404
|
+
);
|
|
8405
|
+
if (resolvedStmt.errorTable) {
|
|
8406
|
+
appendValidationErrors(
|
|
8407
|
+
tempTables,
|
|
8408
|
+
resolvedStmt.errorTable,
|
|
8409
|
+
result.columns,
|
|
8410
|
+
result.rows,
|
|
8411
|
+
options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
|
|
8412
|
+
materializedMetaBySelectResult.get(result) ?? existingValidationColumnMeta()
|
|
8413
|
+
);
|
|
8414
|
+
}
|
|
8415
|
+
return { result };
|
|
8416
|
+
}
|
|
7680
8417
|
if ("validateOnly" in resolvedStmt && resolvedStmt.validateOnly === true) {
|
|
7681
8418
|
const result = await executeDmlValidation(
|
|
7682
8419
|
resolvedStmt,
|
|
@@ -7861,9 +8598,9 @@ function evaluateScalarExpr(expr) {
|
|
|
7861
8598
|
}
|
|
7862
8599
|
}
|
|
7863
8600
|
}
|
|
7864
|
-
function
|
|
8601
|
+
function resolveBatchVariableReferences(node, variables) {
|
|
7865
8602
|
if (Array.isArray(node)) {
|
|
7866
|
-
return node.map((v) =>
|
|
8603
|
+
return node.map((v) => resolveBatchVariableReferences(v, variables));
|
|
7867
8604
|
}
|
|
7868
8605
|
if (node !== null && typeof node === "object") {
|
|
7869
8606
|
const obj = node;
|
|
@@ -7872,14 +8609,72 @@ function resolveVariableRefs(node, variables) {
|
|
|
7872
8609
|
if (value === void 0) {
|
|
7873
8610
|
throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
7874
8611
|
}
|
|
8612
|
+
if (value.type === "array") {
|
|
8613
|
+
throw new Error(`ParseError: array variable @${obj["name"]} can only be used as IN @${obj["name"]}.`);
|
|
8614
|
+
}
|
|
7875
8615
|
return value.type === "number" ? { type: "NUMBER", value: value.value, raw: value.raw ?? String(value.value) } : { type: "STRING", value: value.value };
|
|
7876
8616
|
}
|
|
7877
|
-
|
|
7878
|
-
|
|
8617
|
+
if (obj["type"] === "VARIABLE_COL" && typeof obj["name"] === "string" && typeof obj["alias"] === "string") {
|
|
8618
|
+
const value = variables.get(obj["name"]);
|
|
8619
|
+
if (value === void 0) throw new Error(`ParseError: variable @${obj["name"]} is not defined in this batch.`);
|
|
8620
|
+
if (value.type === "array") throw new Error(`ParseError: array variable @${obj["name"]} cannot be used as a SELECT column.`);
|
|
8621
|
+
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"] };
|
|
8622
|
+
}
|
|
8623
|
+
if (obj["type"] === "VARIABLE_IN_LIST") return obj;
|
|
8624
|
+
const resolved = Object.fromEntries(
|
|
8625
|
+
Object.entries(obj).map(([key, value]) => [key, resolveBatchVariableReferences(value, variables)])
|
|
7879
8626
|
);
|
|
8627
|
+
if (resolved["type"] === "BINARY") {
|
|
8628
|
+
const right = resolved["right"];
|
|
8629
|
+
if (right?.["type"] === "VARIABLE_IN_LIST" && typeof right["name"] === "string") {
|
|
8630
|
+
const value = variables.get(right["name"]);
|
|
8631
|
+
if (value === void 0) throw new Error(`ParseError: variable @${right["name"]} is not defined in this batch.`);
|
|
8632
|
+
if (value.type !== "array") {
|
|
8633
|
+
throw new Error(`ParseError: scalar variable @${right["name"]} cannot be used as IN @${right["name"]}; use IN (@${right["name"]}) instead.`);
|
|
8634
|
+
}
|
|
8635
|
+
if (value.elements.length === 0) {
|
|
8636
|
+
return { type: "BOOLEAN", value: resolved["op"] === "NOT_IN" };
|
|
8637
|
+
}
|
|
8638
|
+
resolved["right"] = {
|
|
8639
|
+
type: "IN_LIST",
|
|
8640
|
+
values: value.elements.map((element) => ({ type: "STRING", value: element.value }))
|
|
8641
|
+
};
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
const simplified = simplifyBooleanWhere(resolved);
|
|
8645
|
+
if (simplified["type"] === "SELECT" && isBooleanNode(simplified["where"], true)) {
|
|
8646
|
+
simplified["where"] = null;
|
|
8647
|
+
}
|
|
8648
|
+
if ((simplified["type"] === "UPDATE" || simplified["type"] === "DELETE" || simplified["type"] === "REORDER") && isBooleanNode(simplified["where"], true)) {
|
|
8649
|
+
throw new Error("ArgumentError: empty-array simplification makes the target WHERE always true; use an explicit safe target condition.");
|
|
8650
|
+
}
|
|
8651
|
+
return simplified;
|
|
7880
8652
|
}
|
|
7881
8653
|
return node;
|
|
7882
8654
|
}
|
|
8655
|
+
function isBooleanNode(value, expected) {
|
|
8656
|
+
return value !== null && typeof value === "object" && value.type === "BOOLEAN" && (expected === void 0 || value.value === expected);
|
|
8657
|
+
}
|
|
8658
|
+
function simplifyBooleanWhere(obj) {
|
|
8659
|
+
if (obj["type"] === "NOT" && isBooleanNode(obj["expr"])) {
|
|
8660
|
+
return { type: "BOOLEAN", value: !obj["expr"].value };
|
|
8661
|
+
}
|
|
8662
|
+
if (obj["type"] === "GROUP" && isBooleanNode(obj["expr"])) return obj["expr"];
|
|
8663
|
+
if (obj["type"] === "LOGICAL") {
|
|
8664
|
+
const left = obj["left"];
|
|
8665
|
+
const right = obj["right"];
|
|
8666
|
+
if (obj["op"] === "AND") {
|
|
8667
|
+
if (isBooleanNode(left, false) || isBooleanNode(right, false)) return { type: "BOOLEAN", value: false };
|
|
8668
|
+
if (isBooleanNode(left, true)) return right;
|
|
8669
|
+
if (isBooleanNode(right, true)) return left;
|
|
8670
|
+
} else if (obj["op"] === "OR") {
|
|
8671
|
+
if (isBooleanNode(left, true) || isBooleanNode(right, true)) return { type: "BOOLEAN", value: true };
|
|
8672
|
+
if (isBooleanNode(left, false)) return right;
|
|
8673
|
+
if (isBooleanNode(right, false)) return left;
|
|
8674
|
+
}
|
|
8675
|
+
}
|
|
8676
|
+
return obj;
|
|
8677
|
+
}
|
|
7883
8678
|
function findVariableRef(node) {
|
|
7884
8679
|
if (Array.isArray(node)) {
|
|
7885
8680
|
for (const value of node) {
|
|
@@ -7890,7 +8685,7 @@ function findVariableRef(node) {
|
|
|
7890
8685
|
}
|
|
7891
8686
|
if (node !== null && typeof node === "object") {
|
|
7892
8687
|
const obj = node;
|
|
7893
|
-
if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") return obj["name"];
|
|
8688
|
+
if ((obj["type"] === "VARIABLE" || obj["type"] === "VARIABLE_COL" || obj["type"] === "VARIABLE_IN_LIST") && typeof obj["name"] === "string") return obj["name"];
|
|
7894
8689
|
for (const value of Object.values(obj)) {
|
|
7895
8690
|
const found = findVariableRef(value);
|
|
7896
8691
|
if (found !== null) return found;
|
|
@@ -8100,7 +8895,7 @@ function buildHavingFieldSemanticsResolver(stmt, rowResolver) {
|
|
|
8100
8895
|
}
|
|
8101
8896
|
} else if (column.type === "STRFUNC_COL") {
|
|
8102
8897
|
semantics = stringFunctionColumnMeta(column.expr).semantics;
|
|
8103
|
-
} else if (column.type === "LITERAL_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "CASE_COL") {
|
|
8898
|
+
} else if (column.type === "LITERAL_COL" || column.type === "SCALAR_SUBQUERY_COL" || column.type === "CASE_COL" || column.type === "SCALAR_VALUE_COL") {
|
|
8104
8899
|
semantics = syntheticSemantics("string");
|
|
8105
8900
|
}
|
|
8106
8901
|
if (semantics) aliases.set(column.alias, semantics);
|
|
@@ -8133,6 +8928,7 @@ async function assertDmlWhereCapability(stmt, client, cacheContext) {
|
|
|
8133
8928
|
if (stmt.subtableCode || stmt.type === "UPDATE" && stmt.from != null) return;
|
|
8134
8929
|
const fields = whereNeedsFieldMetadata(stmt.where) ? await getFieldsCached(stmt.appId, client, cacheContext) : [];
|
|
8135
8930
|
const byCode = new Map(fields.map((field) => [field.code, field]));
|
|
8931
|
+
if (stmt.where.type === "BOOLEAN" && stmt.where.value === false) return;
|
|
8136
8932
|
const result = classifyWhereCapability(stmt.where, (field) => {
|
|
8137
8933
|
if (field.field === "$id") return resolveFieldSemantics({ fieldType: "__ID__" });
|
|
8138
8934
|
const info = byCode.get(field.field);
|
|
@@ -8205,6 +9001,9 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
|
|
|
8205
9001
|
}
|
|
8206
9002
|
return result;
|
|
8207
9003
|
}
|
|
9004
|
+
function isConstantFalseWhere(where) {
|
|
9005
|
+
return where?.type === "BOOLEAN" && where.value === false;
|
|
9006
|
+
}
|
|
8208
9007
|
function isNoFromSelect(stmt) {
|
|
8209
9008
|
return stmt.from.appId === 0 && stmt.from.cteName === NO_FROM_CTE_NAME;
|
|
8210
9009
|
}
|
|
@@ -8215,10 +9014,19 @@ function arithHasFieldRef(node) {
|
|
|
8215
9014
|
return false;
|
|
8216
9015
|
}
|
|
8217
9016
|
function stringFuncArgHasFieldRef(arg) {
|
|
8218
|
-
if (arg.type === "FIELD_REF") return true;
|
|
8219
|
-
if (arg.type === "ARITH") return arithHasFieldRef(arg);
|
|
8220
|
-
if (arg.type === "STRING_FUNC") return stringFuncHasFieldRef(arg);
|
|
8221
9017
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") return true;
|
|
9018
|
+
return scalarValueHasFieldRef(arg);
|
|
9019
|
+
}
|
|
9020
|
+
function scalarValueHasFieldRef(expr) {
|
|
9021
|
+
if (expr.type === "FIELD") return true;
|
|
9022
|
+
if (expr.type === "STRING_FUNC") return stringFuncHasFieldRef(expr);
|
|
9023
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
9024
|
+
return scalarValueHasFieldRef(expr.left) || scalarValueHasFieldRef(expr.right);
|
|
9025
|
+
}
|
|
9026
|
+
if (expr.type === "CASE_WHEN") {
|
|
9027
|
+
const results = [...expr.branches.map((branch) => branch.result), ...expr.elseResult ? [expr.elseResult] : []];
|
|
9028
|
+
return results.some((result) => result.type !== "ARRAY" && (result.type === "FIELD_REF" || result.type === "ARITH" ? arithHasFieldRef(result) : scalarValueHasFieldRef(result)));
|
|
9029
|
+
}
|
|
8222
9030
|
return false;
|
|
8223
9031
|
}
|
|
8224
9032
|
function stringFuncHasFieldRef(expr) {
|
|
@@ -8227,6 +9035,8 @@ function stringFuncHasFieldRef(expr) {
|
|
|
8227
9035
|
function validateNoFromColumns(stmt) {
|
|
8228
9036
|
for (const col of stmt.columns) {
|
|
8229
9037
|
switch (col.type) {
|
|
9038
|
+
case "VARIABLE_COL":
|
|
9039
|
+
throw new Error(`internal error: unresolved SELECT variable @${col.name}`);
|
|
8230
9040
|
case "LITERAL_COL":
|
|
8231
9041
|
break;
|
|
8232
9042
|
case "ARITH_COL":
|
|
@@ -8239,6 +9049,11 @@ function validateNoFromColumns(stmt) {
|
|
|
8239
9049
|
throw new Error("ArgumentError: field reference is not allowed without FROM.");
|
|
8240
9050
|
}
|
|
8241
9051
|
break;
|
|
9052
|
+
case "SCALAR_VALUE_COL":
|
|
9053
|
+
if (scalarValueHasFieldRef(col.expr)) {
|
|
9054
|
+
throw new Error("ArgumentError: field reference is not allowed without FROM.");
|
|
9055
|
+
}
|
|
9056
|
+
break;
|
|
8242
9057
|
case "WINDOW_COL":
|
|
8243
9058
|
if (col.partitionBy.length > 0 || col.orderBy.length > 0) {
|
|
8244
9059
|
throw new Error("ArgumentError: field reference is not allowed without FROM.");
|
|
@@ -8457,6 +9272,7 @@ function collectTypedInFieldRefs(expr, out) {
|
|
|
8457
9272
|
return;
|
|
8458
9273
|
case "NULL_CHECK":
|
|
8459
9274
|
case "EXISTS":
|
|
9275
|
+
case "BOOLEAN":
|
|
8460
9276
|
return;
|
|
8461
9277
|
}
|
|
8462
9278
|
}
|
|
@@ -8525,8 +9341,26 @@ function collectStringFuncAggregateRefs(expr, out) {
|
|
|
8525
9341
|
for (const arg of expr.args) {
|
|
8526
9342
|
if (arg.type === "AGG_REF" || arg.type === "AGG_ARITH") {
|
|
8527
9343
|
collectAggregateOperandRefs(arg, out);
|
|
8528
|
-
} else
|
|
8529
|
-
|
|
9344
|
+
} else {
|
|
9345
|
+
collectScalarAggregateRefs(arg, out);
|
|
9346
|
+
}
|
|
9347
|
+
}
|
|
9348
|
+
}
|
|
9349
|
+
function collectScalarAggregateRefs(expr, out) {
|
|
9350
|
+
if (expr.type === "STRING_FUNC") {
|
|
9351
|
+
collectStringFuncAggregateRefs(expr, out);
|
|
9352
|
+
return;
|
|
9353
|
+
}
|
|
9354
|
+
if (expr.type === "SCALAR_ARITH" || expr.type === "CONCAT_OP") {
|
|
9355
|
+
collectScalarAggregateRefs(expr.left, out);
|
|
9356
|
+
collectScalarAggregateRefs(expr.right, out);
|
|
9357
|
+
return;
|
|
9358
|
+
}
|
|
9359
|
+
if (expr.type === "CASE_WHEN") {
|
|
9360
|
+
const results = [...expr.branches.map((branch) => branch.result), ...expr.elseResult ? [expr.elseResult] : []];
|
|
9361
|
+
for (const result of results) {
|
|
9362
|
+
if (result.type === "STRING_FUNC") collectStringFuncAggregateRefs(result, out);
|
|
9363
|
+
else if (result.type !== "ARRAY" && result.type !== "FIELD_REF" && result.type !== "ARITH") collectScalarAggregateRefs(result, out);
|
|
8530
9364
|
}
|
|
8531
9365
|
}
|
|
8532
9366
|
}
|
|
@@ -8539,6 +9373,8 @@ function collectSelectAggregateSortRefs(columns) {
|
|
|
8539
9373
|
collectAggregateOperandRefs(column.expr, refs);
|
|
8540
9374
|
} else if (column.type === "STRFUNC_COL") {
|
|
8541
9375
|
collectStringFuncAggregateRefs(column.expr, refs);
|
|
9376
|
+
} else if (column.type === "SCALAR_VALUE_COL") {
|
|
9377
|
+
collectScalarAggregateRefs(column.expr, refs);
|
|
8542
9378
|
}
|
|
8543
9379
|
}
|
|
8544
9380
|
return refs;
|
|
@@ -8704,10 +9540,11 @@ function stringFunctionColumnMeta(expr) {
|
|
|
8704
9540
|
function caseResultColumnMeta(result, resolveField2) {
|
|
8705
9541
|
if (result.type === "STRING") return syntheticColumnMeta("string");
|
|
8706
9542
|
if (result.type === "ARRAY") return unsupportedColumnMeta();
|
|
8707
|
-
if (result.type === "NUMBER" || result.type === "ARITH") return syntheticColumnMeta("number");
|
|
9543
|
+
if (result.type === "NUMBER" || result.type === "ARITH" || result.type === "SCALAR_ARITH") return syntheticColumnMeta("number");
|
|
8708
9544
|
if (result.type === "STRING_FUNC") return stringFunctionColumnMeta(result);
|
|
8709
|
-
|
|
8710
|
-
return
|
|
9545
|
+
if (result.type === "FIELD_REF") return resolveField2(aggregateFieldRef(result.field)) ?? unknownStringColumnMeta();
|
|
9546
|
+
if (result.type === "FIELD") return resolveField2(result) ?? unknownStringColumnMeta();
|
|
9547
|
+
return unknownStringColumnMeta();
|
|
8711
9548
|
}
|
|
8712
9549
|
function mergeExpressionColumnMeta(candidates) {
|
|
8713
9550
|
if (candidates.length === 0) return unknownStringColumnMeta();
|
|
@@ -8809,7 +9646,7 @@ async function inferSelectColumnMeta(stmt, outputColumns, client, cacheContext,
|
|
|
8809
9646
|
}
|
|
8810
9647
|
} else if (column.type === "ARITH_AGG_COL" || column.type === "ARITH_COL") {
|
|
8811
9648
|
meta = syntheticColumnMeta("number");
|
|
8812
|
-
} else if (column.type === "LITERAL_COL") {
|
|
9649
|
+
} else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") {
|
|
8813
9650
|
meta = syntheticColumnMeta("string");
|
|
8814
9651
|
} else if (column.type === "STRFUNC_COL") {
|
|
8815
9652
|
meta = stringFunctionColumnMeta(column.expr);
|
|
@@ -8898,7 +9735,8 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
8898
9735
|
validateKlikePushdownPlan(pushdownPlan);
|
|
8899
9736
|
const mainPushDown = pushdownPlan.mainCondition;
|
|
8900
9737
|
const tableConditions = pushdownPlan.joinConditions;
|
|
8901
|
-
const
|
|
9738
|
+
const constantFalse = isConstantFalseWhere(stmt.where);
|
|
9739
|
+
const mainFetch = constantFalse ? Promise.resolve([]) : fetchTableRecordsForFullScan(
|
|
8902
9740
|
stmt,
|
|
8903
9741
|
stmt.from,
|
|
8904
9742
|
client,
|
|
@@ -8913,6 +9751,10 @@ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCac
|
|
|
8913
9751
|
const parallelJoins = [];
|
|
8914
9752
|
const onOptJoins = [];
|
|
8915
9753
|
for (const join2 of stmt.joins) {
|
|
9754
|
+
if (constantFalse) {
|
|
9755
|
+
parallelJoins.push({ join: join2, promise: Promise.resolve([]) });
|
|
9756
|
+
continue;
|
|
9757
|
+
}
|
|
8916
9758
|
const jCond = join2.table.alias ? tableConditions.get(join2.table.alias) ?? null : null;
|
|
8917
9759
|
if (jCond !== null) {
|
|
8918
9760
|
parallelJoins.push({
|
|
@@ -9516,7 +10358,7 @@ async function buildOrderSemanticsForSelect(stmt, client, cacheContext, material
|
|
|
9516
10358
|
if (column.type === "FIELD") meta = resolveField2(aggregateFieldRef(column.field));
|
|
9517
10359
|
else if (column.type === "ARITH_COL" || column.type === "ARITH_AGG_COL" || column.type === "WINDOW_COL") {
|
|
9518
10360
|
meta = syntheticColumnMeta("number");
|
|
9519
|
-
} else if (column.type === "LITERAL_COL") meta = syntheticColumnMeta("string");
|
|
10361
|
+
} else if (column.type === "LITERAL_COL" || column.type === "SCALAR_VALUE_COL") meta = syntheticColumnMeta("string");
|
|
9520
10362
|
else if (column.type === "STRFUNC_COL") meta = stringFunctionColumnMeta(column.expr);
|
|
9521
10363
|
else if (column.type === "SCALAR_SUBQUERY_COL") meta = unknownStringColumnMeta();
|
|
9522
10364
|
else if (column.type === "CASE_COL") {
|
|
@@ -9704,7 +10546,7 @@ var RejectLimitExceededError = class extends Error {
|
|
|
9704
10546
|
this.name = "RejectLimitExceededError";
|
|
9705
10547
|
}
|
|
9706
10548
|
};
|
|
9707
|
-
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber) {
|
|
10549
|
+
async function prepareDmlValidation(stmt, client, options, cacheContext, tempTables, statementNumber, validateMissingCreateFields = true, includePreErrors = true) {
|
|
9708
10550
|
const operation = stmt.type === "UPDATE" ? "UPDATE" : stmt.type.startsWith("UPSERT") ? "UPSERT" : "INSERT";
|
|
9709
10551
|
const payloadFields = stmt.type === "UPDATE" ? ["$id", ...stmt.assignments.map((a) => a.field)] : [...stmt.fields];
|
|
9710
10552
|
if (new Set(payloadFields).size !== payloadFields.length) {
|
|
@@ -9736,7 +10578,10 @@ async function prepareDmlValidation(stmt, client, options, cacheContext, tempTab
|
|
|
9736
10578
|
targetFields,
|
|
9737
10579
|
fieldInfos,
|
|
9738
10580
|
statementNumber,
|
|
9739
|
-
numberPrecision
|
|
10581
|
+
numberPrecision,
|
|
10582
|
+
stmt.checkGroups ?? [],
|
|
10583
|
+
validateMissingCreateFields,
|
|
10584
|
+
includePreErrors
|
|
9740
10585
|
);
|
|
9741
10586
|
const columns = [...payloadFields, ...VALIDATION_META_COLUMNS];
|
|
9742
10587
|
const result = {
|
|
@@ -9846,15 +10691,33 @@ async function executeOnErrorSkip(stmt, client, options, cacheContext, tempTable
|
|
|
9846
10691
|
async function materializeValidationCandidates(stmt, operation, client, options, cacheContext, tempTables, infoByCode) {
|
|
9847
10692
|
if (stmt.type === "UPDATE") return materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables);
|
|
9848
10693
|
let rows;
|
|
10694
|
+
let sourceRows;
|
|
10695
|
+
let evaluationTypes;
|
|
9849
10696
|
if (stmt.type === "INSERT" || stmt.type === "UPSERT") {
|
|
10697
|
+
assertInsertCheckRefs(stmt, stmt.fields);
|
|
10698
|
+
evaluationTypes = new Map(stmt.fields.map((field) => [field, infoByCode.get(field)?.fieldType ?? ""]));
|
|
10699
|
+
assertCheckComparisonTypes(stmt, evaluationTypes);
|
|
9850
10700
|
rows = stmt.values.map((row) => row.map(
|
|
9851
10701
|
(value, i) => value.type === "CASE_VALUE" ? evalCaseWhenValue(value.expr, {}, infoByCode.get(stmt.fields[i])?.fieldType) : value
|
|
9852
10702
|
));
|
|
9853
10703
|
} else {
|
|
9854
|
-
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);
|
|
9855
|
-
|
|
10704
|
+
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, void 0, true);
|
|
10705
|
+
const hasChecks = (stmt.checkGroups?.length ?? 0) > 0;
|
|
10706
|
+
if (selectResult.columns.length < stmt.fields.length || !hasChecks && selectResult.columns.length !== stmt.fields.length) {
|
|
9856
10707
|
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`);
|
|
9857
10708
|
}
|
|
10709
|
+
if (hasChecks && new Set(selectResult.columns).size !== selectResult.columns.length) {
|
|
10710
|
+
throw customCheckParseError("CHECK \u4ED8\u304D DML \u30BD\u30FC\u30B9 SELECT \u306E\u51FA\u529B\u540D\u306F\u4E00\u610F\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
|
|
10711
|
+
}
|
|
10712
|
+
assertInsertCheckRefs(stmt, selectResult.columns);
|
|
10713
|
+
sourceRows = selectResult.rows;
|
|
10714
|
+
const meta = materializedMetaBySelectResult.get(selectResult);
|
|
10715
|
+
evaluationTypes = new Map(selectResult.columns.map((column) => {
|
|
10716
|
+
const columnMeta = meta?.get(column);
|
|
10717
|
+
const type = columnMeta?.fieldType ?? (columnMeta?.semantics?.compareMode === "number" || columnMeta?.sortKind === "number" ? "NUMBER" : "SINGLE_LINE_TEXT");
|
|
10718
|
+
return [column, type];
|
|
10719
|
+
}));
|
|
10720
|
+
assertCheckComparisonTypes(stmt, evaluationTypes);
|
|
9858
10721
|
rows = selectResult.rows.map((row) => selectResult.columns.map((column) => row[column] ?? ""));
|
|
9859
10722
|
}
|
|
9860
10723
|
const candidates = rows.map((values, index) => ({
|
|
@@ -9863,7 +10726,11 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
9863
10726
|
mode: "create",
|
|
9864
10727
|
payload: new Map(stmt.fields.map((field, i) => [field, values[i]])),
|
|
9865
10728
|
preErrors: [],
|
|
9866
|
-
record: {}
|
|
10729
|
+
record: {},
|
|
10730
|
+
evaluationRow: sourceRows?.[index] ?? Object.fromEntries(
|
|
10731
|
+
stmt.fields.map((field, i) => [field, renderValidationValue(values[i])])
|
|
10732
|
+
),
|
|
10733
|
+
evaluationFieldTypes: evaluationTypes
|
|
9867
10734
|
}));
|
|
9868
10735
|
if (stmt.type !== "UPSERT" && stmt.type !== "UPSERT_SELECT") return candidates;
|
|
9869
10736
|
for (const key of stmt.keyFields) {
|
|
@@ -9892,26 +10759,74 @@ async function materializeValidationCandidates(stmt, operation, client, options,
|
|
|
9892
10759
|
});
|
|
9893
10760
|
return candidates;
|
|
9894
10761
|
}
|
|
10762
|
+
function checkRefs(stmt) {
|
|
10763
|
+
return stmt.checkGroups ? collectCheckFieldRefs(stmt.checkGroups) : [];
|
|
10764
|
+
}
|
|
10765
|
+
function assertInsertCheckRefs(stmt, available) {
|
|
10766
|
+
const names = new Set(available);
|
|
10767
|
+
for (const ref of checkRefs(stmt)) {
|
|
10768
|
+
if (ref.tableAlias !== null) {
|
|
10769
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.tableAlias}.${ref.field} \u306F\u3053\u306E\u8A55\u4FA1\u884C\u3067\u306F\u4FEE\u98FE\u3067\u304D\u307E\u305B\u3093`);
|
|
10770
|
+
}
|
|
10771
|
+
if (!names.has(ref.field)) {
|
|
10772
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F\u8A55\u4FA1\u884C\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
10773
|
+
}
|
|
10774
|
+
}
|
|
10775
|
+
}
|
|
10776
|
+
var CHECK_UNSUPPORTED_COMPARISON_TYPES = /* @__PURE__ */ new Set([
|
|
10777
|
+
"CHECK_BOX",
|
|
10778
|
+
"MULTI_SELECT",
|
|
10779
|
+
"USER_SELECT",
|
|
10780
|
+
"ORGANIZATION_SELECT",
|
|
10781
|
+
"GROUP_SELECT",
|
|
10782
|
+
"FILE",
|
|
10783
|
+
"KSQL_ARRAY"
|
|
10784
|
+
]);
|
|
10785
|
+
function assertCheckComparisonTypes(stmt, types) {
|
|
10786
|
+
if (!stmt.checkGroups) return;
|
|
10787
|
+
for (const ref of collectCheckComparisonFieldRefs(stmt.checkGroups)) {
|
|
10788
|
+
const key = ref.tableAlias ? `${ref.tableAlias}.${ref.field}` : ref.field;
|
|
10789
|
+
const type = types.get(key) ?? types.get(ref.field);
|
|
10790
|
+
if (CHECK_UNSUPPORTED_COMPARISON_TYPES.has(type ?? "")) {
|
|
10791
|
+
throw customCheckParseError(`CHECK \u306E\u6BD4\u8F03\u3067\u306F ${type} \u30D5\u30A3\u30FC\u30EB\u30C9 ${key} \u3092\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
9895
10795
|
async function materializeUpdateValidationCandidates(stmt, client, options, cacheContext, tempTables) {
|
|
9896
10796
|
if (stmt.from) return materializeUpdateFromValidationCandidates(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
9897
10797
|
await resolveSetSubqueries(stmt.assignments, client, options, cacheContext);
|
|
9898
10798
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
10799
|
+
const checkTargetFields = assertUpdateCheckRefs(stmt, fieldTypes);
|
|
10800
|
+
assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes, stmt.appId));
|
|
9899
10801
|
let records;
|
|
10802
|
+
let evaluationById = /* @__PURE__ */ new Map();
|
|
9900
10803
|
if (hasRowDependentAssignment(stmt)) {
|
|
9901
10804
|
const getParams = updateToGetQueryForArith(stmt);
|
|
9902
|
-
const
|
|
10805
|
+
const fields = [.../* @__PURE__ */ new Set([...getParams.fields, ...checkTargetFields])];
|
|
10806
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, fields, {
|
|
9903
10807
|
maxRecords: options.maxRecords ?? 1e4,
|
|
9904
10808
|
parallel: options.fetchParallel ?? 1,
|
|
9905
10809
|
onLimit: "error"
|
|
9906
10810
|
});
|
|
10811
|
+
evaluationById = new Map(resolved.records.map((record) => [Number(record["$id"]?.value), record]));
|
|
9907
10812
|
records = updateToPutBatchesArith(stmt, resolved.records, fieldTypes).flatMap((batch) => batch.records);
|
|
9908
10813
|
} else {
|
|
9909
10814
|
const getParams = updateToGetQuery(stmt);
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
10815
|
+
if (checkTargetFields.length > 0) {
|
|
10816
|
+
const resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [.../* @__PURE__ */ new Set(["$id", ...checkTargetFields])], {
|
|
10817
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
10818
|
+
parallel: options.fetchParallel ?? 1,
|
|
10819
|
+
onLimit: "error"
|
|
10820
|
+
});
|
|
10821
|
+
evaluationById = new Map(resolved.records.map((record) => [Number(record["$id"]?.value), record]));
|
|
10822
|
+
records = updateToPutBatches(stmt, [...evaluationById.keys()], fieldTypes).flatMap((batch) => batch.records);
|
|
10823
|
+
} else {
|
|
10824
|
+
const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
|
|
10825
|
+
maxRecords: options.maxRecords ?? 1e4,
|
|
10826
|
+
parallel: options.fetchParallel ?? 1
|
|
10827
|
+
});
|
|
10828
|
+
records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
|
|
10829
|
+
}
|
|
9915
10830
|
}
|
|
9916
10831
|
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
9917
10832
|
rowNumber: index + 1,
|
|
@@ -9920,13 +10835,48 @@ async function materializeUpdateValidationCandidates(stmt, client, options, cach
|
|
|
9920
10835
|
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
9921
10836
|
preErrors: [],
|
|
9922
10837
|
record: entry.record,
|
|
9923
|
-
targetId: entry.id
|
|
10838
|
+
targetId: entry.id,
|
|
10839
|
+
evaluationRow: updateEvaluationRow(evaluationById.get(entry.id), stmt.appId),
|
|
10840
|
+
evaluationFieldTypes: updateEvaluationTypes(fieldTypes, stmt.appId)
|
|
9924
10841
|
}));
|
|
9925
10842
|
}
|
|
10843
|
+
function assertUpdateCheckRefs(stmt, targetTypes) {
|
|
10844
|
+
if (stmt.from) return [];
|
|
10845
|
+
const fields = /* @__PURE__ */ new Set();
|
|
10846
|
+
for (const ref of checkRefs(stmt)) {
|
|
10847
|
+
if (ref.tableAlias !== null && ref.tableAlias.toLowerCase() !== `app${stmt.appId}`.toLowerCase()) {
|
|
10848
|
+
throw customCheckParseError(`CHECK \u306E\u4FEE\u98FE\u5B50 ${ref.tableAlias} \u306F\u66F4\u65B0\u5148 APP${stmt.appId} \u3067\u306F\u3042\u308A\u307E\u305B\u3093`);
|
|
10849
|
+
}
|
|
10850
|
+
if (ref.field !== "$id" && !targetTypes.has(ref.field)) {
|
|
10851
|
+
throw customCheckParseError(`CHECK \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
10852
|
+
}
|
|
10853
|
+
fields.add(ref.field);
|
|
10854
|
+
}
|
|
10855
|
+
return [...fields];
|
|
10856
|
+
}
|
|
10857
|
+
function updateEvaluationRow(record, appId) {
|
|
10858
|
+
if (!record) return {};
|
|
10859
|
+
const plain = flatten(record, null);
|
|
10860
|
+
return Object.fromEntries([
|
|
10861
|
+
...Object.entries(plain),
|
|
10862
|
+
...Object.entries(plain).map(([field, value]) => [`APP${appId}.${field}`, value])
|
|
10863
|
+
]);
|
|
10864
|
+
}
|
|
10865
|
+
function updateEvaluationTypes(types, appId) {
|
|
10866
|
+
return new Map([
|
|
10867
|
+
...types,
|
|
10868
|
+
...[...types].map(([field, type]) => [`APP${appId}.${field}`, type]),
|
|
10869
|
+
["$id", "RECORD_NUMBER"],
|
|
10870
|
+
[`APP${appId}.$id`, "RECORD_NUMBER"]
|
|
10871
|
+
]);
|
|
10872
|
+
}
|
|
9926
10873
|
async function materializeUpdateFromValidationCandidates(stmt, from, client, options, cacheContext, tempTables) {
|
|
10874
|
+
const scope = await resolveUpdateFromCheckScope(stmt, from, client, cacheContext, tempTables);
|
|
10875
|
+
assertCheckComparisonTypes(stmt, scope.evaluationTypes);
|
|
9927
10876
|
const matched = await resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables);
|
|
9928
10877
|
const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
9929
10878
|
const records = updateFromToPutBatches(stmt, matched, fieldTypes).flatMap((batch) => batch.records);
|
|
10879
|
+
const matchedById = new Map(matched.map((pair) => [Number(pair.target["$id"]?.value), pair]));
|
|
9930
10880
|
return records.sort((a, b) => a.id - b.id).map((entry, index) => ({
|
|
9931
10881
|
rowNumber: index + 1,
|
|
9932
10882
|
operation: "UPDATE",
|
|
@@ -9934,7 +10884,9 @@ async function materializeUpdateFromValidationCandidates(stmt, from, client, opt
|
|
|
9934
10884
|
payload: new Map([["$id", String(entry.id)], ...stmt.assignments.map((a) => [a.field, entry.record[a.field]?.value ?? ""])]),
|
|
9935
10885
|
preErrors: [],
|
|
9936
10886
|
record: entry.record,
|
|
9937
|
-
targetId: entry.id
|
|
10887
|
+
targetId: entry.id,
|
|
10888
|
+
evaluationRow: updateFromEvaluationRow(matchedById.get(entry.id), stmt.appId, from.alias),
|
|
10889
|
+
evaluationFieldTypes: scope.evaluationTypes
|
|
9938
10890
|
}));
|
|
9939
10891
|
}
|
|
9940
10892
|
var UPDATE_FROM_KEY_CHUNK_SIZE = UPSERT_IN_CHUNK_SIZE;
|
|
@@ -9948,7 +10900,8 @@ var UPDATE_FROM_UNSUPPORTED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
9948
10900
|
]);
|
|
9949
10901
|
async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cacheContext, tempTables) {
|
|
9950
10902
|
const joinKind = await resolveUpdateFromTargetJoinKind(stmt, from, client, cacheContext);
|
|
9951
|
-
const
|
|
10903
|
+
const checkScope = await resolveUpdateFromCheckScope(stmt, from, client, cacheContext, tempTables);
|
|
10904
|
+
const sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : "").concat(checkScope.sourceFields))];
|
|
9952
10905
|
const requiredSourceFields = [.../* @__PURE__ */ new Set([from.joinKeyField, ...sourceFields])];
|
|
9953
10906
|
const sourceRows = await loadUpdateFromSourceRows(
|
|
9954
10907
|
from,
|
|
@@ -9974,8 +10927,8 @@ async function resolveUpdateFromMatchedRecords(stmt, from, client, options, cach
|
|
|
9974
10927
|
}
|
|
9975
10928
|
if (sourceByKey.size === 0) return [];
|
|
9976
10929
|
const maxRecords = options.maxRecords ?? 1e4;
|
|
9977
|
-
const targetFields = collectUpdateFromTargetFields(stmt);
|
|
9978
|
-
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter }).query;
|
|
10930
|
+
const targetFields = [.../* @__PURE__ */ new Set([...collectUpdateFromTargetFields(stmt), ...checkScope.targetFields])];
|
|
10931
|
+
const filterQuery = from.targetFilter === null ? "" : updateToGetQuery({ ...stmt, from: null, where: from.targetFilter, checkGroups: void 0 }).query;
|
|
9979
10932
|
const targetRecords = [];
|
|
9980
10933
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
9981
10934
|
let fetchedTargetCount = 0;
|
|
@@ -10093,7 +11046,54 @@ function normalizeUpdateFromJoinKey(raw, kind, side) {
|
|
|
10093
11046
|
}
|
|
10094
11047
|
return JSON.stringify(decimal);
|
|
10095
11048
|
}
|
|
11049
|
+
async function executeCheckedPlainDml(stmt, client, options, cacheContext, tempTables) {
|
|
11050
|
+
const prepared = await prepareDmlValidation(
|
|
11051
|
+
stmt,
|
|
11052
|
+
client,
|
|
11053
|
+
options,
|
|
11054
|
+
cacheContext,
|
|
11055
|
+
tempTables,
|
|
11056
|
+
1,
|
|
11057
|
+
false,
|
|
11058
|
+
false
|
|
11059
|
+
);
|
|
11060
|
+
if (prepared.result.errors.length > 0) {
|
|
11061
|
+
const first = prepared.result.errors[0];
|
|
11062
|
+
throw new Error(
|
|
11063
|
+
`DmlValidationError: ${first["$err_code"]} ${first["$err_message"]} (row=${first["$err_row"]}, field=${first["$err_field"]})`
|
|
11064
|
+
);
|
|
11065
|
+
}
|
|
11066
|
+
const candidates = prepared.candidates;
|
|
11067
|
+
const confirmOperation = stmt.type.startsWith("INSERT") ? "INSERT" : "UPDATE";
|
|
11068
|
+
if (options.confirm && candidates.length > 0) {
|
|
11069
|
+
const ok = await options.confirm(candidates.length, confirmOperation);
|
|
11070
|
+
if (!ok) throw new OperationCancelledError(confirmOperation, candidates.length);
|
|
11071
|
+
}
|
|
11072
|
+
if (stmt.type === "INSERT" || stmt.type === "INSERT_SELECT") {
|
|
11073
|
+
const createdIds = [];
|
|
11074
|
+
for (let i = 0; i < candidates.length; i += 100) {
|
|
11075
|
+
const response = await client.postRecords({ app: stmt.appId, records: candidates.slice(i, i + 100).map((c) => c.record) });
|
|
11076
|
+
createdIds.push(response.ids);
|
|
11077
|
+
}
|
|
11078
|
+
return { type: "INSERT", createdIds, insertedCount: createdIds.flat().length };
|
|
11079
|
+
}
|
|
11080
|
+
if (stmt.type === "UPDATE") {
|
|
11081
|
+
const updates2 = candidates.map((candidate) => ({ id: candidate.targetId, record: candidate.record }));
|
|
11082
|
+
for (let i = 0; i < updates2.length; i += 100) await client.putRecords({ app: stmt.appId, records: updates2.slice(i, i + 100) });
|
|
11083
|
+
return { type: "UPDATE", updatedCount: updates2.length };
|
|
11084
|
+
}
|
|
11085
|
+
const inserts = candidates.filter((candidate) => candidate.mode === "create");
|
|
11086
|
+
const updates = candidates.filter((candidate) => candidate.mode === "update").map((candidate) => ({ id: candidate.targetId, record: candidate.record }));
|
|
11087
|
+
let insertedCount = 0;
|
|
11088
|
+
for (let i = 0; i < inserts.length; i += 100) {
|
|
11089
|
+
const response = await client.postRecords({ app: stmt.appId, records: inserts.slice(i, i + 100).map((c) => c.record) });
|
|
11090
|
+
insertedCount += response.ids.length;
|
|
11091
|
+
}
|
|
11092
|
+
for (let i = 0; i < updates.length; i += 100) await client.putRecords({ app: stmt.appId, records: updates.slice(i, i + 100) });
|
|
11093
|
+
return { type: "UPSERT", insertedCount, updatedCount: updates.length };
|
|
11094
|
+
}
|
|
10096
11095
|
async function executeInsert(stmt, client, options, cacheContext) {
|
|
11096
|
+
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext);
|
|
10097
11097
|
if (stmt.subtableCode) {
|
|
10098
11098
|
return executeInsertSubtable(stmt, client, options, cacheContext);
|
|
10099
11099
|
}
|
|
@@ -10114,6 +11114,7 @@ async function executeInsert(stmt, client, options, cacheContext) {
|
|
|
10114
11114
|
};
|
|
10115
11115
|
}
|
|
10116
11116
|
async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
11117
|
+
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
10117
11118
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10118
11119
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
10119
11120
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
@@ -10151,6 +11152,27 @@ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache
|
|
|
10151
11152
|
};
|
|
10152
11153
|
}
|
|
10153
11154
|
async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
11155
|
+
if (stmt.checkGroups?.length && isConstantFalseWhere(stmt.where)) {
|
|
11156
|
+
const fieldInfos2 = await loadWritableTopLevelDmlFields(
|
|
11157
|
+
stmt.appId,
|
|
11158
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
11159
|
+
client,
|
|
11160
|
+
cacheContext
|
|
11161
|
+
);
|
|
11162
|
+
await loadNumberPrecisionForTargets(
|
|
11163
|
+
stmt.appId,
|
|
11164
|
+
stmt.assignments.map((assignment) => assignment.field),
|
|
11165
|
+
fieldInfos2,
|
|
11166
|
+
client,
|
|
11167
|
+
cacheContext
|
|
11168
|
+
);
|
|
11169
|
+
const fieldTypes2 = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
11170
|
+
assertUpdateCheckRefs(stmt, fieldTypes2);
|
|
11171
|
+
assertCheckComparisonTypes(stmt, updateEvaluationTypes(fieldTypes2, stmt.appId));
|
|
11172
|
+
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
11173
|
+
return { type: "UPDATE", updatedCount: 0 };
|
|
11174
|
+
}
|
|
11175
|
+
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, tempTables);
|
|
10154
11176
|
if (stmt.subtableCode) {
|
|
10155
11177
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
10156
11178
|
return executeUpdateSubtable(stmt, client, options, cacheContext);
|
|
@@ -10170,6 +11192,7 @@ async function executeUpdate(stmt, client, options, cacheContext, tempTables) {
|
|
|
10170
11192
|
cacheContext
|
|
10171
11193
|
);
|
|
10172
11194
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
11195
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "UPDATE", updatedCount: 0 };
|
|
10173
11196
|
if (stmt.from != null) {
|
|
10174
11197
|
return executeUpdateFrom(stmt, stmt.from, client, options, cacheContext, tempTables);
|
|
10175
11198
|
}
|
|
@@ -10251,6 +11274,7 @@ function collectUpdateFromTargetFields(stmt) {
|
|
|
10251
11274
|
}
|
|
10252
11275
|
async function executeDelete(stmt, client, options, cacheContext) {
|
|
10253
11276
|
await assertDmlWhereCapability(stmt, client, cacheContext);
|
|
11277
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "DELETE", deletedCount: 0 };
|
|
10254
11278
|
if (stmt.subtableCode) {
|
|
10255
11279
|
return executeDeleteSubtable(stmt, client, options, cacheContext);
|
|
10256
11280
|
}
|
|
@@ -10276,6 +11300,7 @@ async function executeDelete(stmt, client, options, cacheContext) {
|
|
|
10276
11300
|
return { type: "DELETE", deletedCount: ids.length };
|
|
10277
11301
|
}
|
|
10278
11302
|
async function executeUpsert(stmt, client, options, cacheContext) {
|
|
11303
|
+
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext);
|
|
10279
11304
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10280
11305
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
10281
11306
|
const toInsert = [];
|
|
@@ -10632,6 +11657,7 @@ async function executeReorder(stmt, client, options, cacheContext) {
|
|
|
10632
11657
|
cacheContext
|
|
10633
11658
|
);
|
|
10634
11659
|
const reorderFields = await getFieldsCached(stmt.appId, client, cacheContext);
|
|
11660
|
+
if (isConstantFalseWhere(stmt.where)) return { type: "REORDER", reorderedParentCount: 0 };
|
|
10635
11661
|
const reorderSemanticsByCode = new Map(reorderFields.map((field) => [
|
|
10636
11662
|
field.code,
|
|
10637
11663
|
field.semantics ?? resolveFieldSemantics(field)
|
|
@@ -10710,6 +11736,7 @@ function evalOrderKeyForRow(key, row) {
|
|
|
10710
11736
|
}
|
|
10711
11737
|
}
|
|
10712
11738
|
async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
11739
|
+
if (stmt.checkGroups?.length) return executeCheckedPlainDml(stmt, client, options, cacheContext, cteCache);
|
|
10713
11740
|
const fieldInfos = await loadWritableTopLevelDmlFields(stmt.appId, stmt.fields, client, cacheContext);
|
|
10714
11741
|
const numberPrecision = await loadNumberPrecisionForTargets(stmt.appId, stmt.fields, fieldInfos, client, cacheContext);
|
|
10715
11742
|
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
@@ -10857,6 +11884,8 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCa
|
|
|
10857
11884
|
}));
|
|
10858
11885
|
break;
|
|
10859
11886
|
}
|
|
11887
|
+
case "BOOLEAN":
|
|
11888
|
+
break;
|
|
10860
11889
|
}
|
|
10861
11890
|
}
|
|
10862
11891
|
async function resolveSetSubqueries(assignments, client, options, cacheContext) {
|
|
@@ -10894,9 +11923,11 @@ async function resolveScalarColumns(columns, client, options, cacheContext, cteC
|
|
|
10894
11923
|
pending.forEach(([i], idx) => cache.set(i, values[idx]));
|
|
10895
11924
|
return cache;
|
|
10896
11925
|
}
|
|
11926
|
+
var validateExplainInfo = /* @__PURE__ */ new WeakMap();
|
|
10897
11927
|
async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords = 1e4) {
|
|
10898
11928
|
const fieldApps = /* @__PURE__ */ new Set();
|
|
10899
11929
|
const processStatusApps = /* @__PURE__ */ new Set();
|
|
11930
|
+
const numberPrecisionApps = /* @__PURE__ */ new Set();
|
|
10900
11931
|
const tracedClient = {
|
|
10901
11932
|
...client,
|
|
10902
11933
|
getFields: async (appId) => {
|
|
@@ -10906,6 +11937,10 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
10906
11937
|
getProcessStatuses: async (appId) => {
|
|
10907
11938
|
processStatusApps.add(appId);
|
|
10908
11939
|
return client.getProcessStatuses(appId);
|
|
11940
|
+
},
|
|
11941
|
+
getNumberPrecision: async (appId) => {
|
|
11942
|
+
numberPrecisionApps.add(appId);
|
|
11943
|
+
return client.getNumberPrecision(appId);
|
|
10909
11944
|
}
|
|
10910
11945
|
};
|
|
10911
11946
|
const capabilities = /* @__PURE__ */ new Map();
|
|
@@ -10954,6 +11989,51 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
10954
11989
|
}));
|
|
10955
11990
|
}
|
|
10956
11991
|
}
|
|
11992
|
+
} else if (typed["type"] === "VALIDATE") {
|
|
11993
|
+
const validate = node;
|
|
11994
|
+
fieldApps.add(validate.appId);
|
|
11995
|
+
const fields = await getFieldsCached(validate.appId, tracedClient, cacheContext);
|
|
11996
|
+
const infoByCode = new Map(fields.map((field) => [field.code, field]));
|
|
11997
|
+
const targets = resolveExistingValidationTargets(validate, fields);
|
|
11998
|
+
const checks = collectCheckFieldRefs(validate.checkGroups ?? []);
|
|
11999
|
+
const whereFields = collectValidateWhereFields(validate.where);
|
|
12000
|
+
for (const ref of checks) {
|
|
12001
|
+
if (ref.field !== "$id" && !infoByCode.has(ref.field)) {
|
|
12002
|
+
throw customCheckParseError(`CHECK \u306E\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F APP${validate.appId} \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
12003
|
+
}
|
|
12004
|
+
}
|
|
12005
|
+
for (const field of whereFields) {
|
|
12006
|
+
if (field !== "$id" && !infoByCode.has(field)) {
|
|
12007
|
+
throw new Error(`ArgumentError: WHERE field ${field} does not exist in APP${validate.appId}.`);
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
const types = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
12011
|
+
types.set("$id", "RECORD_NUMBER");
|
|
12012
|
+
assertCheckComparisonTypes(validate, types);
|
|
12013
|
+
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));
|
|
12014
|
+
if (capability.capability === "UNSUPPORTED") {
|
|
12015
|
+
throw new Error(`ArgumentError: WHERE predicate is unsupported (${formatWhereCapabilityFailure(capability)}).`);
|
|
12016
|
+
}
|
|
12017
|
+
const fieldTypes = new Map(fields.map((field) => [field.code, field.fieldType]));
|
|
12018
|
+
const fieldOptions = new Map(fields.flatMap((field) => field.optionOrder ? [[field.code, new Set(Object.keys(field.optionOrder))]] : []));
|
|
12019
|
+
const prefilter = validate.where === null ? null : capability.capability === "EXACT_PUSHDOWN" ? validate.where : extractSafePushdownLeaves(validate.where, {
|
|
12020
|
+
allowUnqualifiedFields: true,
|
|
12021
|
+
fieldTypes,
|
|
12022
|
+
fieldOptions,
|
|
12023
|
+
allowKlike: false
|
|
12024
|
+
});
|
|
12025
|
+
const needsPrecision = targets.some((field) => field.fieldType === "NUMBER");
|
|
12026
|
+
if (needsPrecision) {
|
|
12027
|
+
numberPrecisionApps.add(validate.appId);
|
|
12028
|
+
await getNumberPrecisionCached(validate.appId, tracedClient, cacheContext);
|
|
12029
|
+
}
|
|
12030
|
+
validateExplainInfo.set(validate, {
|
|
12031
|
+
targetFields: targets.map((field) => field.code),
|
|
12032
|
+
fetchFields: [.../* @__PURE__ */ new Set(["$id", ...targets.map((field) => field.code), ...whereFields, ...checks.map((ref) => ref.field)])],
|
|
12033
|
+
capability,
|
|
12034
|
+
prefilter,
|
|
12035
|
+
numberPrecision: needsPrecision
|
|
12036
|
+
});
|
|
10957
12037
|
} else if (typed["type"] === "UPDATE" || typed["type"] === "DELETE") {
|
|
10958
12038
|
fieldApps.add(node.appId);
|
|
10959
12039
|
await assertDmlWhereCapability(
|
|
@@ -10984,12 +12064,13 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
|
|
|
10984
12064
|
}));
|
|
10985
12065
|
}
|
|
10986
12066
|
}
|
|
10987
|
-
return { capabilities, orderPlans, fieldApps, processStatusApps };
|
|
12067
|
+
return { capabilities, orderPlans, fieldApps, processStatusApps, numberPrecisionApps };
|
|
10988
12068
|
}
|
|
10989
12069
|
function explainMetadataLines(analysis) {
|
|
10990
12070
|
return [
|
|
10991
12071
|
...[...analysis.fieldApps].sort((a, b) => a - b).map((appId) => ` metadata API: form definition APP${appId}`),
|
|
10992
|
-
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
|
|
12072
|
+
...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`),
|
|
12073
|
+
...[...analysis.numberPrecisionApps].sort((a, b) => a - b).map((appId) => ` metadata API: number precision APP${appId}`)
|
|
10993
12074
|
];
|
|
10994
12075
|
}
|
|
10995
12076
|
async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords = 1e4, cursorMaxActive = 2) {
|
|
@@ -11000,7 +12081,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
11000
12081
|
const plans = [];
|
|
11001
12082
|
for (let i = 0; i < statements.length; i++) {
|
|
11002
12083
|
const stmt = statements[i];
|
|
11003
|
-
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr:
|
|
12084
|
+
const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveBatchVariableReferences(stmt.expr, variables) } : stmt : resolveBatchVariableReferences(stmt, variables);
|
|
11004
12085
|
validateKlikeStatement(planStmt);
|
|
11005
12086
|
const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords);
|
|
11006
12087
|
const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
|
|
@@ -11016,7 +12097,7 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
|
|
|
11016
12097
|
plan: statementPlan.length === 0 ? metadataPlan : [statementPlan[0], ...metadataPlan, ...statementPlan.slice(1)]
|
|
11017
12098
|
});
|
|
11018
12099
|
if (stmt.type === "SET_VARIABLE" || stmt.type === "DECLARE_VARIABLE") {
|
|
11019
|
-
variables.set(stmt.name, { type: "string", value: `@${stmt.name}` });
|
|
12100
|
+
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}` });
|
|
11020
12101
|
}
|
|
11021
12102
|
}
|
|
11022
12103
|
return { statementCount: statements.length, statements: plans };
|
|
@@ -11151,8 +12232,30 @@ function buildExplainPlan(query, label, capabilities, orderPlans) {
|
|
|
11151
12232
|
if (query.type === "UPDATE") return buildUpdatePlan(query, label, capabilities, orderPlans);
|
|
11152
12233
|
if (query.type === "DELETE") return buildDeletePlan(query, label);
|
|
11153
12234
|
if (query.type === "REORDER") return buildReorderPlan(query, label);
|
|
12235
|
+
if (query.type === "VALIDATE") return buildValidatePlan(query, label);
|
|
11154
12236
|
return buildSelectPlan(query, label, capabilities, orderPlans);
|
|
11155
12237
|
}
|
|
12238
|
+
function buildValidatePlan(stmt, label) {
|
|
12239
|
+
const info = validateExplainInfo.get(stmt);
|
|
12240
|
+
const lines = [];
|
|
12241
|
+
if (label) lines.push(label);
|
|
12242
|
+
lines.push(`VALIDATE APP${stmt.appId}`);
|
|
12243
|
+
lines.push(" operation: read-only existing-record constraint audit (writesKintone=false)");
|
|
12244
|
+
lines.push(" fetch API: GET records via offset + $id keyset paging (Cursor API unused)");
|
|
12245
|
+
lines.push(" complete input: required (onLimit=truncate disabled)");
|
|
12246
|
+
if (!info) {
|
|
12247
|
+
lines.push(" metadata: form definition required; number precision required for NUMBER targets");
|
|
12248
|
+
return lines;
|
|
12249
|
+
}
|
|
12250
|
+
lines.push(` WHERE capability: ${info.capability.capability}`);
|
|
12251
|
+
lines.push(` kintone query: ${info.prefilter === null ? "(\u5168\u4EF6\u53D6\u5F97)" : whereToKintone(info.prefilter)}`);
|
|
12252
|
+
lines.push(` audit fields: ${info.targetFields.length === 0 ? "(\u306A\u3057)" : info.targetFields.join(", ")}`);
|
|
12253
|
+
lines.push(` fetch fields: ${info.fetchFields.join(", ")}`);
|
|
12254
|
+
lines.push(` number precision: ${info.numberPrecision ? "required" : "not required"}`);
|
|
12255
|
+
lines.push(" local checks: original WHERE re-evaluation + built-in constraints + CHECK groups");
|
|
12256
|
+
lines.push(" records/mutation API during EXPLAIN: none; violation count unavailable");
|
|
12257
|
+
return lines;
|
|
12258
|
+
}
|
|
11156
12259
|
function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
11157
12260
|
const whereCapability = capabilities?.get(stmt) ?? (capabilities ? [...capabilities].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
11158
12261
|
const orderPlan = orderPlans?.get(stmt) ?? (orderPlans ? [...orderPlans].find(([candidate]) => JSON.stringify(candidate) === JSON.stringify(stmt))?.[1] : void 0);
|
|
@@ -11164,6 +12267,12 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
|
|
|
11164
12267
|
const lines = [];
|
|
11165
12268
|
if (label) lines.push(label);
|
|
11166
12269
|
lines.push(` mode: ${mode}`);
|
|
12270
|
+
if (isConstantFalseWhere(stmt.where)) {
|
|
12271
|
+
lines.push(" predicate: constant false");
|
|
12272
|
+
lines.push(" records API access: none");
|
|
12273
|
+
lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
|
|
12274
|
+
return lines;
|
|
12275
|
+
}
|
|
11167
12276
|
if (orderPlan) {
|
|
11168
12277
|
lines.push(` order plan: ${orderPlan.kind}`);
|
|
11169
12278
|
if (orderPlan.reasonCodes.length > 0) lines.push(` order reason: ${orderPlan.reasonCodes.join(", ")}`);
|
|
@@ -11311,6 +12420,8 @@ function collectSubqueryPlans(stmt, capabilities, orderPlans) {
|
|
|
11311
12420
|
break;
|
|
11312
12421
|
case "NULL_CHECK":
|
|
11313
12422
|
break;
|
|
12423
|
+
case "BOOLEAN":
|
|
12424
|
+
break;
|
|
11314
12425
|
}
|
|
11315
12426
|
};
|
|
11316
12427
|
visitWhere(stmt.where);
|
|
@@ -11363,7 +12474,7 @@ function buildUpdatePlan(stmt, label, capabilities, orderPlans) {
|
|
|
11363
12474
|
} else {
|
|
11364
12475
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
11365
12476
|
}
|
|
11366
|
-
lines.push(` api: GET /k/v1/records.json \u2192 PUT /k/v1/records.json`);
|
|
12477
|
+
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`);
|
|
11367
12478
|
const setTypes = [];
|
|
11368
12479
|
if (isArith) setTypes.push("\u7B97\u8853 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A08\u7B97\uFF09");
|
|
11369
12480
|
if (isStringFunc) setTypes.push("\u6587\u5B57\u5217\u95A2\u6570 SET\uFF08\u73FE\u5728\u5024\u3092\u53D6\u5F97\u3057\u3066\u8A55\u4FA1\uFF09");
|
|
@@ -11394,7 +12505,7 @@ function buildDeletePlan(stmt, label) {
|
|
|
11394
12505
|
lines.push(` [DELETE]`);
|
|
11395
12506
|
lines.push(` target: APP${stmt.appId} (${stmt.appId})`);
|
|
11396
12507
|
lines.push(` kintone query: ${safeWhereToKintone(stmt.where)}`);
|
|
11397
|
-
lines.push(` api: GET /k/v1/records.json \u2192 DELETE /k/v1/records.json`);
|
|
12508
|
+
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`);
|
|
11398
12509
|
return lines;
|
|
11399
12510
|
}
|
|
11400
12511
|
function buildUpsertPlan(stmt, label) {
|
|
@@ -11434,7 +12545,7 @@ function buildReorderPlan(stmt, label) {
|
|
|
11434
12545
|
` table: ${target}`,
|
|
11435
12546
|
` scope: ${scope}`,
|
|
11436
12547
|
` by: ${byStr}`,
|
|
11437
|
-
` 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`
|
|
12548
|
+
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`
|
|
11438
12549
|
];
|
|
11439
12550
|
if (!stmt.all && stmt.where) {
|
|
11440
12551
|
lines.splice(5, 0, ` where: ${safeWhereToKintone(stmt.where)}`);
|
|
@@ -11446,6 +12557,7 @@ function formatOrderByItem(item) {
|
|
|
11446
12557
|
return `${key} ${item.direction}`;
|
|
11447
12558
|
}
|
|
11448
12559
|
function safeWhereToKintone(where) {
|
|
12560
|
+
if (where.type === "BOOLEAN") return where.value ? "TRUE" : "FALSE (constant)";
|
|
11449
12561
|
try {
|
|
11450
12562
|
return whereToKintone(where);
|
|
11451
12563
|
} catch {
|
|
@@ -11457,6 +12569,7 @@ function collectArithRefFields(stmt) {
|
|
|
11457
12569
|
for (const { value } of stmt.assignments) {
|
|
11458
12570
|
if (value.type === "ARITH") collectArithNodeRefs(value, refs);
|
|
11459
12571
|
if (value.type === "STRING_FUNC") collectArithNodeRefs(value, refs);
|
|
12572
|
+
if (value.type === "SCALAR_ARITH" || value.type === "CONCAT_OP") collectScalarNodeRefs(value, refs);
|
|
11460
12573
|
}
|
|
11461
12574
|
return [...refs];
|
|
11462
12575
|
}
|
|
@@ -11471,10 +12584,74 @@ function collectArithNodeRefs(node, out) {
|
|
|
11471
12584
|
}
|
|
11472
12585
|
if (node.type === "STRING_FUNC") {
|
|
11473
12586
|
for (const arg of node.args) {
|
|
11474
|
-
if (arg.type !== "
|
|
11475
|
-
|
|
12587
|
+
if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") collectScalarNodeRefs(arg, out);
|
|
12588
|
+
}
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12591
|
+
async function resolveUpdateFromCheckScope(stmt, from, client, cacheContext, tempTables) {
|
|
12592
|
+
const refs = checkRefs(stmt);
|
|
12593
|
+
const targetTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
|
|
12594
|
+
const sourceTableName = from.cteName;
|
|
12595
|
+
const sourceTypes = sourceTableName !== null ? new Map((tempTables?.get(sourceTableName)?.columns ?? []).map((column) => [
|
|
12596
|
+
column,
|
|
12597
|
+
tempTables?.get(sourceTableName)?.columnMeta?.get(column)?.fieldType ?? (tempTables?.get(sourceTableName)?.columnMeta?.get(column)?.semantics?.compareMode === "number" ? "NUMBER" : "SINGLE_LINE_TEXT")
|
|
12598
|
+
])) : await getFieldTypeMap(from.appId, client, cacheContext);
|
|
12599
|
+
const targetFields = /* @__PURE__ */ new Set();
|
|
12600
|
+
const sourceFields = /* @__PURE__ */ new Set();
|
|
12601
|
+
for (const ref of refs) {
|
|
12602
|
+
if (ref.tableAlias !== null) {
|
|
12603
|
+
if (ref.tableAlias.toLowerCase() === `app${stmt.appId}`.toLowerCase()) {
|
|
12604
|
+
if (ref.field !== "$id" && !targetTypes.has(ref.field)) throw customCheckParseError(`CHECK \u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
12605
|
+
targetFields.add(ref.field);
|
|
12606
|
+
} else if (ref.tableAlias.toLowerCase() === from.alias.toLowerCase()) {
|
|
12607
|
+
if (ref.field !== "$id" && !sourceTypes.has(ref.field)) throw customCheckParseError(`CHECK \u306E\u30BD\u30FC\u30B9\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F\u5B58\u5728\u3057\u307E\u305B\u3093`);
|
|
12608
|
+
sourceFields.add(ref.field);
|
|
12609
|
+
} else {
|
|
12610
|
+
throw customCheckParseError(`CHECK \u306E\u4FEE\u98FE\u5B50 ${ref.tableAlias} \u306F\u66F4\u65B0\u5148\u307E\u305F\u306F FROM alias \u3067\u306F\u3042\u308A\u307E\u305B\u3093`);
|
|
11476
12611
|
}
|
|
12612
|
+
continue;
|
|
11477
12613
|
}
|
|
12614
|
+
const inTarget = ref.field === "$id" || targetTypes.has(ref.field);
|
|
12615
|
+
const inSource = ref.field === "$id" || sourceTypes.has(ref.field);
|
|
12616
|
+
if (!inTarget) {
|
|
12617
|
+
throw customCheckParseError(`UPDATE FROM \u306E CHECK \u3067\u306F\u30BD\u30FC\u30B9\u5217 ${ref.field} \u3092\u4FEE\u98FE\u3057\u3066\u304F\u3060\u3055\u3044`);
|
|
12618
|
+
}
|
|
12619
|
+
if (inSource) {
|
|
12620
|
+
throw customCheckParseError(`UPDATE FROM \u306E CHECK \u306E\u975E\u4FEE\u98FE\u30D5\u30A3\u30FC\u30EB\u30C9 ${ref.field} \u306F\u66D6\u6627\u3067\u3059`);
|
|
12621
|
+
}
|
|
12622
|
+
targetFields.add(ref.field);
|
|
12623
|
+
}
|
|
12624
|
+
const evaluationTypes = /* @__PURE__ */ new Map();
|
|
12625
|
+
for (const [field, type] of targetTypes) {
|
|
12626
|
+
evaluationTypes.set(field, type);
|
|
12627
|
+
evaluationTypes.set(`APP${stmt.appId}.${field}`, type);
|
|
12628
|
+
}
|
|
12629
|
+
evaluationTypes.set("$id", "RECORD_NUMBER");
|
|
12630
|
+
evaluationTypes.set(`APP${stmt.appId}.$id`, "RECORD_NUMBER");
|
|
12631
|
+
for (const [field, type] of sourceTypes) evaluationTypes.set(`${from.alias}.${field}`, type);
|
|
12632
|
+
return { targetFields: [...targetFields], sourceFields: [...sourceFields], evaluationTypes };
|
|
12633
|
+
}
|
|
12634
|
+
function updateFromEvaluationRow(pair, appId, sourceAlias) {
|
|
12635
|
+
if (!pair) return {};
|
|
12636
|
+
const target = flatten(pair.target, null);
|
|
12637
|
+
return Object.fromEntries([
|
|
12638
|
+
...Object.entries(target),
|
|
12639
|
+
...Object.entries(target).map(([field, value]) => [`APP${appId}.${field}`, value]),
|
|
12640
|
+
...Object.entries(pair.source).map(([field, value]) => [`${sourceAlias}.${field}`, value])
|
|
12641
|
+
]);
|
|
12642
|
+
}
|
|
12643
|
+
function collectScalarNodeRefs(node, out) {
|
|
12644
|
+
if (node.type === "FIELD") {
|
|
12645
|
+
out.add(node.tableAlias ? `${node.tableAlias}.${node.field}` : node.field);
|
|
12646
|
+
return;
|
|
12647
|
+
}
|
|
12648
|
+
if (node.type === "STRING_FUNC") {
|
|
12649
|
+
for (const arg of node.args) if (arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") collectScalarNodeRefs(arg, out);
|
|
12650
|
+
return;
|
|
12651
|
+
}
|
|
12652
|
+
if (node.type === "SCALAR_ARITH" || node.type === "CONCAT_OP") {
|
|
12653
|
+
collectScalarNodeRefs(node.left, out);
|
|
12654
|
+
collectScalarNodeRefs(node.right, out);
|
|
11478
12655
|
}
|
|
11479
12656
|
}
|
|
11480
12657
|
function formatAssignment(a) {
|
|
@@ -14348,7 +15525,7 @@ async function run() {
|
|
|
14348
15525
|
isDmlStatement = writesKintone(stmt);
|
|
14349
15526
|
hasWhere = hasWhereClause(stmt);
|
|
14350
15527
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
14351
|
-
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlType(stmtType);
|
|
15528
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || stmtType === "VALIDATE" || isDmlType(stmtType);
|
|
14352
15529
|
if (!supported) {
|
|
14353
15530
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
14354
15531
|
`);
|
|
@@ -14398,10 +15575,11 @@ async function run() {
|
|
|
14398
15575
|
const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
|
|
14399
15576
|
const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
|
|
14400
15577
|
const isValidationOnly = batchAnalysis?.containsValidationOnly === true || parsedStmt !== null && typeof parsedStmt === "object" && "validateOnly" in parsedStmt && parsedStmt.validateOnly === true;
|
|
14401
|
-
const
|
|
15578
|
+
const isExistingRecordValidation = batchAnalysis?.statements.some((s) => s.statementType === "VALIDATE") === true || getStatementType(parsedStmt) === "VALIDATE";
|
|
15579
|
+
const surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly || isExistingRecordValidation;
|
|
14402
15580
|
const effectiveOnLimit = surfaceForcesOnLimitError ? "error" : onLimit;
|
|
14403
15581
|
if (surfaceForcesOnLimitError && onLimit === "truncate" && !quiet && !args.dryRun) {
|
|
14404
|
-
const reason = isDmlStatement || batchContainsDml ? "DML" : "VALIDATE ONLY";
|
|
15582
|
+
const reason = isDmlStatement || batchContainsDml ? "DML" : isExistingRecordValidation ? "VALIDATE" : "VALIDATE ONLY";
|
|
14405
15583
|
process.stderr.write(`note: onLimit=truncate is ignored for ${reason} (forced to error)
|
|
14406
15584
|
`);
|
|
14407
15585
|
}
|