fix(transfer): defer MySQL foreign keys until after data transfer

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-20 09:26:55 +08:00
1 parent 78aab1e5ed
commit f36d854e68
6 files changed
+467 -37

No files matched your search

+29 -5
View File
@@ -57,10 +57,12 @@ pub async fn start_transfer(
.await?;
tokio::spawn(async move {
// Sort tables by FK dependency so referenced tables are transferred first.
// Sort tables by FK dependency so referenced tables are transferred first,
// and keep the foreign key metadata fetched along the way — MySQL-family
// targets reuse it per table below instead of re-querying it.
// Skip for external Doris/StarRocks catalogs — the database name does not
// exist in the default catalog and sorting is unnecessary (no FK constraints).
let sorted_tables = {
let (sorted_tables, known_foreign_keys) = {
let skip_fk_sort = {
let configs = state.configs.read().await;
configs
@@ -74,9 +76,9 @@ pub async fn start_transfer(
.is_some()
};
if skip_fk_sort {
request.tables.clone()
(request.tables.clone(), std::collections::HashMap::new())
} else {
dbx_core::transfer::sort_tables_by_fk_dependency(
dbx_core::transfer::sort_tables_by_fk_dependency_with_foreign_keys(
&state,
&request.source_connection_id,
&request.source_database,
@@ -87,7 +89,7 @@ pub async fn start_transfer(
.await
.unwrap_or_else(|e| {
log::warn!("[transfer] failed to sort tables by FK dependency, using original order: {e}");
request.tables.clone()
(request.tables.clone(), std::collections::HashMap::new())
})
}
};
@@ -96,6 +98,7 @@ pub async fn start_transfer(
log::info!("[transfer] starting transfer_id={} tables={}", transfer_id, total_tables);
let mut failed_tables: Vec<String> = Vec::new();
let mut pending_fk_alters: Vec<(String, String)> = Vec::new();
let mut last_rows_transferred = 0_u64;
let mut last_total_rows = None;
@@ -185,6 +188,8 @@ pub async fn start_transfer(
&target_db_type,
&source_pool_key,
&target_pool_key,
&known_foreign_keys,
&mut pending_fk_alters,
|progress| {
last_rows_transferred = progress.rows_transferred;
last_total_rows = progress.total_rows;
@@ -247,6 +252,25 @@ pub async fn start_transfer(
}
}
// Add any foreign keys deferred during MySQL-family table creation now that
// every selected table exists — see transfer_table's use of
// strip_inline_foreign_key_constraint_lines for why these can't be created
// inline (a foreign key cycle has no valid CREATE TABLE order at all).
let mut failed_fk_tables: Vec<String> = Vec::new();
let mut failed_fk_count = 0usize;
for (table, alter_sql) in &pending_fk_alters {
if let Err(e) = dbx_core::transfer::execute_on_pool(&state, &target_pool_key, alter_sql).await {
log::warn!("[transfer] failed to add deferred foreign key constraint for {table}: {e}");
failed_fk_count += 1;
failed_fk_tables.push(table.clone());
}
}
if failed_fk_count > 0 {
failed_fk_tables.sort();
failed_fk_tables.dedup();
failed_tables.push(format!("{} foreign key(s) on: {}", failed_fk_count, failed_fk_tables.join(", ")));
}
// Transfer selected non-table objects (views, procedures, functions,
// triggers, sequences, events) after the per-table loop. The shared
// Core decision handles all content modes: DataOnly never