From 439625a49cc7f7466dbb5b8bac611e1b6a091735 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sat, 28 Feb 2026 12:14:34 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20fix(duckdb-pagination):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20DuckDB=20=E6=80=BB=E6=95=B0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=AF=BC=E8=87=B4=E5=88=86=E9=A1=B5=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 DataViewer 在 hasMore 与 totalKnown 冲突时的分页状态处理 - 增强 DuckDB COUNT(*) 结果解析,兼容字段名与数值类型差异 - 将分页兜底逻辑收敛为 DuckDB 专用,避免影响其他数据库 - 修复 total=0 时分页文案显示异常 - refs #136 --- frontend/src/components/DataGrid.tsx | 6 +++ frontend/src/components/DataViewer.tsx | 60 ++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index f1903f8..534cdfe 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -549,6 +549,8 @@ const DataGrid: React.FC = ({ const showColumnComment = queryOptions?.showColumnComment !== false; const showColumnType = queryOptions?.showColumnType !== false; const selectionColumnWidth = 46; + const connTypeLower = String(connections.find(c => c.id === connectionId)?.config?.type || '').trim().toLowerCase(); + const isDuckDBConnection = connTypeLower === 'duckdb'; // Background Helper const getBg = (darkHex: string) => { @@ -3089,6 +3091,10 @@ const DataGrid: React.FC = ({ pageSize={pagination.pageSize} total={pagination.total} showTotal={(total, range) => { + if (isDuckDBConnection && (!Number.isFinite(total) || total <= 0)) { + if (pagination.totalKnown === false) return '当前 0 条 / 正在统计总数...'; + return '当前 0 条 / 共 0 条'; + } const currentCount = Math.max(0, range[1] - range[0] + 1); if (pagination.totalKnown === false) return `当前 ${currentCount} 条 / 正在统计总数...`; return `当前 ${currentCount} 条 / 共 ${total} 条`; diff --git a/frontend/src/components/DataViewer.tsx b/frontend/src/components/DataViewer.tsx index 2fd8e83..575ee0b 100644 --- a/frontend/src/components/DataViewer.tsx +++ b/frontend/src/components/DataViewer.tsx @@ -6,6 +6,43 @@ import { DBQuery, DBGetColumns } from '../../wailsjs/go/app/App'; import DataGrid, { GONAVI_ROW_KEY } from './DataGrid'; import { buildOrderBySQL, buildWhereSQL, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; +const toNonNegativeFiniteNumber = (value: unknown): number | null => { + if (typeof value === 'number') { + return Number.isFinite(value) && value >= 0 ? value : null; + } + if (typeof value === 'bigint') { + return value >= 0n ? Number(value) : null; + } + if (typeof value === 'string') { + const text = value.trim(); + if (!text) return null; + const parsed = Number(text); + return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; + } + return null; +}; + +const parseTotalFromCountRow = (row: any): number | null => { + if (!row || typeof row !== 'object') return null; + const entries = Object.entries(row as Record); + if (entries.length === 0) return null; + + for (const [key, raw] of entries) { + const normalized = String(key || '').trim().toLowerCase(); + if (normalized === 'total' || normalized === 'count' || normalized.includes('count')) { + const parsed = toNonNegativeFiniteNumber(raw); + if (parsed !== null) return parsed; + } + } + + for (const [, raw] of entries) { + const parsed = toNonNegativeFiniteNumber(raw); + if (parsed !== null) return parsed; + } + + return null; +}; + const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { const [data, setData] = useState([]); const [columnNames, setColumnNames] = useState([]); @@ -157,6 +194,8 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { const countKey = `${tab.connectionId}|${dbName}|${tableName}|${whereSQL}`; const derivedTotalKnown = !hasMore; const derivedTotal = derivedTotalKnown ? offset + resultData.length : page * size + 1; + const isDuckDB = dbTypeLower === 'duckdb'; + const minExpectedTotal = hasMore ? offset + resultData.length + 1 : offset + resultData.length; if (derivedTotalKnown) countKeyRef.current = countKey; setPagination(prev => { @@ -164,7 +203,14 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { return { ...prev, current: page, pageSize: size, total: derivedTotal, totalKnown: true }; } if (prev.totalKnown && countKeyRef.current === countKey) { - return { ...prev, current: page, pageSize: size }; + if (!isDuckDB) { + return { ...prev, current: page, pageSize: size }; + } + // 当当前页存在“下一页”信号时,已知总数至少应大于当前页末尾。 + // 若旧总数不满足该条件(例如历史统计值为 0),降级为未知总数并回退到 derivedTotal。 + if (Number.isFinite(prev.total) && prev.total >= minExpectedTotal) { + return { ...prev, current: page, pageSize: size }; + } } return { ...prev, current: page, pageSize: size, total: derivedTotal, totalKnown: false }; }); @@ -198,8 +244,16 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { if (!resCount.success) return; if (!Array.isArray(resCount.data) || resCount.data.length === 0) return; - const total = Number(resCount.data[0]?.['total']); - if (!Number.isFinite(total) || total < 0) return; + let total: number | null = null; + if (dbTypeLower === 'duckdb') { + total = parseTotalFromCountRow(resCount.data[0]); + } else { + const parsed = Number(resCount.data[0]?.['total']); + if (Number.isFinite(parsed) && parsed >= 0) { + total = parsed; + } + } + if (total === null) return; setPagination(prev => ({ ...prev, total, totalKnown: true })); })