From 8a10519f9b11890a2d5d3622d6f6d5964847901c Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 17 Apr 2026 13:30:07 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(query):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E6=96=B0=E5=BB=BA=E6=9F=A5=E8=AF=A2=E6=9C=AA=E5=BC=95=E7=94=A8?= =?UTF-8?q?=20PostgreSQL=20=E5=A4=A7=E5=86=99=E8=A1=A8=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 抽取表查询模板 helper 并统一复用方言标识符引用逻辑 - 修正 Sidebar 与 TableOverview 的表节点新建查询入口 - 补充前端回归测试并更新 issue backlog 记录 Fixes #349 --- docs/issues/2026-04-11-issue-backlog-tracking.md | 7 +++++++ frontend/src/components/Sidebar.tsx | 3 ++- frontend/src/components/TableOverview.tsx | 16 ++++++++++------ frontend/src/utils/objectQueryTemplates.test.ts | 9 +++++++++ frontend/src/utils/objectQueryTemplates.ts | 9 +++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 frontend/src/utils/objectQueryTemplates.test.ts create mode 100644 frontend/src/utils/objectQueryTemplates.ts diff --git a/docs/issues/2026-04-11-issue-backlog-tracking.md b/docs/issues/2026-04-11-issue-backlog-tracking.md index 5d1e0a3..636f3b6 100644 --- a/docs/issues/2026-04-11-issue-backlog-tracking.md +++ b/docs/issues/2026-04-11-issue-backlog-tracking.md @@ -37,6 +37,7 @@ | #343 | redis删除hash类型中的key报错 | Fixed | Pending | | #346 | TDEngine只显示子表不显示超级表 | Fixed | Pending | | #348 | [Bug] sql查询同名字段,结果集不会自动添加别名 | Fixed | Pending | +| #349 | [Bug] postgres对于表名大小写敏感,且为大写时,通过选中表右键新建查询时生成的sql语句没有自动带上引号"" | Fixed | Pending | | #351 | 为什么没有截断和清空表的功能呀? | Fixed | Pending | ## Notes @@ -137,6 +138,12 @@ - 处理:为 `scanRows` 增加稳定列名归一化逻辑。首次出现保留原名,重复列自动追加 `_2`、`_3` 后缀;空列名回退为 `column_N`。返回的列列表和每行数据统一使用同一套唯一列名,避免覆盖。 - 验证:新增 `internal/db/scan_rows_test.go` 回归测试,覆盖重复列 `id/id/name` 自动归一化为 `id/id_2/name` 且两列值均保留,并执行 `go test ./internal/db -run TestScanRowsRenamesDuplicateColumns -count=1` 与 `go test ./internal/db -count=1`。 +### #349 + +- 根因:表节点“新建查询”模板在 Sidebar 与 TableOverview 两处都直接拼接 `SELECT * FROM ${tableName};`,没有复用现有的标识符引用逻辑。对 PostgreSQL 这类未加引号会把标识符折叠为小写的数据库,遇到大写表名时生成的 SQL 会直接指向错误对象。 +- 处理:抽出统一的 `buildTableSelectQuery` helper,内部复用 `quoteQualifiedIdent` 按数据库方言生成表引用;并将 Sidebar、TableOverview 的三个“新建查询”入口统一接到该 helper,保证 PostgreSQL/Kingbase 等方言在大写或特殊字符表名场景下自动补双引号。 +- 验证:新增 `frontend/src/utils/objectQueryTemplates.test.ts` 回归测试,覆盖 PostgreSQL `public.MyTable` 自动生成 `SELECT * FROM public.\"MyTable\";`,并执行 `frontend` 下 `npm exec vitest run src/utils/objectQueryTemplates.test.ts` 与 `npm run build`。 + ### #330 - 根因:查询结果表格已经支持拖拽调整列宽,但 resize handle 没有提供双击自适应逻辑,导致用户只能靠手工拖拽慢慢试宽度。 diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index f1ac23f..a1d4488 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -45,6 +45,7 @@ import { getTableDataDangerActionMeta, supportsTableTruncateAction, type TableDa import { useAutoFetchVisibility } from '../utils/autoFetchVisibility'; import FindInDatabaseModal from './FindInDatabaseModal'; import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig'; +import { buildTableSelectQuery } from '../utils/objectQueryTemplates'; const { Search } = Input; @@ -3559,7 +3560,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> icon: , onClick: () => { const tableName = String(node.dataRef?.tableName || '').trim(); - const queryTemplate = tableName ? `SELECT * FROM ${tableName};` : 'SELECT * FROM '; + const queryTemplate = buildTableSelectQuery(getMetadataDialect(node.dataRef as SavedConnection), tableName); addTab({ id: `query-${Date.now()}`, title: `新建查询`, diff --git a/frontend/src/components/TableOverview.tsx b/frontend/src/components/TableOverview.tsx index 6b07484..4a39a67 100644 --- a/frontend/src/components/TableOverview.tsx +++ b/frontend/src/components/TableOverview.tsx @@ -7,6 +7,7 @@ import type { TabData } from '../types'; import { useAutoFetchVisibility } from '../utils/autoFetchVisibility'; import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig'; import { getTableDataDangerActionMeta, supportsTableTruncateAction, type TableDataDangerActionKind } from './tableDataDangerActions'; +import { buildTableSelectQuery } from '../utils/objectQueryTemplates'; interface TableOverviewProps { tab: TabData; @@ -153,6 +154,10 @@ const TableOverview: React.FC = ({ tab }) => { const [viewMode, setViewMode] = useState('list'); const connection = useMemo(() => connections.find(c => c.id === tab.connectionId), [connections, tab.connectionId]); + const metadataDialect = useMemo( + () => getMetadataDialect(connection?.config?.type || '', connection?.config?.driver), + [connection?.config?.driver, connection?.config?.type] + ); const autoFetchVisible = useAutoFetchVisibility(); const loadData = useCallback(async () => { @@ -167,11 +172,10 @@ const TableOverview: React.FC = ({ tab }) => { useSSH: connection.config.useSSH || false, ssh: connection.config.ssh || { host: '', port: 22, user: '', password: '', keyPath: '' }, }; - const dialect = getMetadataDialect(connection.config.type, connection.config.driver); - const sql = buildTableStatusSQL(dialect, tab.dbName || '', (tab as any).schemaName); + const sql = buildTableStatusSQL(metadataDialect, tab.dbName || '', (tab as any).schemaName); const res = await DBQuery(buildRpcConnectionConfig(config) as any, tab.dbName || '', sql); if (res.success && Array.isArray(res.data)) { - setTables(parseTableStats(dialect, res.data)); + setTables(parseTableStats(metadataDialect, res.data)); } else { message.error('获取表信息失败: ' + (res.message || '未知错误')); } @@ -180,7 +184,7 @@ const TableOverview: React.FC = ({ tab }) => { } finally { setLoading(false); } - }, [connection, tab.dbName]); + }, [connection, metadataDialect, tab.dbName]); useEffect(() => { if (!autoFetchVisible) { @@ -471,7 +475,7 @@ const TableOverview: React.FC = ({ tab }) => { type: 'query', connectionId: tab.connectionId, dbName: tab.dbName, - query: `SELECT * FROM ${t.name};`, + query: buildTableSelectQuery(metadataDialect, t.name), }); }}, { type: 'divider' }, @@ -557,7 +561,7 @@ const TableOverview: React.FC = ({ tab }) => { type: 'query', connectionId: tab.connectionId, dbName: tab.dbName, - query: `SELECT * FROM ${t.name};`, + query: buildTableSelectQuery(metadataDialect, t.name), }); }}, { type: 'divider' }, diff --git a/frontend/src/utils/objectQueryTemplates.test.ts b/frontend/src/utils/objectQueryTemplates.test.ts new file mode 100644 index 0000000..7dd8f35 --- /dev/null +++ b/frontend/src/utils/objectQueryTemplates.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest'; + +import { buildTableSelectQuery } from './objectQueryTemplates'; + +describe('buildTableSelectQuery', () => { + it('quotes uppercase postgres table names in new query templates', () => { + expect(buildTableSelectQuery('postgres', 'public.MyTable')).toBe('SELECT * FROM public."MyTable";'); + }); +}); diff --git a/frontend/src/utils/objectQueryTemplates.ts b/frontend/src/utils/objectQueryTemplates.ts new file mode 100644 index 0000000..7bf65c5 --- /dev/null +++ b/frontend/src/utils/objectQueryTemplates.ts @@ -0,0 +1,9 @@ +import { quoteQualifiedIdent } from './sql'; + +export const buildTableSelectQuery = (dbType: string, tableName: string): string => { + const normalizedTableName = String(tableName || '').trim(); + if (!normalizedTableName) { + return 'SELECT * FROM '; + } + return `SELECT * FROM ${quoteQualifiedIdent(dbType, normalizedTableName)};`; +};