@rex0220/kintone-sql-tools 1.9.0 → 1.11.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 +8 -0
- package/dist-cli/ksql.js +586 -48
- package/dist-mcp/ksql-mcp.js +488 -72
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +2 -2
package/dist-cli/ksql.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
buildBatchDmlConfirmMessage: () => buildBatchDmlConfirmMessage,
|
|
26
26
|
buildBatchStatementSummary: () => buildBatchStatementSummary,
|
|
27
27
|
buildOutput: () => buildOutput,
|
|
28
|
+
buildReplExecArgv: () => buildReplExecArgv,
|
|
28
29
|
extractAppIds: () => extractAppIds,
|
|
29
30
|
normalizeAppKey: () => normalizeAppKey,
|
|
30
31
|
normalizeSqlAppProfiles: () => normalizeSqlAppProfiles,
|
|
@@ -33,7 +34,8 @@ __export(index_exports, {
|
|
|
33
34
|
parseConsoleMetaCommand: () => parseConsoleMetaCommand,
|
|
34
35
|
parseTokenFile: () => parseTokenFile,
|
|
35
36
|
parseTokenMap: () => parseTokenMap,
|
|
36
|
-
shouldExitOnEmpty: () => shouldExitOnEmpty
|
|
37
|
+
shouldExitOnEmpty: () => shouldExitOnEmpty,
|
|
38
|
+
writeBatchOutput: () => writeBatchOutput
|
|
37
39
|
});
|
|
38
40
|
module.exports = __toCommonJS(index_exports);
|
|
39
41
|
var import_fs2 = require("fs");
|
|
@@ -83,6 +85,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
83
85
|
["AVG", "AVG" /* AVG */],
|
|
84
86
|
["MAX", "MAX" /* MAX */],
|
|
85
87
|
["MIN", "MIN" /* MIN */],
|
|
88
|
+
["ASSERT", "ASSERT" /* ASSERT */],
|
|
86
89
|
["AND", "AND" /* AND */],
|
|
87
90
|
["OR", "OR" /* OR */],
|
|
88
91
|
["NOT", "NOT" /* NOT */],
|
|
@@ -428,6 +431,58 @@ function isJapanese(cp) {
|
|
|
428
431
|
|
|
429
432
|
// src/parser/parser.ts
|
|
430
433
|
var MAX_BATCH_STATEMENTS = 20;
|
|
434
|
+
var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
435
|
+
"IDENT" /* IDENT */,
|
|
436
|
+
"BIDENT" /* BIDENT */,
|
|
437
|
+
"COUNT" /* COUNT */,
|
|
438
|
+
"SUM" /* SUM */,
|
|
439
|
+
"AVG" /* AVG */,
|
|
440
|
+
"MAX" /* MAX */,
|
|
441
|
+
"MIN" /* MIN */,
|
|
442
|
+
"TODAY" /* TODAY */,
|
|
443
|
+
"NOW" /* NOW */,
|
|
444
|
+
"LOGINUSER" /* LOGINUSER */,
|
|
445
|
+
"UPPER" /* UPPER */,
|
|
446
|
+
"LOWER" /* LOWER */,
|
|
447
|
+
"TRIM" /* TRIM */,
|
|
448
|
+
"LTRIM" /* LTRIM */,
|
|
449
|
+
"RTRIM" /* RTRIM */,
|
|
450
|
+
"LENGTH" /* LENGTH */,
|
|
451
|
+
"SUBSTRING" /* SUBSTRING */,
|
|
452
|
+
"SUBSTR" /* SUBSTR */,
|
|
453
|
+
"CONCAT" /* CONCAT */,
|
|
454
|
+
"REPLACE" /* REPLACE */,
|
|
455
|
+
"COALESCE" /* COALESCE */,
|
|
456
|
+
"NULLIF" /* NULLIF */,
|
|
457
|
+
"ISNULL" /* ISNULL */,
|
|
458
|
+
"CAST" /* CAST */,
|
|
459
|
+
"CONVERT" /* CONVERT */,
|
|
460
|
+
"FORMAT" /* FORMAT */,
|
|
461
|
+
"ROUND" /* ROUND */,
|
|
462
|
+
"FLOOR" /* FLOOR */,
|
|
463
|
+
"CEIL" /* CEIL */,
|
|
464
|
+
"CEILING" /* CEILING */,
|
|
465
|
+
"ABS" /* ABS */,
|
|
466
|
+
"MOD" /* MOD */,
|
|
467
|
+
"POWER" /* POWER */,
|
|
468
|
+
"POW" /* POW */,
|
|
469
|
+
"SQRT" /* SQRT */,
|
|
470
|
+
"YEAR" /* YEAR */,
|
|
471
|
+
"MONTH" /* MONTH */,
|
|
472
|
+
"DAY" /* DAY */,
|
|
473
|
+
"DATE_FORMAT" /* DATE_FORMAT */,
|
|
474
|
+
"DATEDIFF" /* DATEDIFF */,
|
|
475
|
+
"DATE_ADD" /* DATE_ADD */,
|
|
476
|
+
"IF" /* IF */
|
|
477
|
+
]);
|
|
478
|
+
function needsSpaceBetween(prev, cur) {
|
|
479
|
+
if (prev.kind === "(" /* LPAREN */ || prev.kind === "." /* DOT */) return false;
|
|
480
|
+
if (cur.kind === ")" /* RPAREN */ || cur.kind === "," /* COMMA */ || cur.kind === "." /* DOT */) return false;
|
|
481
|
+
if (cur.kind === "(" /* LPAREN */) {
|
|
482
|
+
return !FUNC_CALL_PREFIX_KINDS.has(prev.kind);
|
|
483
|
+
}
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
431
486
|
var ParseError = class extends Error {
|
|
432
487
|
constructor(message, token) {
|
|
433
488
|
super(`${message}\uFF08\u4F4D\u7F6E ${token.pos}\u3001\u30C8\u30FC\u30AF\u30F3: \u300C${token.value}\u300D\uFF09`);
|
|
@@ -517,6 +572,8 @@ var Parser = class {
|
|
|
517
572
|
return this.parseDescribe();
|
|
518
573
|
case "EXPLAIN" /* EXPLAIN */:
|
|
519
574
|
return this.parseExplain();
|
|
575
|
+
case "ASSERT" /* ASSERT */:
|
|
576
|
+
return this.parseAssert();
|
|
520
577
|
case "IDENT" /* IDENT */: {
|
|
521
578
|
const upper = tok.value.toUpperCase();
|
|
522
579
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
@@ -527,7 +584,7 @@ var Parser = class {
|
|
|
527
584
|
break;
|
|
528
585
|
}
|
|
529
586
|
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",
|
|
587
|
+
"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
588
|
tok
|
|
532
589
|
);
|
|
533
590
|
}
|
|
@@ -621,6 +678,174 @@ var Parser = class {
|
|
|
621
678
|
return { type: "EXPLAIN", query };
|
|
622
679
|
}
|
|
623
680
|
// ----------------------------------------------------------
|
|
681
|
+
// ASSERT
|
|
682
|
+
//
|
|
683
|
+
// ASSERT <式> <比較演算子> <式>
|
|
684
|
+
// ASSERT <式> BETWEEN <式> AND <式>
|
|
685
|
+
//
|
|
686
|
+
// 式: リテラル / 算術式 / スカラーサブクエリ。
|
|
687
|
+
// フィールド参照(FROM コンテキストがない)・AND / OR 複合条件・
|
|
688
|
+
// 裸の値のみ(ASSERT 1)は ParseError。
|
|
689
|
+
// ----------------------------------------------------------
|
|
690
|
+
parseAssert() {
|
|
691
|
+
this.expect("ASSERT" /* ASSERT */);
|
|
692
|
+
const condStart = this.pos;
|
|
693
|
+
const left = this.parseAssertOperand();
|
|
694
|
+
const opTok = this.peek();
|
|
695
|
+
if (this.consume("BETWEEN" /* BETWEEN */)) {
|
|
696
|
+
const low = this.parseAssertOperand();
|
|
697
|
+
this.expect(
|
|
698
|
+
"AND" /* AND */,
|
|
699
|
+
"ASSERT \u306E BETWEEN \u306B\u306F AND \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: ASSERT (SELECT COUNT(*) FROM #t) BETWEEN 1 AND 500\uFF09"
|
|
700
|
+
);
|
|
701
|
+
const high = this.parseAssertOperand();
|
|
702
|
+
this.rejectAssertCompound();
|
|
703
|
+
return {
|
|
704
|
+
type: "ASSERT",
|
|
705
|
+
left,
|
|
706
|
+
op: "BETWEEN",
|
|
707
|
+
right: null,
|
|
708
|
+
low,
|
|
709
|
+
high,
|
|
710
|
+
text: this.renderTokenRange(condStart, this.pos)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const op = this.tryAssertCompareOp();
|
|
714
|
+
if (op === null) {
|
|
715
|
+
throw new ParseError(
|
|
716
|
+
"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",
|
|
717
|
+
opTok
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const right = this.parseAssertOperand();
|
|
721
|
+
this.rejectAssertCompound();
|
|
722
|
+
return {
|
|
723
|
+
type: "ASSERT",
|
|
724
|
+
left,
|
|
725
|
+
op,
|
|
726
|
+
right,
|
|
727
|
+
low: null,
|
|
728
|
+
high: null,
|
|
729
|
+
text: this.renderTokenRange(condStart, this.pos)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
|
|
733
|
+
parseAssertOperand() {
|
|
734
|
+
const tok = this.peek();
|
|
735
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
736
|
+
this.advance();
|
|
737
|
+
return { type: "STRING", value: tok.value };
|
|
738
|
+
}
|
|
739
|
+
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
740
|
+
this.advance();
|
|
741
|
+
const query = this.parseSelect();
|
|
742
|
+
this.expect(")" /* RPAREN */);
|
|
743
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
744
|
+
throw new ParseError(
|
|
745
|
+
"\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",
|
|
746
|
+
this.peek()
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
const hasWildcard = query.columns.some(
|
|
750
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
751
|
+
);
|
|
752
|
+
if (!hasWildcard && query.columns.length > 1) {
|
|
753
|
+
throw new ParseError("scalar subquery in ASSERT must return exactly 1 column.", tok);
|
|
754
|
+
}
|
|
755
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
756
|
+
}
|
|
757
|
+
if (tok.kind === "NUMBER" /* NUMBER */ || tok.kind === "(" /* LPAREN */ || tok.kind === "-" /* MINUS */) {
|
|
758
|
+
const expr = this.parseArithAddSub();
|
|
759
|
+
this.rejectNonLiteralArith(expr, tok);
|
|
760
|
+
if (expr.type === "NUMBER") return expr;
|
|
761
|
+
return expr;
|
|
762
|
+
}
|
|
763
|
+
if (this.tryStringFuncName() !== null) {
|
|
764
|
+
throw new ParseError(
|
|
765
|
+
"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",
|
|
766
|
+
tok
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
throw new ParseError(
|
|
770
|
+
"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",
|
|
771
|
+
tok
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
/** ASSERT の算術式にフィールド参照・関数呼び出しが含まれていたら拒否する */
|
|
775
|
+
rejectNonLiteralArith(node, tok) {
|
|
776
|
+
if (node.type === "FIELD_REF") {
|
|
777
|
+
throw new ParseError(
|
|
778
|
+
`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}`,
|
|
779
|
+
tok
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
if (node.type === "STRING_FUNC") {
|
|
783
|
+
throw new ParseError(
|
|
784
|
+
"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",
|
|
785
|
+
tok
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
if (node.type === "ARITH") {
|
|
789
|
+
this.rejectNonLiteralArith(node.left, tok);
|
|
790
|
+
this.rejectNonLiteralArith(node.right, tok);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/** ASSERT の比較演算子を読む(該当しなければ null・消費しない) */
|
|
794
|
+
tryAssertCompareOp() {
|
|
795
|
+
switch (this.peek().kind) {
|
|
796
|
+
case "=" /* EQ */:
|
|
797
|
+
this.advance();
|
|
798
|
+
return "=";
|
|
799
|
+
case "!=" /* NEQ */:
|
|
800
|
+
this.advance();
|
|
801
|
+
return "!=";
|
|
802
|
+
case "<>" /* LT_GT */:
|
|
803
|
+
this.advance();
|
|
804
|
+
return "<>";
|
|
805
|
+
case ">" /* GT */:
|
|
806
|
+
this.advance();
|
|
807
|
+
return ">";
|
|
808
|
+
case "<" /* LT */:
|
|
809
|
+
this.advance();
|
|
810
|
+
return "<";
|
|
811
|
+
case ">=" /* GTE */:
|
|
812
|
+
this.advance();
|
|
813
|
+
return ">=";
|
|
814
|
+
case "<=" /* LTE */:
|
|
815
|
+
this.advance();
|
|
816
|
+
return "<=";
|
|
817
|
+
default:
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/** ASSERT は AND / OR による複合条件に対応しない(初期版仕様) */
|
|
822
|
+
rejectAssertCompound() {
|
|
823
|
+
const tok = this.peek();
|
|
824
|
+
if (tok.kind === "AND" /* AND */ || tok.kind === "OR" /* OR */) {
|
|
825
|
+
throw new ParseError(
|
|
826
|
+
"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",
|
|
827
|
+
tok
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* トークン列 [fromIdx, toIdx) を SQL 風テキストに再構成する。
|
|
833
|
+
* AssertError の "assertion failed: <条件>" メッセージ用(正規化表示で十分)。
|
|
834
|
+
*/
|
|
835
|
+
renderTokenRange(fromIdx, toIdx) {
|
|
836
|
+
let out = "";
|
|
837
|
+
for (let i = fromIdx; i < toIdx; i++) {
|
|
838
|
+
const t = this.tokens[i];
|
|
839
|
+
let text;
|
|
840
|
+
if (t.kind === "STRING" /* STRING */) text = `'${t.value.replace(/'/g, "''")}'`;
|
|
841
|
+
else if (t.kind === "BIDENT" /* BIDENT */) text = `\`${t.value}\``;
|
|
842
|
+
else text = t.value;
|
|
843
|
+
if (out.length > 0 && needsSpaceBetween(this.tokens[i - 1], t)) out += " ";
|
|
844
|
+
out += text;
|
|
845
|
+
}
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
// ----------------------------------------------------------
|
|
624
849
|
// SELECT
|
|
625
850
|
// ----------------------------------------------------------
|
|
626
851
|
parseSelect() {
|
|
@@ -1970,7 +2195,7 @@ function isDmlType(type) {
|
|
|
1970
2195
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
1971
2196
|
}
|
|
1972
2197
|
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";
|
|
2198
|
+
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
2199
|
}
|
|
1975
2200
|
function hasWhereClause(stmt) {
|
|
1976
2201
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -4351,6 +4576,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
4351
4576
|
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4352
4577
|
case "DROP_TEMP_TABLE":
|
|
4353
4578
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
4579
|
+
case "ASSERT":
|
|
4580
|
+
return executeAssert(stmt, client, options, cacheContext);
|
|
4354
4581
|
}
|
|
4355
4582
|
}
|
|
4356
4583
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
@@ -4425,6 +4652,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
4425
4652
|
failed.add(i);
|
|
4426
4653
|
if (e instanceof BatchTimeoutError) {
|
|
4427
4654
|
aborted = "timeout";
|
|
4655
|
+
} else if (e instanceof AssertError) {
|
|
4656
|
+
aborted = "assertion";
|
|
4428
4657
|
} else if (!options.continueOnError) {
|
|
4429
4658
|
aborted = "fail-fast";
|
|
4430
4659
|
}
|
|
@@ -4457,6 +4686,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
4457
4686
|
if (stmt.type === "EXPLAIN") {
|
|
4458
4687
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
4459
4688
|
}
|
|
4689
|
+
if (stmt.type === "ASSERT") {
|
|
4690
|
+
await executeAssert(stmt, client, options, cacheContext, tempTables);
|
|
4691
|
+
return {};
|
|
4692
|
+
}
|
|
4460
4693
|
if (info.tempTablesReferenced.length > 0) {
|
|
4461
4694
|
if (stmt.type === "SELECT" || stmt.type === "UNION") {
|
|
4462
4695
|
return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
|
|
@@ -4533,6 +4766,107 @@ function parseSqlBatch(sql) {
|
|
|
4533
4766
|
const tokens = new Lexer(sql).tokenize();
|
|
4534
4767
|
return new Parser(tokens).parseStatements();
|
|
4535
4768
|
}
|
|
4769
|
+
var AssertError = class extends Error {
|
|
4770
|
+
constructor(message) {
|
|
4771
|
+
super(`AssertError: ${message}`);
|
|
4772
|
+
this.name = "AssertError";
|
|
4773
|
+
}
|
|
4774
|
+
};
|
|
4775
|
+
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
4776
|
+
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
4777
|
+
if (stmt.op === "BETWEEN") {
|
|
4778
|
+
if (stmt.low === null || stmt.high === null) {
|
|
4779
|
+
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
4780
|
+
}
|
|
4781
|
+
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
4782
|
+
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
4783
|
+
if (!compareAssertValues(">=", left, low) || !compareAssertValues("<=", left, high)) {
|
|
4784
|
+
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
4785
|
+
}
|
|
4786
|
+
return { type: "ASSERT", condition: stmt.text };
|
|
4787
|
+
}
|
|
4788
|
+
if (stmt.right === null) {
|
|
4789
|
+
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
4790
|
+
}
|
|
4791
|
+
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
4792
|
+
if (!compareAssertValues(stmt.op, left, right)) {
|
|
4793
|
+
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
4794
|
+
}
|
|
4795
|
+
return { type: "ASSERT", condition: stmt.text };
|
|
4796
|
+
}
|
|
4797
|
+
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
4798
|
+
switch (operand.type) {
|
|
4799
|
+
case "NUMBER":
|
|
4800
|
+
return String(operand.value);
|
|
4801
|
+
case "STRING":
|
|
4802
|
+
return operand.value;
|
|
4803
|
+
case "ARITH":
|
|
4804
|
+
return String(evalAssertArith(operand));
|
|
4805
|
+
case "SCALAR_SUBQUERY": {
|
|
4806
|
+
const { query, probed } = withScalarProbeLimit(operand.query);
|
|
4807
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
4808
|
+
if (result.columns.length > 1) {
|
|
4809
|
+
throw new AssertError(
|
|
4810
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
4811
|
+
);
|
|
4812
|
+
}
|
|
4813
|
+
if (result.rowCount === 0) {
|
|
4814
|
+
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
4815
|
+
}
|
|
4816
|
+
if (result.rowCount > 1) {
|
|
4817
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
4818
|
+
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
4819
|
+
}
|
|
4820
|
+
const col = result.columns[0] ?? "";
|
|
4821
|
+
return result.rows[0]?.[col] ?? "";
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
function withScalarProbeLimit(query) {
|
|
4826
|
+
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
4827
|
+
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
4828
|
+
return { query: { ...query, limit: 2 }, probed: true };
|
|
4829
|
+
}
|
|
4830
|
+
function evalAssertArith(node) {
|
|
4831
|
+
if (node.type === "NUMBER") return node.value;
|
|
4832
|
+
if (node.type === "ARITH") {
|
|
4833
|
+
const left = evalAssertArith(node.left);
|
|
4834
|
+
const right = evalAssertArith(node.right);
|
|
4835
|
+
switch (node.op) {
|
|
4836
|
+
case "+":
|
|
4837
|
+
return left + right;
|
|
4838
|
+
case "-":
|
|
4839
|
+
return left - right;
|
|
4840
|
+
case "*":
|
|
4841
|
+
return left * right;
|
|
4842
|
+
case "/":
|
|
4843
|
+
return left / right;
|
|
4844
|
+
case "%":
|
|
4845
|
+
return left % right;
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
4849
|
+
}
|
|
4850
|
+
function compareAssertValues(op, leftStr, rightStr) {
|
|
4851
|
+
const leftNum = Number(leftStr);
|
|
4852
|
+
const rightNum = Number(rightStr);
|
|
4853
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
4854
|
+
switch (op) {
|
|
4855
|
+
case "=":
|
|
4856
|
+
return leftStr === rightStr;
|
|
4857
|
+
case "!=":
|
|
4858
|
+
case "<>":
|
|
4859
|
+
return leftStr !== rightStr;
|
|
4860
|
+
case ">":
|
|
4861
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
4862
|
+
case "<":
|
|
4863
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
4864
|
+
case ">=":
|
|
4865
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
4866
|
+
case "<=":
|
|
4867
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4536
4870
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
4537
4871
|
if (isNoFromSelect(stmt)) {
|
|
4538
4872
|
return executeNoFromSelect(stmt);
|
|
@@ -5977,7 +6311,7 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
5977
6311
|
return [
|
|
5978
6312
|
`CREATE TEMP TABLE ${stmt.name}`,
|
|
5979
6313
|
` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
|
|
5980
|
-
` rows: \u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u4E0D\u660E\uFF08\u4E0A\u9650 ${TEMP_TABLE_MAX_ROWS} \u884C\u3001\u8D85\u904E\u306F\u30A8\u30E9\u30FC\uFF09`,
|
|
6314
|
+
` rows: \u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u4E0D\u660E\uFF08\u65E2\u5B9A\u4E0A\u9650 ${TEMP_TABLE_MAX_ROWS} \u884C\u3001tempTableMaxRows \u3067\u5909\u66F4\u53EF\u3001\u8D85\u904E\u306F\u30A8\u30E9\u30FC\uFF09`,
|
|
5981
6315
|
...buildPlanForBatchQuery(stmt.query, info).map((l) => ` ${l}`)
|
|
5982
6316
|
];
|
|
5983
6317
|
}
|
|
@@ -5990,8 +6324,33 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
5990
6324
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
5991
6325
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
5992
6326
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
6327
|
+
if (stmt.type === "ASSERT") {
|
|
6328
|
+
const lines = [
|
|
6329
|
+
`ASSERT ${stmt.text}`,
|
|
6330
|
+
" 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"
|
|
6331
|
+
];
|
|
6332
|
+
const subqueries = [stmt.left, stmt.right, stmt.low, stmt.high].filter(
|
|
6333
|
+
(o) => o !== null && o.type === "SCALAR_SUBQUERY"
|
|
6334
|
+
);
|
|
6335
|
+
subqueries.forEach((sq, i) => {
|
|
6336
|
+
lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
|
|
6337
|
+
const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
6338
|
+
lines.push(...buildPlanForBatchQuery(sq.query, subInfo).map((l) => ` ${l}`));
|
|
6339
|
+
});
|
|
6340
|
+
return lines;
|
|
6341
|
+
}
|
|
5993
6342
|
return buildPlanForBatchQuery(stmt, info);
|
|
5994
6343
|
}
|
|
6344
|
+
function hasTempTableRef(node) {
|
|
6345
|
+
if (Array.isArray(node)) return node.some(hasTempTableRef);
|
|
6346
|
+
if (node !== null && typeof node === "object") {
|
|
6347
|
+
const obj = node;
|
|
6348
|
+
const cte = obj["cteName"];
|
|
6349
|
+
if (typeof cte === "string" && cte.startsWith("#")) return true;
|
|
6350
|
+
return Object.values(obj).some(hasTempTableRef);
|
|
6351
|
+
}
|
|
6352
|
+
return false;
|
|
6353
|
+
}
|
|
5995
6354
|
function buildPlanForBatchQuery(query, info) {
|
|
5996
6355
|
if (info.tempTablesReferenced.length === 0) {
|
|
5997
6356
|
return buildExplainPlan(query);
|
|
@@ -6428,6 +6787,90 @@ function isSubtableRow(v) {
|
|
|
6428
6787
|
return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
|
|
6429
6788
|
}
|
|
6430
6789
|
|
|
6790
|
+
// src/output/batchEnvelope.ts
|
|
6791
|
+
function toMutationSummary(result) {
|
|
6792
|
+
if (result.type === "INSERT") {
|
|
6793
|
+
return { insertedCount: result.insertedCount, createdIds: result.createdIds };
|
|
6794
|
+
}
|
|
6795
|
+
if (result.type === "UPDATE") return { updatedCount: result.updatedCount };
|
|
6796
|
+
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
6797
|
+
if (result.type === "UPSERT") {
|
|
6798
|
+
return { insertedCount: result.insertedCount, updatedCount: result.updatedCount };
|
|
6799
|
+
}
|
|
6800
|
+
return { reorderedParentCount: result.reorderedParentCount };
|
|
6801
|
+
}
|
|
6802
|
+
function buildBatchEnvelope(batch, options = {}) {
|
|
6803
|
+
const { maxTotalRecords } = options;
|
|
6804
|
+
const results = [];
|
|
6805
|
+
let totalRows = 0;
|
|
6806
|
+
const statements = batch.statements.map((s) => {
|
|
6807
|
+
const entry = {
|
|
6808
|
+
index: s.index,
|
|
6809
|
+
type: s.type,
|
|
6810
|
+
status: s.status
|
|
6811
|
+
};
|
|
6812
|
+
if (s.status === "error" && s.error) entry.error = s.error;
|
|
6813
|
+
if (s.status === "skipped" && s.skippedReason) entry.skippedReason = s.skippedReason;
|
|
6814
|
+
if (s.tempTable !== void 0) entry.tempTable = s.tempTable;
|
|
6815
|
+
if (s.rowCount !== void 0) entry.rowCount = s.rowCount;
|
|
6816
|
+
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
6817
|
+
totalRows += s.result.rowCount;
|
|
6818
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
6819
|
+
throw new Error(
|
|
6820
|
+
`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
entry.resultIndex = results.length;
|
|
6824
|
+
results.push({
|
|
6825
|
+
columns: s.result.columns,
|
|
6826
|
+
rows: s.result.rows,
|
|
6827
|
+
rowCount: s.result.rowCount,
|
|
6828
|
+
warnings: s.result.warnings ?? []
|
|
6829
|
+
});
|
|
6830
|
+
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
6831
|
+
Object.assign(entry, toMutationSummary(s.result));
|
|
6832
|
+
}
|
|
6833
|
+
return entry;
|
|
6834
|
+
});
|
|
6835
|
+
return {
|
|
6836
|
+
ok: batch.ok,
|
|
6837
|
+
batch: true,
|
|
6838
|
+
statementCount: batch.statementCount,
|
|
6839
|
+
statements,
|
|
6840
|
+
results,
|
|
6841
|
+
// バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
|
|
6842
|
+
warnings: []
|
|
6843
|
+
};
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6846
|
+
// src/node/config.ts
|
|
6847
|
+
function envString(name) {
|
|
6848
|
+
const v = process.env[name];
|
|
6849
|
+
return v && v.trim() ? v : null;
|
|
6850
|
+
}
|
|
6851
|
+
function envInt(name) {
|
|
6852
|
+
const v = envString(name);
|
|
6853
|
+
if (v === null) return null;
|
|
6854
|
+
const n = Number(v);
|
|
6855
|
+
if (!Number.isInteger(n) || n <= 0) return null;
|
|
6856
|
+
return n;
|
|
6857
|
+
}
|
|
6858
|
+
function envNonNegativeInt(name) {
|
|
6859
|
+
const v = envString(name);
|
|
6860
|
+
if (v === null) return null;
|
|
6861
|
+
const n = Number(v);
|
|
6862
|
+
if (!Number.isInteger(n) || n < 0) return null;
|
|
6863
|
+
return n;
|
|
6864
|
+
}
|
|
6865
|
+
function resolveRequestGateOptions(base) {
|
|
6866
|
+
return {
|
|
6867
|
+
...base,
|
|
6868
|
+
maxConcurrent: envInt("KSQL_MAX_CONCURRENT") ?? base.maxConcurrent,
|
|
6869
|
+
// KSQL_RETRY=0(リトライ無効)は有効値のため envNonNegativeInt で読む
|
|
6870
|
+
maxRetries: envNonNegativeInt("KSQL_RETRY") ?? base.maxRetries
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
|
|
6431
6874
|
// src/node/appProfiles.ts
|
|
6432
6875
|
var import_fs = require("fs");
|
|
6433
6876
|
function parseTokenMap(raw) {
|
|
@@ -6712,8 +7155,11 @@ var RequestGate = class {
|
|
|
6712
7155
|
this.waiters = [];
|
|
6713
7156
|
this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
|
|
6714
7157
|
this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
|
|
6715
|
-
this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
6716
|
-
this.maxDelayMs =
|
|
7158
|
+
this.baseDelayMs = clampInt(options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS, 1, 6e4);
|
|
7159
|
+
this.maxDelayMs = Math.max(
|
|
7160
|
+
clampInt(options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS, 1, 6e5),
|
|
7161
|
+
this.baseDelayMs
|
|
7162
|
+
);
|
|
6717
7163
|
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6718
7164
|
this.random = options.random ?? Math.random;
|
|
6719
7165
|
}
|
|
@@ -6724,6 +7170,18 @@ var RequestGate = class {
|
|
|
6724
7170
|
get limit() {
|
|
6725
7171
|
return this.maxConcurrent;
|
|
6726
7172
|
}
|
|
7173
|
+
/** 解決済みの GET リトライ回数(テスト・診断用) */
|
|
7174
|
+
get retries() {
|
|
7175
|
+
return this.maxRetries;
|
|
7176
|
+
}
|
|
7177
|
+
/** 解決済みのバックオフ初期値ミリ秒(テスト・診断用) */
|
|
7178
|
+
get retryBaseDelayMs() {
|
|
7179
|
+
return this.baseDelayMs;
|
|
7180
|
+
}
|
|
7181
|
+
/** 解決済みのバックオフ上限ミリ秒(テスト・診断用) */
|
|
7182
|
+
get retryMaxDelayMs() {
|
|
7183
|
+
return this.maxDelayMs;
|
|
7184
|
+
}
|
|
6727
7185
|
/** GET 系: セマフォ + リトライ付きで実行する */
|
|
6728
7186
|
async runReadOnly(fn) {
|
|
6729
7187
|
let attempt = 0;
|
|
@@ -6780,11 +7238,11 @@ function withRequestGate(client, gate) {
|
|
|
6780
7238
|
};
|
|
6781
7239
|
}
|
|
6782
7240
|
var globalGate = null;
|
|
6783
|
-
function getGlobalRequestGate(
|
|
7241
|
+
function getGlobalRequestGate(options) {
|
|
6784
7242
|
if (globalGate === null) {
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
7243
|
+
globalGate = new RequestGate(
|
|
7244
|
+
typeof options === "number" ? { maxConcurrent: options } : options ?? {}
|
|
7245
|
+
);
|
|
6788
7246
|
}
|
|
6789
7247
|
return globalGate;
|
|
6790
7248
|
}
|
|
@@ -7008,10 +7466,17 @@ Options:
|
|
|
7008
7466
|
--console Start interactive console mode
|
|
7009
7467
|
--dry-run Parse and show execution plan only
|
|
7010
7468
|
--format <type> Output format: table | json | jsonl | csv | markdown | md
|
|
7469
|
+
(batch + json: prints one JSON envelope for the whole batch)
|
|
7011
7470
|
--max-records <n> Max records to fetch (default: 500)
|
|
7012
7471
|
--fetch-parallel <n> Parallel page fetches per query: 1-10 (default: 3)
|
|
7013
7472
|
--on-limit <mode> On record limit: error | truncate
|
|
7473
|
+
--temp-table-max-rows <n> Max rows per temp table (default: 10000, always errors on overflow)
|
|
7014
7474
|
--timeout <ms> Request timeout in milliseconds (default: 30000)
|
|
7475
|
+
--max-concurrent <n> Max concurrent kintone requests: 1-50 (default: 10)
|
|
7476
|
+
(process-wide; fixed at first resolution; KSQL_MAX_CONCURRENT wins)
|
|
7477
|
+
--retry <n> GET retry count: 0-10, 0 disables (default: 3; KSQL_RETRY wins)
|
|
7478
|
+
--retry-base-delay <ms> GET retry backoff base delay (default: 500)
|
|
7479
|
+
--retry-max-delay <ms> GET retry backoff max delay (default: 8000)
|
|
7015
7480
|
--config <path> Config file path (default: ./ksql.config.json)
|
|
7016
7481
|
--profile <name> Profile name in config
|
|
7017
7482
|
--base-url <url> kintone base URL
|
|
@@ -7058,6 +7523,7 @@ function parseArgs(argv) {
|
|
|
7058
7523
|
maxRecords: null,
|
|
7059
7524
|
fetchParallel: null,
|
|
7060
7525
|
onLimit: null,
|
|
7526
|
+
tempTableMaxRows: null,
|
|
7061
7527
|
timeout: null,
|
|
7062
7528
|
configPath: null,
|
|
7063
7529
|
profile: null,
|
|
@@ -7085,6 +7551,10 @@ function parseArgs(argv) {
|
|
|
7085
7551
|
allowWithoutWhere: false,
|
|
7086
7552
|
continueOnError: false,
|
|
7087
7553
|
dmlMaxRows: null,
|
|
7554
|
+
maxConcurrent: null,
|
|
7555
|
+
retry: null,
|
|
7556
|
+
retryBaseDelay: null,
|
|
7557
|
+
retryMaxDelay: null,
|
|
7088
7558
|
userFormat: null,
|
|
7089
7559
|
arrayFormat: null,
|
|
7090
7560
|
tableFormat: null,
|
|
@@ -7276,6 +7746,13 @@ function parseArgs(argv) {
|
|
|
7276
7746
|
i++;
|
|
7277
7747
|
continue;
|
|
7278
7748
|
}
|
|
7749
|
+
if (a === "--temp-table-max-rows") {
|
|
7750
|
+
const n = Number(v);
|
|
7751
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --temp-table-max-rows must be a positive integer.");
|
|
7752
|
+
out.tempTableMaxRows = n;
|
|
7753
|
+
i++;
|
|
7754
|
+
continue;
|
|
7755
|
+
}
|
|
7279
7756
|
if (a === "--fetch-parallel") {
|
|
7280
7757
|
const n = Number(v);
|
|
7281
7758
|
if (!Number.isInteger(n) || n < 1 || n > 10) throw new Error("ArgumentError: --fetch-parallel must be an integer between 1 and 10.");
|
|
@@ -7311,6 +7788,34 @@ function parseArgs(argv) {
|
|
|
7311
7788
|
i++;
|
|
7312
7789
|
continue;
|
|
7313
7790
|
}
|
|
7791
|
+
if (a === "--max-concurrent") {
|
|
7792
|
+
const n = Number(v);
|
|
7793
|
+
if (!Number.isInteger(n) || n < 1 || n > 50) throw new Error("ArgumentError: --max-concurrent must be an integer between 1 and 50.");
|
|
7794
|
+
out.maxConcurrent = n;
|
|
7795
|
+
i++;
|
|
7796
|
+
continue;
|
|
7797
|
+
}
|
|
7798
|
+
if (a === "--retry") {
|
|
7799
|
+
const n = Number(v);
|
|
7800
|
+
if (!Number.isInteger(n) || n < 0 || n > 10) throw new Error("ArgumentError: --retry must be an integer between 0 and 10 (0 disables retry).");
|
|
7801
|
+
out.retry = n;
|
|
7802
|
+
i++;
|
|
7803
|
+
continue;
|
|
7804
|
+
}
|
|
7805
|
+
if (a === "--retry-base-delay") {
|
|
7806
|
+
const n = Number(v);
|
|
7807
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --retry-base-delay must be a positive integer (ms).");
|
|
7808
|
+
out.retryBaseDelay = n;
|
|
7809
|
+
i++;
|
|
7810
|
+
continue;
|
|
7811
|
+
}
|
|
7812
|
+
if (a === "--retry-max-delay") {
|
|
7813
|
+
const n = Number(v);
|
|
7814
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error("ArgumentError: --retry-max-delay must be a positive integer (ms).");
|
|
7815
|
+
out.retryMaxDelay = n;
|
|
7816
|
+
i++;
|
|
7817
|
+
continue;
|
|
7818
|
+
}
|
|
7314
7819
|
throw new Error(`ArgumentError: unknown option ${a}`);
|
|
7315
7820
|
}
|
|
7316
7821
|
return out;
|
|
@@ -7334,35 +7839,35 @@ function resolveTokenValue(raw) {
|
|
|
7334
7839
|
}
|
|
7335
7840
|
return raw;
|
|
7336
7841
|
}
|
|
7337
|
-
function
|
|
7842
|
+
function envString2(name) {
|
|
7338
7843
|
const v = process.env[name];
|
|
7339
7844
|
return v && v.trim() ? v : null;
|
|
7340
7845
|
}
|
|
7341
|
-
function
|
|
7342
|
-
const v =
|
|
7846
|
+
function envInt2(name) {
|
|
7847
|
+
const v = envString2(name);
|
|
7343
7848
|
if (v === null) return null;
|
|
7344
7849
|
const n = Number(v);
|
|
7345
7850
|
if (!Number.isInteger(n) || n <= 0) return null;
|
|
7346
7851
|
return n;
|
|
7347
7852
|
}
|
|
7348
7853
|
function envBool(name) {
|
|
7349
|
-
const v =
|
|
7854
|
+
const v = envString2(name);
|
|
7350
7855
|
if (v === null) return null;
|
|
7351
7856
|
if (v === "1" || v.toLowerCase() === "true") return true;
|
|
7352
7857
|
if (v === "0" || v.toLowerCase() === "false") return false;
|
|
7353
7858
|
return null;
|
|
7354
7859
|
}
|
|
7355
7860
|
function envFormat(name) {
|
|
7356
|
-
const v =
|
|
7861
|
+
const v = envString2(name);
|
|
7357
7862
|
return normalizeOutputFormat(v);
|
|
7358
7863
|
}
|
|
7359
7864
|
function envOnLimit(name) {
|
|
7360
|
-
const v =
|
|
7865
|
+
const v = envString2(name);
|
|
7361
7866
|
if (v === "error" || v === "truncate") return v;
|
|
7362
7867
|
return null;
|
|
7363
7868
|
}
|
|
7364
7869
|
function envAuth(name) {
|
|
7365
|
-
const v =
|
|
7870
|
+
const v = envString2(name);
|
|
7366
7871
|
if (v === "token" || v === "userpass" || v === "auto") return v;
|
|
7367
7872
|
return null;
|
|
7368
7873
|
}
|
|
@@ -7387,6 +7892,12 @@ function getAffectedCount(result) {
|
|
|
7387
7892
|
if (result.type === "REORDER") return result.reorderedParentCount;
|
|
7388
7893
|
return 0;
|
|
7389
7894
|
}
|
|
7895
|
+
function buildAssertOutput(result, format, pretty) {
|
|
7896
|
+
const payload = { ok: true, type: result.type, condition: result.condition };
|
|
7897
|
+
if (format === "json") return JSON.stringify(payload, null, pretty ? 2 : 0);
|
|
7898
|
+
if (format === "jsonl") return JSON.stringify(payload);
|
|
7899
|
+
return `assertion ok: ${result.condition}`;
|
|
7900
|
+
}
|
|
7390
7901
|
function buildMutationOutput(result, format, noHeader, pretty) {
|
|
7391
7902
|
const row = { type: result.type };
|
|
7392
7903
|
if (result.type === "INSERT") {
|
|
@@ -7510,7 +8021,7 @@ function buildBatchStatementSummary(s) {
|
|
|
7510
8021
|
else if (r.type === "UPDATE") parts.push(`updated=${r.updatedCount}`);
|
|
7511
8022
|
else if (r.type === "DELETE") parts.push(`deleted=${r.deletedCount}`);
|
|
7512
8023
|
else if (r.type === "UPSERT") parts.push(`inserted=${r.insertedCount} updated=${r.updatedCount}`);
|
|
7513
|
-
else parts.push(`reordered=${r.reorderedParentCount}`);
|
|
8024
|
+
else if (r.type === "REORDER") parts.push(`reordered=${r.reorderedParentCount}`);
|
|
7514
8025
|
}
|
|
7515
8026
|
if (s.status === "error" && s.error) parts.push(s.error.message);
|
|
7516
8027
|
if (s.status === "skipped" && s.skippedReason) parts.push(`reason=${s.skippedReason}`);
|
|
@@ -7526,15 +8037,11 @@ function buildBatchDmlConfirmMessage(analysis) {
|
|
|
7526
8037
|
return lines.join("\n");
|
|
7527
8038
|
}
|
|
7528
8039
|
function writeBatchOutput(batch, opts) {
|
|
7529
|
-
const
|
|
7530
|
-
|
|
7531
|
-
|
|
8040
|
+
const output = opts.format === "json" ? JSON.stringify(buildBatchEnvelope(batch), null, opts.pretty ? 2 : 0) : buildBatchResultsOutput(batch, opts);
|
|
8041
|
+
if (!opts.quiet) {
|
|
8042
|
+
for (const s of batch.statements) process.stderr.write(`${buildBatchStatementSummary(s)}
|
|
7532
8043
|
`);
|
|
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
8044
|
}
|
|
7537
|
-
const output = outputs.join("\n\n");
|
|
7538
8045
|
if (opts.outputPath) (0, import_fs2.writeFileSync)(opts.outputPath, `${output}
|
|
7539
8046
|
`, "utf-8");
|
|
7540
8047
|
else if (output) process.stdout.write(`${output}
|
|
@@ -7543,6 +8050,15 @@ function writeBatchOutput(batch, opts) {
|
|
|
7543
8050
|
const firstError = batch.statements.find((s) => s.status === "error");
|
|
7544
8051
|
return firstError?.error ? toExitCodeFromError(new Error(firstError.error.message)) : 1;
|
|
7545
8052
|
}
|
|
8053
|
+
function buildBatchResultsOutput(batch, opts) {
|
|
8054
|
+
const outputs = [];
|
|
8055
|
+
for (const s of batch.statements) {
|
|
8056
|
+
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
8057
|
+
outputs.push(buildOutput(s.result, opts.format, opts.noHeader, opts.pretty, opts.displayOptions));
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
return outputs.join("\n\n");
|
|
8061
|
+
}
|
|
7546
8062
|
function shouldExitOnEmpty(dryRun, exitOnEmpty, rowCount) {
|
|
7547
8063
|
if (dryRun) return false;
|
|
7548
8064
|
return exitOnEmpty && rowCount === 0;
|
|
@@ -7681,6 +8197,7 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
7681
8197
|
pushOpt(argv, "--max-records", base.maxRecords);
|
|
7682
8198
|
pushOpt(argv, "--fetch-parallel", base.fetchParallel);
|
|
7683
8199
|
pushOpt(argv, "--on-limit", base.onLimit);
|
|
8200
|
+
pushOpt(argv, "--temp-table-max-rows", base.tempTableMaxRows);
|
|
7684
8201
|
pushOpt(argv, "--timeout", base.timeout);
|
|
7685
8202
|
pushOpt(argv, "--output", base.outputPath);
|
|
7686
8203
|
pushOpt(argv, "--user-format", base.userFormat);
|
|
@@ -7689,6 +8206,10 @@ function buildReplExecArgv(base, sql, dryRun, format) {
|
|
|
7689
8206
|
pushOpt(argv, "--date-format", base.dateFormat);
|
|
7690
8207
|
pushOpt(argv, "--attachment-format", base.attachmentFormat);
|
|
7691
8208
|
pushOpt(argv, "--dml-max-rows", base.dmlMaxRows);
|
|
8209
|
+
pushOpt(argv, "--max-concurrent", base.maxConcurrent);
|
|
8210
|
+
pushOpt(argv, "--retry", base.retry);
|
|
8211
|
+
pushOpt(argv, "--retry-base-delay", base.retryBaseDelay);
|
|
8212
|
+
pushOpt(argv, "--retry-max-delay", base.retryMaxDelay);
|
|
7692
8213
|
const tokenMapArg = buildTokenMapArg(base.tokenMap);
|
|
7693
8214
|
if (tokenMapArg) argv.push("--token-map", tokenMapArg);
|
|
7694
8215
|
if (base.noHeader) argv.push("--no-header");
|
|
@@ -8163,13 +8684,13 @@ async function run() {
|
|
|
8163
8684
|
process.stderr.write("ArgumentError: specify -e/--execute or -f/--file. Use --help for details.\n");
|
|
8164
8685
|
return 2;
|
|
8165
8686
|
}
|
|
8166
|
-
const configPath = args.configPath ??
|
|
8687
|
+
const configPath = args.configPath ?? envString2("KSQL_CONFIG") ?? "./ksql.config.json";
|
|
8167
8688
|
let config = {};
|
|
8168
8689
|
try {
|
|
8169
8690
|
config = loadConfig(configPath);
|
|
8170
8691
|
} catch {
|
|
8171
8692
|
}
|
|
8172
|
-
const profileName = args.profile ??
|
|
8693
|
+
const profileName = args.profile ?? envString2("KSQL_PROFILE") ?? config.defaultProfile ?? "dev";
|
|
8173
8694
|
const profile = config.profiles?.[profileName] ?? {};
|
|
8174
8695
|
let sql = null;
|
|
8175
8696
|
let hasProfileSyntax = false;
|
|
@@ -8212,7 +8733,7 @@ async function run() {
|
|
|
8212
8733
|
isDmlStatement = isDmlType(stmtType);
|
|
8213
8734
|
hasWhere = hasWhereClause(stmt);
|
|
8214
8735
|
insertValuesCount = getInsertValuesCount(stmt);
|
|
8215
|
-
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || isDmlStatement;
|
|
8736
|
+
const supported = stmtType === "SELECT" || stmtType === "UNION" || stmtType === "WITH" || stmtType === "EXPLAIN" || stmtType === "SHOW_APPS" || stmtType === "DESCRIBE" || stmtType === "ASSERT" || isDmlStatement;
|
|
8216
8737
|
if (!supported) {
|
|
8217
8738
|
process.stderr.write(`ArgumentError: unsupported statement type in CLI: ${stmtType}
|
|
8218
8739
|
`);
|
|
@@ -8225,10 +8746,11 @@ async function run() {
|
|
|
8225
8746
|
return 1;
|
|
8226
8747
|
}
|
|
8227
8748
|
}
|
|
8228
|
-
const maxRecords = args.maxRecords ??
|
|
8229
|
-
const fetchParallel = args.fetchParallel ??
|
|
8749
|
+
const maxRecords = args.maxRecords ?? envInt2("KSQL_MAX_RECORDS") ?? profile.query?.maxRecords ?? 500;
|
|
8750
|
+
const fetchParallel = args.fetchParallel ?? envInt2("KSQL_FETCH_PARALLEL") ?? profile.query?.fetchParallel ?? 3;
|
|
8230
8751
|
const onLimit = args.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile.query?.onLimit ?? "error";
|
|
8231
|
-
const timeout = args.timeout ??
|
|
8752
|
+
const timeout = args.timeout ?? envInt2("KSQL_TIMEOUT") ?? profile.query?.timeout ?? 3e4;
|
|
8753
|
+
const tempTableMaxRows = args.tempTableMaxRows ?? envInt2("KSQL_TEMP_TABLE_MAX_ROWS") ?? profile.query?.tempTableMaxRows ?? void 0;
|
|
8232
8754
|
if (!Number.isInteger(fetchParallel) || fetchParallel < 1 || fetchParallel > 10) {
|
|
8233
8755
|
process.stderr.write("ArgumentError: fetch-parallel must be an integer between 1 and 10.\n");
|
|
8234
8756
|
return 2;
|
|
@@ -8245,12 +8767,12 @@ async function run() {
|
|
|
8245
8767
|
const quiet = args.quiet || envBool("KSQL_QUIET") === true || Boolean(profile.output?.quiet);
|
|
8246
8768
|
const debug = args.debug || args.debugUrl || envBool("KSQL_DEBUG") === true || envBool("KSQL_DEBUG_URL") === true;
|
|
8247
8769
|
const debugHeaders = args.debugHeaders || envBool("KSQL_DEBUG_HEADERS") === true;
|
|
8248
|
-
const outputPath = args.outputPath ??
|
|
8770
|
+
const outputPath = args.outputPath ?? envString2("KSQL_OUTPUT") ?? profile.output?.output ?? null;
|
|
8249
8771
|
const exitOnEmpty = args.exitOnEmpty || envBool("KSQL_EXIT_ON_EMPTY") === true || Boolean(profile.output?.exitOnEmpty);
|
|
8250
8772
|
const allowDml = args.allowDml || envBool("KSQL_ALLOW_DML") === true || Boolean(profile.dml?.allowDml);
|
|
8251
8773
|
const yes = args.yes || envBool("KSQL_YES") === true || Boolean(profile.dml?.yes);
|
|
8252
8774
|
const allowWithoutWhere = args.allowWithoutWhere || envBool("KSQL_ALLOW_WITHOUT_WHERE") === true || Boolean(profile.dml?.allowWithoutWhere);
|
|
8253
|
-
const dmlMaxRows = args.dmlMaxRows ??
|
|
8775
|
+
const dmlMaxRows = args.dmlMaxRows ?? envInt2("KSQL_DML_MAX_ROWS") ?? profile.dml?.maxRows ?? 100;
|
|
8254
8776
|
if (format === "markdown" && noHeader) {
|
|
8255
8777
|
process.stderr.write("ArgumentError: --no-header cannot be used with --format markdown|md.\n");
|
|
8256
8778
|
return 2;
|
|
@@ -8264,7 +8786,7 @@ async function run() {
|
|
|
8264
8786
|
attachmentFormat: args.attachmentFormat ?? profile.output?.attachmentFormat ?? "full"
|
|
8265
8787
|
};
|
|
8266
8788
|
const appIds = sql ? extractAppIds(sql) : [];
|
|
8267
|
-
const defaultApp = args.app ??
|
|
8789
|
+
const defaultApp = args.app ?? envInt2("KSQL_APP") ?? profile.app ?? null;
|
|
8268
8790
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
8269
8791
|
const allowNoFromSelect = isNoFromSelectStatement(parsedStmt) || stmtType === "SHOW_APPS";
|
|
8270
8792
|
if (appIds.length === 0 && !allowNoFromSelect && !args.dryRun && args.diagRecordId === null) {
|
|
@@ -8324,10 +8846,10 @@ async function run() {
|
|
|
8324
8846
|
return 2;
|
|
8325
8847
|
}
|
|
8326
8848
|
}
|
|
8327
|
-
const mapFromEnv =
|
|
8849
|
+
const mapFromEnv = envString2("KSQL_TOKEN_MAP") ? parseTokenMap(envString2("KSQL_TOKEN_MAP")) : {};
|
|
8328
8850
|
const mapFromFile = args.tokenFile ? parseTokenFile(args.tokenFile) : {};
|
|
8329
8851
|
const mapFromArg = args.tokenMap;
|
|
8330
|
-
const singleToken = args.token ??
|
|
8852
|
+
const singleToken = args.token ?? envString2("KSQL_TOKEN");
|
|
8331
8853
|
const profileClientMap = /* @__PURE__ */ new Map();
|
|
8332
8854
|
const missingAppProfiles = [];
|
|
8333
8855
|
const usedProfiles = /* @__PURE__ */ new Set([...appProfileByApp.values(), profileName]);
|
|
@@ -8338,17 +8860,17 @@ async function run() {
|
|
|
8338
8860
|
`);
|
|
8339
8861
|
return 2;
|
|
8340
8862
|
}
|
|
8341
|
-
const baseUrl = args.baseUrl ??
|
|
8342
|
-
const guestSpaceId = args.guestSpaceId ??
|
|
8863
|
+
const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? p.baseUrl ?? "";
|
|
8864
|
+
const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? p.guestSpaceId ?? null;
|
|
8343
8865
|
if (!baseUrl) {
|
|
8344
8866
|
process.stderr.write(`AuthError: --base-url is required for profile "${pName}".
|
|
8345
8867
|
`);
|
|
8346
8868
|
return 3;
|
|
8347
8869
|
}
|
|
8348
8870
|
const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? p.auth ?? "auto";
|
|
8349
|
-
const username = args.username ??
|
|
8350
|
-
const passwordFromEnvRef = p.passwordEnv ?
|
|
8351
|
-
const password = args.password ??
|
|
8871
|
+
const username = args.username ?? envString2("KSQL_USERNAME") ?? p.username ?? null;
|
|
8872
|
+
const passwordFromEnvRef = p.passwordEnv ? envString2(p.passwordEnv) : null;
|
|
8873
|
+
const password = args.password ?? envString2("KSQL_PASSWORD") ?? passwordFromEnvRef ?? p.password ?? null;
|
|
8352
8874
|
const hasUserPass = Boolean(username && password);
|
|
8353
8875
|
const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
|
|
8354
8876
|
if (auth === "userpass") {
|
|
@@ -8429,17 +8951,17 @@ async function run() {
|
|
|
8429
8951
|
`);
|
|
8430
8952
|
return 2;
|
|
8431
8953
|
}
|
|
8432
|
-
const baseUrl = args.baseUrl ??
|
|
8433
|
-
const guestSpaceId = args.guestSpaceId ??
|
|
8954
|
+
const baseUrl = args.baseUrl ?? envString2("KSQL_BASE_URL") ?? diagProfile.baseUrl ?? "";
|
|
8955
|
+
const guestSpaceId = args.guestSpaceId ?? envInt2("KSQL_GUEST_SPACE_ID") ?? diagProfile.guestSpaceId ?? null;
|
|
8434
8956
|
if (!baseUrl) {
|
|
8435
8957
|
process.stderr.write(`AuthError: --base-url is required for profile "${diagProfileName}".
|
|
8436
8958
|
`);
|
|
8437
8959
|
return 3;
|
|
8438
8960
|
}
|
|
8439
8961
|
const authReq = args.auth ?? envAuth("KSQL_AUTH") ?? diagProfile.auth ?? "auto";
|
|
8440
|
-
const username = args.username ??
|
|
8441
|
-
const passwordFromEnvRef = diagProfile.passwordEnv ?
|
|
8442
|
-
const password = args.password ??
|
|
8962
|
+
const username = args.username ?? envString2("KSQL_USERNAME") ?? diagProfile.username ?? null;
|
|
8963
|
+
const passwordFromEnvRef = diagProfile.passwordEnv ? envString2(diagProfile.passwordEnv) : null;
|
|
8964
|
+
const password = args.password ?? envString2("KSQL_PASSWORD") ?? passwordFromEnvRef ?? diagProfile.password ?? null;
|
|
8443
8965
|
const hasUserPass = Boolean(username && password);
|
|
8444
8966
|
const auth = authReq === "auto" ? hasUserPass ? "userpass" : "token" : authReq;
|
|
8445
8967
|
try {
|
|
@@ -8537,7 +9059,12 @@ async function run() {
|
|
|
8537
9059
|
};
|
|
8538
9060
|
}
|
|
8539
9061
|
if (!args.dryRun) {
|
|
8540
|
-
client = withRequestGate(client, getGlobalRequestGate(
|
|
9062
|
+
client = withRequestGate(client, getGlobalRequestGate(resolveRequestGateOptions({
|
|
9063
|
+
maxConcurrent: args.maxConcurrent ?? profile.query?.maxConcurrent,
|
|
9064
|
+
maxRetries: args.retry ?? profile.query?.retry,
|
|
9065
|
+
baseDelayMs: args.retryBaseDelay ?? profile.query?.retryBaseDelayMs,
|
|
9066
|
+
maxDelayMs: args.retryMaxDelay ?? profile.query?.retryMaxDelayMs
|
|
9067
|
+
})));
|
|
8541
9068
|
}
|
|
8542
9069
|
try {
|
|
8543
9070
|
if (isDmlStatement && !args.dryRun) {
|
|
@@ -8580,6 +9107,7 @@ query=${label}`);
|
|
|
8580
9107
|
onLimitReached: onLimit,
|
|
8581
9108
|
cacheContext,
|
|
8582
9109
|
continueOnError: args.continueOnError,
|
|
9110
|
+
tempTableMaxRows,
|
|
8583
9111
|
timeoutMs: timeout,
|
|
8584
9112
|
confirm: batchContainsDml ? async (count, operation) => {
|
|
8585
9113
|
if (count > dmlMaxRows) {
|
|
@@ -8597,6 +9125,14 @@ query=${label}`);
|
|
|
8597
9125
|
confirm: isDmlStatement ? confirm : void 0,
|
|
8598
9126
|
cacheContext
|
|
8599
9127
|
});
|
|
9128
|
+
if (result.type === "ASSERT") {
|
|
9129
|
+
const output2 = buildAssertOutput(result, format, pretty);
|
|
9130
|
+
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
9131
|
+
`, "utf-8");
|
|
9132
|
+
else if (output2) process.stdout.write(`${output2}
|
|
9133
|
+
`);
|
|
9134
|
+
return 0;
|
|
9135
|
+
}
|
|
8600
9136
|
if (result.type !== "SELECT") {
|
|
8601
9137
|
const output2 = buildMutationOutput(result, format, noHeader, pretty);
|
|
8602
9138
|
if (outputPath) (0, import_fs2.writeFileSync)(outputPath, `${output2}
|
|
@@ -8642,6 +9178,7 @@ if (isDirectCliRun()) {
|
|
|
8642
9178
|
buildBatchDmlConfirmMessage,
|
|
8643
9179
|
buildBatchStatementSummary,
|
|
8644
9180
|
buildOutput,
|
|
9181
|
+
buildReplExecArgv,
|
|
8645
9182
|
extractAppIds,
|
|
8646
9183
|
normalizeAppKey,
|
|
8647
9184
|
normalizeSqlAppProfiles,
|
|
@@ -8650,5 +9187,6 @@ if (isDirectCliRun()) {
|
|
|
8650
9187
|
parseConsoleMetaCommand,
|
|
8651
9188
|
parseTokenFile,
|
|
8652
9189
|
parseTokenMap,
|
|
8653
|
-
shouldExitOnEmpty
|
|
9190
|
+
shouldExitOnEmpty,
|
|
9191
|
+
writeBatchOutput
|
|
8654
9192
|
});
|