Add and document some more changes

This commit is contained in:
Marcel Müller
2018-04-23 13:23:34 +02:00
parent 1ac7eca877
commit 90baa2aaa5
7 changed files with 336 additions and 43 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ type DB = FileDatabase<Config, Yaml>;
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
+1 -2
View File
@@ -18,8 +18,7 @@ struct Person {
fn main() {
use std::collections::HashMap;
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path(HashMap::new(),
Ron, "test.ron").unwrap();
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new()).unwrap();
println!("Writing to Database");
db.write(|db| {
+183
View File
@@ -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<i32, String>,
}
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<i32, Player>,
}
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<i32, Player>,
}
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<T: 'static>(input: T) -> Option<$res> {
fn inner(mut input: Box<std::any::Any>) -> 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::<data::Versioning, Ron>::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::<Players, Ron>::from_path("migration.ron", updated_data).unwrap();
println!("{:#?}", database);
}
+1 -2
View File
@@ -18,8 +18,7 @@ struct Person {
fn main() {
use std::collections::HashMap;
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path(HashMap::new(),
Ron, "test.ron").unwrap();
let db = FileDatabase::<HashMap<String, Person>, Ron>::from_path("test.ron", HashMap::new()).unwrap();
println!("Writing to Database");
db.write(|db| {
+2
View File
@@ -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<u8>`
#[derive(Debug)]
pub struct MemoryBackend(Vec<u8>);
impl MemoryBackend {
+4 -4
View File
@@ -20,7 +20,7 @@ pub use self::yaml::Yaml;
pub use self::bincode::Bincode;
/// A trait to bundle serializer and deserializer
pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::fmt::Debug + Send + Sync {
pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::std::default::Default + Send + Sync + Clone {
/// Serializes a given value to a String
fn serialize(&self, val: &T) -> error::Result<String>;
/// Deserializes a String to a value
@@ -28,7 +28,7 @@ pub trait DeSerializer<T: Serialize + DeserializeOwned> : ::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<T: Serialize + DeserializeOwned> DeSerializer<T> 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<T: Serialize + DeserializeOwned> DeSerializer<T> 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<T: Serialize + DeserializeOwned> DeSerializer<T> for Bincode {
+144 -34
View File
@@ -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::<HashMap<String, String>, Ron>::memory(HashMap::new(), Ron);
//! # fn main() {
//! # let func = || -> Result<(), failure::Error> {
//! let db = MemoryDatabase::<HashMap<String, String>, 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<Data, Back, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
Back: Backend,
DeSer: DeSerializer<Data> + Send + Sync
Back: Backend + Debug,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
data: RwLock<Data>,
backend: Mutex<Back>,
@@ -123,8 +130,8 @@ pub struct Database<Data, Back, DeSer>
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
Back: Backend,
DeSer: DeSerializer<Data> + Send + Sync
Back: Backend + Debug,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Write lock the database and get write access to the `Data` container
pub fn write<T>(&self, task: T) -> error::Result<()>
@@ -143,17 +150,21 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
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<Data> {
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<Data, Back, DeSer> Database<Data, Back, DeSer>
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<Data> {
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<Data, Back, DeSer> {
Database {
@@ -186,6 +230,58 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
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::<Data, Ron>::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<MemoryDatabase<Data, DeSer>> {
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<D, DS> = Database<D, FileBackend, DS>;
impl<Data, DeSer> Database<Data, FileBackend, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
DeSer: DeSerializer<Data> + Send + Sync
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Create new FileDatabase from Path
pub fn from_path<S>(data: Data, deser: DeSer, path: S)
pub fn from_path<S>(path: S, data: Data)
-> error::Result<FileDatabase<Data, DeSer>>
where S: AsRef<std::path::Path>
{
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<FileDatabase<Data, DeSer>>
{
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<D, DS> = Database<D, MemoryBackend, DS>;
impl<Data, DeSer> Database<Data, MemoryBackend, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
DeSer: DeSerializer<Data> + Send + Sync
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Create new FileDatabase from Path
pub fn memory(data: Data, deser: DeSer) -> MemoryDatabase<Data, DeSer> {
Database {
pub fn memory(data: Data) -> error::Result<MemoryDatabase<Data, DeSer>> {
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<Data, Back, DeSer> Database<Data, Back, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
Back: Backend,
DeSer: DeSerializer<Data> + Send + Sync
Back: Backend + Debug,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Exchanges the DeSerialization strategy with the given one
pub fn with_deser<T>(self, deser: T) -> Database<Data, Back, T>
where T: DeSerializer<Data> + Send + Sync
where T: DeSerializer<Data> + Debug + Send + Sync
{
Database {
backend: self.backend,
@@ -250,12 +360,12 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
Back: Backend,
DeSer: DeSerializer<Data> + Send + Sync
Back: Backend + Debug,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Exchanges the Backend with the given one
pub fn with_backend<T>(self, backend: T) -> Database<Data, T, DeSer>
where T: Backend
where T: Backend + Debug
{
Database {
backend: Mutex::new(backend),
@@ -268,8 +378,8 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
impl<Data, Back, DeSer> Database<Data, Back, DeSer>
where
Data: Serialize + DeserializeOwned + Debug + Clone + Send,
Back: Backend,
DeSer: DeSerializer<Data> + Send + Sync
Back: Backend + Debug,
DeSer: DeSerializer<Data> + Debug + Send + Sync + Clone
{
/// Converts from one data type to another
///
@@ -279,7 +389,7 @@ impl<Data, Back, DeSer> Database<Data, Back, DeSer>
where
OutputData: Serialize + DeserializeOwned + Debug + Clone + Send,
C: FnOnce(Data) -> OutputData,
DeSer: DeSerializer<OutputData>,
DeSer: DeSerializer<OutputData> + Debug + Send + Sync,
{
let (data, backend, deser) = self.into_inner()?;
Ok(Database {