fix(compare): let oracle pick schemas in data compare

This commit is contained in:
jearhe authored and GitHub committed 2026-09-01 11:00:24 +08:00
1 parent 5f9ba51769
commit 23f0b1255b
4 files changed
+148 -26

No files matched your search

@@ -319,13 +319,12 @@ async function loadDatabases(connectionId: string, side: "source" | "target") {
if (!connectionId) return;
await store.ensureConnected(connectionId);
const config = store.getConfig(connectionId);
const names =
config?.db_type === "dameng"
? await fetchNamespaceOptionsForConnection(connectionId, config)
: databaseOptionsForConnection(
(await api.listDatabases(connectionId)).map((database) => database.name),
config,
);
const names = config
? await fetchNamespaceOptionsForConnection(connectionId, config)
: databaseOptionsForConnection(
(await api.listDatabases(connectionId)).map((database) => database.name),
config,
);
if (side === "source") {
sourceDatabases.value = names;
sourceDatabase.value = names.length === 1 ? names[0] : "";
@@ -7,18 +7,44 @@ import DataCompareDialog from "@/components/diff/DataCompareDialog.vue";
const mocks = vi.hoisted(() => ({
ensureConnected: vi.fn().mockResolvedValue(undefined),
listDatabases: vi.fn().mockResolvedValue([{ name: "DBX_TEST" }, { name: "OPS$ORACLE" }]),
listSchemas: vi.fn().mockResolvedValue(["DBX_TEST"]),
listTables: vi.fn().mockResolvedValue([{ name: "CODEX_7046_META", table_type: "TABLE" }]),
listDatabases: vi.fn().mockResolvedValue([]),
listSchemas: vi.fn().mockResolvedValue(["DBX_TEST", "REPORTING", "SYS"]),
listTables: vi.fn().mockResolvedValue([{ name: "CODEX_7467_META", table_type: "TABLE" }]),
getColumns: vi.fn().mockResolvedValue([{ name: "ID", data_type: "NUMBER", is_primary_key: true }]),
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: () => ({
connections: [{ id: "oracle-11g", name: "Oracle XE 11g", db_type: "oracle", driver_profile: "oracle", database: "XE" }],
getConfig: (id: string) => (id === "oracle-11g" ? { id, name: "Oracle XE 11g", db_type: "oracle", driver_profile: "oracle", database: "XE" } : undefined),
ensureConnected: mocks.ensureConnected,
}),
function passthrough(tag: string) {
return defineComponent({
inheritAttrs: false,
setup(_, { attrs, slots }) {
return () => h(tag, attrs, slots.default?.());
},
});
}
vi.mock("@/stores/connectionStore", () => {
const connections = [
{ id: "oracle-11g", name: "Oracle XE 11g", db_type: "oracle", driver_profile: "oracle", database: "XE" },
{
id: "oracle-jdbc-11g",
name: "Oracle JDBC 11g",
db_type: "jdbc",
driver_profile: "oracle",
connection_string: "jdbc:oracle:thin:@//localhost:1521/XE",
jdbc_driver_class: "oracle.jdbc.OracleDriver",
},
];
return {
useConnectionStore: () => ({
connections,
getConfig: (id: string) => connections.find((connection) => connection.id === id),
ensureConnected: mocks.ensureConnected,
}),
};
});
vi.mock("@/components/connection/ConnectionGroupBadge.vue", () => ({
default: passthrough("span"),
}));
vi.mock("@/lib/backend/api", () => ({
@@ -39,7 +65,7 @@ async function flushAsyncSetup() {
afterEach(() => {
for (const app of mountedApps.splice(0)) app.unmount();
document.body.innerHTML = "";
document.body.textContent = "";
vi.clearAllMocks();
});
@@ -64,8 +90,71 @@ describe("DataCompareDialog source prefill", () => {
app.mount(container);
await flushAsyncSetup();
expect(mocks.listDatabases).not.toHaveBeenCalled();
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-11g", "XE", true);
const searchableSelectTriggers = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
const sourceDatabaseTrigger = searchableSelectTriggers[1];
expect(sourceDatabaseTrigger?.title).toBe("DBX_TEST");
expect(sourceDatabaseTrigger?.disabled).toBe(false);
sourceDatabaseTrigger?.click();
await flushAsyncSetup();
const databaseOptions = [...document.querySelectorAll<HTMLButtonElement>(".dbx-searchable-select-list button")].map((button) => button.textContent?.trim());
expect(databaseOptions).toEqual(expect.arrayContaining(["DBX_TEST", "REPORTING"]));
expect(mocks.listTables).toHaveBeenCalledWith("oracle-11g", "DBX_TEST", "DBX_TEST");
expect(document.body.textContent).toContain("CODEX_7046_META");
expect(document.body.textContent).toContain("CODEX_7467_META");
expect(document.body.textContent).not.toContain("暂无可比较的表");
});
it("loads schemas and tables after selecting an Oracle JDBC target connection", async () => {
const container = document.createElement("div");
document.body.append(container);
const app = createApp(
defineComponent({
setup: () => () =>
h(DataCompareDialog, {
open: true,
prefillConnectionId: "oracle-11g",
prefillDatabase: "DBX_TEST",
prefillSchema: "DBX_TEST",
prefillTable: "CODEX_7467_META",
}),
}),
);
mountedApps.push(app);
app.use(i18n);
app.mount(container);
await flushAsyncSetup();
const searchableSelectTriggers = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
const targetConnectionTrigger = searchableSelectTriggers[3];
expect(targetConnectionTrigger).toBeDefined();
targetConnectionTrigger?.click();
await flushAsyncSetup();
const jdbcConnectionOption = [...document.querySelectorAll<HTMLButtonElement>(".dbx-searchable-select-list button")].find((button) => button.textContent?.trim() === "Oracle JDBC 11g");
expect(jdbcConnectionOption).toBeDefined();
jdbcConnectionOption?.click();
await flushAsyncSetup();
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-jdbc-11g", "", true);
const triggersAfterTargetLoad = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
const targetDatabaseTrigger = triggersAfterTargetLoad[4];
expect(targetDatabaseTrigger?.disabled).toBe(false);
targetDatabaseTrigger?.click();
await flushAsyncSetup();
const targetDatabaseOptions = [...document.querySelectorAll<HTMLButtonElement>(".dbx-searchable-select-list button")].map((button) => button.textContent?.trim());
expect(targetDatabaseOptions).toEqual(expect.arrayContaining(["DBX_TEST", "REPORTING"]));
const reportingOption = [...document.querySelectorAll<HTMLButtonElement>(".dbx-searchable-select-list button")].find((button) => button.textContent?.trim() === "REPORTING");
expect(reportingOption).toBeDefined();
reportingOption?.click();
await flushAsyncSetup();
expect(mocks.listTables).toHaveBeenCalledWith("oracle-jdbc-11g", "REPORTING", "DBX_TEST");
expect(document.body.textContent).toContain("CODEX_7467_META");
});
});
@@ -112,6 +112,31 @@ describe("namespace options", () => {
expect(options).toEqual(["ARCHIVE"]);
});
it("uses schemas for native and inferred JDBC Oracle namespaces", async () => {
mocks.listSchemas.mockResolvedValue(["APP", "REPORTING", "SYS"]);
await expect(
fetchNamespaceOptionsForConnection("oracle-1", {
db_type: "oracle",
database: "XE",
}),
).resolves.toEqual(["APP", "REPORTING"]);
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-1", "XE", true);
vi.clearAllMocks();
mocks.listSchemas.mockResolvedValue(["APP", "REPORTING", "SYS"]);
await expect(
fetchNamespaceOptionsForConnection("oracle-jdbc-1", {
db_type: "jdbc",
connection_string: "jdbc:oracle:thin:@//localhost:1521/XE",
jdbc_driver_class: "oracle.jdbc.OracleDriver",
}),
).resolves.toEqual(["APP", "REPORTING"]);
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-jdbc-1", "", true);
expect(mocks.listDatabases).not.toHaveBeenCalled();
});
it("preserves listDatabases and visible database filtering for other databases", async () => {
mocks.listDatabases.mockResolvedValue([{ name: "app" }, { name: "analytics" }, { name: "postgres" }]);
@@ -187,9 +212,15 @@ describe("namespace options", () => {
).resolves.toEqual(["APP_USER", "REPORTING"]);
});
it("identifies only Dameng top-level options as schemas", () => {
it("identifies connection-root namespace options as schemas", () => {
expect(namespaceOptionsAreSchemas({ db_type: "dameng" })).toBe(true);
expect(namespaceOptionsAreSchemas({ db_type: "oracle" })).toBe(false);
expect(namespaceOptionsAreSchemas({ db_type: "oracle" })).toBe(true);
expect(
namespaceOptionsAreSchemas({
db_type: "jdbc",
connection_string: "jdbc:oracle:thin:@//localhost:1521/XE",
}),
).toBe(true);
expect(namespaceOptionsAreSchemas({ db_type: "postgres" })).toBe(false);
});
@@ -4,10 +4,12 @@ import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection } from
import { isDorisFamilyCatalogCapable, supportsQueryTargetDatabaseListing } from "@/lib/database/databaseFeatureSupport";
import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
import { isInternalDorisCatalog } from "@/lib/database/databaseFeatureSupport";
import { connectionUsesConnectionRootSchemaMode } from "@/lib/database/jdbcDialect";
import type { CatalogInfo, ConnectionConfig } from "@/types/database";
import * as api from "@/lib/backend/api";
type NamespaceOptionsConnection = Pick<ConnectionConfig, "database" | "db_type" | "driver_profile" | "visible_databases" | "visible_schemas"> & Partial<Pick<ConnectionConfig, "database_info">>;
type NamespaceOptionsConnection = Pick<ConnectionConfig, "database" | "db_type" | "driver_profile" | "driver_label" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths" | "visible_databases" | "visible_database_patterns" | "visible_schemas" | "username" | "show_system_schemas"> &
Partial<Pick<ConnectionConfig, "database_info">>;
export function catalogDatabaseOptionsKey(connectionId: string, catalog: string): string {
return `${connectionId}:${catalog}`;
}
@@ -39,15 +41,16 @@ export function databaseOptionsForConnection(databaseNames: string[], connection
return names;
}
export function namespaceOptionsAreSchemas(connection: Pick<ConnectionConfig, "db_type"> | undefined): boolean {
return connection?.db_type === "dameng";
export function namespaceOptionsAreSchemas(connection: NamespaceOptionsConnection | undefined): boolean {
return connectionUsesConnectionRootSchemaMode(connection);
}
export async function fetchNamespaceOptionsForConnection(connectionId: string, connection: NamespaceOptionsConnection): Promise<string[]> {
if (connection.db_type === "dameng") {
if (namespaceOptionsAreSchemas(connection)) {
const database = connection.database || "";
// Dameng users and schemas are not interchangeable: independent schemas
// appear in listSchemas but not in the user-backed listDatabases result.
// Oracle, Dameng, and their inferred JDBC dialects expose schemas directly
// below the connection; their catalog/database listing is not the namespace
// picker used by schema-aware UI surfaces.
const schemas = await api.listSchemas(connectionId, database, true);
return filterSchemaNamesForConnection(schemas, connection, database);
}