fix(grid): preserve page size when final fetch is narrowed

Closes #6478
This commit is contained in:
t8y2
2026-08-17 10:24:08 +00:00
parent aa6d60533c
commit c724fd46b0
2 changed files with 142 additions and 11 deletions
+13 -10
View File
@@ -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;
+129 -1
View File
@@ -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 },