NAC_Blockchain/nac-upgrade-framework/src/migration.rs

308 lines
7.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 状态迁移和升级数据模块
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(_)));
}
}