diff --git a/apps/desktop/src/composables/useDataGridActions.ts b/apps/desktop/src/composables/useDataGridActions.ts index 07e96f90b..ad647a8f9 100644 --- a/apps/desktop/src/composables/useDataGridActions.ts +++ b/apps/desktop/src/composables/useDataGridActions.ts @@ -52,7 +52,7 @@ export function useDataGridActions(activeTab: ComputedRef) const tab = activeTab.value; if (!tab) return; queryStore.updateSql(tab.id, sql); - await queryStore.executeTabSql(tab.id, sql); + await queryStore.executeTabSql(tab.id, sql, { preserveResultDuringExecution: true }); } async function onReloadData( @@ -71,13 +71,17 @@ export function useDataGridActions(activeTab: ComputedRef) const pageOffset = offset ?? 0; const nextSql = await buildTableSql(tab, { whereInput, orderBy, limit: pageLimit, offset: pageOffset }); queryStore.updateSql(tab.id, nextSql); - await queryStore.executeTabSql(tab.id, nextSql, { pagination: { limit: pageLimit, offset: pageOffset } }); + await queryStore.executeTabSql(tab.id, nextSql, { + pagination: { limit: pageLimit, offset: pageOffset }, + preserveResultDuringExecution: true, + }); return; } if (tab.resultSortedSql) { await queryStore.executeTabSql(tab.id, tab.resultSortedSql, { resultBaseSql: tab.resultBaseSql ?? tab.sql, resultSortedSql: tab.resultSortedSql, + preserveResultDuringExecution: true, }); return; } @@ -85,6 +89,7 @@ export function useDataGridActions(activeTab: ComputedRef) await queryStore.executeTabSql(tab.id, sql, { resultBaseSql: sql, resultSortedSql: undefined, + preserveResultDuringExecution: true, }); return; } @@ -106,6 +111,7 @@ export function useDataGridActions(activeTab: ComputedRef) resultBaseSql: tab.resultBaseSql ?? tab.sql, resultSortedSql: tab.resultSortedSql, pagination: { offset, limit, sessionId }, + preserveResultDuringExecution: true, }); return; } @@ -114,7 +120,10 @@ export function useDataGridActions(activeTab: ComputedRef) tab.whereInput = whereInput ?? ""; const sql = await buildTableSql(tab, { limit, offset, whereInput, orderBy }); queryStore.updateSql(tab.id, sql); - await queryStore.executeTabSql(tab.id, sql, { pagination: { offset, limit } }); + await queryStore.executeTabSql(tab.id, sql, { + pagination: { offset, limit }, + preserveResultDuringExecution: true, + }); } async function onSort(column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string) { @@ -134,7 +143,7 @@ export function useDataGridActions(activeTab: ComputedRef) : undefined; const sql = await buildTableSql(tab, { orderBy, whereInput }); queryStore.updateSql(tab.id, sql); - await queryStore.executeCurrentTab(); + await queryStore.executeTabSql(tab.id, sql, { preserveResultDuringExecution: true }); return; } @@ -145,6 +154,7 @@ export function useDataGridActions(activeTab: ComputedRef) await queryStore.executeTabSql(tab.id, baseSql, { resultBaseSql: baseSql, resultSortedSql: undefined, + preserveResultDuringExecution: true, }); return; } @@ -166,6 +176,7 @@ export function useDataGridActions(activeTab: ComputedRef) await queryStore.executeTabSql(tab.id, built.sql, { resultBaseSql: baseSql, resultSortedSql: built.sql, + preserveResultDuringExecution: true, }); } diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index 34275501b..41f56122a 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -665,6 +665,7 @@ export const useQueryStore = defineStore("query", () => { resultSortedSql?: string | undefined; pagination?: { limit: number; offset: number; sessionId?: string }; mongoSafety?: MongoAggregateSafetyOptions; + preserveResultDuringExecution?: boolean; }, ) { const tab = tabs.value.find((t) => t.id === id); @@ -680,7 +681,9 @@ export const useQueryStore = defineStore("query", () => { tab.lastExecutedSql = sql; tab.resultTotalRowCount = undefined; const previousResultSessionClose = closeResultSession(tab, options?.pagination?.sessionId); - clearResultPayload(tab); + if (!options?.preserveResultDuringExecution || !tab.result) { + clearResultPayload(tab); + } console.info("[DBX][executeTabSql:start]", { traceId, tabId: id, diff --git a/packages/app-tests/queryStore.test.ts b/packages/app-tests/queryStore.test.ts index fd624b8a5..46a40ff88 100644 --- a/packages/app-tests/queryStore.test.ts +++ b/packages/app-tests/queryStore.test.ts @@ -496,6 +496,66 @@ test("starting a new query clears the previous result payload immediately", asyn } }); +test("grid refreshes can preserve the previous result while loading", async () => { + const restoreStorage = installMemoryStorage(); + setActivePinia(createPinia()); + const connectionStore = useConnectionStore(); + const store = useQueryStore(); + const originalFetch = globalThis.fetch; + + connectionStore.addEphemeralConnection(conn("conn-1")); + const tabId = store.createTab("conn-1", "db", "Query"); + const tab = store.tabs.find((item) => item.id === tabId); + assert.ok(tab); + const previousResult: QueryResult = { + columns: ["id", "name"], + rows: [[1, "Ada"]], + affected_rows: 0, + execution_time_ms: 1, + }; + tab.result = previousResult; + + globalThis.fetch = (async (input) => { + const url = String(input); + if (url === "/api/query/prepare-pagination-plan") { + return new Response(JSON.stringify({ sqlToExecute: "select 1 order by name", useAgentResultSession: false }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + if (url === "/api/query/execute-multi") { + return new Response( + JSON.stringify([{ columns: ["id", "name"], rows: [[2, "Grace"]], affected_rows: 0, execution_time_ms: 1 }]), + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); + } + if (url === "/api/query/analyze-editability") { + return new Response(JSON.stringify({ editable: false, reason: "complex-source" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response("unexpected request", { status: 500 }); + }) as typeof fetch; + + try { + const execution = store.executeTabSql(tabId, "select 1 order by name", { + preserveResultDuringExecution: true, + }); + assert.deepEqual(tab.result?.columns, previousResult.columns); + assert.deepEqual(tab.result?.rows, previousResult.rows); + assert.equal(tab.isExecuting, true); + await execution; + assert.deepEqual(tab.result?.rows, [[2, "Grace"]]); + } finally { + globalThis.fetch = originalFetch; + restoreStorage(); + } +}); + test("data tab execution preserves pagination offset metadata", async () => { const restoreStorage = installMemoryStorage(); setActivePinia(createPinia());