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)};`;
+};