mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-01 03:09:37 +08:00
🐛 fix(query): 修正新建查询未引用 PostgreSQL 大写表名
- 抽取表查询模板 helper 并统一复用方言标识符引用逻辑 - 修正 Sidebar 与 TableOverview 的表节点新建查询入口 - 补充前端回归测试并更新 issue backlog 记录 Fixes #349
This commit is contained in:
@@ -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 没有提供双击自适应逻辑,导致用户只能靠手工拖拽慢慢试宽度。
|
||||
|
||||
@@ -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: <ConsoleSqlOutlined />,
|
||||
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: `新建查询`,
|
||||
|
||||
@@ -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<TableOverviewProps> = ({ tab }) => {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('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<TableOverviewProps> = ({ 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<TableOverviewProps> = ({ tab }) => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [connection, tab.dbName]);
|
||||
}, [connection, metadataDialect, tab.dbName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoFetchVisible) {
|
||||
@@ -471,7 +475,7 @@ const TableOverview: React.FC<TableOverviewProps> = ({ 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<TableOverviewProps> = ({ tab }) => {
|
||||
type: 'query',
|
||||
connectionId: tab.connectionId,
|
||||
dbName: tab.dbName,
|
||||
query: `SELECT * FROM ${t.name};`,
|
||||
query: buildTableSelectQuery(metadataDialect, t.name),
|
||||
});
|
||||
}},
|
||||
{ type: 'divider' },
|
||||
|
||||
9
frontend/src/utils/objectQueryTemplates.test.ts
Normal file
9
frontend/src/utils/objectQueryTemplates.test.ts
Normal file
@@ -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";');
|
||||
});
|
||||
});
|
||||
9
frontend/src/utils/objectQueryTemplates.ts
Normal file
9
frontend/src/utils/objectQueryTemplates.ts
Normal file
@@ -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)};`;
|
||||
};
|
||||
Reference in New Issue
Block a user