mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-18 04:29:39 +08:00
- 合并 DuckDB 约束与索引元数据,恢复唯一索引表的可编辑判定 - 修复 attach 多库场景下 catalog/schema/table 定位混乱问题 - 统一前后端 qualified name 解析,支持带点和带引号对象名 - 补充 DuckDB 元数据与编辑链路回归测试
127 lines
3.0 KiB
TypeScript
127 lines
3.0 KiB
TypeScript
export type QualifiedNameParts = {
|
|
parentPath: string;
|
|
objectName: string;
|
|
};
|
|
|
|
const normalizeIdentifierEscapes = (raw: string): string => {
|
|
let value = String(raw || '').trim();
|
|
for (let i = 0; i < 4; i += 1) {
|
|
const next = String(value || '').trim()
|
|
.replace(/\\\\"/g, '\\"')
|
|
.replace(/\\"/g, '"');
|
|
if (next === value) break;
|
|
value = next;
|
|
}
|
|
return String(value || '').trim();
|
|
};
|
|
|
|
export const stripIdentifierQuotes = (part: string): string => {
|
|
const text = normalizeIdentifierEscapes(part);
|
|
if (!text) return '';
|
|
if (text.length >= 2) {
|
|
const first = text[0];
|
|
const last = text[text.length - 1];
|
|
if (first === '"' && last === '"') {
|
|
return text.slice(1, -1).replace(/""/g, '"').trim();
|
|
}
|
|
if (first === '`' && last === '`') {
|
|
return text.slice(1, -1).replace(/``/g, '`').trim();
|
|
}
|
|
if (first === '[' && last === ']') {
|
|
return text.slice(1, -1).replace(/]]/g, ']').trim();
|
|
}
|
|
}
|
|
return text;
|
|
};
|
|
|
|
export const splitQualifiedNameSegments = (qualifiedName: string): string[] => {
|
|
const text = normalizeIdentifierEscapes(qualifiedName);
|
|
if (!text) return [];
|
|
|
|
const segments: string[] = [];
|
|
let current = '';
|
|
let inDouble = false;
|
|
let inBacktick = false;
|
|
let inBracket = false;
|
|
|
|
const flush = () => {
|
|
const value = current.trim();
|
|
current = '';
|
|
if (!value) return;
|
|
segments.push(stripIdentifierQuotes(value));
|
|
};
|
|
|
|
for (let i = 0; i < text.length; i += 1) {
|
|
const ch = text[i];
|
|
|
|
if (inDouble) {
|
|
current += ch;
|
|
if (ch === '"' && text[i + 1] === '"') {
|
|
current += text[i + 1];
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (ch === '"') inDouble = false;
|
|
continue;
|
|
}
|
|
|
|
if (inBacktick) {
|
|
current += ch;
|
|
if (ch === '`' && text[i + 1] === '`') {
|
|
current += text[i + 1];
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (ch === '`') inBacktick = false;
|
|
continue;
|
|
}
|
|
|
|
if (inBracket) {
|
|
current += ch;
|
|
if (ch === ']' && text[i + 1] === ']') {
|
|
current += text[i + 1];
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (ch === ']') inBracket = false;
|
|
continue;
|
|
}
|
|
|
|
if (ch === '"') {
|
|
inDouble = true;
|
|
current += ch;
|
|
continue;
|
|
}
|
|
if (ch === '`') {
|
|
inBacktick = true;
|
|
current += ch;
|
|
continue;
|
|
}
|
|
if (ch === '[') {
|
|
inBracket = true;
|
|
current += ch;
|
|
continue;
|
|
}
|
|
if (ch === '.') {
|
|
flush();
|
|
continue;
|
|
}
|
|
current += ch;
|
|
}
|
|
|
|
flush();
|
|
return segments;
|
|
};
|
|
|
|
export const splitQualifiedName = (qualifiedName: string): QualifiedNameParts => {
|
|
const segments = splitQualifiedNameSegments(qualifiedName);
|
|
if (segments.length === 0) return { parentPath: '', objectName: '' };
|
|
if (segments.length === 1) return { parentPath: '', objectName: segments[0] };
|
|
return {
|
|
parentPath: segments.slice(0, -1).join('.'),
|
|
objectName: segments[segments.length - 1],
|
|
};
|
|
};
|
|
|
|
export const splitQualifiedNameLast = splitQualifiedName;
|