feat(sync): 按数据源组合约束迁移能力

- 展示源端到目标端的完整、部分、规划中和不支持状态
- 按能力控制自动建表、补列、索引及继续执行入口
- 支持数据库列表为空时手工输入并补齐六语言提示
This commit is contained in:
Syngnat
2026-08-08 19:34:36 +08:00
parent f15b630e70
commit 4df879ccde
13 changed files with 639 additions and 74 deletions

View File

@@ -31,6 +31,7 @@ import {
DBGetTables,
DataSync,
DataSyncAnalyze,
DataSyncCapability,
DataSyncPreview,
} from "../../wailsjs/go/app/App";
import { SavedConnection } from "../types";
@@ -76,6 +77,11 @@ import {
startDataSyncBackgroundTask,
useDataSyncBackgroundTask,
} from "./dataSyncBackgroundTask";
import {
resolveDataSyncCapabilityPresentation,
type DataSyncCapabilitySnapshot,
} from "./dataSyncCapability";
import { resolveDataSyncDatabaseSelection } from "./dataSyncDatabaseSelection";
const { Title, Text } = Typography;
const { Step } = Steps;
const { Option } = Select;
@@ -413,6 +419,11 @@ const DataSyncModal: React.FC<{
const [targetSchema, setTargetSchema] = useState<string>("");
const [targetSchemaLoading, setTargetSchemaLoading] =
useState<boolean>(false);
const [migrationCapability, setMigrationCapability] =
useState<DataSyncCapabilitySnapshot | null>(null);
const [migrationCapabilityStatus, setMigrationCapabilityStatus] = useState<
"idle" | "loading" | "ready" | "error"
>("idle");
// Step 2: Tables
const [allTables, setAllTables] = useState<string[]>([]);
@@ -713,17 +724,22 @@ const DataSyncModal: React.FC<{
return;
}
if (workflowType === "migration") {
const supportsAutoCreate = migrationCapability?.supportsAutoCreate === true;
if (syncMode === "insert_update") {
setSyncMode("insert_only");
}
if (syncContent === "schema") {
setSyncContent("both");
}
if (targetTableStrategy === "existing_only") {
if (supportsAutoCreate && targetTableStrategy === "existing_only") {
setTargetTableStrategy("smart");
} else if (!supportsAutoCreate && targetTableStrategy !== "existing_only") {
setTargetTableStrategy("existing_only");
}
if (!createIndexes) {
if (supportsAutoCreate && !createIndexes) {
setCreateIndexes(true);
} else if (!supportsAutoCreate && createIndexes) {
setCreateIndexes(false);
}
} else {
if (targetTableStrategy !== "existing_only") {
@@ -742,6 +758,7 @@ const DataSyncModal: React.FC<{
syncMode,
targetTableStrategy,
createIndexes,
migrationCapability?.supportsAutoCreate,
]);
useEffect(() => {
@@ -750,6 +767,12 @@ const DataSyncModal: React.FC<{
}
}, [syncContent, autoAddColumns]);
useEffect(() => {
if (migrationCapability?.supportsAutoAddColumns === false && autoAddColumns) {
setAutoAddColumns(false);
}
}, [migrationCapability?.supportsAutoAddColumns, autoAddColumns]);
useEffect(() => {
if (sourceDatasetMode !== "query") return;
if (workflowType !== "sync") {
@@ -794,18 +817,17 @@ const DataSyncModal: React.FC<{
try {
const res = await DBGetDatabases(normalizeConnConfig(conn) as any);
if (requestSeq !== sourceDatabaseRequestSeqRef.current) return;
if (res.success) {
const dbRows = Array.isArray(res.data) ? res.data : [];
setSourceDbs(
dbRows
.map((r: any) => r?.Database || r?.database || r?.username)
.filter(
(name: any) => typeof name === "string" && name.trim() !== "",
),
);
}
const selection = resolveDataSyncDatabaseSelection(
conn.config,
res.success && Array.isArray(res.data) ? res.data : [],
);
setSourceDbs(selection.options);
setSourceDb(selection.preferred);
} catch (e: any) {
if (requestSeq !== sourceDatabaseRequestSeqRef.current) return;
const selection = resolveDataSyncDatabaseSelection(conn.config, []);
setSourceDbs(selection.options);
setSourceDb(selection.preferred);
message.error(
tr("data_sync.message.fetch_source_databases_failed_detail", {
detail: e?.message || String(e),
@@ -836,18 +858,17 @@ const DataSyncModal: React.FC<{
try {
const res = await DBGetDatabases(normalizeConnConfig(conn) as any);
if (requestSeq !== targetDatabaseRequestSeqRef.current) return;
if (res.success) {
const dbRows = Array.isArray(res.data) ? res.data : [];
setTargetDbs(
dbRows
.map((r: any) => r?.Database || r?.database || r?.username)
.filter(
(name: any) => typeof name === "string" && name.trim() !== "",
),
);
}
const selection = resolveDataSyncDatabaseSelection(
conn.config,
res.success && Array.isArray(res.data) ? res.data : [],
);
setTargetDbs(selection.options);
setTargetDb(selection.preferred);
} catch (e: any) {
if (requestSeq !== targetDatabaseRequestSeqRef.current) return;
const selection = resolveDataSyncDatabaseSelection(conn.config, []);
setTargetDbs(selection.options);
setTargetDb(selection.preferred);
message.error(
tr("data_sync.message.fetch_target_databases_failed_detail", {
detail: e?.message || String(e),
@@ -871,6 +892,21 @@ const DataSyncModal: React.FC<{
const nextToTables = async () => {
if (!sourceConnId || !targetConnId) return message.error(tr('data_sync.message.select_connections_first'));
if (!isSourceQueryMode && migrationCapabilityStatus === "loading") {
return message.info(tr("data_sync.capability.loading"));
}
if (!isSourceQueryMode && migrationCapabilityStatus === "error") {
return message.error(tr("data_sync.capability.load_failed"));
}
if (
!isSourceQueryMode &&
migrationCapability &&
!migrationCapability.canExecute
) {
return message.error(
resolveDataSyncCapabilityPresentation(migrationCapability, tr).message,
);
}
if (!sourceDb) return message.error(tr('data_sync.message.select_source_database'));
if (!targetDb) return message.error(tr('data_sync.message.select_target_database'));
if (!ensureTargetSchemaSelected()) return;
@@ -1320,6 +1356,28 @@ const DataSyncModal: React.FC<{
() => connections.find((c) => c.id === targetConnId),
[connections, targetConnId],
);
const capabilityPresentation = useMemo(
() =>
migrationCapability
? resolveDataSyncCapabilityPresentation(migrationCapability, tr)
: null,
[migrationCapability, i18nLanguage],
);
const capabilityStatusPresentation = useMemo(() => {
if (migrationCapabilityStatus === "loading") {
return {
alertType: "info" as const,
message: tr("data_sync.capability.loading"),
};
}
if (migrationCapabilityStatus === "error") {
return {
alertType: "error" as const,
message: tr("data_sync.capability.load_failed"),
};
}
return null;
}, [migrationCapabilityStatus, i18nLanguage]);
const targetDialect = useMemo(
() =>
resolveSqlDialect(
@@ -1339,6 +1397,46 @@ const DataSyncModal: React.FC<{
isMigrationWorkflow &&
((sourceType === "redis" && targetType === "mongodb") ||
(sourceType === "mongodb" && targetType === "redis"));
useEffect(() => {
if (!sourceConn || !targetConn) {
setMigrationCapability(null);
setMigrationCapabilityStatus("idle");
return;
}
let cancelled = false;
setMigrationCapability(null);
setMigrationCapabilityStatus("loading");
void DataSyncCapability(
{
type: String(sourceConn.config?.type || ""),
driver: String(sourceConn.config?.driver || ""),
oceanBaseProtocol: String(sourceConn.config?.oceanBaseProtocol || ""),
} as any,
{
type: String(targetConn.config?.type || ""),
driver: String(targetConn.config?.driver || ""),
oceanBaseProtocol: String(targetConn.config?.oceanBaseProtocol || ""),
} as any,
)
.then((capability) => {
if (!cancelled) {
setMigrationCapability(capability as DataSyncCapabilitySnapshot);
setMigrationCapabilityStatus("ready");
}
})
.catch(() => {
if (!cancelled) {
setMigrationCapability(null);
setMigrationCapabilityStatus("error");
}
});
return () => {
cancelled = true;
};
}, [sourceConn, targetConn]);
const defaultMongoCollectionName = useMemo(() => {
if (sourceType === "redis" && targetType === "mongodb") {
return `redis_db_${resolveRedisDbIndex(sourceDb || sourceConn?.config?.database)}_keys`;
@@ -1713,13 +1811,21 @@ const DataSyncModal: React.FC<{
</Select>
</Form.Item>
<Form.Item label={tr("data_sync.field.database")}>
<Select value={sourceDb} onChange={setSourceDb} showSearch>
{sourceDbs.map((d) => (
<Option key={d} value={d}>
{d}
</Option>
))}
</Select>
{sourceDbs.length > 0 ? (
<Select value={sourceDb} onChange={setSourceDb} showSearch>
{sourceDbs.map((d) => (
<Option key={d} value={d}>
{d}
</Option>
))}
</Select>
) : (
<Input
value={sourceDb}
onChange={(event) => setSourceDb(event.target.value)}
placeholder={tr("data_sync.placeholder.database_manual")}
/>
)}
</Form.Item>
</Form>
</Card>
@@ -1764,13 +1870,21 @@ const DataSyncModal: React.FC<{
</Select>
</Form.Item>
<Form.Item label={tr("data_sync.field.database")}>
<Select value={targetDb} onChange={setTargetDb} showSearch>
{targetDbs.map((d) => (
<Option key={d} value={d}>
{d}
</Option>
))}
</Select>
{targetDbs.length > 0 ? (
<Select value={targetDb} onChange={setTargetDb} showSearch>
{targetDbs.map((d) => (
<Option key={d} value={d}>
{d}
</Option>
))}
</Select>
) : (
<Input
value={targetDb}
onChange={(event) => setTargetDb(event.target.value)}
placeholder={tr("data_sync.placeholder.database_manual")}
/>
)}
</Form.Item>
{targetSupportsSchemaSelection && (
<Form.Item label={tr("data_sync.field.schema")}>
@@ -1946,17 +2060,28 @@ const DataSyncModal: React.FC<{
<Select
value={targetTableStrategy}
onChange={setTargetTableStrategy}
disabled={!isMigrationWorkflow || isSourceQueryMode}
disabled={
!isMigrationWorkflow ||
isSourceQueryMode ||
migrationCapabilityStatus !== "ready" ||
capabilityPresentation?.forceExistingTarget === true
}
>
<Option value="existing_only">
{tr("data_sync.option.target_strategy.existing_only")}
</Option>
<Option value="auto_create_if_missing">
<Option
value="auto_create_if_missing"
disabled={migrationCapability?.supportsAutoCreate !== true}
>
{tr(
"data_sync.option.target_strategy.auto_create_if_missing",
)}
</Option>
<Option value="smart">
<Option
value="smart"
disabled={migrationCapability?.supportsAutoCreate !== true}
>
{tr("data_sync.option.target_strategy.smart")}
</Option>
</Select>
@@ -1988,7 +2113,11 @@ const DataSyncModal: React.FC<{
<Checkbox
checked={autoAddColumns}
onChange={(e) => setAutoAddColumns(e.target.checked)}
disabled={isSourceQueryMode || syncContent === "data"}
disabled={
isSourceQueryMode ||
syncContent === "data" ||
migrationCapability?.supportsAutoAddColumns !== true
}
>
{isSchemaCompareEntry
? tr("data_sync.compare_entry.option.auto_add_columns")
@@ -2004,14 +2133,35 @@ const DataSyncModal: React.FC<{
disabled={
!isMigrationWorkflow ||
targetTableStrategy === "existing_only" ||
isSourceQueryMode
isSourceQueryMode ||
migrationCapability?.supportsAutoCreate !== true
}
>
{tr("data_sync.option.create_indexes")}
</Checkbox>
</Form.Item>
)}
{!isSourceQueryMode && capabilityStatusPresentation && (
<Alert
type={capabilityStatusPresentation.alertType}
showIcon
message={capabilityStatusPresentation.message}
style={{ marginBottom: 12 }}
/>
)}
{!isSourceQueryMode &&
capabilityPresentation &&
(isMigrationWorkflow ||
capabilityPresentation.blocksExecution) && (
<Alert
type={capabilityPresentation.alertType}
showIcon
message={capabilityPresentation.message}
style={{ marginBottom: 12 }}
/>
)}
{isMigrationWorkflow &&
!capabilityPresentation &&
targetTableStrategy !== "existing_only" && (
<Alert
type="info"
@@ -2020,14 +2170,16 @@ const DataSyncModal: React.FC<{
style={{ marginBottom: 12 }}
/>
)}
{!isCompareEntry && !isMigrationWorkflow && (
<Alert
type="info"
showIcon
message={tr("data_sync.alert.existing_target_only")}
style={{ marginBottom: 12 }}
/>
)}
{!isCompareEntry &&
!isMigrationWorkflow &&
!capabilityPresentation?.blocksExecution && (
<Alert
type="info"
showIcon
message={tr("data_sync.alert.existing_target_only")}
style={{ marginBottom: 12 }}
/>
)}
{syncContent !== "schema" && syncMode === "full_overwrite" && (
<Alert
type="warning"
@@ -2493,7 +2645,17 @@ const DataSyncModal: React.FC<{
<div style={modalFooterBarStyle}>
{currentStep === 0 && (
<Button type="primary" onClick={nextToTables} loading={loading}>
<Button
type="primary"
onClick={nextToTables}
loading={loading}
disabled={
!isSourceQueryMode &&
(migrationCapabilityStatus === "loading" ||
migrationCapabilityStatus === "error" ||
capabilityPresentation?.blocksExecution === true)
}
>
{tr("data_sync.action.next")}
</Button>
)}
@@ -2512,6 +2674,8 @@ const DataSyncModal: React.FC<{
(isCompareEntry ? false : syncContent === "schema") ||
selectedTables.length === 0 ||
analyzing ||
(!isSourceQueryMode && migrationCapabilityStatus !== "ready") ||
(!isSourceQueryMode && capabilityPresentation?.blocksExecution === true) ||
(isSourceQueryMode && !sourceQuery.trim())
}
style={{ marginRight: 8 }}
@@ -2533,6 +2697,8 @@ const DataSyncModal: React.FC<{
disabled={
selectedTables.length === 0 ||
(isSourceQueryMode && !sourceQuery.trim()) ||
(!isSourceQueryMode && migrationCapabilityStatus !== "ready") ||
(!isSourceQueryMode && capabilityPresentation?.blocksExecution === true) ||
!executionReadiness.ready
}
>

View File

@@ -0,0 +1,36 @@
import { describe, expect, it } from 'vitest';
import { t } from '../i18n';
const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP', 'de-DE', 'ru-RU'] as const;
describe('data sync capability catalog', () => {
it.each(locales)('does not expose the obsolete MySQL-to-Kingbase-only scope in %s', (locale) => {
const plannerScope = t(
'data_sync.alert.auto_create_planner_scope',
undefined,
locale,
).toLowerCase();
const autoAddColumns = t(
'data_sync.option.auto_add_columns',
undefined,
locale,
).toLowerCase();
expect(plannerScope.includes('mysql') && plannerScope.includes('kingbase')).toBe(false);
expect(autoAddColumns.includes('mysql') && autoAddColumns.includes('kingbase')).toBe(false);
});
it.each(locales)('renders the selected source and target pair in %s', (locale) => {
const message = t(
'data_sync.capability.full',
{ sourceType: 'mysql', targetType: 'postgres' },
locale,
);
expect(message).toContain('mysql');
expect(message).toContain('postgres');
expect(message).not.toContain('{{sourceType}}');
expect(message).not.toContain('{{targetType}}');
});
});

View File

@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest';
import {
resolveDataSyncCapabilityPresentation,
type DataSyncCapabilitySnapshot,
} from './dataSyncCapability';
const tr = (key: string, vars?: Record<string, unknown>) =>
`${key}:${String(vars?.sourceType || '')}->${String(vars?.targetType || '')}`;
const capability = (
overrides: Partial<DataSyncCapabilitySnapshot> = {},
): DataSyncCapabilitySnapshot => ({
sourceType: 'mysql',
targetType: 'postgres',
planner: 'mysql-pglike-planner',
supportLevel: 'full',
canExecute: true,
supportsAutoCreate: true,
supportsAutoAddColumns: true,
requiresExistingTarget: false,
...overrides,
});
describe('resolveDataSyncCapabilityPresentation', () => {
it('presents a full planner without the obsolete MySQL-to-Kingbase-only claim', () => {
const result = resolveDataSyncCapabilityPresentation(capability(), tr);
expect(result.alertType).toBe('info');
expect(result.message).toBe(
'data_sync.capability.full:mysql->postgres',
);
expect(result.blocksExecution).toBe(false);
expect(result.forceExistingTarget).toBe(false);
});
it('makes legacy compatibility mode require an existing target', () => {
const result = resolveDataSyncCapabilityPresentation(
capability({
sourceType: 'oracle',
targetType: 'sqlserver',
planner: 'generic-legacy-planner',
supportLevel: 'partial',
supportsAutoCreate: false,
supportsAutoAddColumns: false,
requiresExistingTarget: true,
}),
tr,
);
expect(result.alertType).toBe('warning');
expect(result.message).toBe(
'data_sync.capability.partial:oracle->sqlserver',
);
expect(result.blocksExecution).toBe(false);
expect(result.forceExistingTarget).toBe(true);
});
it.each(['planned', 'unsupported'] as const)(
'blocks %s migration pairs before execution',
(supportLevel) => {
const result = resolveDataSyncCapabilityPresentation(
capability({
supportLevel,
canExecute: false,
supportsAutoCreate: false,
requiresExistingTarget: true,
}),
tr,
);
expect(result.alertType).toBe('error');
expect(result.message).toBe(
`data_sync.capability.${supportLevel}:mysql->postgres`,
);
expect(result.blocksExecution).toBe(true);
expect(result.forceExistingTarget).toBe(true);
},
);
});

View File

@@ -0,0 +1,69 @@
import type { sync } from '../../wailsjs/go/models';
export type DataSyncCapabilitySupportLevel =
| 'full'
| 'partial'
| 'planned'
| 'unsupported';
export type DataSyncCapabilitySnapshot = Pick<
sync.MigrationCapability,
| 'sourceType'
| 'targetType'
| 'planner'
| 'canExecute'
| 'supportsAutoCreate'
| 'supportsAutoAddColumns'
| 'requiresExistingTarget'
> & {
supportLevel: DataSyncCapabilitySupportLevel;
};
type Translate = (
key: string,
variables?: Record<string, string | number | boolean | null | undefined>,
) => string;
export type DataSyncCapabilityPresentation = {
alertType: 'info' | 'warning' | 'error';
message: string;
blocksExecution: boolean;
forceExistingTarget: boolean;
};
export const resolveDataSyncCapabilityPresentation = (
capability: DataSyncCapabilitySnapshot,
tr: Translate,
): DataSyncCapabilityPresentation => {
const sourceType = String(capability.sourceType || '').trim() || 'unknown';
const targetType = String(capability.targetType || '').trim() || 'unknown';
const variables = { sourceType, targetType };
if (capability.supportLevel === 'full' && capability.canExecute) {
return {
alertType: 'info',
message: tr('data_sync.capability.full', variables),
blocksExecution: false,
forceExistingTarget: !capability.supportsAutoCreate,
};
}
if (capability.supportLevel === 'partial' && capability.canExecute) {
return {
alertType: 'warning',
message: tr('data_sync.capability.partial', variables),
blocksExecution: false,
forceExistingTarget: true,
};
}
const supportLevel = capability.supportLevel === 'planned'
? 'planned'
: 'unsupported';
return {
alertType: 'error',
message: tr(`data_sync.capability.${supportLevel}`, variables),
blocksExecution: true,
forceExistingTarget: true,
};
};

View File

@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { resolveDataSyncDatabaseSelection } from './dataSyncDatabaseSelection';
describe('resolveDataSyncDatabaseSelection', () => {
it('deduplicates metadata rows and prefers the configured database', () => {
expect(resolveDataSyncDatabaseSelection(
{ type: 'mysql', database: 'app' },
[{ Database: 'mysql' }, { database: 'APP' }, 'app'],
)).toEqual({
options: ['mysql', 'APP'],
preferred: 'APP',
});
});
it('auto-selects the only database returned by the driver', () => {
expect(resolveDataSyncDatabaseSelection(
{ type: 'trino' },
[{ catalog: 'hive' }],
)).toEqual({ options: ['hive'], preferred: 'hive' });
});
it('falls back to the configured database when enumeration is empty', () => {
expect(resolveDataSyncDatabaseSelection(
{ type: 'sqlite', database: 'D:/data/app.db' },
[],
)).toEqual({
options: ['D:/data/app.db'],
preferred: 'D:/data/app.db',
});
});
it('uses the owner for Oracle service-name connections', () => {
expect(resolveDataSyncDatabaseSelection(
{ type: 'oracle', database: 'ORCL', user: 'APP_OWNER' },
[],
)).toEqual({ options: ['APP_OWNER'], preferred: 'APP_OWNER' });
});
it('uses the owner for OceanBase Oracle tenants', () => {
expect(resolveDataSyncDatabaseSelection(
{
type: 'oceanbase',
database: 'service',
user: 'tenant_owner',
oceanBaseProtocol: 'oracle',
},
[],
)).toEqual({ options: ['tenant_owner'], preferred: 'tenant_owner' });
});
it('leaves a manual-entry fallback when no database can be inferred', () => {
expect(resolveDataSyncDatabaseSelection(
{ type: 'custom', driver: 'acme-sql' },
[],
)).toEqual({ options: [], preferred: '' });
});
});

View File

@@ -0,0 +1,97 @@
import { resolveOceanBaseProtocolFromConfig } from '../utils/oceanBaseProtocol';
type DataSyncDatabaseConfig = {
type?: unknown;
driver?: unknown;
database?: unknown;
user?: unknown;
redisDB?: unknown;
oceanBaseProtocol?: unknown;
connectionParams?: unknown;
uri?: unknown;
};
export type DataSyncDatabaseSelection = {
options: string[];
preferred: string;
};
const METADATA_NAME_KEYS = ['database', 'username', 'name', 'catalog', 'schema'];
const normalizeText = (value: unknown): string => String(value ?? '').trim();
const readMetadataName = (row: unknown): string => {
if (typeof row === 'string') {
return row.trim();
}
if (!row || typeof row !== 'object' || Array.isArray(row)) {
return '';
}
const values = new Map<string, unknown>();
Object.entries(row as Record<string, unknown>).forEach(([key, value]) => {
values.set(key.toLowerCase(), value);
});
for (const key of METADATA_NAME_KEYS) {
const value = normalizeText(values.get(key));
if (value) {
return value;
}
}
return '';
};
const resolveConfiguredDatabase = (config: DataSyncDatabaseConfig): string => {
const type = normalizeText(config.type || config.driver).toLowerCase();
const isOracle = type === 'oracle';
let isOceanBaseOracle = false;
if (type === 'oceanbase') {
try {
isOceanBaseOracle = resolveOceanBaseProtocolFromConfig(
config as Record<string, unknown>,
) === 'oracle';
} catch {
isOceanBaseOracle = false;
}
}
if (isOracle || isOceanBaseOracle) {
return normalizeText(config.user);
}
if (type === 'redis') {
const configuredDatabase = normalizeText(config.database);
return configuredDatabase || normalizeText(config.redisDB);
}
return normalizeText(config.database);
};
export const resolveDataSyncDatabaseSelection = (
config: DataSyncDatabaseConfig,
rows: unknown[],
): DataSyncDatabaseSelection => {
const seen = new Set<string>();
const options: string[] = [];
rows.forEach((row) => {
const name = readMetadataName(row);
const key = name.toLowerCase();
if (!name || seen.has(key)) {
return;
}
seen.add(key);
options.push(name);
});
const configured = resolveConfiguredDatabase(config);
if (options.length === 0) {
return configured
? { options: [configured], preferred: configured }
: { options: [], preferred: '' };
}
const configuredOption = configured
? options.find((option) => option.toLowerCase() === configured.toLowerCase())
: undefined;
return {
options,
preferred: configuredOption || (options.length === 1 ? options[0] : ''),
};
};

View File

@@ -433,6 +433,23 @@ if (
GetTableColumns: async () => [],
DBGetDatabases: async () => ({ success: true, data: ['missav_bot'] }),
DBGetTables: async () => ({ success: true, data: cloneBrowserMockValue(mockQueryTables) }),
DataSyncCapability: async (sourceConfig: any, targetConfig: any) => {
const sourceType = String(sourceConfig?.type || sourceConfig?.driver || '').trim().toLowerCase();
const targetType = String(targetConfig?.type || targetConfig?.driver || '').trim().toLowerCase();
const canExecute = sourceType !== '' && targetType !== '';
return {
sourceType,
targetType,
sourceModel: 'custom',
targetModel: 'custom',
planner: canExecute ? 'browser-mock-existing-target' : '',
supportLevel: canExecute ? 'partial' : 'unsupported',
canExecute,
supportsAutoCreate: false,
supportsAutoAddColumns: false,
requiresExistingTarget: true,
};
},
DBGetAllColumns: async () => ({ success: true, data: cloneBrowserMockValue(mockQueryColumns) }),
DBGetDatabaseForeignKeys: async () => ({ success: true, data: {} }),
DBGetColumns: async (_config: any, _dbName: string, tableName: string) => ({

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "Zurück",
"data_sync.action.start_sync": "Synchronisierung starten",
"data_sync.action.view": "Ansehen",
"data_sync.alert.auto_create_planner_scope": "Automatisches Erstellen von Tabellen unterstützt derzeit nur MySQL nach Kingbase. Spalten, Primärschlüssel, normale Indizes, eindeutige Indizes und zusammengesetzte Indizes werden migriert; Volltext-, räumliche, Präfix- und Funktionsindizes werden ausdrücklich übersprungen.",
"data_sync.alert.auto_create_scope": "Automatisches Erstellen von Tabellen unterstützt derzeit nur MySQL nach Kingbase. Spalten, Primärschlüssel, normale Indizes, eindeutige Indizes und zusammengesetzte Indizes werden migriert; Volltext-, räumliche, Präfix- und Funktionsindizes werden ausdrücklich übersprungen.",
"data_sync.alert.auto_create_planner_scope": "Der Migrationsplaner wird anhand der aktuellen Quell-/Zielkombination gewählt. Der Fähigkeitshinweis und die Vorprüfung bestimmen die Unterstützung für automatische Objekte, Felder und Indizes.",
"data_sync.alert.auto_create_scope": "Der Migrationsplaner wird anhand der aktuellen Quell-/Zielkombination gewählt. Der Fähigkeitshinweis und die Vorprüfung bestimmen die Unterstützung für automatische Objekte, Felder und Indizes.",
"data_sync.alert.existing_target_only": "Die Datensynchronisierung arbeitet standardmäßig mit vorhandenen Zieltabellen. Wechseln Sie zur datenbankübergreifenden Migration, wenn Tabellen erstellt und Daten importiert werden sollen.",
"data_sync.alert.full_overwrite": "Vollständiges Überschreiben löscht Daten in den Zieltabellen. Verwenden Sie diese Option vorsichtig.",
"data_sync.alert.migration_mode": "Die datenbankübergreifende Migration ist aktiv. Nutzen Sie sie, um Tabellen in eine andere Datenquelle zu übertragen, automatisch zu erstellen und zu importieren.",
"data_sync.alert.query_mode": "Die Synchronisierung von SQL-Ergebnismengen unterstützt derzeit benutzerdefiniertes Quell-SQL zu genau einer vorhandenen Zieltabelle. Das Abfrageergebnis muss die Primärschlüsselspalte der Zieltabelle enthalten.",
"data_sync.alert.sync_mode": "Die Datensynchronisierung ist aktiv. Nutzen Sie sie für inkrementelle Synchronisierung oder Import mit Überschreiben, wenn Zieltabellen bereits vorhanden sind.",
"data_sync.capability.full": "{{sourceType}} → {{targetType}} verwendet einen dedizierten Migrationsplaner mit automatischer Zielerstellung. Felder, Indizes und Einschränkungen werden vor der Ausführung geprüft.",
"data_sync.capability.load_failed": "Die Migrationsfähigkeit dieser Quell-/Zielkombination konnte nicht geladen werden; die Ausführung wurde gesperrt. Versuchen Sie es erneut oder prüfen Sie die Anwendungslaufzeit.",
"data_sync.capability.loading": "Die Migrationsfähigkeit der aktuellen Quell-/Zielkombination wird geprüft…",
"data_sync.capability.partial": "{{sourceType}} → {{targetType}} verwendet den Kompatibilitätsmodus für vorhandene Ziele und kann zur Vorprüfung fortfahren, das Ziel aber nicht automatisch erstellen. Erstellen Sie es zuerst; die tatsächliche Lese-/Schreibfähigkeit wird durch Differenzanalyse und Treibervorprüfung bestimmt.",
"data_sync.capability.planned": "{{sourceType}} → {{targetType}} ist an die Migrationsplanung angebunden, kann in dieser Version aber noch nicht ausgeführt werden.",
"data_sync.capability.unsupported": "Migration oder Synchronisierung von {{sourceType}} → {{targetType}} wird noch nicht unterstützt.",
"data_sync.backend.error.analyze_prepare_secrets_failed": "Zugangsdaten für die Analyse des Datenabgleichs konnten nicht vorbereitet werden: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "Änderungen konnten nicht angewendet werden: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "Der Zieltreiber unterstützt das Anwenden von Datenänderungen nicht",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "Zieltabelle ist erforderlich",
"data_sync.backend.warning.apply_changes_unsupported": "Der Zieltreiber unterstützt das Anwenden von Datenänderungen nicht.",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "SQL für automatische Ergänzung der Spalte {{column}} konnte nicht erzeugt werden: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "Automatische Tabellenerstellung unterstützt derzeit nur MySQL -> Kingbase; aktuelles Paar={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "Für die aktuelle Kombination gibt es keinen Planer zur automatischen Zielerstellung: {{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "Spalte {{column}} ist eine Auto-Increment-Spalte; {{feature}} wird beim Ergänzen in einer vorhandenen Zieltabelle nicht automatisch neu erstellt",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "Spalte {{column}} Typ {{type}} wurde zu json herabgestuft",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "Spalte {{column}} Typ {{type}} wurde zu jsonb herabgestuft",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "Vollständiges Überschreiben löscht zuerst die Daten der Zieltabelle und fügt danach Zeilen ein. Bestätigen Sie, dass die Zieldatenbank gesichert wurde.",
"data_sync.modal.full_overwrite_ok": "Fortfahren",
"data_sync.modal.full_overwrite_title": "Vollständiges Überschreiben bestätigen",
"data_sync.option.auto_add_columns": "Fehlende Zielspalten automatisch ergänzen (derzeit für MySQL-Ziele und MySQL nach Kingbase; SQL-Ergebnismengenmodus wird nicht unterstützt)",
"data_sync.option.auto_add_columns": "Fehlende Zielspalten automatisch ergänzen (Verfügbarkeit hängt von der aktuellen Quell-/Zielkombination ab; SQL-Ergebnismengenmodus wird nicht unterstützt)",
"data_sync.option.content.both": "Schema und Daten synchronisieren",
"data_sync.option.content.data": "Nur Daten",
"data_sync.option.content.schema": "Nur Schema",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "Datenbankübergreifende Migration (automatisch erstellen und importieren)",
"data_sync.option.workflow.sync": "Datensynchronisierung (Unterschiede mit vorhandenen Zieltabellen synchronisieren)",
"data_sync.placeholder.mongo_collection_name": "Mongo-Collection-Namen eingeben",
"data_sync.placeholder.database_manual": "Datenbank, Schema oder Katalog eingeben",
"data_sync.placeholder.source_query_sql": "Beispiel: SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "Eine Zieltabelle auswählen",
"data_sync.plan.add_missing_columns_before_import": "{{count}} fehlende Felder vor dem Import ergänzen",

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "Previous",
"data_sync.action.start_sync": "Start Sync",
"data_sync.action.view": "View",
"data_sync.alert.auto_create_planner_scope": "Automatic table creation currently supports only MySQL to Kingbase. It migrates columns, primary keys, regular indexes, unique indexes, and composite indexes, and skips full-text, spatial, prefix, and function indexes explicitly.",
"data_sync.alert.auto_create_scope": "Automatic table creation currently supports only MySQL to Kingbase. It migrates columns, primary keys, regular indexes, unique indexes, and composite indexes, and skips full-text, spatial, prefix, and function indexes explicitly.",
"data_sync.alert.auto_create_planner_scope": "The migration planner is selected from the current source and target pair. The pair capability notice and preflight results define automatic object, field, and index support.",
"data_sync.alert.auto_create_scope": "The migration planner is selected from the current source and target pair. The pair capability notice and preflight results define automatic object, field, and index support.",
"data_sync.alert.existing_target_only": "Data sync runs against existing target tables by default. Switch to cross-database migration when you need table creation and import.",
"data_sync.alert.full_overwrite": "Full overwrite clears target table data. Use it carefully.",
"data_sync.alert.migration_mode": "Cross-database migration is active. Use it to move tables to another data source with automatic table creation and import.",
"data_sync.alert.query_mode": "SQL result-set sync currently supports custom source SQL to one existing target table. The query result must include the target table primary-key column.",
"data_sync.alert.sync_mode": "Data sync is active. Use it for incremental sync or overwrite import when target tables already exist.",
"data_sync.capability.full": "{{sourceType}} → {{targetType}} uses a dedicated migration planner with automatic target creation. Fields, indexes, and degradations are checked before execution.",
"data_sync.capability.load_failed": "The migration capability for this source/target pair could not be loaded, so execution is blocked. Retry or check the application runtime.",
"data_sync.capability.loading": "Checking migration capability for the current source/target pair…",
"data_sync.capability.partial": "{{sourceType}} → {{targetType}} uses existing-target compatibility mode and may proceed to preflight, but cannot create the target automatically. Create it first; actual read/write support is determined by diff analysis and driver preflight.",
"data_sync.capability.planned": "{{sourceType}} → {{targetType}} is connected to the migration planning layer, but execution is not available in this version.",
"data_sync.capability.unsupported": "Migration or sync from {{sourceType}} → {{targetType}} is not supported yet.",
"data_sync.backend.error.analyze_prepare_secrets_failed": "Failed to prepare data sync analysis secrets: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "Failed to apply changes: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "The target driver does not support applying data changes",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "Target table is required",
"data_sync.backend.warning.apply_changes_unsupported": "The target driver does not support applying data changes.",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "Failed to generate auto-add-column SQL for column {{column}}: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "Automatic table creation currently supports only MySQL -> Kingbase; current pair={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "The current pair has no automatic target-creation planner: {{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "Column {{column}} is an auto-increment column; {{feature}} will not be recreated automatically when adding it to an existing target table",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "Column {{column}} type {{type}} was degraded to json",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "Column {{column}} type {{type}} was degraded to jsonb",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "Full overwrite clears target table data before inserting rows. Confirm that the target database has been backed up.",
"data_sync.modal.full_overwrite_ok": "Continue",
"data_sync.modal.full_overwrite_title": "Confirm Full Overwrite",
"data_sync.option.auto_add_columns": "Add missing target columns automatically (currently supports MySQL targets and MySQL to Kingbase; SQL result-set mode is not supported)",
"data_sync.option.auto_add_columns": "Add missing target columns automatically (availability depends on the current source/target pair; SQL result-set mode is not supported)",
"data_sync.option.content.both": "Sync Schema + Data",
"data_sync.option.content.data": "Data Only",
"data_sync.option.content.schema": "Schema Only",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "Cross-Database Migration (create tables automatically and import)",
"data_sync.option.workflow.sync": "Data Sync (compare and sync against existing target tables)",
"data_sync.placeholder.mongo_collection_name": "Enter Mongo collection name",
"data_sync.placeholder.database_manual": "Enter a database, schema, or catalog",
"data_sync.placeholder.source_query_sql": "Example: SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "Select one target table",
"data_sync.plan.add_missing_columns_before_import": "Add {{count}} missing columns before import",

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "戻る",
"data_sync.action.start_sync": "同期を開始",
"data_sync.action.view": "表示",
"data_sync.alert.auto_create_planner_scope": "自動テーブル作成は現在 MySQL から Kingbase への移行のみ対応しています。列、主キー、通常インデックス、一意インデックス、複合インデックスを移行し、全文、空間、プレフィックス、関数系インデックスは明示的にスキップします。",
"data_sync.alert.auto_create_scope": "自動テーブル作成は現在 MySQL から Kingbase への移行のみ対応しています。列、主キー、通常インデックス、一意インデックス、複合インデックスを移行し、全文、空間、プレフィックス、関数系インデックスは明示的にスキップします。",
"data_sync.alert.auto_create_planner_scope": "現在のソースとターゲットの組み合わせに応じて移行プランナーを選択します。オブジェクト、フィールド、インデックスの自動作成可否は、組み合わせの機能表示と事前検証結果に従います。",
"data_sync.alert.auto_create_scope": "現在のソースとターゲットの組み合わせに応じて移行プランナーを選択します。オブジェクト、フィールド、インデックスの自動作成可否は、組み合わせの機能表示と事前検証結果に従います。",
"data_sync.alert.existing_target_only": "データ同期は既存のターゲットテーブルに対して実行されます。テーブル作成とインポートが必要な場合は、クロスデータベース移行に切り替えてください。",
"data_sync.alert.full_overwrite": "全量上書きはターゲットテーブルのデータを消去します。慎重に使用してください。",
"data_sync.alert.migration_mode": "クロスデータベース移行が有効です。別のデータソースへテーブルを移し、自動作成とインポートを行う場合に使用します。",
"data_sync.alert.query_mode": "SQL 結果セット同期は現在、ソース側のカスタム SQL から単一の既存ターゲットテーブルへの同期に対応しています。クエリ結果にはターゲットテーブルの主キー列が必要です。",
"data_sync.alert.sync_mode": "データ同期が有効です。ターゲットテーブルが既に存在する場合の増分同期や上書きインポートに使用します。",
"data_sync.capability.full": "{{sourceType}} → {{targetType}} は専用移行プランナーを使用し、ターゲットの自動作成に対応しています。実行前にフィールド、インデックス、縮退項目を検証します。",
"data_sync.capability.load_failed": "このソース/ターゲットの移行機能を読み込めなかったため、実行を停止しました。再試行するか、アプリケーションの実行環境を確認してください。",
"data_sync.capability.loading": "現在のソース/ターゲットの移行機能を確認しています…",
"data_sync.capability.partial": "{{sourceType}} → {{targetType}} は既存ターゲット互換モードで事前検証へ進めますが、自動作成には未対応です。先にターゲットを作成してください。実際の読み書き可否は差分分析とドライバー事前検証で確認されます。",
"data_sync.capability.planned": "{{sourceType}} → {{targetType}} は移行計画レイヤーに接続済みですが、このバージョンではまだ実行できません。",
"data_sync.capability.unsupported": "{{sourceType}} → {{targetType}} の移行または同期にはまだ対応していません。",
"data_sync.backend.error.analyze_prepare_secrets_failed": "データ同期分析のシークレット準備に失敗しました: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "変更の適用に失敗しました: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "ターゲットドライバーはデータ変更の適用をサポートしていません",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "ターゲットテーブルは必須です",
"data_sync.backend.warning.apply_changes_unsupported": "ターゲットドライバーはデータ変更の適用をサポートしていません。",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "列 {{column}} の自動追加 SQL 生成に失敗しました: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "自動テーブル作成は現在 MySQL -> Kingbase のみサポートしています。現在の組み合わせ={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "現在の組み合わせには自動ターゲット作成プランナーがありません:{{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "列 {{column}} は自動増分列です。既存ターゲットテーブルに追加する際、{{feature}} は自動再作成されません",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "列 {{column}} の型 {{type}} は json に降格されました",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "列 {{column}} の型 {{type}} は jsonb に降格されました",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "全量上書きはターゲットテーブルのデータを消去してから行を挿入します。ターゲットデータベースのバックアップを確認してください。",
"data_sync.modal.full_overwrite_ok": "続行",
"data_sync.modal.full_overwrite_title": "全量上書きの確認",
"data_sync.option.auto_add_columns": "ターゲットテーブルに不足している列を自動追加(現在は MySQL ターゲットおよび MySQL から Kingbase に対応。SQL 結果セットモードは未対応)",
"data_sync.option.auto_add_columns": "ターゲットテーブルに不足している列を自動追加(可否は現在のソース/ターゲットの組み合わせによります。SQL 結果セットモードは未対応)",
"data_sync.option.content.both": "スキーマとデータを同期",
"data_sync.option.content.data": "データのみ",
"data_sync.option.content.schema": "スキーマのみ",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "クロスデータベース移行(自動作成後にインポート)",
"data_sync.option.workflow.sync": "データ同期(既存ターゲットテーブルとの差分を同期)",
"data_sync.placeholder.mongo_collection_name": "Mongo コレクション名を入力",
"data_sync.placeholder.database_manual": "データベース、スキーマ、またはカタログを入力",
"data_sync.placeholder.source_query_sql": "例: SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "ターゲットテーブルを 1 つ選択",
"data_sync.plan.add_missing_columns_before_import": "{{count}} 個の不足フィールドを補完してからインポート",

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "Назад",
"data_sync.action.start_sync": "Начать синхронизацию",
"data_sync.action.view": "Просмотреть",
"data_sync.alert.auto_create_planner_scope": "Автоматическое создание таблиц сейчас поддерживает только MySQL в Kingbase. Переносятся столбцы, первичные ключи, обычные индексы, уникальные индексы и составные индексы; полнотекстовые, пространственные, префиксные и функциональные индексы явно пропускаются.",
"data_sync.alert.auto_create_scope": "Автоматическое создание таблиц сейчас поддерживает только MySQL в Kingbase. Переносятся столбцы, первичные ключи, обычные индексы, уникальные индексы и составные индексы; полнотекстовые, пространственные, префиксные и функциональные индексы явно пропускаются.",
"data_sync.alert.auto_create_planner_scope": "Планировщик миграции выбирается по текущей паре источника и цели. Поддержка автоматического создания объектов, полей и индексов определяется сведениями о возможностях пары и результатами предварительной проверки.",
"data_sync.alert.auto_create_scope": "Планировщик миграции выбирается по текущей паре источника и цели. Поддержка автоматического создания объектов, полей и индексов определяется сведениями о возможностях пары и результатами предварительной проверки.",
"data_sync.alert.existing_target_only": "Синхронизация данных по умолчанию выполняется с существующими целевыми таблицами. Переключитесь на межбазовую миграцию, если нужны создание таблиц и импорт.",
"data_sync.alert.full_overwrite": "Полная перезапись очищает данные целевых таблиц. Используйте ее осторожно.",
"data_sync.alert.migration_mode": "Активна межбазовая миграция. Используйте ее для переноса таблиц в другой источник данных с автоматическим созданием и импортом.",
"data_sync.alert.query_mode": "Синхронизация результата SQL сейчас поддерживает пользовательский SQL источника в одну существующую целевую таблицу. Результат запроса должен содержать столбец первичного ключа целевой таблицы.",
"data_sync.alert.sync_mode": "Активна синхронизация данных. Используйте ее для инкрементальной синхронизации или импорта с перезаписью, когда целевые таблицы уже существуют.",
"data_sync.capability.full": "Для {{sourceType}} → {{targetType}} используется специализированный планировщик миграции с автоматическим созданием цели. Перед запуском проверяются поля, индексы и ограничения.",
"data_sync.capability.load_failed": "Не удалось загрузить возможности миграции для этой пары источника и цели, поэтому выполнение заблокировано. Повторите попытку или проверьте среду приложения.",
"data_sync.capability.loading": "Проверяются возможности миграции для текущей пары источника и цели…",
"data_sync.capability.partial": "Для {{sourceType}} → {{targetType}} используется режим совместимости с существующей целью: можно перейти к предварительной проверке, но автоматическое создание недоступно. Сначала создайте цель; фактическая поддержка чтения и записи определяется анализом различий и проверкой драйвера.",
"data_sync.capability.planned": "Пара {{sourceType}} → {{targetType}} подключена к уровню планирования миграции, но выполнение в этой версии пока недоступно.",
"data_sync.capability.unsupported": "Миграция или синхронизация {{sourceType}} → {{targetType}} пока не поддерживается.",
"data_sync.backend.error.analyze_prepare_secrets_failed": "Не удалось подготовить секреты анализа синхронизации данных: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "Не удалось применить изменения: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "Целевой драйвер не поддерживает применение изменений данных",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "Целевая таблица обязательна",
"data_sync.backend.warning.apply_changes_unsupported": "Целевой драйвер не поддерживает применение изменений данных.",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "Не удалось создать SQL для автоматического добавления столбца {{column}}: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "Автоматическое создание таблиц сейчас поддерживает только MySQL -> Kingbase; текущая пара={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "Для текущей пары нет планировщика автоматического создания цели: {{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "Столбец {{column}} является автоинкрементным; {{feature}} не будет автоматически создан заново при добавлении в существующую целевую таблицу",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "Столбец {{column}} типа {{type}} деградирован до json",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "Столбец {{column}} типа {{type}} деградирован до jsonb",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "Полная перезапись сначала очищает целевую таблицу, а затем вставляет строки. Подтвердите, что целевая база данных сохранена в резервной копии.",
"data_sync.modal.full_overwrite_ok": "Продолжить",
"data_sync.modal.full_overwrite_title": "Подтвердите полную перезапись",
"data_sync.option.auto_add_columns": "Автоматически добавить недостающие целевые столбцы (сейчас поддерживаются цели MySQL и MySQL в Kingbase; режим результата SQL не поддерживается)",
"data_sync.option.auto_add_columns": "Автоматически добавить недостающие целевые столбцы (доступность зависит от текущей пары источника и цели; режим результата SQL не поддерживается)",
"data_sync.option.content.both": "Синхронизировать схему и данные",
"data_sync.option.content.data": "Только данные",
"data_sync.option.content.schema": "Только схема",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "Межбазовая миграция (автоматическое создание и импорт)",
"data_sync.option.workflow.sync": "Синхронизация данных (сравнение и синхронизация с существующими целевыми таблицами)",
"data_sync.placeholder.mongo_collection_name": "Введите имя коллекции Mongo",
"data_sync.placeholder.database_manual": "Введите базу данных, схему или каталог",
"data_sync.placeholder.source_query_sql": "Пример: SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "Выберите одну целевую таблицу",
"data_sync.plan.add_missing_columns_before_import": "Дополнить отсутствующие поля перед импортом: {{count}}",

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "上一步",
"data_sync.action.start_sync": "开始同步",
"data_sync.action.view": "查看",
"data_sync.alert.auto_create_planner_scope": "自动建表当前仅支持 MySQL 到 Kingbase。会迁移字段、主键、普通索引、唯一索引和联合索引并明确跳过全文、空间、前缀和函数类索引。",
"data_sync.alert.auto_create_scope": "自动建表当前仅支持 MySQL 到 Kingbase。会迁移字段、主键、普通索引、唯一索引和联合索引并明确跳过全文、空间、前缀和函数类索引。",
"data_sync.alert.auto_create_planner_scope": "系统会按当前源端和目标端选择迁移规划器;自动建表、字段与索引的实际支持范围以组合能力提示和预检结果为准。",
"data_sync.alert.auto_create_scope": "系统会按当前源端和目标端选择迁移规划器;自动建表、字段与索引的实际支持范围以组合能力提示和预检结果为准。",
"data_sync.alert.existing_target_only": "数据同步默认使用已有目标表执行。需要跨数据源建表并导入时,请切换到跨库迁移。",
"data_sync.alert.full_overwrite": "全量覆盖会清空目标表数据,请谨慎使用。",
"data_sync.alert.migration_mode": "当前为跨库迁移。适合将表迁移到另一数据源,并自动建表和导入数据。",
"data_sync.alert.query_mode": "SQL 结果集同步当前支持源端自定义 SQL 到单个已存在目标表。查询结果必须包含目标表主键列。",
"data_sync.alert.sync_mode": "当前为数据同步。适合目标表已存在时做增量同步或覆盖导入。",
"data_sync.capability.full": "{{sourceType}} → {{targetType}} 已接入专用迁移规划器,支持自动创建目标对象;执行前会预检字段、索引和降级项。",
"data_sync.capability.load_failed": "无法读取当前源/目标组合的迁移能力,已阻止继续执行;请重试或检查应用运行时。",
"data_sync.capability.loading": "正在验证当前源/目标组合的迁移能力…",
"data_sync.capability.partial": "{{sourceType}} → {{targetType}} 使用已有目标对象兼容模式,可进入预检但暂不支持自动创建;请先创建目标对象,实际读写能力以差异分析和驱动预检为准。",
"data_sync.capability.planned": "{{sourceType}} → {{targetType}} 已进入迁移规划阶段,但当前版本尚不可执行。",
"data_sync.capability.unsupported": "当前尚不支持 {{sourceType}} → {{targetType}} 的迁移或同步。",
"data_sync.backend.error.analyze_prepare_secrets_failed": "准备数据同步分析密钥失败: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "应用变更失败: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "目标驱动不支持应用数据变更",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "目标表不能为空",
"data_sync.backend.warning.apply_changes_unsupported": "目标驱动不支持应用数据变更。",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "字段 {{column}} 自动补齐 SQL 生成失败: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "自动建表目前仅支持 MySQL -> Kingbase当前组合={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "当前组合未接入自动建表规划器:{{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "字段 {{column}} 为自增列,补齐到已有目标表时不会自动补建 {{feature}}",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "字段 {{column}} 类型 {{type}} 已降级为 json",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "字段 {{column}} 类型 {{type}} 已降级为 jsonb",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "全量覆盖会先清空目标表数据再插入,请确认目标库已完成备份。",
"data_sync.modal.full_overwrite_ok": "继续执行",
"data_sync.modal.full_overwrite_title": "确认全量覆盖",
"data_sync.option.auto_add_columns": "自动补齐目标表缺失字段(当前支持 MySQL 目标及 MySQL 到 KingbaseSQL 结果集模式暂不支持)",
"data_sync.option.auto_add_columns": "自动补齐目标表缺失字段(是否可用由当前源/目标组合决定SQL 结果集模式暂不支持)",
"data_sync.option.content.both": "同步结构和数据",
"data_sync.option.content.data": "仅同步数据",
"data_sync.option.content.schema": "仅同步结构",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "跨库迁移(自动建表后导入)",
"data_sync.option.workflow.sync": "数据同步(基于已有目标表做差异同步)",
"data_sync.placeholder.mongo_collection_name": "请输入 Mongo 集合名",
"data_sync.placeholder.database_manual": "请输入数据库、Schema 或 Catalog",
"data_sync.placeholder.source_query_sql": "例如SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "请选择一个目标表",
"data_sync.plan.add_missing_columns_before_import": "补齐 {{count}} 个缺失字段后导入",

View File

@@ -4477,13 +4477,19 @@
"data_sync.action.previous": "上一步",
"data_sync.action.start_sync": "開始同步",
"data_sync.action.view": "查看",
"data_sync.alert.auto_create_planner_scope": "自動建表目前僅支援 MySQL 到 Kingbase。會遷移欄位、主鍵、一般索引、唯一索引和聯合索引並明確跳過全文、空間、前綴和函數類索引。",
"data_sync.alert.auto_create_scope": "自動建表目前僅支援 MySQL 到 Kingbase。會遷移欄位、主鍵、一般索引、唯一索引和聯合索引並明確跳過全文、空間、前綴和函數類索引。",
"data_sync.alert.auto_create_planner_scope": "系統會依目前來源端與目標端選擇遷移規劃器;自動建表、欄位與索引的實際支援範圍以組合能力提示和預檢結果為準。",
"data_sync.alert.auto_create_scope": "系統會依目前來源端與目標端選擇遷移規劃器;自動建表、欄位與索引的實際支援範圍以組合能力提示和預檢結果為準。",
"data_sync.alert.existing_target_only": "資料同步預設使用已有目標表執行。需要跨資料來源建表並導入時,請切換到跨庫遷移。",
"data_sync.alert.full_overwrite": "全量覆寫會清空目標表資料,請謹慎使用。",
"data_sync.alert.migration_mode": "目前為跨庫遷移。適合將表遷移到另一資料來源,並自動建表和導入資料。",
"data_sync.alert.query_mode": "SQL 結果集同步目前支援來源端自定義 SQL 到單個已存在目標表。查詢結果必須包含目標表主鍵列。",
"data_sync.alert.sync_mode": "目前為資料同步。適合目標表已存在時做增量同步或覆寫導入。",
"data_sync.capability.full": "{{sourceType}} → {{targetType}} 已接入專用遷移規劃器,支援自動建立目標物件;執行前會預檢欄位、索引與降級項目。",
"data_sync.capability.load_failed": "無法讀取目前來源/目標組合的遷移能力,已阻止繼續執行;請重試或檢查應用程式執行環境。",
"data_sync.capability.loading": "正在驗證目前來源/目標組合的遷移能力…",
"data_sync.capability.partial": "{{sourceType}} → {{targetType}} 使用既有目標物件相容模式,可進入預檢但暫不支援自動建立;請先建立目標物件,實際讀寫能力以差異分析與驅動預檢為準。",
"data_sync.capability.planned": "{{sourceType}} → {{targetType}} 已進入遷移規劃階段,但目前版本尚不可執行。",
"data_sync.capability.unsupported": "目前尚不支援 {{sourceType}} → {{targetType}} 的遷移或同步。",
"data_sync.backend.error.analyze_prepare_secrets_failed": "準備資料同步分析金鑰失敗: {{detail}}",
"data_sync.backend.error.apply_changes_failed": "套用變更失敗: {{detail}}",
"data_sync.backend.error.apply_changes_unsupported": "目標驅動不支援套用資料變更",
@@ -4613,7 +4619,7 @@
"data_sync.backend.validation.target_table_required": "目標表不能為空",
"data_sync.backend.warning.apply_changes_unsupported": "目標驅動不支援套用資料變更。",
"data_sync.backend.warning.auto_add_column_sql_generation_failed": "欄位 {{column}} 自動補齊 SQL 產生失敗: {{detail}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "自動建表目前僅支援 MySQL -> Kingbase目前組合={{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_create_pair_unsupported": "目前組合尚未接入自動建表規劃器:{{sourceType}} -> {{targetType}}",
"data_sync.backend.warning.auto_increment_not_preserved_existing_target_add_column": "欄位 {{column}} 為自增列,補齊到已有目標表時不會自動補建 {{feature}}",
"data_sync.backend.warning.clickhouse_complex_type_degraded_mysql": "欄位 {{column}} 型別 {{type}} 已降級為 json",
"data_sync.backend.warning.clickhouse_complex_type_degraded_pg_like": "欄位 {{column}} 型別 {{type}} 已降級為 jsonb",
@@ -4769,7 +4775,7 @@
"data_sync.modal.full_overwrite_content": "全量覆寫會先清空目標表資料再插入,請確認目標庫已完成備份。",
"data_sync.modal.full_overwrite_ok": "繼續執行",
"data_sync.modal.full_overwrite_title": "確認全量覆寫",
"data_sync.option.auto_add_columns": "自動補齊目標表缺失欄位(目前支援 MySQL 目標及 MySQL 到 KingbaseSQL 結果集模式暫不支援)",
"data_sync.option.auto_add_columns": "自動補齊目標表缺失欄位(是否可用由目前來源/目標組合決定SQL 結果集模式暫不支援)",
"data_sync.option.content.both": "同步結構和資料",
"data_sync.option.content.data": "僅同步資料",
"data_sync.option.content.schema": "僅同步結構",
@@ -4786,6 +4792,7 @@
"data_sync.option.workflow.migration": "跨庫遷移(自動建表後導入)",
"data_sync.option.workflow.sync": "資料同步(基於已有目標表做差異同步)",
"data_sync.placeholder.mongo_collection_name": "請輸入 Mongo 集合名",
"data_sync.placeholder.database_manual": "請輸入資料庫、Schema 或 Catalog",
"data_sync.placeholder.source_query_sql": "例如SELECT id, name, email FROM users WHERE status = 'active'",
"data_sync.placeholder.target_table": "請選擇一個目標表",
"data_sync.plan.add_missing_columns_before_import": "補齊 {{count}} 個缺失欄位後導入",