diff --git a/frontend/src/components/DataViewer.primary-key.test.tsx b/frontend/src/components/DataViewer.primary-key.test.tsx index 5759daa0..4230d4ec 100644 --- a/frontend/src/components/DataViewer.primary-key.test.tsx +++ b/frontend/src/components/DataViewer.primary-key.test.tsx @@ -606,6 +606,58 @@ describe('DataViewer safe editing locator', () => { renderer!.unmount(); }); + it('recounts the known total when table data shrinks after a manual refresh', async () => { + storeState.connections[0].config.type = 'mysql'; + storeState.connections[0].config.database = 'main'; + backendApp.DBGetColumns.mockResolvedValue({ + success: true, + data: [{ name: 'ID', key: 'PRI' }, { name: 'NAME', key: '' }], + }); + + let countQueryCount = 0; + backendApp.DBQuery.mockImplementation(async (_config: any, _dbName: string, sql: string) => { + if (/count\s*\(/i.test(String(sql))) { + countQueryCount += 1; + return { + success: true, + fields: ['total'], + data: [{ total: countQueryCount === 1 ? 500 : 430 }], + }; + } + return { + success: true, + fields: ['ID', 'NAME'], + data: createRows(101), + }; + }); + + let renderer: ReactTestRenderer; + await act(async () => { + renderer = create(); + }); + await flushPromises(); + + expect(dataGridState.latestProps?.pagination).toMatchObject({ + total: 500, + totalKnown: true, + }); + + await act(async () => { + dataGridState.latestProps?.onReload(); + await Promise.resolve(); + await Promise.resolve(); + }); + await flushPromises(); + + expect(countQueryCount).toBe(2); + expect(dataGridState.latestProps?.pagination).toMatchObject({ + total: 430, + totalKnown: true, + }); + expect(dataGridState.latestProps?.data).toHaveLength(100); + renderer!.unmount(); + }); + it('shows an actionable message for DuckDB timeout interruption errors', async () => { storeState.languagePreference = 'en-US'; storeState.connections[0].config.type = 'duckdb'; diff --git a/frontend/src/components/DataViewer.tsx b/frontend/src/components/DataViewer.tsx index e03555c3..bb7de2c0 100644 --- a/frontend/src/components/DataViewer.tsx +++ b/frontend/src/components/DataViewer.tsx @@ -583,7 +583,8 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ setPagination(prev => ({ ...prev, totalCountLoading: false, totalCountCancelled: true })); }, []); - const fetchData = useCallback(async (page = pagination.current, size = pagination.pageSize) => { + const fetchData = useCallback(async (page = pagination.current, size = pagination.pageSize, options?: { refreshTotal?: boolean }) => { + const refreshTotal = options?.refreshTotal === true; const seq = ++fetchSeqRef.current; setLoading(true); const conn = connections.find(c => c.id === tab.connectionId); @@ -717,15 +718,16 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ : buildOrderBySQL(dbType, sortInfo, resolveDataViewerOrderFallbackColumns(editLocatorForQuery, pkColumnsForQuery)); const totalRows = Number(pagination.total); const hasFiniteTotal = Number.isFinite(totalRows) && totalRows >= 0; - const totalKnown = pagination.totalKnown && hasFiniteTotal; + const totalKnown = !refreshTotal && pagination.totalKnown && hasFiniteTotal; const approximateTotalRows = Number(pagination.approximateTotal); const hasApproximateTotalPages = + !refreshTotal && !totalKnown && supportsApproximateTotalPages && pagination.totalApprox && Number.isFinite(approximateTotalRows) && approximateTotalRows > 0; - const effectiveTotalRows = hasApproximateTotalPages ? approximateTotalRows : totalRows; + const effectiveTotalRows = hasApproximateTotalPages ? approximateTotalRows : (refreshTotal ? 0 : totalRows); const totalPages = Number.isFinite(effectiveTotalRows) && effectiveTotalRows > 0 ? Math.max(1, Math.ceil(effectiveTotalRows / size)) : 0; const currentPage = totalPages > 0 ? Math.min(Math.max(1, page), totalPages) : Math.max(1, page); const offset = (currentPage - 1) * size; @@ -1141,7 +1143,24 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ void fetchData(pagination.current, pagination.pageSize); }, [fetchData, pagination.current, pagination.pageSize]); const handleReload = useCallback(() => { - fetchData(pagination.current, pagination.pageSize); + countSeqRef.current++; + manualCountSeqRef.current++; + duckdbApproxSeqRef.current++; + oracleApproxSeqRef.current++; + countKeyRef.current = ''; + autoCountKeyRef.current = ''; + manualCountKeyRef.current = ''; + duckdbApproxKeyRef.current = ''; + oracleApproxKeyRef.current = ''; + setPagination(prev => ({ + ...prev, + totalKnown: false, + totalApprox: false, + approximateTotal: undefined, + totalCountLoading: false, + totalCountCancelled: false, + })); + fetchData(pagination.current, pagination.pageSize, { refreshTotal: true }); }, [fetchData, pagination.current, pagination.pageSize]); const handleSort = useCallback((field: string, order: string) => { // 支持多字段排序:field 为 JSON 数组字符串时解析为多字段