From 90baa2aaa50a2bb48c1ba3c063c578afbf9d5847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 23 Apr 2018 13:23:34 +0200 Subject: [PATCH] Add and document some more changes --- examples/config.rs | 2 +- examples/full.rs | 3 +- examples/migration.rs.incomplete | 183 +++++++++++++++++++++++++++++++ examples/switching.rs | 3 +- src/backend.rs | 2 + src/deser.rs | 8 +- src/lib.rs | 178 ++++++++++++++++++++++++------ 7 files changed, 336 insertions(+), 43 deletions(-) create mode 100644 examples/migration.rs.incomplete diff --git a/examples/config.rs b/examples/config.rs index 395cf5b..0f57b0c 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -22,7 +22,7 @@ type DB = FileDatabase; lazy_static! { static ref CONFIG: DB = { - let db = FileDatabase::from_path(Config::default(), Yaml, "/tmp/config.yml") + let db = FileDatabase::from_path("/tmp/config.yml", Config::default()) .expect("Create database from path"); db.reload().expect("Config to load"); db diff --git a/examples/full.rs b/examples/full.rs index b3c5e73..0a2bc57 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -18,8 +18,7 @@ struct Person { fn main() { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path(HashMap::new(), - Ron, "test.ron").unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); println!("Writing to Database"); db.write(|db| { diff --git a/examples/migration.rs.incomplete b/examples/migration.rs.incomplete new file mode 100644 index 0000000..1d73b9f --- /dev/null +++ b/examples/migration.rs.incomplete @@ -0,0 +1,183 @@ +/* This file is a complete work in progress! And is meant to illustrate a possible way of + * migrating databases with Rustbreak. + * + * Currently the major challenge is to statically define the upgrade process. + * + * This is currently done by using the `upgrading_macro`. + * + * The idea is to read the 'version' tag in the file, load it into a simple tag struct and + * read the version from there. This version is then used to dynamically get the corresponding + * struct type. This then gets converted to the latest version. + */ + + +extern crate rustbreak; +#[macro_use] extern crate serde_derive; + +use rustbreak::FileDatabase; +use rustbreak::deser::Ron; + +mod data { + #[derive(Debug, Serialize, Deserialize, Clone, Copy)] + pub enum Version { + First, Second, Third, + } + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Versioning { + pub version: Version + } + + pub mod v1 { + use data::Version; + use std::collections::HashMap; + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Players { + version: Version, + pub players: HashMap, + } + + impl Players { + pub fn new() -> Players { + Players { + version: Version::First, + players: HashMap::new(), + } + } + } + } + + pub mod v2 { + use data::Version; + use std::collections::HashMap; + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Player { + pub name: String, + pub level: i32, + } + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Players { + version: Version, + pub players: HashMap, + } + + impl Players { + pub fn new() -> Players { + Players { + version: Version::Second, + players: HashMap::new(), + } + } + } + } + + pub mod v3 { + use data::Version; + use std::collections::HashMap; + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Player { + pub name: String, + pub score: i64 + } + + #[derive(Debug, Serialize, Deserialize, Clone)] + pub struct Players { + version: Version, + pub players: HashMap, + } + + impl Players { + pub fn new() -> Players { + Players { + version: Version::Third, + players: HashMap::new(), + } + } + } + } + + pub fn convert_v1_v2(input: v1::Players) -> v2::Players { + let mut new = v2::Players::new(); + for (key, player) in input.players { + new.players.insert(key, + v2::Player { + name: player, + level: 1 + }); + } + + new + } + + pub fn convert_v2_v3(input: v2::Players) -> v3::Players { + let mut new = v3::Players::new(); + for (key, player) in input.players { + new.players.insert(key, + v3::Player { + name: player.name, + score: 0 + }); + } + + new + } +} + +macro_rules! migration_rules { + ( $name:ident -> $res:ty { $( $t:ty => $conv:path ),* $(,)* } ) => { + fn $name(input: T) -> Option<$res> { + fn inner(mut input: Box) -> Option<$res> { + println!("We have: {:#?}", input); + $( + { + let mut maybe_in = None; + if let Some(out) = input.downcast_ref::<$t>() { + maybe_in = Some(Box::new($conv(out.clone()))); + } + + if let Some(inp) = maybe_in { + input = inp; + } + } + )* + + if let Ok(out) = input.downcast::<$res>() { + return Some(*out); + } + + None + } + inner(Box::new(input)) + } + } +} + +migration_rules! { + update_database -> data::v3::Players { + data::v1::Players => data::convert_v1_v2, + data::v2::Players => data::convert_v2_v3, + } +} + +fn get_version() -> data::Version { + let db = FileDatabase::::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap(); + db.reload().unwrap(); + db.read(|ver| ver.version).unwrap() +} + +fn main() { + use data::v3::Players; + + let version = get_version(); + + + // Let's check if there are any updates + let updated_data = update_database(database.get_data(false).unwrap()).unwrap(); + let database = FileDatabase::::from_path("migration.ron", updated_data).unwrap(); + + println!("{:#?}", database); +} diff --git a/examples/switching.rs b/examples/switching.rs index 5f09894..db9d147 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -18,8 +18,7 @@ struct Person { fn main() { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path(HashMap::new(), - Ron, "test.ron").unwrap(); + let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new()).unwrap(); println!("Writing to Database"); db.write(|db| { diff --git a/src/backend.rs b/src/backend.rs index 46d9ba1..4ae4ac2 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -17,6 +17,7 @@ pub trait Backend { } /// A backend using a file +#[derive(Debug)] pub struct FileBackend(::std::fs::File); impl Backend for FileBackend { @@ -63,6 +64,7 @@ impl FileBackend { /// An in memory backend /// /// It is backed by a `Vec` +#[derive(Debug)] pub struct MemoryBackend(Vec); impl MemoryBackend { diff --git a/src/deser.rs b/src/deser.rs index 5247a56..294199a 100644 --- a/src/deser.rs +++ b/src/deser.rs @@ -20,7 +20,7 @@ pub use self::yaml::Yaml; pub use self::bincode::Bincode; /// A trait to bundle serializer and deserializer -pub trait DeSerializer : ::std::fmt::Debug + Send + Sync { +pub trait DeSerializer : ::std::default::Default + Send + Sync + Clone { /// Serializes a given value to a String fn serialize(&self, val: &T) -> error::Result; /// Deserializes a String to a value @@ -28,7 +28,7 @@ pub trait DeSerializer : ::std::fmt::Debug + Se } /// The Struct that allows you to use `ron` the Rusty Object Notation -#[derive(Debug)] +#[derive(Debug, Default, Clone)] pub struct Ron; impl DeSerializer for Ron { @@ -52,7 +52,7 @@ mod yaml { use deser::DeSerializer; /// The struct that allows you to use yaml - #[derive(Debug)] + #[derive(Debug, Default, Clone)] pub struct Yaml; impl DeSerializer for Yaml { @@ -78,7 +78,7 @@ mod bincode { use deser::DeSerializer; /// The struct that allows you to use bincode - #[derive(Debug)] + #[derive(Debug, Default, Clone)] pub struct Bincode; impl DeSerializer for Bincode { diff --git a/src/lib.rs b/src/lib.rs index 567459d..443800c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! # Rustbreak //! -//! Rustbreak is a [Daybreak][daybreak] inspiried single file Database. +//! Rustbreak is a [Daybreak][daybreak] inspired single file Database. //! //! You will find an overview here in the docs, but to give you a more complete tale of how this is //! used please check the [examples][examples]. @@ -46,10 +46,14 @@ //! ## Quickstart //! //! ```rust +//! # extern crate failure; +//! # extern crate rustbreak; //! # use std::collections::HashMap; //! use rustbreak::{MemoryDatabase, deser::Ron}; //! -//! let db = MemoryDatabase::, Ron>::memory(HashMap::new(), Ron); +//! # fn main() { +//! # let func = || -> Result<(), failure::Error> { +//! let db = MemoryDatabase::, Ron>::memory(HashMap::new())?; //! //! println!("Writing to Database"); //! db.write(|db| { @@ -62,6 +66,9 @@ //! // The above line will not compile since we are only reading //! println!("Hello: {:?}", db.get("hello")); //! }); +//! # return Ok(()); }; +//! # func().unwrap(); +//! # } //! ``` //! //! [daybreak]:https://propublica.github.io/daybreak @@ -112,8 +119,8 @@ use backend::{Backend, MemoryBackend, FileBackend}; pub struct Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { data: RwLock, backend: Mutex, @@ -123,8 +130,8 @@ pub struct Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Write lock the database and get write access to the `Data` container pub fn write(&self, task: T) -> error::Result<()> @@ -143,17 +150,21 @@ impl Database Ok(task(&mut lock)) } - /// Reload the Data from the backend - pub fn reload(&self) -> error::Result<()> { + fn load(backend: &mut Back, deser: &DeSer) -> error::Result { use failure::ResultExt; - - let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; - - let mut new_data = self.deser.deserialize(&backend.get_data()?[..]) + let new_data = deser.deserialize(&backend.get_data()?[..]) .context(error::RustbreakErrorKind::DeserializationError)?; - ::std::mem::swap(&mut *data, &mut new_data); + Ok(new_data) + } + + /// Reload the Data from the backend + pub fn reload(&self) -> error::Result<()> { + + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + + *data = Self::load(&mut backend, &self.deser)?; Ok(()) } @@ -171,6 +182,39 @@ impl Database Ok(()) } + /// Get a clone of the data as it is in memory right now + /// + /// To make sure you have the latest data, call this method with `reload` true + pub fn get_data(&self, reload: bool) -> error::Result { + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + if reload { + *data = Self::load(&mut backend, &self.deser)?; + drop(backend); + } + Ok(data.clone()) + } + + /// Puts the data as is into memory + /// + /// To sync the data afterwards, call with `sync` true. + pub fn put_data(&self, new_data: Data, sync: bool) -> error::Result<()> { + let mut backend = self.backend.lock().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + let mut data = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + if sync { + // TODO: Spin this into its own method + use failure::ResultExt; + + let ser = self.deser.serialize(&*data) + .context(error::RustbreakErrorKind::SerializationError)?; + + backend.put_data(ser.as_bytes())?; + drop(backend); + } + *data = new_data; + Ok(()) + } + /// Create a database from its constituents pub fn from_parts(data: Data, backend: Back, deser: DeSer) -> Database { Database { @@ -186,6 +230,58 @@ impl Database self.backend.into_inner().map_err(|_| error::RustbreakErrorKind::PoisonError)?, self.deser)) } + + /// Tries to clone the Data in the Database. + /// + /// This method returns a `MemoryDatabase` which has an empty vector as a + /// backend initially. This means that the user is responsible for assigning a new backend + /// if an alternative is wanted. + /// + /// # Examples + /// + /// ```rust + /// # #[macro_use] extern crate serde_derive; + /// # extern crate rustbreak; + /// # extern crate serde; + /// # extern crate tempfile; + /// # extern crate failure; + /// + /// use rustbreak::{FileDatabase, deser::Ron}; + /// + /// #[derive(Debug, Serialize, Deserialize, Clone)] + /// struct Data { + /// level: u32, + /// } + /// + /// # fn main() { + /// # let func = || -> Result<(), failure::Error> { + /// # let file = tempfile::tempfile()?; + /// let db = FileDatabase::::from_file(file, Data { level: 0 })?; + /// + /// db.write(|db| { + /// db.level = 42; + /// })?; + /// + /// db.sync()?; + /// + /// let other_db = db.try_clone()?; + /// + /// let value = other_db.read(|db| db.level)?; + /// assert_eq!(42, value); + /// # return Ok(()); + /// # }; + /// # func().unwrap(); + /// # } + /// ``` + pub fn try_clone(&self) -> error::Result> { + let lock = self.data.write().map_err(|_| error::RustbreakErrorKind::PoisonError)?; + + Ok(Database { + data: RwLock::new(lock.clone()), + backend: Mutex::new(MemoryBackend::new()), + deser: self.deser.clone(), + }) + } } /// A database backed by a file @@ -194,19 +290,31 @@ pub type FileDatabase = Database; impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - DeSer: DeSerializer + Send + Sync + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Create new FileDatabase from Path - pub fn from_path(data: Data, deser: DeSer, path: S) + pub fn from_path(path: S, data: Data) -> error::Result> where S: AsRef { - let b = FileBackend::open(path)?; + let backend = FileBackend::open(path)?; Ok(Database { data: RwLock::new(data), - backend: Mutex::new(b), - deser: deser, + backend: Mutex::new(backend), + deser: DeSer::default(), + }) + } + + /// Create new FileDatabase from a file + pub fn from_file(file: ::std::fs::File, data: Data) -> error::Result> + { + let backend = FileBackend::from_file(file); + + Ok(Database { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser: DeSer::default(), }) } } @@ -217,27 +325,29 @@ pub type MemoryDatabase = Database; impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - DeSer: DeSerializer + Send + Sync + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Create new FileDatabase from Path - pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase { - Database { + pub fn memory(data: Data) -> error::Result> { + let backend = MemoryBackend::new(); + + Ok(Database { data: RwLock::new(data), - backend: Mutex::new(MemoryBackend::new()), - deser: deser, - } + backend: Mutex::new(backend), + deser: DeSer::default(), + }) } } impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Exchanges the DeSerialization strategy with the given one pub fn with_deser(self, deser: T) -> Database - where T: DeSerializer + Send + Sync + where T: DeSerializer + Debug + Send + Sync { Database { backend: self.backend, @@ -250,12 +360,12 @@ impl Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Exchanges the Backend with the given one pub fn with_backend(self, backend: T) -> Database - where T: Backend + where T: Backend + Debug { Database { backend: Mutex::new(backend), @@ -268,8 +378,8 @@ impl Database impl Database where Data: Serialize + DeserializeOwned + Debug + Clone + Send, - Back: Backend, - DeSer: DeSerializer + Send + Sync + Back: Backend + Debug, + DeSer: DeSerializer + Debug + Send + Sync + Clone { /// Converts from one data type to another /// @@ -279,7 +389,7 @@ impl Database where OutputData: Serialize + DeserializeOwned + Debug + Clone + Send, C: FnOnce(Data) -> OutputData, - DeSer: DeSerializer, + DeSer: DeSerializer + Debug + Send + Sync, { let (data, backend, deser) = self.into_inner()?; Ok(Database {