simuldb/db/
mod.rs

1//! Database backends
2//!
3//! This module contains both database backends and generic traits to represent them.
4
5use std::{
6    fmt::Debug,
7    fs::{create_dir_all, File},
8    io::{Seek, Write},
9    path::{Path, PathBuf},
10};
11
12use log::info;
13use serde::Serialize;
14use uuid::Uuid;
15
16use crate::{Dataset, Host, Session};
17
18#[cfg(feature = "json")]
19pub mod json;
20#[cfg(feature = "neo4j")]
21pub mod neo4j;
22
23/// Database backend
24///
25/// Storage backend for the database.
26// TODO allow deletion of sessions
27pub trait Database: Debug {
28    type Error: std::error::Error;
29
30    /// Add a session to the database
31    fn add_session(
32        &mut self,
33        session: Session,
34    ) -> Result<Box<dyn DatabaseSession<Error = Self::Error>>, Self::Error>;
35
36    /// Add a session to the database
37    fn remove_session(&mut self, session: Session) -> Result<(), Self::Error>;
38
39    /// Get an interation over sessions in the database
40    #[allow(clippy::type_complexity)]
41    fn get_sessions(
42        &self,
43    ) -> Result<Vec<Box<dyn DatabaseSession<Error = Self::Error>>>, Self::Error>;
44}
45
46impl<E> Database for Box<dyn Database<Error = E>>
47where
48    E: std::error::Error,
49{
50    type Error = E;
51
52    fn add_session(
53        &mut self,
54        session: Session,
55    ) -> Result<Box<dyn DatabaseSession<Error = Self::Error>>, Self::Error> {
56        (**self).add_session(session)
57    }
58
59    fn remove_session(&mut self, session: Session) -> Result<(), Self::Error> {
60        (**self).remove_session(session)
61    }
62
63    fn get_sessions(
64        &self,
65    ) -> Result<Vec<Box<dyn DatabaseSession<Error = Self::Error>>>, Self::Error> {
66        (**self).get_sessions()
67    }
68}
69
70/// Database session
71///
72/// This represents access to a single session stored in the database
73// TODO allow deletions of datasets
74pub trait DatabaseSession: Debug {
75    type Error: std::error::Error;
76
77    /// Get session information
78    fn session(&self) -> &Session;
79
80    /// Add a dataset to the database
81    fn add_dataset(&mut self, dataset: &Dataset) -> Result<(), Self::Error>;
82
83    /// Get iterator over datasets in the session
84    fn get_datasets(&self) -> Result<Vec<Dataset>, Self::Error>;
85
86    /// Add a dataset to the database
87    fn remove_dataset(&mut self, id: &Uuid) -> Result<(), Self::Error>;
88}
89
90/// Wrapper for easier [Dataset] generation
91///
92/// This type can be used to write data to a file and then automatically generate an id and calculate a hash for the file.
93/// The hash algorithm used is SHA-512 (see also [Dataset::from_file]).
94///
95/// This type implements [Write] and [Seek] and therefor can be used together with things like [serde_json::to_writer] or similar functions.
96///
97/// # Example
98/// ```
99/// # use simuldb::prelude::*;
100/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
101/// #     std::env::set_current_dir(format!("{}/..", env!("CARGO_MANIFEST_DIR")))?; // change to top level directory
102/// #     let mut json = Json::new("output/json");
103/// #     let software = Software::new("example", "1.0", "now");
104/// #     let run = Run::new("now");
105/// #     let session = Session::new(software, run);
106/// #     let mut dbsession = json.add_session(session)?;
107/// let mut writer = DatasetWriter::new("output/data")?;
108/// serde_json::to_writer(&mut writer, &[0, 1, 2])?;
109/// let dataset = writer.finalize(())?;
110/// dbsession.add_dataset(&dataset)?;
111/// #     Ok(())
112/// # }
113/// ```
114#[cfg(feature = "sha")]
115pub struct DatasetWriter {
116    id: Uuid,
117    file: File,
118    path: PathBuf,
119}
120
121#[cfg(feature = "sha")]
122impl DatasetWriter {
123    /// Create new writer
124    ///
125    /// This generates an id uses this as a filename to create a file in `folder`.
126    /// If the path did not exist before, parent folders are created.
127    pub fn new<P: AsRef<Path>>(folder: P) -> Result<Self, std::io::Error> {
128        let mut path: PathBuf = folder.as_ref().into();
129        let id = Uuid::new_v4();
130        create_dir_all(&path)?;
131        path.push(format!("{id}"));
132        let file = File::create(&path)?;
133        Ok(Self { id, file, path })
134    }
135
136    /// Generate [Dataset]
137    ///
138    /// This uses [Dataset::from_file] to calculate a SHA-512 hash and generate a [Dataset].
139    ///
140    /// # Arguments
141    ///
142    /// * `metadata`: associated metadata, has to serializable to a [Map](crate::value::Value::Map)
143    pub fn finalize<S: Serialize + Debug>(
144        self,
145        metadata: S,
146    ) -> Result<Dataset, crate::error::Error> {
147        Dataset::from_file(self.path, metadata, Some(self.id))
148    }
149
150    /// Generate [Dataset]
151    ///
152    /// This uses [Dataset::from_file] to calculate a SHA-512 hash and generate a [Dataset].
153    ///
154    /// # Arguments
155    ///
156    /// * `metadata`: associated metadata, has to serializable to a [Map](crate::value::Value::Map)
157    pub fn finalize_with_host<S: Serialize + Debug>(
158        self,
159        metadata: S,
160        host: Option<Host>,
161    ) -> Result<Dataset, crate::error::Error> {
162        Dataset::from_file_with_host(self.path, metadata, Some(self.id), host)
163    }
164}
165
166#[cfg(feature = "sha")]
167impl Write for DatasetWriter {
168    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
169        self.file.write(buf)
170    }
171
172    fn flush(&mut self) -> std::io::Result<()> {
173        self.file.flush()
174    }
175}
176
177#[cfg(feature = "sha")]
178impl Seek for DatasetWriter {
179    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
180        self.file.seek(pos)
181    }
182}
183
184/// Transfer data from one [Database] to another
185///
186/// This function iterates over all sessions and the contained [Dataset]s and transfers the information.
187/// No data is cleared, so if the target session was not empty before, the contents will differ.
188pub fn transfer<A: Database + Debug, B: Database + Debug>(
189    a: &A,
190    b: &mut B,
191) -> Result<(), Box<dyn std::error::Error>>
192where
193    A::Error: 'static,
194    B::Error: 'static,
195{
196    info!("Transfering from {a:?} to {b:?}");
197    for session in a.get_sessions()? {
198        info!("Transfering session {:?}", session.session());
199        let mut session_b = b.add_session(session.session().clone())?;
200        for dataset in session.get_datasets()? {
201            info!("Transfering dataset {dataset:?}");
202            session_b.add_dataset(&dataset)?;
203        }
204    }
205    Ok(())
206}