mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(query): redirect to result view when live-message query returns rows
This commit is contained in:
@@ -210,7 +210,7 @@ import { loadQueryResultTransferSourceDdl } from "@/components/transfer/queryRes
|
||||
import { formatDdlForDisplay } from "@/lib/sql/ddlDisplay";
|
||||
import { sqlObjectNavigationTypeFromTableType } from "@/lib/sql/sqlNavigation";
|
||||
import type { CustomSaveHandler } from "@/composables/useDataGridEditor";
|
||||
import type { QueryMessage, QueryResult, QueryTab, RedisResultViewMode, TableInfoTab, TreeNode, VectorCollectionMeta } from "@/types/database";
|
||||
import type { QueryMessage, QueryResult, QueryTab, RedisResultViewMode, TableInfoTab, TabOutputView, TreeNode, VectorCollectionMeta } from "@/types/database";
|
||||
import type { SqlObjectNavigationTarget } from "@/lib/sql/sqlNavigation";
|
||||
import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
import { productionContextForDatabase } from "@/lib/database/productionSafety";
|
||||
@@ -871,6 +871,14 @@ function setRedisResultViewMode(mode: RedisResultViewMode) {
|
||||
queryStore.updateTabUiState(props.activeTab.id, { redisResultViewMode: mode });
|
||||
}
|
||||
|
||||
function selectOutputView(tabId: string, view: TabOutputView) {
|
||||
queryStore.updateTabUiState(tabId, {
|
||||
activeOutputView: view,
|
||||
autoSwitchedToMessagesDuringExecution: false,
|
||||
});
|
||||
emit("update:activeOutputView", tabId, view);
|
||||
}
|
||||
|
||||
function observeStandaloneResultToolbar() {
|
||||
standaloneResultToolbarResizeObserver?.disconnect();
|
||||
standaloneResultToolbarResizeObserver = undefined;
|
||||
@@ -1106,6 +1114,10 @@ watch(
|
||||
() => props.activeTab.liveQueryMessages?.length ?? 0,
|
||||
(count, previousCount) => {
|
||||
if (count > 0 && !previousCount && props.activeTab.isExecuting && !props.editorOnly && !hasTabularResult.value && (props.activeOutputView === "result" || props.activeOutputView === "summary")) {
|
||||
queryStore.updateTabUiState(props.activeTab.id, {
|
||||
activeOutputView: "messages",
|
||||
autoSwitchedToMessagesDuringExecution: true,
|
||||
});
|
||||
emit("update:activeOutputView", props.activeTab.id, "messages");
|
||||
}
|
||||
},
|
||||
@@ -1126,6 +1138,7 @@ watch(
|
||||
if (hasExecutionSummary.value && (!hasTabularResult.value || props.activeTab.result?.server_message === true) && props.activeOutputView === "result") {
|
||||
const result = props.activeTab.result;
|
||||
emit("update:activeOutputView", props.activeTab.id, result ? defaultViewForResult(result) : "summary");
|
||||
return;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -2539,7 +2552,7 @@ defineExpose({
|
||||
:result-mode="redisResultViewMode"
|
||||
:message-count="resultMessageCount"
|
||||
:compact="standaloneResultToolbarCompact"
|
||||
@select-view="emit('update:activeOutputView', activeTab.id, $event)"
|
||||
@select-view="selectOutputView(activeTab.id, $event)"
|
||||
@select-result-mode="setRedisResultViewMode"
|
||||
/>
|
||||
<QueryResultToolbarActions
|
||||
@@ -2551,8 +2564,8 @@ defineExpose({
|
||||
:archive-exporting="resultArchiveExporting"
|
||||
:compact="standaloneResultToolbarCompact"
|
||||
:has-result="!!activeTab.result"
|
||||
@select-explain="emit('update:activeOutputView', activeTab.id, 'explain')"
|
||||
@select-profile="emit('update:activeOutputView', activeTab.id, 'profile')"
|
||||
@select-explain="selectOutputView(activeTab.id, 'explain')"
|
||||
@select-profile="selectOutputView(activeTab.id, 'profile')"
|
||||
@export-archive="exportResultArchive"
|
||||
@open-result-view="openPluginResultView"
|
||||
/>
|
||||
@@ -2809,7 +2822,7 @@ defineExpose({
|
||||
:result-mode="redisResultViewMode"
|
||||
:message-count="resultMessageCount"
|
||||
:compact="compact"
|
||||
@select-view="emit('update:activeOutputView', activeTab.id, $event)"
|
||||
@select-view="selectOutputView(activeTab.id, $event)"
|
||||
@select-result-mode="setRedisResultViewMode"
|
||||
/>
|
||||
<template v-if="activeElasticsearchRawBody">
|
||||
@@ -2831,7 +2844,7 @@ defineExpose({
|
||||
class="inline-flex h-5 shrink-0 items-center gap-1 rounded-sm border border-transparent px-2 text-xs leading-none transition-colors"
|
||||
:class="activeOutputView === 'profile' ? 'bg-secondary text-secondary-foreground' : 'text-muted-foreground hover:text-foreground'"
|
||||
:aria-pressed="activeOutputView === 'profile'"
|
||||
@click="emit('update:activeOutputView', activeTab.id, activeOutputView === 'profile' ? 'result' : 'profile')"
|
||||
@click="selectOutputView(activeTab.id, activeOutputView === 'profile' ? 'result' : 'profile')"
|
||||
>
|
||||
<Gauge class="h-3.5 w-3.5" />
|
||||
{{ t("profile.title") }}
|
||||
@@ -2847,8 +2860,8 @@ defineExpose({
|
||||
:archive-exporting="resultArchiveExporting"
|
||||
:compact="compact"
|
||||
:has-result="!!activeTab.result"
|
||||
@select-explain="emit('update:activeOutputView', activeTab.id, 'explain')"
|
||||
@select-profile="emit('update:activeOutputView', activeTab.id, 'profile')"
|
||||
@select-explain="selectOutputView(activeTab.id, 'explain')"
|
||||
@select-profile="selectOutputView(activeTab.id, 'profile')"
|
||||
@export-archive="exportResultArchive"
|
||||
@open-result-view="openPluginResultView"
|
||||
/>
|
||||
|
||||
+31
-1
@@ -7,7 +7,7 @@ import type { QueryResult, QueryTab } from "@/types/database";
|
||||
|
||||
vi.mock("@/components/editor/QueryEditor.vue", () => ({ default: { render: () => null } }));
|
||||
vi.mock("@/components/transfer/QueryResultTransferDialog.vue", () => ({ __esModule: true, default: { render: () => null } }));
|
||||
vi.mock("@/components/grid/DataGrid.vue", () => ({ default: { render: () => h("div", { "data-test": "data-grid" }) } }));
|
||||
vi.mock("@/components/grid/DataGrid.vue", () => ({ __esModule: true, default: { render: () => h("div", { "data-test": "data-grid" }) } }));
|
||||
|
||||
import ContentArea from "../ContentArea.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
@@ -95,4 +95,34 @@ describe("live server output", () => {
|
||||
expect(host.textContent).toContain("running output");
|
||||
expect(host.querySelector('[data-test="data-grid"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("marks autoSwitchedToMessagesDuringExecution in tab uiState when live messages arrive (#11822)", async () => {
|
||||
const { state, tab } = await mountResults([], 0);
|
||||
tab.isExecuting = true;
|
||||
await nextTick();
|
||||
tab.liveQueryMessages = [{ severity: "INFO", message: "function info message" }];
|
||||
await nextTick();
|
||||
expect(state.view).toBe("messages");
|
||||
expect(useQueryStore().tabs.find((t) => t.id === tab.id)?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(true);
|
||||
});
|
||||
|
||||
it("clears autoSwitchedToMessagesDuringExecution when user explicitly selects an output view (#11822)", async () => {
|
||||
const { host, state, tab } = await mountResults([], 0);
|
||||
tab.isExecuting = true;
|
||||
await nextTick();
|
||||
tab.liveQueryMessages = [{ severity: "INFO", message: "function info message" }];
|
||||
await nextTick();
|
||||
expect(state.view).toBe("messages");
|
||||
expect(useQueryStore().tabs.find((t) => t.id === tab.id)?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(true);
|
||||
|
||||
tab.result = { columns: ["val"], rows: [[1]], affected_rows: 0, execution_time_ms: 1 };
|
||||
tab.results = [tab.result];
|
||||
await nextTick();
|
||||
|
||||
const buttons = host.querySelectorAll<HTMLButtonElement>("[data-query-result-view-switcher] button");
|
||||
buttons[0]?.click();
|
||||
await nextTick();
|
||||
expect(state.view).toBe("result");
|
||||
expect(useQueryStore().tabs.find((t) => t.id === tab.id)?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+179
-1
@@ -778,7 +778,10 @@ SELECT @value AS Message;`;
|
||||
// Mirrors the real order of events: the notice is surfaced live and the view moves to
|
||||
// Messages, then the statement finishes with a tabular result.
|
||||
activeOutputView.value = "messages";
|
||||
if (activeTab.value) activeTab.value.result = { columns: ["log_and_return"], rows: [["done"]], affected_rows: 0, execution_time_ms: 1, messages: [{ severity: "NOTICE", message: "working" }] };
|
||||
if (activeTab.value) {
|
||||
activeTab.value.uiState = { ...activeTab.value.uiState, autoSwitchedToMessagesDuringExecution: true };
|
||||
activeTab.value.result = { columns: ["log_and_return"], rows: [["done"]], affected_rows: 0, execution_time_ms: 1, messages: [{ severity: "NOTICE", message: "working" }] };
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
@@ -793,6 +796,7 @@ SELECT @value AS Message;`;
|
||||
|
||||
expect(activeTab.value?.result?.rows).toEqual([["done"]]);
|
||||
expect(activeOutputView.value).toBe("result");
|
||||
expect(activeTab.value?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps Messages selected when a completed run produced no tabular result", async () => {
|
||||
@@ -853,6 +857,180 @@ SELECT @value AS Message;`;
|
||||
expect(activeOutputView.value).toBe("messages");
|
||||
});
|
||||
|
||||
it("redirects from messages to result when tabular data is produced after auto-switching (#11822)", async () => {
|
||||
const sql = "SELECT * FROM test_func_with_info()";
|
||||
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("result");
|
||||
vi.spyOn(useQueryStore(), "executeCurrentSql").mockImplementation(async () => {
|
||||
activeOutputView.value = "messages";
|
||||
if (activeTab.value) {
|
||||
activeTab.value.uiState = { ...activeTab.value.uiState, autoSwitchedToMessagesDuringExecution: true };
|
||||
activeTab.value.result = {
|
||||
columns: ["val"],
|
||||
rows: [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
messages: [{ severity: "INFO", message: "function info message" }],
|
||||
};
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => sql),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute();
|
||||
|
||||
expect(activeOutputView.value).toBe("result");
|
||||
expect(activeTab.value?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(false);
|
||||
});
|
||||
|
||||
it("does not redirect from messages when query fails with error result (#11822)", async () => {
|
||||
const sql = "SELECT * FROM test_func_with_error()";
|
||||
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("result");
|
||||
vi.spyOn(useQueryStore(), "executeCurrentSql").mockImplementation(async () => {
|
||||
activeOutputView.value = "messages";
|
||||
if (activeTab.value) {
|
||||
activeTab.value.uiState = { ...activeTab.value.uiState, autoSwitchedToMessagesDuringExecution: true };
|
||||
activeTab.value.result = {
|
||||
columns: ["Error"],
|
||||
execution_error: true,
|
||||
rows: [["function exception"]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
messages: [{ severity: "INFO", message: "function info message" }],
|
||||
};
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => sql),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute();
|
||||
|
||||
expect(activeOutputView.value).toBe("messages");
|
||||
expect(activeTab.value?.uiState?.autoSwitchedToMessagesDuringExecution).toBe(false);
|
||||
});
|
||||
|
||||
it("does not redirect when user manually switched to messages mid-run (#11822)", async () => {
|
||||
const sql = "SELECT * FROM test_func_with_info()";
|
||||
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("result");
|
||||
vi.spyOn(useQueryStore(), "executeCurrentSql").mockImplementation(async () => {
|
||||
activeOutputView.value = "messages";
|
||||
if (activeTab.value) {
|
||||
// User manually selected messages mid-run, so autoSwitchedToMessagesDuringExecution is false
|
||||
activeTab.value.uiState = { ...activeTab.value.uiState, autoSwitchedToMessagesDuringExecution: false };
|
||||
activeTab.value.result = {
|
||||
columns: ["val"],
|
||||
rows: [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => sql),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute();
|
||||
|
||||
expect(activeOutputView.value).toBe("messages");
|
||||
});
|
||||
|
||||
it("honors multiStatementDefaultView when redirecting multi-statement execution (#11822)", async () => {
|
||||
const sql = "SELECT 1; SELECT 2;";
|
||||
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("result");
|
||||
const settingsStore = useSettingsStore();
|
||||
settingsStore.editorSettings.multiStatementDefaultView = "summary";
|
||||
|
||||
vi.spyOn(useQueryStore(), "executeCurrentSql").mockImplementation(async () => {
|
||||
activeOutputView.value = "messages";
|
||||
if (activeTab.value) {
|
||||
activeTab.value.uiState = { ...activeTab.value.uiState, autoSwitchedToMessagesDuringExecution: true };
|
||||
activeTab.value.result = {
|
||||
columns: ["val"],
|
||||
rows: [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
activeTab.value.results = [activeTab.value.result];
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => sql),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute();
|
||||
|
||||
expect(activeOutputView.value).toBe("summary");
|
||||
});
|
||||
|
||||
it("redirects background tab uiState to result when query finishes while tab is inactive (#11822)", async () => {
|
||||
const sql = "SELECT * FROM test_func_with_info()";
|
||||
const tab1: QueryTab = { ...queryTab("app"), id: "tab-1", sql, uiState: { activeOutputView: "messages", autoSwitchedToMessagesDuringExecution: true } };
|
||||
const tab2: QueryTab = { ...queryTab("app"), id: "tab-2", sql: "SELECT 2", uiState: { activeOutputView: "chart" } };
|
||||
const queryStore = useQueryStore();
|
||||
queryStore.tabs = [tab1, tab2];
|
||||
const activeTab = ref<QueryTab | undefined>(tab2);
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("postgres"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart" | "messages">("chart");
|
||||
|
||||
vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async (_sql, options) => {
|
||||
const targetTab = queryStore.tabs.find((t) => t.id === options?.tabId);
|
||||
if (targetTab) {
|
||||
targetTab.uiState = { ...targetTab.uiState, activeOutputView: "messages", autoSwitchedToMessagesDuringExecution: true };
|
||||
targetTab.result = {
|
||||
columns: ["val"],
|
||||
rows: [[1]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 1,
|
||||
};
|
||||
}
|
||||
});
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => sql),
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute(undefined, { tabId: tab1.id });
|
||||
|
||||
// Active tab (tab2) output view should remain untouched
|
||||
expect(activeOutputView.value).toBe("chart");
|
||||
// Background tab (tab1) uiState should have redirected to "result"
|
||||
expect(tab1.uiState?.activeOutputView).toBe("result");
|
||||
expect(tab1.uiState?.autoSwitchedToMessagesDuringExecution).toBe(false);
|
||||
});
|
||||
|
||||
it("forwards execute-in-new-result-tab intent to the query store", async () => {
|
||||
const sql = "SELECT * FROM users";
|
||||
const activeTab = ref<QueryTab | undefined>({ ...queryTab("app"), sql });
|
||||
|
||||
@@ -224,6 +224,13 @@ export function useSqlExecution(deps: {
|
||||
return deps.activeTab.value?.id === tabId ? deps.activeTab.value : undefined;
|
||||
}
|
||||
|
||||
function updateExecutionTabUiState(tab: QueryTab, patch: Partial<NonNullable<QueryTab["uiState"]>>) {
|
||||
queryStore.updateTabUiState(tab.id, patch);
|
||||
if (!queryStore.tabs.some((candidate) => candidate.id === tab.id)) {
|
||||
tab.uiState = { ...tab.uiState, ...patch };
|
||||
}
|
||||
}
|
||||
|
||||
function resolveExecutionConnection(tabId: string | undefined): ConnectionConfig | undefined {
|
||||
const tab = resolveExecutionTab(tabId);
|
||||
return (tab ? connectionStore.getConfig(tab.connectionId) : undefined) ?? deps.activeConnection.value;
|
||||
@@ -480,6 +487,7 @@ export function useSqlExecution(deps: {
|
||||
if (deps.activeTab.value?.id === executionTabId) {
|
||||
deps.activeOutputView.value = redisConsoleSelected ? "result" : statementCount > 1 ? settingsStore.editorSettings.multiStatementDefaultView : "result";
|
||||
}
|
||||
updateExecutionTabUiState(tab, { autoSwitchedToMessagesDuringExecution: false });
|
||||
const connName = executionConnection?.name || "";
|
||||
const start = Date.now();
|
||||
const isRedis = executionDatabaseType === "redis";
|
||||
@@ -497,53 +505,62 @@ export function useSqlExecution(deps: {
|
||||
cancelEditorViewportRequest(options.editorViewportRequestId);
|
||||
return notStarted("busy", executionTabId);
|
||||
}
|
||||
const currentTab = resolveExecutionTab(executionTabId) ?? tab;
|
||||
const executionTabStillActive = deps.activeTab.value?.id === executionTabId;
|
||||
const sqlServerMessageResultIndex = executionDatabaseType === "sqlserver" ? tab.results?.findIndex((result) => result.server_message === true) : undefined;
|
||||
const sqlServerMessageResultIndex = executionDatabaseType === "sqlserver" ? currentTab.results?.findIndex((result) => result.server_message === true) : undefined;
|
||||
if (sqlServerMessageResultIndex !== undefined && sqlServerMessageResultIndex >= 0) {
|
||||
focusSqlServerDataResult(tab.id, executionDatabaseType, tab);
|
||||
focusSqlServerDataResult(currentTab.id, executionDatabaseType, currentTab);
|
||||
if (executionTabStillActive) {
|
||||
deps.activeOutputView.value = tab.result?.server_message === true ? "messages" : "result";
|
||||
deps.activeOutputView.value = currentTab.result?.server_message === true ? "messages" : "result";
|
||||
}
|
||||
} else if (executionDatabaseType === "sqlserver" && tab.result?.server_message === true) {
|
||||
} else if (executionDatabaseType === "sqlserver" && currentTab.result?.server_message === true) {
|
||||
if (executionTabStillActive) {
|
||||
deps.activeOutputView.value = "messages";
|
||||
}
|
||||
} else if (tab.result && !tab.result.columns.length && !tab.results?.some((result) => result.columns.length > 0)) {
|
||||
} else if (currentTab.result && !currentTab.result.columns.length && !currentTab.results?.some((result) => result.columns.length > 0)) {
|
||||
if (executionTabStillActive && deps.activeOutputView.value !== "messages") {
|
||||
deps.activeOutputView.value = statementCount === 1 ? defaultViewForResult(tab.result) : "summary";
|
||||
deps.activeOutputView.value = statementCount === 1 ? defaultViewForResult(currentTab.result) : "summary";
|
||||
}
|
||||
} else if (currentTab.uiState?.autoSwitchedToMessagesDuringExecution) {
|
||||
const hasTabularData =
|
||||
!firstQueryExecutionError(currentTab) &&
|
||||
!!((currentTab.result?.columns.length && currentTab.result.server_message !== true && !isQueryExecutionErrorResult(currentTab.result)) || currentTab.results?.some((result) => result.columns.length > 0 && result.server_message !== true && !isQueryExecutionErrorResult(result)));
|
||||
if (hasTabularData) {
|
||||
const targetView = statementCount > 1 && settingsStore.editorSettings.multiStatementDefaultView === "summary" ? "summary" : "result";
|
||||
updateExecutionTabUiState(currentTab, {
|
||||
activeOutputView: targetView,
|
||||
autoSwitchedToMessagesDuringExecution: false,
|
||||
});
|
||||
if (executionTabStillActive) {
|
||||
deps.activeOutputView.value = targetView;
|
||||
}
|
||||
}
|
||||
} else if (executionTabStillActive && deps.activeOutputView.value === "messages" && ((tab.result?.columns.length ?? 0) > 0 || tab.results?.some((result) => result.columns.length > 0))) {
|
||||
// A notice can arrive before the statement produces its rows, so the live-output watcher
|
||||
// in ContentArea advances to Messages while it cannot yet know that data will follow.
|
||||
// Once the run finishes with a real result set, show the data again; Messages stays one
|
||||
// click away in the view switcher. Message-only runs and SQL Server PRINT batches are
|
||||
// handled by the branches above and keep their existing behavior.
|
||||
deps.activeOutputView.value = "result";
|
||||
}
|
||||
updateExecutionTabUiState(currentTab, { autoSwitchedToMessagesDuringExecution: false });
|
||||
const elapsed = Date.now() - start;
|
||||
const failure = firstQueryExecutionError(tab);
|
||||
const failure = firstQueryExecutionError(currentTab);
|
||||
const success = !failure;
|
||||
historyStore.add({
|
||||
connection_id: tab.connectionId,
|
||||
connection_id: currentTab.connectionId,
|
||||
connection_name: connName,
|
||||
database: tab.database,
|
||||
database: currentTab.database,
|
||||
sql,
|
||||
execution_time_ms: elapsed,
|
||||
success,
|
||||
error: failure ? (failure.error ? translateBackendError(t, failure.error, failure.rows?.[0]?.[0]) : String(failure.rows?.[0]?.[0] ?? "")) : undefined,
|
||||
activity_kind: classifySqlActivityKind(sql),
|
||||
operation: primarySqlOperation(sql),
|
||||
affected_rows: success ? tab.result?.affected_rows : undefined,
|
||||
affected_rows: success ? currentTab.result?.affected_rows : undefined,
|
||||
});
|
||||
if (success) {
|
||||
const refreshTarget = sqlMetadataRefreshTarget(sql, tab.schema);
|
||||
const refreshTarget = sqlMetadataRefreshTarget(sql, currentTab.schema);
|
||||
if (refreshTarget.scope === "connection") {
|
||||
connectionStore.invalidateMetadataCache(tab.connectionId);
|
||||
await invalidateObjectMetadataCache({ connectionId: tab.connectionId });
|
||||
await connectionStore.loadDatabases(tab.connectionId, { force: true });
|
||||
connectionStore.invalidateMetadataCache(currentTab.connectionId);
|
||||
await invalidateObjectMetadataCache({ connectionId: currentTab.connectionId });
|
||||
await connectionStore.loadDatabases(currentTab.connectionId, { force: true });
|
||||
} else if (refreshTarget.scope === "database") {
|
||||
await invalidateObjectMetadataCache({ connectionId: tab.connectionId, database: tab.database, schema: refreshTarget.schema });
|
||||
await connectionStore.refreshObjectListTreeNode(tab.connectionId, tab.database, refreshTarget.schema);
|
||||
await invalidateObjectMetadataCache({ connectionId: currentTab.connectionId, database: currentTab.database, schema: refreshTarget.schema });
|
||||
await connectionStore.refreshObjectListTreeNode(currentTab.connectionId, currentTab.database, refreshTarget.schema);
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -2076,6 +2076,8 @@ export interface TabUiState {
|
||||
/** Redis query results default to grid; a per-tab override selects command-line output. */
|
||||
redisResultViewMode?: RedisResultViewMode;
|
||||
resultPaneOpen?: boolean;
|
||||
/** Tracks whether the active output view was auto-switched to "messages" by live server output during execution. */
|
||||
autoSwitchedToMessagesDuringExecution?: boolean;
|
||||
/** Small JSON-compatible snapshots owned by special-page components. */
|
||||
page?: Record<string, TabPageUiState>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user