use thiserror::Error;
use std::fmt;
pub use self::extensions::{AccessExt, CopyAccessExt};
pub use crate::views::{AsReadonly, RawAccess, RawAccessMut};
use crate::{
validation::assert_valid_name_component,
views::{GroupKeys, IndexAddress, IndexMetadata, IndexType, ViewWithMetadata},
BinaryKey,
};
mod extensions;
pub trait Access: Clone {
type Base: RawAccess;
fn get_index_metadata(self, addr: IndexAddress) -> Result<Option<IndexMetadata>, AccessError>;
fn get_or_create_view(
self,
addr: IndexAddress,
index_type: IndexType,
) -> Result<ViewWithMetadata<Self::Base>, AccessError>;
fn group_keys<K>(self, base_addr: IndexAddress) -> GroupKeys<Self::Base, K>
where
K: BinaryKey + ?Sized,
Self::Base: AsReadonly<Readonly = Self::Base>;
}
impl<T: RawAccess> Access for T {
type Base = Self;
fn get_index_metadata(self, addr: IndexAddress) -> Result<Option<IndexMetadata>, AccessError> {
ViewWithMetadata::get_metadata(self, &addr)
}
fn get_or_create_view(
self,
addr: IndexAddress,
index_type: IndexType,
) -> Result<ViewWithMetadata<Self::Base>, AccessError> {
ViewWithMetadata::get_or_create(self, &addr, index_type)
}
fn group_keys<K>(self, base_addr: IndexAddress) -> GroupKeys<Self::Base, K>
where
K: BinaryKey + ?Sized,
Self::Base: AsReadonly<Readonly = Self::Base>,
{
GroupKeys::new(self, &base_addr)
}
}
#[derive(Debug, Clone)]
pub struct Prefixed<T> {
access: T,
prefix: String,
}
impl<T> Prefixed<T> {
pub(crate) fn access(&self) -> &T {
&self.access
}
pub(crate) fn into_parts(self) -> (String, T) {
(self.prefix, self.access)
}
}
impl<T: RawAccess> Prefixed<T> {
pub fn new(prefix: impl Into<String>, access: T) -> Self {
let prefix = prefix.into();
assert_valid_name_component(prefix.as_ref());
Self { access, prefix }
}
}
impl<T: RawAccess> Access for Prefixed<T> {
type Base = T;
fn get_index_metadata(self, addr: IndexAddress) -> Result<Option<IndexMetadata>, AccessError> {
let prefixed_addr = addr.prepend_name(self.prefix.as_ref());
self.access.get_index_metadata(prefixed_addr)
}
fn get_or_create_view(
self,
addr: IndexAddress,
index_type: IndexType,
) -> Result<ViewWithMetadata<Self::Base>, AccessError> {
let prefixed_addr = addr.prepend_name(self.prefix.as_ref());
self.access.get_or_create_view(prefixed_addr, index_type)
}
fn group_keys<K>(self, base_addr: IndexAddress) -> GroupKeys<Self::Base, K>
where
K: BinaryKey + ?Sized,
Self::Base: AsReadonly<Readonly = Self::Base>,
{
let prefixed_addr = base_addr.prepend_name(self.prefix.as_ref());
self.access.group_keys(prefixed_addr)
}
}
#[derive(Debug, Error)]
pub struct AccessError {
pub addr: IndexAddress,
#[source]
pub kind: AccessErrorKind,
}
impl fmt::Display for AccessError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Error accessing {:?}: {}", self.addr, self.kind)
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AccessErrorKind {
#[error("Wrong index type: expected {:?}, but got {:?}", expected, actual)]
WrongIndexType {
expected: IndexType,
actual: IndexType,
},
#[error("Index name is reserved")]
ReservedName,
#[error("Index name must not be empty")]
EmptyName,
#[error("Invalid characters used in name ({}). Use {}", name, allowed_chars)]
InvalidCharsInName {
name: String,
allowed_chars: &'static str,
},
#[error("Invalid tombstone location. Tombstones can only be created in migrations")]
InvalidTombstone,
#[error("{0}")]
Custom(#[source] anyhow::Error),
}
pub trait FromAccess<T: Access>: Sized {
fn from_access(access: T, addr: IndexAddress) -> Result<Self, AccessError>;
fn from_root(access: T) -> Result<Self, AccessError> {
Self::from_access(access, IndexAddress::default())
}
}
#[cfg(test)]
mod tests {
use super::{Access, AccessExt, CopyAccessExt, FromAccess, IndexType, Prefixed};
use crate::{Database, ListIndex, TemporaryDB};
#[test]
fn prefixed_works() {
let db = TemporaryDB::new();
let fork = db.fork();
{
let prefixed = Prefixed::new("test", &fork);
let mut list = prefixed.get_list::<_, i32>("foo");
list.extend(vec![1, 2, 3]);
}
{
let list = fork.get_list::<_, i32>("test.foo");
assert_eq!(list.len(), 3);
assert_eq!(list.iter().collect::<Vec<_>>(), vec![1, 2, 3]);
}
db.merge_sync(fork.into_patch()).unwrap();
let snapshot = db.snapshot();
let list = snapshot.get_list::<_, i32>("test.foo");
assert_eq!(list.len(), 3);
assert_eq!(list.iter().collect::<Vec<_>>(), vec![1, 2, 3]);
let prefixed = Prefixed::new("test", &snapshot);
let list = prefixed.get_list::<_, i32>("foo");
assert_eq!(list.len(), 3);
assert_eq!(list.iter().collect::<Vec<_>>(), vec![1, 2, 3]);
}
#[test]
fn prefixed_views_do_not_collide() {
let db = TemporaryDB::new();
let fork = db.fork();
let foo_space = Prefixed::new("foo", &fork);
let bar_space = Prefixed::new("bar", &fork);
{
let mut list = foo_space.get_list("test");
list.push("Test".to_owned());
let mut other_list = bar_space.get_list("test");
other_list.extend(vec![1_u64, 2, 3]);
assert_eq!(list.len(), 1);
assert_eq!(other_list.len(), 3);
}
db.merge_sync(fork.into_patch()).unwrap();
let snapshot = db.snapshot();
let foo_space = Prefixed::new("foo", &snapshot);
let list = foo_space.get_list::<_, String>("test");
assert_eq!(list.get(0), Some("Test".to_owned()));
let bar_space = Prefixed::new("bar", &snapshot);
let list = bar_space.get_list::<_, u64>("test");
assert_eq!(list.get(0), Some(1_u64));
let fork = db.fork();
let foo_space = Prefixed::new("foo", &fork);
foo_space.get_list::<_, u32>(("fam", &1_u32));
let bar_space = Prefixed::new("bar", &fork);
bar_space.get_map::<_, u32, u32>(("fam", &1_u32));
db.merge_sync(fork.into_patch()).unwrap();
let snapshot = db.snapshot();
let view = snapshot
.as_ref()
.get_or_create_view(("foo.fam", &1_u32).into(), IndexType::List)
.unwrap();
assert!(!view.is_phantom());
let view = snapshot
.as_ref()
.get_or_create_view(("bar.fam", &1_u32).into(), IndexType::Map)
.unwrap();
assert!(!view.is_phantom());
}
#[test]
fn from_root_method() {
let db = TemporaryDB::new();
let fork = db.fork();
let prefixed = Prefixed::new("foo", &fork);
{
let mut list: ListIndex<_, u64> = ListIndex::from_root(prefixed).unwrap();
list.extend(vec![1, 2, 3]);
}
assert_eq!(fork.get_list::<_, u64>("foo").len(), 3);
}
}