1 parent
7c8e0a88cf
commit
e7ef9df85c
4 files changed
+123
-51
No files matched your search
@@ -21,7 +21,8 @@ import type { DatabaseType } from "@/types/database";
|
||||
import type { TransferTask, TransferTaskConfig } from "@/types/database";
|
||||
import { isSchemaAware, supportsTransfer } from "@/lib/database/databaseCapabilities";
|
||||
import { isDorisFamilyCatalogCapable } from "@/lib/database/databaseFeatureSupport";
|
||||
import { isSameTransferDatabase, normalizeTransferCatalog } from "@/lib/database/dataTransferSelection";
|
||||
import { decodeTransferDatabaseOption, encodeTransferDatabaseOptions, isSameTransferDatabase, isTransferDatabaseSelected, normalizeTransferCatalog } from "@/lib/database/dataTransferSelection";
|
||||
import { formatDatabaseLabel } from "@/lib/database/defaultDatabase";
|
||||
import { databaseOptionsForConnection, fetchCatalogNamespaceOptions, fetchNamespaceOptionsForConnection, namespaceOptionsAreSchemas } from "@/composables/useDatabaseOptions";
|
||||
import { useExportTracker } from "@/composables/useExportTracker";
|
||||
import { useTransferTaskStore, TransferTaskNameConflictError, nextTransferTaskCopyName } from "@/stores/transferTaskStore";
|
||||
@@ -184,19 +185,37 @@ function isCatalogCapable(id: string): boolean {
|
||||
return isDorisFamilyCatalogCapable(config?.db_type, config?.driver_profile);
|
||||
}
|
||||
|
||||
function decodedDatabase(connectionId: string, option: string): string {
|
||||
return decodeTransferDatabaseOption(connectionType(connectionId), option);
|
||||
}
|
||||
|
||||
function encodedDatabase(connectionId: string, database: string): string {
|
||||
return encodeTransferDatabaseOptions(connectionType(connectionId), [database])[0] ?? "";
|
||||
}
|
||||
|
||||
function databaseOptionLabel(connectionId: string, option: string): string {
|
||||
return formatDatabaseLabel(store.getConfig(connectionId), decodedDatabase(connectionId, option), {
|
||||
defaultDatabase: t("editor.defaultDatabase"),
|
||||
noDatabase: t("editor.noDatabase"),
|
||||
});
|
||||
}
|
||||
|
||||
const sourceDatabaseName = computed(() => decodedDatabase(sourceConnectionId.value, sourceDatabase.value));
|
||||
const targetDatabaseName = computed(() => decodedDatabase(targetConnectionId.value, targetDatabase.value));
|
||||
|
||||
const canStart = computed(() => {
|
||||
const effectiveSourceSchema = sourceSchema.value || sourceDatabase.value;
|
||||
const effectiveTargetSchema = targetSchema.value || targetDatabase.value;
|
||||
const effectiveSourceSchema = sourceSchema.value || sourceDatabaseName.value;
|
||||
const effectiveTargetSchema = targetSchema.value || targetDatabaseName.value;
|
||||
const sameCatalogAndDatabase = isSameTransferDatabase(
|
||||
{ connectionId: sourceConnectionId.value, catalog: sourceCatalog.value, catalogs: sourceCatalogs.value, database: sourceDatabase.value },
|
||||
{ connectionId: targetConnectionId.value, catalog: targetCatalog.value, catalogs: targetCatalogs.value, database: targetDatabase.value },
|
||||
{ connectionId: sourceConnectionId.value, catalog: sourceCatalog.value, catalogs: sourceCatalogs.value, database: sourceDatabaseName.value },
|
||||
{ connectionId: targetConnectionId.value, catalog: targetCatalog.value, catalogs: targetCatalogs.value, database: targetDatabaseName.value },
|
||||
);
|
||||
const sameSourceAndTarget = sameCatalogAndDatabase && effectiveSourceSchema === effectiveTargetSchema;
|
||||
return (
|
||||
!!sourceConnectionId.value &&
|
||||
!!sourceDatabase.value &&
|
||||
isTransferDatabaseSelected(sourceDatabase.value) &&
|
||||
!!targetConnectionId.value &&
|
||||
!!targetDatabase.value &&
|
||||
isTransferDatabaseSelected(targetDatabase.value) &&
|
||||
(sourceCatalogs.value.length <= 1 || !!sourceCatalog.value) &&
|
||||
(targetCatalogs.value.length <= 1 || !!targetCatalog.value) &&
|
||||
(selectedTables.value.size > 0 || Object.values(selectedObjects.value).some((names) => names.size > 0)) &&
|
||||
@@ -249,13 +268,14 @@ async function loadDatabases(connectionId: string, target: "source" | "target",
|
||||
const config = store.getConfig(connectionId);
|
||||
if (!config) return;
|
||||
const names = isMongoConnection(connectionId) ? databaseOptionsForConnection(await api.mongoListDatabases(connectionId), config) : await fetchNamespaceOptionsForConnection(connectionId, config);
|
||||
const options = encodeTransferDatabaseOptions(config.db_type, names);
|
||||
if (isStale()) return;
|
||||
if (target === "source") {
|
||||
sourceDatabases.value = names;
|
||||
sourceDatabase.value = names.length === 1 ? names[0] : "";
|
||||
sourceDatabases.value = options;
|
||||
sourceDatabase.value = options.length === 1 ? options[0] : "";
|
||||
} else {
|
||||
targetDatabases.value = names;
|
||||
targetDatabase.value = names.length === 1 ? names[0] : "";
|
||||
targetDatabases.value = options;
|
||||
targetDatabase.value = options.length === 1 ? options[0] : "";
|
||||
}
|
||||
} catch {
|
||||
if (isStale()) return;
|
||||
@@ -277,13 +297,14 @@ async function loadDatabasesForCatalog(connectionId: string, catalog: string, ta
|
||||
const config = store.getConfig(connectionId);
|
||||
if (!config) return;
|
||||
const names = await fetchCatalogNamespaceOptions(connectionId, catalog, config);
|
||||
const options = encodeTransferDatabaseOptions(config.db_type, names);
|
||||
if (isStale()) return;
|
||||
if (target === "source") {
|
||||
sourceDatabases.value = names;
|
||||
sourceDatabase.value = names.length === 1 ? names[0] : "";
|
||||
sourceDatabases.value = options;
|
||||
sourceDatabase.value = options.length === 1 ? options[0] : "";
|
||||
} else {
|
||||
targetDatabases.value = names;
|
||||
targetDatabase.value = names.length === 1 ? names[0] : "";
|
||||
targetDatabases.value = options;
|
||||
targetDatabase.value = options.length === 1 ? options[0] : "";
|
||||
}
|
||||
} catch {
|
||||
if (isStale()) return;
|
||||
@@ -293,11 +314,11 @@ async function loadDatabasesForCatalog(connectionId: string, catalog: string, ta
|
||||
}
|
||||
|
||||
async function loadSchemas(connectionId: string, database: string, side: "source" | "target", preferredSchema = "", isCancelled: () => boolean = () => false) {
|
||||
if (!connectionId || !database) return;
|
||||
if (!connectionId) return;
|
||||
// 竞态防护:连接或数据库切换后,旧请求的 schema 列表回调直接丢弃。
|
||||
const isStale = () => {
|
||||
const currentConnection = side === "source" ? sourceConnectionId.value : targetConnectionId.value;
|
||||
const currentDatabase = side === "source" ? sourceDatabase.value : targetDatabase.value;
|
||||
const currentDatabase = side === "source" ? sourceDatabaseName.value : targetDatabaseName.value;
|
||||
return isCancelled() || currentConnection !== connectionId || currentDatabase !== database;
|
||||
};
|
||||
if (isMongoConnection(connectionId)) {
|
||||
@@ -362,8 +383,9 @@ function applyPendingObjectSelection() {
|
||||
|
||||
async function loadObjects(isCancelled: () => boolean = () => false) {
|
||||
const connectionId = sourceConnectionId.value;
|
||||
const database = sourceDatabase.value;
|
||||
if (!connectionId || !database) {
|
||||
const databaseOption = sourceDatabase.value;
|
||||
const database = sourceDatabaseName.value;
|
||||
if (!connectionId || !isTransferDatabaseSelected(databaseOption)) {
|
||||
objectGroups.value = {};
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +393,7 @@ async function loadObjects(isCancelled: () => boolean = () => false) {
|
||||
// 旧请求的对象列表回调直接丢弃,不覆盖新选择对应的状态。
|
||||
const catalog = sourceCatalog.value || undefined;
|
||||
const schemaValue = sourceSchema.value;
|
||||
const isStale = () => isCancelled() || sourceConnectionId.value !== connectionId || sourceDatabase.value !== database || (sourceCatalog.value || undefined) !== catalog || sourceSchema.value !== schemaValue;
|
||||
const isStale = () => isCancelled() || sourceConnectionId.value !== connectionId || sourceDatabase.value !== databaseOption || (sourceCatalog.value || undefined) !== catalog || sourceSchema.value !== schemaValue;
|
||||
loadingObjects.value = true;
|
||||
try {
|
||||
if (isMongoConnection(connectionId)) {
|
||||
@@ -465,18 +487,19 @@ watch(sourceDatabase, async (db) => {
|
||||
skipSourceDatabaseWatch.value = false;
|
||||
return;
|
||||
}
|
||||
if (db) {
|
||||
if (isTransferDatabaseSelected(db)) {
|
||||
const config = store.getConfig(sourceConnectionId.value);
|
||||
const database = sourceDatabaseName.value;
|
||||
if (namespaceOptionsAreSchemas(config)) {
|
||||
// Dameng has no selectable catalog, so the top-level namespace option is
|
||||
// also the schema used for metadata lookup and qualified transfer SQL.
|
||||
sourceSchemas.value = [];
|
||||
sourceSchema.value = db;
|
||||
sourceSchema.value = database;
|
||||
} else if (isSchemaAware(config?.db_type)) {
|
||||
await loadSchemas(sourceConnectionId.value, db, "source", pendingSourceSchemaPrefill.value);
|
||||
await loadSchemas(sourceConnectionId.value, database, "source", pendingSourceSchemaPrefill.value);
|
||||
pendingSourceSchemaPrefill.value = "";
|
||||
} else {
|
||||
sourceSchema.value = db;
|
||||
sourceSchema.value = database;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -531,16 +554,17 @@ watch(targetDatabase, async (db) => {
|
||||
skipTargetDatabaseWatch.value = false;
|
||||
return;
|
||||
}
|
||||
if (db) {
|
||||
if (isTransferDatabaseSelected(db)) {
|
||||
const config = store.getConfig(targetConnectionId.value);
|
||||
const database = targetDatabaseName.value;
|
||||
if (namespaceOptionsAreSchemas(config)) {
|
||||
targetSchemas.value = [];
|
||||
targetSchema.value = db;
|
||||
targetSchema.value = database;
|
||||
} else if (isSchemaAware(config?.db_type)) {
|
||||
await loadSchemas(targetConnectionId.value, db, "target", pendingTargetSchemaPrefill.value);
|
||||
await loadSchemas(targetConnectionId.value, database, "target", pendingTargetSchemaPrefill.value);
|
||||
pendingTargetSchemaPrefill.value = "";
|
||||
} else {
|
||||
targetSchema.value = db;
|
||||
targetSchema.value = database;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -568,7 +592,7 @@ watch(
|
||||
} else {
|
||||
await loadDatabases(props.prefillConnectionId, "source");
|
||||
}
|
||||
if (props.prefillDatabase) sourceDatabase.value = props.prefillDatabase;
|
||||
if (props.prefillDatabase !== undefined) sourceDatabase.value = encodedDatabase(props.prefillConnectionId, props.prefillDatabase);
|
||||
}
|
||||
if (props.prefillTargetConnectionId) {
|
||||
skipTargetWatch.value = true;
|
||||
@@ -581,7 +605,7 @@ watch(
|
||||
} else {
|
||||
await loadDatabases(props.prefillTargetConnectionId, "target");
|
||||
}
|
||||
if (props.prefillTargetDatabase) targetDatabase.value = props.prefillTargetDatabase;
|
||||
if (props.prefillTargetDatabase !== undefined) targetDatabase.value = encodedDatabase(props.prefillTargetConnectionId, props.prefillTargetDatabase);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -631,21 +655,21 @@ async function startTransfer() {
|
||||
if (!canStart.value || isSubmitting.value) return;
|
||||
isSubmitting.value = true;
|
||||
|
||||
const effectiveSourceSchema = sourceSchema.value || sourceDatabase.value;
|
||||
const effectiveTargetSchema = targetSchema.value || targetDatabase.value;
|
||||
const sourceDatabaseName = sourceDatabase.value;
|
||||
const effectiveSourceSchema = sourceSchema.value || sourceDatabaseName.value;
|
||||
const effectiveTargetSchema = targetSchema.value || targetDatabaseName.value;
|
||||
const sourceDatabase = sourceDatabaseName.value;
|
||||
const targetConnection = targetConnectionId.value;
|
||||
const targetDatabaseName = targetDatabase.value;
|
||||
const targetDatabase = targetDatabaseName.value;
|
||||
const shouldRefreshTargetTree = transferContent.value !== "dataOnly";
|
||||
|
||||
const request: api.TransferRequest = {
|
||||
transferId: uuid(),
|
||||
sourceConnectionId: sourceConnectionId.value,
|
||||
sourceDatabase: sourceDatabaseName,
|
||||
sourceDatabase,
|
||||
sourceSchema: effectiveSourceSchema,
|
||||
sourceCatalog: normalizeTransferCatalog(sourceCatalog.value, sourceCatalogs.value) || undefined,
|
||||
targetConnectionId: targetConnection,
|
||||
targetDatabase: targetDatabaseName,
|
||||
targetDatabase,
|
||||
targetSchema: effectiveTargetSchema,
|
||||
targetCatalog: normalizeTransferCatalog(targetCatalog.value, targetCatalogs.value) || undefined,
|
||||
tables: [...selectedTables.value],
|
||||
@@ -729,11 +753,11 @@ function currentConfig(): TransferTaskConfig {
|
||||
return {
|
||||
sourceConnectionId: sourceConnectionId.value,
|
||||
sourceCatalog: normalizeTransferCatalog(sourceCatalog.value, sourceCatalogs.value) || undefined,
|
||||
sourceDatabase: sourceDatabase.value,
|
||||
sourceDatabase: sourceDatabaseName.value,
|
||||
sourceSchema: sourceSchema.value || undefined,
|
||||
targetConnectionId: targetConnectionId.value,
|
||||
targetCatalog: normalizeTransferCatalog(targetCatalog.value, targetCatalogs.value) || undefined,
|
||||
targetDatabase: targetDatabase.value,
|
||||
targetDatabase: targetDatabaseName.value,
|
||||
targetSchema: targetSchema.value || undefined,
|
||||
objects,
|
||||
content: transferContent.value,
|
||||
@@ -755,7 +779,7 @@ function configSnapshot(config: TransferTaskConfig) {
|
||||
const formHasContent = computed(() => !!sourceConnectionId.value || !!targetConnectionId.value);
|
||||
const isConfigDirty = computed(() => !!activeTaskId.value && configSnapshot(currentConfig()) !== savedConfigSnapshot.value);
|
||||
const needsDiscardConfirm = computed(() => isConfigDirty.value || (!activeTaskId.value && formHasContent.value));
|
||||
const canSaveConfig = computed(() => !!sourceConnectionId.value && !!sourceDatabase.value && !!targetConnectionId.value && !!targetDatabase.value);
|
||||
const canSaveConfig = computed(() => !!sourceConnectionId.value && isTransferDatabaseSelected(sourceDatabase.value) && !!targetConnectionId.value && isTransferDatabaseSelected(targetDatabase.value));
|
||||
|
||||
/** Applies a saved task to the form, loading catalogs/databases/schemas/objects with explicit awaits. */
|
||||
async function loadTaskIntoForm(task: TransferTask) {
|
||||
@@ -787,11 +811,12 @@ async function loadTaskIntoForm(task: TransferTask) {
|
||||
await loadDatabases(config.sourceConnectionId, "source", isTaskLoadStale);
|
||||
if (isTaskLoadStale()) return;
|
||||
}
|
||||
if (sourceDatabase.value !== config.sourceDatabase) {
|
||||
skipSourceDatabaseWatch.value = true;
|
||||
sourceDatabase.value = config.sourceDatabase;
|
||||
}
|
||||
const sourceConfig = store.getConfig(config.sourceConnectionId);
|
||||
const sourceDatabaseOption = encodedDatabase(config.sourceConnectionId, config.sourceDatabase);
|
||||
if (sourceDatabase.value !== sourceDatabaseOption) {
|
||||
skipSourceDatabaseWatch.value = true;
|
||||
sourceDatabase.value = sourceDatabaseOption;
|
||||
}
|
||||
if (namespaceOptionsAreSchemas(sourceConfig)) {
|
||||
sourceSchemas.value = [];
|
||||
sourceSchema.value = config.sourceDatabase;
|
||||
@@ -823,11 +848,12 @@ async function loadTaskIntoForm(task: TransferTask) {
|
||||
await loadDatabases(config.targetConnectionId, "target", isTaskLoadStale);
|
||||
if (isTaskLoadStale()) return;
|
||||
}
|
||||
if (targetDatabase.value !== config.targetDatabase) {
|
||||
skipTargetDatabaseWatch.value = true;
|
||||
targetDatabase.value = config.targetDatabase;
|
||||
}
|
||||
const targetConfig = store.getConfig(config.targetConnectionId);
|
||||
const targetDatabaseOption = encodedDatabase(config.targetConnectionId, config.targetDatabase);
|
||||
if (targetDatabase.value !== targetDatabaseOption) {
|
||||
skipTargetDatabaseWatch.value = true;
|
||||
targetDatabase.value = targetDatabaseOption;
|
||||
}
|
||||
if (namespaceOptionsAreSchemas(targetConfig)) {
|
||||
targetSchemas.value = [];
|
||||
targetSchema.value = config.targetDatabase;
|
||||
@@ -884,8 +910,8 @@ function onSelectedTaskIdUpdate(id: string | null) {
|
||||
/** Number of objects currently selected across all kinds. */
|
||||
const selectedObjectCount = computed(() => Object.values(selectedObjects.value).reduce((total, names) => total + (names?.size ?? 0), 0));
|
||||
|
||||
const startConfirmSource = computed(() => `${getConnectionName(sourceConnectionId.value)}.${sourceDatabase.value}`);
|
||||
const startConfirmTarget = computed(() => `${getConnectionName(targetConnectionId.value)}.${targetDatabase.value}`);
|
||||
const startConfirmSource = computed(() => `${getConnectionName(sourceConnectionId.value)}.${databaseOptionLabel(sourceConnectionId.value, sourceDatabase.value)}`);
|
||||
const startConfirmTarget = computed(() => `${getConnectionName(targetConnectionId.value)}.${databaseOptionLabel(targetConnectionId.value, targetDatabase.value)}`);
|
||||
|
||||
/** Opens the confirmation dialog before starting a transfer. */
|
||||
function requestStartTransfer() {
|
||||
@@ -985,6 +1011,7 @@ async function saveConfigTask() {
|
||||
<SearchableSelect
|
||||
v-model="sourceDatabase"
|
||||
:options="sourceDatabases"
|
||||
:display-name="(option) => databaseOptionLabel(sourceConnectionId, option)"
|
||||
:placeholder="t('transfer.selectDatabase')"
|
||||
:search-placeholder="t('transfer.searchDatabase')"
|
||||
:empty-text="t('common.noResults')"
|
||||
@@ -1055,6 +1082,7 @@ async function saveConfigTask() {
|
||||
<SearchableSelect
|
||||
v-model="targetDatabase"
|
||||
:options="targetDatabases"
|
||||
:display-name="(option) => databaseOptionLabel(targetConnectionId, option)"
|
||||
:placeholder="t('transfer.selectDatabase')"
|
||||
:search-placeholder="t('transfer.searchDatabase')"
|
||||
:empty-text="t('common.noResults')"
|
||||
|
||||
@@ -99,6 +99,15 @@ describe("DataTransferDialog transfer prefill", () => {
|
||||
expect(dialogSource).toContain("store.refreshObjectListTreeNode(request.targetConnectionId, request.targetDatabase, request.targetSchema, request.targetCatalog)");
|
||||
});
|
||||
|
||||
it("keeps default tree-schema databases selectable without leaking sentinels", () => {
|
||||
expect(dialogSource).toContain("const sourceDatabaseName = computed(() => decodedDatabase(sourceConnectionId.value, sourceDatabase.value))");
|
||||
expect(dialogSource).toContain("const targetDatabaseName = computed(() => decodedDatabase(targetConnectionId.value, targetDatabase.value))");
|
||||
expect(dialogSource).toContain("sourceDatabase: sourceDatabaseName.value");
|
||||
expect(dialogSource).toContain("targetDatabase: targetDatabaseName.value");
|
||||
expect(dialogSource).toContain(':display-name="(option) => databaseOptionLabel(sourceConnectionId, option)"');
|
||||
expect(dialogSource).toContain(':display-name="(option) => databaseOptionLabel(targetConnectionId, option)"');
|
||||
});
|
||||
|
||||
it("discards stale async results after the connection changes", () => {
|
||||
// 竞态防护:切换连接后旧请求的回调必须丢弃,不能覆盖新选择的下拉选项
|
||||
expect(dialogSource).toContain("if (isStale()) return;");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isSameTransferDatabase, normalizeTransferCatalog, type TransferDatabaseSelection } from "@/lib/database/dataTransferSelection";
|
||||
import { decodeTransferDatabaseOption, encodeTransferDatabaseOptions, isSameTransferDatabase, isTransferDatabaseSelected, normalizeTransferCatalog, type TransferDatabaseSelection } from "@/lib/database/dataTransferSelection";
|
||||
import { TREE_SCHEMA_DEFAULT_DATABASE_SELECT_VALUE } from "@/lib/database/defaultDatabase";
|
||||
import type { CatalogInfo } from "@/types/database";
|
||||
|
||||
function catalog(name: string, catalog_type: string): CatalogInfo {
|
||||
@@ -17,6 +18,27 @@ function selection(overrides: Partial<TransferDatabaseSelection> = {}): Transfer
|
||||
}
|
||||
|
||||
describe("data transfer database selection", () => {
|
||||
it("keeps a Kingbase default database distinct from no selection", () => {
|
||||
const options = encodeTransferDatabaseOptions("kingbase", [""]);
|
||||
|
||||
expect(options).toEqual([TREE_SCHEMA_DEFAULT_DATABASE_SELECT_VALUE]);
|
||||
expect(isTransferDatabaseSelected(options[0])).toBe(true);
|
||||
expect(isTransferDatabaseSelected("")).toBe(false);
|
||||
expect(decodeTransferDatabaseOption("kingbase", options[0])).toBe("");
|
||||
});
|
||||
|
||||
it("uses the tree-schema default database contract for PostgreSQL", () => {
|
||||
const [option] = encodeTransferDatabaseOptions("postgres", [""]);
|
||||
|
||||
expect(option).toBe(TREE_SCHEMA_DEFAULT_DATABASE_SELECT_VALUE);
|
||||
expect(decodeTransferDatabaseOption("postgres", option)).toBe("");
|
||||
});
|
||||
|
||||
it.each(["kingbase", "dameng", "mysql", "mongodb", "starrocks"] as const)("preserves named %s databases", (databaseType) => {
|
||||
expect(encodeTransferDatabaseOptions(databaseType, ["app", "analytics"])).toEqual(["app", "analytics"]);
|
||||
expect(decodeTransferDatabaseOption(databaseType, "analytics")).toBe("analytics");
|
||||
});
|
||||
|
||||
it("normalizes empty, Doris internal, and StarRocks default catalogs", () => {
|
||||
expect(normalizeTransferCatalog("", [])).toBe("");
|
||||
expect(normalizeTransferCatalog("internal", [])).toBe("");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isInternalDorisCatalog } from "@/lib/database/databaseFeatureSupport";
|
||||
import type { CatalogInfo } from "@/types/database";
|
||||
import { decodeSelectableDatabaseValue, encodeSelectableDatabaseValue } from "@/lib/database/defaultDatabase";
|
||||
import type { CatalogInfo, DatabaseType } from "@/types/database";
|
||||
|
||||
export interface TransferDatabaseSelection {
|
||||
connectionId: string;
|
||||
@@ -8,6 +9,18 @@ export interface TransferDatabaseSelection {
|
||||
database: string;
|
||||
}
|
||||
|
||||
export function encodeTransferDatabaseOptions(databaseType: DatabaseType | undefined, databases: readonly string[]): string[] {
|
||||
return databases.map((database) => encodeSelectableDatabaseValue(databaseType, database));
|
||||
}
|
||||
|
||||
export function decodeTransferDatabaseOption(databaseType: DatabaseType | undefined, option: string): string {
|
||||
return decodeSelectableDatabaseValue(databaseType, option);
|
||||
}
|
||||
|
||||
export function isTransferDatabaseSelected(option: string): boolean {
|
||||
return option.length > 0;
|
||||
}
|
||||
|
||||
export function normalizeTransferCatalog(catalog: string, catalogs: readonly CatalogInfo[]): string {
|
||||
const normalizedCatalog = catalog.trim();
|
||||
if (!normalizedCatalog) return "";
|
||||
|
||||
Reference in new issue
Block a user