From 7e15379c58fc098de04002c900337ca601b9ccb7 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sun, 1 Nov 2020 18:59:07 +0000 Subject: [PATCH] Migrate existing delineators to delimiter 0xfe. --- src/config.rs | 2 +- src/db/data_migrations.rs | 3 +- src/db/variables.rs | 2 +- src/db/variables/migrations.rs | 233 +++++++++++++++++++++++---------- 4 files changed, 167 insertions(+), 73 deletions(-) diff --git a/src/config.rs b/src/config.rs index cddfe10..0d7dc2a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use thiserror::Error; /// Shortcut to defining db migration versions. Will probably /// eventually be moved to a config file. -const MIGRATION_VERSION: u32 = 3; +const MIGRATION_VERSION: u32 = 4; #[derive(Error, Debug)] pub enum ConfigError { diff --git a/src/db/data_migrations.rs b/src/db/data_migrations.rs index e44aa3b..3ab674a 100644 --- a/src/db/data_migrations.rs +++ b/src/db/data_migrations.rs @@ -6,9 +6,10 @@ use phf::phf_map; pub(super) type DataMigration = (fn(&Database) -> Result<(), DataError>, &'static str); static MIGRATIONS: phf::Map = phf_map! { - 1u32 => (add_room_user_variable_count, "add_room_user_variable_count"), + 1u32 => (add_room_user_variable_count::migrate, "add_room_user_variable_count"), 2u32 => (delete_v0_schema, "delete_v0_schema"), 3u32 => (delete_variable_count, "delete_variable_count"), + 4u32 => (change_delineator_delimiter::migrate, "change_delineator_delimiter") }; pub fn get_migrations(versions: &[u32]) -> Result, MigrationError> { diff --git a/src/db/variables.rs b/src/db/variables.rs index 8871b7d..ae2d016 100644 --- a/src/db/variables.rs +++ b/src/db/variables.rs @@ -62,7 +62,7 @@ impl<'a> From> for Vec { fn from(value: RoomAndUser<'a>) -> Vec { let mut bytes = vec![]; bytes.extend_from_slice(value.0.as_bytes()); - bytes.push(0xff); + bytes.push(0xfe); bytes.extend_from_slice(value.1.as_bytes()); bytes } diff --git a/src/db/variables/migrations.rs b/src/db/variables/migrations.rs index 0bff9dd..a18a1c2 100644 --- a/src/db/variables/migrations.rs +++ b/src/db/variables/migrations.rs @@ -8,90 +8,94 @@ use sled::{Batch, IVec}; use zerocopy::byteorder::U32; use zerocopy::AsBytes; -//Not to be confused with the super::RoomAndUser delineator. -#[derive(PartialEq, Eq, std::hash::Hash)] -struct RoomAndUser { - room_id: String, - username: String, -} +pub(in crate::db) mod add_room_user_variable_count { + use super::*; + //Not to be confused with the super::RoomAndUser delineator. + #[derive(PartialEq, Eq, std::hash::Hash)] + struct RoomAndUser { + room_id: String, + username: String, + } -/// Create a version 0 user variable key. -fn v0_variable_key(info: &RoomAndUser, variable_name: &str) -> Vec { - let mut key = vec![]; - key.extend_from_slice(info.room_id.as_bytes()); - key.extend_from_slice(info.username.as_bytes()); - key.extend_from_slice(variable_name.as_bytes()); - key -} + /// Create a version 0 user variable key. + fn v0_variable_key(info: &RoomAndUser, variable_name: &str) -> Vec { + let mut key = vec![]; + key.extend_from_slice(info.room_id.as_bytes()); + key.extend_from_slice(info.username.as_bytes()); + key.extend_from_slice(variable_name.as_bytes()); + key + } -fn map_value_to_room_and_user( - entry: sled::Result<(IVec, IVec)>, -) -> Result { - if let Ok((key, _)) = entry { - let keys: Vec> = key - .split(|&b| b == 0xff) - .map(|b| str::from_utf8(b)) - .collect(); + fn map_value_to_room_and_user( + entry: sled::Result<(IVec, IVec)>, + ) -> Result { + if let Ok((key, _)) = entry { + let keys: Vec> = key + .split(|&b| b == 0xff) + .map(|b| str::from_utf8(b)) + .collect(); - if let &[_, Ok(room_id), Ok(username), Ok(_variable)] = keys.as_slice() { - Ok(RoomAndUser { - room_id: room_id.to_owned(), - username: username.to_owned(), - }) + if let &[_, Ok(room_id), Ok(username), Ok(_variable)] = keys.as_slice() { + Ok(RoomAndUser { + room_id: room_id.to_owned(), + username: username.to_owned(), + }) + } else { + Err(MigrationError::MigrationFailed( + "a key violates utf8 schema".to_string(), + )) + } } else { Err(MigrationError::MigrationFailed( - "a key violates utf8 schema".to_string(), + "encountered unexpected key".to_string(), )) } - } else { - Err(MigrationError::MigrationFailed( - "encountered unexpected key".to_string(), - )) } -} -pub(in crate::db) fn add_room_user_variable_count(db: &Database) -> Result<(), DataError> { - let tree = &db.variables.0; - let prefix = variables_space_prefix(""); + pub(in crate::db) fn migrate(db: &Database) -> Result<(), DataError> { + let tree = &db.variables.0; + let prefix = variables_space_prefix(""); - //Extract a vec of tuples, consisting of room id + username. - let results: Vec = tree - .scan_prefix(prefix) - .map(map_value_to_room_and_user) - .collect::, MigrationError>>()?; + //Extract a vec of tuples, consisting of room id + username. + let results: Vec = tree + .scan_prefix(prefix) + .map(map_value_to_room_and_user) + .collect::, MigrationError>>()?; - let counts: HashMap = - results - .into_iter() - .fold(HashMap::new(), |mut count_map, room_and_user| { - let count = count_map.entry(room_and_user).or_insert(0); - *count += 1; - count_map + let counts: HashMap = + results + .into_iter() + .fold(HashMap::new(), |mut count_map, room_and_user| { + let count = count_map.entry(room_and_user).or_insert(0); + *count += 1; + count_map + }); + + //Start a transaction on the variables tree. + let tx_result: Result<_, TransactionError> = + db.variables.0.transaction(|tx_vars| { + let batch = counts.iter().fold(Batch::default(), |mut batch, entry| { + let (info, count) = entry; + + //Add variable count according to new schema. + let delineator = super::RoomAndUser(&info.room_id, &info.username); + let key = variables_space_key(delineator, VARIABLE_COUNT_KEY); + let db_value: U32 = U32::new(*count); + batch.insert(key, db_value.as_bytes()); + + //Delete the old variable_count variable if exists. + let old_key = v0_variable_key(&info, "variable_count"); + batch.remove(old_key); + batch + }); + + tx_vars.apply_batch(&batch)?; + Ok(()) }); - //Start a transaction on the variables tree. - let tx_result: Result<_, TransactionError> = db.variables.0.transaction(|tx_vars| { - let batch = counts.iter().fold(Batch::default(), |mut batch, entry| { - let (info, count) = entry; - - //Add variable count according to new schema. - let delineator = super::RoomAndUser(&info.room_id, &info.username); - let key = variables_space_key(delineator, VARIABLE_COUNT_KEY); - let db_value: U32 = U32::new(*count); - batch.insert(key, db_value.as_bytes()); - - //Delete the old variable_count variable if exists. - let old_key = v0_variable_key(&info, "variable_count"); - batch.remove(old_key); - batch - }); - - tx_vars.apply_batch(&batch)?; + tx_result?; //For some reason, it cannot infer the type Ok(()) - }); - - tx_result?; //For some reason, it cannot infer the type - Ok(()) + } } pub(in crate::db) fn delete_v0_schema(db: &Database) -> Result<(), DataError> { @@ -132,3 +136,92 @@ pub(in crate::db) fn delete_variable_count(db: &Database) -> Result<(), DataErro db.variables.0.apply_batch(batch)?; Ok(()) } + +pub(in crate::db) mod change_delineator_delimiter { + use super::*; + + /// An entry in the room user variables keyspace. + struct UserVariableEntry { + room_id: String, + username: String, + variable_name: String, + value: IVec, + } + + /// Extract keys and values from the variables keyspace according + /// to the v1 schema. + fn extract_v1_entries( + entry: sled::Result<(IVec, IVec)>, + ) -> Result { + if let Ok((key, value)) = entry { + let keys: Vec> = key + .split(|&b| b == 0xff) + .map(|b| str::from_utf8(b)) + .collect(); + + if let &[_, Ok(room_id), Ok(username), Ok(variable)] = keys.as_slice() { + Ok(UserVariableEntry { + room_id: room_id.to_owned(), + username: username.to_owned(), + variable_name: variable.to_owned(), + value: value, + }) + } else { + Err(MigrationError::MigrationFailed( + "a key violates utf8 schema".to_string(), + )) + } + } else { + Err(MigrationError::MigrationFailed( + "encountered unexpected key".to_string(), + )) + } + } + + /// Create an old key, where delineator is separated by 0xff. + fn create_old_key(prefix: &[u8], insert: &UserVariableEntry) -> Vec { + let mut key = vec![]; + key.extend_from_slice(&prefix); //prefix already has 0xff. + key.extend_from_slice(&insert.room_id.as_bytes()); + key.push(0xff); + key.extend_from_slice(&insert.username.as_bytes()); + key.push(0xff); + key.extend_from_slice(&insert.variable_name.as_bytes()); + key + } + + /// Create an old key, where delineator is separated by 0xfe. + fn create_new_key(prefix: &[u8], insert: &UserVariableEntry) -> Vec { + let mut key = vec![]; + key.extend_from_slice(&prefix); //prefix already has 0xff. + key.extend_from_slice(&insert.room_id.as_bytes()); + key.push(0xfe); + key.extend_from_slice(&insert.username.as_bytes()); + key.push(0xff); + key.extend_from_slice(&insert.variable_name.as_bytes()); + key + } + + pub fn migrate(db: &Database) -> Result<(), DataError> { + let tree = &db.variables.0; + let prefix = variables_space_prefix(""); + + let results: Vec = tree + .scan_prefix(&prefix) + .map(extract_v1_entries) + .collect::, MigrationError>>()?; + + let mut batch = Batch::default(); + + for insert in results { + let old = create_old_key(&prefix, &insert); + let new = create_new_key(&prefix, &insert); + + batch.remove(old); + batch.insert(new, insert.value); + } + + tree.apply_batch(batch)?; + Ok(()) + } +}