feat: compare_schemas command + diff staging into the changes queue (Task 15)

This commit is contained in:
2026-09-09 21:03:32 +08:00
parent c3e10820d6
commit f2e44c56a3
9 changed files with 190 additions and 5 deletions
+12 -1
View File
@@ -2,6 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
import type { Connection, ConnectionInput, ConnectionTestResult, Folder, FolderInput, Tag, TagInput, Settings, ImportResult, TableInfo, QueryResult, BackupOptions, RestoreOptions, SyncOptions, PgToolStatus, MySqlToolStatus, MySqlBackupOptions, MySqlRestoreOptions, SqliteBackupOptions, SqliteRestoreOptions, FunctionInfo, TriggerInfo, SequenceInfo, EnumInfo, ExtensionInfo, SchemaGraph, IndexInfo, ConstraintInfo, RecentConnection, ObjectSearchHit, DependencyInfo, RoleInfo, PrivilegeEntry, RebuildReadiness, MaintenanceResult, TablespaceInfo, ColumnInfo, HypertableListResponse, MultiQueryResult } from "./types";
import type { FilterRule, SortRule } from "../stores/dbViewerStore";
import type { ChangePayload } from "./changePayload";
import type { DiffReport } from "./types";
import { buildObjectDdl as buildObjectDdlImpl, type ObjectKind, type DdlParams } from "./objectCrud";
export { type ObjectKind, type DdlParams };
@@ -418,4 +419,14 @@ export async function getTablespaces(connectionId: string): Promise<TablespaceIn
}
export async function buildRebuildScript<C extends { name: string; type: string; nullable: boolean; default: string | null; is_pk: boolean }[]>(connectionId: string, schema: string, table: string, newColumns: C): Promise<string> {
return invoke<string>("build_rebuild_script", { connectionId, schema, table, newColumns });
}
}
export async function compareSchemas(
sourceConnectionId: string,
sourceSchema: string,
targetConnectionId: string,
targetSchema: string,
): Promise<DiffReport> {
return invoke<DiffReport>("compare_schemas", {
sourceConnectionId, sourceSchema, targetConnectionId, targetSchema,
});
}
+22 -1
View File
@@ -506,4 +506,25 @@ export interface Relationship {
export interface SchemaGraph {
tables: TableNode[];
relationships: Relationship[];
}
}
export interface DiffDetailLine {
label: string;
old: string;
new: string;
}
export interface DiffItem {
object_type: string;
name: string;
kind: "added" | "removed" | "changed";
detail: DiffDetailLine[];
sync_sql: string[];
destructive: boolean;
}
export interface DiffReport {
items: DiffItem[];
source_label: string;
target_label: string;
truncated: boolean;
}
@@ -0,0 +1,30 @@
import { describe, it, expect, beforeEach } from "vitest";
import { useDbViewerStore } from "./dbViewerStore";
import type { DiffItem } from "../lib/types";
const item = (name: string, sql: string, destructive: boolean): DiffItem => ({
object_type: "table",
name,
kind: "added",
detail: [],
sync_sql: [sql],
destructive,
});
describe("addDiffItems", () => {
beforeEach(() => useDbViewerStore.getState().clearChanges());
it("stages one ddl change per non-destructive item, skipping destructive and empty", () => {
const items = [
item("users", "CREATE TABLE users (id int);", false),
item("old", "DROP TABLE old;", true),
{ ...item("weird", "", false), sync_sql: [] },
];
useDbViewerStore.getState().addDiffItems(items);
const queue = useDbViewerStore.getState().changesQueue;
expect(queue).toHaveLength(1);
expect(queue[0].type).toBe("ddl");
expect(queue[0].sql).toBe("CREATE TABLE users (id int);");
expect(queue[0].description).toContain("users");
});
});
+15 -1
View File
@@ -1,5 +1,5 @@
import { create } from "zustand";
import type { QueryResult, TableInfo, ChangeItemType, FunctionInfo, TriggerInfo, SequenceInfo, EnumInfo, ExtensionInfo, HypertableInfo, IndexInfo, ConstraintInfo, ObjectType, RoleInfo, MultiQueryResult } from "../lib/types";
import type { QueryResult, TableInfo, ChangeItemType, FunctionInfo, TriggerInfo, SequenceInfo, EnumInfo, ExtensionInfo, HypertableInfo, IndexInfo, ConstraintInfo, ObjectType, RoleInfo, MultiQueryResult, DiffItem } from "../lib/types";
import { getDatabases, getSchemas, getTables } from "../lib/commands";
import type { ObjectKind, DdlParams } from "../lib/objectCrud";
@@ -169,6 +169,7 @@ interface DbViewerState {
rows?: unknown[][];
description?: string | null;
}) => void;
addDiffItems: (items: DiffItem[]) => void;
cancelChange: (changeId: string) => void;
removeChange: (changeId: string) => void;
updateChange: (changeId: string, patch: Partial<QueueItem>) => void;
@@ -549,6 +550,19 @@ export const useDbViewerStore = create<DbViewerState>((set, get) => ({
set((state) => ({ changesQueue: [...state.changesQueue, item] }));
},
addDiffItems: (items) => {
for (const it of items) {
if (it.destructive || it.sync_sql.length === 0) continue;
for (const sql of it.sync_sql) {
get().addChange({
type: "ddl",
sql,
description: `Schema diff: ${it.object_type} ${it.name} (${it.kind})`,
});
}
}
},
cancelChange: (changeId) =>
set((state) => ({
changesQueue: state.changesQueue.map((c) =>