fix(grid): preserve exact totals beyond the result row limit

Closes #6786
This commit is contained in:
t8y2
2026-08-20 14:26:52 +00:00
parent b4015a4393
commit 69e5e81071
5 changed files with 61 additions and 15 deletions
@@ -205,7 +205,7 @@ import {
replaceColumnValueFilterCondition,
} from "@/lib/dataGrid/dataGridColumnFilter";
import { normalizeResultPageSize, resultPageSizeMenuOptions } from "@/lib/dataGrid/paginationPageSize";
import { continuousQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
import { continuousQueryResultMaxRows, effectiveQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
import { allNullColumnIndexes } from "@/lib/dataGrid/dataGridColumnVisibility";
import { buildDataGridColumnLookupItems, dataGridColumnCommentFor, filterDataGridColumnLookupItems } from "@/lib/dataGrid/dataGridColumnLookup";
import { uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
@@ -633,6 +633,8 @@ const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridR
const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode);
const compactDataGridToolbar = computed(() => isDataGridToolbarCompact(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH));
const infiniteScrollEnabled = computed(() => props.paginationEnabled && settingsStore.editorSettings.infiniteScroll);
const queryResultMaxRows = computed(() => effectiveQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows));
const paginationMaxRows = computed(() => (isResultsContext.value ? queryResultMaxRows.value : undefined));
const infiniteScrollMaxRows = computed(() => continuousQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows));
const flatteningMultiLineEnabled = computed(() => settingsStore.editorSettings.flatteningMultiLineText);
const expandedCellEditor = ref<{ rowId: number; col: number } | null>(null);
@@ -3058,6 +3060,7 @@ const paginationTotalRowCount = computed(() =>
paginationTotalRowCount: props.paginationTotalRowCount,
serverKnownTotalRowCount: serverKnownTotalRowCount.value,
totalRowCountIsExact: totalRowCountIsExact.value,
maxRows: paginationMaxRows.value,
}),
);
// Only a server-confirmed total drives pagination — an inferred total means
@@ -3335,8 +3338,9 @@ function applyCustomPageSize() {
}
function jumpToCountedLastPage(total: number) {
if (total <= 0) return;
const lastPageNum = Math.max(1, Math.ceil(total / pageSize.value));
const paginationTotal = resolveDataGridPaginationTotal({ paginationTotalRowCount: total, totalRowCountIsExact: true, maxRows: paginationMaxRows.value });
if (paginationTotal === undefined || paginationTotal <= 0) return;
const lastPageNum = Math.max(1, Math.ceil(paginationTotal / pageSize.value));
if (lastPageNum <= currentPage.value) return;
// Do not bump currentPage before the new page loads — otherwise stale rows
// briefly render with last-page indexes (e.g. 12001-13000) and flash a fake full page.
@@ -42,9 +42,10 @@ export function dataGridTotalRowCountLabelKey(totalRowCountIsExact: boolean, ine
return inexactMode === "estimated" ? "grid.totalRowCountEstimated" : "grid.totalRowCountAtLeast";
}
export function resolveDataGridPaginationTotal(options: { paginationTotalRowCount?: number; serverKnownTotalRowCount?: number; totalRowCountIsExact: boolean }): number | undefined {
if (options.paginationTotalRowCount !== undefined) return options.paginationTotalRowCount;
return options.totalRowCountIsExact ? options.serverKnownTotalRowCount : undefined;
export function resolveDataGridPaginationTotal(options: { paginationTotalRowCount?: number; serverKnownTotalRowCount?: number; totalRowCountIsExact: boolean; maxRows?: number }): number | undefined {
const total = options.paginationTotalRowCount ?? (options.totalRowCountIsExact ? options.serverKnownTotalRowCount : undefined);
if (total === undefined || options.maxRows === undefined) return total;
return Math.min(total, options.maxRows);
}
export function hasCompleteLocalDataGridResult(options: CompleteLocalDataGridResultOptions): boolean {
+4 -5
View File
@@ -4115,7 +4115,6 @@ export const useQueryStore = defineStore("query", () => {
traceId: string;
elapsed: () => string;
timeoutSecs: number;
maxRows?: number;
}) {
const resultRowCount = options.result.rows.length;
if (resultRowCount <= 0) {
@@ -4158,11 +4157,12 @@ export const useQueryStore = defineStore("query", () => {
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, undefined);
return;
}
const cappedTotal = capQueryResultTotal(total, options.maxRows);
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, cappedTotal);
// COUNT describes all matching rows; the configured result limit only
// constrains how many of them pagination may load and retain.
setQueryTotalRowCountIfCurrent(options.tabId, options.executionId, options.result, total);
queryExecutionLog("info", "count:done", {
traceId: options.traceId,
total: cappedTotal,
total,
elapsed: options.elapsed(),
});
} catch (error) {
@@ -5151,7 +5151,6 @@ export const useQueryStore = defineStore("query", () => {
traceId,
elapsed,
timeoutSecs: queryTimeoutSecs,
maxRows: queryResultMaxRows,
});
}
queryExecutionLog("info", "result:assigned", {
+42
View File
@@ -28,6 +28,40 @@ test("estimated display totals do not become pagination bounds", () => {
);
});
test("exact display totals keep pagination inside the configured result cap", () => {
assert.equal(
resolveDataGridPaginationTotal({
serverKnownTotalRowCount: 175_390,
totalRowCountIsExact: true,
maxRows: 100_000,
}),
100_000,
);
assert.equal(
resolveDataGridPaginationTotal({
serverKnownTotalRowCount: 99_999,
totalRowCountIsExact: true,
maxRows: 100_000,
}),
99_999,
);
assert.equal(
resolveDataGridPaginationTotal({
serverKnownTotalRowCount: 100_000,
totalRowCountIsExact: true,
maxRows: 100_000,
}),
100_000,
);
assert.equal(
resolveDataGridPaginationTotal({
serverKnownTotalRowCount: 175_390,
totalRowCountIsExact: true,
}),
175_390,
);
});
test("first query page is complete when its known total is already loaded", () => {
assert.equal(
hasCompleteLocalDataGridResult({
@@ -208,11 +242,19 @@ test("last page always re-counts when a count path is available", () => {
test("jumping to last page does not rewrite indexes before the new page loads", () => {
const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8");
const jumpFn = source.match(/function jumpToCountedLastPage\(total: number\) \{[\s\S]*?\n\}/)?.[0] ?? "";
assert.match(jumpFn, /resolveDataGridPaginationTotal/);
assert.match(jumpFn, /maxRows: paginationMaxRows\.value/);
assert.match(jumpFn, /emit\("paginate"/);
assert.doesNotMatch(jumpFn, /currentPage\.value\s*=/);
assert.match(source, /function rowNumberPageOffset/);
});
test("query result caps do not limit table-data pagination", () => {
const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8");
assert.match(source, /const paginationMaxRows = computed\(\(\) => \(isResultsContext\.value \? queryResultMaxRows\.value : undefined\)\)/);
assert.equal(source.match(/maxRows: paginationMaxRows\.value/g)?.length, 2);
});
test("row number gutter width tracks the largest visible row index", () => {
const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8");
assert.match(source, /dataGridRowNumberColumnWidth/);
+4 -4
View File
@@ -7169,7 +7169,7 @@ for (const resultState of [
{ label: "truncated", result: { truncated: true, has_more: false } },
{ label: "ambiguous exhaustion", result: {} },
]) {
test(`oracle agent ${resultState.label} short page uses COUNT and caps the configured result total`, async () => {
test(`oracle agent ${resultState.label} short page preserves an exact COUNT beyond the result cap`, async () => {
const restoreStorage = installMemoryStorage();
setActivePinia(createPinia());
const connectionStore = useConnectionStore();
@@ -7214,7 +7214,7 @@ for (const resultState of [
}
if (url === "/api/query/execute") {
countRequests += 1;
return new Response(JSON.stringify({ columns: ["COUNT(*)"], rows: [[3_357_833]], affected_rows: 0, execution_time_ms: 1 }), { status: 200, headers: { "Content-Type": "application/json" } });
return new Response(JSON.stringify({ columns: ["COUNT(*)"], rows: [[175_390]], 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" }), {
@@ -7227,11 +7227,11 @@ for (const resultState of [
try {
await store.executeTabSql(tabId, "SELECT ID FROM LARGE_TABLE");
await waitFor(() => tab.resultTotalRowCount === 100_000);
await waitFor(() => tab.resultTotalRowCount === 175_390);
assert.equal(countRequests, 1);
assert.equal(tab.result?.rows.length, 10_000);
assert.equal(tab.resultTotalRowCount, 100_000);
assert.equal(tab.resultTotalRowCount, 175_390);
} finally {
globalThis.fetch = originalFetch;
restoreStorage();