@rex0220/kintone-sql-tools 3.0.0 → 3.1.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.
@@ -36543,7 +36543,7 @@ var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
36543
36543
  "CREATOR",
36544
36544
  "MODIFIER"
36545
36545
  ]);
36546
- function planKorderNative(input) {
36546
+ function planKorder(input) {
36547
36547
  const { stmt } = input;
36548
36548
  const reasons = [];
36549
36549
  if (stmt.orderMode !== "KINTONE_NATIVE") reasons.push("KORDER_MODE_REQUIRED");
@@ -36573,29 +36573,120 @@ function planKorderNative(input) {
36573
36573
  reasons.push(`KORDER_TYPE_UNSUPPORTED(field=${name}, type=${semantics.fieldType})`);
36574
36574
  }
36575
36575
  }
36576
- if (stmt.limit === null || stmt.limit < 0 || stmt.limit > 500) {
36576
+ if (stmt.limit === null || !Number.isSafeInteger(stmt.limit) || stmt.limit < 0) {
36577
36577
  reasons.push(`KORDER_LIMIT_INVALID(limit=${String(stmt.limit)})`);
36578
36578
  }
36579
- if (stmt.limit !== null && stmt.limit > input.maxRecords) {
36580
- reasons.push(`KORDER_LIMIT_EXCEEDS_MAX_RECORDS(limit=${stmt.limit}, maxRecords=${input.maxRecords})`);
36581
- }
36582
36579
  const offset = stmt.offset ?? 0;
36583
- if (offset < 0 || offset > 1e4) reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
36580
+ if (!Number.isSafeInteger(offset) || offset < 0) {
36581
+ reasons.push(`KORDER_OFFSET_INVALID(offset=${offset})`);
36582
+ }
36583
+ const scanRows = stmt.limit === null ? Number.NaN : offset + stmt.limit;
36584
+ if (stmt.limit !== null && !Number.isSafeInteger(scanRows)) {
36585
+ reasons.push(`KORDER_SCAN_ROWS_INVALID(offset=${offset}, limit=${stmt.limit})`);
36586
+ }
36584
36587
  const unique = [...new Set(reasons)];
36585
36588
  if (unique.length > 0) {
36586
36589
  throw new Error(
36587
36590
  `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; ${unique.join(", ")}). Use ORDER BY for canonical local ordering or simplify the query.`
36588
36591
  );
36589
36592
  }
36593
+ const native = stmt.limit <= 500 && offset <= 1e4 && stmt.limit <= input.maxRecords;
36594
+ if (!native && scanRows > input.maxRecords) {
36595
+ throw new Error(
36596
+ `ArgumentError: KORDER BY cannot be executed (mode=KINTONE_NATIVE; KORDER_SCAN_ROWS_EXCEEDS_MAX_RECORDS(scanRows=${scanRows}, maxRecords=${input.maxRecords})). Use ORDER BY for canonical local ordering, raise maxRecords, or reduce LIMIT/OFFSET.`
36597
+ );
36598
+ }
36590
36599
  return {
36591
- kind: "KORDER_NATIVE",
36600
+ kind: native ? "KORDER_NATIVE" : "KORDER_CURSOR",
36592
36601
  requiresCompleteInput: false,
36593
36602
  localOrderBy: false,
36594
36603
  applyLocalOffsetLimit: false,
36595
- reasonCodes: []
36604
+ reasonCodes: [],
36605
+ scanRows
36596
36606
  };
36597
36607
  }
36598
36608
 
36609
+ // src/core/errors/cursorErrors.ts
36610
+ var CursorCapacityError = class extends Error {
36611
+ constructor(host, limit, waitMs) {
36612
+ super(`CursorCapacityError: host=${host} \u306E active cursor \u4E0A\u9650 ${limit} \u306B ${waitMs}ms \u4EE5\u5185\u3067\u7A7A\u304D\u304C\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`);
36613
+ this.name = "CursorCapacityError";
36614
+ }
36615
+ };
36616
+ var CursorCreateOutcomeUnknownError = class extends Error {
36617
+ constructor(cause) {
36618
+ super("CursorCreateOutcomeUnknownError: Create Cursor \u306E\u6210\u5426\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u81EA\u52D5\u518D\u8A66\u884C\u305B\u305A\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002");
36619
+ this.name = "CursorCreateOutcomeUnknownError";
36620
+ this.cause = cause;
36621
+ }
36622
+ };
36623
+ var CursorCleanupWarning = class extends Error {
36624
+ constructor(cause) {
36625
+ const detail = cause instanceof Error ? cause.message : String(cause);
36626
+ super(`CursorCleanupWarning: Cursor \u306E\u89E3\u653E\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u7D50\u679C\u306F\u6709\u52B9\u3067\u3059\u304C\u3001\u6700\u592710\u5206+\u5B89\u5168\u4F59\u88D5\u306E\u9593\u306F\u67A0\u3092\u9694\u96E2\u3057\u307E\u3059\u3002\u8A73\u7D30: ${detail}`);
36627
+ this.name = "CursorCleanupWarning";
36628
+ this.cause = cause;
36629
+ }
36630
+ };
36631
+
36632
+ // src/core/optimization/korderCursorExecutor.ts
36633
+ async function executeKorderCursor(input) {
36634
+ const handle = await input.client.openCursor({
36635
+ app: input.app,
36636
+ fields: input.fields.length > 0 ? input.fields : void 0,
36637
+ query: input.query,
36638
+ size: 500
36639
+ });
36640
+ const records = [];
36641
+ let seen = 0;
36642
+ let primaryError;
36643
+ let cleanupWarning;
36644
+ try {
36645
+ if (handle.totalCount > input.offset) {
36646
+ while (records.length < input.limit) {
36647
+ const page = await handle.nextPage();
36648
+ for (const record2 of page.records) {
36649
+ if (seen < input.offset) seen += 1;
36650
+ else if (records.length < input.limit) records.push(record2);
36651
+ else break;
36652
+ }
36653
+ if (!page.next) break;
36654
+ }
36655
+ }
36656
+ } catch (error51) {
36657
+ primaryError = error51;
36658
+ throw error51;
36659
+ } finally {
36660
+ try {
36661
+ await handle.close();
36662
+ } catch (cleanupError) {
36663
+ if (primaryError && primaryError instanceof Error) {
36664
+ Object.defineProperty(primaryError, "cursorCleanupError", {
36665
+ value: cleanupError,
36666
+ configurable: true
36667
+ });
36668
+ } else {
36669
+ cleanupWarning = new CursorCleanupWarning(cleanupError).message;
36670
+ }
36671
+ }
36672
+ }
36673
+ return { records, cleanupWarning };
36674
+ }
36675
+
36676
+ // src/converter/korderCursorQuery.ts
36677
+ function buildKorderCursorQuery(stmt) {
36678
+ const parts = [];
36679
+ if (stmt.where) parts.push(whereToKintone(stmt.where));
36680
+ const order = stmt.orderBy.map((item) => {
36681
+ if (item.key.type !== "FIELD_NAME") {
36682
+ throw new Error("ArgumentError: KORDER cursor key must be a direct field.");
36683
+ }
36684
+ return `${item.key.name} ${item.direction === "ASC" ? "asc" : "desc"}`;
36685
+ });
36686
+ parts.push(`order by ${order.join(", ")}`);
36687
+ return parts.join(" ");
36688
+ }
36689
+
36599
36690
  // src/engine/process.ts
36600
36691
  function flatten(record2, alias) {
36601
36692
  const row = {};
@@ -37736,6 +37827,15 @@ function createEmptyMetrics() {
37736
37827
  fieldCalls: 0,
37737
37828
  appsCalls: 0,
37738
37829
  processStatusCalls: 0,
37830
+ cursorCreateCalls: 0,
37831
+ cursorGetCalls: 0,
37832
+ cursorDeleteCalls: 0,
37833
+ cursorRecordsScanned: 0,
37834
+ cursorActiveCurrent: 0,
37835
+ cursorActivePeak: 0,
37836
+ cursorCleanupFailures: 0,
37837
+ cursorCreateOutcomeUnknown: 0,
37838
+ cursorQuarantinedCurrent: 0,
37739
37839
  fetchedRows: 0,
37740
37840
  elapsedMs: 0
37741
37841
  };
@@ -37748,6 +37848,48 @@ function wrapClientWithMetrics(client, metrics) {
37748
37848
  metrics.fetchedRows += res.records.length;
37749
37849
  return res;
37750
37850
  },
37851
+ openCursor: async (params) => {
37852
+ metrics.cursorCreateCalls += 1;
37853
+ let handle;
37854
+ try {
37855
+ handle = await client.openCursor(params);
37856
+ } catch (error51) {
37857
+ if (error51 instanceof Error && error51.name === "CursorCreateOutcomeUnknownError") {
37858
+ metrics.cursorCreateOutcomeUnknown += 1;
37859
+ metrics.cursorQuarantinedCurrent += 1;
37860
+ }
37861
+ throw error51;
37862
+ }
37863
+ metrics.cursorActiveCurrent += 1;
37864
+ metrics.cursorActivePeak = Math.max(metrics.cursorActivePeak, metrics.cursorActiveCurrent);
37865
+ let released = false;
37866
+ const markReleased = () => {
37867
+ if (released) return;
37868
+ released = true;
37869
+ metrics.cursorActiveCurrent -= 1;
37870
+ };
37871
+ return {
37872
+ totalCount: handle.totalCount,
37873
+ nextPage: async () => {
37874
+ metrics.cursorGetCalls += 1;
37875
+ const page = await handle.nextPage();
37876
+ metrics.cursorRecordsScanned += page.records.length;
37877
+ if (!page.next) markReleased();
37878
+ return page;
37879
+ },
37880
+ close: async () => {
37881
+ if (!released) metrics.cursorDeleteCalls += 1;
37882
+ try {
37883
+ await handle.close();
37884
+ markReleased();
37885
+ } catch (error51) {
37886
+ metrics.cursorCleanupFailures += 1;
37887
+ metrics.cursorQuarantinedCurrent += 1;
37888
+ throw error51;
37889
+ }
37890
+ }
37891
+ };
37892
+ },
37751
37893
  postRecords: (params) => {
37752
37894
  metrics.postCalls += 1;
37753
37895
  return client.postRecords(params);
@@ -37787,6 +37929,37 @@ function wrapClientWithSearchAbort(client, collector, failClosed) {
37787
37929
  }
37788
37930
  };
37789
37931
  }
37932
+ function wrapClientWithCursorScope(client) {
37933
+ const active = /* @__PURE__ */ new Set();
37934
+ return {
37935
+ client: {
37936
+ ...client,
37937
+ openCursor: async (params) => {
37938
+ const handle = await client.openCursor(params);
37939
+ active.add(handle);
37940
+ const remove = () => active.delete(handle);
37941
+ return {
37942
+ totalCount: handle.totalCount,
37943
+ async nextPage() {
37944
+ const page = await handle.nextPage();
37945
+ if (!page.next) remove();
37946
+ return page;
37947
+ },
37948
+ async close() {
37949
+ try {
37950
+ await handle.close();
37951
+ } finally {
37952
+ remove();
37953
+ }
37954
+ }
37955
+ };
37956
+ }
37957
+ },
37958
+ closeActive: async () => {
37959
+ await Promise.all([...active].map((handle) => handle.close().catch(() => void 0)));
37960
+ }
37961
+ };
37962
+ }
37790
37963
  function isSelectLikeStatement(stmt) {
37791
37964
  return stmt.type === "SELECT" || stmt.type === "UNION" || stmt.type === "WITH";
37792
37965
  }
@@ -37837,7 +38010,13 @@ async function executeParsedStatement(stmt, client, options, cacheContext) {
37837
38010
  case "DESCRIBE":
37838
38011
  return executeDescribe(stmt, client, cacheContext);
37839
38012
  case "EXPLAIN":
37840
- return executeExplain(stmt, client, cacheContext, options.maxRecords ?? 1e4);
38013
+ return executeExplain(
38014
+ stmt,
38015
+ client,
38016
+ cacheContext,
38017
+ options.maxRecords ?? 1e4,
38018
+ options.cursorMaxActive ?? 2
38019
+ );
37841
38020
  // 一時テーブルはバッチスコープのため単文実行では拒否する(executeBatch を使う)
37842
38021
  case "CREATE_TEMP_TABLE":
37843
38022
  throw new Error("ArgumentError: CREATE TEMP TABLE requires a batch (temp tables are batch-scoped).");
@@ -37944,9 +38123,11 @@ async function executeBatch(sql, client, options = {}) {
37944
38123
  searchAbortCollector,
37945
38124
  info.statementType !== "SELECT" && info.statementType !== "UNION" && info.statementType !== "WITH"
37946
38125
  );
38126
+ const cursorScope = wrapClientWithCursorScope(statementClient);
37947
38127
  const outcome = await runWithDeadline(
37948
- executeBatchStatement(statements[i], info, statementClient, stmtOptions, cacheContext, tempTables, variables),
37949
- remaining
38128
+ executeBatchStatement(statements[i], info, cursorScope.client, stmtOptions, cacheContext, tempTables, variables),
38129
+ remaining,
38130
+ cursorScope.closeActive
37950
38131
  );
37951
38132
  if (outcome.result) {
37952
38133
  outcome.result = attachSearchAbortWarning(outcome.result, searchAbortCollector);
@@ -38104,19 +38285,47 @@ async function runSelectLike(query, client, options, cacheContext, tempTables) {
38104
38285
  }
38105
38286
  return executeQueryWithCte(query, client, options, tempTables, cacheContext, true);
38106
38287
  }
38107
- async function runWithDeadline(work, remainingMs) {
38288
+ async function runWithDeadline(work, remainingMs, onTimeout) {
38108
38289
  if (remainingMs === null) return work;
38109
38290
  if (remainingMs <= 0) {
38291
+ if (onTimeout) await onTimeout();
38110
38292
  void work.catch(() => {
38111
38293
  });
38112
38294
  throw new BatchTimeoutError();
38113
38295
  }
38114
38296
  let timer;
38297
+ let timedOut = false;
38298
+ const guardedWork = work.then(
38299
+ (value) => timedOut ? new Promise(() => void 0) : value,
38300
+ (error51) => {
38301
+ if (timedOut) return new Promise(() => void 0);
38302
+ throw error51;
38303
+ }
38304
+ );
38115
38305
  try {
38116
38306
  return await Promise.race([
38117
- work,
38307
+ guardedWork,
38118
38308
  new Promise((_, reject) => {
38119
- timer = setTimeout(() => reject(new BatchTimeoutError()), remainingMs);
38309
+ timer = setTimeout(() => {
38310
+ timedOut = true;
38311
+ void (async () => {
38312
+ if (onTimeout) {
38313
+ let cleanupTimer;
38314
+ try {
38315
+ await Promise.race([
38316
+ onTimeout(),
38317
+ new Promise((resolve2) => {
38318
+ cleanupTimer = setTimeout(resolve2, 5e3);
38319
+ cleanupTimer.unref?.();
38320
+ })
38321
+ ]);
38322
+ } finally {
38323
+ if (cleanupTimer) clearTimeout(cleanupTimer);
38324
+ }
38325
+ }
38326
+ reject(new BatchTimeoutError());
38327
+ })();
38328
+ }, remainingMs);
38120
38329
  })
38121
38330
  ]);
38122
38331
  } catch (e) {
@@ -38476,7 +38685,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
38476
38685
  const staticMode = resolveSelectMode(stmt);
38477
38686
  const mode = whereCapability.capability === "EXACT_PUSHDOWN" ? staticMode : "FULL_SCAN";
38478
38687
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
38479
- const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
38688
+ const orderPlan = hasCanonicalOrder(stmt) ? (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
38480
38689
  stmt,
38481
38690
  staticMode: mode,
38482
38691
  whereCapability: whereCapability.capability,
@@ -38490,7 +38699,7 @@ async function executeSelect(stmt, client, options, cacheContext, cteCache, capt
38490
38699
  client,
38491
38700
  cacheContext
38492
38701
  );
38493
- const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
38702
+ const completeInputRequired = orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" ? requiresCompleteInput({ ...stmt, orderBy: [] }) : requiresCompleteInput(stmt);
38494
38703
  const truncateWasDisabled = completeInputRequired && options.onLimitReached === "truncate";
38495
38704
  const effectiveOptions = truncateWasDisabled ? { ...options, onLimitReached: "error" } : options;
38496
38705
  try {
@@ -38591,12 +38800,23 @@ async function executeSimpleSelect(stmt, client, options, cacheContext, orderPla
38591
38800
  const warnings = /* @__PURE__ */ new Set();
38592
38801
  const onLimit2 = options.onLimitReached ?? "error";
38593
38802
  const parallel = options.fetchParallel ?? 1;
38594
- const useRestWindow = stmt.orderBy.length > 0 ? orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" : stmt.limit !== null && stmt.limit <= 500;
38803
+ const useRestWindow = stmt.orderBy.length > 0 ? orderPlan?.kind === "CANONICAL_REST_TOP_N" || orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR" : stmt.limit !== null && stmt.limit <= 500;
38595
38804
  const needed = stmt.limit === null ? null : (stmt.offset ?? 0) + stmt.limit;
38596
38805
  const stopAfter = stmt.orderBy.length === 0 && needed !== null && needed <= maxRecords2 && !whereHasKlike(stmt.where) ? needed : void 0;
38597
38806
  let records;
38598
- if (orderPlan?.kind === "KORDER_NATIVE" && stmt.limit === 0) {
38807
+ if ((orderPlan?.kind === "KORDER_NATIVE" || orderPlan?.kind === "KORDER_CURSOR") && stmt.limit === 0) {
38599
38808
  records = [];
38809
+ } else if (orderPlan?.kind === "KORDER_CURSOR") {
38810
+ const cursorResult = await executeKorderCursor({
38811
+ client,
38812
+ app: params.app,
38813
+ fields: params.fields,
38814
+ query: buildKorderCursorQuery(stmt),
38815
+ offset: stmt.offset ?? 0,
38816
+ limit: stmt.limit
38817
+ });
38818
+ records = cursorResult.records;
38819
+ if (cursorResult.cleanupWarning) warnings.add(cursorResult.cleanupWarning);
38600
38820
  } else if (useRestWindow) {
38601
38821
  const res = await client.getRecords({
38602
38822
  app: params.app,
@@ -39388,7 +39608,7 @@ async function executeFullScanWithCte(stmt, client, options, cteCache, cacheCont
39388
39608
  }
39389
39609
  const orderMeta = await buildOrderByMetaForSelect(stmt, client, cacheContext, cteCache);
39390
39610
  if (hasCanonicalOrder(stmt)) {
39391
- (stmt.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
39611
+ (stmt.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
39392
39612
  stmt,
39393
39613
  staticMode: "FULL_SCAN",
39394
39614
  whereCapability: whereCapability.capability,
@@ -41165,7 +41385,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
41165
41385
  const hasUnmaterializedSource = [select.from, ...select.joins.map((join) => join.table)].some((table) => table.cteName !== null);
41166
41386
  if (hasCanonicalOrder(select) && !hasUnmaterializedSource) {
41167
41387
  const mode = capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(select) : "FULL_SCAN";
41168
- orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
41388
+ orderPlans.set(select, (select.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
41169
41389
  stmt: select,
41170
41390
  staticMode: mode,
41171
41391
  whereCapability: capability.capability,
@@ -41195,7 +41415,7 @@ async function buildExplainWhereAnalysis(query, client, cacheContext, maxRecords
41195
41415
  capabilities.set(inlined, capability);
41196
41416
  if (hasCanonicalOrder(inlined)) {
41197
41417
  const meta3 = await buildOrderByMetaForSelect(inlined, tracedClient, cacheContext);
41198
- orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorderNative : planCanonicalOrder)({
41418
+ orderPlans.set(inlined, (inlined.orderMode === "KINTONE_NATIVE" ? planKorder : planCanonicalOrder)({
41199
41419
  stmt: inlined,
41200
41420
  staticMode: capability.capability === "EXACT_PUSHDOWN" ? resolveSelectMode(inlined) : "FULL_SCAN",
41201
41421
  whereCapability: capability.capability,
@@ -41213,7 +41433,7 @@ function explainMetadataLines(analysis) {
41213
41433
  ...[...analysis.processStatusApps].sort((a, b) => a - b).map((appId) => ` metadata API: process status APP${appId}`)
41214
41434
  ];
41215
41435
  }
41216
- async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4) {
41436
+ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheContext = "batch-explain", maxRecords2 = 1e4, cursorMaxActive2 = 2) {
41217
41437
  const statements = parseSqlBatch(sql);
41218
41438
  const analysis = analyzeBatch(statements);
41219
41439
  validateDeclaredBatchVariables(statements, injectedVariables);
@@ -41224,12 +41444,12 @@ async function buildBatchExplainPlans(sql, client, injectedVariables, cacheConte
41224
41444
  const planStmt = stmt.type === "SET_VARIABLE" ? stmt.expr.type === "SCALAR_SUBQUERY" ? { ...stmt, expr: resolveVariableRefs(stmt.expr, variables) } : stmt : resolveVariableRefs(stmt, variables);
41225
41445
  validateKlikeStatement(planStmt);
41226
41446
  const whereAnalysis = await buildExplainWhereAnalysis(planStmt, client, cacheContext, maxRecords2);
41227
- const statementPlan = buildBatchStatementPlan(
41447
+ const statementPlan = addCursorConcurrency(buildBatchStatementPlan(
41228
41448
  planStmt,
41229
41449
  analysis.statements[i],
41230
41450
  whereAnalysis.capabilities,
41231
41451
  whereAnalysis.orderPlans
41232
- );
41452
+ ), cursorMaxActive2);
41233
41453
  const metadataPlan = explainMetadataLines(whereAnalysis);
41234
41454
  plans.push({
41235
41455
  index: i,
@@ -41335,11 +41555,14 @@ function buildPlanForBatchQuery(query, info, capabilities, orderPlans) {
41335
41555
  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");
41336
41556
  return lines;
41337
41557
  }
41338
- async function executeExplain(stmt, client, cacheContext, maxRecords2) {
41558
+ async function executeExplain(stmt, client, cacheContext, maxRecords2, cursorMaxActive2) {
41339
41559
  const analysis = await buildExplainWhereAnalysis(stmt.query, client, cacheContext, maxRecords2);
41340
41560
  const lines = [
41341
41561
  ...explainMetadataLines(analysis),
41342
- ...buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans)
41562
+ ...addCursorConcurrency(
41563
+ buildExplainPlan(stmt.query, void 0, analysis.capabilities, analysis.orderPlans),
41564
+ cursorMaxActive2
41565
+ )
41343
41566
  ];
41344
41567
  return {
41345
41568
  type: "SELECT",
@@ -41348,6 +41571,17 @@ async function executeExplain(stmt, client, cacheContext, maxRecords2) {
41348
41571
  rowCount: lines.length
41349
41572
  };
41350
41573
  }
41574
+ function addCursorConcurrency(lines, cursorMaxActive2) {
41575
+ const result = [];
41576
+ for (const line of lines) {
41577
+ result.push(line);
41578
+ if (line.trim() === "cursor page size: 500") {
41579
+ const indent = line.match(/^\s*/)?.[0] ?? "";
41580
+ result.push(`${indent}cursor concurrency: ${cursorMaxActive2} per domain (process-local)`);
41581
+ }
41582
+ }
41583
+ return result;
41584
+ }
41351
41585
  function buildExplainPlan(query, label, capabilities, orderPlans) {
41352
41586
  if (query.type === "UNION") return buildUnionPlan(query, capabilities, orderPlans);
41353
41587
  if (query.type === "WITH") return buildWithPlan(query, capabilities, orderPlans);
@@ -41377,6 +41611,11 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
41377
41611
  if (orderPlan.kind === "KORDER_NATIVE") {
41378
41612
  lines.push(" order semantics: kintone native (not kSQL canonical)");
41379
41613
  lines.push(" REST execution: single GET");
41614
+ } else if (orderPlan.kind === "KORDER_CURSOR") {
41615
+ lines.push(" order semantics: kintone native (not kSQL canonical)");
41616
+ lines.push(" fetch API: POST/GET/DELETE records/cursor.json");
41617
+ lines.push(" cursor page size: 500");
41618
+ lines.push(` scan rows: ${orderPlan.scanRows}`);
41380
41619
  }
41381
41620
  }
41382
41621
  if (orderPlan?.requiresCompleteInput ?? requiresCompleteInput(stmt)) {
@@ -41388,7 +41627,8 @@ function buildSelectPlan(stmt, label, capabilities, orderPlans) {
41388
41627
  if (mode === "SIMPLE") {
41389
41628
  const params = selectToKintoneParams(orderPlan?.kind === "CANONICAL_REST_TOP_N" ? withCanonicalRestTie(stmt) : stmt);
41390
41629
  lines.push(` app: APP${stmt.from.appId} (${stmt.from.appId})`);
41391
- lines.push(` kintone query: ${params.query || "(\u306A\u3057)"}`);
41630
+ const displayedQuery = orderPlan?.kind === "KORDER_CURSOR" ? buildKorderCursorQuery(stmt) : params.query;
41631
+ lines.push(` kintone query: ${displayedQuery || "(\u306A\u3057)"}`);
41392
41632
  lines.push(` fields: ${params.fields.length === 0 ? "(\u5168\u30D5\u30A3\u30FC\u30EB\u30C9)" : params.fields.join(", ")}`);
41393
41633
  } else {
41394
41634
  const pushdownPlan = buildKlikePushdownPlan(stmt);
@@ -41931,6 +42171,12 @@ function validateKsqlConfig(config2) {
41931
42171
  }
41932
42172
  const logicalApps = normalizeLogicalApps(profileName, profile2.logicalApps);
41933
42173
  if (logicalApps !== void 0) profile2.logicalApps = logicalApps;
42174
+ if (profile2.query?.cursorMaxActive !== void 0) {
42175
+ const value = profile2.query.cursorMaxActive;
42176
+ if (!Number.isSafeInteger(value) || value < 1 || value > 5) {
42177
+ throw argumentError(`query.cursorMaxActive for profile "${profileName}" must be an integer from 1 to 5.`);
42178
+ }
42179
+ }
41934
42180
  }
41935
42181
  return config2;
41936
42182
  }
@@ -42093,6 +42339,10 @@ var RequestGate = class {
42093
42339
  async runMutation(fn) {
42094
42340
  return this.withSlot(fn);
42095
42341
  }
42342
+ /** Cursor Create/Get/Delete: セマフォのみ。GETでも位置を進めるため再試行しない。 */
42343
+ async runCursorStep(fn) {
42344
+ return this.withSlot(fn);
42345
+ }
42096
42346
  async withSlot(fn) {
42097
42347
  await this.acquire();
42098
42348
  try {
@@ -42124,6 +42374,14 @@ var RequestGate = class {
42124
42374
  function withRequestGate(client, gate) {
42125
42375
  return {
42126
42376
  getRecords: (params) => gate.runReadOnly(() => client.getRecords(params)),
42377
+ openCursor: async (params) => {
42378
+ const handle = await gate.runCursorStep(() => client.openCursor(params));
42379
+ return {
42380
+ totalCount: handle.totalCount,
42381
+ nextPage: () => gate.runCursorStep(() => handle.nextPage()),
42382
+ close: () => gate.runCursorStep(() => handle.close())
42383
+ };
42384
+ },
42127
42385
  getApps: () => gate.runReadOnly(() => client.getApps()),
42128
42386
  getFields: (appId) => gate.runReadOnly(() => client.getFields(appId)),
42129
42387
  getProcessStatuses: (appId) => gate.runReadOnly(() => client.getProcessStatuses(appId)),
@@ -42238,7 +42496,218 @@ function normalizeProcessStatusStates(states) {
42238
42496
  });
42239
42497
  }
42240
42498
 
42499
+ // src/api/kintoneCursor.ts
42500
+ function isAlreadyReleasedCursorError(error51) {
42501
+ const shaped = error51;
42502
+ return shaped?.status === 404 && shaped.code === "GAIA_CN01";
42503
+ }
42504
+ async function deleteCursorWithConfirmation(deleteCursor, sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), isAlreadyReleased = isAlreadyReleasedCursorError) {
42505
+ try {
42506
+ await deleteCursor();
42507
+ return;
42508
+ } catch (firstError) {
42509
+ if (isAlreadyReleased(firstError)) return;
42510
+ }
42511
+ await sleep(250);
42512
+ try {
42513
+ await deleteCursor();
42514
+ } catch (confirmationError) {
42515
+ if (isAlreadyReleased(confirmationError)) return;
42516
+ throw confirmationError;
42517
+ }
42518
+ }
42519
+ async function withTimeout(promise2, timeoutMs) {
42520
+ let timer;
42521
+ const timeout2 = new Promise((_resolve, reject) => {
42522
+ timer = setTimeout(() => reject(new Error(`CursorCleanupTimeoutError: cleanup exceeded ${timeoutMs}ms.`)), timeoutMs);
42523
+ timer.unref?.();
42524
+ });
42525
+ try {
42526
+ return await Promise.race([promise2, timeout2]);
42527
+ } finally {
42528
+ if (timer) clearTimeout(timer);
42529
+ }
42530
+ }
42531
+ function createKintoneCursorHandle(totalCount, operations) {
42532
+ let released = false;
42533
+ let closing = false;
42534
+ let pageTail = Promise.resolve();
42535
+ let closePromise = null;
42536
+ const nextPage = () => {
42537
+ if (closing || released) return Promise.resolve({ records: [], next: false });
42538
+ const result = pageTail.then(async () => {
42539
+ if (closing || released) return { records: [], next: false };
42540
+ const page = await operations.get();
42541
+ if (!page.next) {
42542
+ released = true;
42543
+ operations.onReleased?.();
42544
+ }
42545
+ return page;
42546
+ });
42547
+ pageTail = result.then(() => void 0, () => void 0);
42548
+ return result;
42549
+ };
42550
+ const close = () => {
42551
+ if (released) return Promise.resolve();
42552
+ if (closePromise) return closePromise;
42553
+ closing = true;
42554
+ closePromise = pageTail.then(async () => {
42555
+ if (released) return;
42556
+ try {
42557
+ await withTimeout(
42558
+ deleteCursorWithConfirmation(
42559
+ operations.delete,
42560
+ operations.sleep,
42561
+ operations.isAlreadyReleasedError
42562
+ ),
42563
+ operations.cleanupTimeoutMs ?? 5e3
42564
+ );
42565
+ released = true;
42566
+ operations.onReleased?.();
42567
+ } catch (error51) {
42568
+ operations.onReleaseUnknown?.();
42569
+ throw error51;
42570
+ }
42571
+ });
42572
+ return closePromise;
42573
+ };
42574
+ return { totalCount, nextPage, close };
42575
+ }
42576
+
42577
+ // src/api/cursorLeaseManager.ts
42578
+ var DEFAULT_MAX_ACTIVE = 2;
42579
+ var MAX_ACTIVE = 5;
42580
+ var DEFAULT_WAIT_MS = 3e4;
42581
+ var DEFAULT_QUARANTINE_MS = 10 * 6e4 + 3e4;
42582
+ var CursorLeaseManager = class {
42583
+ constructor(host, options = {}) {
42584
+ this.host = host;
42585
+ this.active = 0;
42586
+ this.peak = 0;
42587
+ this.quarantined = 0;
42588
+ this.waiters = [];
42589
+ this.createTail = Promise.resolve();
42590
+ const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
42591
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
42592
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
42593
+ }
42594
+ this.maxActive = maxActive;
42595
+ this.waitTimeoutMs = options.waitTimeoutMs ?? DEFAULT_WAIT_MS;
42596
+ this.quarantineMs = options.quarantineMs ?? DEFAULT_QUARANTINE_MS;
42597
+ }
42598
+ acquire() {
42599
+ if (this.active < this.maxActive) {
42600
+ this.active += 1;
42601
+ this.peak = Math.max(this.peak, this.active);
42602
+ return Promise.resolve(this.makeLease());
42603
+ }
42604
+ return new Promise((resolve2, reject) => {
42605
+ const waiter = {};
42606
+ waiter.resolve = resolve2;
42607
+ waiter.reject = reject;
42608
+ waiter.timer = setTimeout(() => {
42609
+ const index = this.waiters.indexOf(waiter);
42610
+ if (index >= 0) this.waiters.splice(index, 1);
42611
+ reject(new CursorCapacityError(this.host, this.maxActive, this.waitTimeoutMs));
42612
+ }, this.waitTimeoutMs);
42613
+ waiter.timer.unref?.();
42614
+ this.waiters.push(waiter);
42615
+ });
42616
+ }
42617
+ /**
42618
+ * 同一hostを共有する後続surfaceの設定を反映する。
42619
+ * 縮小時は既存leaseを強制終了せず、activeが新上限を下回るまで新規取得だけを止める。
42620
+ */
42621
+ setMaxActive(maxActive) {
42622
+ this.validateMaxActive(maxActive);
42623
+ if (this.maxActive === maxActive) return;
42624
+ this.maxActive = maxActive;
42625
+ this.dispatchWaiters();
42626
+ }
42627
+ async runCreate(fn) {
42628
+ const previous = this.createTail;
42629
+ let unlock;
42630
+ this.createTail = new Promise((resolve2) => {
42631
+ unlock = resolve2;
42632
+ });
42633
+ await previous;
42634
+ try {
42635
+ return await fn();
42636
+ } finally {
42637
+ unlock();
42638
+ }
42639
+ }
42640
+ snapshot() {
42641
+ return {
42642
+ active: this.active,
42643
+ peak: this.peak,
42644
+ quarantined: this.quarantined,
42645
+ waiting: this.waiters.length,
42646
+ limit: this.maxActive
42647
+ };
42648
+ }
42649
+ makeLease() {
42650
+ let done = false;
42651
+ return {
42652
+ release: () => {
42653
+ if (done) return;
42654
+ done = true;
42655
+ this.returnPermit();
42656
+ },
42657
+ quarantine: (durationMs = this.quarantineMs) => {
42658
+ if (done) return;
42659
+ done = true;
42660
+ this.quarantined += 1;
42661
+ const timer = setTimeout(() => {
42662
+ this.quarantined -= 1;
42663
+ this.returnPermit();
42664
+ }, durationMs);
42665
+ timer.unref?.();
42666
+ }
42667
+ };
42668
+ }
42669
+ returnPermit() {
42670
+ this.active -= 1;
42671
+ this.dispatchWaiters();
42672
+ }
42673
+ dispatchWaiters() {
42674
+ while (this.active < this.maxActive) {
42675
+ const waiter = this.waiters.shift();
42676
+ if (!waiter) return;
42677
+ clearTimeout(waiter.timer);
42678
+ this.active += 1;
42679
+ this.peak = Math.max(this.peak, this.active);
42680
+ waiter.resolve(this.makeLease());
42681
+ }
42682
+ }
42683
+ validateMaxActive(maxActive) {
42684
+ if (!Number.isSafeInteger(maxActive) || maxActive < 1 || maxActive > MAX_ACTIVE) {
42685
+ throw new Error(`ArgumentError: cursorMaxActive must be an integer from 1 to ${MAX_ACTIVE}.`);
42686
+ }
42687
+ }
42688
+ };
42689
+ var managers = /* @__PURE__ */ new Map();
42690
+ function getCursorLeaseManager(host, maxActive = DEFAULT_MAX_ACTIVE) {
42691
+ const key = host.toLowerCase();
42692
+ let manager = managers.get(key);
42693
+ if (!manager) {
42694
+ manager = new CursorLeaseManager(key, { maxActive });
42695
+ managers.set(key, manager);
42696
+ } else {
42697
+ manager.setMaxActive(maxActive);
42698
+ }
42699
+ return manager;
42700
+ }
42701
+
42241
42702
  // src/cli/nodeKintoneClient.ts
42703
+ var KintoneApiError = class extends Error {
42704
+ constructor(status, code, bodyText) {
42705
+ super(`kintone API error ${status}: ${bodyText}`);
42706
+ this.status = status;
42707
+ this.code = code;
42708
+ this.name = "KintoneApiError";
42709
+ }
42710
+ };
42242
42711
  var SEARCH_ABORTED_HEADER_VALUE = "Filter aborted because of too many search results";
42243
42712
  function createNodeKintoneClient(baseUrl, tokenResolver) {
42244
42713
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
@@ -42286,7 +42755,13 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
42286
42755
  if (tokenResolver.debug) {
42287
42756
  tokenResolver.log?.(`[debug] response status=${res.status} body=${bodyText}`);
42288
42757
  }
42289
- throw new Error(`kintone API error ${res.status}: ${bodyText}`);
42758
+ let code;
42759
+ try {
42760
+ const body = JSON.parse(bodyText);
42761
+ if (typeof body.code === "string") code = body.code;
42762
+ } catch {
42763
+ }
42764
+ throw new KintoneApiError(res.status, code, bodyText);
42290
42765
  }
42291
42766
  if (tokenResolver.debug) {
42292
42767
  tokenResolver.log?.(`[debug] response status=${res.status}`);
@@ -42353,6 +42828,48 @@ function createNodeKintoneClient(baseUrl, tokenResolver) {
42353
42828
  return response.searchAborted ? { ...response.body, searchAborted: true } : response.body;
42354
42829
  }
42355
42830
  },
42831
+ async openCursor(params) {
42832
+ const manager = getCursorLeaseManager(new URL(normalizedBaseUrl).host, tokenResolver.cursorMaxActive);
42833
+ const lease = await manager.acquire();
42834
+ let created;
42835
+ try {
42836
+ created = await manager.runCreate(() => requestJson(
42837
+ `${apiBasePath}/records/cursor.json`,
42838
+ {
42839
+ method: "POST",
42840
+ body: JSON.stringify({
42841
+ app: params.app,
42842
+ query: params.query,
42843
+ size: params.size,
42844
+ fields: params.fields && params.fields.length > 0 ? params.fields : void 0
42845
+ })
42846
+ },
42847
+ params.app
42848
+ ));
42849
+ } catch (error51) {
42850
+ if (error51 instanceof KintoneApiError) {
42851
+ lease.release();
42852
+ throw error51;
42853
+ }
42854
+ lease.quarantine();
42855
+ throw new CursorCreateOutcomeUnknownError(error51);
42856
+ }
42857
+ const cursorId = created.id;
42858
+ return createKintoneCursorHandle(Number(created.totalCount), {
42859
+ get: () => requestJson(
42860
+ `${apiBasePath}/records/cursor.json?id=${encodeURIComponent(cursorId)}`,
42861
+ { method: "GET" },
42862
+ params.app
42863
+ ),
42864
+ delete: () => requestJson(
42865
+ `${apiBasePath}/records/cursor.json`,
42866
+ { method: "DELETE", body: JSON.stringify({ id: cursorId }) },
42867
+ params.app
42868
+ ),
42869
+ onReleased: () => lease.release(),
42870
+ onReleaseUnknown: () => lease.quarantine()
42871
+ });
42872
+ },
42356
42873
  async postRecords(_params) {
42357
42874
  const res = await requestJson(
42358
42875
  `${apiBasePath}/records.json`,
@@ -42819,6 +43336,10 @@ async function createKsqlRuntime(serverOptions, input) {
42819
43336
  const onLimit2 = input.onLimit ?? envOnLimit("KSQL_ON_LIMIT") ?? profile2.query?.onLimit ?? "error";
42820
43337
  const timeout2 = input.timeout ?? envInt("KSQL_TIMEOUT") ?? profile2.query?.timeout ?? 3e4;
42821
43338
  const tempTableMaxRows2 = input.tempTableMaxRows ?? envInt("KSQL_TEMP_TABLE_MAX_ROWS") ?? profile2.query?.tempTableMaxRows;
43339
+ const cursorMaxActive2 = input.cursorMaxActive ?? envInt("KSQL_CURSOR_MAX_ACTIVE") ?? profile2.query?.cursorMaxActive ?? 2;
43340
+ if (!Number.isSafeInteger(cursorMaxActive2) || cursorMaxActive2 < 1 || cursorMaxActive2 > 5) {
43341
+ throw new Error("ArgumentError: cursorMaxActive must be an integer from 1 to 5.");
43342
+ }
42822
43343
  const appIds = extractAppIds(sql);
42823
43344
  const defaultApp = envInt("KSQL_APP") ?? profile2.app ?? null;
42824
43345
  if (appIds.length === 0 && defaultApp !== null) appIds.push(defaultApp);
@@ -42856,6 +43377,7 @@ async function createKsqlRuntime(serverOptions, input) {
42856
43377
  }
42857
43378
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
42858
43379
  guestSpaceId,
43380
+ cursorMaxActive: cursorMaxActive2,
42859
43381
  timeoutMs: timeout2,
42860
43382
  debug: input.debug,
42861
43383
  debugHeaders: input.debugHeaders,
@@ -42887,6 +43409,7 @@ async function createKsqlRuntime(serverOptions, input) {
42887
43409
  }
42888
43410
  profileClientMap.set(pName, createNodeKintoneClient(baseUrl, {
42889
43411
  guestSpaceId,
43412
+ cursorMaxActive: cursorMaxActive2,
42890
43413
  timeoutMs: timeout2,
42891
43414
  debug: input.debug,
42892
43415
  debugHeaders: input.debugHeaders,
@@ -42920,6 +43443,12 @@ async function createKsqlRuntime(serverOptions, input) {
42920
43443
  if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
42921
43444
  return routed.getRecords({ ...params, app: binding.appId });
42922
43445
  },
43446
+ openCursor: (params) => {
43447
+ const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
43448
+ const routed = runtimeContext.clientsByProfile.get(binding.profile);
43449
+ if (!routed) throw new Error(`AuthError: profile "${binding.profile}" is not resolved for APP${params.app}.`);
43450
+ return routed.openCursor({ ...params, app: binding.appId });
43451
+ },
42923
43452
  postRecords: (params) => {
42924
43453
  const binding = resolveRuntimeBinding(runtimeContext.sqlContext, params.app);
42925
43454
  const routed = runtimeContext.clientsByProfile.get(binding.profile);
@@ -42970,6 +43499,7 @@ async function createKsqlRuntime(serverOptions, input) {
42970
43499
  fetchParallel: fetchParallel2,
42971
43500
  onLimit: onLimit2,
42972
43501
  timeout: timeout2,
43502
+ cursorMaxActive: cursorMaxActive2,
42973
43503
  tempTableMaxRows: tempTableMaxRows2
42974
43504
  };
42975
43505
  }
@@ -43170,6 +43700,7 @@ function noOpClient() {
43170
43700
  };
43171
43701
  return {
43172
43702
  getRecords: fail,
43703
+ openCursor: fail,
43173
43704
  postRecords: fail,
43174
43705
  putRecords: fail,
43175
43706
  deleteRecords: fail,
@@ -43446,7 +43977,9 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43446
43977
  const runtime = needsAppMetadata ? await createRuntime(serverOptions, {
43447
43978
  sql: input.sql,
43448
43979
  sqlContext: normalized.sqlContext,
43449
- profile: input.profile
43980
+ profile: input.profile,
43981
+ maxRecords: input.maxRecords,
43982
+ cursorMaxActive: input.cursorMaxActive
43450
43983
  }) : null;
43451
43984
  const explainClient = runtime?.client ?? noOpClient();
43452
43985
  const explainCacheContext = runtime?.cacheContext ?? normalized.cacheContext;
@@ -43457,7 +43990,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43457
43990
  explainClient,
43458
43991
  void 0,
43459
43992
  explainCacheContext,
43460
- runtime?.maxRecords
43993
+ runtime?.maxRecords ?? input.maxRecords,
43994
+ runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
43461
43995
  );
43462
43996
  return {
43463
43997
  ok: true,
@@ -43469,7 +44003,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43469
44003
  }
43470
44004
  const result = await executeSql(explainSql(explainSourceSql), explainClient, {
43471
44005
  cacheContext: explainCacheContext,
43472
- maxRecords: runtime?.maxRecords
44006
+ maxRecords: runtime?.maxRecords ?? input.maxRecords,
44007
+ cursorMaxActive: runtime?.cursorMaxActive ?? input.cursorMaxActive ?? 2
43473
44008
  });
43474
44009
  if (result.type !== "SELECT") {
43475
44010
  throw new Error(`ArgumentError: EXPLAIN returned unexpected result type ${result.type}.`);
@@ -43496,7 +44031,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43496
44031
  fetchParallel: input.fetchParallel,
43497
44032
  onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
43498
44033
  timeout: input.timeout,
43499
- tempTableMaxRows: input.tempTableMaxRows
44034
+ tempTableMaxRows: input.tempTableMaxRows,
44035
+ cursorMaxActive: input.cursorMaxActive
43500
44036
  });
43501
44037
  const batchResult = await executeBatchSql(runtime2.sql, runtime2.client, {
43502
44038
  maxRecords: runtime2.maxRecords,
@@ -43511,6 +44047,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43511
44047
  // runtime.timeout は env / profile / 既定 30000ms を解決済みの値で、
43512
44048
  // HTTP クライアント側の per-request タイムアウトと同値になる
43513
44049
  timeoutMs: runtime2.timeout,
44050
+ cursorMaxActive: runtime2.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43514
44051
  variables: input.variables
43515
44052
  });
43516
44053
  return { ...buildBatchEnvelope(batchResult, { maxTotalRecords: input.maxTotalRecords }) };
@@ -43540,13 +44077,15 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43540
44077
  maxRecords: input.maxRecords,
43541
44078
  fetchParallel: input.fetchParallel,
43542
44079
  onLimit: validation.containsValidationOnly ? "error" : input.onLimit,
43543
- timeout: input.timeout
44080
+ timeout: input.timeout,
44081
+ cursorMaxActive: input.cursorMaxActive
43544
44082
  });
43545
44083
  const result = await executeSql(runtime.sql, runtime.client, {
43546
44084
  maxRecords: runtime.maxRecords,
43547
44085
  fetchParallel: runtime.fetchParallel,
43548
44086
  onLimitReached: runtime.onLimit,
43549
- cacheContext: runtime.cacheContext
44087
+ cacheContext: runtime.cacheContext,
44088
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2
43550
44089
  });
43551
44090
  if (result.type === "ASSERT") return toAssertPayload(result);
43552
44091
  if (result.type === "VALIDATION") return toDmlValidationPayload(result);
@@ -43590,7 +44129,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43590
44129
  fetchParallel: input.fetchParallel,
43591
44130
  onLimit: DEFAULT_ON_LIMIT,
43592
44131
  timeout: input.timeout,
43593
- tempTableMaxRows: input.tempTableMaxRows
44132
+ tempTableMaxRows: input.tempTableMaxRows,
44133
+ cursorMaxActive: input.cursorMaxActive
43594
44134
  });
43595
44135
  let totalAffected = staticInsertTotal;
43596
44136
  const batchResult = await executeBatchSql(runtime.sql, runtime.client, {
@@ -43602,6 +44142,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43602
44142
  tempTableMaxRows: runtime.tempTableMaxRows,
43603
44143
  // 合計タイムアウト(解決済みの runtime.timeout。per-request と同値)
43604
44144
  timeoutMs: runtime.timeout,
44145
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43605
44146
  variables: input.variables,
43606
44147
  confirm: async (count, operation) => {
43607
44148
  if (count > dmlMaxRows) {
@@ -43657,7 +44198,8 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43657
44198
  maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
43658
44199
  fetchParallel: input.fetchParallel,
43659
44200
  onLimit: DEFAULT_ON_LIMIT,
43660
- timeout: input.timeout
44201
+ timeout: input.timeout,
44202
+ cursorMaxActive: input.cursorMaxActive
43661
44203
  });
43662
44204
  let result;
43663
44205
  try {
@@ -43666,6 +44208,7 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
43666
44208
  fetchParallel: runtime.fetchParallel,
43667
44209
  onLimitReached: runtime.onLimit,
43668
44210
  cacheContext: runtime.cacheContext,
44211
+ cursorMaxActive: runtime.cursorMaxActive ?? input.cursorMaxActive ?? 2,
43669
44212
  confirm: async (count, operation) => {
43670
44213
  if (count > dmlMaxRows) {
43671
44214
  throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
@@ -43838,6 +44381,7 @@ var fetchParallel = external_exports.number().int().min(1).max(10).describe("Num
43838
44381
  var onLimit = external_exports.enum(["error", "truncate"]).describe("Behavior when maxRecords is exceeded: 'error' rejects, 'truncate' returns the first maxRecords rows (default 'error'). Local ORDER BY plans require complete input and fail instead of returning a truncated top-N; REST top-N and KORDER_NATIVE do not fetch a partial candidate set. VALIDATE ONLY always overrides 'truncate' to 'error'.").optional();
43839
44382
  var tempTableMaxRows = external_exports.number().int().positive().describe("Per-temp-table cap on materialized rows for CREATE TEMP TABLE ... AS SELECT (default 10000). Overflow always errors \u2014 'truncate' never applies to temp tables, so downstream statements never see silently truncated data. Raising this increases memory use (up to 16 temp tables per batch); prefer narrowing the SELECT with WHERE.").optional();
43840
44383
  var timeout = external_exports.number().int().positive().describe("Request timeout in milliseconds. For multi-statement batches this also acts as the total batch deadline.").optional();
44384
+ var cursorMaxActive = external_exports.number().int().min(1).max(5).describe("Maximum active Cursor API handles per kintone host in this process (1-5, default 2). Later calls update the host limit; lowering it keeps existing cursors and delays new ones until active usage falls below the new limit. Create/Get are never automatically retried; capacity waits up to 30 seconds.").optional();
43841
44385
  var savedQueryName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/).describe("Saved query name (alphanumeric, '_' and '-', up to 64 chars).");
43842
44386
  var savedQueryTags = external_exports.array(external_exports.string().min(1)).describe("Tags for organizing saved queries.").optional();
43843
44387
  var validateInputSchema = external_exports.object({
@@ -43846,7 +44390,9 @@ var validateInputSchema = external_exports.object({
43846
44390
  });
43847
44391
  var explainInputSchema = external_exports.object({
43848
44392
  sql: external_exports.string().min(1).describe("kSQL text to explain. May contain multiple ;-separated statements (batch) and temp tables (#name)."),
43849
- profile
44393
+ profile,
44394
+ maxRecords,
44395
+ cursorMaxActive
43850
44396
  });
43851
44397
  var queryInputSchema = external_exports.object({
43852
44398
  sql: external_exports.string().min(1).describe("Read-only kSQL text. May contain multiple ;-separated statements (batch) with temp tables, e.g. CREATE TEMP TABLE #t AS SELECT ...; SELECT ... FROM #t;"),
@@ -43856,6 +44402,7 @@ var queryInputSchema = external_exports.object({
43856
44402
  onLimit,
43857
44403
  tempTableMaxRows,
43858
44404
  timeout,
44405
+ cursorMaxActive,
43859
44406
  continueOnError: external_exports.boolean().describe("Batch (multi-statement) only: keep executing subsequent statements after a runtime error (default false = fail-fast).").optional(),
43860
44407
  maxTotalRecords: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total rows returned across all result sets (default: unlimited).").optional(),
43861
44408
  variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
@@ -43869,6 +44416,7 @@ var mutateInputSchema = external_exports.object({
43869
44416
  fetchParallel,
43870
44417
  tempTableMaxRows,
43871
44418
  timeout,
44419
+ cursorMaxActive,
43872
44420
  dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional(),
43873
44421
  variables: external_exports.record(external_exports.string(), external_exports.string()).describe("Batch only: string values for variables declared with DECLARE. Keys omit @ and are case-insensitive.").optional()
43874
44422
  });
@@ -43959,7 +44507,7 @@ Options:
43959
44507
  -h, --help Show help
43960
44508
  `);
43961
44509
  }
43962
- var SERVER_VERSION = true ? "3.0.0" : "0.0.0-dev";
44510
+ var SERVER_VERSION = true ? "3.1.0" : "0.0.0-dev";
43963
44511
  function createServer(args) {
43964
44512
  const server = new McpServer({
43965
44513
  name: "ksql-mcp",