Skip to main content

DatabaseManager

Struct DatabaseManager 

Source
pub struct DatabaseManager { /* private fields */ }
Expand description

A manager for a file-system database.

A “database” is essentially just an arbitrary directory in the file system with subdirectories for specific implementors of DatabaseEntry. Those subdirectories are named after the implementor (see type_name) and stores files with the serialized representations of individual instances. The directories are created on a as-needed base by the DatabaseManager.

The following code defines a database at the path /path/to/db (the “database root”) and stores two composed DatabaseEntry implementors within it.

use std::ffi::OsStr;

use serde::{Serialize, Deserialize};
use serde_mosaic::*;

#[derive(Serialize, Deserialize, Clone)]
struct Material {
    name: String,
    cotton_content: f64,
}

#[typetag::serde]
impl DatabaseEntry for Material {
    fn name(&self) -> &OsStr {
        self.name.as_ref()
    }
}

#[derive(Serialize, Deserialize)]
struct Shirt {
    owner: String,
    #[serde(serialize_with = "serialize_link")]
    #[serde(deserialize_with = "deserialize_link")]
    material: Material,
    size: usize
}

#[typetag::serde]
impl DatabaseEntry for Shirt {
    fn name(&self) -> &OsStr {
        self.owner.as_ref()
    }
}

let pure_cotton = Material {
    name: "pure_cotton".into(),
    cotton_content: 100.0,
};

let mikes_shirt = Shirt {
    owner: "mike".into(),
    material: pure_cotton.clone(),
    size: 40
};

let joes_shirt = Shirt {
    owner: "joe".into(),
    material: pure_cotton.clone(),
    size: 38
};

let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
dbm.write(&mikes_shirt, &WriteOptions::default()).expect("serialization and writing succeeds");
dbm.write(&joes_shirt, &WriteOptions::default()).expect("serialization and writing succeeds");

The first dbm.write creates a directory /path/to/db/Shirt (if it didn’t exist yet) and a file /path/to/db/Shirt/mike.yaml where the content of the variable mikes_shirt is stored. Additionally, since pure_cotton should be linked during serialization and deserialization, a second file /path/to/db/Material/pure_cotton.yaml (and the corresponding directory) is created, which contains the serialized representation of pure_cotton.

The second dbm.write now creates a file /path/to/db/Shirt/joe.yaml. Since /path/to/db/Material/pure_cotton.yaml already exists, no additional file is created.

The DatabaseManager holds the path to the database directory /path/to/db, the database format SerdeYaml and a Cache for reference-counted instances (see the docstring of Cache for more). It is therefore cheap to create new DatabaseManager instances.

