@rex0220/kintone-sql-tools 1.3.0 → 1.4.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.
@@ -31052,11 +31052,12 @@ var KEYWORDS = /* @__PURE__ */ new Map([
31052
31052
 
31053
31053
  // src/lexer/lexer.ts
31054
31054
  var LexError = class extends Error {
31055
- constructor(message, pos, input) {
31055
+ constructor(message, pos, input, unterminated = false) {
31056
31056
  const around = input.slice(Math.max(0, pos - 10), pos + 10);
31057
31057
  super(`${message}\uFF08\u4F4D\u7F6E ${pos}\u3001\u524D\u5F8C: \u300C${around}\u300D\uFF09`);
31058
31058
  this.pos = pos;
31059
31059
  this.input = input;
31060
+ this.unterminated = unterminated;
31060
31061
  this.name = "LexError";
31061
31062
  }
31062
31063
  };
@@ -31093,6 +31094,7 @@ var Lexer = class {
31093
31094
  const opTok = this.tryReadOperator(start);
31094
31095
  if (opTok) return opTok;
31095
31096
  if (isIdentStart(ch)) return this.readIdentOrKeyword(start);
31097
+ if (ch === "#") return this.readHashIdent(start);
31096
31098
  throw new LexError(
31097
31099
  `\u4E88\u671F\u3057\u306A\u3044\u6587\u5B57 \u300C${ch}\u300D \u3067\u3059`,
31098
31100
  this.pos,
@@ -31121,7 +31123,7 @@ var Lexer = class {
31121
31123
  this.pos++;
31122
31124
  }
31123
31125
  }
31124
- throw new LexError("\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093", start, this.input);
31126
+ throw new LexError("\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093", start, this.input, true);
31125
31127
  }
31126
31128
  // ----------------------------------------------------------
31127
31129
  // バッククォート識別子: `field name`
@@ -31141,7 +31143,8 @@ var Lexer = class {
31141
31143
  throw new LexError(
31142
31144
  "\u30D0\u30C3\u30AF\u30AF\u30A9\u30FC\u30C8\u8B58\u5225\u5B50\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093",
31143
31145
  start,
31144
- this.input
31146
+ this.input,
31147
+ true
31145
31148
  );
31146
31149
  }
31147
31150
  // ----------------------------------------------------------
@@ -31248,6 +31251,30 @@ var Lexer = class {
31248
31251
  return this.makeToken(kind, value, start);
31249
31252
  }
31250
31253
  // ----------------------------------------------------------
31254
+ // 一時テーブル識別子: #temp
31255
+ // # は先頭のみ有効。isIdentStart に # を加えると isIdentContinue 経由で
31256
+ // 識別子の途中(APP#x 等)にも許容されてしまうため、専用分岐で読む。
31257
+ // ----------------------------------------------------------
31258
+ readHashIdent(start) {
31259
+ this.pos++;
31260
+ const next = this.input[this.pos] ?? "";
31261
+ if (!isIdentStart(next)) {
31262
+ throw new LexError("\u300C#\u300D \u306E\u76F4\u5F8C\u306B\u306F\u8B58\u5225\u5B50\u304C\u5FC5\u8981\u3067\u3059", start, this.input);
31263
+ }
31264
+ while (this.pos < this.input.length && isIdentContinue(this.input[this.pos])) {
31265
+ this.pos++;
31266
+ }
31267
+ const value = this.input.slice(start, this.pos);
31268
+ if (this.input[this.pos] === "@") {
31269
+ throw new LexError(
31270
+ `@profile is not allowed on temp table ${value}.`,
31271
+ this.pos,
31272
+ this.input
31273
+ );
31274
+ }
31275
+ return this.makeToken("IDENT" /* IDENT */, value, start);
31276
+ }
31277
+ // ----------------------------------------------------------
31251
31278
  // 空白・コメントをスキップ
31252
31279
  // ----------------------------------------------------------
31253
31280
  skipWhitespaceAndComments() {
@@ -31264,14 +31291,25 @@ var Lexer = class {
31264
31291
  continue;
31265
31292
  }
31266
31293
  if (ch === "/" && this.input[this.pos + 1] === "*") {
31294
+ const commentStart = this.pos;
31267
31295
  this.pos += 2;
31296
+ let closed = false;
31268
31297
  while (this.pos < this.input.length) {
31269
31298
  if (this.input[this.pos] === "*" && this.input[this.pos + 1] === "/") {
31270
31299
  this.pos += 2;
31300
+ closed = true;
31271
31301
  break;
31272
31302
  }
31273
31303
  this.pos++;
31274
31304
  }
31305
+ if (!closed) {
31306
+ throw new LexError(
31307
+ "\u30D6\u30ED\u30C3\u30AF\u30B3\u30E1\u30F3\u30C8\u304C\u9589\u3058\u3089\u308C\u3066\u3044\u307E\u305B\u3093",
31308
+ commentStart,
31309
+ this.input,
31310
+ true
31311
+ );
31312
+ }
31275
31313
  continue;
31276
31314
  }
31277
31315
  break;
@@ -31305,6 +31343,7 @@ function isJapanese(cp) {
31305
31343
  }
31306
31344
 
31307
31345
  // src/parser/parser.ts
31346
+ var MAX_BATCH_STATEMENTS = 20;
31308
31347
  var ParseError = class extends Error {
31309
31348
  constructor(message, token) {
31310
31349
  super(`${message}\uFF08\u4F4D\u7F6E ${token.pos}\u3001\u30C8\u30FC\u30AF\u30F3: \u300C${token.value}\u300D\uFF09`);
@@ -31318,15 +31357,54 @@ var Parser = class {
31318
31357
  this.pos = 0;
31319
31358
  /** WITH 句で定義された CTE 名のセット(parseTableRef で参照) */
31320
31359
  this.cteNames = /* @__PURE__ */ new Set();
31360
+ /** パース中に出現した一時テーブル参照(#name)のトークン。単文 API での拒否に使う */
31361
+ this.tempTableRefs = [];
31321
31362
  }
31322
31363
  // ----------------------------------------------------------
31323
31364
  // 公開 API
31324
31365
  // ----------------------------------------------------------
31366
+ /** 単文をパースする(従来 API。複文が渡されたらエラー) */
31325
31367
  parse() {
31326
- const stmt = this.parseStatement();
31327
- if (this.peek().kind === ";" /* SEMICOLON */) this.advance();
31368
+ const stmts = this.parseStatements();
31369
+ if (stmts.length === 0) {
31370
+ throw new ParseError("SQL \u6587\u304C\u3042\u308A\u307E\u305B\u3093", this.peek());
31371
+ }
31372
+ if (stmts.length > 1) {
31373
+ throw new ParseError(
31374
+ "\u3053\u306E API \u306F\u5358\u6587\u306E\u307F\u53D7\u3051\u4ED8\u3051\u307E\u3059\uFF08\u8907\u6587\u306F\u30D0\u30C3\u30C1\u5B9F\u884C API \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
31375
+ this.peek()
31376
+ );
31377
+ }
31378
+ if (this.tempTableRefs.length > 0) {
31379
+ const tok = this.tempTableRefs[0];
31380
+ throw new ParseError(
31381
+ `temp table ${tok.value} is not defined in this batch.`,
31382
+ tok
31383
+ );
31384
+ }
31385
+ return stmts[0];
31386
+ }
31387
+ /** 複文(`;` 区切り)をパースする。空文はスキップする */
31388
+ parseStatements() {
31389
+ const stmts = [];
31390
+ while (true) {
31391
+ while (this.peek().kind === ";" /* SEMICOLON */) this.advance();
31392
+ if (this.peek().kind === "EOF" /* EOF */) break;
31393
+ const startTok = this.peek();
31394
+ stmts.push(this.parseStatement());
31395
+ if (stmts.length > MAX_BATCH_STATEMENTS) {
31396
+ throw new ParseError(
31397
+ `batch exceeds ${MAX_BATCH_STATEMENTS} statements.`,
31398
+ startTok
31399
+ );
31400
+ }
31401
+ const after = this.peek();
31402
+ if (after.kind !== ";" /* SEMICOLON */ && after.kind !== "EOF" /* EOF */) {
31403
+ throw new ParseError("\u6587\u306E\u533A\u5207\u308A\u306B\u306F ; \u304C\u5FC5\u8981\u3067\u3059", after);
31404
+ }
31405
+ }
31328
31406
  this.expect("EOF" /* EOF */);
31329
- return stmt;
31407
+ return stmts;
31330
31408
  }
31331
31409
  // ----------------------------------------------------------
31332
31410
  // Statement ディスパッチ
@@ -31355,12 +31433,63 @@ var Parser = class {
31355
31433
  return this.parseDescribe();
31356
31434
  case "EXPLAIN" /* EXPLAIN */:
31357
31435
  return this.parseExplain();
31436
+ case "IDENT" /* IDENT */: {
31437
+ const upper = tok.value.toUpperCase();
31438
+ if (upper === "CREATE") return this.parseCreateTempTable();
31439
+ if (upper === "DROP") return this.parseDropTempTable();
31440
+ break;
31441
+ }
31358
31442
  default:
31359
- throw new ParseError(
31360
- "SELECT / INSERT / UPDATE / DELETE / REORDER / WITH / SHOW / DESCRIBE / EXPLAIN \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B SQL \u6587\u304C\u5FC5\u8981\u3067\u3059",
31361
- tok
31362
- );
31443
+ break;
31444
+ }
31445
+ 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",
31447
+ tok
31448
+ );
31449
+ }
31450
+ // ----------------------------------------------------------
31451
+ // CREATE TEMP TABLE / DROP TEMP TABLE(バッチ内一時テーブル)
31452
+ // CREATE / DROP / TEMP / TABLE は予約語にしない(ソフトキーワード)
31453
+ // ----------------------------------------------------------
31454
+ parseCreateTempTable() {
31455
+ this.advance();
31456
+ this.expectSoftKeyword("TEMP", "CREATE \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: CREATE TEMP TABLE #temp AS SELECT ...\uFF09");
31457
+ this.expectSoftKeyword("TABLE", "CREATE TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
31458
+ const name = this.parseTempTableName();
31459
+ this.expect("AS" /* AS */, "CREATE TEMP TABLE \u306B\u306F AS SELECT \u304C\u5FC5\u8981\u3067\u3059");
31460
+ const tok = this.peek();
31461
+ let query;
31462
+ if (tok.kind === "WITH" /* WITH */) {
31463
+ query = this.parseWith();
31464
+ } else if (tok.kind === "SELECT" /* SELECT */) {
31465
+ query = this.tryParseUnionChain(this.parseSelect());
31466
+ } else {
31467
+ throw new ParseError("CREATE TEMP TABLE ... AS \u306E\u5F8C\u306B\u306F SELECT / WITH \u304C\u5FC5\u8981\u3067\u3059", tok);
31468
+ }
31469
+ return { type: "CREATE_TEMP_TABLE", name, query };
31470
+ }
31471
+ parseDropTempTable() {
31472
+ this.advance();
31473
+ this.expectSoftKeyword("TEMP", "DROP \u306E\u5F8C\u306B\u306F TEMP TABLE \u304C\u5FC5\u8981\u3067\u3059\uFF08\u4F8B: DROP TEMP TABLE #temp\uFF09");
31474
+ this.expectSoftKeyword("TABLE", "DROP TEMP \u306E\u5F8C\u306B\u306F TABLE \u304C\u5FC5\u8981\u3067\u3059");
31475
+ const name = this.parseTempTableName();
31476
+ return { type: "DROP_TEMP_TABLE", name };
31477
+ }
31478
+ expectSoftKeyword(word, msg) {
31479
+ const tok = this.peek();
31480
+ if (tok.kind === "IDENT" /* IDENT */ && tok.value.toUpperCase() === word) {
31481
+ this.advance();
31482
+ return;
31363
31483
  }
31484
+ throw new ParseError(msg, tok);
31485
+ }
31486
+ parseTempTableName() {
31487
+ const tok = this.peek();
31488
+ if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
31489
+ this.advance();
31490
+ return tok.value;
31491
+ }
31492
+ throw new ParseError("\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\u306F # \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08\u4F8B: #temp\uFF09", tok);
31364
31493
  }
31365
31494
  parseShow() {
31366
31495
  this.advance();
@@ -31917,24 +32046,38 @@ var Parser = class {
31917
32046
  // FROM / JOIN
31918
32047
  // ----------------------------------------------------------
31919
32048
  parseTableRef() {
31920
- const name = this.parseIdentifier();
32049
+ const nameTok = this.peek();
32050
+ const name = this.parseTableName();
32051
+ if (nameTok.kind === "IDENT" /* IDENT */ && name.startsWith("#")) {
32052
+ this.tempTableRefs.push(this.prev());
32053
+ const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
32054
+ return { appId: 0, alias: alias2, cteName: name };
32055
+ }
31921
32056
  if (this.cteNames.has(name)) {
31922
- const alias2 = this.consume("AS" /* AS */) ? this.parseIdentifier() : this.tryParseImplicitAlias();
32057
+ const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
31923
32058
  return { appId: 0, alias: alias2, cteName: name };
31924
32059
  }
31925
32060
  const { appId, subtableCode } = extractTableRef(name, this.prev());
31926
32061
  if (subtableCode) {
31927
- const alias2 = this.consume("AS" /* AS */) ? this.parseIdentifier() : this.tryParseImplicitAlias();
32062
+ const alias2 = this.consume("AS" /* AS */) ? this.parseTableAliasName() : this.tryParseImplicitAlias();
31928
32063
  return { appId, alias: alias2, cteName: null, subtableCode };
31929
32064
  }
31930
32065
  const implicit = this.tryParseImplicitAlias();
31931
- const alias = this.consume("AS" /* AS */) ? this.parseIdentifier() : implicit ?? name;
32066
+ const alias = this.consume("AS" /* AS */) ? this.parseTableAliasName() : implicit ?? name;
31932
32067
  return { appId, alias, cteName: null };
31933
32068
  }
32069
+ // テーブル alias 名を読む(IDENT / BIDENT)。alias 位置の # は BIDENT でも拒否
32070
+ parseTableAliasName() {
32071
+ const tok = this.peek();
32072
+ if ((tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) && tok.value.startsWith("#")) {
32073
+ throw new ParseError("\u30A8\u30A4\u30EA\u30A2\u30B9\u540D\u306B # \u3067\u59CB\u307E\u308B\u540D\u524D\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
32074
+ }
32075
+ return this.parseIdentifier();
32076
+ }
31934
32077
  tryParseImplicitAlias() {
31935
32078
  const k = this.peek().kind;
31936
32079
  if (k === "IDENT" /* IDENT */ || k === "BIDENT" /* BIDENT */) {
31937
- return this.parseIdentifier();
32080
+ return this.parseTableAliasName();
31938
32081
  }
31939
32082
  return null;
31940
32083
  }
@@ -32339,6 +32482,7 @@ var Parser = class {
32339
32482
  parseInsert() {
32340
32483
  this.expect("INSERT" /* INSERT */);
32341
32484
  this.expect("INTO" /* INTO */);
32485
+ this.rejectTempTableDml();
32342
32486
  const name = this.parseIdentifier();
32343
32487
  const { appId, subtableCode } = extractTableRef(name, this.prev());
32344
32488
  this.expect("(" /* LPAREN */);
@@ -32364,6 +32508,7 @@ var Parser = class {
32364
32508
  parseUpsert() {
32365
32509
  this.expect("UPSERT" /* UPSERT */);
32366
32510
  this.expect("INTO" /* INTO */);
32511
+ this.rejectTempTableDml();
32367
32512
  const name = this.parseIdentifier();
32368
32513
  const { appId, subtableCode } = extractTableRef(name, this.prev());
32369
32514
  if (subtableCode) {
@@ -32448,6 +32593,7 @@ var Parser = class {
32448
32593
  // ----------------------------------------------------------
32449
32594
  parseUpdate() {
32450
32595
  this.expect("UPDATE" /* UPDATE */);
32596
+ this.rejectTempTableDml();
32451
32597
  const name = this.parseIdentifier();
32452
32598
  const { appId, subtableCode } = extractTableRef(name, this.prev());
32453
32599
  this.expect("SET" /* SET */);
@@ -32523,6 +32669,7 @@ var Parser = class {
32523
32669
  parseDelete() {
32524
32670
  this.expect("DELETE" /* DELETE */);
32525
32671
  this.expect("FROM" /* FROM */);
32672
+ this.rejectTempTableDml();
32526
32673
  const name = this.parseIdentifier();
32527
32674
  const { appId, subtableCode } = extractTableRef(name, this.prev());
32528
32675
  const whereTok = this.peek();
@@ -32541,6 +32688,7 @@ var Parser = class {
32541
32688
  parseReorder() {
32542
32689
  this.expect("REORDER" /* REORDER */);
32543
32690
  const all = this.consume("ALL" /* ALL */);
32691
+ this.rejectTempTableDml();
32544
32692
  const name = this.parseIdentifier();
32545
32693
  const { appId, subtableCode } = extractTableRef(name, this.prev());
32546
32694
  if (!subtableCode) {
@@ -32622,8 +32770,29 @@ var Parser = class {
32622
32770
  }
32623
32771
  return n;
32624
32772
  }
32625
- // 識別子(IDENT / BIDENT)を読む
32773
+ // 識別子(IDENT / BIDENT)を読む。# 始まりの一時テーブル名は不可
32774
+ //(temp マーカーはレキサが生成する IDENT のみ。`#field` のような
32775
+ // バッククォート識別子は # で始まる通常フィールド名として許容する)
32626
32776
  parseIdentifier() {
32777
+ const tok = this.peek();
32778
+ if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
32779
+ if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
32780
+ throw new ParseError(
32781
+ "\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u540D\uFF08# \u3067\u59CB\u307E\u308B\u540D\u524D\uFF09\u306F FROM / JOIN / CREATE / DROP TEMP TABLE \u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
32782
+ tok
32783
+ );
32784
+ }
32785
+ this.advance();
32786
+ return tok.value;
32787
+ }
32788
+ throw new ParseError(
32789
+ "\u30D5\u30A3\u30FC\u30EB\u30C9\u540D\u307E\u305F\u306F\u30C6\u30FC\u30D6\u30EB\u540D\u304C\u5FC5\u8981\u3067\u3059",
32790
+ tok
32791
+ );
32792
+ }
32793
+ // テーブル名(IDENT / BIDENT)を読む。# 始まりの一時テーブル名を許容する
32794
+ //(一時テーブルを受理してよいのはテーブル参照位置のみ。他は parseIdentifier を使う)
32795
+ parseTableName() {
32627
32796
  const tok = this.peek();
32628
32797
  if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */) {
32629
32798
  this.advance();
@@ -32634,10 +32803,23 @@ var Parser = class {
32634
32803
  tok
32635
32804
  );
32636
32805
  }
32806
+ // DML の対象テーブル位置に一時テーブルが指定されていたら拒否する
32807
+ rejectTempTableDml() {
32808
+ const tok = this.peek();
32809
+ if (tok.kind === "IDENT" /* IDENT */ && tok.value.startsWith("#")) {
32810
+ throw new ParseError(
32811
+ `DML on temp table ${tok.value} is not supported.`,
32812
+ tok
32813
+ );
32814
+ }
32815
+ }
32637
32816
  // エイリアス名: IDENT / BIDENT に加え、キーワードも許容する
32638
32817
  // 例: SELECT SUM(金額) AS avg → "avg" は AVG キーワードだが alias として有効
32639
32818
  parseAliasName() {
32640
32819
  const tok = this.peek();
32820
+ if (tok.value.startsWith("#")) {
32821
+ throw new ParseError("\u30A8\u30A4\u30EA\u30A2\u30B9\u540D\u306B # \u3067\u59CB\u307E\u308B\u540D\u524D\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093", tok);
32822
+ }
32641
32823
  if (tok.kind === "IDENT" /* IDENT */ || tok.kind === "BIDENT" /* BIDENT */ || KEYWORDS.has(tok.value.toUpperCase())) {
32642
32824
  this.advance();
32643
32825
  return tok.value.toLowerCase();
@@ -32694,6 +32876,159 @@ function extractTableRef(name, tok) {
32694
32876
  return { appId: Number(m[1]), subtableCode: m[2] ?? null };
32695
32877
  }
32696
32878
 
32879
+ // src/core/dmlGuard.ts
32880
+ function getStatementType(stmt) {
32881
+ if (!stmt || typeof stmt !== "object") return "UNKNOWN";
32882
+ const obj = stmt;
32883
+ return typeof obj.type === "string" ? obj.type : "UNKNOWN";
32884
+ }
32885
+ function isDmlType(type) {
32886
+ return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
32887
+ }
32888
+ 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";
32890
+ }
32891
+ function hasWhereClause(stmt) {
32892
+ if (!stmt || typeof stmt !== "object") return false;
32893
+ const obj = stmt;
32894
+ return obj.where !== null && obj.where !== void 0;
32895
+ }
32896
+ function isNoFromSelectStatement(stmt) {
32897
+ if (!stmt || typeof stmt !== "object") return false;
32898
+ const obj = stmt;
32899
+ return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
32900
+ }
32901
+ function getInsertValuesCount(stmt) {
32902
+ if (!stmt || typeof stmt !== "object") return null;
32903
+ const obj = stmt;
32904
+ if (obj.type !== "INSERT") return null;
32905
+ return Array.isArray(obj.values) ? obj.values.length : null;
32906
+ }
32907
+
32908
+ // src/core/batch.ts
32909
+ var MAX_TEMP_TABLES = 16;
32910
+ var BatchAnalysisError = class extends Error {
32911
+ constructor(message, statementIndex) {
32912
+ super(message);
32913
+ this.statementIndex = statementIndex;
32914
+ }
32915
+ };
32916
+ function collectRefs(node, tempRefs, appIds) {
32917
+ if (Array.isArray(node)) {
32918
+ for (const v of node) collectRefs(v, tempRefs, appIds);
32919
+ return;
32920
+ }
32921
+ if (node !== null && typeof node === "object") {
32922
+ const obj = node;
32923
+ const cte = obj["cteName"];
32924
+ if (typeof cte === "string" && cte.startsWith("#")) tempRefs.add(cte);
32925
+ const appId = obj["appId"];
32926
+ if (typeof appId === "number" && appId > 0) appIds.add(appId);
32927
+ for (const v of Object.values(obj)) collectRefs(v, tempRefs, appIds);
32928
+ }
32929
+ }
32930
+ function analyzeBatch(statements) {
32931
+ if (statements.length === 0) {
32932
+ throw new BatchAnalysisError("ArgumentError: SQL is empty.", 0);
32933
+ }
32934
+ if (statements.length === 1) {
32935
+ const t = statements[0].type;
32936
+ if (t === "CREATE_TEMP_TABLE" || t === "DROP_TEMP_TABLE") {
32937
+ const verb = t === "CREATE_TEMP_TABLE" ? "CREATE TEMP TABLE" : "DROP TEMP TABLE";
32938
+ throw new BatchAnalysisError(
32939
+ `ArgumentError: ${verb} requires a batch (temp tables are batch-scoped).`,
32940
+ 0
32941
+ );
32942
+ }
32943
+ }
32944
+ const defined = /* @__PURE__ */ new Map();
32945
+ const createdOrder = [];
32946
+ const results = [];
32947
+ statements.forEach((stmt, index) => {
32948
+ const statementType = getStatementType(stmt);
32949
+ const created = [];
32950
+ const dropped = [];
32951
+ const refs = /* @__PURE__ */ new Set();
32952
+ const stmtAppIds = /* @__PURE__ */ new Set();
32953
+ const dependsOn = /* @__PURE__ */ new Set();
32954
+ if (stmt.type === "CREATE_TEMP_TABLE") {
32955
+ collectRefs(stmt.query, refs, stmtAppIds);
32956
+ } else if (stmt.type === "DROP_TEMP_TABLE") {
32957
+ } else {
32958
+ collectRefs(stmt, refs, stmtAppIds);
32959
+ }
32960
+ let tempOnlySource = false;
32961
+ if (stmt.type === "INSERT_SELECT" || stmt.type === "UPSERT_SELECT") {
32962
+ const srcTemp = /* @__PURE__ */ new Set();
32963
+ const srcApps = /* @__PURE__ */ new Set();
32964
+ collectRefs(stmt.select, srcTemp, srcApps);
32965
+ tempOnlySource = srcTemp.size > 0 && srcApps.size === 0;
32966
+ }
32967
+ for (const name of refs) {
32968
+ const at = defined.get(name);
32969
+ if (at === void 0) {
32970
+ throw new BatchAnalysisError(
32971
+ `ParseError: temp table ${name} is not defined in this batch.`,
32972
+ index
32973
+ );
32974
+ }
32975
+ dependsOn.add(at);
32976
+ }
32977
+ if (stmt.type === "CREATE_TEMP_TABLE") {
32978
+ if (defined.has(stmt.name)) {
32979
+ throw new BatchAnalysisError(
32980
+ `ParseError: temp table ${stmt.name} is already defined.`,
32981
+ index
32982
+ );
32983
+ }
32984
+ defined.set(stmt.name, index);
32985
+ createdOrder.push(stmt.name);
32986
+ created.push(stmt.name);
32987
+ if (defined.size > MAX_TEMP_TABLES) {
32988
+ throw new BatchAnalysisError(
32989
+ `ParseError: batch exceeds ${MAX_TEMP_TABLES} temp tables.`,
32990
+ index
32991
+ );
32992
+ }
32993
+ }
32994
+ if (stmt.type === "DROP_TEMP_TABLE") {
32995
+ const at = defined.get(stmt.name);
32996
+ if (at === void 0) {
32997
+ throw new BatchAnalysisError(
32998
+ `ParseError: temp table ${stmt.name} is not defined in this batch.`,
32999
+ index
33000
+ );
33001
+ }
33002
+ dependsOn.add(at);
33003
+ dropped.push(stmt.name);
33004
+ defined.delete(stmt.name);
33005
+ }
33006
+ results.push({
33007
+ index,
33008
+ statementType,
33009
+ isDml: isDmlType(statementType),
33010
+ isReadOnly: isReadOnlyType(statementType),
33011
+ hasWhere: hasWhereClause(stmt),
33012
+ insertValuesCount: getInsertValuesCount(stmt),
33013
+ appIds: [...stmtAppIds].sort((a, b) => a - b),
33014
+ tempTablesCreated: created,
33015
+ tempTablesReferenced: [...refs],
33016
+ tempTablesDropped: dropped,
33017
+ dependsOn: [...dependsOn].sort((a, b) => a - b),
33018
+ tempOnlySource,
33019
+ targetAppId: isDmlType(statementType) && typeof stmt.appId === "number" ? stmt.appId : null
33020
+ });
33021
+ });
33022
+ const containsDml = results.some((r) => r.isDml);
33023
+ return {
33024
+ statementCount: statements.length,
33025
+ isReadOnlyBatch: !containsDml && results.every((r) => r.isReadOnly),
33026
+ containsDml,
33027
+ tempTables: createdOrder,
33028
+ statements: results
33029
+ };
33030
+ }
33031
+
32697
33032
  // src/engine/pushDownNot.ts
32698
33033
  function pushDownNot(expr) {
32699
33034
  switch (expr.type) {
@@ -34371,8 +34706,10 @@ function applyGroupBy(rows, groupByKeys, columns) {
34371
34706
  }
34372
34707
  for (const col of columns) {
34373
34708
  if (col.type === "AGGREGATE") {
34374
- const outputKey = col.alias ?? aggregateSyntheticName2(col.func, col.distinct, col.arg);
34375
- outRow[outputKey] = String(evalAggregate(col.func, col.distinct, col.arg, groupRows));
34709
+ const syntheticKey = aggregateSyntheticName2(col.func, col.distinct, col.arg);
34710
+ const value = String(evalAggregate(col.func, col.distinct, col.arg, groupRows));
34711
+ outRow[col.alias ?? syntheticKey] = value;
34712
+ if (col.alias) outRow[syntheticKey] = value;
34376
34713
  } else if (col.type === "ARITH_AGG_COL") {
34377
34714
  const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
34378
34715
  outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows));
@@ -34883,6 +35220,9 @@ function wrapClientWithMetrics(client, metrics) {
34883
35220
  async function executeStatement(sql, client, options) {
34884
35221
  const cacheContext = options.cacheContext ?? "default";
34885
35222
  const stmt = parseSql(sql);
35223
+ return executeParsedStatement(stmt, client, options, cacheContext);
35224
+ }
35225
+ async function executeParsedStatement(stmt, client, options, cacheContext) {
34886
35226
  switch (stmt.type) {
34887
35227
  case "SELECT":
34888
35228
  return executeSelect(stmt, client, options, cacheContext);
@@ -34910,9 +35250,181 @@ async function executeStatement(sql, client, options) {
34910
35250
  return executeDescribe(stmt, client, cacheContext);
34911
35251
  case "EXPLAIN":
34912
35252
  return executeExplain(stmt);
35253
+ // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
35254
+ case "CREATE_TEMP_TABLE":
35255
+ throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
35256
+ case "DROP_TEMP_TABLE":
35257
+ throw new Error("ArgumentError: DROP TEMP TABLE requires a batch (temp tables are batch-scoped).");
35258
+ }
35259
+ }
35260
+ var TEMP_TABLE_MAX_ROWS = 1e4;
35261
+ var BatchTimeoutError = class extends Error {
35262
+ constructor() {
35263
+ super("TimeoutError: batch timeout exceeded.");
35264
+ }
35265
+ };
35266
+ async function executeBatch(sql, client, options = {}) {
35267
+ const statements = parseSqlBatch(sql);
35268
+ const analysis = analyzeBatch(statements);
35269
+ if (options.continueOnError && analysis.containsDml) {
35270
+ throw new Error("ArgumentError: continueOnError is not allowed for batches containing DML.");
35271
+ }
35272
+ for (const s of analysis.statements) {
35273
+ if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
35274
+ if (s.statementType === "INSERT_SELECT" && s.tempOnlySource) continue;
35275
+ throw new BatchAnalysisError(
35276
+ s.statementType === "INSERT_SELECT" ? `ArgumentError: INSERT_SELECT in a batch must select from temp tables only. (statement ${s.index})` : `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
35277
+ s.index
35278
+ );
35279
+ }
35280
+ const metrics = createEmptyMetrics();
35281
+ const countedClient = wrapClientWithMetrics(client, metrics);
35282
+ const startedAt = Date.now();
35283
+ const deadline = options.timeoutMs != null ? startedAt + options.timeoutMs : null;
35284
+ const cacheContext = options.cacheContext ?? "default";
35285
+ const tempTables = /* @__PURE__ */ new Map();
35286
+ const results = [];
35287
+ const failed = /* @__PURE__ */ new Set();
35288
+ let aborted2 = null;
35289
+ for (let i = 0; i < statements.length; i++) {
35290
+ const info = analysis.statements[i];
35291
+ const base = { index: i, type: info.statementType };
35292
+ if (aborted2) {
35293
+ results.push({ ...base, status: "skipped", skippedReason: aborted2 });
35294
+ failed.add(i);
35295
+ continue;
35296
+ }
35297
+ const brokenDep = info.dependsOn.find((d) => failed.has(d));
35298
+ if (brokenDep !== void 0) {
35299
+ const depName = analysis.statements[brokenDep].tempTablesCreated[0] ?? `statement ${brokenDep}`;
35300
+ results.push({ ...base, status: "skipped", skippedReason: `dependency: ${depName}` });
35301
+ failed.add(i);
35302
+ continue;
35303
+ }
35304
+ if (deadline !== null && Date.now() >= deadline) {
35305
+ results.push({ ...base, status: "skipped", skippedReason: "timeout" });
35306
+ failed.add(i);
35307
+ aborted2 = "timeout";
35308
+ continue;
35309
+ }
35310
+ try {
35311
+ const remaining = deadline !== null ? deadline - Date.now() : null;
35312
+ const outcome = await runWithDeadline(
35313
+ executeBatchStatement(statements[i], info, countedClient, options, cacheContext, tempTables),
35314
+ remaining
35315
+ );
35316
+ results.push({ ...base, status: "success", ...outcome });
35317
+ } catch (e) {
35318
+ results.push({ ...base, status: "error", error: toBatchStatementError(e) });
35319
+ failed.add(i);
35320
+ if (e instanceof BatchTimeoutError) {
35321
+ aborted2 = "timeout";
35322
+ } else if (!options.continueOnError) {
35323
+ aborted2 = "fail-fast";
35324
+ }
35325
+ }
35326
+ }
35327
+ metrics.elapsedMs = Date.now() - startedAt;
35328
+ return {
35329
+ ok: results.every((r) => r.status === "success"),
35330
+ statementCount: statements.length,
35331
+ statements: results,
35332
+ analysis,
35333
+ metrics
35334
+ };
35335
+ }
35336
+ async function executeBatchStatement(stmt, info, client, options, cacheContext, tempTables) {
35337
+ if (stmt.type === "CREATE_TEMP_TABLE") {
35338
+ const materializeOptions = {
35339
+ ...options,
35340
+ maxRecords: options.tempTableMaxRows ?? TEMP_TABLE_MAX_ROWS,
35341
+ onLimitReached: "error"
35342
+ };
35343
+ const result = await runSelectLike(stmt.query, client, materializeOptions, cacheContext, tempTables);
35344
+ tempTables.set(stmt.name, result.rows);
35345
+ return { tempTable: stmt.name, rowCount: result.rows.length };
35346
+ }
35347
+ if (stmt.type === "DROP_TEMP_TABLE") {
35348
+ tempTables.delete(stmt.name);
35349
+ return { tempTable: stmt.name };
35350
+ }
35351
+ if (stmt.type === "EXPLAIN") {
35352
+ return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
35353
+ }
35354
+ if (info.tempTablesReferenced.length > 0) {
35355
+ if (stmt.type === "SELECT" || stmt.type === "UNION") {
35356
+ return { result: await executeQueryWithCte(stmt, client, options, tempTables, cacheContext) };
35357
+ }
35358
+ if (stmt.type === "WITH") {
35359
+ return { result: await executeWith(stmt, client, options, cacheContext, tempTables) };
35360
+ }
35361
+ if (stmt.type === "INSERT_SELECT") {
35362
+ return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
35363
+ }
35364
+ throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
35365
+ }
35366
+ return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
35367
+ }
35368
+ async function runSelectLike(query, client, options, cacheContext, tempTables) {
35369
+ if (query.type === "WITH") {
35370
+ return executeWith(query, client, options, cacheContext, tempTables);
34913
35371
  }
35372
+ return executeQueryWithCte(query, client, options, tempTables, cacheContext);
34914
35373
  }
34915
- async function executeSelect(stmt, client, options, cacheContext) {
35374
+ async function runWithDeadline(work, remainingMs) {
35375
+ if (remainingMs === null) return work;
35376
+ if (remainingMs <= 0) {
35377
+ void work.catch(() => {
35378
+ });
35379
+ throw new BatchTimeoutError();
35380
+ }
35381
+ let timer;
35382
+ try {
35383
+ return await Promise.race([
35384
+ work,
35385
+ new Promise((_, reject) => {
35386
+ timer = setTimeout(() => reject(new BatchTimeoutError()), remainingMs);
35387
+ })
35388
+ ]);
35389
+ } catch (e) {
35390
+ if (e instanceof BatchTimeoutError) {
35391
+ void work.catch(() => {
35392
+ });
35393
+ }
35394
+ throw e;
35395
+ } finally {
35396
+ if (timer !== void 0) clearTimeout(timer);
35397
+ }
35398
+ }
35399
+ function toBatchStatementError(e) {
35400
+ if (e instanceof Error) {
35401
+ const name = e.name !== "Error" ? e.name : null;
35402
+ return { code: name ?? codeFromMessagePrefix(e.message), message: e.message };
35403
+ }
35404
+ if (e !== null && typeof e === "object") {
35405
+ const obj = e;
35406
+ const message2 = typeof obj.message === "string" && obj.message.length > 0 ? obj.message : safeJsonStringify(e);
35407
+ const code = typeof obj.code === "string" && obj.code.length > 0 ? obj.code : codeFromMessagePrefix(message2);
35408
+ return { code, message: message2 };
35409
+ }
35410
+ const message = String(e);
35411
+ return { code: codeFromMessagePrefix(message), message };
35412
+ }
35413
+ function codeFromMessagePrefix(message) {
35414
+ return message.match(/^([A-Za-z]+Error):/)?.[1] ?? "Error";
35415
+ }
35416
+ function safeJsonStringify(v) {
35417
+ try {
35418
+ return JSON.stringify(v) ?? String(v);
35419
+ } catch {
35420
+ return String(v);
35421
+ }
35422
+ }
35423
+ function parseSqlBatch(sql) {
35424
+ const tokens = new Lexer(sql).tokenize();
35425
+ return new Parser(tokens).parseStatements();
35426
+ }
35427
+ async function executeSelect(stmt, client, options, cacheContext, cteCache) {
34916
35428
  if (isNoFromSelect(stmt)) {
34917
35429
  return executeNoFromSelect(stmt);
34918
35430
  }
@@ -34921,7 +35433,7 @@ async function executeSelect(stmt, client, options, cacheContext) {
34921
35433
  if (mode === "SIMPLE") {
34922
35434
  return executeSimpleSelect(stmt, client, options, cacheContext);
34923
35435
  } else {
34924
- return executeFullScanSelect(stmt, client, options, cacheContext);
35436
+ return executeFullScanSelect(stmt, client, options, cacheContext, cteCache);
34925
35437
  }
34926
35438
  }
34927
35439
  function isNoFromSelect(stmt) {
@@ -35045,13 +35557,13 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
35045
35557
  }
35046
35558
  }
35047
35559
  }
35048
- async function executeFullScanSelect(stmt, client, options, cacheContext) {
35560
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
35049
35561
  const maxRecords2 = options.maxRecords ?? 1e4;
35050
35562
  const warnings = /* @__PURE__ */ new Set();
35051
35563
  const parallel = options.fetchParallel ?? 1;
35052
35564
  await Promise.all([
35053
- resolveSubqueries(stmt.where, client, options, cacheContext),
35054
- resolveSubqueries(stmt.having, client, options, cacheContext)
35565
+ resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
35566
+ resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
35055
35567
  ]);
35056
35568
  const tableConditions = /* @__PURE__ */ new Map();
35057
35569
  if (stmt.where !== null) {
@@ -35101,7 +35613,7 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
35101
35613
  onOptJoins.push(join);
35102
35614
  }
35103
35615
  }
35104
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
35616
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
35105
35617
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
35106
35618
  scalarCachePromise.catch(() => {
35107
35619
  });
@@ -35170,11 +35682,11 @@ function deduplicateRows(rows, columns) {
35170
35682
  return true;
35171
35683
  });
35172
35684
  }
35173
- async function executeWith(stmt, client, options, cacheContext) {
35174
- if (canInlineSingleCte(stmt)) {
35685
+ async function executeWith(stmt, client, options, cacheContext, seed) {
35686
+ if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
35175
35687
  return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext);
35176
35688
  }
35177
- const cteCache = /* @__PURE__ */ new Map();
35689
+ const cteCache = new Map(seed ?? []);
35178
35690
  for (const cte of stmt.ctes) {
35179
35691
  let result;
35180
35692
  if (cte.query.type === "SHOW_APPS") {
@@ -35286,7 +35798,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
35286
35798
  }
35287
35799
  const hasCteRef = query.from.cteName != null || query.joins.some((j) => j.table.cteName != null);
35288
35800
  if (!hasCteRef) {
35289
- return executeSelect(query, client, options, cacheContext);
35801
+ return executeSelect(query, client, options, cacheContext, cteCache);
35290
35802
  }
35291
35803
  return executeFullScanWithCte(query, client, options, cteCache, cacheContext);
35292
35804
  }
@@ -35295,10 +35807,10 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
35295
35807
  const warnings = /* @__PURE__ */ new Set();
35296
35808
  const parallel = options.fetchParallel ?? 1;
35297
35809
  await Promise.all([
35298
- resolveSubqueries(stmt.where, client, options, cacheContext),
35299
- resolveSubqueries(stmt.having, client, options, cacheContext)
35810
+ resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
35811
+ resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
35300
35812
  ]);
35301
- const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext);
35813
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
35302
35814
  const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
35303
35815
  scalarCachePromise.catch(() => {
35304
35816
  });
@@ -35696,14 +36208,18 @@ async function executeInsert(stmt, client, options, cacheContext) {
35696
36208
  insertedCount: createdIds.flat().length
35697
36209
  };
35698
36210
  }
35699
- async function executeInsertSelect(stmt, client, options, cacheContext) {
35700
- const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
36211
+ async function executeInsertSelect(stmt, client, options, cacheContext, cteCache) {
36212
+ const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
35701
36213
  const { rows, columns } = selectResult;
35702
36214
  if (columns.length !== stmt.fields.length) {
35703
36215
  throw new Error(
35704
36216
  `SELECT \u306E\u5217\u6570\uFF08${columns.length}\uFF09\u3068 INSERT \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u6570\uFF08${stmt.fields.length}\uFF09\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`
35705
36217
  );
35706
36218
  }
36219
+ if (options.confirm) {
36220
+ const ok = await options.confirm(rows.length, "INSERT");
36221
+ if (!ok) throw new OperationCancelledError("INSERT", rows.length);
36222
+ }
35707
36223
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
35708
36224
  const allRecords = rows.map((row) => {
35709
36225
  const record2 = {};
@@ -36251,24 +36767,30 @@ function parseSql(sql) {
36251
36767
  throw e;
36252
36768
  }
36253
36769
  }
36254
- async function resolveSubqueries(where, client, options, cacheContext) {
36770
+ async function resolveSubqueries(where, client, options, cacheContext, cteCache) {
36255
36771
  const tasks = [];
36256
- collectSubqueryTasks(where, client, options, cacheContext, tasks);
36772
+ collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache);
36257
36773
  await Promise.all(tasks);
36258
36774
  }
36259
- function collectSubqueryTasks(where, client, options, cacheContext, tasks) {
36775
+ function runSubquery(query, client, options, cacheContext, cteCache) {
36776
+ if (cteCache !== void 0 && cteCache.size > 0) {
36777
+ return executeQueryWithCte(query, client, options, cteCache, cacheContext);
36778
+ }
36779
+ return executeSelect(query, client, options, cacheContext);
36780
+ }
36781
+ function collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache) {
36260
36782
  if (where === null) return;
36261
36783
  switch (where.type) {
36262
36784
  case "BINARY": {
36263
36785
  const right = where.right;
36264
36786
  if (right.type === "SUBQUERY_IN_LIST") {
36265
- tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
36787
+ tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
36266
36788
  const col = right.column ?? (result.columns[0] ?? "");
36267
36789
  right.resolved = new Set(result.rows.map((r) => r[col] ?? ""));
36268
36790
  }));
36269
36791
  }
36270
36792
  if (right.type === "SCALAR_SUBQUERY") {
36271
- tasks.push(executeSelect(right.query, client, options, cacheContext).then((result) => {
36793
+ tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
36272
36794
  if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
36273
36795
  if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
36274
36796
  const col = result.columns[0] ?? "";
@@ -36278,16 +36800,16 @@ function collectSubqueryTasks(where, client, options, cacheContext, tasks) {
36278
36800
  break;
36279
36801
  }
36280
36802
  case "LOGICAL":
36281
- collectSubqueryTasks(where.left, client, options, cacheContext, tasks);
36282
- collectSubqueryTasks(where.right, client, options, cacheContext, tasks);
36803
+ collectSubqueryTasks(where.left, client, options, cacheContext, tasks, cteCache);
36804
+ collectSubqueryTasks(where.right, client, options, cacheContext, tasks, cteCache);
36283
36805
  break;
36284
36806
  case "NOT":
36285
36807
  case "GROUP":
36286
- collectSubqueryTasks(where.expr, client, options, cacheContext, tasks);
36808
+ collectSubqueryTasks(where.expr, client, options, cacheContext, tasks, cteCache);
36287
36809
  break;
36288
36810
  case "EXISTS": {
36289
36811
  const node = where;
36290
- tasks.push(executeSelect(node.query, client, options, cacheContext).then((result) => {
36812
+ tasks.push(runSubquery(node.query, client, options, cacheContext, cteCache).then((result) => {
36291
36813
  node.resolved = result.rowCount > 0;
36292
36814
  }));
36293
36815
  break;
@@ -36305,7 +36827,7 @@ async function resolveSetSubqueries(assignments, client, options, cacheContext)
36305
36827
  a.value = { type: "STRING", value: resolved };
36306
36828
  }
36307
36829
  }
36308
- async function resolveScalarColumns(columns, client, options, cacheContext) {
36830
+ async function resolveScalarColumns(columns, client, options, cacheContext, cteCache) {
36309
36831
  const byQuery = /* @__PURE__ */ new Map();
36310
36832
  const pending = [];
36311
36833
  for (let i = 0; i < columns.length; i++) {
@@ -36314,7 +36836,7 @@ async function resolveScalarColumns(columns, client, options, cacheContext) {
36314
36836
  const key = JSON.stringify(col.query);
36315
36837
  let promise2 = byQuery.get(key);
36316
36838
  if (!promise2) {
36317
- promise2 = executeSelect(col.query, client, options, cacheContext).then((result) => {
36839
+ promise2 = runSubquery(col.query, client, options, cacheContext, cteCache).then((result) => {
36318
36840
  if (result.rowCount === 0) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u5024\u3092\u8FD4\u3057\u307E\u305B\u3093\u3067\u3057\u305F");
36319
36841
  if (result.rowCount > 1) throw new Error("\u30B9\u30AB\u30E9\u30FC\u30B5\u30D6\u30AF\u30A8\u30EA\u304C\u8907\u6570\u884C\u3092\u8FD4\u3057\u307E\u3057\u305F\uFF081\u884C\u306E\u307F\u8A31\u53EF\uFF09");
36320
36842
  const firstCol = result.columns[0] ?? "";
@@ -36329,6 +36851,59 @@ async function resolveScalarColumns(columns, client, options, cacheContext) {
36329
36851
  pending.forEach(([i], idx) => cache.set(i, values[idx]));
36330
36852
  return cache;
36331
36853
  }
36854
+ function buildBatchExplainPlans(sql) {
36855
+ const statements = parseSqlBatch(sql);
36856
+ const analysis = analyzeBatch(statements);
36857
+ return {
36858
+ statementCount: statements.length,
36859
+ statements: statements.map((stmt, i) => ({
36860
+ index: i,
36861
+ type: analysis.statements[i].statementType,
36862
+ plan: buildBatchStatementPlan(stmt, analysis.statements[i])
36863
+ }))
36864
+ };
36865
+ }
36866
+ function buildBatchStatementPlan(stmt, info) {
36867
+ if (stmt.type === "CREATE_TEMP_TABLE") {
36868
+ return [
36869
+ `CREATE TEMP TABLE ${stmt.name}`,
36870
+ ` scope: batch\uFF08\u30D0\u30C3\u30C1\u7D42\u4E86\u6642\u306B\u81EA\u52D5\u7834\u68C4\uFF09`,
36871
+ ` 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`,
36872
+ ...buildPlanForBatchQuery(stmt.query, info).map((l) => ` ${l}`)
36873
+ ];
36874
+ }
36875
+ if (stmt.type === "DROP_TEMP_TABLE") {
36876
+ return [
36877
+ `DROP TEMP TABLE ${stmt.name}`,
36878
+ " \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30B9\u30C8\u30A2\u306E\u89E3\u653E\u306E\u307F\uFF08kintone \u30A2\u30AF\u30BB\u30B9\u306A\u3057\uFF09"
36879
+ ];
36880
+ }
36881
+ if (stmt.type === "SHOW_APPS") return ["SHOW APPS\uFF08\u30A2\u30D7\u30EA\u4E00\u89A7\u306E\u53D6\u5F97\uFF09"];
36882
+ if (stmt.type === "DESCRIBE") return [`DESCRIBE APP${stmt.appId}\uFF08\u30D5\u30A3\u30FC\u30EB\u30C9\u5B9A\u7FA9\u306E\u53D6\u5F97\uFF09`];
36883
+ if (stmt.type === "EXPLAIN") return buildPlanForBatchQuery(stmt.query, info);
36884
+ return buildPlanForBatchQuery(stmt, info);
36885
+ }
36886
+ function buildPlanForBatchQuery(query, info) {
36887
+ if (info.tempTablesReferenced.length === 0) {
36888
+ return buildExplainPlan(query);
36889
+ }
36890
+ const lines = [];
36891
+ if (query.type === "INSERT_SELECT") {
36892
+ lines.push(
36893
+ `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
+ );
36895
+ }
36896
+ lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
36897
+ lines.push(
36898
+ ` 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
+ );
36900
+ const apps = info.appIds.filter((a) => query.type !== "INSERT_SELECT" || a !== query.appId);
36901
+ if (apps.length > 0) {
36902
+ lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
36903
+ }
36904
+ lines.push(" note: \u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u3078\u306E WHERE \u30D7\u30C3\u30B7\u30E5\u30C0\u30A6\u30F3\u306F\u884C\u308F\u308C\u306A\u3044");
36905
+ return lines;
36906
+ }
36332
36907
  function executeExplain(stmt) {
36333
36908
  const lines = buildExplainPlan(stmt.query);
36334
36909
  return {
@@ -36649,6 +37224,10 @@ function parseSqlStatement(sql) {
36649
37224
  const tokens = new Lexer(sql).tokenize();
36650
37225
  return new Parser(tokens).parse();
36651
37226
  }
37227
+ function parseSqlStatements(sql) {
37228
+ const tokens = new Lexer(sql).tokenize();
37229
+ return new Parser(tokens).parseStatements();
37230
+ }
36652
37231
 
36653
37232
  // src/node/appProfiles.ts
36654
37233
  function parseTokenMap(raw) {
@@ -36834,35 +37413,6 @@ function buildCacheContext(defaultProfile, appBindingByMappedApp) {
36834
37413
  return `apps:${pairs.join(",")}`;
36835
37414
  }
36836
37415
 
36837
- // src/node/dmlGuard.ts
36838
- function getStatementType(stmt) {
36839
- if (!stmt || typeof stmt !== "object") return "UNKNOWN";
36840
- const obj = stmt;
36841
- return typeof obj.type === "string" ? obj.type : "UNKNOWN";
36842
- }
36843
- function isDmlType(type) {
36844
- return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
36845
- }
36846
- function isReadOnlyType(type) {
36847
- return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE";
36848
- }
36849
- function hasWhereClause(stmt) {
36850
- if (!stmt || typeof stmt !== "object") return false;
36851
- const obj = stmt;
36852
- return obj.where !== null && obj.where !== void 0;
36853
- }
36854
- function isNoFromSelectStatement(stmt) {
36855
- if (!stmt || typeof stmt !== "object") return false;
36856
- const obj = stmt;
36857
- return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
36858
- }
36859
- function getInsertValuesCount(stmt) {
36860
- if (!stmt || typeof stmt !== "object") return null;
36861
- const obj = stmt;
36862
- if (obj.type !== "INSERT") return null;
36863
- return Array.isArray(obj.values) ? obj.values.length : null;
36864
- }
36865
-
36866
37416
  // src/node/config.ts
36867
37417
  var import_fs = require("fs");
36868
37418
  function loadKsqlConfig(configPath) {
@@ -36904,6 +37454,107 @@ function resolveTokenValue(raw) {
36904
37454
  return raw;
36905
37455
  }
36906
37456
 
37457
+ // src/api/requestGate.ts
37458
+ var DEFAULT_MAX_CONCURRENT = 10;
37459
+ var DEFAULT_MAX_RETRIES = 3;
37460
+ var DEFAULT_BASE_DELAY_MS = 500;
37461
+ var DEFAULT_MAX_DELAY_MS = 8e3;
37462
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
37463
+ function isRetryableError(err) {
37464
+ if (!(err instanceof Error)) return false;
37465
+ const status = err.message.match(/^kintone API error (\d{3}):/);
37466
+ if (status) return RETRYABLE_STATUSES.has(Number(status[1]));
37467
+ if (err.name === "AbortError" || err.name === "TimeoutError") return true;
37468
+ if (/fetch failed/i.test(err.message)) return true;
37469
+ return false;
37470
+ }
37471
+ var RequestGate = class {
37472
+ constructor(options = {}) {
37473
+ this.active = 0;
37474
+ this.waiters = [];
37475
+ this.maxConcurrent = clampInt(options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT, 1, 50);
37476
+ this.maxRetries = clampInt(options.maxRetries ?? DEFAULT_MAX_RETRIES, 0, 10);
37477
+ this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
37478
+ this.maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
37479
+ this.sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
37480
+ this.random = options.random ?? Math.random;
37481
+ }
37482
+ /** 現在の同時実行数(テスト・診断用) */
37483
+ get activeCount() {
37484
+ return this.active;
37485
+ }
37486
+ get limit() {
37487
+ return this.maxConcurrent;
37488
+ }
37489
+ /** GET 系: セマフォ + リトライ付きで実行する */
37490
+ async runReadOnly(fn) {
37491
+ let attempt = 0;
37492
+ while (true) {
37493
+ try {
37494
+ return await this.withSlot(fn);
37495
+ } catch (err) {
37496
+ if (attempt >= this.maxRetries || !isRetryableError(err)) throw err;
37497
+ await this.sleep(this.backoffDelay(attempt));
37498
+ attempt += 1;
37499
+ }
37500
+ }
37501
+ }
37502
+ /** 書き込み系: セマフォのみ(リトライしない — 二重実行防止) */
37503
+ async runMutation(fn) {
37504
+ return this.withSlot(fn);
37505
+ }
37506
+ async withSlot(fn) {
37507
+ await this.acquire();
37508
+ try {
37509
+ return await fn();
37510
+ } finally {
37511
+ this.release();
37512
+ }
37513
+ }
37514
+ async acquire() {
37515
+ if (this.active < this.maxConcurrent) {
37516
+ this.active += 1;
37517
+ return;
37518
+ }
37519
+ await new Promise((resolve2) => this.waiters.push(resolve2));
37520
+ this.active += 1;
37521
+ }
37522
+ release() {
37523
+ this.active -= 1;
37524
+ const next = this.waiters.shift();
37525
+ if (next) next();
37526
+ }
37527
+ /** 指数バックオフ + ジッタ(attempt: 0 始まり) */
37528
+ backoffDelay(attempt) {
37529
+ const base = Math.min(this.baseDelayMs * 2 ** attempt, this.maxDelayMs);
37530
+ const jitter = 1 + (this.random() - 0.5) * 0.5;
37531
+ return Math.round(base * jitter);
37532
+ }
37533
+ };
37534
+ function withRequestGate(client, gate) {
37535
+ return {
37536
+ getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
37537
+ getApps: () => gate.runReadOnly(() => client.getApps()),
37538
+ getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
37539
+ postRecords: (params) => gate.runMutation(() => client.postRecords(params)),
37540
+ putRecords: (params) => gate.runMutation(() => client.putRecords(params)),
37541
+ deleteRecords: (params) => gate.runMutation(() => client.deleteRecords(params))
37542
+ };
37543
+ }
37544
+ var globalGate = null;
37545
+ function getGlobalRequestGate(limitHint) {
37546
+ if (globalGate === null) {
37547
+ const envValue = Number(process.env.KSQL_MAX_CONCURRENT);
37548
+ const limit = Number.isInteger(envValue) && envValue > 0 ? envValue : limitHint;
37549
+ globalGate = new RequestGate({ maxConcurrent: limit });
37550
+ }
37551
+ return globalGate;
37552
+ }
37553
+ function clampInt(v, min, max) {
37554
+ if (!Number.isFinite(v)) return min;
37555
+ return Math.max(min, Math.min(max, Math.trunc(v)));
37556
+ }
37557
+
36907
37558
  // src/cli/nodeKintoneClient.ts
36908
37559
  function createNodeKintoneClient(baseUrl, tokenResolver) {
36909
37560
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -37246,10 +37897,14 @@ async function createKsqlRuntime(serverOptions, input) {
37246
37897
  },
37247
37898
  getApps: () => defaultClient.getApps()
37248
37899
  };
37900
+ const gatedClient = withRequestGate(
37901
+ routedClient,
37902
+ getGlobalRequestGate(profile2.query?.maxConcurrent)
37903
+ );
37249
37904
  return {
37250
37905
  sql,
37251
37906
  profileName,
37252
- client: routedClient,
37907
+ client: gatedClient,
37253
37908
  cacheContext: buildCacheContext(profileName, normalized.appBindingByMappedApp),
37254
37909
  maxRecords: maxRecords2,
37255
37910
  fetchParallel: fetchParallel2,
@@ -37438,6 +38093,14 @@ function deleteSavedQuery(catalog, name) {
37438
38093
  }
37439
38094
 
37440
38095
  // src/mcp/tools.ts
38096
+ function requireSingleStatement(validation, toolName) {
38097
+ if (validation.batch) {
38098
+ throw new Error(
38099
+ `ArgumentError: batch SQL (multiple statements) is not supported by ${toolName} yet.`
38100
+ );
38101
+ }
38102
+ return validation;
38103
+ }
37441
38104
  var DEFAULT_MAX_RECORDS = 500;
37442
38105
  var DEFAULT_ON_LIMIT = "error";
37443
38106
  function noOpClient() {
@@ -37493,6 +38156,59 @@ function toSelectPayload(result) {
37493
38156
  warnings: result.warnings ?? []
37494
38157
  };
37495
38158
  }
38159
+ function toMutationSummary(result) {
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
+ });
38202
+ return {
38203
+ ok: batch.ok,
38204
+ batch: true,
38205
+ statementCount: batch.statementCount,
38206
+ statements,
38207
+ results,
38208
+ // バッチ全体の警告(仕様 §6.2)。文ごとの警告は results[].warnings に入る
38209
+ warnings: []
38210
+ };
38211
+ }
37496
38212
  function toMutationPayload(result) {
37497
38213
  if (result.type === "INSERT") {
37498
38214
  return {
@@ -37577,35 +38293,71 @@ async function runSafely(fn) {
37577
38293
  function createKsqlMcpTools(serverOptions, deps = {}) {
37578
38294
  const createRuntime = deps.createRuntime ?? createKsqlRuntime;
37579
38295
  const executeSql = deps.executeSql ?? execute;
38296
+ const executeBatchSql = deps.executeBatchSql ?? executeBatch;
37580
38297
  async function validate(input) {
37581
38298
  const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
37582
- const stmt = parseSqlStatement(normalized.normalizedSql);
37583
- const statementType = getStatementType(stmt);
37584
- const isDml = isDmlType(statementType);
37585
- const isReadOnly = isReadOnlyType(statementType);
38299
+ const statements = parseSqlStatements(normalized.normalizedSql);
38300
+ const analysis = analyzeBatch(statements);
37586
38301
  const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => ({
37587
38302
  mappedAppId,
37588
38303
  appId: binding.appId,
37589
38304
  profile: binding.profile
37590
38305
  }));
37591
- return {
38306
+ const statementValidations = analysis.statements.map((s2) => ({
38307
+ index: s2.index,
38308
+ statementType: s2.statementType,
38309
+ isDml: s2.isDml,
38310
+ isReadOnly: s2.isReadOnly,
38311
+ hasWhere: s2.hasWhere,
38312
+ insertValuesCount: s2.insertValuesCount,
38313
+ appIds: s2.appIds,
38314
+ tempTablesCreated: s2.tempTablesCreated,
38315
+ tempTablesReferenced: s2.tempTablesReferenced,
38316
+ tempTablesDropped: s2.tempTablesDropped,
38317
+ tempOnlySource: s2.tempOnlySource,
38318
+ targetAppId: s2.targetAppId
38319
+ }));
38320
+ const common = {
37592
38321
  ok: true,
37593
- statementType,
37594
- isDml,
37595
- isReadOnly,
37596
- hasWhere: hasWhereClause(stmt),
37597
- insertValuesCount: getInsertValuesCount(stmt),
37598
- appIds: extractAppIds(normalized.normalizedSql),
37599
- canRunWithQueryTool: isReadOnly,
37600
- requiresMutationTool: isDml,
38322
+ statementCount: analysis.statementCount,
38323
+ isReadOnlyBatch: analysis.isReadOnlyBatch,
38324
+ containsDml: analysis.containsDml,
38325
+ tempTables: analysis.tempTables,
38326
+ canRunWithQueryTool: analysis.isReadOnlyBatch,
38327
+ requiresMutationTool: analysis.containsDml,
38328
+ statements: statementValidations,
37601
38329
  normalizedSql: normalized.normalizedSql,
37602
38330
  hasProfileSyntax: normalized.hasProfileSyntax,
37603
38331
  cacheContext: normalized.cacheContext,
37604
38332
  appBindings
37605
38333
  };
38334
+ if (analysis.statementCount > 1) {
38335
+ return { ...common, batch: true };
38336
+ }
38337
+ const s = statementValidations[0];
38338
+ return {
38339
+ ...common,
38340
+ batch: false,
38341
+ statementType: s.statementType,
38342
+ isDml: s.isDml,
38343
+ isReadOnly: s.isReadOnly,
38344
+ hasWhere: s.hasWhere,
38345
+ insertValuesCount: s.insertValuesCount,
38346
+ appIds: s.appIds
38347
+ };
37606
38348
  }
37607
38349
  async function explain(input) {
37608
38350
  const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
38351
+ const statements = parseSqlStatements(normalized.normalizedSql);
38352
+ if (statements.length > 1) {
38353
+ const plans = buildBatchExplainPlans(normalized.normalizedSql);
38354
+ return {
38355
+ ok: true,
38356
+ batch: true,
38357
+ statementCount: plans.statementCount,
38358
+ statements: plans.statements
38359
+ };
38360
+ }
37609
38361
  const result = await executeSql(explainSql(normalized.normalizedSql), noOpClient(), {
37610
38362
  cacheContext: normalized.cacheContext
37611
38363
  });
@@ -37616,6 +38368,31 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37616
38368
  }
37617
38369
  async function query(input) {
37618
38370
  const validation = await validate(input);
38371
+ if (validation.batch) {
38372
+ if (validation.containsDml) {
38373
+ throw new Error("ArgumentError: batch contains DML statements. Use ksql_mutate.");
38374
+ }
38375
+ const runtime2 = await createRuntime(serverOptions, {
38376
+ sql: input.sql,
38377
+ profile: input.profile,
38378
+ maxRecords: input.maxRecords,
38379
+ fetchParallel: input.fetchParallel,
38380
+ onLimit: input.onLimit,
38381
+ timeout: input.timeout
38382
+ });
38383
+ const batchResult = await executeBatchSql(runtime2.sql, runtime2.client, {
38384
+ maxRecords: runtime2.maxRecords,
38385
+ fetchParallel: runtime2.fetchParallel,
38386
+ onLimitReached: runtime2.onLimit,
38387
+ cacheContext: runtime2.cacheContext,
38388
+ continueOnError: input.continueOnError,
38389
+ // バッチでは timeout を合計タイムアウトとして扱う(仕様 §5.7)。
38390
+ // runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
38391
+ // HTTP クライアント側の per-request タイムアウトと同値になる
38392
+ timeoutMs: runtime2.timeout
38393
+ });
38394
+ return toBatchQueryPayload(batchResult, input.maxTotalRecords);
38395
+ }
37619
38396
  if (!validation.isReadOnly) {
37620
38397
  throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
37621
38398
  }
@@ -37651,9 +38428,75 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37651
38428
  }
37652
38429
  return toSelectPayload(result);
37653
38430
  }
38431
+ async function mutateBatch(input, validation, dmlMaxRows2) {
38432
+ if (!validation.containsDml) {
38433
+ throw new Error("ArgumentError: batch contains no DML statements. Use ksql_query.");
38434
+ }
38435
+ let staticInsertTotal = 0;
38436
+ for (const s of validation.statements) {
38437
+ if (!s.isDml) continue;
38438
+ 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
+ if ((s.statementType === "UPDATE" || s.statementType === "DELETE") && !s.hasWhere) {
38448
+ throw new Error(`ArgumentError: ${s.statementType} without WHERE is blocked by ksql_mutate.${at}`);
38449
+ }
38450
+ if (s.insertValuesCount !== null && s.insertValuesCount > dmlMaxRows2) {
38451
+ throw new Error(
38452
+ `ArgumentError: INSERT rows (${s.insertValuesCount}) exceed dmlMaxRows (${dmlMaxRows2}).${at}`
38453
+ );
38454
+ }
38455
+ staticInsertTotal += s.insertValuesCount ?? 0;
38456
+ }
38457
+ const dmlTotalMaxRows = input.dmlTotalMaxRows;
38458
+ if (dmlTotalMaxRows !== void 0 && staticInsertTotal > dmlTotalMaxRows) {
38459
+ throw new Error(
38460
+ `ArgumentError: batch INSERT rows (${staticInsertTotal}) exceed dmlTotalMaxRows (${dmlTotalMaxRows}).`
38461
+ );
38462
+ }
38463
+ const runtime = await createRuntime(serverOptions, {
38464
+ sql: input.sql,
38465
+ profile: input.profile,
38466
+ maxRecords: dmlMaxRows2 + 1,
38467
+ fetchParallel: input.fetchParallel,
38468
+ onLimit: DEFAULT_ON_LIMIT,
38469
+ timeout: input.timeout
38470
+ });
38471
+ let totalAffected = staticInsertTotal;
38472
+ const batchResult = await executeBatchSql(runtime.sql, runtime.client, {
38473
+ maxRecords: runtime.maxRecords,
38474
+ fetchParallel: runtime.fetchParallel,
38475
+ onLimitReached: runtime.onLimit,
38476
+ cacheContext: runtime.cacheContext,
38477
+ // 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
38478
+ timeoutMs: runtime.timeout,
38479
+ confirm: async (count, operation) => {
38480
+ if (count > dmlMaxRows2) {
38481
+ throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows2}).`);
38482
+ }
38483
+ totalAffected += count;
38484
+ if (dmlTotalMaxRows !== void 0 && totalAffected > dmlTotalMaxRows) {
38485
+ throw new Error(
38486
+ `ArgumentError: batch affected rows (${totalAffected}) exceed dmlTotalMaxRows (${dmlTotalMaxRows}).`
38487
+ );
38488
+ }
38489
+ return true;
38490
+ }
38491
+ });
38492
+ return toBatchQueryPayload(batchResult);
38493
+ }
37654
38494
  async function mutate(input) {
37655
38495
  const dmlMaxRows2 = requireDmlApproval(input, "ksql_mutate");
37656
38496
  const validation = await validate(input);
38497
+ if (validation.batch) {
38498
+ return mutateBatch(input, validation, dmlMaxRows2);
38499
+ }
37657
38500
  if (!validation.isDml) {
37658
38501
  throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_mutate. Use ksql_query.`);
37659
38502
  }
@@ -37712,10 +38555,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37712
38555
  });
37713
38556
  }
37714
38557
  async function saveQuery(input) {
37715
- const validation = await validate({
37716
- sql: input.sql,
37717
- profile: input.defaultProfile
37718
- });
38558
+ const validation = requireSingleStatement(
38559
+ await validate({
38560
+ sql: input.sql,
38561
+ profile: input.defaultProfile
38562
+ }),
38563
+ "ksql_save_query"
38564
+ );
37719
38565
  assertSavedQuerySafety(input, {
37720
38566
  isDml: validation.isDml,
37721
38567
  statementType: validation.statementType
@@ -37759,10 +38605,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37759
38605
  const saved = getSavedQuery(catalog, input.name);
37760
38606
  assertProfileOverrideAllowed(saved, input.profile);
37761
38607
  const profile2 = input.profile ?? saved.defaultProfile;
37762
- const validation = await validate({
37763
- sql: saved.sql,
37764
- profile: profile2
37765
- });
38608
+ const validation = requireSingleStatement(
38609
+ await validate({
38610
+ sql: saved.sql,
38611
+ profile: profile2
38612
+ }),
38613
+ "ksql_run_saved_query"
38614
+ );
37766
38615
  assertSavedQuerySafety(saved, {
37767
38616
  isDml: validation.isDml,
37768
38617
  statementType: validation.statementType
@@ -37858,7 +38707,11 @@ var queryInputSchema = external_exports.object({
37858
38707
  maxRecords,
37859
38708
  fetchParallel,
37860
38709
  onLimit,
37861
- timeout
38710
+ timeout,
38711
+ /** バッチ(複文)専用: 実行時エラー後も後続文を実行する(既定 false = fail-fast) */
38712
+ continueOnError: external_exports.boolean().optional(),
38713
+ /** バッチ(複文)専用: 返却する結果セットの合計行数上限(既定なし) */
38714
+ maxTotalRecords: external_exports.number().int().positive().optional()
37862
38715
  });
37863
38716
  var mutateInputSchema = external_exports.object({
37864
38717
  sql: external_exports.string().min(1),
@@ -37867,7 +38720,10 @@ var mutateInputSchema = external_exports.object({
37867
38720
  confirmText: external_exports.literal("yes"),
37868
38721
  dmlMaxRows,
37869
38722
  fetchParallel,
37870
- timeout
38723
+ timeout,
38724
+ /** バッチ(複文)専用: バッチ合計の影響行数上限(既定なし = 文ごとの dmlMaxRows のみ)。
38725
+ * なお DML バッチに continueOnError は存在しない(常に fail-fast) */
38726
+ dmlTotalMaxRows: dmlMaxRows.optional()
37871
38727
  });
37872
38728
  var describeAppInputSchema = external_exports.object({
37873
38729
  app: external_exports.number().int().positive(),