@rex0220/kintone-sql-tools 1.4.1 → 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/README.md +7 -0
- package/dist-cli/ksql.js +596 -53
- package/dist-mcp/ksql-mcp.js +548 -102
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +3 -3
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;
|
|
@@ -4367,9 +4593,9 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4367
4593
|
}
|
|
4368
4594
|
for (const s of analysis.statements) {
|
|
4369
4595
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
4370
|
-
if (s.statementType === "INSERT_SELECT"
|
|
4596
|
+
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
4371
4597
|
throw new BatchAnalysisError(
|
|
4372
|
-
|
|
4598
|
+
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
4373
4599
|
s.index
|
|
4374
4600
|
);
|
|
4375
4601
|
}
|
|
@@ -4405,8 +4631,18 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4405
4631
|
}
|
|
4406
4632
|
try {
|
|
4407
4633
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
4634
|
+
const userConfirm = options.confirm;
|
|
4635
|
+
const stmtOptions = userConfirm ? {
|
|
4636
|
+
...options,
|
|
4637
|
+
confirm: (count, operation) => userConfirm(count, operation, {
|
|
4638
|
+
statementIndex: i,
|
|
4639
|
+
statementCount: statements.length,
|
|
4640
|
+
statementType: info.statementType,
|
|
4641
|
+
targetAppId: info.targetAppId
|
|
4642
|
+
})
|
|
4643
|
+
} : options;
|
|
4408
4644
|
const outcome = await runWithDeadline(
|
|
4409
|
-
executeBatchStatement(statements[i], info, countedClient,
|
|
4645
|
+
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
|
|
4410
4646
|
remaining
|
|
4411
4647
|
);
|
|
4412
4648
|
results.push({ ...base, status: "success", ...outcome });
|
|
@@ -4415,6 +4651,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4415
4651
|
failed.add(i);
|
|
4416
4652
|
if (e instanceof BatchTimeoutError) {
|
|
4417
4653
|
aborted = "timeout";
|
|
4654
|
+
} else if (e instanceof AssertError) {
|
|
4655
|
+
aborted = "assertion";
|
|
4418
4656
|
} else if (!options.continueOnError) {
|
|
4419
4657
|
aborted = "fail-fast";
|
|
4420
4658
|
}
|
|
@@ -4447,6 +4685,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
4447
4685
|
if (stmt.type === "EXPLAIN") {
|
|
4448
4686
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
4449
4687
|
}
|
|
4688
|
+
if (stmt.type === "ASSERT") {
|
|
4689
|
+
await executeAssert(stmt, client, options, cacheContext, tempTables);
|
|
4690
|
+
return {};
|
|
4691
|
+
}
|
|
4450
4692
|
if (info.tempTablesReferenced.length > 0) {
|
|
4451
4693
|
if (stmt.type === "SELECT" || stmt.type === "UNION") {
|
|
4452
4694
|
return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
|
|
@@ -4457,6 +4699,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
4457
4699
|
if (stmt.type === "INSERT_SELECT") {
|
|
4458
4700
|
return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
|
|
4459
4701
|
}
|
|
4702
|
+
if (stmt.type === "UPSERT_SELECT") {
|
|
4703
|
+
return { result: await executeUpsertSelect(stmt, client, options, cacheContext, tempTables) };
|
|
4704
|
+
}
|
|
4460
4705
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
4461
4706
|
}
|
|
4462
4707
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
@@ -4520,6 +4765,107 @@ function parseSqlBatch(sql) {
|
|
|
4520
4765
|
const tokens = new Lexer(sql).tokenize();
|
|
4521
4766
|
return new Parser(tokens).parseStatements();
|
|
4522
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
|
+
}
|
|
4523
4869
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
4524
4870
|
if (isNoFromSelect(stmt)) {
|
|
4525
4871
|
return executeNoFromSelect(stmt);
|
|
@@ -5778,8 +6124,8 @@ function evalOrderKeyForRow(key, row) {
|
|
|
5778
6124
|
return evalStringFunc(key.expr, row);
|
|
5779
6125
|
}
|
|
5780
6126
|
}
|
|
5781
|
-
async function executeUpsertSelect(stmt, client, options, cacheContext) {
|
|
5782
|
-
const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
|
|
6127
|
+
async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
6128
|
+
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
5783
6129
|
const { rows, columns } = selectResult;
|
|
5784
6130
|
if (columns.length !== stmt.fields.length) {
|
|
5785
6131
|
throw new Error(
|
|
@@ -5977,8 +6323,33 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
5977
6323
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
5978
6324
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
5979
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
|
+
}
|
|
5980
6341
|
return buildPlanForBatchQuery(stmt, info);
|
|
5981
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
|
+
}
|
|
5982
6353
|
function buildPlanForBatchQuery(query, info) {
|
|
5983
6354
|
if (info.tempTablesReferenced.length === 0) {
|
|
5984
6355
|
return buildExplainPlan(query);
|
|
@@ -5988,12 +6359,18 @@ function buildPlanForBatchQuery(query, info) {
|
|
|
5988
6359
|
lines.push(
|
|
5989
6360
|
`INSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u5B9F\u884C\u6642\u306B\u4EF6\u6570\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
|
|
5990
6361
|
);
|
|
6362
|
+
} else if (query.type === "UPSERT_SELECT") {
|
|
6363
|
+
lines.push(
|
|
6364
|
+
`UPSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u7167\u5408\u5F8C\u306B insert + update \u5408\u8A08\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
|
|
6365
|
+
);
|
|
5991
6366
|
}
|
|
5992
6367
|
lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
|
|
5993
6368
|
lines.push(
|
|
5994
6369
|
` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
|
|
5995
6370
|
);
|
|
5996
|
-
const apps = info.appIds.filter(
|
|
6371
|
+
const apps = info.appIds.filter(
|
|
6372
|
+
(a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
|
|
6373
|
+
);
|
|
5997
6374
|
if (apps.length > 0) {
|
|
5998
6375
|
lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
|
|
5999
6376
|
}
|
|
@@ -6409,6 +6786,90 @@ function isSubtableRow(v) {
|
|
|
6409
6786
|
return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
|
|
6410
6787
|
}
|
|
6411
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
|
+
|
|
6412
6873
|
// src/node/appProfiles.ts
|
|
6413
6874
|
var import_fs = require("fs");
|
|
6414
6875
|
function parseTokenMap(raw) {
|
|
@@ -6693,8 +7154,11 @@ var RequestGate = class {
|
|
|
6693
7154
|
this.waiters = [];
|
|
6694
7155
|
this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
|
|
6695
7156
|
this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
|
|
6696
|
-
this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
6697
|
-
this.maxDelayMs =
|
|
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
|
+
);
|
|
6698
7162
|
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6699
7163
|
this.random = options.random ?? Math.random;
|
|
6700
7164
|
}
|
|
@@ -6705,6 +7169,18 @@ var RequestGate = class {
|
|
|
6705
7169
|
get limit() {
|
|
6706
7170
|
return this.maxConcurrent;
|
|
6707
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
|
+
}
|
|
6708
7184
|
/** GET 系: セマフォ + リトライ付きで実行する */
|
|
6709
7185
|
async runReadOnly(fn) {
|
|
6710
7186
|
let attempt = 0;
|
|
@@ -6761,11 +7237,11 @@ function withRequestGate(client, gate) {
|
|
|
6761
7237
|
};
|
|
6762
7238
|
}
|
|
6763
7239
|
var globalGate = null;
|
|
6764
|
-
function getGlobalRequestGate(
|
|
7240
|
+
function getGlobalRequestGate(options) {
|
|
6765
7241
|
if (globalGate === null) {
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
7242
|
+
globalGate = new RequestGate(
|
|
7243
|
+
typeof options === "number" ? { maxConcurrent: options } : options ?? {}
|
|
7244
|
+
);
|
|
6769
7245
|
}
|
|
6770
7246
|
return globalGate;
|
|
6771
7247
|
}
|
|
@@ -6989,10 +7465,16 @@ Options:
|
|
|
6989
7465
|
--console Start interactive console mode
|
|
6990
7466
|
--dry-run Parse and show execution plan only
|
|
6991
7467
|
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
7468
|
+
(batch + json: prints one JSON envelope for the whole batch)
|
|
6992
7469
|
--max-records <n> Max records to fetch (default: 500)
|
|
6993
7470
|
--fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
|
|
6994
7471
|
--on-limit <mode> On record limit: error | truncate
|
|
6995
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)
|
|
6996
7478
|
--config <path> Config file path (default: ./ksql.config.json)
|
|
6997
7479
|
--profile <name> Profile name in config
|
|
6998
7480
|
--base-url <url> kintone base URL
|
|
@@ -7066,6 +7548,10 @@ function parseArgs(argv) {
|
|
|
7066
7548
|
allowWithoutWhere: false,
|
|
7067
7549
|
continueOnError: false,
|
|
7068
7550
|
dmlMaxRows: null,
|
|
7551
|
+
maxConcurrent: null,
|
|
7552
|
+
retry: null,
|
|
7553
|
+
retryBaseDelay: null,
|
|
7554
|
+
retryMaxDelay: null,
|
|
7069
7555
|
userFormat: null,
|
|
7070
7556
|
arrayFormat: null,
|
|
7071
7557
|
tableFormat: null,
|
|
@@ -7292,6 +7778,34 @@ function parseArgs(argv) {
|
|
|
7292
7778
|
i++;
|
|
7293
7779
|
continue;
|
|
7294
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
|
+
}
|
|
7295
7809
|
throw new Error(`ArgumentError: unknown option ${a}`);
|
|
7296
7810
|
}
|
|
7297
7811
|
return out;
|
|
@@ -7315,35 +7829,35 @@ function resolveTokenValue(raw) {
|
|
|
7315
7829
|
}
|
|
7316
7830
|
return raw;
|
|
7317
7831
|
}
|
|
7318
|
-
function
|
|
7832
|
+
function envString2(name) {
|
|
7319
7833
|
const v = process.env[name];
|
|
7320
7834
|
return v && v.trim() ? v : null;
|
|
7321
7835
|
}
|
|
7322
|
-
function
|
|
7323
|
-
const v =
|
|
7836
|
+
function envInt2(name) {
|
|
7837
|
+
const v = envString2(name);
|
|
7324
7838
|
if (v === null) return null;
|
|
7325
7839
|
const n = Number(v);
|
|
7326
7840
|
if (!Number.isInteger(n) || n <= 0) return null;
|
|
7327
7841
|
return n;
|
|
7328
7842
|
}
|
|
7329
7843
|
function envBool(name) {
|
|
7330
|
-
const v =
|
|
7844
|
+
const v = envString2(name);
|
|
7331
7845
|
if (v === null) return null;
|
|
7332
7846
|
if (v === "1" || v.toLowerCase() === "true") return true;
|
|
7333
7847
|
if (v === "0" || v.toLowerCase() === "false") return false;
|
|
7334
7848
|
return null;
|
|
7335
7849
|
}
|
|
7336
7850
|
function envFormat(name) {
|
|
7337
|
-
const v =
|
|
7851
|
+
const v = envString2(name);
|
|
7338
7852
|
return normalizeOutputFormat(v);
|
|
7339
7853
|
}
|
|
7340
7854
|
function envOnLimit(name) {
|
|
7341
|
-
const v =
|
|
7855
|
+
const v = envString2(name);
|
|
7342
7856
|
if (v === "error" || v === "truncate") return v;
|
|
7343
7857
|
return null;
|
|
7344
7858
|
}
|
|
7345
7859
|
function envAuth(name) {
|
|
7346
|
-
const v =
|
|
7860
|
+
const v = envString2(name);
|
|
7347
7861
|
if (v === "token" || v === "userpass" || v === "auto") return v;
|
|
7348
7862
|
return null;
|
|
7349
7863
|
}
|
|
@@ -7368,6 +7882,12 @@ function getAffectedCount(result) {
|
|
|
7368
7882
|
if (result.type === "REORDER") return result.reorderedParentCount;
|
|
7369
7883
|
return 0;
|
|
7370
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
|
+
}
|
|
7371
7891
|
function buildMutationOutput(result, format, noHeader, pretty) {
|
|
7372
7892
|
const row = { type: result.type };
|
|
7373
7893
|
if (result.type === "INSERT") {
|
|
@@ -7491,7 +8011,7 @@ function buildBatchStatementSummary(s) {
|
|
|
7491
8011
|
else if (r.type === "UPDATE") parts.push(`updated=${r.updatedCount}`);
|
|
7492
8012
|
else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
|
|
7493
8013
|
else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
|
|
7494
|
-
else parts.push(`reordered=${r.reorderedParentCount}`);
|
|
8014
|
+
else if (r.type === "REORDER") parts.push(`reordered=${r.reorderedParentCount}`);
|
|
7495
8015
|
}
|
|
7496
8016
|
if (s.status === "error" && s.error) parts.push(s.error.message);
|
|
7497
8017
|
if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
|
|
@@ -7507,15 +8027,11 @@ function buildBatchDmlConfirmMessage(analysis) {
|
|
|
7507
8027
|
return lines.join("\n");
|
|
7508
8028
|
}
|
|
7509
8029
|
function writeBatchOutput(batch, opts) {
|
|
7510
|
-
const
|
|
7511
|
-
|
|
7512
|
-
|
|
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)}
|
|
7513
8033
|
`);
|
|
7514
|
-
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
7515
|
-
outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
7516
|
-
}
|
|
7517
8034
|
}
|
|
7518
|
-
const output = outputs.join("\n\n");
|
|
7519
8035
|
if (opts.outputPath) (0, import_fs2.writeFileSync)(opts.outputPath, `${output}
|
|
7520
8036
|
`, "utf-8");
|
|
7521
8037
|
else if (output) process.stdout.write(`${output}
|
|
@@ -7524,6 +8040,15 @@ function writeBatchOutput(batch, opts) {
|
|
|
7524
8040
|
const firstError = batch.statements.find((s) => s.status === "error");
|
|
7525
8041
|
return firstError?.error ? toExitCodeFromError(new Error(firstError.error.message)) : 1;
|
|
7526
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
|
+
}
|
|
7527
8052
|
function shouldExitOnEmpty(dryRun, exitOnEmpty, rowCount) {
|
|
7528
8053
|
if (dryRun) return false;
|
|
7529
8054
|
return exitOnEmpty && rowCount === 0;
|
|
@@ -7670,6 +8195,10 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
7670
8195
|
pushOpt(argv, "--date-format", base.dateFormat);
|
|
7671
8196
|
pushOpt(argv, "--attachment-format", base.attachmentFormat);
|
|
7672
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);
|
|
7673
8202
|
const tokenMapArg = buildTokenMapArg(base.tokenMap);
|
|
7674
8203
|
if (tokenMapArg) argv.push("--token-map", tokenMapArg);
|
|
7675
8204
|
if (base.noHeader) argv.push("--no-header");
|
|
@@ -8144,13 +8673,13 @@ async function run() {
|
|
|
8144
8673
|
process.stderr.write("ArgumentError: specify -e/--execute or -f/--file. Use --help for details.\n");
|
|
8145
8674
|
return 2;
|
|
8146
8675
|
}
|
|
8147
|
-
const configPath = args.configPath ??
|
|
8676
|
+
const configPath = args.configPath ?? envString2("KSQL_CONFIG") ?? "./ksql.config.json";
|
|
8148
8677
|
let config = {};
|
|
8149
8678
|
try {
|
|
8150
8679
|
config = loadConfig(configPath);
|
|
8151
8680
|
} catch {
|
|
8152
8681
|
}
|
|
8153
|
-
const profileName = args.profile ??
|
|
8682
|
+
const profileName = args.profile ?? envString2("KSQL_PROFILE") ?? config.defaultProfile ?? "dev";
|
|
8154
8683
|
const profile = config.profiles?.[profileName] ?? {};
|
|
8155
8684
|
let sql = null;
|
|
8156
8685
|
let hasProfileSyntax = false;
|
|
@@ -8193,7 +8722,7 @@ async function run() {
|
|
|
8193
8722
|
isDmlStatement = isDmlType(stmtType);
|
|
8194
8723
|
hasWhere = hasWhereClause(stmt);
|
|
8195
8724
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
8196
|
-
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;
|
|
8197
8726
|
if (!supported) {
|
|
8198
8727
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
8199
8728
|
`);
|
|
@@ -8206,10 +8735,10 @@ async function run() {
|
|
|
8206
8735
|
return 1;
|
|
8207
8736
|
}
|
|
8208
8737
|
}
|
|
8209
|
-
const maxRecords = args.maxRecords ??
|
|
8210
|
-
const fetchParallel = args.fetchParallel ??
|
|
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;
|
|
8211
8740
|
const onLimit = args.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
|
|
8212
|
-
const timeout = args.timeout ??
|
|
8741
|
+
const timeout = args.timeout ?? envInt2("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
|
|
8213
8742
|
if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
|
|
8214
8743
|
process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
|
|
8215
8744
|
return 2;
|
|
@@ -8226,12 +8755,12 @@ async function run() {
|
|
|
8226
8755
|
const quiet = args.quiet || envBool("KSQL_QUIET") === true || Boolean(profile.output?.quiet);
|
|
8227
8756
|
const debug = args.debug || args.debugUrl || envBool("KSQL_DEBUG") === true || envBool("KSQL_DEBUG_URL") === true;
|
|
8228
8757
|
const debugHeaders = args.debugHeaders || envBool("KSQL_DEBUG_HEADERS") === true;
|
|
8229
|
-
const outputPath = args.outputPath ??
|
|
8758
|
+
const outputPath = args.outputPath ?? envString2("KSQL_OUTPUT") ?? profile.output?.output ?? null;
|
|
8230
8759
|
const exitOnEmpty = args.exitOnEmpty || envBool("KSQL_EXIT_ON_EMPTY") === true || Boolean(profile.output?.exitOnEmpty);
|
|
8231
8760
|
const allowDml = args.allowDml || envBool("KSQL_ALLOW_DML") === true || Boolean(profile.dml?.allowDml);
|
|
8232
8761
|
const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
|
|
8233
8762
|
const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
|
|
8234
|
-
const dmlMaxRows = args.dmlMaxRows ??
|
|
8763
|
+
const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
|
|
8235
8764
|
if (format === "markdown" && noHeader) {
|
|
8236
8765
|
process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
|
|
8237
8766
|
return 2;
|
|
@@ -8245,7 +8774,7 @@ async function run() {
|
|
|
8245
8774
|
attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
|
|
8246
8775
|
};
|
|
8247
8776
|
const appIds = sql ? extractAppIds(sql) : [];
|
|
8248
|
-
const defaultApp = args.app ??
|
|
8777
|
+
const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
|
|
8249
8778
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
8250
8779
|
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
8251
8780
|
if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
|
|
@@ -8305,10 +8834,10 @@ async function run() {
|
|
|
8305
8834
|
return 2;
|
|
8306
8835
|
}
|
|
8307
8836
|
}
|
|
8308
|
-
const mapFromEnv =
|
|
8837
|
+
const mapFromEnv = envString2("KSQL_TOKEN_MAP") ? parseTokenMap(envString2("KSQL_TOKEN_MAP")) : {};
|
|
8309
8838
|
const mapFromFile = args.tokenFile ? parseTokenFile(args.tokenFile) : {};
|
|
8310
8839
|
const mapFromArg = args.tokenMap;
|
|
8311
|
-
const singleToken = args.token ??
|
|
8840
|
+
const singleToken = args.token ?? envString2("KSQL_TOKEN");
|
|
8312
8841
|
const profileClientMap = /* @__PURE__ */ new Map();
|
|
8313
8842
|
const missingAppProfiles = [];
|
|
8314
8843
|
const usedProfiles = /* @__PURE__ */ new Set([...appProfileByApp.values(), profileName]);
|
|
@@ -8319,17 +8848,17 @@ async function run() {
|
|
|
8319
8848
|
`);
|
|
8320
8849
|
return 2;
|
|
8321
8850
|
}
|
|
8322
|
-
const baseUrl = args.baseUrl ??
|
|
8323
|
-
const guestSpaceId = args.guestSpaceId ??
|
|
8851
|
+
const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? p.baseUrl ?? "";
|
|
8852
|
+
const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? p.guestSpaceId ?? null;
|
|
8324
8853
|
if (!baseUrl) {
|
|
8325
8854
|
process.stderr.write(`AuthError: --base-url is required for profile "${pName}".
|
|
8326
8855
|
`);
|
|
8327
8856
|
return 3;
|
|
8328
8857
|
}
|
|
8329
8858
|
const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? p.auth ?? "auto";
|
|
8330
|
-
const username = args.username ??
|
|
8331
|
-
const passwordFromEnvRef = p.passwordEnv ?
|
|
8332
|
-
const password = args.password ??
|
|
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;
|
|
8333
8862
|
const hasUserPass = Boolean(username && password);
|
|
8334
8863
|
const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
|
|
8335
8864
|
if (auth === "userpass") {
|
|
@@ -8410,17 +8939,17 @@ async function run() {
|
|
|
8410
8939
|
`);
|
|
8411
8940
|
return 2;
|
|
8412
8941
|
}
|
|
8413
|
-
const baseUrl = args.baseUrl ??
|
|
8414
|
-
const guestSpaceId = args.guestSpaceId ??
|
|
8942
|
+
const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? diagProfile.baseUrl ?? "";
|
|
8943
|
+
const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? diagProfile.guestSpaceId ?? null;
|
|
8415
8944
|
if (!baseUrl) {
|
|
8416
8945
|
process.stderr.write(`AuthError: --base-url is required for profile "${diagProfileName}".
|
|
8417
8946
|
`);
|
|
8418
8947
|
return 3;
|
|
8419
8948
|
}
|
|
8420
8949
|
const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? diagProfile.auth ?? "auto";
|
|
8421
|
-
const username = args.username ??
|
|
8422
|
-
const passwordFromEnvRef = diagProfile.passwordEnv ?
|
|
8423
|
-
const password = args.password ??
|
|
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;
|
|
8424
8953
|
const hasUserPass = Boolean(username && password);
|
|
8425
8954
|
const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
|
|
8426
8955
|
try {
|
|
@@ -8518,7 +9047,12 @@ async function run() {
|
|
|
8518
9047
|
};
|
|
8519
9048
|
}
|
|
8520
9049
|
if (!args.dryRun) {
|
|
8521
|
-
client = withRequestGate(client, getGlobalRequestGate(
|
|
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
|
+
})));
|
|
8522
9056
|
}
|
|
8523
9057
|
try {
|
|
8524
9058
|
if (isDmlStatement && !args.dryRun) {
|
|
@@ -8578,6 +9112,14 @@ query=${label}`);
|
|
|
8578
9112
|
confirm: isDmlStatement ? confirm : void 0,
|
|
8579
9113
|
cacheContext
|
|
8580
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
|
+
}
|
|
8581
9123
|
if (result.type !== "SELECT") {
|
|
8582
9124
|
const output2 = buildMutationOutput(result, format, noHeader, pretty);
|
|
8583
9125
|
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
@@ -8631,5 +9173,6 @@ if (isDirectCliRun()) {
|
|
|
8631
9173
|
parseConsoleMetaCommand,
|
|
8632
9174
|
parseTokenFile,
|
|
8633
9175
|
parseTokenMap,
|
|
8634
|
-
shouldExitOnEmpty
|
|
9176
|
+
shouldExitOnEmpty,
|
|
9177
|
+
writeBatchOutput
|
|
8635
9178
|
});
|