diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index a1999c33e9..fb58309aef 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -3615,9 +3615,9 @@ const paginationTotalRowCount = computed(() => // rows exist that we never fetched, so navigation must stay inside rows.length. const hasKnownPaginationTotalRowCount = computed(() => typeof paginationTotalRowCount.value === "number" && paginationTotalRowCount.value >= 0); // When context=results and the caller hasn't configured server-side -// pagination (no pageLimit), the backend handed us every row up-front and -// rowCount IS the total. Without this hint, the "page is full → assume more" -// fallback in canGoNextDataGridPage lets the user keep clicking next forever. +// pagination (no pageLimit), the backend handed us every row up-front and the +// grid renders them all, so the pager must treat the result as one page. +// Moving to another page would only shift the row numbers (#12107). const allRowsLoaded = computed(() => isResultsContext.value && props.pageLimit === undefined); // Skip re-executing a query when the first page already contains every row. // This also covers paginated queries whose first page is shorter than the limit. @@ -3644,7 +3644,8 @@ const canGoNextPage = computed(() => { }); }); const maximumPage = computed(() => { - const total = hasKnownPaginationTotalRowCount.value ? paginationTotalRowCount.value : allRowsLoaded.value ? props.result.rows.length : undefined; + if (allRowsLoaded.value) return 1; + const total = hasKnownPaginationTotalRowCount.value ? paginationTotalRowCount.value : undefined; if (typeof total === "number" && Number.isFinite(total) && total >= 0) return Math.max(1, Math.ceil(total / pageSize.value)); return canGoNextPage.value ? undefined : currentPage.value; }); @@ -3660,7 +3661,7 @@ const canFetchNextInfiniteScrollSegment = computed(() => // Editable query results also carry table metadata, but their SQL predicates // are not the table browser's whereInput. Never count that table as a fallback. const canCountTableRows = computed(() => props.context !== "results" && !!props.tableMeta); -const canJumpLastPage = computed(() => canGoNextPage.value && (hasKnownPaginationTotalRowCount.value || allRowsLoaded.value || canCalculateTotalRowCount.value)); +const canJumpLastPage = computed(() => canGoNextPage.value && (hasKnownPaginationTotalRowCount.value || canCalculateTotalRowCount.value)); const totalRowCountBusy = computed(() => props.totalRowCountLoading === true || manualTotalRowCountLoading.value); const pageJumpBusy = computed(() => !!props.pageJumpProgress && props.pageJumpProgress.totalRequests > 1); const rowPreparationProgress = shallowRef(null); @@ -3876,12 +3877,7 @@ function jumpPage(page: number) { return; } const targetPage = Math.min(page, maximumPage.value ?? page); - if (targetPage === currentPage.value) { - return; - } - if (allRowsLoaded.value) { - currentPage.value = targetPage; - resetGridVerticalScroll(true); + if (targetPage === currentPage.value || allRowsLoaded.value) { return; } requestServerPageJump(targetPage); @@ -4127,16 +4123,7 @@ async function beginManualTotalRowCount(): Promise { } async function lastPage() { - if (infiniteScrollEnabled.value) return; - if (allRowsLoaded.value) { - const total = props.result.rows.length; - if (total <= 0) return; - const lastPageNum = Math.max(1, Math.ceil(total / pageSize.value)); - if (lastPageNum <= currentPage.value) return; - currentPage.value = lastPageNum; - resetGridVerticalScroll(true); - return; - } + if (infiniteScrollEnabled.value || allRowsLoaded.value) return; // Navicat-style: always re-COUNT when jumping to the last page. if (props.countTotalRows) { const generation = await beginManualTotalRowCount(); diff --git a/apps/desktop/src/lib/dataGrid/dataGridPagination.ts b/apps/desktop/src/lib/dataGrid/dataGridPagination.ts index 51b1708bd7..2e3411e695 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridPagination.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridPagination.ts @@ -7,9 +7,10 @@ export interface CanGoNextDataGridPageOptions { pageOffset?: number; currentPage?: number; totalRowCount?: number; - // True when every result row is already in memory (SQL editor result with no - // server-side pagination). rowCount IS the authoritative total in that case, - // so a full final page must not appear as "more available". + // True when the result came back without server-side pagination (SQL editor + // statements such as SHOW that cannot be wrapped in LIMIT/OFFSET). The grid + // renders every loaded row at once, so the result is a single page: re-running + // the statement for another page would return the same rows again. allRowsLoaded?: boolean; } @@ -123,6 +124,7 @@ export function hasCompleteLocalDataGridResult(options: CompleteLocalDataGridRes } export function canGoNextDataGridPage(options: CanGoNextDataGridPageOptions): boolean { + if (options.allRowsLoaded === true) return false; if (options.hasMore === true) return true; const pageSize = Math.max(1, options.pageSize); @@ -133,10 +135,6 @@ export function canGoNextDataGridPage(options: CanGoNextDataGridPageOptions): bo return currentOffset + pageSize < totalRowCount; } - if (options.allRowsLoaded === true) { - return currentOffset + pageSize < options.rowCount; - } - return options.rowCount >= pageSize; } diff --git a/packages/app-tests/dataGridPagination.test.ts b/packages/app-tests/dataGridPagination.test.ts index 7ad071171d..0cb729ab90 100644 --- a/packages/app-tests/dataGridPagination.test.ts +++ b/packages/app-tests/dataGridPagination.test.ts @@ -171,6 +171,15 @@ test("user LIMIT larger than the page size still pages up to the bound", () => { assert.equal(canGoNextDataGridPage({ rowCount: 100, pageSize: 100, pageOffset: 400, totalRowCount: 500 }), false); }); +test("a result loaded without server-side pagination is a single page", () => { + // `SHOW BINLOG EVENTS` cannot be wrapped in LIMIT/OFFSET, so every row comes + // back at once and the grid renders all of them. Another page would only + // shift the row numbers over the same rows (#12107). + assert.equal(canGoNextDataGridPage({ rowCount: 5_693, pageSize: 100, allRowsLoaded: true }), false); + assert.equal(canGoNextDataGridPage({ rowCount: 5_693, pageSize: 100, totalRowCount: 5_693, allRowsLoaded: true }), false); + assert.equal(canGoNextDataGridPage({ hasMore: true, rowCount: 100_000, pageSize: 100, allRowsLoaded: true }), false); +}); + test("infinite scroll compares cumulative loaded rows with a known total", () => { assert.equal(canFetchNextDataGridSegment({ loadedRowCount: 1_000, pageSize: 1_000, totalRowCount: 2_000 }), true); assert.equal(canFetchNextDataGridSegment({ loadedRowCount: 2_000, pageSize: 1_000, totalRowCount: 2_000 }), false);