🐛 fix(metadata): 修复多数据源主键唯一索引识别

- 统一 PG-like 数据源字段和索引元数据查询,支持 search_path 可见表

- 兼容 snake_case、布尔别名和字符串唯一索引标记

- 修复 DuckDB main/memory 路径解析,避免误判外部 catalog

- 补充前后端回归测试,覆盖可编辑结果定位和元数据重试路径
This commit is contained in:
Syngnat
2026-06-04 10:49:16 +08:00
parent 9acb1c69f7
commit 02faa4586b
21 changed files with 832 additions and 723 deletions

View File

@@ -28,4 +28,11 @@ describe('columnDefinition metadata normalization', () => {
comment: '更新时间',
});
});
it('maps boolean primary and unique metadata aliases to GoNavi keys', () => {
expect(getColumnDefinitionKey({ column_name: 'id', isPrimary: true })).toBe('PRI');
expect(getColumnDefinitionKey({ column_name: 'id', primary_key: 't' })).toBe('PRI');
expect(getColumnDefinitionKey({ column_name: 'email', is_unique: 'yes' })).toBe('UNI');
expect(getColumnDefinitionKey({ column_name: 'id', column_key: 'primary key' })).toBe('PRI');
});
});

View File

@@ -20,6 +20,34 @@ const readStringProperty = (value: unknown, keys: string[]): string => {
return '';
};
const readProperty = (value: unknown, keys: string[]): unknown => {
const source = value as Record<string, unknown> | null | undefined;
if (!source || typeof source !== 'object') return undefined;
for (const key of keys) {
if (source[key] !== undefined && source[key] !== null) {
return source[key];
}
}
for (const [sourceKey, raw] of Object.entries(source)) {
if (keys.some((key) => sourceKey.toLowerCase() === key.toLowerCase())) {
return raw;
}
}
return undefined;
};
const readBooleanProperty = (value: unknown, keys: string[]): boolean => {
const raw = readProperty(value, keys);
if (raw === undefined || raw === null) return false;
if (typeof raw === 'boolean') return raw;
if (typeof raw === 'number') return raw !== 0;
const text = String(raw).trim().toLowerCase();
return text === '1' || text === 't' || text === 'true' || text === 'y' || text === 'yes' || text === 'pri' || text === 'primary';
};
export const getColumnDefinitionName = (column: unknown): string => (
readStringProperty(column, ['name', 'Name', 'COLUMN_NAME', 'column_name', 'field', 'Field'])
);
@@ -28,9 +56,24 @@ export const getColumnDefinitionType = (column: unknown): string => (
readStringProperty(column, ['type', 'Type', 'DATA_TYPE', 'data_type'])
);
export const getColumnDefinitionKey = (column: unknown): string => (
readStringProperty(column, ['key', 'Key', 'COLUMN_KEY', 'column_key'])
);
export const getColumnDefinitionKey = (column: unknown): string => {
const key = readStringProperty(column, ['key', 'Key', 'COLUMN_KEY', 'column_key']);
if (key) {
const normalized = key.trim();
const lowered = normalized.toLowerCase();
if (lowered === 'pri' || lowered === 'primary' || lowered === 'primary key') return 'PRI';
if (lowered === 'uni' || lowered === 'unique') return 'UNI';
if (lowered === 'mul' || lowered === 'multiple') return 'MUL';
return normalized;
}
if (readBooleanProperty(column, ['primaryKey', 'primary_key', 'isPrimary', 'is_primary', 'IS_PRIMARY', 'pk', 'PK'])) {
return 'PRI';
}
if (readBooleanProperty(column, ['unique', 'isUnique', 'is_unique', 'UNIQUE', 'IS_UNIQUE'])) {
return 'UNI';
}
return '';
};
export const getColumnDefinitionExtra = (column: unknown): string => (
readStringProperty(column, ['extra', 'Extra'])