use std::path::Path;
use std::sync::Arc;
use futures::stream::{FuturesUnordered, StreamExt};
use crate::database::sharding::ShardRouter;
#[derive(Debug, Clone)]
pub struct ShardMigrationResult {
pub shard_id: u32,
pub success: bool,
pub applied_versions: Vec<u32>,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct OrchestratedMigrationResult {
pub total_shards: u32,
pub success_count: u32,
pub failed_shards: Vec<ShardMigrationResult>,
pub results: Vec<ShardMigrationResult>,
}
pub struct ShardMigrationOrchestrator {
router: Arc<ShardRouter>,
parallel: bool,
}
impl ShardMigrationOrchestrator {
pub fn new(router: Arc<ShardRouter>, parallel: bool) -> Self {
Self { router, parallel }
}
pub async fn orchestrate_migration(
&self,
_migrations_dir: &Path,
) -> OrchestratedMigrationResult {
let shards = self.router.all_shards();
let total_shards = shards.len() as u32;
if self.parallel {
self.orchestrate_parallel(shards, total_shards).await
} else {
self.orchestrate_serial(shards, total_shards).await
}
}
async fn orchestrate_parallel(
&self,
shards: Vec<&crate::database::sharding::ShardInfo>,
total_shards: u32,
) -> OrchestratedMigrationResult {
let mut futures = FuturesUnordered::new();
for shard_info in shards {
let shard_id = shard_info.shard_id;
futures.push(async move {
ShardMigrationResult {
shard_id,
success: false,
applied_versions: Vec::new(),
error: Some(
"分片迁移编排尚未实现:未接入 MigrationExecutor,该分片未执行任何迁移"
.to_string(),
),
}
});
}
let mut results = Vec::new();
while let Some(result) = futures.next().await {
results.push(result);
}
Self::summarize(total_shards, results)
}
async fn orchestrate_serial(
&self,
shards: Vec<&crate::database::sharding::ShardInfo>,
total_shards: u32,
) -> OrchestratedMigrationResult {
let mut results = Vec::new();
for shard_info in shards {
let shard_id = shard_info.shard_id;
results.push(ShardMigrationResult {
shard_id,
success: false,
applied_versions: Vec::new(),
error: Some(
"分片迁移编排尚未实现:未接入 MigrationExecutor,该分片未执行任何迁移"
.to_string(),
),
});
}
Self::summarize(total_shards, results)
}
fn summarize(
total_shards: u32,
results: Vec<ShardMigrationResult>,
) -> OrchestratedMigrationResult {
let success_count = results.iter().filter(|r| r.success).count() as u32;
let failed_shards: Vec<_> = results.iter().filter(|r| !r.success).cloned().collect();
OrchestratedMigrationResult {
total_shards,
success_count,
failed_shards,
results,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_router(shard_count: u32) -> Arc<ShardRouter> {
let mut router = ShardRouter::with_strategy("yearly", shard_count);
for shard_id in 0..shard_count {
router.register_shard(
shard_id,
format!("shard_{}", shard_id),
format!("sqlite://shard_{}.db", shard_id),
);
}
Arc::new(router)
}
#[tokio::test]
async fn test_orchestrate_parallel_reports_unimplemented_failure() {
let orchestrator = ShardMigrationOrchestrator::new(make_router(2), true);
let result = orchestrator
.orchestrate_migration(Path::new("./migrations"))
.await;
assert_eq!(result.total_shards, 2);
assert_eq!(result.results.len(), 2);
assert_eq!(result.success_count, 0, "占位实现不应报告任何成功分片");
assert_eq!(result.failed_shards.len(), 2);
for shard in &result.results {
assert!(!shard.success);
assert!(shard.error.is_some(), "error 应说明尚未实现");
assert!(
shard.error.as_deref().unwrap().contains("尚未实现"),
"实际错误: {:?}",
shard.error
);
assert!(shard.applied_versions.is_empty());
}
}
#[tokio::test]
async fn test_orchestrate_serial_reports_unimplemented_failure() {
let orchestrator = ShardMigrationOrchestrator::new(make_router(3), false);
let result = orchestrator
.orchestrate_migration(Path::new("./migrations"))
.await;
assert_eq!(result.total_shards, 3);
assert_eq!(result.results.len(), 3);
assert_eq!(result.success_count, 0);
assert_eq!(result.failed_shards.len(), 3);
for shard in &result.results {
assert!(!shard.success);
assert!(shard.error.is_some());
}
}
}