@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.
- package/README.md +1 -0
- package/dist-cli/ksql.js +586 -31
- package/dist-mcp/ksql-mcp.js +586 -38
- package/dist-mcpb/ksql-mcp.mcpb +0 -0
- package/package.json +1 -1
package/dist-mcp/ksql-mcp.js
CHANGED
|
@@ -36543,7 +36543,7 @@ var KORDER_NATIVE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
36543
36543
|
"CREATOR",
|
|
36544
36544
|
"MODIFIER"
|
|
36545
36545
|
]);
|
|
36546
|
-
function
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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
|
-
|
|
38307
|
+
guardedWork,
|
|
38118
38308
|
new Promise((_, reject) => {
|
|
38119
|
-
timer = setTimeout(() =>
|
|
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" ?
|
|
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" ?
|
|
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" ?
|
|
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" ?
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|