From 819d201483934159704aa706191f2f7731a39f89 Mon Sep 17 00:00:00 2001 From: tianqijiuyun-latiao <69459608+tianqijiuyun-latiao@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:21:56 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(oracle-query):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20Oracle=20=E8=A1=A8=E6=95=B0=E6=8D=AE=E5=88=86?= =?UTF-8?q?=E9=A1=B5=20SQL=20=E5=85=BC=E5=AE=B9=E9=97=AE=E9=A2=98=20refs?= =?UTF-8?q?=20#196?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/DataGrid.tsx | 12 ++++----- frontend/src/components/DataViewer.tsx | 9 +++---- frontend/src/utils/sql.ts | 35 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 0ca13d7..0a4d5b7 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -10,7 +10,7 @@ import { useStore } from '../store'; import type { ColumnDefinition } from '../types'; import { v4 as uuidv4 } from 'uuid'; import 'react-resizable/css/styles.css'; -import { buildOrderBySQL, buildWhereSQL, escapeLiteral, quoteIdentPart, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; +import { buildOrderBySQL, buildPaginatedSelectSQL, buildWhereSQL, escapeLiteral, quoteIdentPart, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; import { isMacLikePlatform, normalizeOpacityForPlatform, resolveAppearanceValues } from '../utils/appearance'; import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities'; @@ -2447,18 +2447,18 @@ const DataGrid: React.FC = ({ return clauses.join(' OR '); }, [pkColumns, tableName]); - const buildCurrentPageSql = useCallback((dbType: string) => { + const buildCurrentPageSql = useCallback((dbType: string) => { if (!tableName || !pagination) return ''; const whereSQL = buildWhereSQL(dbType, filterConditions); - let sql = `SELECT * FROM ${quoteQualifiedIdent(dbType, tableName)} ${whereSQL}`; - sql += buildOrderBySQL(dbType, sortInfo, pkColumns); + const baseSql = `SELECT * FROM ${quoteQualifiedIdent(dbType, tableName)} ${whereSQL}`; + const orderBySQL = buildOrderBySQL(dbType, sortInfo, pkColumns); const normalizedType = String(dbType || '').trim().toLowerCase(); const hasExplicitSort = !!sortInfo?.columnKey && (sortInfo?.order === 'ascend' || sortInfo?.order === 'descend'); + const offset = (pagination.current - 1) * pagination.pageSize; + let sql = buildPaginatedSelectSQL(dbType, baseSql, orderBySQL, pagination.pageSize, offset); if (hasExplicitSort && (normalizedType === 'mysql' || normalizedType === 'mariadb')) { sql = withSortBufferTuningSQL(normalizedType, sql, 32 * 1024 * 1024); } - const offset = (pagination.current - 1) * pagination.pageSize; - sql += ` LIMIT ${pagination.pageSize} OFFSET ${offset}`; return sql; }, [tableName, pagination, filterConditions, sortInfo, pkColumns]); diff --git a/frontend/src/components/DataViewer.tsx b/frontend/src/components/DataViewer.tsx index 597523e..46acfe1 100644 --- a/frontend/src/components/DataViewer.tsx +++ b/frontend/src/components/DataViewer.tsx @@ -4,7 +4,7 @@ import { TabData, ColumnDefinition } from '../types'; import { useStore } from '../store'; import { DBQuery, DBGetColumns } from '../../wailsjs/go/app/App'; import DataGrid, { GONAVI_ROW_KEY } from './DataGrid'; -import { buildOrderBySQL, buildWhereSQL, quoteIdentPart, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; +import { buildOrderBySQL, buildPaginatedSelectSQL, buildWhereSQL, quoteIdentPart, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; import { buildMongoCountCommand, buildMongoFilter, buildMongoFindCommand, buildMongoSort } from '../utils/mongodb'; import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities'; @@ -455,7 +455,7 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { if (pageRowCount > 0) { const tailOffset = Math.max(0, totalRows - (offset + pageRowCount)); if (tailOffset < offset) { - sql = `${baseSql}${reverseOrderSQL} LIMIT ${pageRowCount} OFFSET ${tailOffset}`; + sql = buildPaginatedSelectSQL(dbType, baseSql, reverseOrderSQL, pageRowCount, tailOffset); useClickHouseReversePagination = true; clickHouseReverseLimit = pageRowCount; clickHouseReverseHasMore = currentPage < totalPages; @@ -464,7 +464,7 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { } if (!useClickHouseReversePagination) { // 大表性能:打开表不阻塞在 COUNT(*),先通过多取 1 条判断是否还有下一页;总数在后台统计并异步回填。 - sql += ` LIMIT ${size + 1} OFFSET ${offset}`; + sql = buildPaginatedSelectSQL(dbType, baseSql, orderBySQL, size + 1, offset); } } @@ -534,8 +534,7 @@ const DataViewer: React.FC<{ tab: TabData }> = ({ tab }) => { if (safeSelect) { let fallbackSql = `SELECT ${safeSelect} FROM ${quoteQualifiedIdent(dbType, tableName)} ${whereSQL}`; - fallbackSql += buildOrderBySQL(dbType, sortInfo, pkColumns); - fallbackSql += ` LIMIT ${size + 1} OFFSET ${offset}`; + fallbackSql = buildPaginatedSelectSQL(dbType, fallbackSql, buildOrderBySQL(dbType, sortInfo, pkColumns), size + 1, offset); executedSql = fallbackSql; resData = await executeDataQuery(fallbackSql, '复杂类型降级重试'); } diff --git a/frontend/src/utils/sql.ts b/frontend/src/utils/sql.ts index f15a3ee..14ad50f 100644 --- a/frontend/src/utils/sql.ts +++ b/frontend/src/utils/sql.ts @@ -134,6 +134,41 @@ export const buildOrderBySQL = ( return ''; }; +export const buildPaginatedSelectSQL = ( + dbType: string, + baseSql: string, + orderBySQL: string, + limit: number, + offset: number, +) => { + const normalizedType = String(dbType || '').trim().toLowerCase(); + const safeLimit = Math.max(0, Math.floor(Number(limit) || 0)); + const safeOffset = Math.max(0, Math.floor(Number(offset) || 0)); + const base = String(baseSql || '').trim(); + const orderBy = String(orderBySQL || ''); + + if (!base || safeLimit <= 0) { + return `${base}${orderBy}`; + } + + switch (normalizedType) { + case 'oracle': { + const orderedSql = `${base}${orderBy}`; + const upperBound = safeOffset + safeLimit; + if (safeOffset <= 0) { + return `SELECT * FROM (${orderedSql}) WHERE ROWNUM <= ${upperBound}`; + } + return `SELECT * FROM (SELECT "__gonavi_page__".*, ROWNUM "__gonavi_rn__" FROM (${orderedSql}) "__gonavi_page__" WHERE ROWNUM <= ${upperBound}) WHERE "__gonavi_rn__" > ${safeOffset}`; + } + case 'sqlserver': { + const effectiveOrderBy = orderBy.trim() ? orderBy : ' ORDER BY (SELECT NULL)'; + return `${base}${effectiveOrderBy} OFFSET ${safeOffset} ROWS FETCH NEXT ${safeLimit} ROWS ONLY`; + } + default: + return `${base}${orderBy} LIMIT ${safeLimit} OFFSET ${safeOffset}`; + } +}; + export const parseListValues = (val: string) => { const raw = (val || '').trim(); if (!raw) return [];