mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
feat: add schema version management for database migrations
Implement SQLite PRAGMA user_version based migration system: - Track schema version with SCHEMA_VERSION constant - Apply migrations automatically on init and import - Reject databases from future versions for forward compatibility - Add comprehensive tests for version transitions - Prepare infrastructure for future schema evolution This lays the foundation for safe incremental database upgrades.
This commit is contained in:
@@ -32,6 +32,7 @@ macro_rules! lock_conn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DB_BACKUP_RETAIN: usize = 10;
|
const DB_BACKUP_RETAIN: usize = 10;
|
||||||
|
const SCHEMA_VERSION: i32 = 1;
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
// 使用 Mutex 包装 Connection 以支持在多线程环境(如 Tauri State)中共享
|
// 使用 Mutex 包装 Connection 以支持在多线程环境(如 Tauri State)中共享
|
||||||
@@ -59,6 +60,7 @@ impl Database {
|
|||||||
conn: Mutex::new(conn),
|
conn: Mutex::new(conn),
|
||||||
};
|
};
|
||||||
db.create_tables()?;
|
db.create_tables()?;
|
||||||
|
db.apply_schema_migrations()?;
|
||||||
|
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
@@ -177,6 +179,56 @@ impl Database {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_user_version(conn: &Connection) -> Result<i32, AppError> {
|
||||||
|
conn.query_row("PRAGMA user_version;", [], |row| row.get(0))
|
||||||
|
.map_err(|e| AppError::Database(format!("读取 user_version 失败: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_user_version(conn: &Connection, version: i32) -> Result<(), AppError> {
|
||||||
|
if version < 0 {
|
||||||
|
return Err(AppError::Database(
|
||||||
|
"user_version 不能为负数".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let sql = format!("PRAGMA user_version = {version};");
|
||||||
|
conn.execute(&sql, [])
|
||||||
|
.map_err(|e| AppError::Database(format!("写入 user_version 失败: {e}")))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_schema_migrations(&self) -> Result<(), AppError> {
|
||||||
|
let conn = lock_conn!(self.conn);
|
||||||
|
Self::apply_schema_migrations_on_conn(&conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_schema_migrations_on_conn(conn: &Connection) -> Result<(), AppError> {
|
||||||
|
let mut version = Self::get_user_version(conn)?;
|
||||||
|
|
||||||
|
if version > SCHEMA_VERSION {
|
||||||
|
return Err(AppError::Database(format!(
|
||||||
|
"数据库版本过新({version}),当前应用仅支持 {SCHEMA_VERSION},请升级应用后再尝试。"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
while version < SCHEMA_VERSION {
|
||||||
|
match version {
|
||||||
|
0 => {
|
||||||
|
log::info!("检测到 user_version=0,设置为初始版本 {SCHEMA_VERSION}");
|
||||||
|
Self::set_user_version(conn, SCHEMA_VERSION)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(AppError::Database(format!(
|
||||||
|
"未知的数据库版本 {version},无法迁移到 {SCHEMA_VERSION}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version = Self::get_user_version(conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// 创建内存快照以避免长时间持有数据库锁
|
/// 创建内存快照以避免长时间持有数据库锁
|
||||||
fn snapshot_to_memory(&self) -> Result<Connection, AppError> {
|
fn snapshot_to_memory(&self) -> Result<Connection, AppError> {
|
||||||
let conn = lock_conn!(self.conn);
|
let conn = lock_conn!(self.conn);
|
||||||
@@ -236,6 +288,7 @@ impl Database {
|
|||||||
|
|
||||||
// 补齐缺失表/索引并进行基础校验
|
// 补齐缺失表/索引并进行基础校验
|
||||||
Self::create_tables_on_conn(&temp_conn)?;
|
Self::create_tables_on_conn(&temp_conn)?;
|
||||||
|
Self::apply_schema_migrations_on_conn(&temp_conn)?;
|
||||||
Self::validate_basic_state(&temp_conn)?;
|
Self::validate_basic_state(&temp_conn)?;
|
||||||
|
|
||||||
// 使用 Backup 将临时库原子写回主库
|
// 使用 Backup 将临时库原子写回主库
|
||||||
@@ -1223,3 +1276,40 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn migration_sets_user_version_when_missing() {
|
||||||
|
let conn = Connection::open_in_memory().expect("open memory db");
|
||||||
|
|
||||||
|
Database::create_tables_on_conn(&conn).expect("create tables");
|
||||||
|
assert_eq!(
|
||||||
|
Database::get_user_version(&conn).expect("read version before"),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
Database::apply_schema_migrations_on_conn(&conn).expect("apply migration");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Database::get_user_version(&conn).expect("read version after"),
|
||||||
|
SCHEMA_VERSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn migration_rejects_future_version() {
|
||||||
|
let conn = Connection::open_in_memory().expect("open memory db");
|
||||||
|
Database::create_tables_on_conn(&conn).expect("create tables");
|
||||||
|
Database::set_user_version(&conn, SCHEMA_VERSION + 1).expect("set future version");
|
||||||
|
|
||||||
|
let err = Database::apply_schema_migrations_on_conn(&conn)
|
||||||
|
.expect_err("should reject higher version");
|
||||||
|
assert!(
|
||||||
|
err.to_string().contains("数据库版本过新"),
|
||||||
|
"unexpected error: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user