@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/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 !== "STRFUNC_COL") return false;
1318
- return column.expr.args.some((arg) => this.stringFuncArgHasAggregate(arg));
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
- if (arg.type === "STRING_FUNC") {
1323
- return arg.args.some((nested) => this.stringFuncArgHasAggregate(nested));
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
- const tok = this.peek();
1658
- if (tok.kind === "STRING" /* STRING */) {
1659
- this.advance();
1660
- return { type: "STRING", value: tok.value };
1661
- }
1662
- if (this.tryStringFuncName() !== null) {
1663
- return this.parseStringFuncExpr();
1664
- }
1665
- const startPos = this.pos;
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.pos = startPos;
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.expect("(" /* LPAREN */);
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.expect("(" /* LPAREN */);
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
- collectArithNode(arg, out);
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
- if (arg.type === "STRING_FUNC") return hasAggregateInStringFuncExpr(arg);
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
- walkArith(arg, phase);
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 === "STRING_FUNC") {
3519
- walkStringFunc(result, phase);
3806
+ if (result.type === "FIELD_REF" || result.type === "ARITH") {
3807
+ walkArith(result, phase);
3520
3808
  return;
3521
3809
  }
3522
- walkArith(result, phase);
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 arithNodeLabel(a);
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
- if (obj["type"] === "VARIABLE" && typeof obj["name"] === "string") {
4143
- refs.add(obj["name"]);
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 = /* @__PURE__ */ new Set();
4525
+ const variableRefs = [];
4196
4526
  collectVariableRefs(stmt, variableRefs);
4197
- for (const name of variableRefs) {
4198
- const def = variableDefs.get(name);
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.referencedBy.push(index);
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, { index, referencedBy: [] });
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
- return String(evalArithExpr(arg, row));
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 === "STRING") return result.value;
5281
- if (result.type === "STRING_FUNC") return evalStringFunc(result, row);
5282
- if (result.type === "FIELD_REF") return row[result.field] ?? "";
5283
- return String(evalArithExpr(result, row));
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
- collectArithNode2(arg, out);
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
- collectArithNode2(result, out);
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
- return String(evalArithExpr(result, row));
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 arithColDefaultKey(a);
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
- if (arg.type === "STRING_FUNC") return hasAggregateInStringFuncExpr2(arg);
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 = resolveVariableRefs(stmt, variables);
8352
+ const resolvedStmt2 = resolveBatchVariableReferences(stmt, variables);
7640
8353
  validateKlikeStatement(resolvedStmt2);
7641
- if (resolvedStmt2.expr.type === "SCALAR_SUBQUERY") {
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 = resolveVariableRefs(stmt, variables);
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 resolveVariableRefs(node, variables) {
8601
+ function resolveBatchVariableReferences(node, variables) {
7865
8602
  if (Array.isArray(node)) {
7866
- return node.map((v) => resolveVariableRefs(v, variables));
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
- return Object.fromEntries(
7878
- Object.entries(obj).map(([key, value]) => [key, resolveVariableRefs(value, variables)])
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 if (arg.type === "STRING_FUNC") {
8529
- collectStringFuncAggregateRefs(arg, out);
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
- const source = resolveField2(aggregateFieldRef(result.field));
8710
- return source ?? unknownStringColumnMeta();
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 mainFetch = fetchTableRecordsForFullScan(
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
- if (selectResult.columns.length !== stmt.fields.length) {
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 resolved = await fetchRecordsForSharedPlan(client.getRecords, getParams.app, getParams.query, [...getParams.fields], {
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
- const resolved = await resolveDmlTargetIds(client.getRecords, getParams.app, getParams.query, {
9911
- maxRecords: options.maxRecords ?? 1e4,
9912
- parallel: options.fetchParallel ?? 1
9913
- });
9914
- records = updateToPutBatches(stmt, resolved.ids, fieldTypes).flatMap((batch) => batch.records);
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 sourceFields = [...new Set(stmt.assignments.filter((a) => a.value.type === "SOURCE_FIELD").map((a) => a.value.type === "SOURCE_FIELD" ? a.value.field : ""))];
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: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
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 !== "STRING" && arg.type !== "AGG_REF" && arg.type !== "AGG_ARITH") {
11475
- collectArithNodeRefs(arg, out);
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 surfaceForcesOnLimitError = isDmlStatement || batchContainsDml || isValidationOnly;
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
  }