Files
MyGoNavi/frontend/src/utils/queryResultTable.ts
Syngnat 274c32ebdd 🐛 fix(frontend): 修复 DuckDB 对象编辑与安全修改回归
- 修复 DuckDB qualified table 在查询结果页丢失 schema 导致无法识别主键的问题

- 打开对象修改前强制刷新最新定义,并避免切换对象失败时沿用旧定义

- 为 DuckDB 元数据链路补充前后端回归测试,并给 app 层真实 runtime 测试增加环境门槛
2026-06-04 22:00:55 +08:00

94 lines
3.2 KiB
TypeScript

export type QueryResultTableRef = {
tableName: string;
metadataDbName: string;
metadataTableName: string;
};
const stripIdentifierQuotes = (part: string): string => {
const text = String(part || '').trim();
if (!text) return '';
if ((text.startsWith('`') && text.endsWith('`')) || (text.startsWith('"') && text.endsWith('"'))) {
return text.slice(1, -1).trim();
}
if (text.startsWith('[') && text.endsWith(']')) {
return text.slice(1, -1).trim();
}
return text;
};
const isOracleLikeDialect = (dialect: string): boolean => {
const normalized = String(dialect || '').trim().toLowerCase();
return normalized === 'oracle' || normalized === 'dameng' || normalized === 'dm' || normalized === 'dm8';
};
const keepsQualifiedTableNameForMetadata = (dialect: string): boolean => {
const normalized = String(dialect || '').trim().toLowerCase();
return normalized === 'duckdb';
};
const isQuotedIdentifier = (part: string): boolean => {
const text = String(part || '').trim();
if (!text) return false;
return (text.startsWith('`') && text.endsWith('`'))
|| (text.startsWith('"') && text.endsWith('"'))
|| (text.startsWith('[') && text.endsWith(']'));
};
const normalizeIdentifierPart = (part: string, dialect: string): string => {
const text = String(part || '').trim();
const value = stripIdentifierQuotes(text);
if (!value) return '';
if (isOracleLikeDialect(dialect) && !isQuotedIdentifier(text)) {
return value.toUpperCase();
}
return value;
};
const normalizeCurrentDbName = (currentDb: string, dialect: string): string => {
const value = String(currentDb || '').trim();
if (!value) return '';
return isOracleLikeDialect(dialect) ? value.toUpperCase() : value;
};
const normalizeQualifiedNameParts = (raw: string, dialect: string): string[] => (
String(raw || '')
.split('.')
.map((part) => normalizeIdentifierPart(part, dialect))
.filter(Boolean)
);
export const extractQueryResultTableRef = (
sql: string,
dialect: string,
currentDb: string,
): QueryResultTableRef | undefined => {
const text = String(sql || '').trim();
if (!text) return undefined;
if (/\b(JOIN|UNION|INTERSECT|EXCEPT|MINUS)\b/i.test(text)) return undefined;
if (/^\s*SELECT\s+DISTINCT\b/i.test(text)) return undefined;
if (/\bGROUP\s+BY\b|\bHAVING\b/i.test(text)) return undefined;
const tableMatch = text.match(/^\s*SELECT\s+.+?\s+FROM\s+((?:[`"\[]?\w+[`"\]]?)(?:\s*\.\s*(?:[`"\[]?\w+[`"\]]?)){0,2})\s*(?:$|[\s;])/im);
if (!tableMatch) return undefined;
const parts = normalizeQualifiedNameParts(tableMatch[1], dialect);
if (parts.length === 0) return undefined;
const metadataTableName = parts[parts.length - 1] || '';
if (!metadataTableName) return undefined;
const owner = parts.length >= 2 ? parts[parts.length - 2] : '';
const metadataDbName = owner || normalizeCurrentDbName(currentDb, dialect);
const tableName = (isOracleLikeDialect(dialect) || keepsQualifiedTableNameForMetadata(dialect)) && owner
? `${owner}.${metadataTableName}`
: metadataTableName;
const resolvedMetadataTableName = keepsQualifiedTableNameForMetadata(dialect) && owner
? `${owner}.${metadataTableName}`
: metadataTableName;
return {
tableName,
metadataDbName,
metadataTableName: resolvedMetadataTableName,
};
};