summaryrefslogtreecommitdiffstats
path: root/third_party/rust/webext-storage/src/schema.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/webext-storage/src/schema.rs')
-rw-r--r--third_party/rust/webext-storage/src/schema.rs213
1 files changed, 213 insertions, 0 deletions
diff --git a/third_party/rust/webext-storage/src/schema.rs b/third_party/rust/webext-storage/src/schema.rs
new file mode 100644
index 0000000000..59efcf495a
--- /dev/null
+++ b/third_party/rust/webext-storage/src/schema.rs
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::sql_fns;
+use crate::error::Result;
+use rusqlite::{Connection, Transaction};
+use sql_support::open_database::{
+ ConnectionInitializer as MigrationLogic, Error as MigrationError, Result as MigrationResult,
+};
+
+const CREATE_SCHEMA_SQL: &str = include_str!("../sql/create_schema.sql");
+const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../sql/create_sync_temp_tables.sql");
+
+pub struct WebExtMigrationLogin;
+
+impl MigrationLogic for WebExtMigrationLogin {
+ const NAME: &'static str = "webext storage db";
+ const END_VERSION: u32 = 2;
+
+ fn prepare(&self, conn: &Connection, _db_empty: bool) -> MigrationResult<()> {
+ let initial_pragmas = "
+ -- We don't care about temp tables being persisted to disk.
+ PRAGMA temp_store = 2;
+ -- we unconditionally want write-ahead-logging mode
+ PRAGMA journal_mode=WAL;
+ -- foreign keys seem worth enforcing!
+ PRAGMA foreign_keys = ON;
+ ";
+ conn.execute_batch(initial_pragmas)?;
+ define_functions(conn)?;
+ conn.set_prepared_statement_cache_capacity(128);
+ Ok(())
+ }
+
+ fn init(&self, db: &Transaction<'_>) -> MigrationResult<()> {
+ log::debug!("Creating schema");
+ db.execute_batch(CREATE_SCHEMA_SQL)?;
+ Ok(())
+ }
+
+ fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> MigrationResult<()> {
+ match version {
+ 1 => upgrade_from_1(db),
+ _ => Err(MigrationError::IncompatibleVersion(version)),
+ }
+ }
+}
+
+fn define_functions(c: &Connection) -> MigrationResult<()> {
+ use rusqlite::functions::FunctionFlags;
+ c.create_scalar_function(
+ "generate_guid",
+ 0,
+ FunctionFlags::SQLITE_UTF8,
+ sql_fns::generate_guid,
+ )?;
+ Ok(())
+}
+
+fn upgrade_from_1(db: &Connection) -> MigrationResult<()> {
+ // We changed a not null constraint
+ db.execute_batch("ALTER TABLE storage_sync_mirror RENAME TO old_mirror;")?;
+ // just re-run the full schema commands to recreate the able.
+ db.execute_batch(CREATE_SCHEMA_SQL)?;
+ db.execute_batch(
+ "INSERT OR IGNORE INTO storage_sync_mirror(guid, ext_id, data)
+ SELECT guid, ext_id, data FROM old_mirror;",
+ )?;
+ db.execute_batch("DROP TABLE old_mirror;")?;
+ db.execute_batch("PRAGMA user_version = 2;")?;
+ Ok(())
+}
+
+// Note that we expect this to be called before and after a sync - before to
+// ensure we are syncing with a clean state, after to be good memory citizens
+// given the temp tables are in memory.
+pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()> {
+ log::debug!("Initializing sync temp tables");
+ db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
+ Ok(())
+}
+
+#[cfg(test)]
+pub mod test {
+ use prettytable::{Cell, Row};
+ use rusqlite::Result as RusqliteResult;
+ use rusqlite::{types::Value, Connection};
+
+ // To help debugging tests etc.
+ #[allow(unused)]
+ pub fn print_table(conn: &Connection, table_name: &str) -> RusqliteResult<()> {
+ let mut stmt = conn.prepare(&format!("SELECT * FROM {}", table_name))?;
+ let mut rows = stmt.query([])?;
+ let mut table = prettytable::Table::new();
+ let mut titles = Row::empty();
+ for col in rows.as_ref().expect("must have statement").columns() {
+ titles.add_cell(Cell::new(col.name()));
+ }
+ table.set_titles(titles);
+ while let Some(sql_row) = rows.next()? {
+ let mut table_row = Row::empty();
+ for i in 0..sql_row.as_ref().column_count() {
+ let val = match sql_row.get::<_, Value>(i)? {
+ Value::Null => "null".to_string(),
+ Value::Integer(i) => i.to_string(),
+ Value::Real(f) => f.to_string(),
+ Value::Text(s) => s,
+ Value::Blob(b) => format!("<blob with {} bytes>", b.len()),
+ };
+ table_row.add_cell(Cell::new(&val));
+ }
+ table.add_row(table_row);
+ }
+ table.printstd();
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::db::test::new_mem_db;
+ use rusqlite::Error;
+ use sql_support::open_database::test_utils::MigratedDatabaseFile;
+ use sql_support::ConnExt;
+
+ const CREATE_SCHEMA_V1_SQL: &str = include_str!("../sql/tests/create_schema_v1.sql");
+
+ #[test]
+ fn test_create_schema_twice() {
+ let db = new_mem_db();
+ db.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");
+ // insert something into our new temp table and check it's there.
+ db.execute_batch(
+ "INSERT INTO temp.storage_sync_staging
+ (guid, ext_id) VALUES
+ ('guid', 'ext_id');",
+ )
+ .expect("should work once");
+ let count = db
+ .query_row_and_then(
+ "SELECT COUNT(*) FROM temp.storage_sync_staging;",
+ [],
+ |row| row.get::<_, u32>(0),
+ )
+ .expect("query should work");
+ assert_eq!(count, 1, "should be one row");
+
+ // re-execute
+ create_empty_sync_temp_tables(&db).expect("should second first time");
+ // and it should have deleted existing data.
+ let count = db
+ .query_row_and_then(
+ "SELECT COUNT(*) FROM temp.storage_sync_staging;",
+ [],
+ |row| row.get::<_, u32>(0),
+ )
+ .expect("query should work");
+ assert_eq!(count, 0, "should be no rows");
+ }
+
+ #[test]
+ fn test_all_upgrades() -> Result<()> {
+ let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
+ db_file.run_all_upgrades();
+ let db = db_file.open();
+
+ let get_id_data = |guid: &str| -> Result<(Option<String>, Option<String>)> {
+ let (ext_id, data) = db
+ .try_query_row::<_, Error, _, _>(
+ "SELECT ext_id, data FROM storage_sync_mirror WHERE guid = :guid",
+ &[(":guid", &guid.to_string())],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ true,
+ )?
+ .expect("row should exist.");
+ Ok((ext_id, data))
+ };
+ assert_eq!(
+ get_id_data("guid-1")?,
+ (Some("ext-id-1".to_string()), Some("data-1".to_string()))
+ );
+ assert_eq!(
+ get_id_data("guid-2")?,
+ (Some("ext-id-2".to_string()), Some("data-2".to_string()))
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_upgrade_2() -> Result<()> {
+ let _ = env_logger::try_init();
+
+ let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
+ db_file.upgrade_to(2);
+ let db = db_file.open();
+
+ // Should be able to insert a new with a NULL ext_id
+ db.execute_batch(
+ "INSERT INTO storage_sync_mirror(guid, ext_id, data)
+ VALUES ('guid-3', NULL, NULL);",
+ )?;
+ Ok(())
+ }
+}