This repository has been archived by the owner on Sep 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Introduce Importable/Exportable Storage traits.
- Loading branch information
Showing
7 changed files
with
217 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
use crate::{Storage, Store, SPHERE_DB_STORE_NAMES}; | ||
use anyhow::Result; | ||
use async_trait::async_trait; | ||
use noosphere_common::ConditionalSync; | ||
use std::pin::Pin; | ||
use tokio_stream::{Stream, StreamExt}; | ||
|
||
#[cfg(target_arch = "wasm32")] | ||
pub type IterableStream<'a> = Pin<Box<dyn Stream<Item = Result<(Vec<u8>, Option<Vec<u8>>)>> + 'a>>; | ||
#[cfg(not(target_arch = "wasm32"))] | ||
pub type IterableStream<'a> = | ||
Pin<Box<dyn Stream<Item = Result<(Vec<u8>, Option<Vec<u8>>)>> + Send + 'a>>; | ||
|
||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
pub trait Iterable: Store { | ||
fn get_all_entries<'a>(&'a self) -> IterableStream<'a>; | ||
} | ||
|
||
/// A blanket implementation for [Storage]s that can be | ||
/// imported via [Importable::import]. | ||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
pub trait Exportable | ||
where | ||
Self: Storage, | ||
<Self as Storage>::KeyValueStore: Iterable, | ||
{ | ||
async fn get_all_store_names(&self) -> Result<Vec<String>> { | ||
let mut names = vec![]; | ||
names.extend(SPHERE_DB_STORE_NAMES.iter().map(|name| String::from(*name))); | ||
Ok(names) | ||
} | ||
} | ||
|
||
impl<S> Exportable for S | ||
where | ||
S: Storage, | ||
S::KeyValueStore: Iterable, | ||
{ | ||
} | ||
|
||
/// A blanket implementation for all [Storage]s to import | ||
/// an [Exportable] storage. | ||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
pub trait Importable<'a, E> | ||
where | ||
Self: Storage, | ||
Self::KeyValueStore: Store, | ||
E: Exportable + ConditionalSync + 'a, | ||
<E as Storage>::KeyValueStore: Iterable, | ||
{ | ||
async fn import(&'a mut self, exportable: E) -> Result<()> { | ||
for store_name in exportable.get_all_store_names().await? { | ||
let mut store = self.get_key_value_store(&store_name).await?; | ||
let export_store = exportable.get_key_value_store(&store_name).await?; | ||
{ | ||
let mut stream = export_store.get_all_entries(); | ||
while let Some((key, value)) = stream.try_next().await? { | ||
if let Some(value) = value { | ||
Store::write(&mut store, key.as_ref(), value.as_ref()).await?; | ||
} | ||
} | ||
} | ||
drop(export_store) | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
impl<'a, T, E> Importable<'a, E> for T | ||
where | ||
T: Storage, | ||
T::KeyValueStore: Store, | ||
E: Exportable + ConditionalSync + 'a, | ||
<E as Storage>::KeyValueStore: Iterable, | ||
{ | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use std::path::PathBuf; | ||
|
||
#[cfg(target_arch = "wasm32")] | ||
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; | ||
#[cfg(target_arch = "wasm32")] | ||
wasm_bindgen_test_configure!(run_in_browser); | ||
|
||
struct TempPath { | ||
#[cfg(target_arch = "wasm32")] | ||
pub path: String, | ||
#[cfg(not(target_arch = "wasm32"))] | ||
pub path: PathBuf, | ||
#[cfg(not(target_arch = "wasm32"))] | ||
#[allow(unused)] | ||
temp_dir: tempfile::TempDir, | ||
} | ||
|
||
impl TempPath { | ||
pub fn new() -> Result<Self> { | ||
#[cfg(target_arch = "wasm32")] | ||
let path: String = witty_phrase_generator::WPGen::new() | ||
.with_words(3) | ||
.unwrap() | ||
.into_iter() | ||
.map(|word| String::from(word)) | ||
.collect(); | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
let temp_dir = tempfile::TempDir::new()?; | ||
#[cfg(not(target_arch = "wasm32"))] | ||
let path = temp_dir.path().to_owned(); | ||
|
||
#[cfg(target_arch = "wasm32")] | ||
let obj = Self { path }; | ||
#[cfg(not(target_arch = "wasm32"))] | ||
let obj = Self { temp_dir, path }; | ||
|
||
Ok(obj) | ||
} | ||
} | ||
|
||
/// wasm32: MemoryStorage -> IndexedDbStorage | ||
/// native: SledStorage -> MemoryStorage | ||
/// native+rocks: SledStorage -> RocksDbStorage | ||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] | ||
pub async fn it_can_migrate_to_new_storage_backend() -> Result<()> { | ||
noosphere_core::tracing::initialize_tracing(None); | ||
|
||
#[allow(unused)] | ||
let from_temp_path = TempPath::new()?; | ||
#[allow(unused)] | ||
let to_temp_path = TempPath::new()?; | ||
|
||
#[cfg(target_arch = "wasm32")] | ||
let from_storage = crate::MemoryStorage::default(); | ||
#[cfg(not(target_arch = "wasm32"))] | ||
let from_storage = crate::SledStorage::new(from_temp_path.path.clone())?; | ||
|
||
#[cfg(target_arch = "wasm32")] | ||
let mut to_storage = crate::IndexedDbStorage::new(&to_temp_path.path).await?; | ||
#[cfg(all(feature = "rocksdb", not(target_arch = "wasm32")))] | ||
let mut to_storage = crate::RocksDbStorage::new(to_temp_path.path.clone())?; | ||
#[cfg(all(not(feature = "rocksdb"), not(target_arch = "wasm32")))] | ||
let mut to_storage = crate::MemoryStorage::default(); | ||
|
||
{ | ||
let mut store = from_storage.get_key_value_store("links").await?; | ||
for n in 0..10 { | ||
let bytes = vec![n; 10]; | ||
store.write(&[n], bytes.as_ref()).await?; | ||
} | ||
} | ||
|
||
to_storage.import(from_storage).await?; | ||
|
||
{ | ||
let store = to_storage.get_key_value_store("links").await?; | ||
for n in 0..10 { | ||
let expected_bytes = vec![n; 10]; | ||
|
||
if let Some(bytes) = store.read(&[n]).await? { | ||
assert_eq!(bytes, expected_bytes); | ||
} else { | ||
panic!("Expected key `{n}` to exist in new db"); | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters