fix(xugu): exclude synthetic scopes from schema export

This commit is contained in:
Elias
2026-09-06 10:25:16 +08:00
committed by GitHub
parent 6886fde709
commit 5d65be03a3
3 changed files with 55 additions and 4 deletions
@@ -12,7 +12,7 @@ import * as api from "@/lib/backend/api";
import type { ExportProgress } from "@/lib/backend/api";
import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection } from "@/composables/useDatabaseOptions";
import { buildAllDatabaseExportPlan, generateDatabaseExportId, runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, shouldUseDatabaseBackupSnapshot, type AllDatabaseExportPlanItem } from "@/lib/export/databaseExport";
import { buildAllDatabaseExportPlan, filterExportableSchemas, generateDatabaseExportId, runDatabaseExportUntilTerminal, runWithDatabaseBackupSnapshot, shouldUseDatabaseBackupSnapshot, type AllDatabaseExportPlanItem } from "@/lib/export/databaseExport";
import { buildSelectedTablesPayload, isDatabaseExportTableSelectionValid } from "@/lib/export/databaseExportSelection";
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
import { useToast } from "@/composables/useToast";
@@ -215,7 +215,7 @@ async function loadSchemas(preferredSchema = "") {
return;
}
const schemaList = await api.listSchemas(connectionId.value, database.value);
const schemaList = filterExportableSchemas(await api.listSchemas(connectionId.value, database.value), config?.db_type);
const selected = preferredSchema && schemaList.includes(preferredSchema) ? preferredSchema : schemaList.includes("public") ? "public" : (schemaList[0] ?? "");
schemas.value = schemaList;
schema.value = selected;
@@ -289,7 +289,7 @@ async function buildExportPlanForDatabases(dbs: string[]): Promise<AllDatabaseEx
const schemasByDatabase: Record<string, string[]> = {};
if (schemaAware) {
for (const db of dbs) {
schemasByDatabase[db] = await api.listSchemas(connectionId.value, db);
schemasByDatabase[db] = filterExportableSchemas(await api.listSchemas(connectionId.value, db), dbType);
}
}
return buildAllDatabaseExportPlan({ databases: dbs, schemaAware, schemasByDatabase, dbType });
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { buildAllDatabaseExportPlan, filterExportableSchemas } from "@/lib/export/databaseExport";
import { XUGU_PUBLIC_SYNONYM_SCOPE, XUGU_SCHEDULER_JOB_SCOPE } from "@/lib/sidebar/xuguPublicSynonyms";
describe("database export schema selection", () => {
it("hides Xugu synthetic tree scopes while preserving real schemas", () => {
const schemas = ["APP_TEST", "GUEST", XUGU_PUBLIC_SYNONYM_SCOPE, XUGU_SCHEDULER_JOB_SCOPE];
expect(filterExportableSchemas(schemas, "xugu")).toEqual(["APP_TEST", "GUEST"]);
});
it("does not apply Xugu filtering to other database types", () => {
const schemas = ["APP_TEST", XUGU_PUBLIC_SYNONYM_SCOPE, XUGU_SCHEDULER_JOB_SCOPE];
expect(filterExportableSchemas(schemas, "postgres")).toEqual(schemas);
});
it("excludes synthetic scopes from an all-database Xugu export plan", () => {
expect(
buildAllDatabaseExportPlan({
databases: ["SHOP_DEMO"],
schemaAware: true,
dbType: "xugu",
schemasByDatabase: {
SHOP_DEMO: ["APP_TEST", XUGU_PUBLIC_SYNONYM_SCOPE, XUGU_SCHEDULER_JOB_SCOPE],
},
}),
).toEqual([
{
database: "SHOP_DEMO",
schema: "APP_TEST",
fileStem: "SHOP_DEMO",
displayName: "SHOP_DEMO",
},
]);
});
});
+15 -1
View File
@@ -3,6 +3,7 @@ import * as api from "@/lib/backend/api.ts";
import { buildTableSelectSql } from "@/lib/table/tableSelectSql.ts";
import { uuid } from "@/lib/common/utils.ts";
import { SINGLE_DATABASE_TYPES } from "@/lib/database/databaseCapabilitySets";
import { isXuguSyntheticScope } from "@/lib/sidebar/xuguPublicSynonyms";
export const DATABASE_EXPORT_ROW_LIMIT = 10_000;
export const DATABASE_EXPORT_PAGE_SIZE = 500;
@@ -76,6 +77,19 @@ export interface AllDatabaseExportPlanItem {
displayName: string;
}
/**
* Return schemas that can be exported as ordinary schema-owned objects.
*
* Xugu exposes database-global namespaces (public synonyms and scheduler jobs)
* through reserved synthetic schema names so the sidebar can reuse its normal
* tree loading path. They are not real schemas and must not be offered by the
* schema export selector.
*/
export function filterExportableSchemas(schemas: readonly string[], databaseType?: DatabaseType): string[] {
if (databaseType !== "xugu") return [...schemas];
return schemas.filter((schema) => !isXuguSyntheticScope(schema));
}
export interface DatabaseBackupSnapshotOptions {
connectionId: string;
database: string;
@@ -153,7 +167,7 @@ export function buildAllDatabaseExportPlan(options: AllDatabaseExportPlanInput):
}));
}
return options.databases.flatMap((database) => {
const schemas = options.schemaAware ? (options.schemasByDatabase?.[database] ?? []).filter((schema) => schema.trim()) : [database];
const schemas = options.schemaAware ? filterExportableSchemas(options.schemasByDatabase?.[database] ?? [], options.dbType).filter((schema) => schema.trim()) : [database];
const exportSchemas = schemas.length > 0 ? schemas : [database];
const includeSchemaInFileName = options.schemaAware && exportSchemas.length > 1;