mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-01 17:42:18 +08:00
fix(schema): add missing base columns migration for proxy_config (#492)
* fix(schema): add missing base columns migration for proxy_config Add compatibility migration for older databases that may be missing the basic proxy_config columns (proxy_enabled, listen_address, listen_port, enable_logging) before adding newer timeout fields. * fix: add proxy_config base column patches for v3.9.0-2 upgrade Add base config column patches in create_tables_on_conn(): - proxy_enabled - listen_address - listen_port - enable_logging Ensures v3.9.0-2 users (user_version=2 but missing columns) can properly upgrade with all required fields added. * fix: migrate proxy_config singleton to per-app on startup for v2 databases Add startup migration for legacy proxy_config tables that still have singleton structure (no app_type column) even with user_version=2. This fixes the issue where v3.9.0-2 databases with v2 schema but legacy proxy_config structure would fail with "no such column: app_type" error. - Call migrate_proxy_config_to_per_app in create_tables_on_conn - Add regression test to verify the fix * style: cargo fmt --------- Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -75,8 +75,7 @@ mod tests {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn test_get_macos_app_bundle_path_valid() {
|
||||
let exe_path =
|
||||
std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch");
|
||||
let exe_path = std::path::Path::new("/Applications/CC Switch.app/Contents/MacOS/CC Switch");
|
||||
let result = get_macos_app_bundle_path(exe_path);
|
||||
assert_eq!(
|
||||
result,
|
||||
@@ -92,7 +91,9 @@ mod tests {
|
||||
let result = get_macos_app_bundle_path(exe_path);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(std::path::PathBuf::from("/Users/test/My Apps/CC Switch.app"))
|
||||
Some(std::path::PathBuf::from(
|
||||
"/Users/test/My Apps/CC Switch.app"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,22 +12,22 @@ const UNIVERSAL_PROVIDERS_KEY: &str = "universal_providers";
|
||||
|
||||
impl Database {
|
||||
/// 获取所有统一供应商
|
||||
pub fn get_all_universal_providers(&self) -> Result<HashMap<String, UniversalProvider>, AppError> {
|
||||
pub fn get_all_universal_providers(
|
||||
&self,
|
||||
) -> Result<HashMap<String, UniversalProvider>, AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT value FROM settings WHERE key = ?")
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
|
||||
let result: Option<String> = stmt
|
||||
.query_row([UNIVERSAL_PROVIDERS_KEY], |row| row.get(0))
|
||||
.ok();
|
||||
|
||||
|
||||
match result {
|
||||
Some(json) => {
|
||||
serde_json::from_str(&json)
|
||||
.map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}")))
|
||||
}
|
||||
Some(json) => serde_json::from_str(&json)
|
||||
.map_err(|e| AppError::Database(format!("解析统一供应商数据失败: {e}"))),
|
||||
None => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
@@ -62,14 +62,13 @@ impl Database {
|
||||
) -> Result<(), AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
let json = to_json_string(providers)?;
|
||||
|
||||
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
||||
[UNIVERSAL_PROVIDERS_KEY, &json],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,6 +233,24 @@ impl Database {
|
||||
[],
|
||||
);
|
||||
|
||||
// 尝试添加基础配置列到 proxy_config 表(兼容 v3.9.0-2 升级)
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE proxy_config ADD COLUMN proxy_enabled INTEGER NOT NULL DEFAULT 0",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE proxy_config ADD COLUMN listen_address TEXT NOT NULL DEFAULT '127.0.0.1'",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE proxy_config ADD COLUMN listen_port INTEGER NOT NULL DEFAULT 5000",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE proxy_config ADD COLUMN enable_logging INTEGER NOT NULL DEFAULT 1",
|
||||
[],
|
||||
);
|
||||
|
||||
// 尝试添加超时配置列到 proxy_config 表
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE proxy_config ADD COLUMN streaming_first_byte_timeout INTEGER NOT NULL DEFAULT 30",
|
||||
@@ -247,6 +265,14 @@ impl Database {
|
||||
[],
|
||||
);
|
||||
|
||||
// 兼容:若旧版 proxy_config 仍为单例结构(无 app_type),则在启动时直接转换为三行结构
|
||||
// 说明:user_version=2 时不会再触发 v1->v2 迁移,但新代码查询依赖 app_type 列。
|
||||
if Self::table_exists(conn, "proxy_config")?
|
||||
&& !Self::has_column(conn, "proxy_config", "app_type")?
|
||||
{
|
||||
Self::migrate_proxy_config_to_per_app(conn)?;
|
||||
}
|
||||
|
||||
// 确保 in_failover_queue 列存在(对于已存在的 v2 数据库)
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
@@ -411,6 +437,32 @@ impl Database {
|
||||
|
||||
// 添加代理超时配置字段
|
||||
if Self::table_exists(conn, "proxy_config")? {
|
||||
// 兼容旧版本缺失的基础字段
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"proxy_config",
|
||||
"proxy_enabled",
|
||||
"INTEGER NOT NULL DEFAULT 0",
|
||||
)?;
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"proxy_config",
|
||||
"listen_address",
|
||||
"TEXT NOT NULL DEFAULT '127.0.0.1'",
|
||||
)?;
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"proxy_config",
|
||||
"listen_port",
|
||||
"INTEGER NOT NULL DEFAULT 5000",
|
||||
)?;
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"proxy_config",
|
||||
"enable_logging",
|
||||
"INTEGER NOT NULL DEFAULT 1",
|
||||
)?;
|
||||
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"proxy_config",
|
||||
|
||||
@@ -201,6 +201,53 @@ fn migration_aligns_column_defaults_and_types() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_tables_repairs_legacy_proxy_config_singleton_to_per_app() {
|
||||
let conn = Connection::open_in_memory().expect("open memory db");
|
||||
|
||||
// 模拟测试版 v2:user_version=2,但 proxy_config 仍是单例结构(无 app_type)
|
||||
Database::set_user_version(&conn, 2).expect("set user_version");
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
CREATE TABLE proxy_config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
listen_port INTEGER NOT NULL DEFAULT 5000,
|
||||
max_retries INTEGER NOT NULL DEFAULT 3,
|
||||
request_timeout INTEGER NOT NULL DEFAULT 300,
|
||||
enable_logging INTEGER NOT NULL DEFAULT 1,
|
||||
target_app TEXT NOT NULL DEFAULT 'claude',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
INSERT INTO proxy_config (id, enabled) VALUES (1, 1);
|
||||
"#,
|
||||
)
|
||||
.expect("seed legacy proxy_config");
|
||||
|
||||
Database::create_tables_on_conn(&conn).expect("create tables should repair proxy_config");
|
||||
|
||||
assert!(
|
||||
Database::has_column(&conn, "proxy_config", "app_type").expect("check app_type"),
|
||||
"proxy_config should be migrated to per-app structure"
|
||||
);
|
||||
|
||||
let count: i32 = conn
|
||||
.query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0))
|
||||
.expect("count rows");
|
||||
assert_eq!(count, 3, "per-app proxy_config should have 3 rows");
|
||||
|
||||
// 新结构下应能按 app_type 查询
|
||||
let _: i32 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM proxy_config WHERE app_type = 'claude'",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.expect("query by app_type");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_does_not_write_to_disk() {
|
||||
// Create minimal valid config for migration
|
||||
|
||||
Reference in New Issue
Block a user