All methods which manipulate files (e.g. read, write or remove take a mutable reference of self. This is done in order to prevent race conditions when operating multi-threaded. If it is necessary to use a DatabaseManager in multiple threads at once, consider using a Mutex lock or creating one manager instance per thread (although this prevents sharing the Cache over the different threads).

Implementations§

Source§

impl DatabaseManager

Source

pub fn new<P, F>(path: P, format: F) -> Result<Self>
where P: AsRef<Path>, F: Format + 'static,

Creates a new instance of Self with the given path and format. If the path does not exist in the file system, this function tries to create it. Returns a std::io::Error if the directory cannot be used as a database (e.g. due to permission issues).

§Examples
use serde_mosaic::*;

let dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
Source

pub fn with_boxed_format<P>(path: P, format: Box<dyn Format>) -> Result<Self>
where P: AsRef<Path>,

Like DatabaseManager::new, but takes a boxed Format trait object. DatabaseManager::new boxes its format argument and then calls this function. If the format is already available as a boxed trait object (e.g. because it has been taken from another DatabaseManager instance), using this function skips an allocation.

Source

pub fn open<P, F>(path: P, format: F) -> Result<Self>
where P: AsRef<Path>, F: Format + 'static,

Like DatabaseManager::new, but returns an error if the specified path does not exist.

Source

pub fn open_with_boxed_format<P>( path: P, format: Box<dyn Format>, ) -> Result<Self>
where P: AsRef<Path>,

Like DatabaseManager::open, but takes a boxed Format instead of being generic. See DatabaseManager::with_boxed_format for details.

Source

pub fn dir(&self) -> &Path

Returns a reference to the Path used as the database root of self.

§Examples
use std::path::Path;
use serde_mosaic::*;

let dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
assert_eq!(dbm.dir(), Path::new("/path/to/db"));
Source

pub fn data_format(&self) -> &dyn Format

Returns a reference to the underlying Format of the database.

Since the Format is internally stored as a trait object, this function returns a reference to that trait object as well. The trait bounds of Format guarantee that any implementor also implements the Any trait and can therefore be downcasted to the concrete type.

§Examples
use std::any::Any;
use serde_mosaic::*;

let dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
let format_ref = dbm.data_format() as &dyn Any; // Possible since Rust 1.86
assert!(format_ref.downcast_ref::<SerdeYaml>().is_some());
Source

pub fn file_ext(&self) -> &OsStr

Returns the file extension used by self to write and read files.

This function is a shorthand for dbm.data_format().file_ext().

Source

pub fn checksum<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<u32>

Returns the checksum of a database file specified by the given key. If the file doesn’t exist, this function returns None.

Source

pub fn remove_empty_subfolders(&mut self) -> Result<()>

Removes all empty subfolders within the database path self.dir().

Be aware that the DatabaseManager doesn’t know which folders belong to the database and which folders do not. For example, the following snippet would remove an empty folder /path/to/db/foo, even though it wasn’t created by the database manager:

use std::path::PathBuf;
use serde_mosaic::*;

let unrelated_dir = PathBuf::from("/path/to/db/foo");

assert!(unrelated_dir.exists());
assert!(unrelated_dir.read_dir().expect("read permissions available").next().is_none());

let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");

assert!(unrelated_dir.exists());
assert!(unrelated_dir.read_dir().expect("read permissions available").next().is_none());

dbm.remove_empty_subfolders();

assert!(!unrelated_dir.exists());
Source

pub fn remove<'a, T: Into<DatabaseKey<'a>>>(&mut self, key: T) -> Result<()>

Tries to remove the specified database file from the database.

This function essentially derives the file path from the given key with DatabaseManager::full_path and then tries to delete the file. If the file doesn’t exist or can’t be removed, this function returns an error.

Be aware that the DatabaseManager does not know which files “belong” to the database - if a file fitting the naming scheme has been created in an unrelated way, it will still be removed.

Source

pub fn remove_all<O: AsRef<OsStr>>(&mut self, name: O) -> Result<()>

Searches through all direct subfolders (non-recursively) of self.dir() and removes all files with the given file name whose file extension matches that of self.file_ext. Similar to DatabaseManager::remove, this function does not discriminate between files which were created by self and files which were created by something else.

Source

pub fn exists<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> bool

Checks if the database has an entry for the given key.

Under the hood, this function calls self.full_path(key).is_some().

Source

pub fn full_path<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<PathBuf>

Returns the full path of the database entry specified by key, if the entry exist. If not, returns None.

Source

pub fn cache(&self) -> &Cache

Returns a reference to the Cache used within self.

Source

pub fn cache_mut(&mut self) -> &mut Cache

Returns a mutable reference to the Cache used within self. This can be used to manually add entries to the Cache. See the docstrings of Cache and CacheEntry.

Source

pub fn write<T: DatabaseEntry>( &mut self, instance: &T, write_options: &WriteOptions, ) -> Result<PathBuf>

Serializes the given instance into the database according to the given WriteOptions. If successfull, the path to the written file is returned.

This is the central function to store new entries within the database. As outlined in the docstring of DatabaseManager, calling this function can actually result in multiple files being written, if instance is composed of other DatabaseEntry implementor instances which are annotated with one of the “link” attributes for serialization (depending on the WriteMode of WriteOptions). Using serialization functions from other packages (as e.g. serde_yaml::to_string) bypasses the entire linking machinery of this crate and just creates the expected serialized representations.

Source

pub fn write_verbose<T: DatabaseEntry>( &mut self, instance: &T, write_options: &WriteOptions, ) -> Result<(PathBuf, WriteInfo)>

Like DatabaseManager::write, but returns additional WriteInfo in case writing to the database was successfull.

The WriteInfo contains the following information:

  • Which files were created new.
  • Which existing files have been overwritten.

These results heavily depend on the settings within WriteOptions, see its docstring for more.

Source

pub fn read<T: DatabaseEntry, O: AsRef<OsStr>>(&mut self, name: O) -> Result<T>

Deserializes an instance of T stored within the file with the given name from the database and returns it.

This function first derives the full file path name by concatenating self.dir(), the name of T (see type_name) and by combining name and self.file_ext to the file name. If this file exists, its content is then deserialized using Format::deserialize_dyn of self.data_format(). Any encountered links are resolved by reading the corresponding files and storing the resulting object within the created T instance.

Like DatabaseManager::write, using this function is mandatory in order to read files with links in them. Using serialization functions from other packages (as e.g. serde_yaml::from_str) bypasses the entire linking machinery of this crate and will result in failure if any links are stored within the files.

Source

pub fn read_verbose<T: DatabaseEntry, O: AsRef<OsStr>>( &mut self, name: O, ) -> Result<(T, ReadInfo)>

Like DatabaseManager::read, but returns additional ReadInfo in case reading from the database was successfull.

The ReadInfo contains all ChecksumMismatches which happened when a link contained a checksum which didn’t match the linked file. If such a mismatch occurs, the file is still read and its contents are deserialized and replace the link regardless. Therefore, this information is useful to check if a linked file was changed since the creation of the link (e.g. in order to determine whether the returned instance of T should be used or not).

Source

pub fn from_str<T: DeserializeOwned + 'static, F: Format>( &mut self, str: impl AsRef<str>, ) -> Result<T>

Deserializes the given string using Format::deserialize from self.data_format() and resolves any encountered links using the underlying database.

This function behaves similarily to DatabaseManager::read, except that the starting point is not a file from the database, but str instead. Because the Format is stored as a trait object inside self, it needs to be downcasted into its concrete type F inside this function. Specifying the wrong type will result in an error.

§Examples
use std::ffi::OsStr;
use std::sync::Arc;

use serde::{Serialize, Deserialize};
use serde_mosaic::*;

#[derive(Serialize, Deserialize, Clone)]
struct Material {
    name: String,
    cotton_content: f64,
}

#[typetag::serde]
impl DatabaseEntry for Material {
    fn name(&self) -> &OsStr {
        self.name.as_ref()
    }
}

#[derive(Deserialize)]
struct Shirt {
    owner: String,
    #[serde(deserialize_with = "deserialize_arc_link")]
    #[serde(serialize_with = "serialize_arc_link")]
    material: Arc<Material>,
    size: usize
}

let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists");

let shirt_str = indoc::indoc! {"
---
owner: Sven
material:
  name: pure_cotton
size: 46
"};

let shirt = dbm.from_str::<Shirt, SerdeYaml>(&shirt_str).unwrap();
assert_eq!(shirt.material.name, "pure_cotton");

Trait Implementations§

Source§

impl Clone for DatabaseManager

Source§

fn clone(&self) -> DatabaseManager

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl From<DatabaseManager> for Box<dyn Format>

Source§

fn from(value: DatabaseManager) -> Self

Converts to this type from the input type.
Source§

impl From<DatabaseManager> for Cache

Source§

fn from(value: DatabaseManager) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.