feat(database): support Doris and StarRocks catalogs

This commit is contained in:
如梦技术 authored and GitHub committed 2026-07-30 16:03:32 +08:00
1 parent b39a0f18a4
commit adb6d86b53
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,
};
}