fix(mysql): use database-specific catalog switching
This commit is contained in:
1 parent
9571519179
commit
2152f8cde8
31 files changed
+818
-182
No files matched your search
@@ -1,16 +1,30 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection, fetchSqlFileTargetOptions, namespaceOptionsAreSchemas, useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import {
|
||||
databaseAfterCatalogChange,
|
||||
databaseOptionsForConnection,
|
||||
fetchNamespaceOptionsForConnection,
|
||||
fetchSqlFileTargetOptions,
|
||||
namespaceOptionsAreSchemas,
|
||||
normalizedQueryTabCatalog,
|
||||
queryCatalogSelectorVisible,
|
||||
selectedQueryCatalogName,
|
||||
useDatabaseOptions,
|
||||
} from "@/composables/useDatabaseOptions";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureConnected: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
listDatabases: vi.fn(),
|
||||
listSchemas: vi.fn(),
|
||||
listDorisCatalogs: vi.fn(),
|
||||
listDorisCatalogDatabases: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
listDatabases: mocks.listDatabases,
|
||||
listSchemas: mocks.listSchemas,
|
||||
listDorisCatalogs: mocks.listDorisCatalogs,
|
||||
listDorisCatalogDatabases: mocks.listDorisCatalogDatabases,
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
@@ -20,6 +34,47 @@ vi.mock("@/stores/connectionStore", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const internalCatalog = { name: "default_catalog", catalog_type: "internal", is_current: true };
|
||||
const paimonCatalog = { name: "paimon_catalog", catalog_type: "paimon", is_current: false };
|
||||
|
||||
describe("query catalog options", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("shows the selector only when an external catalog exists", () => {
|
||||
expect(queryCatalogSelectorVisible([internalCatalog])).toBe(false);
|
||||
expect(queryCatalogSelectorVisible([internalCatalog, paimonCatalog])).toBe(true);
|
||||
});
|
||||
|
||||
it("maps the internal catalog to an undefined tab catalog", () => {
|
||||
const catalogs = [internalCatalog, paimonCatalog];
|
||||
|
||||
expect(selectedQueryCatalogName(catalogs)).toBe("default_catalog");
|
||||
expect(normalizedQueryTabCatalog(catalogs, "default_catalog")).toBeUndefined();
|
||||
expect(normalizedQueryTabCatalog(catalogs, "paimon_catalog")).toBe("paimon_catalog");
|
||||
});
|
||||
|
||||
it("keeps a database only when it exists in the selected catalog", () => {
|
||||
expect(databaseAfterCatalogChange("bi", ["bi", "default"])).toBe("bi");
|
||||
expect(databaseAfterCatalogChange("bi", ["analytics"])).toBe("");
|
||||
});
|
||||
|
||||
it("loads and caches catalog-scoped databases", async () => {
|
||||
mocks.getConfig.mockReturnValue({ db_type: "starrocks" });
|
||||
mocks.listDorisCatalogs.mockResolvedValue([internalCatalog, paimonCatalog]);
|
||||
mocks.listDorisCatalogDatabases.mockResolvedValue([{ name: "bi" }, { name: "analytics" }]);
|
||||
const options = useDatabaseOptions();
|
||||
|
||||
await expect(options.loadCatalogOptions("connection-1")).resolves.toEqual([internalCatalog, paimonCatalog]);
|
||||
await expect(options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog")).resolves.toEqual(["bi", "analytics"]);
|
||||
await options.loadCatalogDatabaseOptions("connection-1", "paimon_catalog");
|
||||
|
||||
expect(options.catalogDatabaseOptions.value["connection-1:paimon_catalog"]).toEqual(["bi", "analytics"]);
|
||||
expect(mocks.listDorisCatalogDatabases).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("namespace options", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -2,10 +2,33 @@ import { ref } from "vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection } from "@/lib/database/visibleDatabases";
|
||||
import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
import { isInternalDorisCatalog } from "@/lib/database/databaseFeatureSupport";
|
||||
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">;
|
||||
export function catalogDatabaseOptionsKey(connectionId: string, catalog: string): string {
|
||||
return `${connectionId}:${catalog}`;
|
||||
}
|
||||
|
||||
export function queryCatalogSelectorVisible(catalogs: CatalogInfo[]): boolean {
|
||||
return catalogs.some((catalog) => !isInternalDorisCatalog(catalog.catalog_type, catalog.name));
|
||||
}
|
||||
|
||||
export function selectedQueryCatalogName(catalogs: CatalogInfo[], tabCatalog?: string): string {
|
||||
if (tabCatalog) return tabCatalog;
|
||||
return catalogs.find((catalog) => isInternalDorisCatalog(catalog.catalog_type, catalog.name))?.name ?? "";
|
||||
}
|
||||
|
||||
export function normalizedQueryTabCatalog(catalogs: CatalogInfo[], selectedCatalog: string): string | undefined {
|
||||
if (!selectedCatalog) return undefined;
|
||||
const catalog = catalogs.find((candidate) => candidate.name === selectedCatalog);
|
||||
return catalog && isInternalDorisCatalog(catalog.catalog_type, catalog.name) ? undefined : selectedCatalog;
|
||||
}
|
||||
|
||||
export function databaseAfterCatalogChange(currentDatabase: string, databaseOptions: string[]): string {
|
||||
return databaseOptions.includes(currentDatabase) ? currentDatabase : "";
|
||||
}
|
||||
|
||||
export function databaseOptionsForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_databases"> | undefined): string[] {
|
||||
const names = filterDatabaseNamesForConnection(databaseNames, connection);
|
||||
@@ -42,6 +65,12 @@ export function useDatabaseOptions() {
|
||||
|
||||
const databaseOptions = ref<Record<string, string[]>>({});
|
||||
const loadingDatabaseOptions = ref<Record<string, boolean>>({});
|
||||
const catalogOptions = ref<Record<string, CatalogInfo[]>>({});
|
||||
const loadingCatalogOptions = ref<Record<string, boolean>>({});
|
||||
const catalogDatabaseOptions = ref<Record<string, string[]>>({});
|
||||
const loadingCatalogDatabaseOptions = ref<Record<string, boolean>>({});
|
||||
const catalogRequests = new Map<string, Promise<CatalogInfo[]>>();
|
||||
const catalogDatabaseRequests = new Map<string, Promise<string[]>>();
|
||||
|
||||
async function loadDatabaseOptions(connectionId: string) {
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
@@ -70,6 +99,56 @@ export function useDatabaseOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCatalogOptions(connectionId: string): Promise<CatalogInfo[]> {
|
||||
const cached = catalogOptions.value[connectionId];
|
||||
if (cached) return cached;
|
||||
const pending = catalogRequests.get(connectionId);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = (async () => {
|
||||
loadingCatalogOptions.value[connectionId] = true;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
const catalogs = await api.listDorisCatalogs(connectionId);
|
||||
catalogOptions.value[connectionId] = catalogs;
|
||||
return catalogs;
|
||||
} finally {
|
||||
loadingCatalogOptions.value[connectionId] = false;
|
||||
catalogRequests.delete(connectionId);
|
||||
}
|
||||
})();
|
||||
catalogRequests.set(connectionId, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async function loadCatalogDatabaseOptions(connectionId: string, catalog: string): Promise<string[]> {
|
||||
const key = catalogDatabaseOptionsKey(connectionId, catalog);
|
||||
const cached = catalogDatabaseOptions.value[key];
|
||||
if (cached) return cached;
|
||||
const pending = catalogDatabaseRequests.get(key);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = (async () => {
|
||||
loadingCatalogDatabaseOptions.value[key] = true;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
const databases = await api.listDorisCatalogDatabases(connectionId, catalog);
|
||||
const names = databaseOptionsForConnection(
|
||||
databases.map((database) => database.name),
|
||||
connection,
|
||||
);
|
||||
catalogDatabaseOptions.value[key] = names;
|
||||
return names;
|
||||
} finally {
|
||||
loadingCatalogDatabaseOptions.value[key] = false;
|
||||
catalogDatabaseRequests.delete(key);
|
||||
}
|
||||
})();
|
||||
catalogDatabaseRequests.set(key, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async function getDatabaseOptions(connectionId: string): Promise<string[]> {
|
||||
if (!databaseOptions.value[connectionId]) {
|
||||
await loadDatabaseOptions(connectionId);
|
||||
@@ -77,5 +156,16 @@ export function useDatabaseOptions() {
|
||||
return databaseOptions.value[connectionId] ?? [];
|
||||
}
|
||||
|
||||
return { databaseOptions, loadingDatabaseOptions, loadDatabaseOptions, getDatabaseOptions };
|
||||
return {
|
||||
databaseOptions,
|
||||
loadingDatabaseOptions,
|
||||
loadDatabaseOptions,
|
||||
getDatabaseOptions,
|
||||
catalogOptions,
|
||||
loadingCatalogOptions,
|
||||
loadCatalogOptions,
|
||||
catalogDatabaseOptions,
|
||||
loadingCatalogDatabaseOptions,
|
||||
loadCatalogDatabaseOptions,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user