From c724fd46b0120f979c8a12c0189ffe59108ac469 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 17 Aug 2026 10:24:08 +0000 Subject: [PATCH] fix(grid): preserve page size when final fetch is narrowed Closes #6478 --- apps/desktop/src/stores/queryStore.ts | 23 +++-- packages/app-tests/queryStore.test.ts | 130 +++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index bfedacc243..8c34dbd76d 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -4036,6 +4036,7 @@ export const useQueryStore = defineStore("query", () => { let hiddenPrimaryKeys: HiddenPrimaryKeyProjection[] = []; let useOracleLobPreview = false; let pageSql: string | undefined; + let requestedPageLimit: number | undefined; let pageLimit: number | undefined; let pageOffset: number | undefined; let countSql: string | undefined; @@ -4626,7 +4627,9 @@ export const useQueryStore = defineStore("query", () => { sqlToExecute = sorted.sql; resultSortedSql = sorted.sql; } - const pagination = limitQueryPagination(options?.pagination ?? { limit: settingsStore.editorSettings.pageSize, offset: 0 }, queryResultMaxRows); + const requestedPagination = options?.pagination ?? { limit: settingsStore.editorSettings.pageSize, offset: 0 }; + requestedPageLimit = requestedPagination.limit; + const pagination = limitQueryPagination(requestedPagination, queryResultMaxRows); const plan = await api.prepareQueryPaginationExecutionPlan({ sql: sqlToExecute, queryBaseSql, @@ -4658,13 +4661,12 @@ export const useQueryStore = defineStore("query", () => { return false; } } else if (tab.mode === "data") { - const pagination = limitQueryPagination( - { - limit: options?.pagination?.limit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize), - offset: options?.pagination?.offset ?? 0, - }, - queryResultMaxRows, - ); + const requestedPagination = { + limit: options?.pagination?.limit ?? tableOpenPageLimit(settingsStore.editorSettings.tableOpenPageSize), + offset: options?.pagination?.offset ?? 0, + }; + requestedPageLimit = requestedPagination.limit; + const pagination = limitQueryPagination(requestedPagination, queryResultMaxRows); pageLimit = pagination.limit; pageOffset = pagination.offset; } @@ -4828,7 +4830,8 @@ export const useQueryStore = defineStore("query", () => { // Keep the base page state so later table refresh/cache recovery does // not re-execute only the most recently fetched tail segment. current.resultPageSql = shouldAppendResult ? (current.resultPageSql ?? pageSql) : pageSql; - current.resultPageLimit = pageLimit; + const displayPageLimit = typeof pageLimit === "number" ? (requestedPageLimit ?? pageLimit) : undefined; + current.resultPageLimit = shouldAppendResult ? (current.resultPageLimit ?? displayPageLimit) : displayPageLimit; current.resultPageOffset = shouldAppendResult ? (current.resultPageOffset ?? 0) : pageOffset; current.resultCountSql = countSql; current.resultSessionId = current.result?.session_id ?? undefined; @@ -4983,7 +4986,7 @@ export const useQueryStore = defineStore("query", () => { current.resultBaseSql = shouldReplaceActiveResultInGroup ? (current.resultBaseSql ?? queryBaseSql) : queryBaseSql; current.resultSortedSql = resultSortedSql; current.resultPageSql = pageSql; - current.resultPageLimit = pageLimit; + current.resultPageLimit = typeof pageLimit === "number" ? (requestedPageLimit ?? pageLimit) : undefined; current.resultPageOffset = pageOffset; current.resultCountSql = countSql; current.resultSessionId = undefined; diff --git a/packages/app-tests/queryStore.test.ts b/packages/app-tests/queryStore.test.ts index 4f1480427f..cf45fb1077 100644 --- a/packages/app-tests/queryStore.test.ts +++ b/packages/app-tests/queryStore.test.ts @@ -3494,6 +3494,8 @@ test("append pagination preserves existing rows and respects the memory cap", as affected_rows: 0, execution_time_ms: 3, }; + tab.resultPageLimit = 1000; + tab.resultPageOffset = 0; globalThis.fetch = withConnectionHealthMock(async (input) => { if (String(input) === "/api/query/execute-multi") { @@ -3527,7 +3529,7 @@ test("append pagination preserves existing rows and respects the memory cap", as assert.equal(tab.result?.execution_time_ms, 7); assert.equal(tab.result?.has_more, false); assert.equal(tab.resultPageOffset, 0, "later refreshes must restart from the logical result origin"); - assert.equal(tab.resultPageLimit, 2); + assert.equal(tab.resultPageLimit, 1000, "a short appended segment must preserve the base display page size"); } finally { globalThis.fetch = originalFetch; restoreStorage(); @@ -6807,6 +6809,132 @@ test("query execution keeps automatically counting total rows in the background" } }); +test("SQL Server keeps the requested page size when the final fetch is narrowed", async () => { + const restoreStorage = installMemoryStorage(); + setActivePinia(createPinia()); + const connectionStore = useConnectionStore(); + const settingsStore = useSettingsStore(); + const store = useQueryStore(); + const originalFetch = globalThis.fetch; + let executeBody: any; + let prepareBody: any; + let countRequests = 0; + let prepareRequests = 0; + let executeRequests = 0; + let failExecution = false; + let paginationPlan = { + pageLimit: 1, + pageOffset: 9999, + }; + + settingsStore.updateEditorSettings({ + autoCalculateTotalRows: true, + queryResultMaxRowsEnabled: false, + pageSize: 1000, + }); + connectionStore.addEphemeralConnection(sqlServerConn("sqlserver-final-page")); + const tabId = store.createTab("sqlserver-final-page", "db", "Query", "query", "dbo"); + const tab = store.tabs.find((item) => item.id === tabId); + assert.ok(tab); + + globalThis.fetch = withConnectionHealthMock(async (input, init) => { + const url = String(input); + if (url === "/api/query/prepare-pagination-plan") { + prepareRequests += 1; + prepareBody = JSON.parse(String(init?.body ?? "{}")); + return Response.json({ + sqlToExecute: "SELECT TOP 10000 1 AS [id]", + pageSql: "SELECT TOP 1 1 AS [id] OFFSET 9999 ROWS", + ...paginationPlan, + exactQueryRowBound: 10_000, + useAgentResultSession: false, + }); + } + if (url === "/api/query/execute-multi") { + executeRequests += 1; + executeBody = JSON.parse(String(init?.body ?? "{}")); + if (failExecution) return new Response("final page failed", { status: 500 }); + return Response.json([ + { + columns: ["id"], + rows: [[10_000]], + affected_rows: 0, + execution_time_ms: 1, + }, + ]); + } + if (url === "/api/query/execute") { + countRequests += 1; + return new Response("unexpected count", { status: 500 }); + } + if (url === "/api/query/analyze-editability") { + return Response.json({ editable: false, reason: "complex-source" }); + } + return new Response("unexpected request", { status: 500 }); + }); + + try { + await store.executeTabSql(tabId, "SELECT TOP 10000 1 AS [id]", { + pagination: { limit: 1000, offset: 9000 }, + }); + + assert.equal(executeBody.maxRows, 1, "the narrowed execution limit must remain effective"); + assert.equal(executeBody.fetchSize, 1, "the narrowed fetch size must remain effective"); + assert.equal(tab.resultPageLimit, 1000, "the requested rows-per-page value must remain visible"); + assert.equal(tab.resultPageOffset, 9999); + assert.equal(Math.floor(tab.resultPageOffset / tab.resultPageLimit) + 1, 10); + + paginationPlan = { pageLimit: 1000, pageOffset: 9000 }; + await store.executeTabSql(tabId, "SELECT TOP 10000 1 AS [id]", { + pagination: { limit: 1000, offset: 9000 }, + }); + assert.equal(executeBody.maxRows, 1000); + assert.equal(executeBody.fetchSize, 1000); + assert.equal(tab.resultPageLimit, 1000); + assert.equal(Math.floor(tab.resultPageOffset! / tab.resultPageLimit) + 1, 10); + + paginationPlan = { pageLimit: 500, pageOffset: 0 }; + await store.executeTabSql(tabId, "SELECT TOP 10000 1 AS [id]", { + pagination: { limit: 500, offset: 0 }, + }); + assert.equal(executeBody.maxRows, 500); + assert.equal(tab.resultPageLimit, 500, "an explicit page-size change must still update the display state"); + assert.equal(Math.floor(tab.resultPageOffset! / tab.resultPageLimit) + 1, 1); + + paginationPlan = { pageLimit: 1, pageOffset: 9999 }; + failExecution = true; + await store.executeTabSql(tabId, "SELECT TOP 10000 1 AS [id]", { + pagination: { limit: 1000, offset: 9000 }, + }); + assert.equal(executeBody.maxRows, 1); + assert.equal(tab.resultPageLimit, 1000, "a failed narrowed fetch must not collapse the display state"); + assert.equal(tab.resultPageOffset, 9999); + + failExecution = false; + settingsStore.updateEditorSettings({ + queryResultMaxRowsEnabled: true, + queryResultMaxRows: 9001, + }); + paginationPlan = { pageLimit: 1, pageOffset: 9000 }; + await store.executeTabSql(tabId, "SELECT TOP 10000 1 AS [id]", { + pagination: { limit: 1000, offset: 9000 }, + }); + assert.deepEqual(prepareBody.options.pagination, { limit: 1, offset: 9000 }, "the enabled row cap must still narrow the execution plan"); + assert.equal(executeBody.maxRows, 1); + assert.equal(executeBody.fetchSize, 1); + assert.equal(tab.resultPageLimit, 1000, "the max-row cap must not replace the requested display size"); + assert.equal(tab.resultPageOffset, 9000); + assert.equal(Math.floor(tab.resultPageOffset / tab.resultPageLimit) + 1, 10); + + assert.equal(prepareRequests, 5); + assert.equal(executeRequests, 5); + assert.equal(countRequests, 0, "the state fix must not add a COUNT request"); + } finally { + globalThis.fetch = originalFetch; + restoreStorage(); + } +}); + for (const scenario of [ { label: "TOP equal to one page", bound: 100, pageLimit: 100, pageOffset: 0, rowCount: 100, expectedTotal: 100 }, { label: "TOP below one page", bound: 50, pageLimit: 100, pageOffset: 0, rowCount: 50, expectedTotal: 50 },