pub mod redb_storage_backend;
use super::resource_handler::MultiResourceReader;
use crate::QueryableValue;
use crate::{
asset::ResourceId, model::ArchivedQueryableValue, ArchivedSerializableResource, ProcessedAsset, SerializableResource,
};
#[derive(
Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
)]
pub struct QueryCursor {
pub(crate) token: Vec<u8>,
}
impl QueryCursor {
pub fn new(token: Vec<u8>) -> Self {
Self { token }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum QueryPredicate {
Eq { property: String, value: QueryableValue },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Query {
pub class: String,
pub predicates: Vec<QueryPredicate>,
pub limit: usize,
pub cursor: Option<QueryCursor>,
}
impl Query {
pub fn new(class: &str) -> Self {
Self {
class: class.to_string(),
predicates: Vec::new(),
limit: usize::MAX,
cursor: None,
}
}
pub fn eq(mut self, property: &str, value: &str) -> Self {
self.predicates.push(QueryPredicate::Eq {
property: property.to_string(),
value: QueryableValue::String(value.to_string()),
});
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
pub fn cursor(mut self, cursor: QueryCursor) -> Self {
self.cursor = Some(cursor);
self
}
pub fn matches(&self, resource: &SerializableResource, properties: &[crate::QueryableProperty]) -> bool {
if resource.class != self.class {
return false;
}
self.predicates.iter().all(|predicate| match predicate {
QueryPredicate::Eq { property, value } => properties
.iter()
.any(|candidate| candidate.name == *property && &candidate.value == value),
})
}
pub fn matches_archived(&self, resource: &ArchivedSerializableResource) -> bool {
if resource.class.as_str() != self.class {
return false;
}
self.predicates.iter().all(|predicate| match predicate {
QueryPredicate::Eq { property, value } => resource.queryable_properties.iter().any(|candidate| {
candidate.name.as_str() == property
&& match (&candidate.value, value) {
(ArchivedQueryableValue::String(candidate), QueryableValue::String(value)) => {
candidate.as_str() == value
}
}
}),
})
}
pub fn first_indexed_predicate(&self) -> Option<(&str, &QueryableValue)> {
self.predicates.first().map(|predicate| match predicate {
QueryPredicate::Eq { property, value } => (property.as_str(), value),
})
}
}
#[derive(Debug)]
pub struct QueryPage<T> {
pub items: Vec<T>,
pub cursor: Option<QueryCursor>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryError {
InvalidCursor,
StorageFailure,
}
pub trait ReadStorageBackend: Sync + Send + downcast_rs::Downcast {
fn list(&self) -> Result<Vec<String>, String>;
fn read<'s, 'a, 'b>(&'s self, id: ResourceId<'b>) -> Option<(SerializableResource, MultiResourceReader)>;
fn query(&self, query: Query) -> Result<QueryPage<(SerializableResource, MultiResourceReader)>, QueryError>;
#[cfg(debug_assertions)]
fn read_trace(&self, _: ResourceId<'_>) -> Result<Vec<crate::ResourceTraceItem>, String> {
Ok(Vec::new())
}
fn get_type<'a>(&'a self, url: ResourceId<'a>) -> Option<&'a str> {
Some(url.get_extension())
}
fn exists<'a>(&'a self, id: ResourceId<'a>) -> bool {
self.read(id).is_some()
}
}
pub trait WriteStorageBackend: Sync + Send + downcast_rs::Downcast {
fn delete<'a>(&'a self, id: ResourceId<'a>) -> Result<(), String>;
fn store(&self, resource: ProcessedAsset, data: &[u8]) -> Result<SerializableResource, ()> {
self.store_in(resource, data, &std::alloc::Global)
}
fn store_in(
&self,
resource: ProcessedAsset,
data: &[u8],
allocator: &dyn std::alloc::Allocator,
) -> Result<SerializableResource, ()>;
fn sync(&self, _: &dyn ReadStorageBackend) {}
#[cfg(debug_assertions)]
fn replace_trace(&self, _: ResourceId<'_>, _: &[crate::ResourceTraceItem]) -> Result<(), String> {
Ok(())
}
fn start(&self, _: ResourceId<'_>) {}
}
downcast_rs::impl_downcast!(ReadStorageBackend);
downcast_rs::impl_downcast!(WriteStorageBackend);
pub trait StorageBackend: ReadStorageBackend + WriteStorageBackend {}
downcast_rs::impl_downcast!(StorageBackend);
#[cfg(test)]
pub mod tests {
use std::{hash::Hasher, sync::Arc};
use gxhash::HashMapExt;
use utils::{hash::HashMap, sync::Mutex};
use super::*;
use crate::resource::resource_handler::tests::MemoryResourceReader;
#[derive(Clone)]
pub struct TestStorageBackend {
resources: Arc<Mutex<HashMap<String, (Box<[u8]>, Box<[u8]>)>>>,
#[cfg(debug_assertions)]
traces: Arc<Mutex<HashMap<String, Vec<crate::ResourceTraceItem>>>>,
}
impl TestStorageBackend {
pub fn new() -> Self {
Self {
resources: Arc::new(Mutex::new(HashMap::new())),
#[cfg(debug_assertions)]
traces: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn get_resources(&self) -> Vec<ProcessedAsset> {
self.resources
.lock()
.iter()
.map(|x| {
let resource: SerializableResource = crate::from_slice(&x.1 .0).unwrap();
ProcessedAsset {
id: resource.id,
class: resource.class,
resource: resource.resource,
streams: resource.streams,
queryable_properties: resource.queryable_properties,
}
})
.collect()
}
pub fn get_resource(&self, name: ResourceId<'_>) -> Option<ProcessedAsset> {
self.resources
.lock()
.iter()
.find(|x| {
let resource: SerializableResource = crate::from_slice(&x.1 .0).unwrap();
resource.id == name.as_ref()
})
.map(|x| {
let resource: SerializableResource = crate::from_slice(&x.1 .0).unwrap();
ProcessedAsset {
id: resource.id,
class: resource.class,
resource: resource.resource,
streams: resource.streams,
queryable_properties: resource.queryable_properties,
}
})
}
pub fn get_resource_data_by_name(&self, name: ResourceId<'_>) -> Option<Box<[u8]>> {
Some(
self.resources
.lock()
.iter()
.find(|x| {
let resource: SerializableResource = crate::from_slice(&x.1 .0).unwrap();
resource.id == name.as_ref()
})?
.1
.1
.clone(),
)
}
}
impl ReadStorageBackend for TestStorageBackend {
fn list<'a>(&'a self) -> Result<Vec<String>, String> {
Ok(self.resources.lock().keys().map(|x| x.to_string()).collect())
}
fn read<'s, 'a, 'b>(&'s self, id: ResourceId<'b>) -> Option<(SerializableResource, MultiResourceReader)> {
let (resource, data) = if let Some(e) = self.resources.lock().get(id.as_ref()) {
(e.0.clone(), e.1.clone())
} else {
return None;
};
let _ = id.get_base().to_string();
let resource: SerializableResource = crate::from_slice(&resource).unwrap();
let resource_reader = Box::new(MemoryResourceReader::new(data));
Some((resource, resource_reader))
}
fn query(&self, _: Query) -> Result<QueryPage<(SerializableResource, MultiResourceReader)>, QueryError> {
Err(QueryError::StorageFailure)
}
#[cfg(debug_assertions)]
fn read_trace(&self, id: ResourceId<'_>) -> Result<Vec<crate::ResourceTraceItem>, String> {
Ok(self.traces.lock().get(id.as_ref()).cloned().unwrap_or_default())
}
}
impl WriteStorageBackend for TestStorageBackend {
fn delete<'a>(&'a self, id: ResourceId<'a>) -> Result<(), String> {
self.resources.lock().remove(id.as_ref());
#[cfg(debug_assertions)]
self.traces.lock().remove(id.as_ref());
Ok(())
}
fn store_in(
&self,
resource: ProcessedAsset,
data: &[u8],
allocator: &dyn std::alloc::Allocator,
) -> Result<SerializableResource, ()> {
let id = resource.id.clone();
let size = data.len();
let hash = {
let mut hasher = gxhash::GxHasher::with_seed(961961961961961);
std::hash::Hasher::write(&mut hasher, data);
hasher.finish()
};
let container = resource.into_serializable(hash, size);
let serialized_container = crate::to_vec_in(&container, allocator).unwrap();
self.resources
.lock()
.insert(id, (serialized_container.to_vec().into(), data.into()));
Ok(container)
}
#[cfg(debug_assertions)]
fn replace_trace(&self, id: ResourceId<'_>, items: &[crate::ResourceTraceItem]) -> Result<(), String> {
let mut traces = self.traces.lock();
if items.is_empty() {
traces.remove(id.as_ref());
} else {
traces.insert(id.to_string(), items.to_vec());
}
Ok(())
}
fn sync<'s, 'a>(&'s self, _: &'a dyn ReadStorageBackend) -> () {
{}
}
}
impl StorageBackend for TestStorageBackend {}
}