feat(database): support Doris and StarRocks catalogs
This commit is contained in:
44 files changed
+1330
-242
No files matched your search
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
databaseAfterCatalogChange,
|
||||
databaseOptionsForConnection,
|
||||
fetchCatalogNamespaceOptions,
|
||||
fetchNamespaceOptionsForConnection,
|
||||
fetchSqlFileTargetOptions,
|
||||
namespaceOptionsAreSchemas,
|
||||
@@ -130,6 +131,19 @@ describe("namespace options", () => {
|
||||
).toEqual(["analytics"]);
|
||||
});
|
||||
|
||||
it("preserves visible database filtering for catalog-scoped transfer options", async () => {
|
||||
mocks.listDorisCatalogDatabases.mockResolvedValue([{ name: "app" }, { name: "analytics" }]);
|
||||
|
||||
await expect(
|
||||
fetchCatalogNamespaceOptions("connection-1", "hive", {
|
||||
db_type: "starrocks",
|
||||
visible_databases: ["analytics"],
|
||||
}),
|
||||
).resolves.toEqual(["analytics"]);
|
||||
|
||||
expect(mocks.listDorisCatalogDatabases).toHaveBeenCalledWith("connection-1", "hive");
|
||||
});
|
||||
|
||||
it("propagates metadata loading errors", async () => {
|
||||
const error = new Error("schema metadata failed");
|
||||
mocks.listSchemas.mockRejectedValue(error);
|
||||
|
||||
@@ -139,6 +139,26 @@ describe("data transfer task duration", () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
it("allows the same target table in different catalogs", async () => {
|
||||
const resolvers: Array<() => void> = [];
|
||||
vi.mocked(api.startTransfer).mockImplementation(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
resolvers.push(resolve);
|
||||
}),
|
||||
);
|
||||
const tracker = useExportTracker();
|
||||
|
||||
tracker.startDataTransferTask({ ...transferRequest("hive"), targetCatalog: "hive" }, "hive");
|
||||
const iceberg = tracker.startDataTransferTask({ ...transferRequest("iceberg"), targetCatalog: "iceberg" }, "iceberg");
|
||||
|
||||
expect(iceberg.status).toBe("Running");
|
||||
expect(api.startTransfer).toHaveBeenCalledTimes(2);
|
||||
|
||||
for (const resolve of resolvers) resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
it("keeps SQL-file backend elapsed time unchanged", () => {
|
||||
const tracker = useExportTracker();
|
||||
const task = tracker.addSqlFileTask("sql", "script.sql", "/tmp/script.sql");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ref } from "vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection } from "@/lib/database/visibleDatabases";
|
||||
import { isDorisFamilyCatalogCapable } from "@/lib/database/databaseFeatureSupport";
|
||||
import { usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
|
||||
import { isInternalDorisCatalog } from "@/lib/database/databaseFeatureSupport";
|
||||
import type { CatalogInfo, ConnectionConfig } from "@/types/database";
|
||||
@@ -56,6 +57,14 @@ export async function fetchNamespaceOptionsForConnection(connectionId: string, c
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchCatalogNamespaceOptions(connectionId: string, catalog: string, connection: NamespaceOptionsConnection): Promise<string[]> {
|
||||
const dbs = await api.listDorisCatalogDatabases(connectionId, catalog);
|
||||
return databaseOptionsForConnection(
|
||||
dbs.map((db) => db.name),
|
||||
connection,
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchSqlFileTargetOptions(connectionId: string, connection: NamespaceOptionsConnection): Promise<string[]> {
|
||||
return fetchNamespaceOptionsForConnection(connectionId, connection);
|
||||
}
|
||||
@@ -72,7 +81,7 @@ export function useDatabaseOptions() {
|
||||
const catalogRequests = new Map<string, Promise<CatalogInfo[]>>();
|
||||
const catalogDatabaseRequests = new Map<string, Promise<string[]>>();
|
||||
|
||||
async function loadDatabaseOptions(connectionId: string) {
|
||||
async function loadDatabaseOptions(connectionId: string, catalog?: string) {
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
if (!connection || loadingDatabaseOptions.value[connectionId]) return;
|
||||
|
||||
@@ -87,6 +96,12 @@ export function useDatabaseOptions() {
|
||||
);
|
||||
} else if (connection.db_type === "mongodb") {
|
||||
databaseOptions.value[connectionId] = filterDatabaseNamesForConnection(await api.mongoListDatabases(connectionId), connection);
|
||||
} else if (catalog && isDorisFamilyCatalogCapable(connection?.db_type, connection?.driver_profile)) {
|
||||
const dbs = await api.listDorisCatalogDatabases(connectionId, catalog);
|
||||
databaseOptions.value[connectionId] = databaseOptionsForConnection(
|
||||
dbs.map((db) => db.name),
|
||||
connection,
|
||||
);
|
||||
} else {
|
||||
const dbs = await api.listDatabases(connectionId);
|
||||
databaseOptions.value[connectionId] = databaseOptionsForConnection(
|
||||
|
||||
@@ -23,6 +23,7 @@ const pendingImportContent = ref("");
|
||||
|
||||
const transferPrefillConnectionId = ref("");
|
||||
const transferPrefillDatabase = ref("");
|
||||
const transferPrefillCatalog = ref("");
|
||||
const transferPrefillSchema = ref("");
|
||||
const transferPrefillTables = ref<string[]>([]);
|
||||
const transferPrefillTargetConnectionId = ref("");
|
||||
@@ -70,6 +71,7 @@ let watchersRegistered = false;
|
||||
function clearTransferPrefill() {
|
||||
transferPrefillConnectionId.value = "";
|
||||
transferPrefillDatabase.value = "";
|
||||
transferPrefillCatalog.value = "";
|
||||
transferPrefillSchema.value = "";
|
||||
transferPrefillTables.value = [];
|
||||
transferPrefillTargetConnectionId.value = "";
|
||||
@@ -92,6 +94,7 @@ export function useDialogSources() {
|
||||
if (v) {
|
||||
transferPrefillConnectionId.value = v.connectionId;
|
||||
transferPrefillDatabase.value = v.database;
|
||||
transferPrefillCatalog.value = v.catalog ?? "";
|
||||
transferPrefillSchema.value = v.schema ?? "";
|
||||
transferPrefillTables.value = v.tables ?? [];
|
||||
transferPrefillTargetConnectionId.value = v.targetConnectionId ?? "";
|
||||
@@ -331,6 +334,7 @@ export function useDialogSources() {
|
||||
pendingImportContent,
|
||||
transferPrefillConnectionId,
|
||||
transferPrefillDatabase,
|
||||
transferPrefillCatalog,
|
||||
transferPrefillSchema,
|
||||
transferPrefillTables,
|
||||
transferPrefillTargetConnectionId,
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface ExportTask {
|
||||
totalTables?: number;
|
||||
currentTable?: string;
|
||||
targetConnectionId?: string;
|
||||
targetCatalog?: string;
|
||||
targetDatabase?: string;
|
||||
targetSchema?: string;
|
||||
targetTables?: string[];
|
||||
@@ -201,6 +202,7 @@ function findActiveOverlappingTransfer(request: api.TransferRequest): string[] {
|
||||
if (task.kind !== "data-transfer") continue;
|
||||
if (task.status !== "Running" && task.status !== "Writing") continue;
|
||||
if (task.targetConnectionId !== request.targetConnectionId) continue;
|
||||
if ((task.targetCatalog ?? "") !== (request.targetCatalog ?? "")) continue;
|
||||
if (task.targetDatabase !== request.targetDatabase) continue;
|
||||
if (task.targetSchema !== request.targetSchema) continue;
|
||||
|
||||
@@ -321,6 +323,7 @@ export function useExportTracker() {
|
||||
const task = existingTask ?? addDataTransferTask(request.transferId, label, request.tables.length);
|
||||
task.startedAt ??= Date.now();
|
||||
task.targetConnectionId = request.targetConnectionId;
|
||||
task.targetCatalog = request.targetCatalog;
|
||||
task.targetDatabase = request.targetDatabase;
|
||||
task.targetSchema = request.targetSchema;
|
||||
task.targetTables = request.tables.map((table) => targetTableName(table, request.targetTableNameCase));
|
||||
|
||||
@@ -21,6 +21,7 @@ export function useSidebarTreeToolRuntime(options: SidebarTreeToolRuntimeOptions
|
||||
connectionStore.transferSource = {
|
||||
connectionId: activeNode.value.connectionId,
|
||||
database: activeNode.value.database ?? "",
|
||||
catalog: activeNode.value.catalog,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user