feat(core): optimize query resource lifecycle
This commit is contained in:
1 parent
b4bd6c42a7
commit
295b67dd36
29 files changed
+1857
-335
No files matched your search
@@ -51,6 +51,7 @@ import { findDatabaseTreeNode } from "@/lib/sidebar/treeRefreshTarget";
|
||||
import { shouldMarkDisconnected } from "@/lib/connection/connectionHealth";
|
||||
import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connection/connectionAttemptTimeout";
|
||||
import { requiresSqlServerLegacyCompatibilityComponent, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility";
|
||||
import { deleteTabResultSnapshotsForOwner } from "@/lib/tabs/tabResultCache";
|
||||
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases";
|
||||
import {
|
||||
buildObjectGroupPlaceholderNodes,
|
||||
@@ -1756,6 +1757,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
for (const id of removedIds) {
|
||||
invalidateCompletionCache(id);
|
||||
clearLoadedChildrenCache(id);
|
||||
void deleteTabResultSnapshotsForOwner(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
|
||||
import { executableStatementRanges, splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import { externalSqlFileDisplayTitles, normalizeExternalSqlPath } from "@/lib/sql/sqlFileOpen";
|
||||
import { clearDataGridPendingSnapshotsForTab } from "@/composables/useDataGridEditor";
|
||||
import { buildTabResultSnapshot, deleteTabResultSnapshot, readTabResultSnapshot, tabResultCacheKey, writeTabResultSnapshot } from "@/lib/tabs/tabResultCache";
|
||||
import { buildTabResultSnapshot, deleteTabResultSnapshot, pruneTabResultSnapshots, readTabResultSnapshot, tabResultCacheKey, writeTabResultSnapshot } from "@/lib/tabs/tabResultCache";
|
||||
import { estimateQueryResultsBytes, selectInactiveResultEvictions } from "@/lib/tabs/queryResultSize";
|
||||
import { queryResultBaseSql, queryResultExecutionSql } from "@/lib/tabs/tabPresentation";
|
||||
import { isMysqlExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { decodeQueryResultArchive, encodeQueryResultArchive, type DecodedQueryResultArchive } from "@/lib/query/queryResultArchive";
|
||||
@@ -52,6 +53,8 @@ import * as api from "@/lib/backend/api";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
import { recordQueryCancellationLatency, resourceLifecycleDiagnostics } from "@/lib/diagnostics/resourceLifecycleDiagnostics";
|
||||
import { appendDebugLog } from "@/lib/backend/debugLog";
|
||||
import { createSavedSqlEditorPosition, initSavedSqlEditorPositions, restoreSavedSqlEditorPosition, saveSavedSqlEditorPosition } from "@/lib/app/savedSqlEditorPosition";
|
||||
import { ensureSqlExtension } from "@/lib/savedSql/savedSqlFileName";
|
||||
import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage";
|
||||
@@ -416,6 +419,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return tableStructureRefreshVersions.value[tableStructureKey(connectionId, database, schema, tableName)] ?? 0;
|
||||
}
|
||||
const MAX_CACHED_RESULTS = 5;
|
||||
const MAX_CACHED_RESULT_BYTES = 128 * 1024 * 1024;
|
||||
|
||||
function queryExecutionLog(level: "debug" | "info" | "warn" | "error", event: string, details: Record<string, unknown>) {
|
||||
appendDebugLog(level, `[DBX][executeTabSql:${event}]`, details);
|
||||
}
|
||||
|
||||
async function closeResultSession(tab: QueryTab | undefined, preserveSessionId?: string, throwOnError = false) {
|
||||
const sessionId = tab?.resultSessionId ?? tab?.result?.session_id;
|
||||
@@ -481,6 +489,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
function touchResult(tab: QueryTab | undefined, accessedAt = Date.now()) {
|
||||
if (tab?.result || tab?.results) {
|
||||
tab.resultAccessedAt = accessedAt;
|
||||
tab.resultEstimatedBytes = estimateQueryResultsBytes(tab.result, tab.results);
|
||||
tab.resultCacheState = "memory";
|
||||
tab.resultEvicted = undefined;
|
||||
}
|
||||
@@ -496,6 +505,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.resultSortMode = undefined;
|
||||
tab.resultSessionId = undefined;
|
||||
tab.resultAccessedAt = undefined;
|
||||
tab.resultEstimatedBytes = undefined;
|
||||
tab.queryAnalysis = undefined;
|
||||
tab.querySourceColumns = undefined;
|
||||
tab.queryEditabilityReason = undefined;
|
||||
@@ -521,6 +531,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
run.resultLocalSortOriginalRows = undefined;
|
||||
run.resultLocalSortOriginalMongoDocuments = undefined;
|
||||
run.resultSessionId = undefined;
|
||||
run.resultEstimatedBytes = undefined;
|
||||
run.queryAnalysis = undefined;
|
||||
run.querySourceColumns = undefined;
|
||||
run.queryEditabilityReason = undefined;
|
||||
@@ -556,6 +567,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.resultAccessedAt = run.resultAccessedAt;
|
||||
tab.resultCacheKey = run.resultCacheKey;
|
||||
tab.resultCacheState = run.resultCacheState;
|
||||
tab.resultEstimatedBytes = run.resultEstimatedBytes ?? estimateQueryResultsBytes(run.result, run.results);
|
||||
tab.resultEvicted = run.resultEvicted;
|
||||
tab.queryAnalysis = run.queryAnalysis;
|
||||
tab.querySourceColumns = run.querySourceColumns;
|
||||
@@ -632,24 +644,28 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const key = run.resultCacheKey ?? resultRunCacheKey(tab.id, run.id);
|
||||
run.resultCacheKey = key;
|
||||
run.resultCacheState = "memory";
|
||||
return writeTabResultSnapshot(key, {
|
||||
result: run.result,
|
||||
results: run.results,
|
||||
activeResultIndex: run.activeResultIndex,
|
||||
resultEditorFingerprint: run.resultEditorFingerprint,
|
||||
resultRuns: [run],
|
||||
activeResultRunId: run.id,
|
||||
queryAnalysis: run.queryAnalysis,
|
||||
querySourceColumns: run.querySourceColumns,
|
||||
queryEditabilityReason: run.queryEditabilityReason,
|
||||
tableMeta: run.tableMeta,
|
||||
resultPageSql: run.resultPageSql,
|
||||
resultPageLimit: run.resultPageLimit,
|
||||
resultPageOffset: run.resultPageOffset,
|
||||
resultCountSql: run.resultCountSql,
|
||||
resultTotalRowCount: run.resultTotalRowCount,
|
||||
cachedAt: Date.now(),
|
||||
});
|
||||
return writeTabResultSnapshot(
|
||||
key,
|
||||
{
|
||||
result: run.result,
|
||||
results: run.results,
|
||||
activeResultIndex: run.activeResultIndex,
|
||||
resultEditorFingerprint: run.resultEditorFingerprint,
|
||||
resultRuns: [run],
|
||||
activeResultRunId: run.id,
|
||||
queryAnalysis: run.queryAnalysis,
|
||||
querySourceColumns: run.querySourceColumns,
|
||||
queryEditabilityReason: run.queryEditabilityReason,
|
||||
tableMeta: run.tableMeta,
|
||||
resultPageSql: run.resultPageSql,
|
||||
resultPageLimit: run.resultPageLimit,
|
||||
resultPageOffset: run.resultPageOffset,
|
||||
resultCountSql: run.resultCountSql,
|
||||
resultTotalRowCount: run.resultTotalRowCount,
|
||||
cachedAt: Date.now(),
|
||||
},
|
||||
tab.connectionId,
|
||||
);
|
||||
}
|
||||
|
||||
function evictInactiveResultRunPayloads(tab: QueryTab) {
|
||||
@@ -698,6 +714,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
resultTotalRowCountLoading: tab.resultTotalRowCountLoading,
|
||||
resultSessionId: tab.resultSessionId,
|
||||
resultAccessedAt: tab.resultAccessedAt,
|
||||
resultEstimatedBytes: tab.resultEstimatedBytes,
|
||||
resultCacheKey: tab.resultCacheKey,
|
||||
resultCacheState: tab.resultCacheState,
|
||||
resultEvicted: tab.resultEvicted,
|
||||
@@ -750,6 +767,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
resultTotalRowCountLoading: tab.resultTotalRowCountLoading,
|
||||
resultSessionId: tab.resultSessionId,
|
||||
resultAccessedAt: tab.resultAccessedAt,
|
||||
resultEstimatedBytes: tab.resultEstimatedBytes,
|
||||
resultCacheKey: tab.resultCacheKey,
|
||||
resultCacheState: tab.resultCacheState,
|
||||
resultEvicted: tab.resultEvicted,
|
||||
@@ -823,7 +841,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
async function evictCachedResult(tab: QueryTab) {
|
||||
await closeResultSession(tab);
|
||||
const cacheKey = tabResultCacheKey(tab.id);
|
||||
const cached = await writeTabResultSnapshot(cacheKey, buildTabResultSnapshot(tab));
|
||||
const cached = await writeTabResultSnapshot(cacheKey, buildTabResultSnapshot(tab), tab.connectionId);
|
||||
tab.resultCacheKey = cached ? cacheKey : undefined;
|
||||
tab.resultCacheState = cached ? "disk" : "missing";
|
||||
clearResultPayload(tab, { evicted: true });
|
||||
@@ -838,6 +856,16 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleResultCacheMaintenance() {
|
||||
const maintain = () => {
|
||||
const liveKeys = tabs.value.flatMap((tab) => [tab.resultCacheKey, ...(tab.resultRuns?.map((run) => run.resultCacheKey) ?? [])]).filter((key): key is string => !!key);
|
||||
void pruneTabResultSnapshots(liveKeys).catch((error) => console.warn("[DBX][result-cache:maintenance:error]", error));
|
||||
};
|
||||
if (typeof requestIdleCallback !== "undefined") requestIdleCallback(maintain, { timeout: 5000 });
|
||||
else if (typeof window !== "undefined") window.setTimeout(maintain, 0);
|
||||
else setTimeout(maintain, 0);
|
||||
}
|
||||
|
||||
async function initOpenTabs() {
|
||||
if (isOpenTabsLoaded.value) return;
|
||||
const saved = await api.loadOpenTabsState().catch(() => null);
|
||||
@@ -851,6 +879,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
await saveTabs(tabs.value, activeTabId.value).catch(() => undefined);
|
||||
}
|
||||
isOpenTabsLoaded.value = true;
|
||||
scheduleResultCacheMaintenance();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -863,6 +892,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// would resurrect old tabs if the user later changes the setting.
|
||||
clearLegacySavedTabs();
|
||||
isOpenTabsLoaded.value = true;
|
||||
scheduleResultCacheMaintenance();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -875,6 +905,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
}
|
||||
isOpenTabsLoaded.value = true;
|
||||
scheduleResultCacheMaintenance();
|
||||
}
|
||||
|
||||
const _persistSnapshot = computed(() =>
|
||||
@@ -1452,6 +1483,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
void closeResultSession(tabs.value[idx]);
|
||||
void closeClientConnectionSession(tabs.value[idx]);
|
||||
clearResultRunSnapshots(tabs.value[idx]);
|
||||
void deleteTabResultSnapshot(tabResultCacheKey(id));
|
||||
clearResultPayload(tabs.value[idx]);
|
||||
tabs.value.splice(idx, 1);
|
||||
if (tab.externalSqlPath) refreshExternalSqlFileTitles();
|
||||
@@ -1703,6 +1735,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
void closeResultSession(tab);
|
||||
void closeClientConnectionSession(tab);
|
||||
clearResultRunSnapshots(tab);
|
||||
void deleteTabResultSnapshot(tabResultCacheKey(tab.id));
|
||||
clearResultPayload(tab);
|
||||
});
|
||||
|
||||
@@ -2265,7 +2298,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tableName: metadataTableName,
|
||||
};
|
||||
const knownTableType = tab.tableMeta?.tableName.toLowerCase() === metadataTableName.toLowerCase() && normalizeOptionalSchema(tab.tableMeta.schema) === normalizeOptionalSchema(metadataSchema) ? tab.tableMeta.tableType : undefined;
|
||||
console.info("[DBX][executeTabSql:metadata:table:start]", {
|
||||
queryExecutionLog("info", "metadata:table:start", {
|
||||
traceId,
|
||||
schema: metadataSchema,
|
||||
table: metadataTableName,
|
||||
@@ -2281,11 +2314,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
databaseType: dbType,
|
||||
driverProfile: conn?.driver_profile || conn?.db_type,
|
||||
catalog: metadataCatalog,
|
||||
traceLogger: (event) => console.debug("[DBX][executeTabSql:metadata:table-trace]", { sourceTraceId: traceId, ...event }),
|
||||
traceLogger: (event) => queryExecutionLog("debug", "metadata:table-trace", { sourceTraceId: traceId, ...event }),
|
||||
});
|
||||
const columns = loadedMetadata.metadata.columns;
|
||||
const primaryKeys = loadedMetadata.metadata.primaryKeys;
|
||||
console.info("[DBX][executeTabSql:metadata:table:done]", {
|
||||
queryExecutionLog("info", "metadata:table:done", {
|
||||
traceId,
|
||||
columnCount: columns.length,
|
||||
primaryKeyCount: primaryKeys.length,
|
||||
@@ -2356,7 +2389,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
sourceExpressions: databaseType === "oracle" && missingPrimaryKeys.includes(DBX_ROWID_COLUMN) ? { [DBX_ROWID_COLUMN]: "ROWIDTOCHAR(ROWID)" } : undefined,
|
||||
});
|
||||
if (!rewritten) return unchanged;
|
||||
console.info("[DBX][executeTabSql:hidden-primary-keys]", {
|
||||
queryExecutionLog("info", "hidden-primary-keys", {
|
||||
traceId,
|
||||
table: loaded.tableMeta.tableName,
|
||||
keyCount: rewritten.projections.length,
|
||||
@@ -2366,7 +2399,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
} catch (error) {
|
||||
// Metadata enrichment is optional. Query execution must retain its prior
|
||||
// behavior when metadata is unavailable or the SQL cannot be rewritten.
|
||||
console.warn("[DBX][executeTabSql:hidden-primary-keys:skip]", { traceId, error, elapsed: elapsed() });
|
||||
queryExecutionLog("warn", "hidden-primary-keys:skip", { traceId, error, elapsed: elapsed() });
|
||||
return unchanged;
|
||||
}
|
||||
}
|
||||
@@ -2382,9 +2415,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
};
|
||||
}
|
||||
|
||||
console.info("[DBX][executeTabSql:metadata:editability:start]", { traceId, elapsed: elapsed?.() });
|
||||
queryExecutionLog("info", "metadata:editability:start", { traceId, elapsed: elapsed?.() });
|
||||
const editability = await api.analyzeEditableQueryEditability(sql);
|
||||
console.info("[DBX][executeTabSql:metadata:editability:done]", {
|
||||
queryExecutionLog("info", "metadata:editability:done", {
|
||||
traceId,
|
||||
editable: editability.editable,
|
||||
reason: editability.editable ? undefined : editability.reason,
|
||||
@@ -2533,7 +2566,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
void (async () => {
|
||||
const tab = tabs.value.find((t) => t.id === tabId);
|
||||
if (!tab || tab.result !== result) return;
|
||||
console.info("[DBX][executeTabSql:metadata:start]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "metadata:start", { traceId, elapsed: elapsed() });
|
||||
const patch = await buildQueryMetadataPatch(tab, sql, traceId, elapsed, hiddenPrimaryKeys);
|
||||
if (patch?.queryAnalysis && hiddenPrimaryKeys.length > 0) {
|
||||
patch.queryAnalysis = { ...patch.queryAnalysis, allowInsert: false };
|
||||
@@ -2542,9 +2575,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (patch && current?.result === result) {
|
||||
applyQueryMetadataPatch(current, patch);
|
||||
syncActiveResultRunFromDisplayed(current);
|
||||
console.info("[DBX][executeTabSql:metadata:done]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "metadata:done", { traceId, elapsed: elapsed() });
|
||||
} else {
|
||||
console.warn("[DBX][executeTabSql:metadata:stale]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("warn", "metadata:stale", { traceId, elapsed: elapsed() });
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -2604,7 +2637,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, undefined);
|
||||
return;
|
||||
}
|
||||
console.info("[DBX][executeTabSql:count:start]", { traceId: options.traceId, elapsed: options.elapsed() });
|
||||
queryExecutionLog("info", "count:start", { traceId: options.traceId, elapsed: options.elapsed() });
|
||||
const countResult = await api.executeQuery(options.connectionId, options.database, countTarget.sql, countTarget.schema, countExecutionId, {
|
||||
clientSessionId,
|
||||
timeoutSecs: options.timeoutSecs,
|
||||
@@ -2615,14 +2648,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return;
|
||||
}
|
||||
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, total);
|
||||
console.info("[DBX][executeTabSql:count:done]", {
|
||||
queryExecutionLog("info", "count:done", {
|
||||
traceId: options.traceId,
|
||||
total,
|
||||
elapsed: options.elapsed(),
|
||||
});
|
||||
} catch (error) {
|
||||
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, undefined);
|
||||
console.warn("[DBX][executeTabSql:count:error]", {
|
||||
queryExecutionLog("warn", "count:error", {
|
||||
traceId: options.traceId,
|
||||
elapsed: options.elapsed(),
|
||||
error,
|
||||
@@ -2686,15 +2719,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (!options?.preserveResultDuringExecution || !tab.result) {
|
||||
clearResultPayload(tab);
|
||||
}
|
||||
console.info("[DBX][executeTabSql:start]", {
|
||||
queryExecutionLog("info", "start", {
|
||||
traceId,
|
||||
tabId: id,
|
||||
mode: tab.mode,
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
schema: tab.schema,
|
||||
sourceTraceId: options?.sourceTraceId,
|
||||
sql,
|
||||
sqlLength: sql.length,
|
||||
});
|
||||
const queryBaseSql = options?.resultBaseSql ?? sql;
|
||||
let sqlToExecute = sql;
|
||||
@@ -2715,13 +2745,13 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const mongoNeedsConnection = mongoCommands.some(({ command }) => command.kind !== "use");
|
||||
|
||||
if (options?.skipEnsureConnected) {
|
||||
console.info("[DBX][executeTabSql:ensure-connected:skip]", { traceId, elapsed: elapsed(), reason: "caller" });
|
||||
queryExecutionLog("info", "ensure-connected:skip", { traceId, elapsed: elapsed(), reason: "caller" });
|
||||
} else if (conn?.db_type === "mongodb" && mongoCommands.length > 0 && !mongoNeedsConnection) {
|
||||
console.info("[DBX][executeTabSql:ensure-connected:skip]", { traceId, elapsed: elapsed(), reason: "mongo-use-only" });
|
||||
queryExecutionLog("info", "ensure-connected:skip", { traceId, elapsed: elapsed(), reason: "mongo-use-only" });
|
||||
} else {
|
||||
console.info("[DBX][executeTabSql:ensure-connected:start]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "ensure-connected:start", { traceId, elapsed: elapsed() });
|
||||
await connStore.ensureConnected(tab.connectionId);
|
||||
console.info("[DBX][executeTabSql:ensure-connected:done]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "ensure-connected:done", { traceId, elapsed: elapsed() });
|
||||
}
|
||||
conn = connStore.getConfig(tab.connectionId);
|
||||
if (parsedMongoCommands === undefined && conn?.db_type === "mongodb") {
|
||||
@@ -2732,9 +2762,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const useAgentCursor = usesAgentCursorForQuery(conn?.db_type);
|
||||
const queryTimeoutSecs = queryTimeoutSecsForConnection(conn);
|
||||
const settingsStore = useSettingsStore();
|
||||
console.info("[DBX][executeTabSql:previous-session-close:start]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "previous-session-close:start", { traceId, elapsed: elapsed() });
|
||||
await previousResultSessionClose;
|
||||
console.info("[DBX][executeTabSql:previous-session-close:done]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "previous-session-close:done", { traceId, elapsed: elapsed() });
|
||||
|
||||
// Redis command execution — split multi-line input into individual commands
|
||||
if (conn?.db_type === "redis") {
|
||||
@@ -2745,7 +2775,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
if (commands.length === 0) return;
|
||||
console.info("[DBX][executeTabSql:redis:start]", { traceId, db: currentDb, commandCount: commands.length, sql });
|
||||
queryExecutionLog("info", "redis:start", { traceId, db: currentDb, commandCount: commands.length, sqlLength: sql.length });
|
||||
|
||||
const allResults: QueryResult[] = [];
|
||||
const commandRanges = executableStatementRanges(sql, "redis");
|
||||
@@ -2769,7 +2799,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
allResults.push(annotateQueryResultSource({ columns: ["Error"], rows: [[e?.message ?? String(e)]], affected_rows: 0, execution_time_ms: 0 }, command, undefined, undefined, sourceRange));
|
||||
}
|
||||
}
|
||||
console.info("[DBX][executeTabSql:redis:done]", { traceId, commandCount: commands.length, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "redis:done", { traceId, commandCount: commands.length, elapsed: elapsed() });
|
||||
|
||||
const current = tabs.value.find((t) => t.id === id);
|
||||
if (current?.executionId === executionId) {
|
||||
@@ -2809,7 +2839,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
|
||||
if (mongoCommands.length > 0) {
|
||||
console.info("[DBX][executeTabSql:mongo:start]", { traceId, database: tab.database, commandCount: mongoCommands.length, sql });
|
||||
queryExecutionLog("info", "mongo:start", { traceId, commandCount: mongoCommands.length, sqlLength: sql.length });
|
||||
|
||||
const allResults: QueryResult[] = [];
|
||||
// Track the effective db as we walk the batch so later commands observe
|
||||
@@ -2832,12 +2862,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
try {
|
||||
switch (mongoCommand.kind) {
|
||||
case "find": {
|
||||
console.info("[DBX][executeTabSql:mongo-find:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-find:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const result = await api.mongoFindDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.skip, mongoCommand.limit, mongoCommand.filter, mongoCommand.projection, mongoCommand.sort, executionId);
|
||||
const queryResult = markQueryResultRowsRaw(annotateMongoResult(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total)));
|
||||
allResults.push(queryResult);
|
||||
mongoEditTarget = mongoCommands.length === 1 && queryResult.columns.includes("_id") ? { collection: mongoCommand.collection, idColumn: "_id" } : undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-find:done]", {
|
||||
queryExecutionLog("info", "mongo-find:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
database: currentDatabase,
|
||||
@@ -2848,12 +2878,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
break;
|
||||
}
|
||||
case "findOne": {
|
||||
console.info("[DBX][executeTabSql:mongo-find-one:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-find-one:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const result = await api.mongoFindOne(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.projection, mongoCommand.options, executionId);
|
||||
const queryResult = markQueryResultRowsRaw(annotateMongoResult(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total)));
|
||||
allResults.push(queryResult);
|
||||
mongoEditTarget = mongoCommands.length === 1 && queryResult.columns.includes("_id") ? { collection: mongoCommand.collection, idColumn: "_id" } : undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-find-one:done]", {
|
||||
queryExecutionLog("info", "mongo-find-one:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
database: currentDatabase,
|
||||
@@ -2863,11 +2893,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
break;
|
||||
}
|
||||
case "version": {
|
||||
console.info("[DBX][executeTabSql:mongo-version:start]", { traceId, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-version:start", { traceId, database: currentDatabase });
|
||||
const version = await api.mongoServerVersion(tab.connectionId, currentDatabase, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoVersionToQueryResult(version, performance.now() - commandStartedAt))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-version:done]", {
|
||||
queryExecutionLog("info", "mongo-version:done", {
|
||||
traceId,
|
||||
database: currentDatabase,
|
||||
version,
|
||||
@@ -2876,11 +2906,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
break;
|
||||
}
|
||||
case "countDocuments": {
|
||||
console.info("[DBX][executeTabSql:mongo-count:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-count:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const total = await api.mongoCountDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.mode, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoCountToQueryResult(total, performance.now() - commandStartedAt))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-count:done]", {
|
||||
queryExecutionLog("info", "mongo-count:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
database: currentDatabase,
|
||||
@@ -2894,12 +2924,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const safety = evaluateMongoAggregateSafety(mongoCommand, options.mongoSafety);
|
||||
if (!safety.allowed) throw new Error(safety.reason);
|
||||
}
|
||||
console.info("[DBX][executeTabSql:mongo-aggregate:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-aggregate:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const aggregateMaxRows = normalizeResultPageSize(pageLimit ?? options?.pagination?.limit ?? settingsStore.editorSettings.pageSize);
|
||||
const result = await api.mongoAggregateDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.pipeline, aggregateMaxRows, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-aggregate:done]", {
|
||||
queryExecutionLog("info", "mongo-aggregate:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
database: currentDatabase,
|
||||
@@ -2910,11 +2940,11 @@ export const useQueryStore = defineStore("query", () => {
|
||||
break;
|
||||
}
|
||||
case "getIndexes": {
|
||||
console.info("[DBX][executeTabSql:mongo-indexes:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
queryExecutionLog("info", "mongo-indexes:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const indexes = await api.listIndexes(tab.connectionId, currentDatabase, "", mongoCommand.collection);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoIndexesToQueryResult(indexes, performance.now() - commandStartedAt))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-indexes:done]", {
|
||||
queryExecutionLog("info", "mongo-indexes:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
database: currentDatabase,
|
||||
@@ -2924,7 +2954,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
break;
|
||||
}
|
||||
case "collectionStats": {
|
||||
console.info("[DBX][executeTabSql:mongo-collection-stats:start]", {
|
||||
queryExecutionLog("info", "mongo-collection-stats:start", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
metric: mongoCommand.metric,
|
||||
@@ -2933,7 +2963,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const stats = await api.mongoCollectionStats(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.scale, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoCollectionStatsToQueryResult(mongoCommand.metric, stats as unknown as Record<string, unknown>, performance.now() - commandStartedAt))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-collection-stats:done]", {
|
||||
queryExecutionLog("info", "mongo-collection-stats:done", {
|
||||
traceId,
|
||||
collection: mongoCommand.collection,
|
||||
metric: mongoCommand.metric,
|
||||
@@ -2949,7 +2979,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
|
||||
if (!safety.allowed) throw new Error(safety.reason);
|
||||
}
|
||||
console.info("[DBX][executeTabSql:mongo-find-and-modify:start]", {
|
||||
queryExecutionLog("info", "mongo-find-and-modify:start", {
|
||||
traceId,
|
||||
kind: mongoCommand.kind,
|
||||
collection: mongoCommand.collection,
|
||||
@@ -2963,7 +2993,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
: await api.mongoFindOneAndDelete(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.options);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-find-and-modify:done]", {
|
||||
queryExecutionLog("info", "mongo-find-and-modify:done", {
|
||||
traceId,
|
||||
kind: mongoCommand.kind,
|
||||
collection: mongoCommand.collection,
|
||||
@@ -2984,7 +3014,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
|
||||
if (!safety.allowed) throw new Error(safety.reason);
|
||||
}
|
||||
console.info("[DBX][executeTabSql:mongo-write:start]", {
|
||||
queryExecutionLog("info", "mongo-write:start", {
|
||||
traceId,
|
||||
database: currentDatabase,
|
||||
kind: mongoCommand.kind,
|
||||
@@ -3010,7 +3040,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const result = await api.mongoDeleteDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.many);
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt))));
|
||||
}
|
||||
console.info("[DBX][executeTabSql:mongo-write:done]", {
|
||||
queryExecutionLog("info", "mongo-write:done", {
|
||||
traceId,
|
||||
database: currentDatabase,
|
||||
kind: mongoCommand.kind,
|
||||
@@ -3023,7 +3053,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
currentDatabase = mongoCommand.database;
|
||||
allResults.push(markQueryResultRowsRaw(annotateMongoResult(mongoUseToQueryResult(currentDatabase, performance.now() - commandStartedAt))));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-use:done]", {
|
||||
queryExecutionLog("info", "mongo-use:done", {
|
||||
traceId,
|
||||
database: currentDatabase,
|
||||
elapsed: elapsed(),
|
||||
@@ -3039,7 +3069,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
}
|
||||
|
||||
console.info("[DBX][executeTabSql:mongo:done]", {
|
||||
queryExecutionLog("info", "mongo:done", {
|
||||
traceId,
|
||||
database: currentDatabase,
|
||||
commandCount: mongoCommands.length,
|
||||
@@ -3127,14 +3157,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
let executionPromise: Promise<QueryResult[]>;
|
||||
if (tab.autoCommit === false) {
|
||||
if (!tab.txnSessionId) {
|
||||
console.info("[DBX][executeTabSql:begin-manual-txn:start]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "begin-manual-txn:start", { traceId, elapsed: elapsed() });
|
||||
tab.txnSessionId = await api.beginManualTransaction(tab.connectionId, executionDatabase, executionSchema);
|
||||
console.info("[DBX][executeTabSql:begin-manual-txn:done]", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "begin-manual-txn:done", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
}
|
||||
console.info("[DBX][executeTabSql:execute-in-txn:invoke]", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "execute-in-txn:invoke", { traceId, txnSessionId: tab.txnSessionId, elapsed: elapsed() });
|
||||
executionPromise = api.executeInManualTransaction(tab.txnSessionId, sqlToExecute, executionDatabase, executionSchema, pageLimit);
|
||||
} else {
|
||||
console.info("[DBX][executeTabSql:execute-multi:start]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "execute-multi:start", { traceId, elapsed: elapsed() });
|
||||
// Query and data tabs use a tab-scoped pool so repeated executions keep
|
||||
// connection-local state and avoid MySQL pool resets on every refresh.
|
||||
const clientSessionId = tab.mode === "query" || tab.mode === "data" ? tabClientSessionId(tab) : undefined;
|
||||
@@ -3152,7 +3182,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
continueOnError: settingsStore.editorSettings.continueOnErrorOnBatch,
|
||||
};
|
||||
console.info("[DBX][executeTabSql:execute-multi:invoke]", {
|
||||
queryExecutionLog("info", "execute-multi:invoke", {
|
||||
traceId,
|
||||
elapsed: elapsed(),
|
||||
executionSchema,
|
||||
@@ -3169,7 +3199,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
} else if (hiddenPrimaryKeys.length > 0) {
|
||||
queryMetadataSql = queryBaseSql;
|
||||
}
|
||||
console.info("[DBX][executeTabSql:execute-multi:done]", {
|
||||
queryExecutionLog("info", "execute-multi:done", {
|
||||
traceId,
|
||||
resultCount: results.length,
|
||||
rowCounts: results.map((result) => result.rows.length),
|
||||
@@ -3262,7 +3292,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
});
|
||||
}
|
||||
console.info("[DBX][executeTabSql:result:assigned]", {
|
||||
queryExecutionLog("info", "result:assigned", {
|
||||
traceId,
|
||||
activeResultIndex: current.activeResultIndex,
|
||||
rowCount: current.result?.rows.length ?? 0,
|
||||
@@ -3274,14 +3304,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
analyzeQueryMetadataInBackground(id, displayedQueryMetadataSql(current, queryMetadataSql), current.result, traceId, elapsed, hiddenPrimaryKeys);
|
||||
}
|
||||
} else {
|
||||
console.warn("[DBX][executeTabSql:stale-result]", {
|
||||
queryExecutionLog("warn", "stale-result", {
|
||||
traceId,
|
||||
currentExecutionId: current?.executionId,
|
||||
elapsed: elapsed(),
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error("[DBX][executeTabSql:error]", { traceId, elapsed: elapsed(), error: e });
|
||||
queryExecutionLog("error", "error", { traceId, elapsed: elapsed(), error: e });
|
||||
// Sync connection state if the error indicates a lost connection
|
||||
useConnectionStore().recordConnectionLostError(tab.connectionId, e);
|
||||
// Handle manual transaction auto-rollback (e.g. deadlock detected by server,
|
||||
@@ -3332,9 +3362,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.isCancelling = false;
|
||||
current.queryExecutionStartedAt = undefined;
|
||||
current.executionId = undefined;
|
||||
console.info("[DBX][executeTabSql:finish]", { traceId, elapsed: elapsed() });
|
||||
queryExecutionLog("info", "finish", { traceId, elapsed: elapsed() });
|
||||
} else {
|
||||
console.warn("[DBX][executeTabSql:finish-stale]", {
|
||||
queryExecutionLog("warn", "finish-stale", {
|
||||
traceId,
|
||||
currentExecutionId: current?.executionId,
|
||||
elapsed: elapsed(),
|
||||
@@ -3561,6 +3591,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const executionId = tab.executionId;
|
||||
if (!executionId) return false;
|
||||
tab.isCancelling = true;
|
||||
const cancellationStartedAt = performance.now();
|
||||
try {
|
||||
const canceled = await withCancelQueryTimeout(api.cancelQuery(executionId));
|
||||
if (canceled) {
|
||||
@@ -3588,6 +3619,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
current.result = toErrorResult(e);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
recordQueryCancellationLatency(performance.now() - cancellationStartedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3648,9 +3681,20 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
|
||||
async function trimResultCache() {
|
||||
const inactive = tabs.value.filter((t) => t.id !== activeTabId.value && (t.result || t.results)).sort((a, b) => (a.resultAccessedAt ?? 0) - (b.resultAccessedAt ?? 0));
|
||||
if (inactive.length > MAX_CACHED_RESULTS) {
|
||||
const toEvict = inactive.slice(0, inactive.length - MAX_CACHED_RESULTS);
|
||||
const inactive = tabs.value.filter((t) => t.id !== activeTabId.value && (t.result || t.results));
|
||||
const evictionIds = new Set(
|
||||
selectInactiveResultEvictions(
|
||||
inactive.map((tab) => ({
|
||||
id: tab.id,
|
||||
estimatedBytes: tab.resultEstimatedBytes ?? estimateQueryResultsBytes(tab.result, tab.results),
|
||||
accessedAt: tab.resultAccessedAt ?? 0,
|
||||
})),
|
||||
MAX_CACHED_RESULT_BYTES,
|
||||
MAX_CACHED_RESULTS,
|
||||
),
|
||||
);
|
||||
const toEvict = inactive.filter((tab) => evictionIds.has(tab.id));
|
||||
if (toEvict.length > 0) {
|
||||
await Promise.all(toEvict.map((t) => evictCachedResult(t)));
|
||||
}
|
||||
}
|
||||
@@ -3808,13 +3852,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return openResultArchiveTab(archive);
|
||||
}
|
||||
|
||||
async function reloadEvictedTab(id: string) {
|
||||
async function reloadEvictedTab(id: string, { reexecuteOnMissing = false }: { reexecuteOnMissing?: boolean } = {}) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (!tab || !tab.resultEvicted) return;
|
||||
if (tab.resultCacheKey) {
|
||||
const restored = restoreCachedResultPayload(tab, await readTabResultSnapshot(tab.resultCacheKey));
|
||||
if (restored) return;
|
||||
tab.resultCacheState = "missing";
|
||||
if (!reexecuteOnMissing) return;
|
||||
}
|
||||
tab.resultEvicted = false;
|
||||
const sql = tab.lastExecutedSql ?? tab.sql;
|
||||
@@ -4120,6 +4165,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
importResultArchive,
|
||||
fetchTabResultForExport,
|
||||
buildQueryResultExportRequest,
|
||||
getResourceLifecycleDiagnostics: () => resourceLifecycleDiagnostics(tabs.value),
|
||||
notifyConnectionMayBeLost,
|
||||
};
|
||||
});
|
||||
Reference in new issue
Block a user