@rex0220/kintone-sql-tools 1.2.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);
31363
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;
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) {
@@ -33722,23 +34057,31 @@ function resolveKintoneFunc(name) {
33722
34057
  return "";
33723
34058
  }
33724
34059
  }
34060
+ var likeRegexCache = /* @__PURE__ */ new Map();
34061
+ var LIKE_REGEX_CACHE_MAX = 200;
33725
34062
  function matchLike(value, pattern) {
33726
34063
  if (!pattern.includes("%") && !pattern.includes("_")) {
33727
34064
  return value.includes(pattern);
33728
34065
  }
33729
- let regexStr = "^";
33730
- for (let i = 0; i < pattern.length; i++) {
33731
- const ch = pattern[i];
33732
- if (ch === "%") {
33733
- regexStr += ".*";
33734
- } else if (ch === "_") {
33735
- regexStr += ".";
33736
- } else {
33737
- regexStr += ch.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
34066
+ let regex = likeRegexCache.get(pattern);
34067
+ if (!regex) {
34068
+ let regexStr = "^";
34069
+ for (let i = 0; i < pattern.length; i++) {
34070
+ const ch = pattern[i];
34071
+ if (ch === "%") {
34072
+ regexStr += ".*";
34073
+ } else if (ch === "_") {
34074
+ regexStr += ".";
34075
+ } else {
34076
+ regexStr += ch.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
34077
+ }
33738
34078
  }
34079
+ regexStr += "$";
34080
+ regex = new RegExp(regexStr, "u");
34081
+ if (likeRegexCache.size >= LIKE_REGEX_CACHE_MAX) likeRegexCache.clear();
34082
+ likeRegexCache.set(pattern, regex);
33739
34083
  }
33740
- regexStr += "$";
33741
- return new RegExp(regexStr, "u").test(value);
34084
+ return regex.test(value);
33742
34085
  }
33743
34086
 
33744
34087
  // src/converter/dmlToKintone.ts
@@ -34166,10 +34509,12 @@ async function fetchPage(fetcher, app, query, fields, pageSize, offset) {
34166
34509
  return fetcher({ app, query: pageQuery, fields });
34167
34510
  }
34168
34511
  function buildCursorQuery(baseQuery, cursorId) {
34169
- if (cursorId <= 0) return baseQuery.trimEnd();
34170
- const cursor = `$id > ${cursorId} order by $id asc`;
34171
34512
  const base = baseQuery.trimEnd();
34172
- return base ? `${base} and ${cursor}` : cursor;
34513
+ if (cursorId <= 0) {
34514
+ return base ? `${base} order by $id asc` : "order by $id asc";
34515
+ }
34516
+ const cursor = `$id > ${cursorId} order by $id asc`;
34517
+ return base ? `(${base}) and ${cursor}` : cursor;
34173
34518
  }
34174
34519
  function buildPageQuery(query, pageSize, offset) {
34175
34520
  const base = query.trimEnd();
@@ -34322,6 +34667,8 @@ function applyJoin(leftRows, rightRows, join) {
34322
34667
  else rightIndex.set(k, [rRow]);
34323
34668
  }
34324
34669
  const result = [];
34670
+ const emptyRight = {};
34671
+ for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
34325
34672
  for (const lRow of leftRows) {
34326
34673
  const k = lRow[leftKey] ?? "";
34327
34674
  const matched = rightIndex.get(k) ?? [];
@@ -34330,8 +34677,6 @@ function applyJoin(leftRows, rightRows, join) {
34330
34677
  result.push({ ...lRow, ...rRow });
34331
34678
  }
34332
34679
  } else if (joinType === "LEFT") {
34333
- const emptyRight = {};
34334
- for (const key of Object.keys(rightRows[0] ?? {})) emptyRight[key] = "";
34335
34680
  result.push({ ...lRow, ...emptyRight });
34336
34681
  }
34337
34682
  }
@@ -34361,8 +34706,10 @@ function applyGroupBy(rows, groupByKeys, columns) {
34361
34706
  }
34362
34707
  for (const col of columns) {
34363
34708
  if (col.type === "AGGREGATE") {
34364
- const outputKey = col.alias ?? aggregateSyntheticName2(col.func, col.distinct, col.arg);
34365
- 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;
34366
34713
  } else if (col.type === "ARITH_AGG_COL") {
34367
34714
  const outputKey = col.alias ?? aggArithDefaultKey(col.expr);
34368
34715
  outRow[outputKey] = String(evalAggArithExpr(col.expr, groupRows));
@@ -34407,12 +34754,23 @@ function evalAggregate(func, distinct, arg, rows) {
34407
34754
  return nums.reduce((a, b) => a + b, 0);
34408
34755
  case "AVG":
34409
34756
  return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
34757
+ // Math.max(...nums) は要素数が多いと RangeError になるためループで求める
34410
34758
  case "MAX":
34411
- return nums.length === 0 ? 0 : Math.max(...nums);
34759
+ return nums.length === 0 ? 0 : maxOf(nums);
34412
34760
  case "MIN":
34413
- return nums.length === 0 ? 0 : Math.min(...nums);
34761
+ return nums.length === 0 ? 0 : minOf(nums);
34414
34762
  }
34415
34763
  }
34764
+ function maxOf(nums) {
34765
+ let m = nums[0];
34766
+ for (const n of nums) if (n > m) m = n;
34767
+ return m;
34768
+ }
34769
+ function minOf(nums) {
34770
+ let m = nums[0];
34771
+ for (const n of nums) if (n < m) m = n;
34772
+ return m;
34773
+ }
34416
34774
  function evalAggArithExpr(node, rows) {
34417
34775
  if (node.type === "NUMBER") return node.value;
34418
34776
  if (node.type === "AGG_REF") return evalAggregate(node.func, node.distinct, node.arg, rows);
@@ -34449,43 +34807,89 @@ function applyHaving(rows, having) {
34449
34807
  return rows.filter((row) => evalWhere(having, row));
34450
34808
  }
34451
34809
  function applyDistinct(rows, columns) {
34810
+ if (rows.length === 0) return rows;
34811
+ const keyFor = buildDistinctKeyBuilder(rows, columns);
34452
34812
  const seen = /* @__PURE__ */ new Set();
34453
34813
  return rows.filter((row) => {
34454
- const key = buildDistinctKey(row, columns);
34814
+ const key = keyFor(row);
34455
34815
  if (seen.has(key)) return false;
34456
34816
  seen.add(key);
34457
34817
  return true;
34458
34818
  });
34459
34819
  }
34460
- function buildDistinctKey(row, columns) {
34820
+ function buildDistinctKeyBuilder(rows, columns) {
34461
34821
  if (columns.some((c) => c.type === "WILDCARD")) {
34462
- return JSON.stringify(Object.entries(row).sort());
34463
- }
34464
- const values = [];
34465
- for (const col of columns) {
34466
- if (col.type === "FIELD") {
34467
- values.push(row[col.field] ?? "");
34468
- continue;
34822
+ const allKeys = /* @__PURE__ */ new Set();
34823
+ for (const row of rows) {
34824
+ for (const k of Object.keys(row)) allKeys.add(k);
34469
34825
  }
34470
- if (col.type === "PARENT_WILDCARD") {
34471
- for (const key of Object.keys(row).filter((k) => k.startsWith("_p.")).sort()) {
34472
- values.push(row[key] ?? "");
34826
+ const keys = [...allKeys].sort();
34827
+ return (row) => JSON.stringify(keys.map((k) => row[k] !== void 0 ? row[k] : null));
34828
+ }
34829
+ let sortedParentKeys = [];
34830
+ if (columns.some((c) => c.type === "PARENT_WILDCARD")) {
34831
+ const parentKeys = /* @__PURE__ */ new Set();
34832
+ for (const row of rows) {
34833
+ for (const k of Object.keys(row)) {
34834
+ if (k.startsWith("_p.")) parentKeys.add(k);
34473
34835
  }
34474
34836
  }
34837
+ sortedParentKeys = [...parentKeys].sort();
34475
34838
  }
34476
- return values.join("\0");
34839
+ return (row) => {
34840
+ const values = [];
34841
+ for (const col of columns) {
34842
+ if (col.type === "FIELD") {
34843
+ values.push(row[col.field] ?? "");
34844
+ continue;
34845
+ }
34846
+ if (col.type === "PARENT_WILDCARD") {
34847
+ for (const k of sortedParentKeys) {
34848
+ values.push(row[k] !== void 0 ? row[k] : null);
34849
+ }
34850
+ }
34851
+ }
34852
+ return JSON.stringify(values);
34853
+ };
34477
34854
  }
34478
34855
  function applyOrderBy(rows, orderBy, optionOrders, sortKinds) {
34479
34856
  if (orderBy.length === 0) return rows;
34480
- return [...rows].sort((a, b) => {
34481
- for (const { key, direction } of orderBy) {
34482
- const av = evalOrderKey(key, a);
34483
- const bv = evalOrderKey(key, b);
34484
- const cmp = compareOrderValues(av, bv, key, optionOrders, sortKinds);
34485
- if (cmp !== 0) return direction === "ASC" ? cmp : -cmp;
34857
+ const keyMeta = orderBy.map(({ key }) => ({
34858
+ orderMap: key.type === "FIELD_NAME" ? optionOrders?.get(key.name) : void 0,
34859
+ sortKind: key.type === "FIELD_NAME" ? sortKinds?.get(key.name) : void 0
34860
+ }));
34861
+ const decorated = rows.map((row) => ({
34862
+ row,
34863
+ keys: orderBy.map(({ key }, i) => {
34864
+ const s = evalOrderKey(key, row);
34865
+ const n = Number(s);
34866
+ const orderMap = keyMeta[i].orderMap;
34867
+ return {
34868
+ s,
34869
+ n,
34870
+ isNum: !Number.isNaN(n),
34871
+ rank: orderMap ? minChoiceIndex(parseChoiceValues(s), orderMap) : 0
34872
+ };
34873
+ })
34874
+ }));
34875
+ decorated.sort((a, b) => {
34876
+ for (let i = 0; i < orderBy.length; i++) {
34877
+ const cmp = compareSortKeys(a.keys[i], b.keys[i], keyMeta[i]);
34878
+ if (cmp !== 0) return orderBy[i].direction === "ASC" ? cmp : -cmp;
34486
34879
  }
34487
34880
  return 0;
34488
34881
  });
34882
+ return decorated.map((d) => d.row);
34883
+ }
34884
+ function compareSortKeys(a, b, meta3) {
34885
+ if (meta3.orderMap) {
34886
+ if (a.rank !== b.rank) return a.rank - b.rank;
34887
+ return a.s.localeCompare(b.s, "ja");
34888
+ }
34889
+ if (meta3.sortKind === "string") {
34890
+ return a.s.localeCompare(b.s, "ja");
34891
+ }
34892
+ return a.isNum && b.isNum ? a.n - b.n : a.s.localeCompare(b.s, "ja");
34489
34893
  }
34490
34894
  function evalOrderKey(key, row) {
34491
34895
  switch (key.type) {
@@ -34497,32 +34901,6 @@ function evalOrderKey(key, row) {
34497
34901
  return evalStringFunc(key.expr, row);
34498
34902
  }
34499
34903
  }
34500
- function compareOrderValues(av, bv, key, optionOrders, sortKinds) {
34501
- if (key.type === "FIELD_NAME") {
34502
- const orderMap = optionOrders?.get(key.name);
34503
- if (orderMap) {
34504
- const ac = compareByChoiceOrder(av, bv, orderMap);
34505
- if (ac !== 0) return ac;
34506
- return av.localeCompare(bv, "ja");
34507
- }
34508
- const sortKind = sortKinds?.get(key.name);
34509
- if (sortKind === "number") {
34510
- return compareAsNumber(av, bv);
34511
- }
34512
- if (sortKind === "string") {
34513
- return av.localeCompare(bv, "ja");
34514
- }
34515
- }
34516
- return compareAuto(av, bv);
34517
- }
34518
- function compareByChoiceOrder(av, bv, orderMap) {
34519
- const aValues = parseChoiceValues(av);
34520
- const bValues = parseChoiceValues(bv);
34521
- const aRank = minChoiceIndex(aValues, orderMap);
34522
- const bRank = minChoiceIndex(bValues, orderMap);
34523
- if (aRank !== bRank) return aRank - bRank;
34524
- return 0;
34525
- }
34526
34904
  function parseChoiceValues(raw) {
34527
34905
  const trimmed = raw.trim();
34528
34906
  if (trimmed === "") return [""];
@@ -34546,18 +34924,6 @@ function minChoiceIndex(values, orderMap) {
34546
34924
  }
34547
34925
  return min;
34548
34926
  }
34549
- function compareAsNumber(av, bv) {
34550
- const an = Number(av);
34551
- const bn = Number(bv);
34552
- const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
34553
- return numeric ? an - bn : av.localeCompare(bv, "ja");
34554
- }
34555
- function compareAuto(av, bv) {
34556
- const an = Number(av);
34557
- const bn = Number(bv);
34558
- const numeric = !Number.isNaN(an) && !Number.isNaN(bn);
34559
- return numeric ? an - bn : av.localeCompare(bv, "ja");
34560
- }
34561
34927
  function applyLimit(rows, limit, offset) {
34562
34928
  const start = offset ?? 0;
34563
34929
  if (limit === null) return rows.slice(start);
@@ -34802,8 +35168,61 @@ function toFlatString(value) {
34802
35168
 
34803
35169
  // src/execute.ts
34804
35170
  async function execute(sql, client, options = {}) {
35171
+ const metrics = createEmptyMetrics();
35172
+ const countedClient = wrapClientWithMetrics(client, metrics);
35173
+ const startedAt = Date.now();
35174
+ const result = await executeStatement(sql, countedClient, options);
35175
+ metrics.elapsedMs = Date.now() - startedAt;
35176
+ return { ...result, metrics };
35177
+ }
35178
+ function createEmptyMetrics() {
35179
+ return {
35180
+ getCalls: 0,
35181
+ postCalls: 0,
35182
+ putCalls: 0,
35183
+ deleteCalls: 0,
35184
+ fieldCalls: 0,
35185
+ appsCalls: 0,
35186
+ fetchedRows: 0,
35187
+ elapsedMs: 0
35188
+ };
35189
+ }
35190
+ function wrapClientWithMetrics(client, metrics) {
35191
+ return {
35192
+ getRecords: async (params) => {
35193
+ metrics.getCalls += 1;
35194
+ const res = await client.getRecords(params);
35195
+ metrics.fetchedRows += res.records.length;
35196
+ return res;
35197
+ },
35198
+ postRecords: (params) => {
35199
+ metrics.postCalls += 1;
35200
+ return client.postRecords(params);
35201
+ },
35202
+ putRecords: (params) => {
35203
+ metrics.putCalls += 1;
35204
+ return client.putRecords(params);
35205
+ },
35206
+ deleteRecords: (params) => {
35207
+ metrics.deleteCalls += 1;
35208
+ return client.deleteRecords(params);
35209
+ },
35210
+ getApps: () => {
35211
+ metrics.appsCalls += 1;
35212
+ return client.getApps();
35213
+ },
35214
+ getFields: (appId) => {
35215
+ metrics.fieldCalls += 1;
35216
+ return client.getFields(appId);
35217
+ }
35218
+ };
35219
+ }
35220
+ async function executeStatement(sql, client, options) {
34805
35221
  const cacheContext = options.cacheContext ?? "default";
34806
35222
  const stmt = parseSql(sql);
35223
+ return executeParsedStatement(stmt, client, options, cacheContext);
35224
+ }
35225
+ async function executeParsedStatement(stmt, client, options, cacheContext) {
34807
35226
  switch (stmt.type) {
34808
35227
  case "SELECT":
34809
35228
  return executeSelect(stmt, client, options, cacheContext);
@@ -34812,7 +35231,7 @@ async function execute(sql, client, options = {}) {
34812
35231
  case "WITH":
34813
35232
  return executeWith(stmt, client, options, cacheContext);
34814
35233
  case "INSERT":
34815
- return executeInsert(stmt, client, cacheContext);
35234
+ return executeInsert(stmt, client, options, cacheContext);
34816
35235
  case "INSERT_SELECT":
34817
35236
  return executeInsertSelect(stmt, client, options, cacheContext);
34818
35237
  case "UPSERT":
@@ -34831,9 +35250,181 @@ async function execute(sql, client, options = {}) {
34831
35250
  return executeDescribe(stmt, client, cacheContext);
34832
35251
  case "EXPLAIN":
34833
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);
35371
+ }
35372
+ return executeQueryWithCte(query, client, options, tempTables, cacheContext);
35373
+ }
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);
34834
35397
  }
34835
35398
  }
34836
- async function executeSelect(stmt, client, options, cacheContext) {
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) {
34837
35428
  if (isNoFromSelect(stmt)) {
34838
35429
  return executeNoFromSelect(stmt);
34839
35430
  }
@@ -34842,7 +35433,7 @@ async function executeSelect(stmt, client, options, cacheContext) {
34842
35433
  if (mode === "SIMPLE") {
34843
35434
  return executeSimpleSelect(stmt, client, options, cacheContext);
34844
35435
  } else {
34845
- return executeFullScanSelect(stmt, client, options, cacheContext);
35436
+ return executeFullScanSelect(stmt, client, options, cacheContext, cteCache);
34846
35437
  }
34847
35438
  }
34848
35439
  function isNoFromSelect(stmt) {
@@ -34927,8 +35518,7 @@ async function executeSimpleSelect(stmt, client, options, cacheContext) {
34927
35518
  }
34928
35519
  let rows = records.map((r) => flatten(r, null));
34929
35520
  if (!useSingleGet) {
34930
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
34931
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
35521
+ const { optionOrders, sortKinds } = await buildOrderByMetaForSelect(stmt, client, cacheContext);
34932
35522
  rows = applyOrderBy(rows, stmt.orderBy, optionOrders, sortKinds);
34933
35523
  rows = applyLimit(rows, stmt.limit, stmt.offset);
34934
35524
  }
@@ -34956,22 +35546,25 @@ async function validateSelectFieldCodes(stmt, mode, client, cacheContext) {
34956
35546
  }
34957
35547
  }
34958
35548
  for (const [appId, fields] of appToFields.entries()) {
34959
- if (fields.size === 0) continue;
35549
+ const userFields = [...fields].filter((f) => !isSystemLikeFieldCode(f));
35550
+ if (userFields.length === 0) continue;
34960
35551
  const defs = await getFieldsCached(appId, client, cacheContext);
34961
35552
  if (defs.length === 0) continue;
34962
35553
  const validCodes = new Set(defs.map((d) => d.code));
34963
- const unknown2 = [...fields].filter((f) => !isSystemLikeFieldCode(f) && !validCodes.has(f));
35554
+ const unknown2 = userFields.filter((f) => !validCodes.has(f));
34964
35555
  if (unknown2.length > 0) {
34965
35556
  throw new Error(`ArgumentError: unknown field code(s): ${unknown2.join(", ")} (APP${appId})`);
34966
35557
  }
34967
35558
  }
34968
35559
  }
34969
- async function executeFullScanSelect(stmt, client, options, cacheContext) {
35560
+ async function executeFullScanSelect(stmt, client, options, cacheContext, cteCache) {
34970
35561
  const maxRecords2 = options.maxRecords ?? 1e4;
34971
35562
  const warnings = /* @__PURE__ */ new Set();
34972
35563
  const parallel = options.fetchParallel ?? 1;
34973
- await resolveSubqueries(stmt.where, client, options, cacheContext);
34974
- await resolveSubqueries(stmt.having, client, options, cacheContext);
35564
+ await Promise.all([
35565
+ resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
35566
+ resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
35567
+ ]);
34975
35568
  const tableConditions = /* @__PURE__ */ new Map();
34976
35569
  if (stmt.where !== null) {
34977
35570
  if (stmt.from.alias) {
@@ -35020,6 +35613,12 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
35020
35613
  onOptJoins.push(join);
35021
35614
  }
35022
35615
  }
35616
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
35617
+ const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
35618
+ scalarCachePromise.catch(() => {
35619
+ });
35620
+ orderByMetaPromise.catch(() => {
35621
+ });
35023
35622
  const mainRecords = await mainFetch;
35024
35623
  const tables = /* @__PURE__ */ new Map();
35025
35624
  tables.set(stmt.from.alias, mainRecords);
@@ -35051,15 +35650,16 @@ async function executeFullScanSelect(stmt, client, options, cacheContext) {
35051
35650
  );
35052
35651
  tables.set(join.table.alias, joinRecords);
35053
35652
  }));
35054
- const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
35055
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
35056
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
35653
+ const scalarCache = await scalarCachePromise;
35654
+ const { optionOrders, sortKinds } = await orderByMetaPromise;
35057
35655
  const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
35058
35656
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
35059
35657
  }
35060
35658
  async function executeUnion(stmt, client, options, cacheContext) {
35061
- const leftResult = stmt.left.type === "UNION" ? await executeUnion(stmt.left, client, options, cacheContext) : await executeSelect(stmt.left, client, options, cacheContext);
35062
- const rightResult = await executeSelect(stmt.right, client, options, cacheContext);
35659
+ const [leftResult, rightResult] = await Promise.all([
35660
+ stmt.left.type === "UNION" ? executeUnion(stmt.left, client, options, cacheContext) : executeSelect(stmt.left, client, options, cacheContext),
35661
+ executeSelect(stmt.right, client, options, cacheContext)
35662
+ ]);
35063
35663
  const leftCols = leftResult.columns;
35064
35664
  const rightCols = rightResult.columns;
35065
35665
  const remappedRight = rightResult.rows.map((row) => {
@@ -35076,17 +35676,17 @@ async function executeUnion(stmt, client, options, cacheContext) {
35076
35676
  function deduplicateRows(rows, columns) {
35077
35677
  const seen = /* @__PURE__ */ new Set();
35078
35678
  return rows.filter((row) => {
35079
- const key = columns.map((c) => row[c] ?? "").join("\0");
35679
+ const key = JSON.stringify(columns.map((c) => row[c] ?? ""));
35080
35680
  if (seen.has(key)) return false;
35081
35681
  seen.add(key);
35082
35682
  return true;
35083
35683
  });
35084
35684
  }
35085
- async function executeWith(stmt, client, options, cacheContext) {
35086
- if (canInlineSingleCte(stmt)) {
35685
+ async function executeWith(stmt, client, options, cacheContext, seed) {
35686
+ if ((seed == null || seed.size === 0) && canInlineSingleCte(stmt)) {
35087
35687
  return executeSelect(buildInlinedQuery(stmt), client, options, cacheContext);
35088
35688
  }
35089
- const cteCache = /* @__PURE__ */ new Map();
35689
+ const cteCache = new Map(seed ?? []);
35090
35690
  for (const cte of stmt.ctes) {
35091
35691
  let result;
35092
35692
  if (cte.query.type === "SHOW_APPS") {
@@ -35179,8 +35779,10 @@ function stripCteAliasFromFieldValue(fv, alias) {
35179
35779
  }
35180
35780
  async function executeQueryWithCte(query, client, options, cteCache, cacheContext) {
35181
35781
  if (query.type === "UNION") {
35182
- const leftResult = await executeQueryWithCte(query.left, client, options, cteCache, cacheContext);
35183
- const rightResult = await executeQueryWithCte(query.right, client, options, cteCache, cacheContext);
35782
+ const [leftResult, rightResult] = await Promise.all([
35783
+ executeQueryWithCte(query.left, client, options, cteCache, cacheContext),
35784
+ executeQueryWithCte(query.right, client, options, cteCache, cacheContext)
35785
+ ]);
35184
35786
  const leftCols = leftResult.columns;
35185
35787
  const rightCols = rightResult.columns;
35186
35788
  const remapped = rightResult.rows.map((row) => {
@@ -35196,7 +35798,7 @@ async function executeQueryWithCte(query, client, options, cteCache, cacheContex
35196
35798
  }
35197
35799
  const hasCteRef = query.from.cteName != null || query.joins.some((j) => j.table.cteName != null);
35198
35800
  if (!hasCteRef) {
35199
- return executeSelect(query, client, options, cacheContext);
35801
+ return executeSelect(query, client, options, cacheContext, cteCache);
35200
35802
  }
35201
35803
  return executeFullScanWithCte(query, client, options, cteCache, cacheContext);
35202
35804
  }
@@ -35204,8 +35806,16 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
35204
35806
  const maxRecords2 = options.maxRecords ?? 1e4;
35205
35807
  const warnings = /* @__PURE__ */ new Set();
35206
35808
  const parallel = options.fetchParallel ?? 1;
35207
- await resolveSubqueries(stmt.where, client, options, cacheContext);
35208
- await resolveSubqueries(stmt.having, client, options, cacheContext);
35809
+ await Promise.all([
35810
+ resolveSubqueries(stmt.where, client, options, cacheContext, cteCache),
35811
+ resolveSubqueries(stmt.having, client, options, cacheContext, cteCache)
35812
+ ]);
35813
+ const scalarCachePromise = resolveScalarColumns(stmt.columns, client, options, cacheContext, cteCache);
35814
+ const orderByMetaPromise = buildOrderByMetaForSelect(stmt, client, cacheContext);
35815
+ scalarCachePromise.catch(() => {
35816
+ });
35817
+ orderByMetaPromise.catch(() => {
35818
+ });
35209
35819
  const tables = /* @__PURE__ */ new Map();
35210
35820
  if (stmt.from.cteName != null) {
35211
35821
  const rows2 = cteCache.get(stmt.from.cteName) ?? [];
@@ -35252,9 +35862,8 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
35252
35862
  }
35253
35863
  });
35254
35864
  await Promise.all(joinFetches);
35255
- const scalarCache = await resolveScalarColumns(stmt.columns, client, options, cacheContext);
35256
- const optionOrders = await buildOptionOrdersForSelect(stmt, client, cacheContext);
35257
- const sortKinds = await buildSortKindsForSelect(stmt, client, cacheContext);
35865
+ const scalarCache = await scalarCachePromise;
35866
+ const { optionOrders, sortKinds } = await orderByMetaPromise;
35258
35867
  const { rows, columns } = runFullScan({ tables, stmt, scalarCache, optionOrders, sortKinds });
35259
35868
  return { type: "SELECT", rows, columns, rowCount: rows.length, warnings: [...warnings] };
35260
35869
  }
@@ -35290,6 +35899,78 @@ async function fetchTableRecordsForFullScan(stmt, table, client, maxRecords2, pa
35290
35899
  const parentRecords = parentResolved.records;
35291
35900
  return expandSubtableRecords(parentRecords, table.subtableCode);
35292
35901
  }
35902
+ var UPSERT_IN_CHUNK_SIZE = 50;
35903
+ function normalizeKeyPart(v) {
35904
+ const t = v.trim();
35905
+ if (t !== "" && !Number.isNaN(Number(t))) return String(Number(t));
35906
+ return v;
35907
+ }
35908
+ function upsertCompositeKey(parts) {
35909
+ return JSON.stringify(parts);
35910
+ }
35911
+ function upsertNormalizedKey(parts, numericKey) {
35912
+ return JSON.stringify(parts.map((p, i) => numericKey[i] ? normalizeKeyPart(p) : p));
35913
+ }
35914
+ function lookupUpsertTarget(index, keyParts) {
35915
+ const exact = index.raw.get(upsertCompositeKey(keyParts));
35916
+ if (exact !== void 0) return exact;
35917
+ if (!index.numericKey.some(Boolean)) return void 0;
35918
+ return index.normalized.get(upsertNormalizedKey(keyParts, index.numericKey));
35919
+ }
35920
+ async function resolveUpsertTargets(appId, keyFields, rowKeyValues, client, options, fieldTypes) {
35921
+ const maxRecords2 = options.maxRecords ?? 1e4;
35922
+ const parallel = options.fetchParallel ?? 1;
35923
+ const numericKey = keyFields.map((f) => fieldTypes.get(f) === "NUMBER");
35924
+ const index = { raw: /* @__PURE__ */ new Map(), normalized: /* @__PURE__ */ new Map(), numericKey };
35925
+ const setMax = (map2, key, id) => {
35926
+ const cur = map2.get(key);
35927
+ if (cur === void 0 || id > cur) map2.set(key, id);
35928
+ };
35929
+ const addRecordToIndex = (parts, id) => {
35930
+ setMax(index.raw, upsertCompositeKey(parts), id);
35931
+ if (numericKey.some(Boolean)) {
35932
+ setMax(index.normalized, upsertNormalizedKey(parts, numericKey), id);
35933
+ }
35934
+ };
35935
+ const batchFirstKeys = /* @__PURE__ */ new Set();
35936
+ const perRowKeys = [];
35937
+ const seen = /* @__PURE__ */ new Set();
35938
+ for (const parts of rowKeyValues) {
35939
+ const composite = upsertCompositeKey(parts);
35940
+ if (seen.has(composite)) continue;
35941
+ seen.add(composite);
35942
+ if (parts.some((p) => p === "")) perRowKeys.push(parts);
35943
+ else batchFirstKeys.add(parts[0]);
35944
+ }
35945
+ const fields = ["$id", ...keyFields];
35946
+ for (const chunk2 of splitChunks([...batchFirstKeys], UPSERT_IN_CHUNK_SIZE)) {
35947
+ const query = `${keyFields[0]} in (${chunk2.map(sqlQuote).join(",")})`;
35948
+ const records = await fetchAll(client.getRecords, appId, query, fields, { maxRecords: maxRecords2, parallel });
35949
+ for (const rec of records) {
35950
+ const id = Number(rec["$id"]?.value);
35951
+ if (!Number.isFinite(id)) continue;
35952
+ addRecordToIndex(keyFields.map((f) => toScalarText(rec[f]?.value)), id);
35953
+ }
35954
+ }
35955
+ for (const parts of perRowKeys) {
35956
+ const query = keyFields.map((f, i) => `${f} = ${sqlQuote(parts[i])}`).join(" and ");
35957
+ const existing = await fetchAll(client.getRecords, appId, query, ["$id"], { maxRecords: maxRecords2, parallel });
35958
+ if (existing.length === 0) continue;
35959
+ addRecordToIndex(parts, maxRecordId(existing));
35960
+ }
35961
+ return index;
35962
+ }
35963
+ function maxRecordId(records) {
35964
+ let max = Number.NEGATIVE_INFINITY;
35965
+ for (const r of records) {
35966
+ const n = Number(r["$id"]?.value);
35967
+ if (Number.isFinite(n) && n > max) max = n;
35968
+ }
35969
+ if (!Number.isFinite(max)) {
35970
+ throw new Error("\u30EC\u30B3\u30FC\u30C9\u306B\u6570\u5024\u306E $id \u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093\u3002");
35971
+ }
35972
+ return max;
35973
+ }
35293
35974
  function toScalarText(value) {
35294
35975
  if (typeof value === "string") return value;
35295
35976
  if (value === null || value === void 0) return "";
@@ -35427,6 +36108,16 @@ async function getSortKindMapByApp(appId, client, cacheContext) {
35427
36108
  setScopedCacheValue(sortKindCache, cacheContext, appId, map2);
35428
36109
  return map2;
35429
36110
  }
36111
+ async function buildOrderByMetaForSelect(stmt, client, cacheContext) {
36112
+ if (stmt.orderBy.length === 0) {
36113
+ return { optionOrders: /* @__PURE__ */ new Map(), sortKinds: /* @__PURE__ */ new Map() };
36114
+ }
36115
+ const [optionOrders, sortKinds] = await Promise.all([
36116
+ buildOptionOrdersForSelect(stmt, client, cacheContext),
36117
+ buildSortKindsForSelect(stmt, client, cacheContext)
36118
+ ]);
36119
+ return { optionOrders, sortKinds };
36120
+ }
35430
36121
  async function buildOptionOrdersForSelect(stmt, client, cacheContext) {
35431
36122
  const optionOrders = /* @__PURE__ */ new Map();
35432
36123
  const tables = [stmt.from, ...stmt.joins.map((j) => j.table)];
@@ -35500,9 +36191,9 @@ function convertProcessRowValue(raw, dstFieldType) {
35500
36191
  }
35501
36192
  return raw;
35502
36193
  }
35503
- async function executeInsert(stmt, client, cacheContext) {
36194
+ async function executeInsert(stmt, client, options, cacheContext) {
35504
36195
  if (stmt.subtableCode) {
35505
- return executeInsertSubtable(stmt, client, cacheContext);
36196
+ return executeInsertSubtable(stmt, client, options, cacheContext);
35506
36197
  }
35507
36198
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
35508
36199
  const batches = insertToPostBatches(stmt, fieldTypes);
@@ -35517,14 +36208,18 @@ async function executeInsert(stmt, client, cacheContext) {
35517
36208
  insertedCount: createdIds.flat().length
35518
36209
  };
35519
36210
  }
35520
- async function executeInsertSelect(stmt, client, options, cacheContext) {
35521
- 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);
35522
36213
  const { rows, columns } = selectResult;
35523
36214
  if (columns.length !== stmt.fields.length) {
35524
36215
  throw new Error(
35525
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`
35526
36217
  );
35527
36218
  }
36219
+ if (options.confirm) {
36220
+ const ok = await options.confirm(rows.length, "INSERT");
36221
+ if (!ok) throw new OperationCancelledError("INSERT", rows.length);
36222
+ }
35528
36223
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
35529
36224
  const allRecords = rows.map((row) => {
35530
36225
  const record2 = {};
@@ -35617,26 +36312,19 @@ async function executeDelete(stmt, client, options, cacheContext) {
35617
36312
  return { type: "DELETE", deletedCount: ids.length };
35618
36313
  }
35619
36314
  async function executeUpsert(stmt, client, options, cacheContext) {
35620
- const maxRecords2 = options.maxRecords ?? 1e4;
35621
36315
  const toInsert = [];
35622
36316
  const toUpdate = [];
35623
36317
  const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
35624
- for (const row of stmt.values) {
35625
- const keyConditions = stmt.keyFields.map((key) => {
36318
+ const rowKeyValues = stmt.values.map(
36319
+ (row) => stmt.keyFields.map((key) => {
35626
36320
  const idx = stmt.fields.indexOf(key);
35627
36321
  if (idx === -1) throw new Error(`ON DUPLICATE \u306E\u30AD\u30FC\u300C${key}\u300D\u304C INSERT \u30D5\u30A3\u30FC\u30EB\u30C9\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093`);
35628
36322
  const val = row[idx];
35629
- const valStr = val.type === "STRING" ? val.value : val.type === "NUMBER" ? String(val.value) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
35630
- return `${key} = "${valStr.replace(/"/g, '\\"')}"`;
35631
- });
35632
- const query = keyConditions.join(" and ");
35633
- const existing = await fetchAll(
35634
- client.getRecords,
35635
- stmt.appId,
35636
- query,
35637
- ["$id"],
35638
- { maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1 }
35639
- );
36323
+ return val.type === "STRING" ? val.value : val.type === "NUMBER" ? String(val.value) : val.type === "CASE_VALUE" ? evalCaseWhen(val.expr, {}) : val.elements.map((e) => e.value).join(",");
36324
+ })
36325
+ );
36326
+ const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
36327
+ stmt.values.forEach((row, rowIdx) => {
35640
36328
  const record2 = {};
35641
36329
  stmt.fields.forEach((field, i) => {
35642
36330
  const val = row[i];
@@ -35646,13 +36334,13 @@ async function executeUpsert(stmt, client, options, cacheContext) {
35646
36334
  record2[field] = { value: toKintoneValue(val, fieldTypes.get(field)) };
35647
36335
  }
35648
36336
  });
35649
- if (existing.length > 0) {
35650
- const id = Number(existing[0]["$id"].value);
36337
+ const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
36338
+ if (id !== void 0) {
35651
36339
  toUpdate.push({ id, record: record2 });
35652
36340
  } else {
35653
36341
  toInsert.push(record2);
35654
36342
  }
35655
- }
36343
+ });
35656
36344
  if (options.confirm && toInsert.length + toUpdate.length > 0) {
35657
36345
  const total = toInsert.length + toUpdate.length;
35658
36346
  const ok = await options.confirm(total, "UPDATE");
@@ -35672,18 +36360,17 @@ async function executeUpsert(stmt, client, options, cacheContext) {
35672
36360
  updatedCount: toUpdate.length
35673
36361
  };
35674
36362
  }
35675
- async function executeInsertSubtable(stmt, client, _cacheContext) {
36363
+ async function executeInsertSubtable(stmt, client, options, _cacheContext) {
35676
36364
  const subtableCode = stmt.subtableCode;
35677
36365
  const pidIndex = stmt.fields.indexOf("_pid");
35678
36366
  if (pidIndex < 0) {
35679
36367
  throw new Error("\u30B5\u30D6\u30C6\u30FC\u30D6\u30EB INSERT \u306B\u306F _pid \u304C\u5FC5\u9808\u3067\u3059");
35680
36368
  }
35681
- const parents = await fetchAll(client.getRecords, stmt.appId, "", [], { maxRecords: 1e4, parallel: 1 });
35682
- const parentMap = /* @__PURE__ */ new Map();
35683
- for (const p of parents) {
35684
- const pid = String(p["$id"]?.value ?? "");
35685
- if (pid) parentMap.set(pid, p);
35686
- }
36369
+ const parents = await fetchAll(client.getRecords, stmt.appId, "", [], {
36370
+ maxRecords: options.maxRecords ?? 1e4,
36371
+ parallel: options.fetchParallel ?? 1
36372
+ });
36373
+ const parentMap = buildParentIdMap(parents);
35687
36374
  const insertsByParent = /* @__PURE__ */ new Map();
35688
36375
  for (const rowValues of stmt.values) {
35689
36376
  const pid = valueToString(rowValues[pidIndex]);
@@ -35750,8 +36437,9 @@ async function executeUpdateSubtable(stmt, client, options, _cacheContext) {
35750
36437
  }
35751
36438
  byRid.set(t.rowId, updates);
35752
36439
  }
36440
+ const parentById = buildParentIdMap(parents);
35753
36441
  for (const [pid, updateMap] of updatesByParent.entries()) {
35754
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
36442
+ const parent = parentById.get(pid);
35755
36443
  if (!parent) continue;
35756
36444
  const currentRows = getMutableTableRows(parent, subtableCode);
35757
36445
  const payloadRows = currentRows.map((row) => {
@@ -35791,8 +36479,9 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
35791
36479
  if (bucket) bucket.push(t.rowIndex);
35792
36480
  else byParent.set(t.parentId, [t.rowIndex]);
35793
36481
  }
36482
+ const parentById = buildParentIdMap(parents);
35794
36483
  for (const [pid, idxs] of byParent.entries()) {
35795
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
36484
+ const parent = parentById.get(pid);
35796
36485
  if (!parent) continue;
35797
36486
  const rows = getMutableTableRows(parent, subtableCode);
35798
36487
  const rm = new Set(idxs);
@@ -35801,6 +36490,14 @@ async function executeDeleteSubtable(stmt, client, options, _cacheContext) {
35801
36490
  }
35802
36491
  return { type: "DELETE", deletedCount: targets.length };
35803
36492
  }
36493
+ function buildParentIdMap(parents) {
36494
+ const map2 = /* @__PURE__ */ new Map();
36495
+ for (const p of parents) {
36496
+ const pid = String(p["$id"]?.value ?? "");
36497
+ if (pid) map2.set(pid, p);
36498
+ }
36499
+ return map2;
36500
+ }
35804
36501
  function expandRowsForSubtableDml(parents, subtableCode) {
35805
36502
  const out = [];
35806
36503
  for (const parent of parents) {
@@ -35936,8 +36633,9 @@ async function executeReorder(stmt, client, options, _cacheContext) {
35936
36633
  const ok = await options.confirm(targetParentIds.size, "UPDATE");
35937
36634
  if (!ok) throw new OperationCancelledError("UPDATE", targetParentIds.size);
35938
36635
  }
36636
+ const parentById = buildParentIdMap(parents);
35939
36637
  for (const pid of targetParentIds) {
35940
- const parent = parents.find((p) => String(p["$id"]?.value ?? "") === pid);
36638
+ const parent = parentById.get(pid);
35941
36639
  if (!parent) continue;
35942
36640
  const rows = getMutableTableRows(parent, stmt.subtableCode);
35943
36641
  const sortable = rows.map((row, i) => ({ row, i, flat: buildFlatRowForSort(parent, stmt.subtableCode, row, i) }));
@@ -35985,7 +36683,6 @@ function evalOrderKeyForRow(key, row) {
35985
36683
  }
35986
36684
  }
35987
36685
  async function executeUpsertSelect(stmt, client, options, cacheContext) {
35988
- const maxRecords2 = options.maxRecords ?? 1e4;
35989
36686
  const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
35990
36687
  const { rows, columns } = selectResult;
35991
36688
  if (columns.length !== stmt.fields.length) {
@@ -36000,29 +36697,26 @@ async function executeUpsertSelect(stmt, client, options, cacheContext) {
36000
36697
  }
36001
36698
  const toInsert = [];
36002
36699
  const toUpdate = [];
36003
- for (const row of rows) {
36700
+ const records = rows.map((row) => {
36004
36701
  const record2 = {};
36005
36702
  stmt.fields.forEach((field, i) => {
36006
36703
  record2[field] = { value: row[columns[i]] ?? "" };
36007
36704
  });
36008
- const keyConditions = stmt.keyFields.map((key) => {
36009
- const val = String(record2[key]?.value ?? "");
36010
- return `${key} = "${val.replace(/"/g, '\\"')}"`;
36011
- });
36012
- const query = keyConditions.join(" and ");
36013
- const existing = await fetchAll(
36014
- client.getRecords,
36015
- stmt.appId,
36016
- query,
36017
- ["$id"],
36018
- { maxRecords: maxRecords2, parallel: options.fetchParallel ?? 1 }
36019
- );
36020
- if (existing.length > 0) {
36021
- toUpdate.push({ id: Number(existing[0]["$id"].value), record: record2 });
36705
+ return record2;
36706
+ });
36707
+ const fieldTypes = await getFieldTypeMap(stmt.appId, client, cacheContext);
36708
+ const rowKeyValues = records.map(
36709
+ (record2) => stmt.keyFields.map((key) => String(record2[key]?.value ?? ""))
36710
+ );
36711
+ const targetIndex = await resolveUpsertTargets(stmt.appId, stmt.keyFields, rowKeyValues, client, options, fieldTypes);
36712
+ records.forEach((record2, rowIdx) => {
36713
+ const id = lookupUpsertTarget(targetIndex, rowKeyValues[rowIdx]);
36714
+ if (id !== void 0) {
36715
+ toUpdate.push({ id, record: record2 });
36022
36716
  } else {
36023
36717
  toInsert.push(record2);
36024
36718
  }
36025
- }
36719
+ });
36026
36720
  if (options.confirm && toInsert.length + toUpdate.length > 0) {
36027
36721
  const total = toInsert.length + toUpdate.length;
36028
36722
  const ok = await options.confirm(total, "UPDATE");
@@ -36073,37 +36767,51 @@ function parseSql(sql) {
36073
36767
  throw e;
36074
36768
  }
36075
36769
  }
36076
- async function resolveSubqueries(where, client, options, cacheContext) {
36770
+ async function resolveSubqueries(where, client, options, cacheContext, cteCache) {
36771
+ const tasks = [];
36772
+ collectSubqueryTasks(where, client, options, cacheContext, tasks, cteCache);
36773
+ await Promise.all(tasks);
36774
+ }
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) {
36077
36782
  if (where === null) return;
36078
36783
  switch (where.type) {
36079
36784
  case "BINARY": {
36080
36785
  const right = where.right;
36081
36786
  if (right.type === "SUBQUERY_IN_LIST") {
36082
- const result = await executeSelect(right.query, client, options, cacheContext);
36083
- const col = right.column ?? (result.columns[0] ?? "");
36084
- const resolved = new Set(result.rows.map((r) => r[col] ?? ""));
36085
- right.resolved = resolved;
36787
+ tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
36788
+ const col = right.column ?? (result.columns[0] ?? "");
36789
+ right.resolved = new Set(result.rows.map((r) => r[col] ?? ""));
36790
+ }));
36086
36791
  }
36087
36792
  if (right.type === "SCALAR_SUBQUERY") {
36088
- const result = await executeSelect(right.query, client, options, cacheContext);
36089
- 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");
36090
- 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");
36091
- const col = result.columns[0] ?? "";
36092
- right.resolved = result.rows[0]?.[col] ?? "";
36793
+ tasks.push(runSubquery(right.query, client, options, cacheContext, cteCache).then((result) => {
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");
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");
36796
+ const col = result.columns[0] ?? "";
36797
+ right.resolved = result.rows[0]?.[col] ?? "";
36798
+ }));
36093
36799
  }
36094
36800
  break;
36095
36801
  }
36096
36802
  case "LOGICAL":
36097
- await resolveSubqueries(where.left, client, options, cacheContext);
36098
- await resolveSubqueries(where.right, client, options, cacheContext);
36803
+ collectSubqueryTasks(where.left, client, options, cacheContext, tasks, cteCache);
36804
+ collectSubqueryTasks(where.right, client, options, cacheContext, tasks, cteCache);
36099
36805
  break;
36100
36806
  case "NOT":
36101
36807
  case "GROUP":
36102
- await resolveSubqueries(where.expr, client, options, cacheContext);
36808
+ collectSubqueryTasks(where.expr, client, options, cacheContext, tasks, cteCache);
36103
36809
  break;
36104
36810
  case "EXISTS": {
36105
- const result = await executeSelect(where.query, client, options, cacheContext);
36106
- where.resolved = result.rowCount > 0;
36811
+ const node = where;
36812
+ tasks.push(runSubquery(node.query, client, options, cacheContext, cteCache).then((result) => {
36813
+ node.resolved = result.rowCount > 0;
36814
+ }));
36107
36815
  break;
36108
36816
  }
36109
36817
  }
@@ -36119,19 +36827,83 @@ async function resolveSetSubqueries(assignments, client, options, cacheContext)
36119
36827
  a.value = { type: "STRING", value: resolved };
36120
36828
  }
36121
36829
  }
36122
- async function resolveScalarColumns(columns, client, options, cacheContext) {
36123
- const cache = /* @__PURE__ */ new Map();
36830
+ async function resolveScalarColumns(columns, client, options, cacheContext, cteCache) {
36831
+ const byQuery = /* @__PURE__ */ new Map();
36832
+ const pending = [];
36124
36833
  for (let i = 0; i < columns.length; i++) {
36125
36834
  const col = columns[i];
36126
36835
  if (col.type !== "SCALAR_SUBQUERY_COL") continue;
36127
- const result = await executeSelect(col.query, client, options, cacheContext);
36128
- 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");
36129
- 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");
36130
- const firstCol = result.columns[0] ?? "";
36131
- cache.set(i, result.rows[0]?.[firstCol] ?? "");
36836
+ const key = JSON.stringify(col.query);
36837
+ let promise2 = byQuery.get(key);
36838
+ if (!promise2) {
36839
+ promise2 = runSubquery(col.query, client, options, cacheContext, cteCache).then((result) => {
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");
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");
36842
+ const firstCol = result.columns[0] ?? "";
36843
+ return result.rows[0]?.[firstCol] ?? "";
36844
+ });
36845
+ byQuery.set(key, promise2);
36846
+ }
36847
+ pending.push([i, promise2]);
36132
36848
  }
36849
+ const values = await Promise.all(pending.map(([, promise2]) => promise2));
36850
+ const cache = /* @__PURE__ */ new Map();
36851
+ pending.forEach(([i], idx) => cache.set(i, values[idx]));
36133
36852
  return cache;
36134
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
+ }
36135
36907
  function executeExplain(stmt) {
36136
36908
  const lines = buildExplainPlan(stmt.query);
36137
36909
  return {
@@ -36452,6 +37224,10 @@ function parseSqlStatement(sql) {
36452
37224
  const tokens = new Lexer(sql).tokenize();
36453
37225
  return new Parser(tokens).parse();
36454
37226
  }
37227
+ function parseSqlStatements(sql) {
37228
+ const tokens = new Lexer(sql).tokenize();
37229
+ return new Parser(tokens).parseStatements();
37230
+ }
36455
37231
 
36456
37232
  // src/node/appProfiles.ts
36457
37233
  function parseTokenMap(raw) {
@@ -36637,35 +37413,6 @@ function buildCacheContext(defaultProfile, appBindingByMappedApp) {
36637
37413
  return `apps:${pairs.join(",")}`;
36638
37414
  }
36639
37415
 
36640
- // src/node/dmlGuard.ts
36641
- function getStatementType(stmt) {
36642
- if (!stmt || typeof stmt !== "object") return "UNKNOWN";
36643
- const obj = stmt;
36644
- return typeof obj.type === "string" ? obj.type : "UNKNOWN";
36645
- }
36646
- function isDmlType(type) {
36647
- return type === "INSERT" || type === "INSERT_SELECT" || type === "UPDATE" || type === "DELETE" || type === "UPSERT" || type === "UPSERT_SELECT" || type === "REORDER";
36648
- }
36649
- function isReadOnlyType(type) {
36650
- return type === "SELECT" || type === "UNION" || type === "WITH" || type === "EXPLAIN" || type === "SHOW_APPS" || type === "DESCRIBE";
36651
- }
36652
- function hasWhereClause(stmt) {
36653
- if (!stmt || typeof stmt !== "object") return false;
36654
- const obj = stmt;
36655
- return obj.where !== null && obj.where !== void 0;
36656
- }
36657
- function isNoFromSelectStatement(stmt) {
36658
- if (!stmt || typeof stmt !== "object") return false;
36659
- const obj = stmt;
36660
- return obj.type === "SELECT" && obj.from?.appId === 0 && obj.from?.cteName === "__NO_FROM__";
36661
- }
36662
- function getInsertValuesCount(stmt) {
36663
- if (!stmt || typeof stmt !== "object") return null;
36664
- const obj = stmt;
36665
- if (obj.type !== "INSERT") return null;
36666
- return Array.isArray(obj.values) ? obj.values.length : null;
36667
- }
36668
-
36669
37416
  // src/node/config.ts
36670
37417
  var import_fs = require("fs");
36671
37418
  function loadKsqlConfig(configPath) {
@@ -36707,6 +37454,107 @@ function resolveTokenValue(raw) {
36707
37454
  return raw;
36708
37455
  }
36709
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
+
36710
37558
  // src/cli/nodeKintoneClient.ts
36711
37559
  function createNodeKintoneClient(baseUrl, tokenResolver) {
36712
37560
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -36920,6 +37768,10 @@ async function createKsqlRuntime(serverOptions, input) {
36920
37768
  const normalized = normalizeSqlAppProfiles(input.sql, profileName);
36921
37769
  const sql = normalized.normalizedSql;
36922
37770
  const maxRecords2 = input.maxRecords ?? envInt("KSQL_MAX_RECORDS") ?? profile2.query?.maxRecords ?? 500;
37771
+ const fetchParallel2 = input.fetchParallel ?? envInt("KSQL_FETCH_PARALLEL") ?? profile2.query?.fetchParallel ?? 3;
37772
+ if (!Number.isInteger(fetchParallel2) || fetchParallel2 < 1 || fetchParallel2 > 10) {
37773
+ throw new Error("ArgumentError: fetchParallel must be an integer between 1 and 10.");
37774
+ }
36923
37775
  const onLimit2 = input.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile2.query?.onLimit ?? "error";
36924
37776
  const timeout2 = input.timeout ?? envInt("KSQL_TIMEOUT") ?? profile2.query?.timeout ?? 3e4;
36925
37777
  const appIds = extractAppIds(sql);
@@ -37045,12 +37897,17 @@ async function createKsqlRuntime(serverOptions, input) {
37045
37897
  },
37046
37898
  getApps: () => defaultClient.getApps()
37047
37899
  };
37900
+ const gatedClient = withRequestGate(
37901
+ routedClient,
37902
+ getGlobalRequestGate(profile2.query?.maxConcurrent)
37903
+ );
37048
37904
  return {
37049
37905
  sql,
37050
37906
  profileName,
37051
- client: routedClient,
37907
+ client: gatedClient,
37052
37908
  cacheContext: buildCacheContext(profileName, normalized.appBindingByMappedApp),
37053
37909
  maxRecords: maxRecords2,
37910
+ fetchParallel: fetchParallel2,
37054
37911
  onLimit: onLimit2,
37055
37912
  timeout: timeout2
37056
37913
  };
@@ -37236,6 +38093,14 @@ function deleteSavedQuery(catalog, name) {
37236
38093
  }
37237
38094
 
37238
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
+ }
37239
38104
  var DEFAULT_MAX_RECORDS = 500;
37240
38105
  var DEFAULT_ON_LIMIT = "error";
37241
38106
  function noOpClient() {
@@ -37291,6 +38156,59 @@ function toSelectPayload(result) {
37291
38156
  warnings: result.warnings ?? []
37292
38157
  };
37293
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
+ }
37294
38212
  function toMutationPayload(result) {
37295
38213
  if (result.type === "INSERT") {
37296
38214
  return {
@@ -37375,35 +38293,71 @@ async function runSafely(fn) {
37375
38293
  function createKsqlMcpTools(serverOptions, deps = {}) {
37376
38294
  const createRuntime = deps.createRuntime ?? createKsqlRuntime;
37377
38295
  const executeSql = deps.executeSql ?? execute;
38296
+ const executeBatchSql = deps.executeBatchSql ?? executeBatch;
37378
38297
  async function validate(input) {
37379
38298
  const normalized = normalizeSqlForTool(serverOptions, input.sql, input.profile);
37380
- const stmt = parseSqlStatement(normalized.normalizedSql);
37381
- const statementType = getStatementType(stmt);
37382
- const isDml = isDmlType(statementType);
37383
- const isReadOnly = isReadOnlyType(statementType);
38299
+ const statements = parseSqlStatements(normalized.normalizedSql);
38300
+ const analysis = analyzeBatch(statements);
37384
38301
  const appBindings = [...normalized.appBindingByMappedApp.entries()].map(([mappedAppId, binding]) => ({
37385
38302
  mappedAppId,
37386
38303
  appId: binding.appId,
37387
38304
  profile: binding.profile
37388
38305
  }));
37389
- 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 = {
37390
38321
  ok: true,
37391
- statementType,
37392
- isDml,
37393
- isReadOnly,
37394
- hasWhere: hasWhereClause(stmt),
37395
- insertValuesCount: getInsertValuesCount(stmt),
37396
- appIds: extractAppIds(normalized.normalizedSql),
37397
- canRunWithQueryTool: isReadOnly,
37398
- 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,
37399
38329
  normalizedSql: normalized.normalizedSql,
37400
38330
  hasProfileSyntax: normalized.hasProfileSyntax,
37401
38331
  cacheContext: normalized.cacheContext,
37402
38332
  appBindings
37403
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
+ };
37404
38348
  }
37405
38349
  async function explain(input) {
37406
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
+ }
37407
38361
  const result = await executeSql(explainSql(normalized.normalizedSql), noOpClient(), {
37408
38362
  cacheContext: normalized.cacheContext
37409
38363
  });
@@ -37414,6 +38368,31 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37414
38368
  }
37415
38369
  async function query(input) {
37416
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
+ }
37417
38396
  if (!validation.isReadOnly) {
37418
38397
  throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_query. Use ksql_mutate.`);
37419
38398
  }
@@ -37434,11 +38413,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37434
38413
  sql: input.sql,
37435
38414
  profile: input.profile,
37436
38415
  maxRecords: input.maxRecords,
38416
+ fetchParallel: input.fetchParallel,
37437
38417
  onLimit: input.onLimit,
37438
38418
  timeout: input.timeout
37439
38419
  });
37440
38420
  const result = await executeSql(runtime.sql, runtime.client, {
37441
38421
  maxRecords: runtime.maxRecords,
38422
+ fetchParallel: runtime.fetchParallel,
37442
38423
  onLimitReached: runtime.onLimit,
37443
38424
  cacheContext: runtime.cacheContext
37444
38425
  });
@@ -37447,9 +38428,75 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37447
38428
  }
37448
38429
  return toSelectPayload(result);
37449
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
+ }
37450
38494
  async function mutate(input) {
37451
38495
  const dmlMaxRows2 = requireDmlApproval(input, "ksql_mutate");
37452
38496
  const validation = await validate(input);
38497
+ if (validation.batch) {
38498
+ return mutateBatch(input, validation, dmlMaxRows2);
38499
+ }
37453
38500
  if (!validation.isDml) {
37454
38501
  throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_mutate. Use ksql_query.`);
37455
38502
  }
@@ -37466,11 +38513,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37466
38513
  sql: input.sql,
37467
38514
  profile: input.profile,
37468
38515
  maxRecords: dmlMaxRows2 + 1,
38516
+ fetchParallel: input.fetchParallel,
37469
38517
  onLimit: DEFAULT_ON_LIMIT,
37470
38518
  timeout: input.timeout
37471
38519
  });
37472
38520
  const result = await executeSql(runtime.sql, runtime.client, {
37473
38521
  maxRecords: runtime.maxRecords,
38522
+ fetchParallel: runtime.fetchParallel,
37474
38523
  onLimitReached: runtime.onLimit,
37475
38524
  cacheContext: runtime.cacheContext,
37476
38525
  confirm: async (count, operation) => {
@@ -37490,6 +38539,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37490
38539
  sql: `DESCRIBE APP${input.app}`,
37491
38540
  profile: input.profile,
37492
38541
  maxRecords: input.maxRecords,
38542
+ fetchParallel: input.fetchParallel,
37493
38543
  onLimit: input.onLimit,
37494
38544
  timeout: input.timeout
37495
38545
  });
@@ -37499,15 +38549,19 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37499
38549
  sql: "SHOW APPS",
37500
38550
  profile: input.profile,
37501
38551
  maxRecords: input.maxRecords,
38552
+ fetchParallel: input.fetchParallel,
37502
38553
  onLimit: input.onLimit,
37503
38554
  timeout: input.timeout
37504
38555
  });
37505
38556
  }
37506
38557
  async function saveQuery(input) {
37507
- const validation = await validate({
37508
- sql: input.sql,
37509
- profile: input.defaultProfile
37510
- });
38558
+ const validation = requireSingleStatement(
38559
+ await validate({
38560
+ sql: input.sql,
38561
+ profile: input.defaultProfile
38562
+ }),
38563
+ "ksql_save_query"
38564
+ );
37511
38565
  assertSavedQuerySafety(input, {
37512
38566
  isDml: validation.isDml,
37513
38567
  statementType: validation.statementType
@@ -37551,10 +38605,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37551
38605
  const saved = getSavedQuery(catalog, input.name);
37552
38606
  assertProfileOverrideAllowed(saved, input.profile);
37553
38607
  const profile2 = input.profile ?? saved.defaultProfile;
37554
- const validation = await validate({
37555
- sql: saved.sql,
37556
- profile: profile2
37557
- });
38608
+ const validation = requireSingleStatement(
38609
+ await validate({
38610
+ sql: saved.sql,
38611
+ profile: profile2
38612
+ }),
38613
+ "ksql_run_saved_query"
38614
+ );
37558
38615
  assertSavedQuerySafety(saved, {
37559
38616
  isDml: validation.isDml,
37560
38617
  statementType: validation.statementType
@@ -37564,6 +38621,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37564
38621
  sql: saved.sql,
37565
38622
  profile: profile2,
37566
38623
  maxRecords: input.maxRecords,
38624
+ fetchParallel: input.fetchParallel,
37567
38625
  onLimit: input.onLimit,
37568
38626
  timeout: input.timeout
37569
38627
  });
@@ -37580,6 +38638,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37580
38638
  allowDml: true,
37581
38639
  confirmText: "yes",
37582
38640
  dmlMaxRows: dmlMaxRows2,
38641
+ fetchParallel: input.fetchParallel,
37583
38642
  timeout: input.timeout
37584
38643
  });
37585
38644
  return {
@@ -37628,6 +38687,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
37628
38687
  // src/mcp/schemas.ts
37629
38688
  var profile = external_exports.string().min(1).optional();
37630
38689
  var maxRecords = external_exports.number().int().positive().optional();
38690
+ var fetchParallel = external_exports.number().int().min(1).max(10).optional();
37631
38691
  var onLimit = external_exports.enum(["error", "truncate"]).optional();
37632
38692
  var timeout = external_exports.number().int().positive().optional();
37633
38693
  var dmlMaxRows = external_exports.number().int().positive();
@@ -37645,8 +38705,13 @@ var queryInputSchema = external_exports.object({
37645
38705
  sql: external_exports.string().min(1),
37646
38706
  profile,
37647
38707
  maxRecords,
38708
+ fetchParallel,
37648
38709
  onLimit,
37649
- timeout
38710
+ timeout,
38711
+ /** バッチ(複文)専用: 実行時エラー後も後続文を実行する(既定 false = fail-fast) */
38712
+ continueOnError: external_exports.boolean().optional(),
38713
+ /** バッチ(複文)専用: 返却する結果セットの合計行数上限(既定なし) */
38714
+ maxTotalRecords: external_exports.number().int().positive().optional()
37650
38715
  });
37651
38716
  var mutateInputSchema = external_exports.object({
37652
38717
  sql: external_exports.string().min(1),
@@ -37654,18 +38719,24 @@ var mutateInputSchema = external_exports.object({
37654
38719
  allowDml: external_exports.literal(true),
37655
38720
  confirmText: external_exports.literal("yes"),
37656
38721
  dmlMaxRows,
37657
- timeout
38722
+ fetchParallel,
38723
+ timeout,
38724
+ /** バッチ(複文)専用: バッチ合計の影響行数上限(既定なし = 文ごとの dmlMaxRows のみ)。
38725
+ * なお DML バッチに continueOnError は存在しない(常に fail-fast) */
38726
+ dmlTotalMaxRows: dmlMaxRows.optional()
37658
38727
  });
37659
38728
  var describeAppInputSchema = external_exports.object({
37660
38729
  app: external_exports.number().int().positive(),
37661
38730
  profile,
37662
38731
  maxRecords,
38732
+ fetchParallel,
37663
38733
  onLimit,
37664
38734
  timeout
37665
38735
  });
37666
38736
  var showAppsInputSchema = external_exports.object({
37667
38737
  profile,
37668
38738
  maxRecords,
38739
+ fetchParallel,
37669
38740
  onLimit,
37670
38741
  timeout
37671
38742
  });
@@ -37687,6 +38758,7 @@ var runSavedQueryInputSchema = external_exports.object({
37687
38758
  name: savedQueryName,
37688
38759
  profile,
37689
38760
  maxRecords,
38761
+ fetchParallel,
37690
38762
  onLimit,
37691
38763
  timeout,
37692
38764
  allowDml: external_exports.literal(true).optional(),