Migrate existing delineators to delimiter 0xfe.

This commit is contained in:
projectmoon 2020-11-01 18:59:07 +00:00
parent 3ccd60c173
commit 7e15379c58
4 changed files with 167 additions and 73 deletions

View File

@ -6,7 +6,7 @@ use thiserror::Error;
/// Shortcut to defining db migration versions. Will probably /// Shortcut to defining db migration versions. Will probably
/// eventually be moved to a config file. /// eventually be moved to a config file.
const MIGRATION_VERSION: u32 = 3; const MIGRATION_VERSION: u32 = 4;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ConfigError { pub enum ConfigError {

View File

@ -6,9 +6,10 @@ use phf::phf_map;
pub(super) type DataMigration = (fn(&Database) -> Result<(), DataError>, &'static str); pub(super) type DataMigration = (fn(&Database) -> Result<(), DataError>, &'static str);
static MIGRATIONS: phf::Map<u32, DataMigration> = phf_map! { static MIGRATIONS: phf::Map<u32, DataMigration> = 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"), 2u32 => (delete_v0_schema, "delete_v0_schema"),
3u32 => (delete_variable_count, "delete_variable_count"), 3u32 => (delete_variable_count, "delete_variable_count"),
4u32 => (change_delineator_delimiter::migrate, "change_delineator_delimiter")
}; };
pub fn get_migrations(versions: &[u32]) -> Result<Vec<DataMigration>, MigrationError> { pub fn get_migrations(versions: &[u32]) -> Result<Vec<DataMigration>, MigrationError> {

View File

@ -62,7 +62,7 @@ impl<'a> From<RoomAndUser<'a>> for Vec<u8> {
fn from(value: RoomAndUser<'a>) -> Vec<u8> { fn from(value: RoomAndUser<'a>) -> Vec<u8> {
let mut bytes = vec![]; let mut bytes = vec![];
bytes.extend_from_slice(value.0.as_bytes()); bytes.extend_from_slice(value.0.as_bytes());
bytes.push(0xff); bytes.push(0xfe);
bytes.extend_from_slice(value.1.as_bytes()); bytes.extend_from_slice(value.1.as_bytes());
bytes bytes
} }

View File

@ -8,90 +8,94 @@ use sled::{Batch, IVec};
use zerocopy::byteorder::U32; use zerocopy::byteorder::U32;
use zerocopy::AsBytes; use zerocopy::AsBytes;
//Not to be confused with the super::RoomAndUser delineator. pub(in crate::db) mod add_room_user_variable_count {
#[derive(PartialEq, Eq, std::hash::Hash)] use super::*;
struct RoomAndUser { //Not to be confused with the super::RoomAndUser delineator.
room_id: String, #[derive(PartialEq, Eq, std::hash::Hash)]
username: String, struct RoomAndUser {
} room_id: String,
username: String,
}
/// Create a version 0 user variable key. /// Create a version 0 user variable key.
fn v0_variable_key(info: &RoomAndUser, variable_name: &str) -> Vec<u8> { fn v0_variable_key(info: &RoomAndUser, variable_name: &str) -> Vec<u8> {
let mut key = vec![]; let mut key = vec![];
key.extend_from_slice(info.room_id.as_bytes()); key.extend_from_slice(info.room_id.as_bytes());
key.extend_from_slice(info.username.as_bytes()); key.extend_from_slice(info.username.as_bytes());
key.extend_from_slice(variable_name.as_bytes()); key.extend_from_slice(variable_name.as_bytes());
key key
} }
fn map_value_to_room_and_user( fn map_value_to_room_and_user(
entry: sled::Result<(IVec, IVec)>, entry: sled::Result<(IVec, IVec)>,
) -> Result<RoomAndUser, MigrationError> { ) -> Result<RoomAndUser, MigrationError> {
if let Ok((key, _)) = entry { if let Ok((key, _)) = entry {
let keys: Vec<Result<&str, _>> = key let keys: Vec<Result<&str, _>> = key
.split(|&b| b == 0xff) .split(|&b| b == 0xff)
.map(|b| str::from_utf8(b)) .map(|b| str::from_utf8(b))
.collect(); .collect();
if let &[_, Ok(room_id), Ok(username), Ok(_variable)] = keys.as_slice() { if let &[_, Ok(room_id), Ok(username), Ok(_variable)] = keys.as_slice() {
Ok(RoomAndUser { Ok(RoomAndUser {
room_id: room_id.to_owned(), room_id: room_id.to_owned(),
username: username.to_owned(), username: username.to_owned(),
}) })
} else {
Err(MigrationError::MigrationFailed(
"a key violates utf8 schema".to_string(),
))
}
} else { } else {
Err(MigrationError::MigrationFailed( 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> { pub(in crate::db) fn migrate(db: &Database) -> Result<(), DataError> {
let tree = &db.variables.0; let tree = &db.variables.0;
let prefix = variables_space_prefix(""); let prefix = variables_space_prefix("");
//Extract a vec of tuples, consisting of room id + username. //Extract a vec of tuples, consisting of room id + username.
let results: Vec<RoomAndUser> = tree let results: Vec<RoomAndUser> = tree
.scan_prefix(prefix) .scan_prefix(prefix)
.map(map_value_to_room_and_user) .map(map_value_to_room_and_user)
.collect::<Result<Vec<_>, MigrationError>>()?; .collect::<Result<Vec<_>, MigrationError>>()?;
let counts: HashMap<RoomAndUser, u32> = let counts: HashMap<RoomAndUser, u32> =
results results
.into_iter() .into_iter()
.fold(HashMap::new(), |mut count_map, room_and_user| { .fold(HashMap::new(), |mut count_map, room_and_user| {
let count = count_map.entry(room_and_user).or_insert(0); let count = count_map.entry(room_and_user).or_insert(0);
*count += 1; *count += 1;
count_map count_map
});
//Start a transaction on the variables tree.
let tx_result: Result<_, TransactionError<DataError>> =
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<LittleEndian> = 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. tx_result?; //For some reason, it cannot infer the type
let tx_result: Result<_, TransactionError<DataError>> = 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<LittleEndian> = 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(()) Ok(())
}); }
tx_result?; //For some reason, it cannot infer the type
Ok(())
} }
pub(in crate::db) fn delete_v0_schema(db: &Database) -> Result<(), DataError> { 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)?; db.variables.0.apply_batch(batch)?;
Ok(()) 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<UserVariableEntry, MigrationError> {
if let Ok((key, value)) = entry {
let keys: Vec<Result<&str, _>> = 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<u8> {
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<u8> {
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<UserVariableEntry> = tree
.scan_prefix(&prefix)
.map(extract_v1_entries)
.collect::<Result<Vec<_>, 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(())
}
}