diff --git a/components/webext-storage/src/api.rs b/components/webext-storage/src/api.rs index a4c1cee248..4f17347548 100644 --- a/components/webext-storage/src/api.rs +++ b/components/webext-storage/src/api.rs @@ -438,8 +438,9 @@ mod tests { #[test] fn test_simple() -> Result<()> { let ext_id = "x"; - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; // an empty store. for q in vec![JsonValue::Null, json!("foo"), json!(["foo"])].into_iter() { @@ -529,8 +530,9 @@ mod tests { fn test_check_get_impl() -> Result<()> { // This is a port of checkGetImpl in test_ext_storage.js in Desktop. let ext_id = "x"; - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let prop = "test-prop"; let value = "test-value"; @@ -584,8 +586,9 @@ mod tests { fn test_bug_1621162() -> Result<()> { // apparently Firefox, unlike Chrome, will not optimize the changes. // See bug 1621162 for more! - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let ext_id = "xyz"; set(&tx, ext_id, json!({"foo": "bar" }))?; @@ -599,8 +602,9 @@ mod tests { #[test] fn test_quota_maxitems() -> Result<()> { - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let ext_id = "xyz"; for i in 1..SYNC_MAX_ITEMS + 1 { set( @@ -619,8 +623,9 @@ mod tests { #[test] fn test_quota_bytesperitem() -> Result<()> { - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let ext_id = "xyz"; // A string 5 bytes less than the max. This should be counted as being // 3 bytes less than the max as the quotes are counted. Plus the length @@ -645,8 +650,9 @@ mod tests { #[test] fn test_quota_bytes() -> Result<()> { - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let ext_id = "xyz"; let val = "x".repeat(SYNC_QUOTA_BYTES + 1); @@ -682,8 +688,9 @@ mod tests { #[test] fn test_get_bytes_in_use() -> Result<()> { - let mut db = new_mem_db(); - let tx = db.transaction()?; + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let ext_id = "xyz"; assert_eq!(get_bytes_in_use(&tx, ext_id, json!(null))?, 0); @@ -714,8 +721,9 @@ mod tests { #[test] fn test_usage() { - let mut db = new_mem_db(); - let tx = db.transaction().unwrap(); + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction().unwrap(); // '{"a":"a","b":"bb","c":"ccc","n":999999}': 39 bytes set(&tx, "xyz", json!({ "a": "a" })).unwrap(); set(&tx, "xyz", json!({ "b": "bb" })).unwrap(); @@ -727,7 +735,7 @@ mod tests { tx.commit().unwrap(); - let usage = usage(&db).unwrap(); + let usage = usage(conn).unwrap(); let expect = [ UsageInfo { ext_id: "abc".to_string(), diff --git a/components/webext-storage/src/db.rs b/components/webext-storage/src/db.rs index e24c8ea97c..c8cb38fac0 100644 --- a/components/webext-storage/src/db.rs +++ b/components/webext-storage/src/db.rs @@ -11,7 +11,7 @@ use rusqlite::Connection; use rusqlite::OpenFlags; use sql_support::open_database::open_database_with_flags; use sql_support::ConnExt; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; use url::Url; @@ -23,10 +23,16 @@ use url::Url; /// /// We only support a single writer connection - so that's the only thing we /// store. It's still a bit overkill, but there's only so many yaks in a day. +pub enum WebExtStorageDb { + Open(Connection), + Closed, +} + pub struct StorageDb { - writer: Connection, + pub writer: WebExtStorageDb, interrupt_handle: Arc, } + impl StorageDb { /// Create a new, or fetch an already open, StorageDb backed by a file on disk. pub fn new(db_path: impl AsRef) -> Result { @@ -54,7 +60,7 @@ impl StorageDb { let conn = open_database_with_flags(db_path, flags, &schema::WebExtMigrationLogin)?; Ok(Self { interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)), - writer: conn, + writer: WebExtStorageDb::Open(conn), }) } @@ -73,29 +79,20 @@ impl StorageDb { /// underlying connection so the caller can retry but (a) that's very tricky /// in an Arc> world and (b) we never actually took advantage of /// that retry capability. - pub fn close(self) -> Result<()> { - self.writer.close().map_err(|(writer, err)| { - // In rusqlite 0.28.0 and earlier, if we just let `writer` drop, - // the close would panic on failure. - // Later rusqlite versions will not panic, but this behavior doesn't - // hurt there. - std::mem::forget(writer); - err.into() - }) - } -} - -impl Deref for StorageDb { - type Target = Connection; - - fn deref(&self) -> &Self::Target { - &self.writer + pub fn close(&mut self) -> Result<()> { + let conn = match std::mem::replace(&mut self.writer, WebExtStorageDb::Closed) { + WebExtStorageDb::Open(conn) => conn, + WebExtStorageDb::Closed => return Ok(()), + }; + conn.close().map_err(|(_, y)| Error::SqlError(y)) } -} -impl DerefMut for StorageDb { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.writer + pub(crate) fn get_connection(&self) -> Result<&Connection> { + let db = &self.writer; + match db { + WebExtStorageDb::Open(y) => Ok(y), + WebExtStorageDb::Closed => Err(Error::DatabaseConnectionClosed), + } } } @@ -290,12 +287,13 @@ mod tests { #[test] fn test_meta() -> Result<()> { - let writer = new_mem_db(); - assert_eq!(get_meta::(&writer, "foo")?, None); - put_meta(&writer, "foo", &"bar".to_string())?; - assert_eq!(get_meta(&writer, "foo")?, Some("bar".to_string())); - delete_meta(&writer, "foo")?; - assert_eq!(get_meta::(&writer, "foo")?, None); + let db = new_mem_db(); + let conn = &db.get_connection()?; + assert_eq!(get_meta::(conn, "foo")?, None); + put_meta(conn, "foo", &"bar".to_string())?; + assert_eq!(get_meta(conn, "foo")?, Some("bar".to_string())); + delete_meta(conn, "foo")?; + assert_eq!(get_meta::(conn, "foo")?, None); Ok(()) } } diff --git a/components/webext-storage/src/migration.rs b/components/webext-storage/src/migration.rs index 27f230ef47..fa2d0cc862 100644 --- a/components/webext-storage/src/migration.rs +++ b/components/webext-storage/src/migration.rs @@ -346,8 +346,9 @@ mod tests { init_source_db(path, f); // now migrate - let mut db = new_mem_db(); - let tx = db.transaction().expect("tx should work"); + let db = new_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction().expect("tx should work"); let mi = migrate(&tx, &tmpdir.path().join("source.db")).expect("migrate should work"); tx.commit().expect("should work"); @@ -384,17 +385,18 @@ mod tests { #[test] fn test_happy_paths() { // some real data. - let conn = do_migrate(HAPPY_PATH_MIGRATION_INFO, |c| { + let db = do_migrate(HAPPY_PATH_MIGRATION_INFO, |c| { c.execute_batch(HAPPY_PATH_SQL).expect("should populate") }); + let conn = db.get_connection().expect("should retrieve connection"); assert_has( - &conn, + conn, "{e7fefcf3-b39c-4f17-5215-ebfe120a7031}", json!({"userWelcomed": 1570659224457u64, "isWho": "4ec8109f"}), ); assert_has( - &conn, + conn, "https-everywhere@eff.org", json!({"userRules": [], "ruleActiveStates": {}, "migration_version": 2}), ); diff --git a/components/webext-storage/src/schema.rs b/components/webext-storage/src/schema.rs index e91706f728..94467050d0 100644 --- a/components/webext-storage/src/schema.rs +++ b/components/webext-storage/src/schema.rs @@ -94,22 +94,24 @@ mod tests { #[test] fn test_create_schema_twice() { let db = new_mem_db(); - db.execute_batch(CREATE_SCHEMA_SQL) + let conn = db.get_connection().expect("should retrieve connection"); + conn.execute_batch(CREATE_SCHEMA_SQL) .expect("should allow running twice"); } #[test] fn test_create_empty_sync_temp_tables_twice() { let db = new_mem_db(); - create_empty_sync_temp_tables(&db).expect("should work first time"); + let conn = db.get_connection().expect("should retrieve connection"); + create_empty_sync_temp_tables(conn).expect("should work first time"); // insert something into our new temp table and check it's there. - db.execute_batch( + conn.execute_batch( "INSERT INTO temp.storage_sync_staging (guid, ext_id) VALUES ('guid', 'ext_id');", ) .expect("should work once"); - let count = db + let count = conn .query_row_and_then( "SELECT COUNT(*) FROM temp.storage_sync_staging;", [], @@ -119,9 +121,9 @@ mod tests { assert_eq!(count, 1, "should be one row"); // re-execute - create_empty_sync_temp_tables(&db).expect("should second first time"); + create_empty_sync_temp_tables(conn).expect("should second first time"); // and it should have deleted existing data. - let count = db + let count = conn .query_row_and_then( "SELECT COUNT(*) FROM temp.storage_sync_staging;", [], diff --git a/components/webext-storage/src/store.rs b/components/webext-storage/src/store.rs index d9c3281dd4..6fa4e384c0 100644 --- a/components/webext-storage/src/store.rs +++ b/components/webext-storage/src/store.rs @@ -59,8 +59,9 @@ impl WebExtStorageStore { /// Sets one or more JSON key-value pairs for an extension ID. Returns a /// list of changes, with existing and new values for each key in `val`. pub fn set(&self, ext_id: &str, val: JsonValue) -> Result { - let db = self.db.lock(); - let tx = db.unchecked_transaction()?; + let db = &self.db.lock(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let result = api::set(&tx, ext_id, val)?; tx.commit()?; Ok(result) @@ -68,8 +69,9 @@ impl WebExtStorageStore { /// Returns information about per-extension usage pub fn usage(&self) -> Result> { - let db = self.db.lock(); - api::usage(&db) + let db = &self.db.lock(); + let conn = db.get_connection()?; + api::usage(conn) } /// Returns the values for one or more keys `keys` can be: @@ -90,8 +92,9 @@ impl WebExtStorageStore { /// `serde_json::Value::Object`). pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result { // Don't care about transactions here. - let db = self.db.lock(); - api::get(&db, ext_id, keys) + let db = &self.db.lock(); + let conn = db.get_connection()?; + api::get(conn, ext_id, keys) } /// Deletes the values for one or more keys. As with `get`, `keys` can be @@ -99,8 +102,9 @@ impl WebExtStorageStore { /// of changes, where each change contains the old value for each deleted /// key. pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result { - let db = self.db.lock(); - let tx = db.unchecked_transaction()?; + let db = &self.db.lock(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let result = api::remove(&tx, ext_id, keys)?; tx.commit()?; Ok(result) @@ -110,8 +114,9 @@ impl WebExtStorageStore { /// a list of changes, where each change contains the old value for each /// deleted key. pub fn clear(&self, ext_id: &str) -> Result { - let db = self.db.lock(); - let tx = db.unchecked_transaction()?; + let db = &self.db.lock(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let result = api::clear(&tx, ext_id)?; tx.commit()?; Ok(result) @@ -120,8 +125,9 @@ impl WebExtStorageStore { /// Returns the bytes in use for the specified items (which can be null, /// a string, or an array) pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result { - let db = self.db.lock(); - api::get_bytes_in_use(&db, ext_id, keys) + let db = &self.db.lock(); + let conn = db.get_connection()?; + api::get_bytes_in_use(conn, ext_id, keys) } /// Returns a bridged sync engine for Desktop for this store. @@ -131,11 +137,11 @@ impl WebExtStorageStore { /// Closes the store and its database connection. See the docs for /// `StorageDb::close` for more details on when this can fail. - pub fn close(self) -> Result<()> { + pub fn close(&self) -> Result<()> { // Even though this consumes `self`, the fact we use an Arc<> means // we can't guarantee we can actually consume the inner DB - so do // the best we can. - let shared: ThreadSafeStorageDb = match Arc::try_unwrap(self.db) { + let shared: ThreadSafeStorageDb = match Arc::try_unwrap(self.db.clone()) { Ok(shared) => shared, _ => { // The only way this is possible is if the sync engine has an operation @@ -157,7 +163,7 @@ impl WebExtStorageStore { } }; // consume the mutex and get back the inner. - let db = shared.into_inner(); + let mut db = shared.into_inner(); db.close() } @@ -177,12 +183,13 @@ impl WebExtStorageStore { /// /// Note that `filename` isn't normalized or canonicalized. pub fn migrate(&self, filename: impl AsRef) -> Result<()> { - let db = self.db.lock(); - let tx = db.unchecked_transaction()?; + let db = &self.db.lock(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let result = migrate(&tx, filename.as_ref())?; tx.commit()?; // Failing to store this information should not cause migration failure. - if let Err(e) = result.store(&db) { + if let Err(e) = result.store(conn) { debug_assert!(false, "Migration error: {:?}", e); log::warn!("Failed to record migration telmetry: {}", e); } @@ -192,8 +199,9 @@ impl WebExtStorageStore { /// Read-and-delete (e.g. `take` in rust parlance, see Option::take) /// operation for any MigrationInfo stored in this database. pub fn take_migration_info(&self) -> Result> { - let db = self.db.lock(); - let tx = db.unchecked_transaction()?; + let db = &self.db.lock(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let result = MigrationInfo::take(&tx)?; tx.commit()?; Ok(result) diff --git a/components/webext-storage/src/sync/bridge.rs b/components/webext-storage/src/sync/bridge.rs index f948003f7a..763dbfea64 100644 --- a/components/webext-storage/src/sync/bridge.rs +++ b/components/webext-storage/src/sync/bridge.rs @@ -58,26 +58,30 @@ impl sync15::engine::BridgedEngine for BridgedEngine { fn last_sync(&self) -> Result { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - Ok(get_meta(&db, LAST_SYNC_META_KEY)?.unwrap_or(0)) + let conn = db.get_connection()?; + Ok(get_meta(conn, LAST_SYNC_META_KEY)?.unwrap_or(0)) } fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - put_meta(&db, LAST_SYNC_META_KEY, &last_sync_millis)?; + let conn = db.get_connection()?; + put_meta(conn, LAST_SYNC_META_KEY, &last_sync_millis)?; Ok(()) } fn sync_id(&self) -> Result> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - Ok(get_meta(&db, SYNC_ID_META_KEY)?) + let conn = db.get_connection()?; + Ok(get_meta(conn, SYNC_ID_META_KEY)?) } fn reset_sync_id(&self) -> Result { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let new_id = SyncGuid::random().to_string(); self.do_reset(&tx)?; put_meta(&tx, SYNC_ID_META_KEY, &new_id)?; @@ -88,11 +92,13 @@ impl sync15::engine::BridgedEngine for BridgedEngine { fn ensure_current_sync_id(&self, sync_id: &str) -> Result { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - let current: Option = get_meta(&db, SYNC_ID_META_KEY)?; + let conn = db.get_connection()?; + let current: Option = get_meta(conn, SYNC_ID_META_KEY)?; Ok(match current { Some(current) if current == sync_id => current, _ => { - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; self.do_reset(&tx)?; let result = sync_id.to_string(); put_meta(&tx, SYNC_ID_META_KEY, &result)?; @@ -105,7 +111,8 @@ impl sync15::engine::BridgedEngine for BridgedEngine { fn sync_started(&self) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - schema::create_empty_sync_temp_tables(&db)?; + let conn = db.get_connection()?; + schema::create_empty_sync_temp_tables(conn)?; Ok(()) } @@ -113,7 +120,8 @@ impl sync15::engine::BridgedEngine for BridgedEngine { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); let signal = db.begin_interrupt_scope()?; - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let incoming_content: Vec<_> = incoming_bsos .into_iter() .map(IncomingBso::into_content::) @@ -127,8 +135,8 @@ impl sync15::engine::BridgedEngine for BridgedEngine { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); let signal = db.begin_interrupt_scope()?; - - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let incoming = get_incoming(&tx)?; let actions = incoming .into_iter() @@ -138,14 +146,15 @@ impl sync15::engine::BridgedEngine for BridgedEngine { stage_outgoing(&tx)?; tx.commit()?; - Ok(get_outgoing(&db, &signal)?.into()) + Ok(get_outgoing(conn, &signal)?.into()) } fn set_uploaded(&self, _server_modified_millis: i64, ids: &[SyncGuid]) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); + let conn = db.get_connection()?; let signal = db.begin_interrupt_scope()?; - let tx = db.unchecked_transaction()?; + let tx = conn.unchecked_transaction()?; record_uploaded(&tx, ids, &signal)?; tx.commit()?; @@ -155,14 +164,16 @@ impl sync15::engine::BridgedEngine for BridgedEngine { fn sync_finished(&self) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - schema::create_empty_sync_temp_tables(&db)?; + let conn = db.get_connection()?; + schema::create_empty_sync_temp_tables(conn)?; Ok(()) } fn reset(&self) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; self.do_reset(&tx)?; delete_meta(&tx, SYNC_ID_META_KEY)?; tx.commit()?; @@ -172,7 +183,8 @@ impl sync15::engine::BridgedEngine for BridgedEngine { fn wipe(&self) -> Result<()> { let shared_db = self.thread_safe_storage_db()?; let db = shared_db.lock(); - let tx = db.unchecked_transaction()?; + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; // We assume the meta table is only used by sync. tx.execute_batch( "DELETE FROM storage_sync_data; DELETE FROM storage_sync_mirror; DELETE FROM meta;", @@ -195,7 +207,8 @@ mod tests { use crate::db::StorageDb; use sync15::engine::BridgedEngine; - fn query_count(conn: &StorageDb, table: &str) -> u32 { + fn query_count(db: &StorageDb, table: &str) -> u32 { + let conn = db.get_connection().expect("should retrieve connection"); conn.query_row_and_then(&format!("SELECT COUNT(*) FROM {};", table), [], |row| { row.get::<_, u32>(0) }) @@ -207,12 +220,13 @@ mod tests { { let shared = engine.thread_safe_storage_db()?; let db = shared.lock(); - db.execute( + let conn = db.get_connection().expect("should retrieve connection"); + conn.execute( "INSERT INTO storage_sync_data (ext_id, data, sync_change_counter) VALUES ('ext-a', 'invalid-json', 2)", [], )?; - db.execute( + conn.execute( "INSERT INTO storage_sync_mirror (guid, ext_id, data) VALUES ('guid', 'ext-a', '3')", [], @@ -234,10 +248,11 @@ mod tests { // A reset never wipes data... let shared = engine.thread_safe_storage_db()?; let db = shared.lock(); + let conn = db.get_connection().expect("should retrieve connection"); assert_eq!(query_count(&db, "storage_sync_data"), 1); // But did reset the change counter. - let cc = db.query_row_and_then( + let cc = conn.query_row_and_then( "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';", [], |row| row.get::<_, u32>(0), @@ -246,7 +261,7 @@ mod tests { // But did wipe the mirror... assert_eq!(query_count(&db, "storage_sync_mirror"), 0); // And the last_sync should have been wiped. - assert!(get_meta::(&db, LAST_SYNC_META_KEY)?.is_none()); + assert!(get_meta::(conn, LAST_SYNC_META_KEY)?.is_none()); Ok(()) } @@ -254,8 +269,9 @@ mod tests { fn assert_not_reset(engine: &super::BridgedEngine) -> Result<()> { let shared = engine.thread_safe_storage_db()?; let db = shared.lock(); + let conn = db.get_connection().expect("should retrieve connection"); assert_eq!(query_count(&db, "storage_sync_data"), 1); - let cc = db.query_row_and_then( + let cc = conn.query_row_and_then( "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';", [], |row| row.get::<_, u32>(0), @@ -263,7 +279,7 @@ mod tests { assert_eq!(cc, 2); assert_eq!(query_count(&db, "storage_sync_mirror"), 1); // And the last_sync should remain. - assert!(get_meta::(&db, LAST_SYNC_META_KEY)?.is_some()); + assert!(get_meta::(conn, LAST_SYNC_META_KEY)?.is_some()); Ok(()) } @@ -287,23 +303,25 @@ mod tests { #[test] fn test_reset() -> Result<()> { - let strong = new_mem_thread_safe_storage_db(); - let engine = super::BridgedEngine::new(&strong); + let strong = &new_mem_thread_safe_storage_db(); + let engine = super::BridgedEngine::new(strong); setup_mock_data(&engine)?; - put_meta( - &engine.thread_safe_storage_db()?.lock(), - SYNC_ID_META_KEY, - &"sync-id".to_string(), - )?; + { + let db = strong.lock(); + let conn = db.get_connection()?; + put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?; + } engine.reset()?; assert_reset(&engine)?; - // Only an explicit reset kills the sync-id, so check that here. - assert_eq!( - get_meta::(&engine.thread_safe_storage_db()?.lock(), SYNC_ID_META_KEY)?, - None - ); + + { + let db = strong.lock(); + let conn = db.get_connection()?; + // Only an explicit reset kills the sync-id, so check that here. + assert_eq!(get_meta::(conn, SYNC_ID_META_KEY)?, None); + } Ok(()) } @@ -330,11 +348,13 @@ mod tests { setup_mock_data(&engine)?; - put_meta( - &engine.thread_safe_storage_db()?.lock(), - SYNC_ID_META_KEY, - &"old-id".to_string(), - )?; + { + let storage_db = &engine.thread_safe_storage_db()?; + let db = storage_db.lock(); + let conn = db.get_connection()?; + put_meta(conn, SYNC_ID_META_KEY, &"old-id".to_string())?; + } + assert_not_reset(&engine)?; assert_eq!(engine.sync_id()?, Some("old-id".to_string())); @@ -354,11 +374,12 @@ mod tests { setup_mock_data(&engine)?; assert_not_reset(&engine)?; - put_meta( - &engine.thread_safe_storage_db()?.lock(), - SYNC_ID_META_KEY, - &"sync-id".to_string(), - )?; + { + let storage_db = &engine.thread_safe_storage_db()?; + let db = storage_db.lock(); + let conn = db.get_connection()?; + put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?; + } engine.ensure_current_sync_id("sync-id")?; // should not have reset. @@ -372,11 +393,13 @@ mod tests { let engine = super::BridgedEngine::new(&strong); setup_mock_data(&engine)?; - put_meta( - &engine.thread_safe_storage_db()?.lock(), - SYNC_ID_META_KEY, - &"sync-id".to_string(), - )?; + + { + let storage_db = &engine.thread_safe_storage_db()?; + let db = storage_db.lock(); + let conn = db.get_connection()?; + put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?; + } assert_eq!(engine.sync_id()?, Some("sync-id".to_string())); let new_id = engine.reset_sync_id()?; diff --git a/components/webext-storage/src/sync/incoming.rs b/components/webext-storage/src/sync/incoming.rs index 5d00fe8de3..6f2cea9718 100644 --- a/components/webext-storage/src/sync/incoming.rs +++ b/components/webext-storage/src/sync/incoming.rs @@ -548,8 +548,9 @@ mod tests { #[test] fn test_incoming_populates_staging() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; let incoming = json! {[ { @@ -570,8 +571,9 @@ mod tests { #[test] fn test_fetch_incoming_state() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; // Start with an item just in staging. tx.execute( @@ -631,8 +633,9 @@ mod tests { // Like test_fetch_incoming_state, but check NULLs are handled correctly. #[test] fn test_fetch_incoming_state_nulls() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; // Start with a tombstone just in staging. tx.execute( @@ -735,10 +738,13 @@ mod tests { #[test] fn test_apply_actions() -> Result<()> { - let mut db = new_syncable_mem_db(); + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("connection should be retrieved"); // DeleteLocally - row should be entirely removed. - let tx = db.transaction().expect("transaction should work"); + let tx = conn + .unchecked_transaction() + .expect("transaction should begin"); api::set(&tx, "ext_id", json!({"foo": "local"}))?; assert_eq!( api::get(&tx, "ext_id", json!(null))?, @@ -759,7 +765,9 @@ mod tests { tx.rollback()?; // TakeRemote - replace local data with remote and marked as not dirty. - let tx = db.transaction().expect("transaction should work"); + let tx = conn + .unchecked_transaction() + .expect("transaction should begin"); api::set(&tx, "ext_id", json!({"foo": "local"}))?; assert_eq!( api::get(&tx, "ext_id", json!(null))?, @@ -794,7 +802,9 @@ mod tests { tx.rollback()?; // Merge - like ::TakeRemote, but data remains dirty. - let tx = db.transaction().expect("transaction should work"); + let tx = conn + .unchecked_transaction() + .expect("transaction should begin"); api::set(&tx, "ext_id", json!({"foo": "local"}))?; assert_eq!( api::get(&tx, "ext_id", json!(null))?, @@ -828,7 +838,9 @@ mod tests { tx.rollback()?; // Same - data stays the same but is marked not dirty. - let tx = db.transaction().expect("transaction should work"); + let tx = conn + .unchecked_transaction() + .expect("transaction should begin"); api::set(&tx, "ext_id", json!({"foo": "local"}))?; assert_eq!( api::get(&tx, "ext_id", json!(null))?, diff --git a/components/webext-storage/src/sync/mod.rs b/components/webext-storage/src/sync/mod.rs index 22ff912def..7d6df456e5 100644 --- a/components/webext-storage/src/sync/mod.rs +++ b/components/webext-storage/src/sync/mod.rs @@ -163,7 +163,8 @@ pub struct SyncedExtensionChange { pub fn get_synced_changes(db: &StorageDb) -> Result> { let signal = db.begin_interrupt_scope()?; let sql = "SELECT ext_id, changes FROM temp.storage_sync_applied"; - db.conn().query_rows_and_then(sql, [], |row| -> Result<_> { + let conn = db.get_connection()?; + conn.query_rows_and_then(sql, [], |row| -> Result<_> { signal.err_if_interrupted()?; Ok(SyncedExtensionChange { ext_id: row.get("ext_id")?, @@ -181,7 +182,8 @@ pub mod test { pub fn new_syncable_mem_db() -> StorageDb { let _ = env_logger::try_init(); let db = new_mem_db(); - create_empty_sync_temp_tables(&db).expect("should work"); + let conn = db.get_connection().expect("should retrieve connection"); + create_empty_sync_temp_tables(conn).expect("should work"); db } } @@ -382,7 +384,8 @@ mod tests { #[test] fn test_get_synced_changes() -> Result<()> { let db = new_syncable_mem_db(); - db.execute_batch(&format!( + let conn = db.get_connection()?; + conn.execute_batch(&format!( r#"INSERT INTO temp.storage_sync_applied (ext_id, changes) VALUES ('an-extension', '{change1}'), diff --git a/components/webext-storage/src/sync/outgoing.rs b/components/webext-storage/src/sync/outgoing.rs index c6c9dd64e5..b14bb09764 100644 --- a/components/webext-storage/src/sync/outgoing.rs +++ b/components/webext-storage/src/sync/outgoing.rs @@ -124,8 +124,9 @@ mod tests { #[test] fn test_simple() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection()?; + let tx = conn.unchecked_transaction()?; tx.execute_batch( r#" diff --git a/components/webext-storage/src/sync/sync_tests.rs b/components/webext-storage/src/sync/sync_tests.rs index 6e51ae9d32..fb5ad5f911 100644 --- a/components/webext-storage/src/sync/sync_tests.rs +++ b/components/webext-storage/src/sync/sync_tests.rs @@ -140,8 +140,9 @@ fn make_incoming_tombstone(guid: &Guid) -> IncomingContent { #[test] fn test_simple_outgoing_sync() -> Result<()> { // So we are starting with an empty local store and empty server store. - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data.clone())?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -151,8 +152,9 @@ fn test_simple_outgoing_sync() -> Result<()> { #[test] fn test_simple_incoming_sync() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); let bridge_record = make_incoming(&Guid::new("guid"), "ext-id", &data); assert_eq!(do_sync(&tx, &[bridge_record])?.len(), 0); @@ -166,8 +168,9 @@ fn test_simple_incoming_sync() -> Result<()> { fn test_outgoing_tombstone() -> Result<()> { // Tombstones are only kept when the mirror has that record - so first // test that, then arrange for the mirror to have the record. - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data.clone())?; assert_eq!( @@ -197,8 +200,9 @@ fn test_outgoing_tombstone() -> Result<()> { fn test_incoming_tombstone_exists() -> Result<()> { // An incoming tombstone for a record we've previously synced (and thus // have data for) - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data.clone())?; assert_eq!( @@ -224,8 +228,9 @@ fn test_incoming_tombstone_exists() -> Result<()> { #[test] fn test_incoming_tombstone_not_exists() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; // An incoming tombstone for something that's not anywhere locally. let guid = Guid::new("guid"); let tombstone = make_incoming_tombstone(&guid); @@ -242,8 +247,9 @@ fn test_incoming_tombstone_not_exists() -> Result<()> { #[test] fn test_reconciled() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value"}); set(&tx, "ext-id", data)?; // Incoming payload with the same data @@ -258,8 +264,9 @@ fn test_reconciled() -> Result<()> { /// identical to what is in the mirrored table. #[test] fn test_reconcile_with_null_payload() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value"}); set(&tx, "ext-id", data.clone())?; // We try to push this change on the next sync. @@ -278,8 +285,9 @@ fn test_reconcile_with_null_payload() -> Result<()> { #[test] fn test_accept_incoming_when_local_is_deleted() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; // We only record an extension as deleted locally if it has been // uploaded before being deleted. let data = json!({"key1": "key1-value"}); @@ -299,8 +307,9 @@ fn test_accept_incoming_when_local_is_deleted() -> Result<()> { #[test] fn test_accept_incoming_when_local_is_deleted_no_mirror() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value"}); set(&tx, "ext-id", data)?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -319,8 +328,9 @@ fn test_accept_incoming_when_local_is_deleted_no_mirror() -> Result<()> { #[test] fn test_accept_deleted_key_mirrored() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data)?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -336,8 +346,9 @@ fn test_accept_deleted_key_mirrored() -> Result<()> { #[test] fn test_merged_no_mirror() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value"}); set(&tx, "ext-id", data)?; // Incoming payload without 'key1' and some data for 'key2'. @@ -355,8 +366,9 @@ fn test_merged_no_mirror() -> Result<()> { #[test] fn test_merged_incoming() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let old_data = json!({"key1": "key1-value", "key2": "key2-value", "doomed_key": "deletable"}); set(&tx, "ext-id", old_data)?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -385,8 +397,9 @@ fn test_merged_incoming() -> Result<()> { #[test] fn test_merged_with_null_payload() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let old_data = json!({"key1": "key1-value"}); set(&tx, "ext-id", old_data.clone())?; // Push this change remotely. @@ -409,8 +422,9 @@ fn test_merged_with_null_payload() -> Result<()> { #[test] fn test_deleted_mirrored_object_accept() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data)?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -427,8 +441,9 @@ fn test_deleted_mirrored_object_accept() -> Result<()> { #[test] fn test_deleted_mirrored_object_merged() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; set(&tx, "ext-id", json!({"key1": "key1-value"}))?; assert_eq!(do_sync(&tx, &[])?.len(), 1); let guid = get_mirror_guid(&tx, "ext-id")?; @@ -450,8 +465,9 @@ fn test_deleted_mirrored_object_merged() -> Result<()> { /// Like the above test, but with a mirrored tombstone. #[test] fn test_deleted_mirrored_tombstone_merged() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; // Sync some data so we can get the guid for this extension. set(&tx, "ext-id", json!({"key1": "key1-value"}))?; assert_eq!(do_sync(&tx, &[])?.len(), 1); @@ -473,8 +489,9 @@ fn test_deleted_mirrored_tombstone_merged() -> Result<()> { #[test] fn test_deleted_not_mirrored_object_merged() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data)?; // Incoming payload with data deleted. @@ -493,8 +510,9 @@ fn test_deleted_not_mirrored_object_merged() -> Result<()> { #[test] fn test_conflicting_incoming() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let data = json!({"key1": "key1-value", "key2": "key2-value"}); set(&tx, "ext-id", data)?; // Incoming payload without 'key1' and conflicting for 'key2'. @@ -517,8 +535,9 @@ fn test_conflicting_incoming() -> Result<()> { #[test] fn test_invalid_incoming() -> Result<()> { - let mut db = new_syncable_mem_db(); - let tx = db.transaction()?; + let db = new_syncable_mem_db(); + let conn = db.get_connection().expect("should retrieve connection"); + let tx = conn.unchecked_transaction()?; let json = json!({"id": "id", "payload": json!("").to_string()}); let bso = serde_json::from_value::(json).unwrap(); let record = bso.into_content();