diff --git a/src/database/abstraction.rs b/src/database/abstraction.rs index 5832c944..4d9da37d 100644 --- a/src/database/abstraction.rs +++ b/src/database/abstraction.rs @@ -26,6 +26,8 @@ pub mod persy; ))] pub mod watchers; +pub mod json_stream_export; + #[cfg(test)] mod tests; diff --git a/src/database/abstraction/json_stream_export.rs b/src/database/abstraction/json_stream_export.rs new file mode 100644 index 00000000..a37f31e6 --- /dev/null +++ b/src/database/abstraction/json_stream_export.rs @@ -0,0 +1,77 @@ +use crate::{ + database::abstraction::{KeyValueDatabaseEngine, KvExport}, + Result, +}; +use base64::{engine::general_purpose::STANDARD, Engine}; +use serde::{Deserialize, Serialize}; +use std::io::{BufRead, BufReader, Read, Write}; + +pub trait KeyValueJsonExporter { + fn export_json_stream(&self, output: &mut dyn Write) -> Result<()>; + fn import_json_stream(&self, input: &mut dyn Read) -> Result<()>; +} + +#[derive(Serialize, Deserialize)] +struct Entry { + tree: String, + key: String, + value: String, +} + +struct JsonExporter<'a> { + current_name: String, + write: &'a mut dyn Write, +} +impl<'a> KvExport for JsonExporter<'a> { + fn start_tree(&mut self, name: &str) -> Result<()> { + self.current_name = name.to_owned(); + Ok(()) + } + + fn key_value(&mut self, key: &[u8], value: &[u8]) -> Result<()> { + let entry = Entry { + tree: self.current_name.clone(), + key: STANDARD.encode(key), + value: STANDARD.encode(value), + }; + writeln!(self.write, "{}", serde_json::to_string(&entry).unwrap())?; + Ok(()) + } + + fn end_tree(&mut self, _name: &str) -> Result<()> { + Ok(()) + } +} + +impl KeyValueJsonExporter for T { + fn export_json_stream(&self, output: &mut dyn Write) -> Result<()> { + self.export(&mut JsonExporter { + current_name: Default::default(), + write: output, + })?; + Ok(()) + } + fn import_json_stream(&self, input: &mut dyn Read) -> Result<()> { + let bf = BufReader::new(input); + //Just a cache to avoid to reopen the tree all the times + let mut cur_tree = None; + for line in bf.lines() { + if let Ok(entry) = serde_json::from_str::(&line?) { + if let (Ok(key), Ok(value)) = + (STANDARD.decode(&entry.key), STANDARD.decode(&entry.value)) + { + let (tree, tree_name) = match cur_tree { + Some((tree, tree_name)) if tree_name == entry.tree => (tree, tree_name), + _ => { + let tree = self.open_tree(&entry.tree)?; + (tree, entry.tree.clone()) + } + }; + tree.insert(&key, &value)?; + cur_tree = Some((tree, tree_name)); + } + } + } + Ok(()) + } +} diff --git a/src/database/abstraction/tests.rs b/src/database/abstraction/tests.rs index c901a44b..da9a6de4 100644 --- a/src/database/abstraction/tests.rs +++ b/src/database/abstraction/tests.rs @@ -1,5 +1,7 @@ use crate::database::{ - abstraction::{KeyValueDatabaseEngine, KvExport, KvTree}, + abstraction::{ + json_stream_export::KeyValueJsonExporter, KeyValueDatabaseEngine, KvExport, KvTree, + }, Config, }; use std::sync::Arc; @@ -325,6 +327,23 @@ where check_data(&instance_r, "two"); } +fn test_export_import_json(test_name: &str) +where + Arc: KeyValueDatabaseEngine, +{ + let (instance, _db_folder) = open_instance(test_name); + insert_data(&instance, "one"); + insert_data(&instance, "two"); + let mut buffer = Vec::new(); + instance.export_json_stream(&mut buffer).unwrap(); + let (instance_r, _db_folder) = open_instance(&format!("{}_restore", test_name)); + instance_r + .import_json_stream(&mut std::io::Cursor::new(buffer)) + .unwrap(); + check_data(&instance_r, "one"); + check_data(&instance_r, "two"); +} + #[cfg(feature = "sqlite")] mod sqlite { @@ -384,6 +403,10 @@ mod sqlite { fn sqlite_export_import() { test_export_import::("sqlite_export_import") } + #[test] + fn sqlite_export_import_json() { + test_export_import_json::("sqlite_export_import_json") + } } #[cfg(feature = "rocksdb")] @@ -446,6 +469,10 @@ mod rocksdb { fn rocksdb_export_import() { test_export_import::("rocksdb_export_import") } + #[test] + fn rocksdb_export_import_json() { + test_export_import_json::("rocksdb_export_import_json") + } } #[cfg(feature = "persy")] mod persy { @@ -507,4 +534,9 @@ mod persy { fn persy_export_import() { test_export_import::("persy_export_import") } + + #[test] + fn persy_export_import_json() { + test_export_import_json::("persy_export_import_json") + } }