diff --git a/crates/tw-store/src/db.rs b/crates/tw-store/src/db.rs index 3fc10ad..e97981b 100644 --- a/crates/tw-store/src/db.rs +++ b/crates/tw-store/src/db.rs @@ -27,7 +27,7 @@ use tw_api::Msg; /// `matching`、`revealed`),结局多了「已删除」。 /// /// 25:插件在每个请求上的运行记录(`plugin_runs`)。 -const SCHEMA: i64 = 25; +pub(crate) const SCHEMA: i64 = 25; /// 这一行算不出钱,**因为价目表里没有这个模型**:用量是有的,缺的是单价。 /// @@ -57,8 +57,8 @@ pub enum DbError { path: String, source: rusqlite::Error, }, - /// 库是别的版本建的。**不迁移、也不硬读** —— [`crate::open`] 连同正文 - /// 目录整个重建。 + /// 库是别的版本建的。**不迁移、也不硬读** —— 旧版本建的由 [`crate::open`] + /// 连同正文目录整个重建,更新的版本建的原样留着。 #[error("the database is schema version {found}; this twcore reads only {supported}")] OtherVersion { found: i64, supported: i64 }, #[error(transparent)] @@ -2348,7 +2348,7 @@ pub(crate) mod tests { #[test] fn a_database_from_another_version_is_refused_rather_than_read() { - // 不迁移,也不硬读:版本对不上就说出来,由 `crate::open` 整个重建 + // 不迁移,也不硬读:版本对不上就说出来,怎么处理由 `crate::open` 定 let d = tempfile::tempdir().unwrap(); let p = d.path().join("data.db"); for found in [SCHEMA - 1, SCHEMA + 1] { diff --git a/crates/tw-store/src/lib.rs b/crates/tw-store/src/lib.rs index 3650000..c654ce2 100644 --- a/crates/tw-store/src/lib.rs +++ b/crates/tw-store/src/lib.rs @@ -26,18 +26,22 @@ use std::path::Path; /// 打开数据目录里的请求库(`data.db`)和正文目录(`blobs/`)。 /// -/// **库是别的版本建的,就连同正文整个重建,不迁移。**项目还没有存量用户, +/// **库是旧版本建的,就连同正文整个重建,不迁移。**项目还没有存量用户, /// 为旧库写迁移只是负担。正文必须一起清:新库的请求号从 1 重新数,留着的 /// 旧正文会被当成新请求的正文显示出来。 +/// +/// **比自己新的库原样留着,返回错误**:这一次不记录历史,转发照常。那是更新的 +/// twcore 写的 —— 装回了旧版、或者同一台机器上另一份更新过的桌面端用过这个目录。 +/// 清掉它,换回新版时那份历史就没了;留着,新版下次打开原样读。 pub fn open(dir: &Path) -> Result<(Db, Blobs), DbError> { let path = dir.join("data.db"); let blobs = Blobs::new(dir.join("blobs")); match Db::open(&path) { - Err(DbError::OtherVersion { found, supported }) => { + Err(DbError::OtherVersion { found, supported }) if found < supported => { tracing::warn!( found, supported, - "the request history was written by another version; starting over with an empty one" + "the request history was written by an older version; starting over with an empty one" ); for suffix in ["", "-wal", "-shm"] { let _ = std::fs::remove_file(format!("{}{suffix}", path.display())); @@ -53,10 +57,10 @@ pub fn open(dir: &Path) -> Result<(Db, Blobs), DbError> { mod tests { use super::*; - /// 别的版本建的库连同正文一起清掉,换一个空的 —— 留着正文的话,新库里 + /// 旧版本建的库连同正文一起清掉,换一个空的 —— 留着正文的话,新库里 /// 的 1 号请求会显示旧库 1 号请求的正文。 #[test] - fn a_database_from_another_version_starts_over_with_its_bodies() { + fn a_database_from_an_older_version_starts_over_with_its_bodies() { let d = tempfile::tempdir().unwrap(); { let (db, blobs) = open(d.path()).unwrap(); @@ -76,6 +80,45 @@ mod tests { assert_eq!(open(d.path()).unwrap().0.count().unwrap(), 1); } + /// 更新的版本建的库不清:这一次打不开,库和正文都原样留给那个版本 + #[test] + fn a_database_from_a_newer_version_is_left_alone() { + let d = tempfile::tempdir().unwrap(); + { + let (db, blobs) = open(d.path()).unwrap(); + db.insert(&db::tests::row(1, 100)).unwrap(); + assert!(blobs.put(100, 1, Which::Request, b"newer")); + } + let newer = db::SCHEMA + 1; + { + let conn = rusqlite::Connection::open(d.path().join("data.db")).unwrap(); + conn.pragma_update(None, "user_version", newer).unwrap(); + } + let e = open(d.path()) + .err() + .expect("a newer database must not open"); + assert!( + matches!(e, DbError::OtherVersion { found, .. } if found == newer), + "{e:?}" + ); + let conn = rusqlite::Connection::open(d.path().join("data.db")).unwrap(); + let kept: i64 = conn + .query_row("SELECT COUNT(*) FROM requests", [], |r| r.get(0)) + .unwrap(); + assert_eq!(kept, 1, "那一行被清掉了"); + let version: i64 = conn + .pragma_query_value(None, "user_version", |r| r.get(0)) + .unwrap(); + assert_eq!(version, newer, "版本号被改了"); + assert_eq!( + Blobs::new(d.path().join("blobs")) + .get(100, 1, Which::Request) + .as_deref(), + Some(&b"newer"[..]), + "正文被清掉了" + ); + } + /// 24 版的库有两种:安全防护合成三项的那一版(安全日志多了 `matching`、`revealed` 两列) /// 和脚本插件的那一版(多了 `plugin_runs`)—— 两边各自从 23 加到了 24,合在一起是 25。 /// **哪一种都要整个重建。**照旧打开的话,少了的那张表、那两列要到第一次写的时候才出错,