webext_storage/
schema.rs
1use crate::db::sql_fns;
6use crate::error::{debug, Result};
7use rusqlite::{Connection, Transaction};
8use sql_support::open_database::{
9 ConnectionInitializer as MigrationLogic, Error as MigrationError, Result as MigrationResult,
10};
11
12const CREATE_SCHEMA_SQL: &str = include_str!("../sql/create_schema.sql");
13const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../sql/create_sync_temp_tables.sql");
14
15pub struct WebExtMigrationLogin;
16
17impl MigrationLogic for WebExtMigrationLogin {
18 const NAME: &'static str = "webext storage db";
19 const END_VERSION: u32 = 2;
20
21 fn prepare(&self, conn: &Connection, _db_empty: bool) -> MigrationResult<()> {
22 let initial_pragmas = "
23 -- We don't care about temp tables being persisted to disk.
24 PRAGMA temp_store = 2;
25 -- we unconditionally want write-ahead-logging mode
26 PRAGMA journal_mode=WAL;
27 -- foreign keys seem worth enforcing!
28 PRAGMA foreign_keys = ON;
29 ";
30 conn.execute_batch(initial_pragmas)?;
31 define_functions(conn)?;
32 conn.set_prepared_statement_cache_capacity(128);
33 Ok(())
34 }
35
36 fn init(&self, db: &Transaction<'_>) -> MigrationResult<()> {
37 debug!("Creating schema");
38 db.execute_batch(CREATE_SCHEMA_SQL)?;
39 Ok(())
40 }
41
42 fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> MigrationResult<()> {
43 match version {
44 1 => upgrade_from_1(db),
45 _ => Err(MigrationError::IncompatibleVersion(version)),
46 }
47 }
48}
49
50fn define_functions(c: &Connection) -> MigrationResult<()> {
51 use rusqlite::functions::FunctionFlags;
52 c.create_scalar_function(
53 "generate_guid",
54 0,
55 FunctionFlags::SQLITE_UTF8,
56 sql_fns::generate_guid,
57 )?;
58 Ok(())
59}
60
61fn upgrade_from_1(db: &Connection) -> MigrationResult<()> {
62 db.execute_batch("ALTER TABLE storage_sync_mirror RENAME TO old_mirror;")?;
64 db.execute_batch(CREATE_SCHEMA_SQL)?;
66 db.execute_batch(
67 "INSERT OR IGNORE INTO storage_sync_mirror(guid, ext_id, data)
68 SELECT guid, ext_id, data FROM old_mirror;",
69 )?;
70 db.execute_batch("DROP TABLE old_mirror;")?;
71 db.execute_batch("PRAGMA user_version = 2;")?;
72 Ok(())
73}
74
75pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()> {
79 debug!("Initializing sync temp tables");
80 db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
81 Ok(())
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::db::test::new_mem_db;
88 use rusqlite::Error;
89 use sql_support::open_database::test_utils::MigratedDatabaseFile;
90 use sql_support::ConnExt;
91
92 const CREATE_SCHEMA_V1_SQL: &str = include_str!("../sql/tests/create_schema_v1.sql");
93
94 #[test]
95 fn test_create_schema_twice() {
96 let db = new_mem_db();
97 let conn = db.get_connection().expect("should retrieve connection");
98 conn.execute_batch(CREATE_SCHEMA_SQL)
99 .expect("should allow running twice");
100 }
101
102 #[test]
103 fn test_create_empty_sync_temp_tables_twice() {
104 let db = new_mem_db();
105 let conn = db.get_connection().expect("should retrieve connection");
106 create_empty_sync_temp_tables(conn).expect("should work first time");
107 conn.execute_batch(
109 "INSERT INTO temp.storage_sync_staging
110 (guid, ext_id) VALUES
111 ('guid', 'ext_id');",
112 )
113 .expect("should work once");
114 let count = conn
115 .query_row_and_then(
116 "SELECT COUNT(*) FROM temp.storage_sync_staging;",
117 [],
118 |row| row.get::<_, u32>(0),
119 )
120 .expect("query should work");
121 assert_eq!(count, 1, "should be one row");
122
123 create_empty_sync_temp_tables(conn).expect("should second first time");
125 let count = conn
127 .query_row_and_then(
128 "SELECT COUNT(*) FROM temp.storage_sync_staging;",
129 [],
130 |row| row.get::<_, u32>(0),
131 )
132 .expect("query should work");
133 assert_eq!(count, 0, "should be no rows");
134 }
135
136 #[test]
137 fn test_all_upgrades() -> Result<()> {
138 let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
139 db_file.run_all_upgrades();
140 let db = db_file.open();
141
142 let get_id_data = |guid: &str| -> Result<(Option<String>, Option<String>)> {
143 let (ext_id, data) = db
144 .try_query_row::<_, Error, _, _>(
145 "SELECT ext_id, data FROM storage_sync_mirror WHERE guid = :guid",
146 &[(":guid", &guid.to_string())],
147 |row| Ok((row.get(0)?, row.get(1)?)),
148 true,
149 )?
150 .expect("row should exist.");
151 Ok((ext_id, data))
152 };
153 assert_eq!(
154 get_id_data("guid-1")?,
155 (Some("ext-id-1".to_string()), Some("data-1".to_string()))
156 );
157 assert_eq!(
158 get_id_data("guid-2")?,
159 (Some("ext-id-2".to_string()), Some("data-2".to_string()))
160 );
161 Ok(())
162 }
163
164 #[test]
165 fn test_upgrade_2() -> Result<()> {
166 error_support::init_for_tests();
167
168 let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
169 db_file.upgrade_to(2);
170 let db = db_file.open();
171
172 db.execute_batch(
174 "INSERT INTO storage_sync_mirror(guid, ext_id, data)
175 VALUES ('guid-3', NULL, NULL);",
176 )?;
177 Ok(())
178 }
179}