@rex0220/kintone-sql-tools 1.9.0 → 1.10.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
@@ -33,7 +33,8 @@ __export(index_exports, {
33
33
  parseConsoleMetaCommand: () => parseConsoleMetaCommand,
34
34
  parseTokenFile: () => parseTokenFile,
35
35
  parseTokenMap: () => parseTokenMap,
36
- shouldExitOnEmpty: () => shouldExitOnEmpty
36
+ shouldExitOnEmpty: () => shouldExitOnEmpty,
37
+ writeBatchOutput: () => writeBatchOutput
37
38
  });
38
39
  module.exports = __toCommonJS(index_exports);
39
40
  var import_fs2 = require("fs");
@@ -83,6 +84,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
83
84
  ["AVG", "AVG" /* AVG */],
84
85
  ["MAX", "MAX" /* MAX */],
85
86
  ["MIN", "MIN" /* MIN */],
87
+ ["ASSERT", "ASSERT" /* ASSERT */],
86
88
  ["AND", "AND" /* AND */],
87
89
  ["OR", "OR" /* OR */],
88
90
  ["NOT", "NOT" /* NOT */],
@@ -428,6 +430,58 @@ function isJapanese(cp) {
428
430
 
429
431
  // src/parser/parser.ts
430
432
  var MAX_BATCH_STATEMENTS = 20;
433
+ var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
434
+ "IDENT" /* IDENT */,
435
+ "BIDENT" /* BIDENT */,
436
+ "COUNT" /* COUNT */,
437
+ "SUM" /* SUM */,
438
+ "AVG" /* AVG */,
439
+ "MAX" /* MAX */,
440
+ "MIN" /* MIN */,
441
+ "TODAY" /* TODAY */,
442
+ "NOW" /* NOW */,
443
+ "LOGINUSER" /* LOGINUSER */,
444
+ "UPPER" /* UPPER */,
445
+ "LOWER" /* LOWER */,
446
+ "TRIM" /* TRIM */,
447
+ "LTRIM" /* LTRIM */,
448
+ "RTRIM" /* RTRIM */,
449
+ "LENGTH" /* LENGTH */,
450
+ "SUBSTRING" /* SUBSTRING */,
451
+ "SUBSTR" /* SUBSTR */,
452
+ "CONCAT" /* CONCAT */,
453
+ "REPLACE" /* REPLACE */,
454
+ "COALESCE" /* COALESCE */,
455
+ "NULLIF" /* NULLIF */,
456
+ "ISNULL" /* ISNULL */,
457
+ "CAST" /* CAST */,
458
+ "CONVERT" /* CONVERT */,
459
+ "FORMAT" /* FORMAT */,
460
+ "ROUND" /* ROUND */,
461
+ "FLOOR" /* FLOOR */,
462
+ "CEIL" /* CEIL */,
463
+ "CEILING" /* CEILING */,
464
+ "ABS" /* ABS */,
465
+ "MOD" /* MOD */,
466
+ "POWER" /* POWER */,
467
+ "POW" /* POW */,
468
+ "SQRT" /* SQRT */,
469
+ "YEAR" /* YEAR */,
470
+ "MONTH" /* MONTH */,
471
+ "DAY" /* DAY */,
472
+ "DATE_FORMAT" /* DATE_FORMAT */,
473
+ "DATEDIFF" /* DATEDIFF */,
474
+ "DATE_ADD" /* DATE_ADD */,
475
+ "IF" /* IF */
476
+ ]);
477
+ function needsSpaceBetween(prev, cur) {
478
+ if (prev.kind === "(" /* LPAREN */ || prev.kind === "." /* DOT */) return false;
479
+ if (cur.kind === ")" /* RPAREN */ || cur.kind === "," /* COMMA */ || cur.kind === "." /* DOT */) return false;
480
+ if (cur.kind === "(" /* LPAREN */) {
481
+ return !FUNC_CALL_PREFIX_KINDS.has(prev.kind);
482
+ }
483
+ return true;
484
+ }
431
485
  var ParseError = class extends Error {
432
486
  constructor(message, token) {
433
487
  super(`${message}\uFF08\u4F4D\u7F6E ${token.pos}\u3001\u30C8\u30FC\u30AF\u30F3: \u300C${token.value}\u300D\uFF09`);
@@ -517,6 +571,8 @@ var Parser = class {
517
571
  return this.parseDescribe();
518
572
  case "EXPLAIN" /* EXPLAIN */:
519
573
  return this.parseExplain();
574
+ case "ASSERT" /* ASSERT */:
575
+ return this.parseAssert();
520
576
  case "IDENT" /* IDENT */: {
521
577
  const upper = tok.value.toUpperCase();
522
578
  if (upper === "CREATE") return this.parseCreateTempTable();
@@ -527,7 +583,7 @@ var Parser = class {
527
583
  break;
528
584
  }
529
585
  throw new ParseError(
530
- "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
586
+ "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN / CREATE TEMP TABLE / DROP TEMP TABLE / ASSERT \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
531
587
  tok
532
588
  );
533
589
  }
@@ -621,6 +677,174 @@ var Parser = class {
621
677
  return { type: "EXPLAIN", query };
622
678
  }
623
679
  // ----------------------------------------------------------
680
+ // ASSERT
681
+ //
682
+ // ASSERT <式> <比較演算子> <式>
683
+ // ASSERT <式> BETWEEN <式> AND <式>
684
+ //
685
+ // 式: リテラル / 算術式 / スカラーサブクエリ。
686
+ // フィールド参照(FROM コンテキストがない)・AND / OR 複合条件・
687
+ // 裸の値のみ(ASSERT 1)は ParseError。
688
+ // ----------------------------------------------------------
689
+ parseAssert() {
690
+ this.expect("ASSERT" /* ASSERT */);
691
+ const condStart = this.pos;
692
+ const left = this.parseAssertOperand();
693
+ const opTok = this.peek();
694
+ if (this.consume("BETWEEN" /* BETWEEN */)) {
695
+ const low = this.parseAssertOperand();
696
+ this.expect(
697
+ "AND" /* AND */,
698
+ "ASSERT \u306E BETWEEN \u306B\u306F AND \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: ASSERT (SELECT COUNT(*) FROM #t) BETWEEN 1 AND 500\uFF09"
699
+ );
700
+ const high = this.parseAssertOperand();
701
+ this.rejectAssertCompound();
702
+ return {
703
+ type: "ASSERT",
704
+ left,
705
+ op: "BETWEEN",
706
+ right: null,
707
+ low,
708
+ high,
709
+ text: this.renderTokenRange(condStart, this.pos)
710
+ };
711
+ }
712
+ const op = this.tryAssertCompareOp();
713
+ if (op === null) {
714
+ throw new ParseError(
715
+ "ASSERT \u306B\u306F\u6BD4\u8F03\u6F14\u7B97\u5B50\uFF08= <> < <= > >=\uFF09\u307E\u305F\u306F BETWEEN \u304C\u5FC5\u8981\u3067\u3059\uFF08\u5024\u306E\u307F\u306E ASSERT \u306F\u4E0D\u53EF\uFF09",
716
+ opTok
717
+ );
718
+ }
719
+ const right = this.parseAssertOperand();
720
+ this.rejectAssertCompound();
721
+ return {
722
+ type: "ASSERT",
723
+ left,
724
+ op,
725
+ right,
726
+ low: null,
727
+ high: null,
728
+ text: this.renderTokenRange(condStart, this.pos)
729
+ };
730
+ }
731
+ /** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
732
+ parseAssertOperand() {
733
+ const tok = this.peek();
734
+ if (tok.kind === "STRING" /* STRING */) {
735
+ this.advance();
736
+ return { type: "STRING", value: tok.value };
737
+ }
738
+ if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
739
+ this.advance();
740
+ const query = this.parseSelect();
741
+ this.expect(")" /* RPAREN */);
742
+ if (this.isArithOp(this.peek().kind)) {
743
+ throw new ParseError(
744
+ "\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u306E\u5F8C\u306B\u7B97\u8853\u6F14\u7B97\u5B50\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: ASSERT (SELECT COUNT(*) * 2 FROM APP100) > 10\uFF09",
745
+ this.peek()
746
+ );
747
+ }
748
+ const hasWildcard = query.columns.some(
749
+ (c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
750
+ );
751
+ if (!hasWildcard && query.columns.length > 1) {
752
+ throw new ParseError("scalar subquery in ASSERT must return exactly 1 column.", tok);
753
+ }
754
+ return { type: "SCALAR_SUBQUERY", query };
755
+ }
756
+ if (tok.kind === "NUMBER" /* NUMBER */ || tok.kind === "(" /* LPAREN */ || tok.kind === "-" /* MINUS */) {
757
+ const expr = this.parseArithAddSub();
758
+ this.rejectNonLiteralArith(expr, tok);
759
+ if (expr.type === "NUMBER") return expr;
760
+ return expr;
761
+ }
762
+ if (this.tryStringFuncName() !== null) {
763
+ throw new ParseError(
764
+ "ASSERT \u306E\u5F0F\u3067\u306F\u95A2\u6570\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
765
+ tok
766
+ );
767
+ }
768
+ throw new ParseError(
769
+ "ASSERT \u306E\u5F0F\u306B\u306F\u30EA\u30C6\u30E9\u30EB\u30FB\u7B97\u8853\u5F0F\u30FB\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF09",
770
+ tok
771
+ );
772
+ }
773
+ /** ASSERT の算術式にフィールド参照・関数呼び出しが含まれていたら拒否する */
774
+ rejectNonLiteralArith(node, tok) {
775
+ if (node.type === "FIELD_REF") {
776
+ throw new ParseError(
777
+ `ASSERT \u3067\u306F\u30D5\u30A3\u30FC\u30EB\u30C9\u53C2\u7167\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08FROM \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u3042\u308A\u307E\u305B\u3093\uFF09: ${node.field}`,
778
+ tok
779
+ );
780
+ }
781
+ if (node.type === "STRING_FUNC") {
782
+ throw new ParseError(
783
+ "ASSERT \u306E\u5F0F\u3067\u306F\u95A2\u6570\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF08\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u5185\u3067\u8A08\u7B97\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
784
+ tok
785
+ );
786
+ }
787
+ if (node.type === "ARITH") {
788
+ this.rejectNonLiteralArith(node.left, tok);
789
+ this.rejectNonLiteralArith(node.right, tok);
790
+ }
791
+ }
792
+ /** ASSERT の比較演算子を読む(該当しなければ null・消費しない) */
793
+ tryAssertCompareOp() {
794
+ switch (this.peek().kind) {
795
+ case "=" /* EQ */:
796
+ this.advance();
797
+ return "=";
798
+ case "!=" /* NEQ */:
799
+ this.advance();
800
+ return "!=";
801
+ case "<>" /* LT_GT */:
802
+ this.advance();
803
+ return "<>";
804
+ case ">" /* GT */:
805
+ this.advance();
806
+ return ">";
807
+ case "<" /* LT */:
808
+ this.advance();
809
+ return "<";
810
+ case ">=" /* GTE */:
811
+ this.advance();
812
+ return ">=";
813
+ case "<=" /* LTE */:
814
+ this.advance();
815
+ return "<=";
816
+ default:
817
+ return null;
818
+ }
819
+ }
820
+ /** ASSERT は AND / OR による複合条件に対応しない(初期版仕様) */
821
+ rejectAssertCompound() {
822
+ const tok = this.peek();
823
+ if (tok.kind === "AND" /* AND */ || tok.kind === "OR" /* OR */) {
824
+ throw new ParseError(
825
+ "ASSERT \u306F AND / OR \u306B\u3088\u308B\u8907\u5408\u6761\u4EF6\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\uFF08\u8907\u6570\u306E ASSERT \u6587\u306B\u5206\u3051\u3066\u304F\u3060\u3055\u3044\uFF09",
826
+ tok
827
+ );
828
+ }
829
+ }
830
+ /**
831
+ * トークン列 [fromIdx, toIdx) を SQL 風テキストに再構成する。
832
+ * AssertError の "assertion failed: <条件>" メッセージ用(正規化表示で十分)。
833
+ */
834
+ renderTokenRange(fromIdx, toIdx) {
835
+ let out = "";
836
+ for (let i = fromIdx; i < toIdx; i++) {
837
+ const t = this.tokens[i];
838
+ let text;
839
+ if (t.kind === "STRING" /* STRING */) text = `'${t.value.replace(/'/g, "''")}'`;
840
+ else if (t.kind === "BIDENT" /* BIDENT */) text = `\`${t.value}\``;
841
+ else text = t.value;
842
+ if (out.length > 0 && needsSpaceBetween(this.tokens[i - 1], t)) out += " ";
843
+ out += text;
844
+ }
845
+ return out;
846
+ }
847
+ // ----------------------------------------------------------
624
848
  // SELECT
625
849
  // ----------------------------------------------------------
626
850
  parseSelect() {
@@ -1970,7 +2194,7 @@ function isDmlType(type) {
1970
2194
  return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
1971
2195
  }
1972
2196
  function isReadOnlyType(type) {
1973
- return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE";
2197
+ return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "ASSERT";
1974
2198
  }
1975
2199
  function hasWhereClause(stmt) {
1976
2200
  if (!stmt || typeof stmt !== "object") return false;
@@ -4351,6 +4575,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
4351
4575
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
4352
4576
  case "DROP_TEMP_TABLE":
4353
4577
  throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
4578
+ case "ASSERT":
4579
+ return executeAssert(stmt, client, options, cacheContext);
4354
4580
  }
4355
4581
  }
4356
4582
  var TEMP_TABLE_MAX_ROWS = 1e4;
@@ -4425,6 +4651,8 @@ async function executeBatch(sql, client, options = {}) {
4425
4651
  failed.add(i);
4426
4652
  if (e instanceof BatchTimeoutError) {
4427
4653
  aborted = "timeout";
4654
+ } else if (e instanceof AssertError) {
4655
+ aborted = "assertion";
4428
4656
  } else if (!options.continueOnError) {
4429
4657
  aborted = "fail-fast";
4430
4658
  }
@@ -4457,6 +4685,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
4457
4685
  if (stmt.type === "EXPLAIN") {
4458
4686
  return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
4459
4687
  }
4688
+ if (stmt.type === "ASSERT") {
4689
+ await executeAssert(stmt, client, options, cacheContext, tempTables);
4690
+ return {};
4691
+ }
4460
4692
  if (info.tempTablesReferenced.length > 0) {
4461
4693
  if (stmt.type === "SELECT" || stmt.type === "UNION") {
4462
4694
  return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
@@ -4533,6 +4765,107 @@ function parseSqlBatch(sql) {
4533
4765
  const tokens = new Lexer(sql).tokenize();
4534
4766
  return new Parser(tokens).parseStatements();
4535
4767
  }
4768
+ var AssertError = class extends Error {
4769
+ constructor(message) {
4770
+ super(`AssertError: ${message}`);
4771
+ this.name = "AssertError";
4772
+ }
4773
+ };
4774
+ async function executeAssert(stmt, client, options, cacheContext, tempTables) {
4775
+ const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
4776
+ if (stmt.op === "BETWEEN") {
4777
+ if (stmt.low === null || stmt.high === null) {
4778
+ throw new Error("ArgumentError: malformed ASSERT statement.");
4779
+ }
4780
+ const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
4781
+ const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
4782
+ if (!compareAssertValues(">=", left, low) || !compareAssertValues("<=", left, high)) {
4783
+ throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
4784
+ }
4785
+ return { type: "ASSERT", condition: stmt.text };
4786
+ }
4787
+ if (stmt.right === null) {
4788
+ throw new Error("ArgumentError: malformed ASSERT statement.");
4789
+ }
4790
+ const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
4791
+ if (!compareAssertValues(stmt.op, left, right)) {
4792
+ throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
4793
+ }
4794
+ return { type: "ASSERT", condition: stmt.text };
4795
+ }
4796
+ async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
4797
+ switch (operand.type) {
4798
+ case "NUMBER":
4799
+ return String(operand.value);
4800
+ case "STRING":
4801
+ return operand.value;
4802
+ case "ARITH":
4803
+ return String(evalAssertArith(operand));
4804
+ case "SCALAR_SUBQUERY": {
4805
+ const { query, probed } = withScalarProbeLimit(operand.query);
4806
+ const result = await runSubquery(query, client, options, cacheContext, tempTables);
4807
+ if (result.columns.length > 1) {
4808
+ throw new AssertError(
4809
+ `scalar subquery returned ${result.columns.length} columns (expected 1 column).`
4810
+ );
4811
+ }
4812
+ if (result.rowCount === 0) {
4813
+ throw new AssertError("scalar subquery returned no rows (expected 1 row).");
4814
+ }
4815
+ if (result.rowCount > 1) {
4816
+ const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
4817
+ throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
4818
+ }
4819
+ const col = result.columns[0] ?? "";
4820
+ return result.rows[0]?.[col] ?? "";
4821
+ }
4822
+ }
4823
+ }
4824
+ function withScalarProbeLimit(query) {
4825
+ const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
4826
+ if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
4827
+ return { query: { ...query, limit: 2 }, probed: true };
4828
+ }
4829
+ function evalAssertArith(node) {
4830
+ if (node.type === "NUMBER") return node.value;
4831
+ if (node.type === "ARITH") {
4832
+ const left = evalAssertArith(node.left);
4833
+ const right = evalAssertArith(node.right);
4834
+ switch (node.op) {
4835
+ case "+":
4836
+ return left + right;
4837
+ case "-":
4838
+ return left - right;
4839
+ case "*":
4840
+ return left * right;
4841
+ case "/":
4842
+ return left / right;
4843
+ case "%":
4844
+ return left % right;
4845
+ }
4846
+ }
4847
+ throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
4848
+ }
4849
+ function compareAssertValues(op, leftStr, rightStr) {
4850
+ const leftNum = Number(leftStr);
4851
+ const rightNum = Number(rightStr);
4852
+ const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
4853
+ switch (op) {
4854
+ case "=":
4855
+ return leftStr === rightStr;
4856
+ case "!=":
4857
+ case "<>":
4858
+ return leftStr !== rightStr;
4859
+ case ">":
4860
+ return numeric ? leftNum > rightNum : leftStr > rightStr;
4861
+ case "<":
4862
+ return numeric ? leftNum < rightNum : leftStr < rightStr;
4863
+ case ">=":
4864
+ return numeric ? leftNum >= rightNum : leftStr >= rightStr;
4865
+ case "<=":
4866
+ return numeric ? leftNum <= rightNum : leftStr <= rightStr;
4867
+ }
4868
+ }
4536
4869
  async function executeSelect(stmt, client, options, cacheContext, cteCache) {
4537
4870
  if (isNoFromSelect(stmt)) {
4538
4871
  return executeNoFromSelect(stmt);
@@ -5990,8 +6323,33 @@ function buildBatchStatementPlan(stmt, info) {
5990
6323
  if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
5991
6324
  if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
5992
6325
  if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
6326
+ if (stmt.type === "ASSERT") {
6327
+ const lines = [
6328
+ `ASSERT ${stmt.text}`,
6329
+ " check: \u5B9F\u884C\u6642\u306B\u6761\u4EF6\u8A55\u4FA1\uFF08\u4E0D\u6210\u7ACB\u306F AssertError \u3067\u30D0\u30C3\u30C1\u505C\u6B62\u3001\u4EE5\u964D\u306E\u6587\u306F skipped\uFF09"
6330
+ ];
6331
+ const subqueries = [stmt.left, stmt.right, stmt.low, stmt.high].filter(
6332
+ (o) => o !== null && o.type === "SCALAR_SUBQUERY"
6333
+ );
6334
+ subqueries.forEach((sq, i) => {
6335
+ lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
6336
+ const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
6337
+ lines.push(...buildPlanForBatchQuery(sq.query, subInfo).map((l) => ` ${l}`));
6338
+ });
6339
+ return lines;
6340
+ }
5993
6341
  return buildPlanForBatchQuery(stmt, info);
5994
6342
  }
6343
+ function hasTempTableRef(node) {
6344
+ if (Array.isArray(node)) return node.some(hasTempTableRef);
6345
+ if (node !== null && typeof node === "object") {
6346
+ const obj = node;
6347
+ const cte = obj["cteName"];
6348
+ if (typeof cte === "string" && cte.startsWith("#")) return true;
6349
+ return Object.values(obj).some(hasTempTableRef);
6350
+ }
6351
+ return false;
6352
+ }
5995
6353
  function buildPlanForBatchQuery(query, info) {
5996
6354
  if (info.tempTablesReferenced.length === 0) {
5997
6355
  return buildExplainPlan(query);
@@ -6428,6 +6786,90 @@ function isSubtableRow(v) {
6428
6786
  return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
6429
6787
  }
6430
6788
 
6789
+ // src/output/batchEnvelope.ts
6790
+ function toMutationSummary(result) {
6791
+ if (result.type === "INSERT") {
6792
+ return { insertedCount: result.insertedCount, createdIds: result.createdIds };
6793
+ }
6794
+ if (result.type === "UPDATE") return { updatedCount: result.updatedCount };
6795
+ if (result.type === "DELETE") return { deletedCount: result.deletedCount };
6796
+ if (result.type === "UPSERT") {
6797
+ return { insertedCount: result.insertedCount, updatedCount: result.updatedCount };
6798
+ }
6799
+ return { reorderedParentCount: result.reorderedParentCount };
6800
+ }
6801
+ function buildBatchEnvelope(batch, options = {}) {
6802
+ const { maxTotalRecords } = options;
6803
+ const results = [];
6804
+ let totalRows = 0;
6805
+ const statements = batch.statements.map((s) => {
6806
+ const entry = {
6807
+ index: s.index,
6808
+ type: s.type,
6809
+ status: s.status
6810
+ };
6811
+ if (s.status === "error" && s.error) entry.error = s.error;
6812
+ if (s.status === "skipped" && s.skippedReason) entry.skippedReason = s.skippedReason;
6813
+ if (s.tempTable !== void 0) entry.tempTable = s.tempTable;
6814
+ if (s.rowCount !== void 0) entry.rowCount = s.rowCount;
6815
+ if (s.status === "success" && s.result?.type === "SELECT") {
6816
+ totalRows += s.result.rowCount;
6817
+ if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
6818
+ throw new Error(
6819
+ `ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`
6820
+ );
6821
+ }
6822
+ entry.resultIndex = results.length;
6823
+ results.push({
6824
+ columns: s.result.columns,
6825
+ rows: s.result.rows,
6826
+ rowCount: s.result.rowCount,
6827
+ warnings: s.result.warnings ?? []
6828
+ });
6829
+ } else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
6830
+ Object.assign(entry, toMutationSummary(s.result));
6831
+ }
6832
+ return entry;
6833
+ });
6834
+ return {
6835
+ ok: batch.ok,
6836
+ batch: true,
6837
+ statementCount: batch.statementCount,
6838
+ statements,
6839
+ results,
6840
+ // バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
6841
+ warnings: []
6842
+ };
6843
+ }
6844
+
6845
+ // src/node/config.ts
6846
+ function envString(name) {
6847
+ const v = process.env[name];
6848
+ return v && v.trim() ? v : null;
6849
+ }
6850
+ function envInt(name) {
6851
+ const v = envString(name);
6852
+ if (v === null) return null;
6853
+ const n = Number(v);
6854
+ if (!Number.isInteger(n) || n <= 0) return null;
6855
+ return n;
6856
+ }
6857
+ function envNonNegativeInt(name) {
6858
+ const v = envString(name);
6859
+ if (v === null) return null;
6860
+ const n = Number(v);
6861
+ if (!Number.isInteger(n) || n < 0) return null;
6862
+ return n;
6863
+ }
6864
+ function resolveRequestGateOptions(base) {
6865
+ return {
6866
+ ...base,
6867
+ maxConcurrent: envInt("KSQL_MAX_CONCURRENT") ?? base.maxConcurrent,
6868
+ // KSQL_RETRY=0(リトライ無効)は有効値のため envNonNegativeInt で読む
6869
+ maxRetries: envNonNegativeInt("KSQL_RETRY") ?? base.maxRetries
6870
+ };
6871
+ }
6872
+
6431
6873
  // src/node/appProfiles.ts
6432
6874
  var import_fs = require("fs");
6433
6875
  function parseTokenMap(raw) {
@@ -6712,8 +7154,11 @@ var RequestGate = class {
6712
7154
  this.waiters = [];
6713
7155
  this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
6714
7156
  this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
6715
- this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
6716
- this.maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
7157
+ this.baseDelayMs = clampInt(options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS, 1, 6e4);
7158
+ this.maxDelayMs = Math.max(
7159
+ clampInt(options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS, 1, 6e5),
7160
+ this.baseDelayMs
7161
+ );
6717
7162
  this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
6718
7163
  this.random = options.random ?? Math.random;
6719
7164
  }
@@ -6724,6 +7169,18 @@ var RequestGate = class {
6724
7169
  get limit() {
6725
7170
  return this.maxConcurrent;
6726
7171
  }
7172
+ /** 解決済みの GET リトライ回数(テスト・診断用) */
7173
+ get retries() {
7174
+ return this.maxRetries;
7175
+ }
7176
+ /** 解決済みのバックオフ初期値ミリ秒(テスト・診断用) */
7177
+ get retryBaseDelayMs() {
7178
+ return this.baseDelayMs;
7179
+ }
7180
+ /** 解決済みのバックオフ上限ミリ秒(テスト・診断用) */
7181
+ get retryMaxDelayMs() {
7182
+ return this.maxDelayMs;
7183
+ }
6727
7184
  /** GET 系: セマフォ + リトライ付きで実行する */
6728
7185
  async runReadOnly(fn) {
6729
7186
  let attempt = 0;
@@ -6780,11 +7237,11 @@ function withRequestGate(client, gate) {
6780
7237
  };
6781
7238
  }
6782
7239
  var globalGate = null;
6783
- function getGlobalRequestGate(limitHint) {
7240
+ function getGlobalRequestGate(options) {
6784
7241
  if (globalGate === null) {
6785
- const envValue = Number(process.env.KSQL_MAX_CONCURRENT);
6786
- const limit = Number.isInteger(envValue) && envValue > 0 ? envValue : limitHint;
6787
- globalGate = new RequestGate({ maxConcurrent: limit });
7242
+ globalGate = new RequestGate(
7243
+ typeof options === "number" ? { maxConcurrent: options } : options ?? {}
7244
+ );
6788
7245
  }
6789
7246
  return globalGate;
6790
7247
  }
@@ -7008,10 +7465,16 @@ Options:
7008
7465
  --console Start interactive console mode
7009
7466
  --dry-run Parse and show execution plan only
7010
7467
  --format <type> Output format: table | json | jsonl | csv | markdown | md
7468
+ (batch + json: prints one JSON envelope for the whole batch)
7011
7469
  --max-records <n> Max records to fetch (default: 500)
7012
7470
  --fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
7013
7471
  --on-limit <mode> On record limit: error | truncate
7014
7472
  --timeout <ms> Request timeout in milliseconds (default: 30000)
7473
+ --max-concurrent <n> Max concurrent kintone requests: 1-50 (default: 10)
7474
+ (process-wide; fixed at first resolution; KSQL_MAX_CONCURRENT wins)
7475
+ --retry <n> GET retry count: 0-10, 0 disables (default: 3; KSQL_RETRY wins)
7476
+ --retry-base-delay <ms> GET retry backoff base delay (default: 500)
7477
+ --retry-max-delay <ms> GET retry backoff max delay (default: 8000)
7015
7478
  --config <path> Config file path (default: ./ksql.config.json)
7016
7479
  --profile <name> Profile name in config
7017
7480
  --base-url <url> kintone base URL
@@ -7085,6 +7548,10 @@ function parseArgs(argv) {
7085
7548
  allowWithoutWhere: false,
7086
7549
  continueOnError: false,
7087
7550
  dmlMaxRows: null,
7551
+ maxConcurrent: null,
7552
+ retry: null,
7553
+ retryBaseDelay: null,
7554
+ retryMaxDelay: null,
7088
7555
  userFormat: null,
7089
7556
  arrayFormat: null,
7090
7557
  tableFormat: null,
@@ -7311,6 +7778,34 @@ function parseArgs(argv) {
7311
7778
  i++;
7312
7779
  continue;
7313
7780
  }
7781
+ if (a === "--max-concurrent") {
7782
+ const n = Number(v);
7783
+ if (!Number.isInteger(n) || n < 1 || n > 50) throw new Error("ArgumentError: --max-concurrent must be an integer between 1 and 50.");
7784
+ out.maxConcurrent = n;
7785
+ i++;
7786
+ continue;
7787
+ }
7788
+ if (a === "--retry") {
7789
+ const n = Number(v);
7790
+ if (!Number.isInteger(n) || n < 0 || n > 10) throw new Error("ArgumentError: --retry must be an integer between 0 and 10 (0 disables retry).");
7791
+ out.retry = n;
7792
+ i++;
7793
+ continue;
7794
+ }
7795
+ if (a === "--retry-base-delay") {
7796
+ const n = Number(v);
7797
+ if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --retry-base-delay must be a positive integer (ms).");
7798
+ out.retryBaseDelay = n;
7799
+ i++;
7800
+ continue;
7801
+ }
7802
+ if (a === "--retry-max-delay") {
7803
+ const n = Number(v);
7804
+ if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --retry-max-delay must be a positive integer (ms).");
7805
+ out.retryMaxDelay = n;
7806
+ i++;
7807
+ continue;
7808
+ }
7314
7809
  throw new Error(`ArgumentError: unknown option ${a}`);
7315
7810
  }
7316
7811
  return out;
@@ -7334,35 +7829,35 @@ function resolveTokenValue(raw) {
7334
7829
  }
7335
7830
  return raw;
7336
7831
  }
7337
- function envString(name) {
7832
+ function envString2(name) {
7338
7833
  const v = process.env[name];
7339
7834
  return v && v.trim() ? v : null;
7340
7835
  }
7341
- function envInt(name) {
7342
- const v = envString(name);
7836
+ function envInt2(name) {
7837
+ const v = envString2(name);
7343
7838
  if (v === null) return null;
7344
7839
  const n = Number(v);
7345
7840
  if (!Number.isInteger(n) || n <= 0) return null;
7346
7841
  return n;
7347
7842
  }
7348
7843
  function envBool(name) {
7349
- const v = envString(name);
7844
+ const v = envString2(name);
7350
7845
  if (v === null) return null;
7351
7846
  if (v === "1" || v.toLowerCase() === "true") return true;
7352
7847
  if (v === "0" || v.toLowerCase() === "false") return false;
7353
7848
  return null;
7354
7849
  }
7355
7850
  function envFormat(name) {
7356
- const v = envString(name);
7851
+ const v = envString2(name);
7357
7852
  return normalizeOutputFormat(v);
7358
7853
  }
7359
7854
  function envOnLimit(name) {
7360
- const v = envString(name);
7855
+ const v = envString2(name);
7361
7856
  if (v === "error" || v === "truncate") return v;
7362
7857
  return null;
7363
7858
  }
7364
7859
  function envAuth(name) {
7365
- const v = envString(name);
7860
+ const v = envString2(name);
7366
7861
  if (v === "token" || v === "userpass" || v === "auto") return v;
7367
7862
  return null;
7368
7863
  }
@@ -7387,6 +7882,12 @@ function getAffectedCount(result) {
7387
7882
  if (result.type === "REORDER") return result.reorderedParentCount;
7388
7883
  return 0;
7389
7884
  }
7885
+ function buildAssertOutput(result, format, pretty) {
7886
+ const payload = { ok: true, type: result.type, condition: result.condition };
7887
+ if (format === "json") return JSON.stringify(payload, null, pretty ? 2 : 0);
7888
+ if (format === "jsonl") return JSON.stringify(payload);
7889
+ return `assertion ok: ${result.condition}`;
7890
+ }
7390
7891
  function buildMutationOutput(result, format, noHeader, pretty) {
7391
7892
  const row = { type: result.type };
7392
7893
  if (result.type === "INSERT") {
@@ -7510,7 +8011,7 @@ function buildBatchStatementSummary(s) {
7510
8011
  else if (r.type === "UPDATE") parts.push(`updated=${r.updatedCount}`);
7511
8012
  else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
7512
8013
  else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
7513
- else parts.push(`reordered=${r.reorderedParentCount}`);
8014
+ else if (r.type === "REORDER") parts.push(`reordered=${r.reorderedParentCount}`);
7514
8015
  }
7515
8016
  if (s.status === "error" && s.error) parts.push(s.error.message);
7516
8017
  if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
@@ -7526,15 +8027,11 @@ function buildBatchDmlConfirmMessage(analysis) {
7526
8027
  return lines.join("\n");
7527
8028
  }
7528
8029
  function writeBatchOutput(batch, opts) {
7529
- const outputs = [];
7530
- for (const s of batch.statements) {
7531
- if (!opts.quiet) process.stderr.write(`${buildBatchStatementSummary(s)}
8030
+ const output = opts.format === "json" ? JSON.stringify(buildBatchEnvelope(batch), null, opts.pretty ? 2 : 0) : buildBatchResultsOutput(batch, opts);
8031
+ if (!opts.quiet) {
8032
+ for (const s of batch.statements) process.stderr.write(`${buildBatchStatementSummary(s)}
7532
8033
  `);
7533
- if (s.status === "success" && s.result?.type === "SELECT") {
7534
- outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
7535
- }
7536
8034
  }
7537
- const output = outputs.join("\n\n");
7538
8035
  if (opts.outputPath) (0, import_fs2.writeFileSync)(opts.outputPath, `${output}
7539
8036
  `, "utf-8");
7540
8037
  else if (output) process.stdout.write(`${output}
@@ -7543,6 +8040,15 @@ function writeBatchOutput(batch, opts) {
7543
8040
  const firstError = batch.statements.find((s) => s.status === "error");
7544
8041
  return firstError?.error ? toExitCodeFromError(new Error(firstError.error.message)) : 1;
7545
8042
  }
8043
+ function buildBatchResultsOutput(batch, opts) {
8044
+ const outputs = [];
8045
+ for (const s of batch.statements) {
8046
+ if (s.status === "success" && s.result?.type === "SELECT") {
8047
+ outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
8048
+ }
8049
+ }
8050
+ return outputs.join("\n\n");
8051
+ }
7546
8052
  function shouldExitOnEmpty(dryRun, exitOnEmpty, rowCount) {
7547
8053
  if (dryRun) return false;
7548
8054
  return exitOnEmpty && rowCount === 0;
@@ -7689,6 +8195,10 @@ function buildReplExecArgv(base, sql, dryRun, format) {
7689
8195
  pushOpt(argv, "--date-format", base.dateFormat);
7690
8196
  pushOpt(argv, "--attachment-format", base.attachmentFormat);
7691
8197
  pushOpt(argv, "--dml-max-rows", base.dmlMaxRows);
8198
+ pushOpt(argv, "--max-concurrent", base.maxConcurrent);
8199
+ pushOpt(argv, "--retry", base.retry);
8200
+ pushOpt(argv, "--retry-base-delay", base.retryBaseDelay);
8201
+ pushOpt(argv, "--retry-max-delay", base.retryMaxDelay);
7692
8202
  const tokenMapArg = buildTokenMapArg(base.tokenMap);
7693
8203
  if (tokenMapArg) argv.push("--token-map", tokenMapArg);
7694
8204
  if (base.noHeader) argv.push("--no-header");
@@ -8163,13 +8673,13 @@ async function run() {
8163
8673
  process.stderr.write("ArgumentError: specify -e/--execute or -f/--file. Use --help for details.\n");
8164
8674
  return 2;
8165
8675
  }
8166
- const configPath = args.configPath ?? envString("KSQL_CONFIG") ?? "./ksql.config.json";
8676
+ const configPath = args.configPath ?? envString2("KSQL_CONFIG") ?? "./ksql.config.json";
8167
8677
  let config = {};
8168
8678
  try {
8169
8679
  config = loadConfig(configPath);
8170
8680
  } catch {
8171
8681
  }
8172
- const profileName = args.profile ?? envString("KSQL_PROFILE") ?? config.defaultProfile ?? "dev";
8682
+ const profileName = args.profile ?? envString2("KSQL_PROFILE") ?? config.defaultProfile ?? "dev";
8173
8683
  const profile = config.profiles?.[profileName] ?? {};
8174
8684
  let sql = null;
8175
8685
  let hasProfileSyntax = false;
@@ -8212,7 +8722,7 @@ async function run() {
8212
8722
  isDmlStatement = isDmlType(stmtType);
8213
8723
  hasWhere = hasWhereClause(stmt);
8214
8724
  insertValuesCount = getInsertValuesCount(stmt);
8215
- const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
8725
+ const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlStatement;
8216
8726
  if (!supported) {
8217
8727
  process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
8218
8728
  `);
@@ -8225,10 +8735,10 @@ async function run() {
8225
8735
  return 1;
8226
8736
  }
8227
8737
  }
8228
- const maxRecords = args.maxRecords ?? envInt("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
8229
- const fetchParallel = args.fetchParallel ?? envInt("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
8738
+ const maxRecords = args.maxRecords ?? envInt2("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
8739
+ const fetchParallel = args.fetchParallel ?? envInt2("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
8230
8740
  const onLimit = args.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
8231
- const timeout = args.timeout ?? envInt("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
8741
+ const timeout = args.timeout ?? envInt2("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
8232
8742
  if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
8233
8743
  process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
8234
8744
  return 2;
@@ -8245,12 +8755,12 @@ async function run() {
8245
8755
  const quiet = args.quiet || envBool("KSQL_QUIET") === true || Boolean(profile.output?.quiet);
8246
8756
  const debug = args.debug || args.debugUrl || envBool("KSQL_DEBUG") === true || envBool("KSQL_DEBUG_URL") === true;
8247
8757
  const debugHeaders = args.debugHeaders || envBool("KSQL_DEBUG_HEADERS") === true;
8248
- const outputPath = args.outputPath ?? envString("KSQL_OUTPUT") ?? profile.output?.output ?? null;
8758
+ const outputPath = args.outputPath ?? envString2("KSQL_OUTPUT") ?? profile.output?.output ?? null;
8249
8759
  const exitOnEmpty = args.exitOnEmpty || envBool("KSQL_EXIT_ON_EMPTY") === true || Boolean(profile.output?.exitOnEmpty);
8250
8760
  const allowDml = args.allowDml || envBool("KSQL_ALLOW_DML") === true || Boolean(profile.dml?.allowDml);
8251
8761
  const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
8252
8762
  const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
8253
- const dmlMaxRows = args.dmlMaxRows ?? envInt("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
8763
+ const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
8254
8764
  if (format === "markdown" && noHeader) {
8255
8765
  process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
8256
8766
  return 2;
@@ -8264,7 +8774,7 @@ async function run() {
8264
8774
  attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
8265
8775
  };
8266
8776
  const appIds = sql ? extractAppIds(sql) : [];
8267
- const defaultApp = args.app ?? envInt("KSQL_APP") ?? profile.app ?? null;
8777
+ const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
8268
8778
  if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
8269
8779
  const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
8270
8780
  if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
@@ -8324,10 +8834,10 @@ async function run() {
8324
8834
  return 2;
8325
8835
  }
8326
8836
  }
8327
- const mapFromEnv = envString("KSQL_TOKEN_MAP") ? parseTokenMap(envString("KSQL_TOKEN_MAP")) : {};
8837
+ const mapFromEnv = envString2("KSQL_TOKEN_MAP") ? parseTokenMap(envString2("KSQL_TOKEN_MAP")) : {};
8328
8838
  const mapFromFile = args.tokenFile ? parseTokenFile(args.tokenFile) : {};
8329
8839
  const mapFromArg = args.tokenMap;
8330
- const singleToken = args.token ?? envString("KSQL_TOKEN");
8840
+ const singleToken = args.token ?? envString2("KSQL_TOKEN");
8331
8841
  const profileClientMap = /* @__PURE__ */ new Map();
8332
8842
  const missingAppProfiles = [];
8333
8843
  const usedProfiles = /* @__PURE__ */ new Set([...appProfileByApp.values(), profileName]);
@@ -8338,17 +8848,17 @@ async function run() {
8338
8848
  `);
8339
8849
  return 2;
8340
8850
  }
8341
- const baseUrl = args.baseUrl ?? envString("KSQL_BASE_URL") ?? p.baseUrl ?? "";
8342
- const guestSpaceId = args.guestSpaceId ?? envInt("KSQL_GUEST_SPACE_ID") ?? p.guestSpaceId ?? null;
8851
+ const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? p.baseUrl ?? "";
8852
+ const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? p.guestSpaceId ?? null;
8343
8853
  if (!baseUrl) {
8344
8854
  process.stderr.write(`AuthError: --base-url is required for profile "${pName}".
8345
8855
  `);
8346
8856
  return 3;
8347
8857
  }
8348
8858
  const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? p.auth ?? "auto";
8349
- const username = args.username ?? envString("KSQL_USERNAME") ?? p.username ?? null;
8350
- const passwordFromEnvRef = p.passwordEnv ? envString(p.passwordEnv) : null;
8351
- const password = args.password ?? envString("KSQL_PASSWORD") ?? passwordFromEnvRef ?? p.password ?? null;
8859
+ const username = args.username ?? envString2("KSQL_USERNAME") ?? p.username ?? null;
8860
+ const passwordFromEnvRef = p.passwordEnv ? envString2(p.passwordEnv) : null;
8861
+ const password = args.password ?? envString2("KSQL_PASSWORD") ?? passwordFromEnvRef ?? p.password ?? null;
8352
8862
  const hasUserPass = Boolean(username && password);
8353
8863
  const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
8354
8864
  if (auth === "userpass") {
@@ -8429,17 +8939,17 @@ async function run() {
8429
8939
  `);
8430
8940
  return 2;
8431
8941
  }
8432
- const baseUrl = args.baseUrl ?? envString("KSQL_BASE_URL") ?? diagProfile.baseUrl ?? "";
8433
- const guestSpaceId = args.guestSpaceId ?? envInt("KSQL_GUEST_SPACE_ID") ?? diagProfile.guestSpaceId ?? null;
8942
+ const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? diagProfile.baseUrl ?? "";
8943
+ const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? diagProfile.guestSpaceId ?? null;
8434
8944
  if (!baseUrl) {
8435
8945
  process.stderr.write(`AuthError: --base-url is required for profile "${diagProfileName}".
8436
8946
  `);
8437
8947
  return 3;
8438
8948
  }
8439
8949
  const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? diagProfile.auth ?? "auto";
8440
- const username = args.username ?? envString("KSQL_USERNAME") ?? diagProfile.username ?? null;
8441
- const passwordFromEnvRef = diagProfile.passwordEnv ? envString(diagProfile.passwordEnv) : null;
8442
- const password = args.password ?? envString("KSQL_PASSWORD") ?? passwordFromEnvRef ?? diagProfile.password ?? null;
8950
+ const username = args.username ?? envString2("KSQL_USERNAME") ?? diagProfile.username ?? null;
8951
+ const passwordFromEnvRef = diagProfile.passwordEnv ? envString2(diagProfile.passwordEnv) : null;
8952
+ const password = args.password ?? envString2("KSQL_PASSWORD") ?? passwordFromEnvRef ?? diagProfile.password ?? null;
8443
8953
  const hasUserPass = Boolean(username && password);
8444
8954
  const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
8445
8955
  try {
@@ -8537,7 +9047,12 @@ async function run() {
8537
9047
  };
8538
9048
  }
8539
9049
  if (!args.dryRun) {
8540
- client = withRequestGate(client, getGlobalRequestGate(profile.query?.maxConcurrent));
9050
+ client = withRequestGate(client, getGlobalRequestGate(resolveRequestGateOptions({
9051
+ maxConcurrent: args.maxConcurrent ?? profile.query?.maxConcurrent,
9052
+ maxRetries: args.retry ?? profile.query?.retry,
9053
+ baseDelayMs: args.retryBaseDelay ?? profile.query?.retryBaseDelayMs,
9054
+ maxDelayMs: args.retryMaxDelay ?? profile.query?.retryMaxDelayMs
9055
+ })));
8541
9056
  }
8542
9057
  try {
8543
9058
  if (isDmlStatement && !args.dryRun) {
@@ -8597,6 +9112,14 @@ query=${label}`);
8597
9112
  confirm: isDmlStatement ? confirm : void 0,
8598
9113
  cacheContext
8599
9114
  });
9115
+ if (result.type === "ASSERT") {
9116
+ const output2 = buildAssertOutput(result, format, pretty);
9117
+ if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
9118
+ `, "utf-8");
9119
+ else if (output2) process.stdout.write(`${output2}
9120
+ `);
9121
+ return 0;
9122
+ }
8600
9123
  if (result.type !== "SELECT") {
8601
9124
  const output2 = buildMutationOutput(result, format, noHeader, pretty);
8602
9125
  if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
@@ -8650,5 +9173,6 @@ if (isDirectCliRun()) {
8650
9173
  parseConsoleMetaCommand,
8651
9174
  parseTokenFile,
8652
9175
  parseTokenMap,
8653
- shouldExitOnEmpty
9176
+ shouldExitOnEmpty,
9177
+ writeBatchOutput
8654
9178
  });