fix(sql-file): list Dameng schemas for execution target

This commit is contained in:
t8y2 committed 2026-07-21 15:58:04 +08:00
1 parent 28a5b22e7e
commit e3d5dbda2a
3 files changed
+99 -7

No files matched your search

@@ -15,9 +15,9 @@ import { useToast } from "@/composables/useToast";
import { useConnectionStore } from "@/stores/connectionStore";
import { useProductionSafetyStore } from "@/stores/productionSafetyStore";
import { productionContextForDatabase } from "@/lib/database/productionSafety";
import { databaseOptionsForConnection } from "@/composables/useDatabaseOptions";
import { fetchSqlFileTargetOptions } from "@/composables/useDatabaseOptions";
import { requiresSqlFileTargetDatabaseSelection } from "@/lib/connection/connectionLevelDatabaseBootstrap";
import { cancelSqlFileExecution, executeSqlFile, listenSqlFileProgress, listDatabases, previewSqlFile, type SqlFilePreview, type SqlFileProgress, type SqlFileStatus } from "@/lib/backend/api";
import { cancelSqlFileExecution, executeSqlFile, listenSqlFileProgress, previewSqlFile, type SqlFilePreview, type SqlFileProgress, type SqlFileStatus } from "@/lib/backend/api";
import { useExportTracker } from "@/composables/useExportTracker";
import { Check, CheckSquare, FileCode, FolderOpen, Loader2, Play, Square, X } from "@lucide/vue";
@@ -188,10 +188,9 @@ async function loadDatabasesForConnection(id: string) {
loadingDatabases.value = true;
try {
await store.ensureConnected(id);
const names = databaseOptionsForConnection(
(await listDatabases(id)).map((db) => db.name),
store.getConfig(id),
);
const connection = store.getConfig(id);
if (!connection) return;
const names = await fetchSqlFileTargetOptions(id, connection);
if (token !== databaseLoadToken) return;
databaseOptions.value = names;
database.value = chooseDatabase(names, id);
@@ -0,0 +1,75 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { fetchSqlFileTargetOptions } from "@/composables/useDatabaseOptions";
const mocks = vi.hoisted(() => ({
listDatabases: vi.fn(),
listSchemas: vi.fn(),
}));
vi.mock("@/lib/backend/api", () => ({
listDatabases: mocks.listDatabases,
listSchemas: mocks.listSchemas,
}));
vi.mock("@/stores/connectionStore", () => ({
useConnectionStore: vi.fn(),
}));
describe("fetchSqlFileTargetOptions", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("uses Dameng schemas so independent schemas remain selectable", async () => {
mocks.listSchemas.mockResolvedValue(["APP_USER", "REPORTING", "SYS"]);
const options = await fetchSqlFileTargetOptions("connection-1", {
db_type: "dameng",
database: "APP_USER",
visible_databases: ["APP_USER", "REPORTING"],
});
expect(options).toEqual(["APP_USER", "REPORTING"]);
expect(mocks.listSchemas).toHaveBeenCalledWith("connection-1", "APP_USER", true);
expect(mocks.listDatabases).not.toHaveBeenCalled();
});
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", {
db_type: "dameng",
database: "APP_USER",
visible_databases: ["APP_USER", "REPORTING"],
visible_schemas: { APP_USER: ["ARCHIVE"] },
});
expect(options).toEqual(["ARCHIVE"]);
});
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", {
db_type: "postgres",
database: "app",
visible_databases: ["analytics"],
});
expect(options).toEqual(["analytics"]);
expect(mocks.listDatabases).toHaveBeenCalledWith("connection-2");
expect(mocks.listSchemas).not.toHaveBeenCalled();
});
it("propagates metadata loading errors", async () => {
const error = new Error("schema metadata failed");
mocks.listSchemas.mockRejectedValue(error);
await expect(
fetchSqlFileTargetOptions("connection-1", {
db_type: "dameng",
database: "APP_USER",
}),
).rejects.toBe(error);
});
});
@@ -1,16 +1,34 @@
import { ref } from "vue";
import { useConnectionStore } from "@/stores/connectionStore";
import { filterDatabaseNamesForConnection } from "@/lib/database/visibleDatabases";
import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection } from "@/lib/database/visibleDatabases";
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">;
export function databaseOptionsForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_databases"> | undefined): string[] {
const names = filterDatabaseNamesForConnection(databaseNames, connection);
if (names.length === 0 && usesTreeSchemaMode(connection?.db_type)) return [""];
return names;
}
export async function fetchSqlFileTargetOptions(connectionId: string, connection: SqlFileTargetConnection): Promise<string[]> {
if (connection.db_type === "dameng") {
const database = connection.database || "";
// Dameng users and schemas are not interchangeable: independent schemas
// appear in listSchemas but not in the user-backed listDatabases result.
const schemas = await api.listSchemas(connectionId, database, true);
return filterSchemaNamesForConnection(schemas, connection, database);
}
const databases = await api.listDatabases(connectionId);
return databaseOptionsForConnection(
databases.map((database) => database.name),
connection,
);
}
export function useDatabaseOptions() {
const connectionStore = useConnectionStore();