feat(sqlserver): add activity trace

This commit is contained in:
zipg authored and GitHub committed 2026-08-13 11:49:54 +08:00
1 parent e5a3917f7d
commit 982778128e
24 files changed
+2108 -10

No files matched your search

@@ -0,0 +1,43 @@
import { readFileSync } from "node:fs";
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
disposeSqlServerActivityTrace: vi.fn(() => Promise.resolve()),
disposeAllSqlServerActivityTraces: vi.fn(() => Promise.resolve()),
disposeSqlServerActivityTracesForConnection: vi.fn(() => Promise.resolve()),
cleanupStaleSqlServerTraceSessions: vi.fn(() => Promise.resolve(0)),
hasSqlServerActivityTraceForConnection: vi.fn(() => false),
}));
vi.mock("@/lib/sqlserver/sqlServerActivityTraceRuntime", () => mocks);
const connectionStoreSource = readFileSync(new URL("../connectionStore.ts", import.meta.url), "utf8");
describe("queryStore SQL Server trace lifecycle", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("localStorage", {
getItem: vi.fn(() => null),
setItem: vi.fn(),
removeItem: vi.fn(),
});
setActivePinia(createPinia());
});
it("disposes the trace runtime only when its tab is explicitly closed", async () => {
const { useQueryStore } = await import("@/stores/queryStore");
const store = useQueryStore();
const tabId = store.openSqlServerActivityTrace("sqlserver-1");
store.switchTab(store.createTab("sqlserver-1", "app", "query_1"));
expect(mocks.disposeSqlServerActivityTrace).not.toHaveBeenCalled();
store.closeTab(tabId);
expect(mocks.disposeSqlServerActivityTrace).toHaveBeenCalledWith(tabId);
});
it("does not scan trace sessions on ordinary connection success", () => {
expect(connectionStoreSource).not.toContain("cleanupStaleSqlServerTraceSessions");
});
});
+4 -1
View File
@@ -75,6 +75,7 @@ import { loadTimeoutInheritanceBackup, saveTimeoutInheritanceBackup } from "@/li
import { migrateSqlServerLegacyCompatibilityConfig, requiresSqlServerLegacyCompatibilityComponent, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility";
import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp";
import { deleteTabResultSnapshotsForOwner } from "@/lib/tabs/tabResultCache";
import { disposeSqlServerActivityTracesForConnection, hasSqlServerActivityTraceForConnection } from "@/lib/sqlserver/sqlServerActivityTraceRuntime";
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases";
import {
buildObjectGroupPlaceholderNodes,
@@ -2931,9 +2932,10 @@ export const useConnectionStore = defineStore("connection", () => {
async function disconnect(connectionId: string) {
const stateRevision = bumpConnectionStateRevision(connectionId);
const shouldRemoveOneTimeConnection = getConfig(connectionId)?.one_time === true;
if (hasSqlServerActivityTraceForConnection(connectionId)) await disposeSqlServerActivityTracesForConnection(connectionId);
const disconnectRequest = startDisconnectRequest(connectionId);
cancelLocalConnectionAttempt(connectionId);
const shouldRemoveOneTimeConnection = getConfig(connectionId)?.one_time === true;
connectedIds.value.delete(connectionId);
clearPrimaryVisibleObjectNames(connectionId);
@@ -2978,6 +2980,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
async function closeDatabaseConnection(connectionId: string, database: string) {
if (hasSqlServerActivityTraceForConnection(connectionId, database)) await disposeSqlServerActivityTracesForConnection(connectionId, database);
await api.closeDatabaseConnection(connectionId, database);
const { useQueryStore } = await import("@/stores/queryStore");
const queryStore = useQueryStore();
+30
View File
@@ -71,6 +71,7 @@ import { ensureSqlExtension } from "@/lib/savedSql/savedSqlFileName";
import { resolveSavedSqlExecutionTarget, savedSqlExecutionTargetFromTab, type SavedSqlExecutionTarget, type SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget";
import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage";
import { sqlTextFingerprint } from "@/lib/sql/sqlTextFingerprint";
import { disposeAllSqlServerActivityTraces, disposeSqlServerActivityTrace } from "@/lib/sqlserver/sqlServerActivityTraceRuntime";
import type { SavedSqlFile } from "@/types/database";
import i18n from "@/i18n";
import { translateBackendError } from "@/i18n/backend-errors";
@@ -1586,6 +1587,7 @@ export const useQueryStore = defineStore("query", () => {
onScopeDispose(() => {
if (persistTimer) clearTimeout(persistTimer);
persistTimer = null;
void disposeAllSqlServerActivityTraces();
});
// Immediately flush any pending debounced persist so the on-disk content
@@ -1834,6 +1836,31 @@ export const useQueryStore = defineStore("query", () => {
return id;
}
function openSqlServerActivityTrace(connectionId: string) {
const existing = tabs.value.find((tab) => tab.mode === "sqlserver-trace" && tab.connectionId === connectionId);
if (existing) {
switchTab(existing.id);
return existing.id;
}
const conn = useConnectionStore().getConfig(connectionId);
const id = uuid();
const tab: QueryTab = {
id,
title: conn?.name ? `${conn.name} - ${t("sqlServerTrace.title")}` : t("sqlServerTrace.title"),
connectionId,
database: conn?.database || "",
sql: "",
isExecuting: false,
isCancelling: false,
isExplaining: false,
mode: "sqlserver-trace",
};
tabs.value.push(tab);
activeTabId.value = id;
return id;
}
function openMysqlDashboard(connectionId: string) {
const existing = tabs.value.find((tab) => tab.mode === "mysql-dashboard" && tab.connectionId === connectionId);
if (existing) {
@@ -2376,6 +2403,7 @@ export const useQueryStore = defineStore("query", () => {
const idx = tabs.value.findIndex((t) => t.id === id);
if (idx < 0) return;
persistSavedSqlEditorPosition(tabs.value[idx]);
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
clearDataGridPendingSnapshotsForTab(id);
if (tabs.value[idx].txnSessionId) void rollbackTransaction(id);
if (tabs.value[idx].isExecuting) void cancelTabExecution(id);
@@ -2657,6 +2685,7 @@ export const useQueryStore = defineStore("query", () => {
tabs.value
.filter((tab) => closingIds.has(tab.id))
.forEach((tab) => {
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
clearDataGridPendingSnapshotsForTab(tab.id);
if (tab.txnSessionId) void rollbackTransaction(tab.id);
if (tab.isExecuting) void cancelTabExecution(tab.id);
@@ -5931,6 +5960,7 @@ export const useQueryStore = defineStore("query", () => {
openMongoBucket,
openUserAdmin,
openProcessList,
openSqlServerActivityTrace,
openMysqlDashboard,
openPostgresDashboard,
openNacosDashboard,