fix(oracle): speed up schema diff and add comment sync (closes #198)
- Replace ALL_OBJECTS with ALL_TABLES UNION ALL_VIEWS in list_databases query to avoid slow full scan on large Oracle instances - Add loading indicator in SchemaDiffDialog during database/schema loading - Generate COMMENT ON TABLE/COLUMN statements in schema diff sync SQL - Compare column and table comments in diff detection
This commit is contained in:
parent
1934205c86
commit
7c38af6cbb
|
|
@ -62,9 +62,9 @@ pub async fn list_databases(conn: &OracleClient) -> Result<Vec<DatabaseInfo>, St
|
|||
"WITH schema_names AS ( \
|
||||
SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS owner FROM DUAL \
|
||||
UNION \
|
||||
SELECT DISTINCT owner \
|
||||
FROM all_objects \
|
||||
WHERE object_type IN ('TABLE', 'VIEW') \
|
||||
SELECT DISTINCT owner FROM all_tables \
|
||||
UNION \
|
||||
SELECT DISTINCT owner FROM all_views \
|
||||
) \
|
||||
SELECT owner \
|
||||
FROM schema_names \
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const targetSchemas = ref<string[]>([]);
|
|||
const step = ref<"select" | "comparing" | "result">("select");
|
||||
const diffs = ref<TableDiff[]>([]);
|
||||
const syncSql = ref("");
|
||||
const loadingMeta = ref(false);
|
||||
const executing = ref(false);
|
||||
|
||||
const sqlConnections = computed(() =>
|
||||
|
|
@ -71,6 +72,7 @@ function connectionIconType(connectionId: string) {
|
|||
|
||||
async function loadDatabases(connectionId: string, side: "source" | "target") {
|
||||
if (!connectionId) return;
|
||||
loadingMeta.value = true;
|
||||
try {
|
||||
await store.ensureConnected(connectionId);
|
||||
const dbs = await api.listDatabases(connectionId);
|
||||
|
|
@ -89,6 +91,8 @@ async function loadDatabases(connectionId: string, side: "source" | "target") {
|
|||
} catch {
|
||||
if (side === "source") sourceDatabases.value = [];
|
||||
else targetDatabases.value = [];
|
||||
} finally {
|
||||
loadingMeta.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +145,8 @@ async function startCompare() {
|
|||
|
||||
const srcTableNames = srcTables.filter((t) => t.table_type !== "VIEW").map((t) => t.name);
|
||||
const tgtTableNames = tgtTables.filter((t) => t.table_type !== "VIEW").map((t) => t.name);
|
||||
const srcTableComments = new Map(srcTables.map((t) => [t.name, t.comment ?? null]));
|
||||
const tgtTableComments = new Map(tgtTables.map((t) => [t.name, t.comment ?? null]));
|
||||
const srcViewNames = srcTables.filter((t) => t.table_type === "VIEW").map((t) => t.name);
|
||||
const tgtViewNames = tgtTables.filter((t) => t.table_type === "VIEW").map((t) => t.name);
|
||||
const { added, removed, common } = diffTables(srcTableNames, tgtTableNames);
|
||||
|
|
@ -181,8 +187,17 @@ async function startCompare() {
|
|||
const idxDiffs = diffIndexes(srcIdx, tgtIdx);
|
||||
const fkDiffs = diffForeignKeys(srcFks, tgtFks);
|
||||
const triggerDiffs = diffTriggers(srcTriggers, tgtTriggers);
|
||||
const srcComment = srcTableComments.get(name) ?? null;
|
||||
const tgtComment = tgtTableComments.get(name) ?? null;
|
||||
const commentChanged = (srcComment ?? "") !== (tgtComment ?? "");
|
||||
|
||||
if (colDiffs.length > 0 || idxDiffs.length > 0 || fkDiffs.length > 0 || triggerDiffs.length > 0) {
|
||||
if (
|
||||
colDiffs.length > 0 ||
|
||||
idxDiffs.length > 0 ||
|
||||
fkDiffs.length > 0 ||
|
||||
triggerDiffs.length > 0 ||
|
||||
commentChanged
|
||||
) {
|
||||
result.push({
|
||||
type: "modified",
|
||||
objectType: "table",
|
||||
|
|
@ -191,6 +206,8 @@ async function startCompare() {
|
|||
indexes: idxDiffs.length > 0 ? idxDiffs : undefined,
|
||||
foreignKeys: fkDiffs.length > 0 ? fkDiffs : undefined,
|
||||
triggers: triggerDiffs.length > 0 ? triggerDiffs : undefined,
|
||||
sourceTableComment: commentChanged ? srcComment : undefined,
|
||||
targetTableComment: commentChanged ? tgtComment : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -403,9 +420,10 @@ watch(open, async (val) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Button v-if="step === 'select'" size="sm" :disabled="!canCompare" @click="startCompare">
|
||||
<GitCompareArrows class="w-3.5 h-3.5 mr-1" />
|
||||
{{ t("diff.compare") }}
|
||||
<Button v-if="step === 'select'" size="sm" :disabled="!canCompare || loadingMeta" @click="startCompare">
|
||||
<Loader2 v-if="loadingMeta" class="w-3.5 h-3.5 mr-1 animate-spin" />
|
||||
<GitCompareArrows v-else class="w-3.5 h-3.5 mr-1" />
|
||||
{{ loadingMeta ? t("common.loading") : t("diff.compare") }}
|
||||
</Button>
|
||||
|
||||
<!-- Comparing -->
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ export interface TableDiff {
|
|||
foreignKeys?: ForeignKeyDiff[];
|
||||
triggers?: TriggerDiff[];
|
||||
ddl?: string;
|
||||
sourceTableComment?: string | null;
|
||||
targetTableComment?: string | null;
|
||||
}
|
||||
|
||||
export function diffColumns(source: ColumnInfo[], target: ColumnInfo[]): ColumnDiff[] {
|
||||
|
|
@ -63,6 +65,9 @@ export function diffColumns(source: ColumnInfo[], target: ColumnInfo[]): ColumnD
|
|||
if ((sc.column_default ?? "") !== (tc.column_default ?? "")) {
|
||||
changes.push(`default: ${tc.column_default ?? "NULL"} → ${sc.column_default ?? "NULL"}`);
|
||||
}
|
||||
if ((sc.comment ?? "") !== (tc.comment ?? "")) {
|
||||
changes.push(`comment: ${tc.comment ?? ""} → ${sc.comment ?? ""}`);
|
||||
}
|
||||
if (changes.length > 0) {
|
||||
diffs.push({ type: "modified", name: sc.name, source: sc, target: tc, changes });
|
||||
}
|
||||
|
|
@ -268,6 +273,34 @@ function dropObjectSql(diff: TableDiff, dbType: DatabaseType, schema?: string):
|
|||
return `DROP ${objectType} IF EXISTS ${qualifiedName(diff.name, dbType, schema)};`;
|
||||
}
|
||||
|
||||
function commentLiteral(comment: string): string {
|
||||
return `'${comment.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
function columnCommentSql(
|
||||
tableName: string,
|
||||
colName: string,
|
||||
comment: string,
|
||||
dbType: DatabaseType,
|
||||
schema?: string,
|
||||
): string {
|
||||
const isMySQL = dbType === "mysql" || dbType === "doris" || dbType === "starrocks";
|
||||
if (isMySQL) {
|
||||
return `-- Column comment for ${colName}: use ALTER TABLE ... MODIFY COLUMN to set comment in MySQL`;
|
||||
}
|
||||
const qt = qualifiedName(tableName, dbType, schema);
|
||||
return `COMMENT ON COLUMN ${qt}.${quoteId(colName, dbType)} IS ${commentLiteral(comment)};`;
|
||||
}
|
||||
|
||||
function tableCommentSql(tableName: string, comment: string, dbType: DatabaseType, schema?: string): string {
|
||||
const isMySQL = dbType === "mysql" || dbType === "doris" || dbType === "starrocks";
|
||||
const qt = qualifiedName(tableName, dbType, schema);
|
||||
if (isMySQL) {
|
||||
return `ALTER TABLE ${qt} COMMENT = ${commentLiteral(comment)};`;
|
||||
}
|
||||
return `COMMENT ON TABLE ${qt} IS ${commentLiteral(comment)};`;
|
||||
}
|
||||
|
||||
export function generateSyncSql(diffs: TableDiff[], dbType: DatabaseType, schema?: string): string {
|
||||
const lines: string[] = [];
|
||||
const isMySQL = dbType === "mysql" || dbType === "doris" || dbType === "starrocks";
|
||||
|
|
@ -351,6 +384,21 @@ export function generateSyncSql(diffs: TableDiff[], dbType: DatabaseType, schema
|
|||
lines.push("");
|
||||
}
|
||||
|
||||
if (diff.columns) {
|
||||
for (const col of diff.columns) {
|
||||
if (col.source && col.changes?.some((c) => c.startsWith("comment:"))) {
|
||||
lines.push(columnCommentSql(diff.name, col.name, col.source.comment ?? "", dbType, schema));
|
||||
}
|
||||
if (col.type === "added" && col.source?.comment) {
|
||||
lines.push(columnCommentSql(diff.name, col.name, col.source.comment, dbType, schema));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff.sourceTableComment !== undefined && diff.sourceTableComment !== diff.targetTableComment) {
|
||||
lines.push(tableCommentSql(diff.name, diff.sourceTableComment ?? "", dbType, schema));
|
||||
}
|
||||
|
||||
if (diff.indexes) {
|
||||
for (const idx of diff.indexes) {
|
||||
if (idx.type === "added" && idx.source) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue