308 lines
7.8 KiB
Rust
308 lines
7.8 KiB
Rust
//! 状态迁移和升级数据模块
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 升级数据
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct UpgradeData {
|
||
/// 迁移脚本(可选,二进制格式)
|
||
pub migration_script: Option<Vec<u8>>,
|
||
/// 配置变更
|
||
pub config_changes: HashMap<String, String>,
|
||
/// 状态迁移列表
|
||
pub state_migrations: Vec<StateMigration>,
|
||
/// 破坏性变更列表
|
||
pub breaking_changes: Vec<String>,
|
||
}
|
||
|
||
impl UpgradeData {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
migration_script: None,
|
||
config_changes: HashMap::new(),
|
||
state_migrations: Vec::new(),
|
||
breaking_changes: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 添加配置变更
|
||
pub fn add_config_change(&mut self, key: String, value: String) {
|
||
self.config_changes.insert(key, value);
|
||
}
|
||
|
||
/// 添加状态迁移
|
||
pub fn add_state_migration(&mut self, migration: StateMigration) {
|
||
self.state_migrations.push(migration);
|
||
}
|
||
|
||
/// 添加破坏性变更
|
||
pub fn add_breaking_change(&mut self, change: String) {
|
||
self.breaking_changes.push(change);
|
||
}
|
||
|
||
/// 检查是否有破坏性变更
|
||
pub fn has_breaking_changes(&self) -> bool {
|
||
!self.breaking_changes.is_empty()
|
||
}
|
||
|
||
/// 检查是否有状态迁移
|
||
pub fn has_state_migrations(&self) -> bool {
|
||
!self.state_migrations.is_empty()
|
||
}
|
||
}
|
||
|
||
impl Default for UpgradeData {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
/// 状态迁移
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct StateMigration {
|
||
/// 迁移ID
|
||
pub migration_id: String,
|
||
/// 源schema版本
|
||
pub from_schema: String,
|
||
/// 目标schema版本
|
||
pub to_schema: String,
|
||
/// 迁移函数名称
|
||
pub migration_fn: String,
|
||
/// 迁移描述
|
||
pub description: String,
|
||
/// 是否可回滚
|
||
pub reversible: bool,
|
||
}
|
||
|
||
impl StateMigration {
|
||
pub fn new(
|
||
migration_id: String,
|
||
from_schema: String,
|
||
to_schema: String,
|
||
migration_fn: String,
|
||
description: String,
|
||
reversible: bool,
|
||
) -> Self {
|
||
Self {
|
||
migration_id,
|
||
from_schema,
|
||
to_schema,
|
||
migration_fn,
|
||
description,
|
||
reversible,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 迁移脚本
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct MigrationScript {
|
||
/// 脚本ID
|
||
pub script_id: String,
|
||
/// 脚本名称
|
||
pub name: String,
|
||
/// 脚本内容(可能是Rust代码、SQL等)
|
||
pub content: String,
|
||
/// 脚本类型(rust, sql, shell等)
|
||
pub script_type: ScriptType,
|
||
/// 执行顺序
|
||
pub execution_order: u32,
|
||
}
|
||
|
||
impl MigrationScript {
|
||
pub fn new(
|
||
script_id: String,
|
||
name: String,
|
||
content: String,
|
||
script_type: ScriptType,
|
||
execution_order: u32,
|
||
) -> Self {
|
||
Self {
|
||
script_id,
|
||
name,
|
||
content,
|
||
script_type,
|
||
execution_order,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 脚本类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum ScriptType {
|
||
/// Rust代码
|
||
Rust,
|
||
/// SQL脚本
|
||
Sql,
|
||
/// Shell脚本
|
||
Shell,
|
||
/// Python脚本
|
||
Python,
|
||
/// 自定义
|
||
Custom(String),
|
||
}
|
||
|
||
/// 迁移执行器
|
||
pub struct MigrationExecutor {
|
||
scripts: Vec<MigrationScript>,
|
||
}
|
||
|
||
impl MigrationExecutor {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
scripts: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 添加迁移脚本
|
||
pub fn add_script(&mut self, script: MigrationScript) {
|
||
self.scripts.push(script);
|
||
}
|
||
|
||
/// 按执行顺序排序脚本
|
||
pub fn sort_scripts(&mut self) {
|
||
self.scripts.sort_by_key(|s| s.execution_order);
|
||
}
|
||
|
||
/// 获取所有脚本
|
||
pub fn get_scripts(&self) -> &[MigrationScript] {
|
||
&self.scripts
|
||
}
|
||
|
||
/// 执行所有迁移脚本(模拟)
|
||
/// 实际实现需要根据script_type调用不同的执行器
|
||
pub fn execute_all(&self) -> Result<(), String> {
|
||
for script in &self.scripts {
|
||
// 这里只是模拟,实际需要根据script_type执行
|
||
log::info!("Executing migration script: {}", script.name);
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl Default for MigrationExecutor {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_upgrade_data_creation() {
|
||
let data = UpgradeData::new();
|
||
assert!(data.config_changes.is_empty());
|
||
assert!(data.state_migrations.is_empty());
|
||
assert!(data.breaking_changes.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn test_upgrade_data_add_config_change() {
|
||
let mut data = UpgradeData::new();
|
||
data.add_config_change("max_block_size".to_string(), "2MB".to_string());
|
||
|
||
assert_eq!(data.config_changes.get("max_block_size"), Some(&"2MB".to_string()));
|
||
}
|
||
|
||
#[test]
|
||
fn test_upgrade_data_add_state_migration() {
|
||
let mut data = UpgradeData::new();
|
||
let migration = StateMigration::new(
|
||
"mig001".to_string(),
|
||
"v1".to_string(),
|
||
"v2".to_string(),
|
||
"migrate_v1_to_v2".to_string(),
|
||
"Migrate from v1 to v2".to_string(),
|
||
true,
|
||
);
|
||
data.add_state_migration(migration);
|
||
|
||
assert_eq!(data.state_migrations.len(), 1);
|
||
assert!(data.has_state_migrations());
|
||
}
|
||
|
||
#[test]
|
||
fn test_upgrade_data_add_breaking_change() {
|
||
let mut data = UpgradeData::new();
|
||
data.add_breaking_change("Removed deprecated API".to_string());
|
||
|
||
assert_eq!(data.breaking_changes.len(), 1);
|
||
assert!(data.has_breaking_changes());
|
||
}
|
||
|
||
#[test]
|
||
fn test_state_migration_creation() {
|
||
let migration = StateMigration::new(
|
||
"mig001".to_string(),
|
||
"v1".to_string(),
|
||
"v2".to_string(),
|
||
"migrate_v1_to_v2".to_string(),
|
||
"Migrate from v1 to v2".to_string(),
|
||
true,
|
||
);
|
||
|
||
assert_eq!(migration.migration_id, "mig001");
|
||
assert_eq!(migration.from_schema, "v1");
|
||
assert_eq!(migration.to_schema, "v2");
|
||
assert!(migration.reversible);
|
||
}
|
||
|
||
#[test]
|
||
fn test_migration_script_creation() {
|
||
let script = MigrationScript::new(
|
||
"script001".to_string(),
|
||
"init_db".to_string(),
|
||
"CREATE TABLE ...".to_string(),
|
||
ScriptType::Sql,
|
||
1,
|
||
);
|
||
|
||
assert_eq!(script.script_id, "script001");
|
||
assert_eq!(script.name, "init_db");
|
||
assert_eq!(script.script_type, ScriptType::Sql);
|
||
assert_eq!(script.execution_order, 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_migration_executor() {
|
||
let mut executor = MigrationExecutor::new();
|
||
|
||
let script1 = MigrationScript::new(
|
||
"s1".to_string(),
|
||
"script1".to_string(),
|
||
"content1".to_string(),
|
||
ScriptType::Rust,
|
||
2,
|
||
);
|
||
|
||
let script2 = MigrationScript::new(
|
||
"s2".to_string(),
|
||
"script2".to_string(),
|
||
"content2".to_string(),
|
||
ScriptType::Rust,
|
||
1,
|
||
);
|
||
|
||
executor.add_script(script1);
|
||
executor.add_script(script2);
|
||
executor.sort_scripts();
|
||
|
||
let scripts = executor.get_scripts();
|
||
assert_eq!(scripts.len(), 2);
|
||
assert_eq!(scripts[0].execution_order, 1);
|
||
assert_eq!(scripts[1].execution_order, 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_script_type() {
|
||
assert_eq!(ScriptType::Rust, ScriptType::Rust);
|
||
assert_ne!(ScriptType::Rust, ScriptType::Sql);
|
||
|
||
let custom = ScriptType::Custom("wasm".to_string());
|
||
assert!(matches!(custom, ScriptType::Custom(_)));
|
||
}
|
||
}
|