@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-mcp/ksql-mcp.js
CHANGED
|
@@ -30999,6 +30999,7 @@ var KEYWORDS = /* @__PURE__ */ new Map([
|
|
|
30999
30999
|
["AVG", "AVG" /* AVG */],
|
|
31000
31000
|
["MAX", "MAX" /* MAX */],
|
|
31001
31001
|
["MIN", "MIN" /* MIN */],
|
|
31002
|
+
["ASSERT", "ASSERT" /* ASSERT */],
|
|
31002
31003
|
["AND", "AND" /* AND */],
|
|
31003
31004
|
["OR", "OR" /* OR */],
|
|
31004
31005
|
["NOT", "NOT" /* NOT */],
|
|
@@ -31344,6 +31345,58 @@ function isJapanese(cp) {
|
|
|
31344
31345
|
|
|
31345
31346
|
// src/parser/parser.ts
|
|
31346
31347
|
var MAX_BATCH_STATEMENTS = 20;
|
|
31348
|
+
var FUNC_CALL_PREFIX_KINDS = /* @__PURE__ */ new Set([
|
|
31349
|
+
"IDENT" /* IDENT */,
|
|
31350
|
+
"BIDENT" /* BIDENT */,
|
|
31351
|
+
"COUNT" /* COUNT */,
|
|
31352
|
+
"SUM" /* SUM */,
|
|
31353
|
+
"AVG" /* AVG */,
|
|
31354
|
+
"MAX" /* MAX */,
|
|
31355
|
+
"MIN" /* MIN */,
|
|
31356
|
+
"TODAY" /* TODAY */,
|
|
31357
|
+
"NOW" /* NOW */,
|
|
31358
|
+
"LOGINUSER" /* LOGINUSER */,
|
|
31359
|
+
"UPPER" /* UPPER */,
|
|
31360
|
+
"LOWER" /* LOWER */,
|
|
31361
|
+
"TRIM" /* TRIM */,
|
|
31362
|
+
"LTRIM" /* LTRIM */,
|
|
31363
|
+
"RTRIM" /* RTRIM */,
|
|
31364
|
+
"LENGTH" /* LENGTH */,
|
|
31365
|
+
"SUBSTRING" /* SUBSTRING */,
|
|
31366
|
+
"SUBSTR" /* SUBSTR */,
|
|
31367
|
+
"CONCAT" /* CONCAT */,
|
|
31368
|
+
"REPLACE" /* REPLACE */,
|
|
31369
|
+
"COALESCE" /* COALESCE */,
|
|
31370
|
+
"NULLIF" /* NULLIF */,
|
|
31371
|
+
"ISNULL" /* ISNULL */,
|
|
31372
|
+
"CAST" /* CAST */,
|
|
31373
|
+
"CONVERT" /* CONVERT */,
|
|
31374
|
+
"FORMAT" /* FORMAT */,
|
|
31375
|
+
"ROUND" /* ROUND */,
|
|
31376
|
+
"FLOOR" /* FLOOR */,
|
|
31377
|
+
"CEIL" /* CEIL */,
|
|
31378
|
+
"CEILING" /* CEILING */,
|
|
31379
|
+
"ABS" /* ABS */,
|
|
31380
|
+
"MOD" /* MOD */,
|
|
31381
|
+
"POWER" /* POWER */,
|
|
31382
|
+
"POW" /* POW */,
|
|
31383
|
+
"SQRT" /* SQRT */,
|
|
31384
|
+
"YEAR" /* YEAR */,
|
|
31385
|
+
"MONTH" /* MONTH */,
|
|
31386
|
+
"DAY" /* DAY */,
|
|
31387
|
+
"DATE_FORMAT" /* DATE_FORMAT */,
|
|
31388
|
+
"DATEDIFF" /* DATEDIFF */,
|
|
31389
|
+
"DATE_ADD" /* DATE_ADD */,
|
|
31390
|
+
"IF" /* IF */
|
|
31391
|
+
]);
|
|
31392
|
+
function needsSpaceBetween(prev, cur) {
|
|
31393
|
+
if (prev.kind === "(" /* LPAREN */ || prev.kind === "." /* DOT */) return false;
|
|
31394
|
+
if (cur.kind === ")" /* RPAREN */ || cur.kind === "," /* COMMA */ || cur.kind === "." /* DOT */) return false;
|
|
31395
|
+
if (cur.kind === "(" /* LPAREN */) {
|
|
31396
|
+
return !FUNC_CALL_PREFIX_KINDS.has(prev.kind);
|
|
31397
|
+
}
|
|
31398
|
+
return true;
|
|
31399
|
+
}
|
|
31347
31400
|
var ParseError = class extends Error {
|
|
31348
31401
|
constructor(message, token) {
|
|
31349
31402
|
super(`${message}\uFF08\u4F4D\u7F6E ${token.pos}\u3001\u30C8\u30FC\u30AF\u30F3: \u300C${token.value}\u300D\uFF09`);
|
|
@@ -31433,6 +31486,8 @@ var Parser = class {
|
|
|
31433
31486
|
return this.parseDescribe();
|
|
31434
31487
|
case "EXPLAIN" /* EXPLAIN */:
|
|
31435
31488
|
return this.parseExplain();
|
|
31489
|
+
case "ASSERT" /* ASSERT */:
|
|
31490
|
+
return this.parseAssert();
|
|
31436
31491
|
case "IDENT" /* IDENT */: {
|
|
31437
31492
|
const upper = tok.value.toUpperCase();
|
|
31438
31493
|
if (upper === "CREATE") return this.parseCreateTempTable();
|
|
@@ -31443,7 +31498,7 @@ var Parser = class {
|
|
|
31443
31498
|
break;
|
|
31444
31499
|
}
|
|
31445
31500
|
throw new ParseError(
|
|
31446
|
-
"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",
|
|
31501
|
+
"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",
|
|
31447
31502
|
tok
|
|
31448
31503
|
);
|
|
31449
31504
|
}
|
|
@@ -31537,6 +31592,174 @@ var Parser = class {
|
|
|
31537
31592
|
return { type: "EXPLAIN", query };
|
|
31538
31593
|
}
|
|
31539
31594
|
// ----------------------------------------------------------
|
|
31595
|
+
// ASSERT
|
|
31596
|
+
//
|
|
31597
|
+
// ASSERT <式> <比較演算子> <式>
|
|
31598
|
+
// ASSERT <式> BETWEEN <式> AND <式>
|
|
31599
|
+
//
|
|
31600
|
+
// 式: リテラル / 算術式 / スカラーサブクエリ。
|
|
31601
|
+
// フィールド参照(FROM コンテキストがない)・AND / OR 複合条件・
|
|
31602
|
+
// 裸の値のみ(ASSERT 1)は ParseError。
|
|
31603
|
+
// ----------------------------------------------------------
|
|
31604
|
+
parseAssert() {
|
|
31605
|
+
this.expect("ASSERT" /* ASSERT */);
|
|
31606
|
+
const condStart = this.pos;
|
|
31607
|
+
const left = this.parseAssertOperand();
|
|
31608
|
+
const opTok = this.peek();
|
|
31609
|
+
if (this.consume("BETWEEN" /* BETWEEN */)) {
|
|
31610
|
+
const low = this.parseAssertOperand();
|
|
31611
|
+
this.expect(
|
|
31612
|
+
"AND" /* AND */,
|
|
31613
|
+
"ASSERT \u306E BETWEEN \u306B\u306F AND \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: ASSERT (SELECT COUNT(*) FROM #t) BETWEEN 1 AND 500\uFF09"
|
|
31614
|
+
);
|
|
31615
|
+
const high = this.parseAssertOperand();
|
|
31616
|
+
this.rejectAssertCompound();
|
|
31617
|
+
return {
|
|
31618
|
+
type: "ASSERT",
|
|
31619
|
+
left,
|
|
31620
|
+
op: "BETWEEN",
|
|
31621
|
+
right: null,
|
|
31622
|
+
low,
|
|
31623
|
+
high,
|
|
31624
|
+
text: this.renderTokenRange(condStart, this.pos)
|
|
31625
|
+
};
|
|
31626
|
+
}
|
|
31627
|
+
const op = this.tryAssertCompareOp();
|
|
31628
|
+
if (op === null) {
|
|
31629
|
+
throw new ParseError(
|
|
31630
|
+
"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",
|
|
31631
|
+
opTok
|
|
31632
|
+
);
|
|
31633
|
+
}
|
|
31634
|
+
const right = this.parseAssertOperand();
|
|
31635
|
+
this.rejectAssertCompound();
|
|
31636
|
+
return {
|
|
31637
|
+
type: "ASSERT",
|
|
31638
|
+
left,
|
|
31639
|
+
op,
|
|
31640
|
+
right,
|
|
31641
|
+
low: null,
|
|
31642
|
+
high: null,
|
|
31643
|
+
text: this.renderTokenRange(condStart, this.pos)
|
|
31644
|
+
};
|
|
31645
|
+
}
|
|
31646
|
+
/** ASSERT のオペランド: 文字列 / スカラーサブクエリ / 数値算術式 */
|
|
31647
|
+
parseAssertOperand() {
|
|
31648
|
+
const tok = this.peek();
|
|
31649
|
+
if (tok.kind === "STRING" /* STRING */) {
|
|
31650
|
+
this.advance();
|
|
31651
|
+
return { type: "STRING", value: tok.value };
|
|
31652
|
+
}
|
|
31653
|
+
if (tok.kind === "(" /* LPAREN */ && this.peekAt(1).kind === "SELECT" /* SELECT */) {
|
|
31654
|
+
this.advance();
|
|
31655
|
+
const query = this.parseSelect();
|
|
31656
|
+
this.expect(")" /* RPAREN */);
|
|
31657
|
+
if (this.isArithOp(this.peek().kind)) {
|
|
31658
|
+
throw new ParseError(
|
|
31659
|
+
"\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",
|
|
31660
|
+
this.peek()
|
|
31661
|
+
);
|
|
31662
|
+
}
|
|
31663
|
+
const hasWildcard = query.columns.some(
|
|
31664
|
+
(c) => c.type === "WILDCARD" || c.type === "PARENT_WILDCARD"
|
|
31665
|
+
);
|
|
31666
|
+
if (!hasWildcard && query.columns.length > 1) {
|
|
31667
|
+
throw new ParseError("scalar subquery in ASSERT must return exactly 1 column.", tok);
|
|
31668
|
+
}
|
|
31669
|
+
return { type: "SCALAR_SUBQUERY", query };
|
|
31670
|
+
}
|
|
31671
|
+
if (tok.kind === "NUMBER" /* NUMBER */ || tok.kind === "(" /* LPAREN */ || tok.kind === "-" /* MINUS */) {
|
|
31672
|
+
const expr = this.parseArithAddSub();
|
|
31673
|
+
this.rejectNonLiteralArith(expr, tok);
|
|
31674
|
+
if (expr.type === "NUMBER") return expr;
|
|
31675
|
+
return expr;
|
|
31676
|
+
}
|
|
31677
|
+
if (this.tryStringFuncName() !== null) {
|
|
31678
|
+
throw new ParseError(
|
|
31679
|
+
"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",
|
|
31680
|
+
tok
|
|
31681
|
+
);
|
|
31682
|
+
}
|
|
31683
|
+
throw new ParseError(
|
|
31684
|
+
"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",
|
|
31685
|
+
tok
|
|
31686
|
+
);
|
|
31687
|
+
}
|
|
31688
|
+
/** ASSERT の算術式にフィールド参照・関数呼び出しが含まれていたら拒否する */
|
|
31689
|
+
rejectNonLiteralArith(node, tok) {
|
|
31690
|
+
if (node.type === "FIELD_REF") {
|
|
31691
|
+
throw new ParseError(
|
|
31692
|
+
`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}`,
|
|
31693
|
+
tok
|
|
31694
|
+
);
|
|
31695
|
+
}
|
|
31696
|
+
if (node.type === "STRING_FUNC") {
|
|
31697
|
+
throw new ParseError(
|
|
31698
|
+
"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",
|
|
31699
|
+
tok
|
|
31700
|
+
);
|
|
31701
|
+
}
|
|
31702
|
+
if (node.type === "ARITH") {
|
|
31703
|
+
this.rejectNonLiteralArith(node.left, tok);
|
|
31704
|
+
this.rejectNonLiteralArith(node.right, tok);
|
|
31705
|
+
}
|
|
31706
|
+
}
|
|
31707
|
+
/** ASSERT の比較演算子を読む(該当しなければ null・消費しない) */
|
|
31708
|
+
tryAssertCompareOp() {
|
|
31709
|
+
switch (this.peek().kind) {
|
|
31710
|
+
case "=" /* EQ */:
|
|
31711
|
+
this.advance();
|
|
31712
|
+
return "=";
|
|
31713
|
+
case "!=" /* NEQ */:
|
|
31714
|
+
this.advance();
|
|
31715
|
+
return "!=";
|
|
31716
|
+
case "<>" /* LT_GT */:
|
|
31717
|
+
this.advance();
|
|
31718
|
+
return "<>";
|
|
31719
|
+
case ">" /* GT */:
|
|
31720
|
+
this.advance();
|
|
31721
|
+
return ">";
|
|
31722
|
+
case "<" /* LT */:
|
|
31723
|
+
this.advance();
|
|
31724
|
+
return "<";
|
|
31725
|
+
case ">=" /* GTE */:
|
|
31726
|
+
this.advance();
|
|
31727
|
+
return ">=";
|
|
31728
|
+
case "<=" /* LTE */:
|
|
31729
|
+
this.advance();
|
|
31730
|
+
return "<=";
|
|
31731
|
+
default:
|
|
31732
|
+
return null;
|
|
31733
|
+
}
|
|
31734
|
+
}
|
|
31735
|
+
/** ASSERT は AND / OR による複合条件に対応しない(初期版仕様) */
|
|
31736
|
+
rejectAssertCompound() {
|
|
31737
|
+
const tok = this.peek();
|
|
31738
|
+
if (tok.kind === "AND" /* AND */ || tok.kind === "OR" /* OR */) {
|
|
31739
|
+
throw new ParseError(
|
|
31740
|
+
"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",
|
|
31741
|
+
tok
|
|
31742
|
+
);
|
|
31743
|
+
}
|
|
31744
|
+
}
|
|
31745
|
+
/**
|
|
31746
|
+
* トークン列 [fromIdx, toIdx) を SQL 風テキストに再構成する。
|
|
31747
|
+
* AssertError の "assertion failed: <条件>" メッセージ用(正規化表示で十分)。
|
|
31748
|
+
*/
|
|
31749
|
+
renderTokenRange(fromIdx, toIdx) {
|
|
31750
|
+
let out = "";
|
|
31751
|
+
for (let i = fromIdx; i < toIdx; i++) {
|
|
31752
|
+
const t = this.tokens[i];
|
|
31753
|
+
let text;
|
|
31754
|
+
if (t.kind === "STRING" /* STRING */) text = `'${t.value.replace(/'/g, "''")}'`;
|
|
31755
|
+
else if (t.kind === "BIDENT" /* BIDENT */) text = `\`${t.value}\``;
|
|
31756
|
+
else text = t.value;
|
|
31757
|
+
if (out.length > 0 && needsSpaceBetween(this.tokens[i - 1], t)) out += " ";
|
|
31758
|
+
out += text;
|
|
31759
|
+
}
|
|
31760
|
+
return out;
|
|
31761
|
+
}
|
|
31762
|
+
// ----------------------------------------------------------
|
|
31540
31763
|
// SELECT
|
|
31541
31764
|
// ----------------------------------------------------------
|
|
31542
31765
|
parseSelect() {
|
|
@@ -32886,7 +33109,7 @@ function isDmlType(type) {
|
|
|
32886
33109
|
return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
|
|
32887
33110
|
}
|
|
32888
33111
|
function isReadOnlyType(type) {
|
|
32889
|
-
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE";
|
|
33112
|
+
return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE" || type === "CREATE_TEMP_TABLE" || type === "DROP_TEMP_TABLE" || type === "ASSERT";
|
|
32890
33113
|
}
|
|
32891
33114
|
function hasWhereClause(stmt) {
|
|
32892
33115
|
if (!stmt || typeof stmt !== "object") return false;
|
|
@@ -35255,6 +35478,8 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
|
|
|
35255
35478
|
throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35256
35479
|
case "DROP_TEMP_TABLE":
|
|
35257
35480
|
throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
|
|
35481
|
+
case "ASSERT":
|
|
35482
|
+
return executeAssert(stmt, client, options, cacheContext);
|
|
35258
35483
|
}
|
|
35259
35484
|
}
|
|
35260
35485
|
var TEMP_TABLE_MAX_ROWS = 1e4;
|
|
@@ -35329,6 +35554,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35329
35554
|
failed.add(i);
|
|
35330
35555
|
if (e instanceof BatchTimeoutError) {
|
|
35331
35556
|
aborted2 = "timeout";
|
|
35557
|
+
} else if (e instanceof AssertError) {
|
|
35558
|
+
aborted2 = "assertion";
|
|
35332
35559
|
} else if (!options.continueOnError) {
|
|
35333
35560
|
aborted2 = "fail-fast";
|
|
35334
35561
|
}
|
|
@@ -35361,6 +35588,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
35361
35588
|
if (stmt.type === "EXPLAIN") {
|
|
35362
35589
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
35363
35590
|
}
|
|
35591
|
+
if (stmt.type === "ASSERT") {
|
|
35592
|
+
await executeAssert(stmt, client, options, cacheContext, tempTables);
|
|
35593
|
+
return {};
|
|
35594
|
+
}
|
|
35364
35595
|
if (info.tempTablesReferenced.length > 0) {
|
|
35365
35596
|
if (stmt.type === "SELECT" || stmt.type === "UNION") {
|
|
35366
35597
|
return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
|
|
@@ -35437,6 +35668,107 @@ function parseSqlBatch(sql) {
|
|
|
35437
35668
|
const tokens = new Lexer(sql).tokenize();
|
|
35438
35669
|
return new Parser(tokens).parseStatements();
|
|
35439
35670
|
}
|
|
35671
|
+
var AssertError = class extends Error {
|
|
35672
|
+
constructor(message) {
|
|
35673
|
+
super(`AssertError: ${message}`);
|
|
35674
|
+
this.name = "AssertError";
|
|
35675
|
+
}
|
|
35676
|
+
};
|
|
35677
|
+
async function executeAssert(stmt, client, options, cacheContext, tempTables) {
|
|
35678
|
+
const left = await evalAssertOperand(stmt.left, client, options, cacheContext, tempTables);
|
|
35679
|
+
if (stmt.op === "BETWEEN") {
|
|
35680
|
+
if (stmt.low === null || stmt.high === null) {
|
|
35681
|
+
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
35682
|
+
}
|
|
35683
|
+
const low = await evalAssertOperand(stmt.low, client, options, cacheContext, tempTables);
|
|
35684
|
+
const high = await evalAssertOperand(stmt.high, client, options, cacheContext, tempTables);
|
|
35685
|
+
if (!compareAssertValues(">=", left, low) || !compareAssertValues("<=", left, high)) {
|
|
35686
|
+
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
35687
|
+
}
|
|
35688
|
+
return { type: "ASSERT", condition: stmt.text };
|
|
35689
|
+
}
|
|
35690
|
+
if (stmt.right === null) {
|
|
35691
|
+
throw new Error("ArgumentError: malformed ASSERT statement.");
|
|
35692
|
+
}
|
|
35693
|
+
const right = await evalAssertOperand(stmt.right, client, options, cacheContext, tempTables);
|
|
35694
|
+
if (!compareAssertValues(stmt.op, left, right)) {
|
|
35695
|
+
throw new AssertError(`assertion failed: ${stmt.text} (actual: ${left}).`);
|
|
35696
|
+
}
|
|
35697
|
+
return { type: "ASSERT", condition: stmt.text };
|
|
35698
|
+
}
|
|
35699
|
+
async function evalAssertOperand(operand, client, options, cacheContext, tempTables) {
|
|
35700
|
+
switch (operand.type) {
|
|
35701
|
+
case "NUMBER":
|
|
35702
|
+
return String(operand.value);
|
|
35703
|
+
case "STRING":
|
|
35704
|
+
return operand.value;
|
|
35705
|
+
case "ARITH":
|
|
35706
|
+
return String(evalAssertArith(operand));
|
|
35707
|
+
case "SCALAR_SUBQUERY": {
|
|
35708
|
+
const { query, probed } = withScalarProbeLimit(operand.query);
|
|
35709
|
+
const result = await runSubquery(query, client, options, cacheContext, tempTables);
|
|
35710
|
+
if (result.columns.length > 1) {
|
|
35711
|
+
throw new AssertError(
|
|
35712
|
+
`scalar subquery returned ${result.columns.length} columns (expected 1 column).`
|
|
35713
|
+
);
|
|
35714
|
+
}
|
|
35715
|
+
if (result.rowCount === 0) {
|
|
35716
|
+
throw new AssertError("scalar subquery returned no rows (expected 1 row).");
|
|
35717
|
+
}
|
|
35718
|
+
if (result.rowCount > 1) {
|
|
35719
|
+
const rows = probed && result.rowCount === 2 ? "2 or more rows" : `${result.rowCount} rows`;
|
|
35720
|
+
throw new AssertError(`scalar subquery returned ${rows} (expected 1 row).`);
|
|
35721
|
+
}
|
|
35722
|
+
const col = result.columns[0] ?? "";
|
|
35723
|
+
return result.rows[0]?.[col] ?? "";
|
|
35724
|
+
}
|
|
35725
|
+
}
|
|
35726
|
+
}
|
|
35727
|
+
function withScalarProbeLimit(query) {
|
|
35728
|
+
const hasAgg = query.groupBy.length > 0 || query.columns.some((c) => c.type === "AGGREGATE" || c.type === "ARITH_AGG_COL");
|
|
35729
|
+
if (hasAgg || query.distinct || query.limit !== null) return { query, probed: false };
|
|
35730
|
+
return { query: { ...query, limit: 2 }, probed: true };
|
|
35731
|
+
}
|
|
35732
|
+
function evalAssertArith(node) {
|
|
35733
|
+
if (node.type === "NUMBER") return node.value;
|
|
35734
|
+
if (node.type === "ARITH") {
|
|
35735
|
+
const left = evalAssertArith(node.left);
|
|
35736
|
+
const right = evalAssertArith(node.right);
|
|
35737
|
+
switch (node.op) {
|
|
35738
|
+
case "+":
|
|
35739
|
+
return left + right;
|
|
35740
|
+
case "-":
|
|
35741
|
+
return left - right;
|
|
35742
|
+
case "*":
|
|
35743
|
+
return left * right;
|
|
35744
|
+
case "/":
|
|
35745
|
+
return left / right;
|
|
35746
|
+
case "%":
|
|
35747
|
+
return left % right;
|
|
35748
|
+
}
|
|
35749
|
+
}
|
|
35750
|
+
throw new Error(`ArgumentError: unsupported operand in ASSERT expression: ${node.type}`);
|
|
35751
|
+
}
|
|
35752
|
+
function compareAssertValues(op, leftStr, rightStr) {
|
|
35753
|
+
const leftNum = Number(leftStr);
|
|
35754
|
+
const rightNum = Number(rightStr);
|
|
35755
|
+
const numeric = !Number.isNaN(leftNum) && !Number.isNaN(rightNum);
|
|
35756
|
+
switch (op) {
|
|
35757
|
+
case "=":
|
|
35758
|
+
return leftStr === rightStr;
|
|
35759
|
+
case "!=":
|
|
35760
|
+
case "<>":
|
|
35761
|
+
return leftStr !== rightStr;
|
|
35762
|
+
case ">":
|
|
35763
|
+
return numeric ? leftNum > rightNum : leftStr > rightStr;
|
|
35764
|
+
case "<":
|
|
35765
|
+
return numeric ? leftNum < rightNum : leftStr < rightStr;
|
|
35766
|
+
case ">=":
|
|
35767
|
+
return numeric ? leftNum >= rightNum : leftStr >= rightStr;
|
|
35768
|
+
case "<=":
|
|
35769
|
+
return numeric ? leftNum <= rightNum : leftStr <= rightStr;
|
|
35770
|
+
}
|
|
35771
|
+
}
|
|
35440
35772
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
35441
35773
|
if (isNoFromSelect(stmt)) {
|
|
35442
35774
|
return executeNoFromSelect(stmt);
|
|
@@ -36881,7 +37213,7 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
36881
37213
|
return [
|
|
36882
37214
|
`CREATE TEMP TABLE ${stmt.name}`,
|
|
36883
37215
|
` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
|
|
36884
|
-
` 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`,
|
|
37216
|
+
` 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`,
|
|
36885
37217
|
...buildPlanForBatchQuery(stmt.query, info).map((l) => ` ${l}`)
|
|
36886
37218
|
];
|
|
36887
37219
|
}
|
|
@@ -36894,8 +37226,33 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
36894
37226
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
36895
37227
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
36896
37228
|
if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
|
|
37229
|
+
if (stmt.type === "ASSERT") {
|
|
37230
|
+
const lines = [
|
|
37231
|
+
`ASSERT ${stmt.text}`,
|
|
37232
|
+
" 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"
|
|
37233
|
+
];
|
|
37234
|
+
const subqueries = [stmt.left, stmt.right, stmt.low, stmt.high].filter(
|
|
37235
|
+
(o) => o !== null && o.type === "SCALAR_SUBQUERY"
|
|
37236
|
+
);
|
|
37237
|
+
subqueries.forEach((sq, i) => {
|
|
37238
|
+
lines.push(subqueries.length > 1 ? ` subquery[${i + 1}]:` : " subquery:");
|
|
37239
|
+
const subInfo = hasTempTableRef(sq.query) ? info : { ...info, tempTablesReferenced: [] };
|
|
37240
|
+
lines.push(...buildPlanForBatchQuery(sq.query, subInfo).map((l) => ` ${l}`));
|
|
37241
|
+
});
|
|
37242
|
+
return lines;
|
|
37243
|
+
}
|
|
36897
37244
|
return buildPlanForBatchQuery(stmt, info);
|
|
36898
37245
|
}
|
|
37246
|
+
function hasTempTableRef(node) {
|
|
37247
|
+
if (Array.isArray(node)) return node.some(hasTempTableRef);
|
|
37248
|
+
if (node !== null && typeof node === "object") {
|
|
37249
|
+
const obj = node;
|
|
37250
|
+
const cte = obj["cteName"];
|
|
37251
|
+
if (typeof cte === "string" && cte.startsWith("#")) return true;
|
|
37252
|
+
return Object.values(obj).some(hasTempTableRef);
|
|
37253
|
+
}
|
|
37254
|
+
return false;
|
|
37255
|
+
}
|
|
36899
37256
|
function buildPlanForBatchQuery(query, info) {
|
|
36900
37257
|
if (info.tempTablesReferenced.length === 0) {
|
|
36901
37258
|
return buildExplainPlan(query);
|
|
@@ -37248,6 +37605,62 @@ function parseSqlStatements(sql) {
|
|
|
37248
37605
|
return new Parser(tokens).parseStatements();
|
|
37249
37606
|
}
|
|
37250
37607
|
|
|
37608
|
+
// src/output/batchEnvelope.ts
|
|
37609
|
+
function toMutationSummary(result) {
|
|
37610
|
+
if (result.type === "INSERT") {
|
|
37611
|
+
return { insertedCount: result.insertedCount, createdIds: result.createdIds };
|
|
37612
|
+
}
|
|
37613
|
+
if (result.type === "UPDATE") return { updatedCount: result.updatedCount };
|
|
37614
|
+
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
37615
|
+
if (result.type === "UPSERT") {
|
|
37616
|
+
return { insertedCount: result.insertedCount, updatedCount: result.updatedCount };
|
|
37617
|
+
}
|
|
37618
|
+
return { reorderedParentCount: result.reorderedParentCount };
|
|
37619
|
+
}
|
|
37620
|
+
function buildBatchEnvelope(batch, options = {}) {
|
|
37621
|
+
const { maxTotalRecords } = options;
|
|
37622
|
+
const results = [];
|
|
37623
|
+
let totalRows = 0;
|
|
37624
|
+
const statements = batch.statements.map((s) => {
|
|
37625
|
+
const entry = {
|
|
37626
|
+
index: s.index,
|
|
37627
|
+
type: s.type,
|
|
37628
|
+
status: s.status
|
|
37629
|
+
};
|
|
37630
|
+
if (s.status === "error" && s.error) entry.error = s.error;
|
|
37631
|
+
if (s.status === "skipped" && s.skippedReason) entry.skippedReason = s.skippedReason;
|
|
37632
|
+
if (s.tempTable !== void 0) entry.tempTable = s.tempTable;
|
|
37633
|
+
if (s.rowCount !== void 0) entry.rowCount = s.rowCount;
|
|
37634
|
+
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
37635
|
+
totalRows += s.result.rowCount;
|
|
37636
|
+
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
37637
|
+
throw new Error(
|
|
37638
|
+
`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`
|
|
37639
|
+
);
|
|
37640
|
+
}
|
|
37641
|
+
entry.resultIndex = results.length;
|
|
37642
|
+
results.push({
|
|
37643
|
+
columns: s.result.columns,
|
|
37644
|
+
rows: s.result.rows,
|
|
37645
|
+
rowCount: s.result.rowCount,
|
|
37646
|
+
warnings: s.result.warnings ?? []
|
|
37647
|
+
});
|
|
37648
|
+
} else if (s.status === "success" && s.result && s.result.type !== "SELECT" && s.result.type !== "ASSERT") {
|
|
37649
|
+
Object.assign(entry, toMutationSummary(s.result));
|
|
37650
|
+
}
|
|
37651
|
+
return entry;
|
|
37652
|
+
});
|
|
37653
|
+
return {
|
|
37654
|
+
ok: batch.ok,
|
|
37655
|
+
batch: true,
|
|
37656
|
+
statementCount: batch.statementCount,
|
|
37657
|
+
statements,
|
|
37658
|
+
results,
|
|
37659
|
+
// バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
|
|
37660
|
+
warnings: []
|
|
37661
|
+
};
|
|
37662
|
+
}
|
|
37663
|
+
|
|
37251
37664
|
// src/node/appProfiles.ts
|
|
37252
37665
|
function parseTokenMap(raw) {
|
|
37253
37666
|
const out = {};
|
|
@@ -37453,6 +37866,21 @@ function envInt(name) {
|
|
|
37453
37866
|
if (!Number.isInteger(n) || n <= 0) return null;
|
|
37454
37867
|
return n;
|
|
37455
37868
|
}
|
|
37869
|
+
function envNonNegativeInt(name) {
|
|
37870
|
+
const v = envString(name);
|
|
37871
|
+
if (v === null) return null;
|
|
37872
|
+
const n = Number(v);
|
|
37873
|
+
if (!Number.isInteger(n) || n < 0) return null;
|
|
37874
|
+
return n;
|
|
37875
|
+
}
|
|
37876
|
+
function resolveRequestGateOptions(base) {
|
|
37877
|
+
return {
|
|
37878
|
+
...base,
|
|
37879
|
+
maxConcurrent: envInt("KSQL_MAX_CONCURRENT") ?? base.maxConcurrent,
|
|
37880
|
+
// KSQL_RETRY=0(リトライ無効)は有効値のため envNonNegativeInt で読む
|
|
37881
|
+
maxRetries: envNonNegativeInt("KSQL_RETRY") ?? base.maxRetries
|
|
37882
|
+
};
|
|
37883
|
+
}
|
|
37456
37884
|
function envOnLimit(name) {
|
|
37457
37885
|
const v = envString(name);
|
|
37458
37886
|
if (v === "error" || v === "truncate") return v;
|
|
@@ -37493,8 +37921,11 @@ var RequestGate = class {
|
|
|
37493
37921
|
this.waiters = [];
|
|
37494
37922
|
this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
|
|
37495
37923
|
this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
|
|
37496
|
-
this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
37497
|
-
this.maxDelayMs =
|
|
37924
|
+
this.baseDelayMs = clampInt(options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS, 1, 6e4);
|
|
37925
|
+
this.maxDelayMs = Math.max(
|
|
37926
|
+
clampInt(options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS, 1, 6e5),
|
|
37927
|
+
this.baseDelayMs
|
|
37928
|
+
);
|
|
37498
37929
|
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
37499
37930
|
this.random = options.random ?? Math.random;
|
|
37500
37931
|
}
|
|
@@ -37505,6 +37936,18 @@ var RequestGate = class {
|
|
|
37505
37936
|
get limit() {
|
|
37506
37937
|
return this.maxConcurrent;
|
|
37507
37938
|
}
|
|
37939
|
+
/** 解決済みの GET リトライ回数(テスト・診断用) */
|
|
37940
|
+
get retries() {
|
|
37941
|
+
return this.maxRetries;
|
|
37942
|
+
}
|
|
37943
|
+
/** 解決済みのバックオフ初期値ミリ秒(テスト・診断用) */
|
|
37944
|
+
get retryBaseDelayMs() {
|
|
37945
|
+
return this.baseDelayMs;
|
|
37946
|
+
}
|
|
37947
|
+
/** 解決済みのバックオフ上限ミリ秒(テスト・診断用) */
|
|
37948
|
+
get retryMaxDelayMs() {
|
|
37949
|
+
return this.maxDelayMs;
|
|
37950
|
+
}
|
|
37508
37951
|
/** GET 系: セマフォ + リトライ付きで実行する */
|
|
37509
37952
|
async runReadOnly(fn) {
|
|
37510
37953
|
let attempt = 0;
|
|
@@ -37561,11 +38004,11 @@ function withRequestGate(client, gate) {
|
|
|
37561
38004
|
};
|
|
37562
38005
|
}
|
|
37563
38006
|
var globalGate = null;
|
|
37564
|
-
function getGlobalRequestGate(
|
|
38007
|
+
function getGlobalRequestGate(options) {
|
|
37565
38008
|
if (globalGate === null) {
|
|
37566
|
-
|
|
37567
|
-
|
|
37568
|
-
|
|
38009
|
+
globalGate = new RequestGate(
|
|
38010
|
+
typeof options === "number" ? { maxConcurrent: options } : options ?? {}
|
|
38011
|
+
);
|
|
37569
38012
|
}
|
|
37570
38013
|
return globalGate;
|
|
37571
38014
|
}
|
|
@@ -37793,6 +38236,7 @@ async function createKsqlRuntime(serverOptions, input) {
|
|
|
37793
38236
|
}
|
|
37794
38237
|
const onLimit2 = input.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile2.query?.onLimit ?? "error";
|
|
37795
38238
|
const timeout2 = input.timeout ?? envInt("KSQL_TIMEOUT") ?? profile2.query?.timeout ?? 3e4;
|
|
38239
|
+
const tempTableMaxRows2 = input.tempTableMaxRows ?? envInt("KSQL_TEMP_TABLE_MAX_ROWS") ?? profile2.query?.tempTableMaxRows;
|
|
37796
38240
|
const appIds = extractAppIds(sql);
|
|
37797
38241
|
const defaultApp = envInt("KSQL_APP") ?? profile2.app ?? null;
|
|
37798
38242
|
if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
|
|
@@ -37918,7 +38362,12 @@ async function createKsqlRuntime(serverOptions, input) {
|
|
|
37918
38362
|
};
|
|
37919
38363
|
const gatedClient = withRequestGate(
|
|
37920
38364
|
routedClient,
|
|
37921
|
-
getGlobalRequestGate(
|
|
38365
|
+
getGlobalRequestGate(resolveRequestGateOptions({
|
|
38366
|
+
maxConcurrent: profile2.query?.maxConcurrent,
|
|
38367
|
+
maxRetries: profile2.query?.retry,
|
|
38368
|
+
baseDelayMs: profile2.query?.retryBaseDelayMs,
|
|
38369
|
+
maxDelayMs: profile2.query?.retryMaxDelayMs
|
|
38370
|
+
}))
|
|
37922
38371
|
);
|
|
37923
38372
|
return {
|
|
37924
38373
|
sql,
|
|
@@ -37928,7 +38377,8 @@ async function createKsqlRuntime(serverOptions, input) {
|
|
|
37928
38377
|
maxRecords: maxRecords2,
|
|
37929
38378
|
fetchParallel: fetchParallel2,
|
|
37930
38379
|
onLimit: onLimit2,
|
|
37931
|
-
timeout: timeout2
|
|
38380
|
+
timeout: timeout2,
|
|
38381
|
+
tempTableMaxRows: tempTableMaxRows2
|
|
37932
38382
|
};
|
|
37933
38383
|
}
|
|
37934
38384
|
|
|
@@ -38175,57 +38625,11 @@ function toSelectPayload(result) {
|
|
|
38175
38625
|
warnings: result.warnings ?? []
|
|
38176
38626
|
};
|
|
38177
38627
|
}
|
|
38178
|
-
function
|
|
38179
|
-
if (result.type === "INSERT") {
|
|
38180
|
-
return { insertedCount: result.insertedCount, createdIds: result.createdIds };
|
|
38181
|
-
}
|
|
38182
|
-
if (result.type === "UPDATE") return { updatedCount: result.updatedCount };
|
|
38183
|
-
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
38184
|
-
if (result.type === "UPSERT") {
|
|
38185
|
-
return { insertedCount: result.insertedCount, updatedCount: result.updatedCount };
|
|
38186
|
-
}
|
|
38187
|
-
return { reorderedParentCount: result.reorderedParentCount };
|
|
38188
|
-
}
|
|
38189
|
-
function toBatchQueryPayload(batch, maxTotalRecords) {
|
|
38190
|
-
const results = [];
|
|
38191
|
-
let totalRows = 0;
|
|
38192
|
-
const statements = batch.statements.map((s) => {
|
|
38193
|
-
const entry = {
|
|
38194
|
-
index: s.index,
|
|
38195
|
-
type: s.type,
|
|
38196
|
-
status: s.status
|
|
38197
|
-
};
|
|
38198
|
-
if (s.status === "error" && s.error) entry.error = s.error;
|
|
38199
|
-
if (s.status === "skipped" && s.skippedReason) entry.skippedReason = s.skippedReason;
|
|
38200
|
-
if (s.tempTable !== void 0) entry.tempTable = s.tempTable;
|
|
38201
|
-
if (s.rowCount !== void 0) entry.rowCount = s.rowCount;
|
|
38202
|
-
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
38203
|
-
totalRows += s.result.rowCount;
|
|
38204
|
-
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
38205
|
-
throw new Error(
|
|
38206
|
-
`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`
|
|
38207
|
-
);
|
|
38208
|
-
}
|
|
38209
|
-
entry.resultIndex = results.length;
|
|
38210
|
-
results.push({
|
|
38211
|
-
columns: s.result.columns,
|
|
38212
|
-
rows: s.result.rows,
|
|
38213
|
-
rowCount: s.result.rowCount,
|
|
38214
|
-
warnings: s.result.warnings ?? []
|
|
38215
|
-
});
|
|
38216
|
-
} else if (s.status === "success" && s.result && s.result.type !== "SELECT") {
|
|
38217
|
-
Object.assign(entry, toMutationSummary(s.result));
|
|
38218
|
-
}
|
|
38219
|
-
return entry;
|
|
38220
|
-
});
|
|
38628
|
+
function toAssertPayload(result) {
|
|
38221
38629
|
return {
|
|
38222
|
-
ok:
|
|
38223
|
-
|
|
38224
|
-
|
|
38225
|
-
statements,
|
|
38226
|
-
results,
|
|
38227
|
-
// バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
|
|
38228
|
-
warnings: []
|
|
38630
|
+
ok: true,
|
|
38631
|
+
type: result.type,
|
|
38632
|
+
condition: result.condition
|
|
38229
38633
|
};
|
|
38230
38634
|
}
|
|
38231
38635
|
function toMutationPayload(result) {
|
|
@@ -38415,7 +38819,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38415
38819
|
maxRecords: input.maxRecords,
|
|
38416
38820
|
fetchParallel: input.fetchParallel,
|
|
38417
38821
|
onLimit: input.onLimit,
|
|
38418
|
-
timeout: input.timeout
|
|
38822
|
+
timeout: input.timeout,
|
|
38823
|
+
tempTableMaxRows: input.tempTableMaxRows
|
|
38419
38824
|
});
|
|
38420
38825
|
const batchResult = await executeBatchSql(runtime2.sql, runtime2.client, {
|
|
38421
38826
|
maxRecords: runtime2.maxRecords,
|
|
@@ -38423,12 +38828,15 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38423
38828
|
onLimitReached: runtime2.onLimit,
|
|
38424
38829
|
cacheContext: runtime2.cacheContext,
|
|
38425
38830
|
continueOnError: input.continueOnError,
|
|
38831
|
+
// 一時テーブル実体化上限(未指定 = エンジン既定 TEMP_TABLE_MAX_ROWS)。
|
|
38832
|
+
// 実体化は onLimit 設定によらず常に error(src/execute.ts の実体化経路で固定)
|
|
38833
|
+
tempTableMaxRows: runtime2.tempTableMaxRows,
|
|
38426
38834
|
// バッチでは timeout を合計タイムアウトとして扱う(仕様 §5.7)。
|
|
38427
38835
|
// runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
|
|
38428
38836
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
38429
38837
|
timeoutMs: runtime2.timeout
|
|
38430
38838
|
});
|
|
38431
|
-
return
|
|
38839
|
+
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
38432
38840
|
}
|
|
38433
38841
|
if (!validation.isReadOnly) {
|
|
38434
38842
|
throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
|
|
@@ -38441,6 +38849,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38441
38849
|
onLimitReached: input.onLimit ?? DEFAULT_ON_LIMIT,
|
|
38442
38850
|
cacheContext: validation.cacheContext
|
|
38443
38851
|
});
|
|
38852
|
+
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
38444
38853
|
if (result2.type !== "SELECT") {
|
|
38445
38854
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result2.type}.`);
|
|
38446
38855
|
}
|
|
@@ -38460,6 +38869,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38460
38869
|
onLimitReached: runtime.onLimit,
|
|
38461
38870
|
cacheContext: runtime.cacheContext
|
|
38462
38871
|
});
|
|
38872
|
+
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
38463
38873
|
if (result.type !== "SELECT") {
|
|
38464
38874
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result.type}.`);
|
|
38465
38875
|
}
|
|
@@ -38498,7 +38908,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38498
38908
|
maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
|
|
38499
38909
|
fetchParallel: input.fetchParallel,
|
|
38500
38910
|
onLimit: DEFAULT_ON_LIMIT,
|
|
38501
|
-
timeout: input.timeout
|
|
38911
|
+
timeout: input.timeout,
|
|
38912
|
+
tempTableMaxRows: input.tempTableMaxRows
|
|
38502
38913
|
});
|
|
38503
38914
|
let totalAffected = staticInsertTotal;
|
|
38504
38915
|
const batchResult = await executeBatchSql(runtime.sql, runtime.client, {
|
|
@@ -38506,6 +38917,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38506
38917
|
fetchParallel: runtime.fetchParallel,
|
|
38507
38918
|
onLimitReached: runtime.onLimit,
|
|
38508
38919
|
cacheContext: runtime.cacheContext,
|
|
38920
|
+
// 一時テーブル実体化上限(未指定 = エンジン既定 TEMP_TABLE_MAX_ROWS)
|
|
38921
|
+
tempTableMaxRows: runtime.tempTableMaxRows,
|
|
38509
38922
|
// 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
|
|
38510
38923
|
timeoutMs: runtime.timeout,
|
|
38511
38924
|
confirm: async (count, operation) => {
|
|
@@ -38521,7 +38934,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38521
38934
|
return true;
|
|
38522
38935
|
}
|
|
38523
38936
|
});
|
|
38524
|
-
const payload =
|
|
38937
|
+
const payload = buildBatchEnvelope(batchResult);
|
|
38525
38938
|
if (selectBasedDml) {
|
|
38526
38939
|
for (const entry of payload.statements) {
|
|
38527
38940
|
if (entry.type !== "INSERT_SELECT" && entry.type !== "UPSERT_SELECT") continue;
|
|
@@ -38531,7 +38944,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38531
38944
|
entry.error = { ...error51, message: `${error51.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}` };
|
|
38532
38945
|
}
|
|
38533
38946
|
}
|
|
38534
|
-
return payload;
|
|
38947
|
+
return { ...payload };
|
|
38535
38948
|
}
|
|
38536
38949
|
async function mutate(input) {
|
|
38537
38950
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
@@ -38576,7 +38989,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38576
38989
|
} catch (err) {
|
|
38577
38990
|
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
38578
38991
|
}
|
|
38579
|
-
if (result.type === "SELECT") {
|
|
38992
|
+
if (result.type === "SELECT" || result.type === "ASSERT") {
|
|
38580
38993
|
throw new Error(`ArgumentError: ksql_mutate returned unexpected result type ${result.type}.`);
|
|
38581
38994
|
}
|
|
38582
38995
|
return toMutationPayload(result);
|
|
@@ -38736,6 +39149,7 @@ var profile = external_exports.string().min(1).describe("kintone connection prof
|
|
|
38736
39149
|
var maxRecords = external_exports.number().int().positive().describe("Maximum records fetched per SELECT (default 500).").optional();
|
|
38737
39150
|
var fetchParallel = external_exports.number().int().min(1).max(10).describe("Number of parallel kintone record-fetch requests (1-10).").optional();
|
|
38738
39151
|
var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error').").optional();
|
|
39152
|
+
var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
|
|
38739
39153
|
var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
|
|
38740
39154
|
var savedQueryName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).describe("Saved query name (alphanumeric, '_' and '-', up to 64 chars).");
|
|
38741
39155
|
var savedQueryTags = external_exports.array(external_exports.string().min(1)).describe("Tags for organizing saved queries.").optional();
|
|
@@ -38753,6 +39167,7 @@ var queryInputSchema = external_exports.object({
|
|
|
38753
39167
|
maxRecords,
|
|
38754
39168
|
fetchParallel,
|
|
38755
39169
|
onLimit,
|
|
39170
|
+
tempTableMaxRows,
|
|
38756
39171
|
timeout,
|
|
38757
39172
|
continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
|
|
38758
39173
|
maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional()
|
|
@@ -38762,8 +39177,9 @@ var mutateInputSchema = external_exports.object({
|
|
|
38762
39177
|
profile,
|
|
38763
39178
|
allowDml: external_exports.literal(true).describe("Must be true to acknowledge that this call writes to kintone."),
|
|
38764
39179
|
confirmText: external_exports.literal("yes").describe('Must be the literal string "yes" to confirm execution.'),
|
|
38765
|
-
dmlMaxRows: external_exports.number().int().positive().describe("Per-statement cap on affected rows. The call fails before writing if any statement would exceed it; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500; temp tables hold at most 10000 rows), so choose it by intended write count only."),
|
|
39180
|
+
dmlMaxRows: external_exports.number().int().positive().describe("Per-statement cap on affected rows. The call fails before writing if any statement would exceed it; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500; temp tables hold at most 10000 rows by default, adjustable via tempTableMaxRows), so choose it by intended write count only."),
|
|
38766
39181
|
fetchParallel,
|
|
39182
|
+
tempTableMaxRows,
|
|
38767
39183
|
timeout,
|
|
38768
39184
|
dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional()
|
|
38769
39185
|
});
|
|
@@ -38805,7 +39221,7 @@ var runSavedQueryInputSchema = external_exports.object({
|
|
|
38805
39221
|
timeout,
|
|
38806
39222
|
allowDml: external_exports.literal(true).describe("Required for DML saved queries: must be true to acknowledge writes.").optional(),
|
|
38807
39223
|
confirmText: external_exports.literal("yes").describe('Required for DML saved queries: must be the literal string "yes".').optional(),
|
|
38808
|
-
dmlMaxRows: external_exports.number().int().positive().describe("Required for DML saved queries: per-statement cap on affected rows; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500
|
|
39224
|
+
dmlMaxRows: external_exports.number().int().positive().describe("Required for DML saved queries: per-statement cap on affected rows; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500). Saved queries are single-statement, so temp tables do not apply here. Note: this tool's maxRecords / onLimit inputs apply to read-only saved queries only.").optional()
|
|
38809
39225
|
});
|
|
38810
39226
|
var validateInputShape = validateInputSchema.shape;
|
|
38811
39227
|
var explainInputShape = explainInputSchema.shape;
|
|
@@ -38854,7 +39270,7 @@ Options:
|
|
|
38854
39270
|
-h, --help Show help
|
|
38855
39271
|
`);
|
|
38856
39272
|
}
|
|
38857
|
-
var SERVER_VERSION = true ? "1.
|
|
39273
|
+
var SERVER_VERSION = true ? "1.11.0" : "0.0.0-dev";
|
|
38858
39274
|
function createServer(args) {
|
|
38859
39275
|
const server = new McpServer({
|
|
38860
39276
|
name: "ksql-mcp",
|
|
@@ -38876,12 +39292,12 @@ function createServer(args) {
|
|
|
38876
39292
|
}, tools.explainTool);
|
|
38877
39293
|
server.registerTool("ksql_query", {
|
|
38878
39294
|
title: "Run read-only kSQL",
|
|
38879
|
-
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE. Supports multi-statement batches with temp tables (CREATE TEMP TABLE #t AS SELECT ...; SELECT ... FROM #t;). DML is rejected.",
|
|
39295
|
+
description: "Execute read-only kSQL: SELECT, WITH, UNION, EXPLAIN, SHOW APPS, DESCRIBE, ASSERT. Supports multi-statement batches with temp tables (CREATE TEMP TABLE #t AS SELECT ...; SELECT ... FROM #t;). ASSERT <expr> <op> <expr> (or BETWEEN) is a runtime gate: on failure it raises AssertError and always stops the batch. DML is rejected.",
|
|
38880
39296
|
inputSchema: queryInputShape
|
|
38881
39297
|
}, tools.queryTool);
|
|
38882
39298
|
server.registerTool("ksql_mutate", {
|
|
38883
39299
|
title: "Run mutating kSQL",
|
|
38884
|
-
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: the source SELECT reads up to the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows.",
|
|
39300
|
+
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: the source SELECT reads up to the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows by default (adjustable via tempTableMaxRows).",
|
|
38885
39301
|
inputSchema: mutateInputShape
|
|
38886
39302
|
}, tools.mutateTool);
|
|
38887
39303
|
server.registerTool("ksql_describe_app", {
|