mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-24 07:45:09 +00:00
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
export type SqlMetadataRefreshScope = "none" | "connection" | "database";
|
|
export type SqlMetadataRefreshTarget =
|
|
| { scope: "none" }
|
|
| { scope: "connection" }
|
|
| { scope: "database"; schema?: string };
|
|
|
|
const DATABASE_DDL_RE = /\b(CREATE|DROP)\s+DATABASE\b/i;
|
|
const SCHEMA_DDL_RE = /\b(CREATE|DROP)\s+SCHEMA\b/i;
|
|
const OBJECT_DDL_RE =
|
|
/\b(CREATE|ALTER|DROP|RENAME)\s+(OR\s+REPLACE\s+)?(((GLOBAL|LOCAL)\s+)?TEMP(ORARY)?\s+)?(MATERIALIZED\s+)?(TABLE|VIEW|INDEX|SEQUENCE|PROCEDURE|FUNCTION|TRIGGER|TYPE)\b/i;
|
|
const OBJECT_NAME_DDL_RE =
|
|
/\b(?:CREATE|ALTER|DROP|RENAME)\s+(?:OR\s+REPLACE\s+)?(?:(?:(?:GLOBAL|LOCAL)\s+)?TEMP(?:ORARY)?\s+)?(?:MATERIALIZED\s+)?(?:TABLE|VIEW|SEQUENCE|PROCEDURE|FUNCTION|TRIGGER|TYPE)\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)\s*\.\s*(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*))/i;
|
|
const INDEX_TABLE_DDL_RE =
|
|
/\bCREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)\s+ON\s+((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)\s*\.\s*(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*))/i;
|
|
|
|
function stripSqlMetadataComments(sql: string): string {
|
|
return sql
|
|
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
.replace(/--.*$/gm, " ")
|
|
.replace(/#.*$/gm, " ");
|
|
}
|
|
|
|
export function sqlMetadataRefreshScope(sql: string): SqlMetadataRefreshScope {
|
|
return sqlMetadataRefreshTarget(sql).scope;
|
|
}
|
|
|
|
function splitSqlMetadataStatements(sql: string): string[] {
|
|
return stripSqlMetadataComments(sql)
|
|
.split(";")
|
|
.map((stmt) => stmt.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function unquoteIdentifier(identifier: string): string {
|
|
const trimmed = identifier.trim();
|
|
if (
|
|
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
(trimmed.startsWith("`") && trimmed.endsWith("`")) ||
|
|
(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
|
) {
|
|
return trimmed.slice(1, -1);
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
function schemaFromQualifiedName(qualifiedName: string): string | undefined {
|
|
const schema = qualifiedName.split(".")[0]?.trim();
|
|
return schema ? unquoteIdentifier(schema) : undefined;
|
|
}
|
|
|
|
function schemaFromObjectDdl(statement: string): string | undefined {
|
|
const match = statement.match(INDEX_TABLE_DDL_RE) || statement.match(OBJECT_NAME_DDL_RE);
|
|
return match?.[1] ? schemaFromQualifiedName(match[1]) : undefined;
|
|
}
|
|
|
|
export function sqlMetadataRefreshTarget(sql: string, activeSchema?: string): SqlMetadataRefreshTarget {
|
|
const statements = splitSqlMetadataStatements(sql);
|
|
if (statements.some((stmt) => DATABASE_DDL_RE.test(stmt))) return { scope: "connection" };
|
|
|
|
const schemaTargets = new Set<string>();
|
|
let hasDatabaseRefresh = false;
|
|
|
|
for (const statement of statements) {
|
|
if (SCHEMA_DDL_RE.test(statement)) {
|
|
hasDatabaseRefresh = true;
|
|
continue;
|
|
}
|
|
if (!OBJECT_DDL_RE.test(statement)) continue;
|
|
hasDatabaseRefresh = true;
|
|
const schema = schemaFromObjectDdl(statement) || activeSchema;
|
|
if (schema) schemaTargets.add(schema);
|
|
}
|
|
|
|
if (!hasDatabaseRefresh) return { scope: "none" };
|
|
if (schemaTargets.size === 1) return { scope: "database", schema: [...schemaTargets][0] };
|
|
return { scope: "database" };
|
|
}
|