fix(transfer): list Dameng schemas as databases
This commit is contained in:
parent
b7f1820e12
commit
0b86d8278d
|
|
@ -15,7 +15,7 @@ import * as api from "@/lib/backend/api";
|
|||
import type { TransferMode, TransferTableNameCase } from "@/lib/backend/api";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { isSchemaAware, supportsTransfer } from "@/lib/database/databaseCapabilities";
|
||||
import { databaseOptionsForConnection } from "@/composables/useDatabaseOptions";
|
||||
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection, namespaceOptionsAreSchemas } from "@/composables/useDatabaseOptions";
|
||||
import { useExportTracker } from "@/composables/useExportTracker";
|
||||
import { ArrowRightLeft, ArrowLeftRight, Loader2, Square, CheckSquare } from "@lucide/vue";
|
||||
|
||||
|
|
@ -99,8 +99,9 @@ async function loadDatabases(connectionId: string, target: "source" | "target")
|
|||
if (!connectionId) return;
|
||||
try {
|
||||
await store.ensureConnected(connectionId);
|
||||
const rawNames = isMongoConnection(connectionId) ? await api.mongoListDatabases(connectionId) : (await api.listDatabases(connectionId)).map((d) => d.name);
|
||||
const names = databaseOptionsForConnection(rawNames, store.getConfig(connectionId));
|
||||
const config = store.getConfig(connectionId);
|
||||
if (!config) return;
|
||||
const names = isMongoConnection(connectionId) ? databaseOptionsForConnection(await api.mongoListDatabases(connectionId), config) : await fetchNamespaceOptionsForConnection(connectionId, config);
|
||||
if (target === "source") {
|
||||
sourceDatabases.value = names;
|
||||
sourceDatabase.value = names.length === 1 ? names[0] : "";
|
||||
|
|
@ -188,7 +189,12 @@ watch(sourceConnectionId, (id) => {
|
|||
watch(sourceDatabase, async (db) => {
|
||||
if (db) {
|
||||
const config = store.getConfig(sourceConnectionId.value);
|
||||
if (isSchemaAware(config?.db_type)) {
|
||||
if (namespaceOptionsAreSchemas(config)) {
|
||||
// Dameng has no selectable catalog, so the top-level namespace option is
|
||||
// also the schema used for metadata lookup and qualified transfer SQL.
|
||||
sourceSchemas.value = [];
|
||||
sourceSchema.value = db;
|
||||
} else if (isSchemaAware(config?.db_type)) {
|
||||
await loadSchemas(sourceConnectionId.value, db, "source");
|
||||
} else {
|
||||
sourceSchema.value = db;
|
||||
|
|
@ -208,7 +214,10 @@ watch(targetConnectionId, (id) => {
|
|||
watch(targetDatabase, async (db) => {
|
||||
if (db) {
|
||||
const config = store.getConfig(targetConnectionId.value);
|
||||
if (isSchemaAware(config?.db_type)) {
|
||||
if (namespaceOptionsAreSchemas(config)) {
|
||||
targetSchemas.value = [];
|
||||
targetSchema.value = db;
|
||||
} else if (isSchemaAware(config?.db_type)) {
|
||||
await loadSchemas(targetConnectionId.value, db, "target");
|
||||
} else {
|
||||
targetSchema.value = db;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { fetchSqlFileTargetOptions } from "@/composables/useDatabaseOptions";
|
||||
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection, fetchSqlFileTargetOptions, namespaceOptionsAreSchemas, useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureConnected: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
listDatabases: vi.fn(),
|
||||
listSchemas: vi.fn(),
|
||||
}));
|
||||
|
|
@ -12,10 +14,13 @@ vi.mock("@/lib/backend/api", () => ({
|
|||
}));
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: vi.fn(),
|
||||
useConnectionStore: () => ({
|
||||
ensureConnected: mocks.ensureConnected,
|
||||
getConfig: mocks.getConfig,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("fetchSqlFileTargetOptions", () => {
|
||||
describe("namespace options", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
@ -23,7 +28,7 @@ describe("fetchSqlFileTargetOptions", () => {
|
|||
it("uses Dameng schemas so independent schemas remain selectable", async () => {
|
||||
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "SYS"]);
|
||||
|
||||
const options = await fetchSqlFileTargetOptions("connection-1", {
|
||||
const options = await fetchNamespaceOptionsForConnection("connection-1", {
|
||||
db_type: "dameng",
|
||||
database: "APP_USER",
|
||||
visible_databases: ["APP_USER", "REPORTING"],
|
||||
|
|
@ -37,7 +42,7 @@ describe("fetchSqlFileTargetOptions", () => {
|
|||
it("honors the configured Dameng schema filter before the legacy database filter", async () => {
|
||||
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "ARCHIVE"]);
|
||||
|
||||
const options = await fetchSqlFileTargetOptions("connection-1", {
|
||||
const options = await fetchNamespaceOptionsForConnection("connection-1", {
|
||||
db_type: "dameng",
|
||||
database: "APP_USER",
|
||||
visible_databases: ["APP_USER", "REPORTING"],
|
||||
|
|
@ -50,7 +55,7 @@ describe("fetchSqlFileTargetOptions", () => {
|
|||
it("preserves listDatabases and visible database filtering for other databases", async () => {
|
||||
mocks.listDatabases.mockResolvedValue([{ name: "app" }, { name: "analytics" }, { name: "postgres" }]);
|
||||
|
||||
const options = await fetchSqlFileTargetOptions("connection-2", {
|
||||
const options = await fetchNamespaceOptionsForConnection("connection-2", {
|
||||
db_type: "postgres",
|
||||
database: "app",
|
||||
visible_databases: ["analytics"],
|
||||
|
|
@ -61,15 +66,53 @@ describe("fetchSqlFileTargetOptions", () => {
|
|||
expect(mocks.listSchemas).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves visible database filtering for MongoDB transfer options", () => {
|
||||
expect(
|
||||
databaseOptionsForConnection(["app", "analytics", "admin"], {
|
||||
db_type: "mongodb",
|
||||
visible_databases: ["analytics"],
|
||||
}),
|
||||
).toEqual(["analytics"]);
|
||||
});
|
||||
|
||||
it("propagates metadata loading errors", async () => {
|
||||
const error = new Error("schema metadata failed");
|
||||
mocks.listSchemas.mockRejectedValue(error);
|
||||
|
||||
await expect(
|
||||
fetchNamespaceOptionsForConnection("connection-1", {
|
||||
db_type: "dameng",
|
||||
database: "APP_USER",
|
||||
}),
|
||||
).rejects.toBe(error);
|
||||
});
|
||||
|
||||
it("keeps the SQL file target on the shared namespace loader", async () => {
|
||||
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING"]);
|
||||
|
||||
await expect(
|
||||
fetchSqlFileTargetOptions("connection-1", {
|
||||
db_type: "dameng",
|
||||
database: "APP_USER",
|
||||
}),
|
||||
).rejects.toBe(error);
|
||||
).resolves.toEqual(["APP_USER", "REPORTING"]);
|
||||
});
|
||||
|
||||
it("identifies only Dameng top-level options as schemas", () => {
|
||||
expect(namespaceOptionsAreSchemas({ db_type: "dameng" })).toBe(true);
|
||||
expect(namespaceOptionsAreSchemas({ db_type: "oracle" })).toBe(false);
|
||||
expect(namespaceOptionsAreSchemas({ db_type: "postgres" })).toBe(false);
|
||||
});
|
||||
|
||||
it("does not expand the global database options composable to Dameng schemas", async () => {
|
||||
mocks.getConfig.mockReturnValue({ db_type: "dameng" });
|
||||
mocks.listDatabases.mockResolvedValue([]);
|
||||
|
||||
const { databaseOptions, loadDatabaseOptions } = useDatabaseOptions();
|
||||
await loadDatabaseOptions("connection-1");
|
||||
|
||||
expect(databaseOptions.value["connection-1"]).toEqual([]);
|
||||
expect(mocks.listDatabases).toHaveBeenCalledWith("connection-1");
|
||||
expect(mocks.listSchemas).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
|||
import type { ConnectionConfig } from "@/types/database";
|
||||
import * as api from "@/lib/backend/api";
|
||||
|
||||
type SqlFileTargetConnection = Pick<ConnectionConfig, "database" | "db_type" | "driver_profile" | "visible_databases" | "visible_schemas">;
|
||||
type NamespaceOptionsConnection = Pick<ConnectionConfig, "database" | "db_type" | "driver_profile" | "visible_databases" | "visible_schemas">;
|
||||
|
||||
export function databaseOptionsForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_databases"> | undefined): string[] {
|
||||
const names = filterDatabaseNamesForConnection(databaseNames, connection);
|
||||
|
|
@ -13,7 +13,11 @@ export function databaseOptionsForConnection(databaseNames: string[], connection
|
|||
return names;
|
||||
}
|
||||
|
||||
export async function fetchSqlFileTargetOptions(connectionId: string, connection: SqlFileTargetConnection): Promise<string[]> {
|
||||
export function namespaceOptionsAreSchemas(connection: Pick<ConnectionConfig, "db_type"> | undefined): boolean {
|
||||
return connection?.db_type === "dameng";
|
||||
}
|
||||
|
||||
export async function fetchNamespaceOptionsForConnection(connectionId: string, connection: NamespaceOptionsConnection): Promise<string[]> {
|
||||
if (connection.db_type === "dameng") {
|
||||
const database = connection.database || "";
|
||||
// Dameng users and schemas are not interchangeable: independent schemas
|
||||
|
|
@ -29,6 +33,10 @@ export async function fetchSqlFileTargetOptions(connectionId: string, connection
|
|||
);
|
||||
}
|
||||
|
||||
export async function fetchSqlFileTargetOptions(connectionId: string, connection: NamespaceOptionsConnection): Promise<string[]> {
|
||||
return fetchNamespaceOptionsForConnection(connectionId, connection);
|
||||
}
|
||||
|
||||
export function useDatabaseOptions() {
|
||||
const connectionStore = useConnectionStore();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue