@rex0220/kintone-sql-tools 1.4.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist-cli/ksql.js +596 -53
- package/dist-mcp/ksql-mcp.js +548 -102
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +3 -3
package/dist-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;
|
|
@@ -35271,9 +35496,9 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35271
35496
|
}
|
|
35272
35497
|
for (const s of analysis.statements) {
|
|
35273
35498
|
if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
|
|
35274
|
-
if (s.statementType === "INSERT_SELECT"
|
|
35499
|
+
if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
|
|
35275
35500
|
throw new BatchAnalysisError(
|
|
35276
|
-
|
|
35501
|
+
`ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
|
|
35277
35502
|
s.index
|
|
35278
35503
|
);
|
|
35279
35504
|
}
|
|
@@ -35309,8 +35534,18 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35309
35534
|
}
|
|
35310
35535
|
try {
|
|
35311
35536
|
const remaining = deadline !== null ? deadline - Date.now() : null;
|
|
35537
|
+
const userConfirm = options.confirm;
|
|
35538
|
+
const stmtOptions = userConfirm ? {
|
|
35539
|
+
...options,
|
|
35540
|
+
confirm: (count, operation) => userConfirm(count, operation, {
|
|
35541
|
+
statementIndex: i,
|
|
35542
|
+
statementCount: statements.length,
|
|
35543
|
+
statementType: info.statementType,
|
|
35544
|
+
targetAppId: info.targetAppId
|
|
35545
|
+
})
|
|
35546
|
+
} : options;
|
|
35312
35547
|
const outcome = await runWithDeadline(
|
|
35313
|
-
executeBatchStatement(statements[i], info, countedClient,
|
|
35548
|
+
executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
|
|
35314
35549
|
remaining
|
|
35315
35550
|
);
|
|
35316
35551
|
results.push({ ...base, status: "success", ...outcome });
|
|
@@ -35319,6 +35554,8 @@ async function executeBatch(sql, client, options = {}) {
|
|
|
35319
35554
|
failed.add(i);
|
|
35320
35555
|
if (e instanceof BatchTimeoutError) {
|
|
35321
35556
|
aborted2 = "timeout";
|
|
35557
|
+
} else if (e instanceof AssertError) {
|
|
35558
|
+
aborted2 = "assertion";
|
|
35322
35559
|
} else if (!options.continueOnError) {
|
|
35323
35560
|
aborted2 = "fail-fast";
|
|
35324
35561
|
}
|
|
@@ -35351,6 +35588,10 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
35351
35588
|
if (stmt.type === "EXPLAIN") {
|
|
35352
35589
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
35353
35590
|
}
|
|
35591
|
+
if (stmt.type === "ASSERT") {
|
|
35592
|
+
await executeAssert(stmt, client, options, cacheContext, tempTables);
|
|
35593
|
+
return {};
|
|
35594
|
+
}
|
|
35354
35595
|
if (info.tempTablesReferenced.length > 0) {
|
|
35355
35596
|
if (stmt.type === "SELECT" || stmt.type === "UNION") {
|
|
35356
35597
|
return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
|
|
@@ -35361,6 +35602,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
|
|
|
35361
35602
|
if (stmt.type === "INSERT_SELECT") {
|
|
35362
35603
|
return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
|
|
35363
35604
|
}
|
|
35605
|
+
if (stmt.type === "UPSERT_SELECT") {
|
|
35606
|
+
return { result: await executeUpsertSelect(stmt, client, options, cacheContext, tempTables) };
|
|
35607
|
+
}
|
|
35364
35608
|
throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
|
|
35365
35609
|
}
|
|
35366
35610
|
return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
|
|
@@ -35424,6 +35668,107 @@ function parseSqlBatch(sql) {
|
|
|
35424
35668
|
const tokens = new Lexer(sql).tokenize();
|
|
35425
35669
|
return new Parser(tokens).parseStatements();
|
|
35426
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
|
+
}
|
|
35427
35772
|
async function executeSelect(stmt, client, options, cacheContext, cteCache) {
|
|
35428
35773
|
if (isNoFromSelect(stmt)) {
|
|
35429
35774
|
return executeNoFromSelect(stmt);
|
|
@@ -36682,8 +37027,8 @@ function evalOrderKeyForRow(key, row) {
|
|
|
36682
37027
|
return evalStringFunc(key.expr, row);
|
|
36683
37028
|
}
|
|
36684
37029
|
}
|
|
36685
|
-
async function executeUpsertSelect(stmt, client, options, cacheContext) {
|
|
36686
|
-
const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
|
|
37030
|
+
async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
|
|
37031
|
+
const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
|
|
36687
37032
|
const { rows, columns } = selectResult;
|
|
36688
37033
|
if (columns.length !== stmt.fields.length) {
|
|
36689
37034
|
throw new Error(
|
|
@@ -36881,8 +37226,33 @@ function buildBatchStatementPlan(stmt, info) {
|
|
|
36881
37226
|
if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
|
|
36882
37227
|
if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
|
|
36883
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
|
+
}
|
|
36884
37244
|
return buildPlanForBatchQuery(stmt, info);
|
|
36885
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
|
+
}
|
|
36886
37256
|
function buildPlanForBatchQuery(query, info) {
|
|
36887
37257
|
if (info.tempTablesReferenced.length === 0) {
|
|
36888
37258
|
return buildExplainPlan(query);
|
|
@@ -36892,12 +37262,18 @@ function buildPlanForBatchQuery(query, info) {
|
|
|
36892
37262
|
lines.push(
|
|
36893
37263
|
`INSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u5B9F\u884C\u6642\u306B\u4EF6\u6570\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
|
|
36894
37264
|
);
|
|
37265
|
+
} else if (query.type === "UPSERT_SELECT") {
|
|
37266
|
+
lines.push(
|
|
37267
|
+
`UPSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u7167\u5408\u5F8C\u306B insert + update \u5408\u8A08\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
|
|
37268
|
+
);
|
|
36895
37269
|
}
|
|
36896
37270
|
lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
|
|
36897
37271
|
lines.push(
|
|
36898
37272
|
` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
|
|
36899
37273
|
);
|
|
36900
|
-
const apps = info.appIds.filter(
|
|
37274
|
+
const apps = info.appIds.filter(
|
|
37275
|
+
(a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
|
|
37276
|
+
);
|
|
36901
37277
|
if (apps.length > 0) {
|
|
36902
37278
|
lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
|
|
36903
37279
|
}
|
|
@@ -37229,6 +37605,62 @@ function parseSqlStatements(sql) {
|
|
|
37229
37605
|
return new Parser(tokens).parseStatements();
|
|
37230
37606
|
}
|
|
37231
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
|
+
|
|
37232
37664
|
// src/node/appProfiles.ts
|
|
37233
37665
|
function parseTokenMap(raw) {
|
|
37234
37666
|
const out = {};
|
|
@@ -37434,6 +37866,21 @@ function envInt(name) {
|
|
|
37434
37866
|
if (!Number.isInteger(n) || n <= 0) return null;
|
|
37435
37867
|
return n;
|
|
37436
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
|
+
}
|
|
37437
37884
|
function envOnLimit(name) {
|
|
37438
37885
|
const v = envString(name);
|
|
37439
37886
|
if (v === "error" || v === "truncate") return v;
|
|
@@ -37474,8 +37921,11 @@ var RequestGate = class {
|
|
|
37474
37921
|
this.waiters = [];
|
|
37475
37922
|
this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
|
|
37476
37923
|
this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
|
|
37477
|
-
this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
37478
|
-
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
|
+
);
|
|
37479
37929
|
this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
37480
37930
|
this.random = options.random ?? Math.random;
|
|
37481
37931
|
}
|
|
@@ -37486,6 +37936,18 @@ var RequestGate = class {
|
|
|
37486
37936
|
get limit() {
|
|
37487
37937
|
return this.maxConcurrent;
|
|
37488
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
|
+
}
|
|
37489
37951
|
/** GET 系: セマフォ + リトライ付きで実行する */
|
|
37490
37952
|
async runReadOnly(fn) {
|
|
37491
37953
|
let attempt = 0;
|
|
@@ -37542,11 +38004,11 @@ function withRequestGate(client, gate) {
|
|
|
37542
38004
|
};
|
|
37543
38005
|
}
|
|
37544
38006
|
var globalGate = null;
|
|
37545
|
-
function getGlobalRequestGate(
|
|
38007
|
+
function getGlobalRequestGate(options) {
|
|
37546
38008
|
if (globalGate === null) {
|
|
37547
|
-
|
|
37548
|
-
|
|
37549
|
-
|
|
38009
|
+
globalGate = new RequestGate(
|
|
38010
|
+
typeof options === "number" ? { maxConcurrent: options } : options ?? {}
|
|
38011
|
+
);
|
|
37550
38012
|
}
|
|
37551
38013
|
return globalGate;
|
|
37552
38014
|
}
|
|
@@ -37899,7 +38361,12 @@ async function createKsqlRuntime(serverOptions, input) {
|
|
|
37899
38361
|
};
|
|
37900
38362
|
const gatedClient = withRequestGate(
|
|
37901
38363
|
routedClient,
|
|
37902
|
-
getGlobalRequestGate(
|
|
38364
|
+
getGlobalRequestGate(resolveRequestGateOptions({
|
|
38365
|
+
maxConcurrent: profile2.query?.maxConcurrent,
|
|
38366
|
+
maxRetries: profile2.query?.retry,
|
|
38367
|
+
baseDelayMs: profile2.query?.retryBaseDelayMs,
|
|
38368
|
+
maxDelayMs: profile2.query?.retryMaxDelayMs
|
|
38369
|
+
}))
|
|
37903
38370
|
);
|
|
37904
38371
|
return {
|
|
37905
38372
|
sql,
|
|
@@ -38156,57 +38623,11 @@ function toSelectPayload(result) {
|
|
|
38156
38623
|
warnings: result.warnings ?? []
|
|
38157
38624
|
};
|
|
38158
38625
|
}
|
|
38159
|
-
function
|
|
38160
|
-
if (result.type === "INSERT") {
|
|
38161
|
-
return { insertedCount: result.insertedCount, createdIds: result.createdIds };
|
|
38162
|
-
}
|
|
38163
|
-
if (result.type === "UPDATE") return { updatedCount: result.updatedCount };
|
|
38164
|
-
if (result.type === "DELETE") return { deletedCount: result.deletedCount };
|
|
38165
|
-
if (result.type === "UPSERT") {
|
|
38166
|
-
return { insertedCount: result.insertedCount, updatedCount: result.updatedCount };
|
|
38167
|
-
}
|
|
38168
|
-
return { reorderedParentCount: result.reorderedParentCount };
|
|
38169
|
-
}
|
|
38170
|
-
function toBatchQueryPayload(batch, maxTotalRecords) {
|
|
38171
|
-
const results = [];
|
|
38172
|
-
let totalRows = 0;
|
|
38173
|
-
const statements = batch.statements.map((s) => {
|
|
38174
|
-
const entry = {
|
|
38175
|
-
index: s.index,
|
|
38176
|
-
type: s.type,
|
|
38177
|
-
status: s.status
|
|
38178
|
-
};
|
|
38179
|
-
if (s.status === "error" && s.error) entry.error = s.error;
|
|
38180
|
-
if (s.status === "skipped" && s.skippedReason) entry.skippedReason = s.skippedReason;
|
|
38181
|
-
if (s.tempTable !== void 0) entry.tempTable = s.tempTable;
|
|
38182
|
-
if (s.rowCount !== void 0) entry.rowCount = s.rowCount;
|
|
38183
|
-
if (s.status === "success" && s.result?.type === "SELECT") {
|
|
38184
|
-
totalRows += s.result.rowCount;
|
|
38185
|
-
if (maxTotalRecords !== void 0 && totalRows > maxTotalRecords) {
|
|
38186
|
-
throw new Error(
|
|
38187
|
-
`ArgumentError: batch total rows (${totalRows}) exceed maxTotalRecords (${maxTotalRecords}).`
|
|
38188
|
-
);
|
|
38189
|
-
}
|
|
38190
|
-
entry.resultIndex = results.length;
|
|
38191
|
-
results.push({
|
|
38192
|
-
columns: s.result.columns,
|
|
38193
|
-
rows: s.result.rows,
|
|
38194
|
-
rowCount: s.result.rowCount,
|
|
38195
|
-
warnings: s.result.warnings ?? []
|
|
38196
|
-
});
|
|
38197
|
-
} else if (s.status === "success" && s.result && s.result.type !== "SELECT") {
|
|
38198
|
-
Object.assign(entry, toMutationSummary(s.result));
|
|
38199
|
-
}
|
|
38200
|
-
return entry;
|
|
38201
|
-
});
|
|
38626
|
+
function toAssertPayload(result) {
|
|
38202
38627
|
return {
|
|
38203
|
-
ok:
|
|
38204
|
-
|
|
38205
|
-
|
|
38206
|
-
statements,
|
|
38207
|
-
results,
|
|
38208
|
-
// バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
|
|
38209
|
-
warnings: []
|
|
38628
|
+
ok: true,
|
|
38629
|
+
type: result.type,
|
|
38630
|
+
condition: result.condition
|
|
38210
38631
|
};
|
|
38211
38632
|
}
|
|
38212
38633
|
function toMutationPayload(result) {
|
|
@@ -38271,6 +38692,24 @@ function requireDmlApproval(input, toolName, suffix = "") {
|
|
|
38271
38692
|
}
|
|
38272
38693
|
return Number(input.dmlMaxRows);
|
|
38273
38694
|
}
|
|
38695
|
+
function containsSelectBasedDml(statements) {
|
|
38696
|
+
return statements.some(
|
|
38697
|
+
(s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT"
|
|
38698
|
+
);
|
|
38699
|
+
}
|
|
38700
|
+
function resolveMutateRuntimeMaxRecords(statements, dmlMaxRows) {
|
|
38701
|
+
return containsSelectBasedDml(statements) ? void 0 : dmlMaxRows + 1;
|
|
38702
|
+
}
|
|
38703
|
+
var READ_LIMIT_MESSAGE_FRAGMENT = "\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650";
|
|
38704
|
+
var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML \u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
|
|
38705
|
+
function appendSelectBasedDmlReadLimitHint(err) {
|
|
38706
|
+
if (err instanceof Error && err.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) {
|
|
38707
|
+
const hinted = new Error(`${err.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}`);
|
|
38708
|
+
hinted.name = err.name;
|
|
38709
|
+
return hinted;
|
|
38710
|
+
}
|
|
38711
|
+
return err;
|
|
38712
|
+
}
|
|
38274
38713
|
function toToolResult(payload, isError = false) {
|
|
38275
38714
|
return {
|
|
38276
38715
|
content: [
|
|
@@ -38391,7 +38830,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38391
38830
|
// HTTP クライアント側の per-request タイムアウトと同値になる
|
|
38392
38831
|
timeoutMs: runtime2.timeout
|
|
38393
38832
|
});
|
|
38394
|
-
return
|
|
38833
|
+
return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
|
|
38395
38834
|
}
|
|
38396
38835
|
if (!validation.isReadOnly) {
|
|
38397
38836
|
throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
|
|
@@ -38404,6 +38843,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38404
38843
|
onLimitReached: input.onLimit ?? DEFAULT_ON_LIMIT,
|
|
38405
38844
|
cacheContext: validation.cacheContext
|
|
38406
38845
|
});
|
|
38846
|
+
if (result2.type === "ASSERT") return toAssertPayload(result2);
|
|
38407
38847
|
if (result2.type !== "SELECT") {
|
|
38408
38848
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result2.type}.`);
|
|
38409
38849
|
}
|
|
@@ -38423,6 +38863,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38423
38863
|
onLimitReached: runtime.onLimit,
|
|
38424
38864
|
cacheContext: runtime.cacheContext
|
|
38425
38865
|
});
|
|
38866
|
+
if (result.type === "ASSERT") return toAssertPayload(result);
|
|
38426
38867
|
if (result.type !== "SELECT") {
|
|
38427
38868
|
throw new Error(`ArgumentError: read-only query returned unexpected result type ${result.type}.`);
|
|
38428
38869
|
}
|
|
@@ -38436,14 +38877,6 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38436
38877
|
for (const s of validation.statements) {
|
|
38437
38878
|
if (!s.isDml) continue;
|
|
38438
38879
|
const at = ` (statement ${s.index})`;
|
|
38439
|
-
if (s.statementType === "INSERT_SELECT" && !s.tempOnlySource) {
|
|
38440
|
-
throw new Error(
|
|
38441
|
-
`ArgumentError: INSERT_SELECT in a batch must select from temp tables only.${at}`
|
|
38442
|
-
);
|
|
38443
|
-
}
|
|
38444
|
-
if (s.statementType === "UPSERT_SELECT") {
|
|
38445
|
-
throw new Error(`ArgumentError: ${s.statementType} is not supported by ksql_mutate yet.${at}`);
|
|
38446
|
-
}
|
|
38447
38880
|
if ((s.statementType === "UPDATE" || s.statementType === "DELETE") && !s.hasWhere) {
|
|
38448
38881
|
throw new Error(`ArgumentError: ${s.statementType} without WHERE is blocked by ksql_mutate.${at}`);
|
|
38449
38882
|
}
|
|
@@ -38460,10 +38893,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38460
38893
|
`ArgumentError: batch INSERT rows (${staticInsertTotal}) exceed dmlTotalMaxRows (${dmlTotalMaxRows}).`
|
|
38461
38894
|
);
|
|
38462
38895
|
}
|
|
38896
|
+
const selectBasedDml = containsSelectBasedDml(validation.statements);
|
|
38463
38897
|
const runtime = await createRuntime(serverOptions, {
|
|
38464
38898
|
sql: input.sql,
|
|
38465
38899
|
profile: input.profile,
|
|
38466
|
-
|
|
38900
|
+
// SELECT-based DML を含む場合は dmlMaxRows で読み取りを絞らない(案A。
|
|
38901
|
+
// resolveMutateRuntimeMaxRecords の doc コメント参照)
|
|
38902
|
+
maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
|
|
38467
38903
|
fetchParallel: input.fetchParallel,
|
|
38468
38904
|
onLimit: DEFAULT_ON_LIMIT,
|
|
38469
38905
|
timeout: input.timeout
|
|
@@ -38489,7 +38925,17 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38489
38925
|
return true;
|
|
38490
38926
|
}
|
|
38491
38927
|
});
|
|
38492
|
-
|
|
38928
|
+
const payload = buildBatchEnvelope(batchResult);
|
|
38929
|
+
if (selectBasedDml) {
|
|
38930
|
+
for (const entry of payload.statements) {
|
|
38931
|
+
if (entry.type !== "INSERT_SELECT" && entry.type !== "UPSERT_SELECT") continue;
|
|
38932
|
+
const error51 = entry.error;
|
|
38933
|
+
if (typeof error51?.message !== "string") continue;
|
|
38934
|
+
if (!error51.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) continue;
|
|
38935
|
+
entry.error = { ...error51, message: `${error51.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}` };
|
|
38936
|
+
}
|
|
38937
|
+
}
|
|
38938
|
+
return { ...payload };
|
|
38493
38939
|
}
|
|
38494
38940
|
async function mutate(input) {
|
|
38495
38941
|
const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
|
|
@@ -38500,41 +38946,41 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
|
|
|
38500
38946
|
if (!validation.isDml) {
|
|
38501
38947
|
throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_mutate. Use ksql_query.`);
|
|
38502
38948
|
}
|
|
38503
|
-
if (validation.statementType === "INSERT_SELECT") {
|
|
38504
|
-
throw new Error(
|
|
38505
|
-
"ArgumentError: INSERT_SELECT is not supported by ksql_mutate as a single statement. Wrap it in a batch: CREATE TEMP TABLE #t AS SELECT ...; INSERT INTO APPx (...) SELECT ... FROM #t;"
|
|
38506
|
-
);
|
|
38507
|
-
}
|
|
38508
|
-
if (validation.statementType === "UPSERT_SELECT") {
|
|
38509
|
-
throw new Error(`ArgumentError: ${validation.statementType} is not supported by ksql_mutate yet.`);
|
|
38510
|
-
}
|
|
38511
38949
|
if ((validation.statementType === "UPDATE" || validation.statementType === "DELETE") && !validation.hasWhere) {
|
|
38512
38950
|
throw new Error(`ArgumentError: ${validation.statementType} without WHERE is blocked by ksql_mutate.`);
|
|
38513
38951
|
}
|
|
38514
38952
|
if (validation.insertValuesCount !== null && validation.insertValuesCount > dmlMaxRows) {
|
|
38515
38953
|
throw new Error(`ArgumentError: INSERT rows (${validation.insertValuesCount}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
38516
38954
|
}
|
|
38955
|
+
const selectBasedDml = containsSelectBasedDml(validation.statements);
|
|
38517
38956
|
const runtime = await createRuntime(serverOptions, {
|
|
38518
38957
|
sql: input.sql,
|
|
38519
38958
|
profile: input.profile,
|
|
38520
|
-
|
|
38959
|
+
// SELECT-based DML は dmlMaxRows で読み取りを絞らない(案A。
|
|
38960
|
+
// resolveMutateRuntimeMaxRecords の doc コメント参照)
|
|
38961
|
+
maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
|
|
38521
38962
|
fetchParallel: input.fetchParallel,
|
|
38522
38963
|
onLimit: DEFAULT_ON_LIMIT,
|
|
38523
38964
|
timeout: input.timeout
|
|
38524
38965
|
});
|
|
38525
|
-
|
|
38526
|
-
|
|
38527
|
-
|
|
38528
|
-
|
|
38529
|
-
|
|
38530
|
-
|
|
38531
|
-
|
|
38532
|
-
|
|
38966
|
+
let result;
|
|
38967
|
+
try {
|
|
38968
|
+
result = await executeSql(runtime.sql, runtime.client, {
|
|
38969
|
+
maxRecords: runtime.maxRecords,
|
|
38970
|
+
fetchParallel: runtime.fetchParallel,
|
|
38971
|
+
onLimitReached: runtime.onLimit,
|
|
38972
|
+
cacheContext: runtime.cacheContext,
|
|
38973
|
+
confirm: async (count, operation) => {
|
|
38974
|
+
if (count > dmlMaxRows) {
|
|
38975
|
+
throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
|
|
38976
|
+
}
|
|
38977
|
+
return true;
|
|
38533
38978
|
}
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
|
|
38537
|
-
|
|
38979
|
+
});
|
|
38980
|
+
} catch (err) {
|
|
38981
|
+
throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
|
|
38982
|
+
}
|
|
38983
|
+
if (result.type === "SELECT" || result.type === "ASSERT") {
|
|
38538
38984
|
throw new Error(`ArgumentError: ksql_mutate returned unexpected result type ${result.type}.`);
|
|
38539
38985
|
}
|
|
38540
38986
|
return toMutationPayload(result);
|
|
@@ -38720,7 +39166,7 @@ var mutateInputSchema = external_exports.object({
|
|
|
38720
39166
|
profile,
|
|
38721
39167
|
allowDml: external_exports.literal(true).describe("Must be true to acknowledge that this call writes to kintone."),
|
|
38722
39168
|
confirmText: external_exports.literal("yes").describe('Must be the literal string "yes" to confirm execution.'),
|
|
38723
|
-
dmlMaxRows: external_exports.number().int().positive().describe("Per-statement cap on affected rows. The call fails before writing if any statement would exceed it."),
|
|
39169
|
+
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."),
|
|
38724
39170
|
fetchParallel,
|
|
38725
39171
|
timeout,
|
|
38726
39172
|
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()
|
|
@@ -38763,7 +39209,7 @@ var runSavedQueryInputSchema = external_exports.object({
|
|
|
38763
39209
|
timeout,
|
|
38764
39210
|
allowDml: external_exports.literal(true).describe("Required for DML saved queries: must be true to acknowledge writes.").optional(),
|
|
38765
39211
|
confirmText: external_exports.literal("yes").describe('Required for DML saved queries: must be the literal string "yes".').optional(),
|
|
38766
|
-
dmlMaxRows: external_exports.number().int().positive().describe("Required for DML saved queries: per-statement cap on affected rows.").optional()
|
|
39212
|
+
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; temp tables hold at most 10000 rows). Note: this tool's maxRecords / onLimit inputs apply to read-only saved queries only.").optional()
|
|
38767
39213
|
});
|
|
38768
39214
|
var validateInputShape = validateInputSchema.shape;
|
|
38769
39215
|
var explainInputShape = explainInputSchema.shape;
|
|
@@ -38812,7 +39258,7 @@ Options:
|
|
|
38812
39258
|
-h, --help Show help
|
|
38813
39259
|
`);
|
|
38814
39260
|
}
|
|
38815
|
-
var SERVER_VERSION = true ? "1.
|
|
39261
|
+
var SERVER_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
38816
39262
|
function createServer(args) {
|
|
38817
39263
|
const server = new McpServer({
|
|
38818
39264
|
name: "ksql-mcp",
|
|
@@ -38834,12 +39280,12 @@ function createServer(args) {
|
|
|
38834
39280
|
}, tools.explainTool);
|
|
38835
39281
|
server.registerTool("ksql_query", {
|
|
38836
39282
|
title: "Run read-only kSQL",
|
|
38837
|
-
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.",
|
|
39283
|
+
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.",
|
|
38838
39284
|
inputSchema: queryInputShape
|
|
38839
39285
|
}, tools.queryTool);
|
|
38840
39286
|
server.registerTool("ksql_mutate", {
|
|
38841
39287
|
title: "Run mutating kSQL",
|
|
38842
|
-
description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT INTO app ... SELECT
|
|
39288
|
+
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.",
|
|
38843
39289
|
inputSchema: mutateInputShape
|
|
38844
39290
|
}, tools.mutateTool);
|
|
38845
39291
|
server.registerTool("ksql_describe_app", {
|