🐛 fix(metadata): 修复 Oracle 字段元数据显示缺失

- Oracle 元数据查询为字段名、类型、默认值、注释等列补齐稳定别名

- 新增字段定义归一化工具,兼容 name/Name/COLUMN_NAME 等返回形态

- 修复 DataGrid、DataViewer、QueryEditor、TableDesigner 对字段元数据的读取

- 补充 Oracle 字段注释、表头元数据和主键定位回归测试
This commit is contained in:
Syngnat
2026-06-01 09:32:18 +08:00
parent 63db9fecb3
commit 5ffaa4361e
10 changed files with 137 additions and 21 deletions

View File

@@ -139,6 +139,7 @@ vi.mock('@ant-design/icons', () => {
RobotOutlined: Icon,
SearchOutlined: Icon,
LinkOutlined: Icon,
AimOutlined: Icon,
TableOutlined: Icon,
AimOutlined: Icon,
SortAscendingOutlined: Icon,
@@ -664,7 +665,7 @@ describe('DataGrid DDL interactions', () => {
storeState.queryOptions.showColumnType = true;
backendApp.DBGetColumns.mockResolvedValueOnce({
success: true,
data: [{ name: 'id', type: 'bigint', comment: '主键 ID' }],
data: [{ Name: 'id', Type: 'bigint', Comment: '主键 ID' }],
});
let renderer: ReactTestRenderer;
@@ -703,6 +704,8 @@ describe('DataGrid DDL interactions', () => {
expect(textContent(renderer!.root)).toContain('隐藏此字段');
expect(textContent(renderer!.root)).toContain('隐藏字段类型');
expect(textContent(renderer!.root)).toContain('隐藏字段备注');
expect(textContent(renderer!.root)).toContain('bigint');
expect(textContent(renderer!.root)).toContain('主键 ID');
renderer!.unmount();
});

View File

@@ -104,6 +104,11 @@ import {
resolveRowLocatorValues,
type EditRowLocator,
} from '../utils/rowLocator';
import {
getColumnDefinitionComment,
getColumnDefinitionName,
getColumnDefinitionType,
} from '../utils/columnDefinition';
import {
V2CellContextMenuView,
V2ColumnHeaderContextMenuView,
@@ -2142,10 +2147,10 @@ const DataGrid: React.FC<DataGridProps> = ({
}
const nextMap: Record<string, ColumnMeta> = {};
(res.data as ColumnDefinition[]).forEach((column: any) => {
const name = String(column?.name ?? column?.Name ?? '').trim();
const name = getColumnDefinitionName(column);
if (!name) return;
const type = String(column?.type ?? column?.Type ?? '').trim();
const comment = String(column?.comment ?? column?.Comment ?? '').trim();
const type = getColumnDefinitionType(column);
const comment = getColumnDefinitionComment(column);
nextMap[name] = { type, comment };
});
columnMetaCacheRef.current[cacheKey] = nextMap;

View File

@@ -120,7 +120,7 @@ describe('DataViewer safe editing locator', () => {
it('enables table preview editing after primary keys are loaded', async () => {
backendApp.DBGetColumns.mockResolvedValue({
success: true,
data: [{ name: 'ID', key: 'PRI' }, { name: 'NAME', key: '' }],
data: [{ Name: 'ID', Key: 'PRI' }, { Name: 'NAME', Key: '' }],
});
const renderer = await renderAndReload();

View File

@@ -21,6 +21,11 @@ import {
type EditRowLocator,
} from '../utils/rowLocator';
import { isOracleLikeDialect } from '../utils/sqlDialect';
import {
getColumnDefinitionKey,
getColumnDefinitionName,
getColumnDefinitionType,
} from '../utils/columnDefinition';
type ViewerPaginationState = {
current: number;
@@ -104,7 +109,7 @@ const formatDataViewerTableName = (dbName: string, tableName: string): string =>
const getTableColumnNames = (columns: ColumnDefinition[] | undefined): string[] => (
(columns || [])
.map((column) => String(column?.name || '').trim())
.map(getColumnDefinitionName)
.filter(Boolean)
);
@@ -572,8 +577,8 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({
} else {
const columnDefs = resCols.data as ColumnDefinition[];
const primaryKeys = columnDefs
.filter((column: any) => column?.key === 'PRI')
.map((column: any) => String(column?.name || '').trim())
.filter((column: any) => getColumnDefinitionKey(column) === 'PRI')
.map(getColumnDefinitionName)
.filter(Boolean);
const indexes = resIndexes?.success && Array.isArray(resIndexes.data)
? resIndexes.data as IndexDefinition[]
@@ -721,10 +726,10 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({
if (resCols?.success && Array.isArray(resCols.data)) {
const columnDefs = resCols.data as ColumnDefinition[];
const selectParts = columnDefs.map((col) => {
const colName = String(col?.name || '').trim();
const colName = getColumnDefinitionName(col);
if (!colName) return '';
const quotedCol = quoteIdentPart(dbType, colName);
if (isDuckDBComplexColumnType(col?.type)) {
if (isDuckDBComplexColumnType(getColumnDefinitionType(col))) {
return `CAST(${quotedCol} AS VARCHAR) AS ${quotedCol}`;
}
return quotedCol;

View File

@@ -23,6 +23,10 @@ import { splitSidebarQualifiedName } from '../utils/sidebarLocate';
import { normalizeSidebarViewName } from '../utils/sidebarMetadata';
import { resolveUniqueKeyGroupsFromIndexes } from './dataGridCopyInsert';
import { ORACLE_ROWID_LOCATOR_COLUMN, type EditRowLocator } from '../utils/rowLocator';
import {
getColumnDefinitionKey,
getColumnDefinitionName,
} from '../utils/columnDefinition';
const SQL_KEYWORDS = [
'SELECT', 'FROM', 'WHERE', 'LIMIT', 'INSERT', 'UPDATE', 'DELETE', 'JOIN', 'LEFT', 'RIGHT',
@@ -1530,10 +1534,10 @@ const resolveQueryLocatorPlan = async ({
}
const tableColumns = resCols.data as ColumnDefinition[];
const tableColumnNames = tableColumns.map((column) => String(column?.name || '').trim()).filter(Boolean);
const tableColumnNames = tableColumns.map(getColumnDefinitionName).filter(Boolean);
const primaryKeys = tableColumns
.filter((column: any) => column?.key === 'PRI')
.map((column: any) => String(column?.name || '').trim())
.filter((column: any) => getColumnDefinitionKey(column) === 'PRI')
.map(getColumnDefinitionName)
.filter(Boolean);
const indexes = resIndexes?.success && Array.isArray(resIndexes.data)
? resIndexes.data as IndexDefinition[]

View File

@@ -15,6 +15,10 @@ import { normalizeSchemaStatementForExecution, parseTableCommentFromDDL, splitSc
import TableDesignerSqlPreview from './TableDesignerSqlPreview';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { noAutoCapInputProps } from '../utils/inputAutoCap';
import {
getColumnDefinitionExtra,
normalizeColumnDefinition,
} from '../utils/columnDefinition';
import {
isMysqlFamilyDialect as isMysqlFamilySqlDialect,
isOracleLikeDialect as isOracleLikeSqlDialect,
@@ -804,9 +808,9 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
if (colsRes.success) {
const colsWithKey = (colsRes.data as ColumnDefinition[]).map((c, index) => ({
...c,
...normalizeColumnDefinition(c),
_key: `col-${index}-${Date.now()}`,
isAutoIncrement: c.extra && c.extra.toLowerCase().includes('auto_increment')
isAutoIncrement: getColumnDefinitionExtra(c).toLowerCase().includes('auto_increment')
}));
setColumns(JSON.parse(JSON.stringify(colsWithKey)));
setOriginalColumns(JSON.parse(JSON.stringify(colsWithKey)));