diff --git a/src/bin/migrate_sled.rs b/src/bin/migrate_sled.rs new file mode 100644 index 0000000..0e7be05 --- /dev/null +++ b/src/bin/migrate_sled.rs @@ -0,0 +1,37 @@ +use tenebrous_dicebot::db::sqlite::{Database as SqliteDatabase, Variables}; +use tenebrous_dicebot::db::Database; +use tenebrous_dicebot::error::BotError; + +#[tokio::main] +async fn main() -> Result<(), BotError> { + let sled_path = std::env::args() + .skip(1) + .next() + .expect("Need a path to a Sled database as an arument."); + + let sqlite_path = std::env::args() + .skip(2) + .next() + .expect("Need a path to an sqlite database as an arument."); + + let db = Database::new(&sled_path)?; + + let all_variables = db.variables.get_all_variables()?; + + let sql_db = SqliteDatabase::new(&sqlite_path).await?; + + for var in all_variables { + if let ((username, room_id, variable_name), value) = var { + println!( + "Migrating {}::{}::{} = {} to sql", + username, room_id, variable_name, value + ); + + sql_db + .set_user_variable(&username, &room_id, &variable_name, value) + .await; + } + } + + Ok(()) +} diff --git a/src/db.rs b/src/db.rs index 4036e3c..e714957 100644 --- a/src/db.rs +++ b/src/db.rs @@ -19,7 +19,7 @@ pub mod variables; #[derive(Clone)] pub struct Database { db: Db, - pub(crate) variables: Variables, + pub variables: Variables, pub(crate) migrations: Migrations, pub(crate) rooms: Rooms, pub(crate) state: DbState, diff --git a/src/db/errors.rs b/src/db/errors.rs index 8b1757f..c776594 100644 --- a/src/db/errors.rs +++ b/src/db/errors.rs @@ -26,6 +26,9 @@ pub enum DataError { #[error("expected i32, but i32 schema was violated")] I32SchemaViolation, + #[error("parse error")] + ParseError(#[from] std::num::ParseIntError), + #[error("unexpected or corruptd data bytes")] InvalidValue, diff --git a/src/db/sqlite/mod.rs b/src/db/sqlite/mod.rs index 480f502..e470764 100644 --- a/src/db/sqlite/mod.rs +++ b/src/db/sqlite/mod.rs @@ -43,7 +43,7 @@ pub(crate) trait Rooms { // TODO move this up to the top once we delete sled. Traits will be the // main API, then we can have different impls for different DBs. #[async_trait] -pub(crate) trait Variables { +pub trait Variables { async fn get_user_variables( &self, user: &str, diff --git a/src/db/variables.rs b/src/db/variables.rs index 1c3834c..947e78b 100644 --- a/src/db/variables.rs +++ b/src/db/variables.rs @@ -10,6 +10,8 @@ use std::str; use zerocopy::byteorder::I32; use zerocopy::AsBytes; +use super::errors; + pub(super) mod migrations; #[derive(Clone)] @@ -67,6 +69,9 @@ fn alter_room_variable_count( Ok(new_count) } +/// Room ID, Username, Variable Name +pub type AllVariablesKey = (String, String, String); + impl Variables { pub(in crate::db) fn new(db: &sled::Db) -> Result { Ok(Variables { @@ -75,6 +80,40 @@ impl Variables { }) } + pub fn get_all_variables(&self) -> Result, DataError> { + use std::convert::TryFrom; + let variables: Result, DataError> = self + .room_user_variables + .scan_prefix("") + .map(|entry| match entry { + Ok((key, raw_value)) => { + let keys: Vec<_> = key + .split(|&b| b == 0xfe || b == 0xff) + .map(|b| str::from_utf8(b)) + .collect(); + + if let &[Ok(room_id), Ok(username), Ok(variable_name), ..] = keys.as_slice() { + Ok(( + ( + room_id.to_owned(), + username.to_owned(), + variable_name.to_owned(), + ), + convert_i32(&raw_value)?, + )) + } else { + Err(errors::DataError::InvalidValue) + } + } + Err(e) => Err(e.into()), + }) + .collect(); + + // Convert tuples to hash map with collect(), inferred via + // return type. + variables.map(|entries| entries.into_iter().collect()) + } + pub fn get_user_variables( &self, key: &UserAndRoom<'_>,