mod address;
mod allocator;
mod config;
mod device;
mod error;
mod flush_checksum;
mod id;
mod index;
mod io;
mod journal;
mod locks;
mod open_options;
mod persy;
mod recover;
mod snapshot;
mod snapshots;
mod transaction;
mod util;
#[cfg(feature = "background_ops")]
mod background;
#[cfg(feature = "experimental_inspect")]
pub mod inspect;
use crate::persy::PersyImpl;
pub use crate::{
address::segment_iter::{SegmentIter, SnapshotSegmentIter, TxSegmentIter},
config::{Config, TransactionConfig, TxStrategy},
error::{
BeginTransactionError, CreateError, CreateIndexError, CreateSegmentError, DeleteError, DropIndexError,
DropSegmentError, GenericError, IndexChangeError, IndexError, IndexOpsError, IndexPutError, InsertError,
OpenError, OpenMemoryError, PersyError, PrepareError, ReadError, SegmentError, UpdateError, PE,
},
id::{IndexId, PersyId, SegmentId, ToIndexId, ToSegmentId},
index::{
bytevec::ByteVec,
config::{IndexType, IndexTypeId, ValueMode},
iter::{IndexIter, TxIndexIter},
value_iter::ValueIter,
},
open_options::OpenOptions,
persy::IndexInfo,
recover::{Recover, RecoverStatus},
snapshot::Snapshot,
transaction::{Transaction, TransactionFinalize},
};
use std::{
fs::{self, File},
ops::RangeBounds,
path::Path,
sync::Arc,
};
pub type TransactionId = Vec<u8>;
#[derive(Clone)]
pub struct Persy {
persy_impl: Arc<PersyImpl>,
}
impl Persy {
pub fn create<P: AsRef<Path>>(path: P) -> Result<(), PE<CreateError>> {
Ok(PersyImpl::create(path.as_ref())?)
}
pub fn create_from_file(file: File) -> Result<(), PE<CreateError>> {
Ok(PersyImpl::create_from_file(file)?)
}
pub fn open<P: AsRef<Path>>(path: P, config: Config) -> Result<Persy, PE<OpenError>> {
Persy::open_with_recover(path, config, |_| true)
}
pub fn open_with_recover<P: AsRef<Path>, C>(path: P, config: Config, recover: C) -> Result<Persy, PE<OpenError>>
where
C: Fn(&TransactionId) -> bool,
{
let f = fs::OpenOptions::new().write(true).read(true).open(path)?;
Persy::open_from_file_with_recover(f, config, recover)
}
pub fn recover<P: AsRef<Path>>(path: P, config: Config) -> Result<Recover, PE<OpenError>> {
let f = fs::OpenOptions::new().write(true).read(true).open(path)?;
let (persy_impl, recover) = PersyImpl::open_recover(f, config)?;
Ok(Recover::new(recover, Arc::new(persy_impl)))
}
pub fn open_from_file(path: File, config: Config) -> Result<Persy, PE<OpenError>> {
let (persy_impl, recover) = PersyImpl::open_recover(path, config)?;
persy_impl.final_recover(recover)?;
Ok(Persy {
persy_impl: Arc::new(persy_impl),
})
}
pub fn open_from_file_with_recover<C>(file: File, config: Config, recover: C) -> Result<Persy, PE<OpenError>>
where
C: Fn(&TransactionId) -> bool,
{
let (persy_impl, mut recov) = PersyImpl::open_recover(file, config)?;
recov.apply(recover)?;
persy_impl.final_recover(recov)?;
Ok(Persy {
persy_impl: Arc::new(persy_impl),
})
}
pub fn open_or_create_with<P, F>(path: P, config: Config, prepare: F) -> Result<Persy, PE<OpenError>>
where
P: AsRef<Path>,
F: FnOnce(&Persy) -> Result<(), Box<dyn std::error::Error>> + 'static,
{
let path = path.as_ref();
let persy;
if !path.exists() {
Persy::create(path).map_err(|e| PE::PE(OpenError::from(e.error())))?;
persy = Persy::open(path, config)?;
prepare(&persy).map_err(|e| OpenError::InitError(format!("{}", e)))?;
} else {
persy = Persy::open(path, config)?;
}
Ok(persy)
}
pub fn begin(&self) -> Result<Transaction, PE<BeginTransactionError>> {
self.begin_with(TransactionConfig::new())
}
pub fn begin_with(&self, config: TransactionConfig) -> Result<Transaction, PE<BeginTransactionError>> {
Ok(Transaction {
tx: Some(self.persy_impl.begin_with(config)?),
persy_impl: self.persy_impl.clone(),
})
}
pub fn exists_segment(&self, segment: &str) -> Result<bool, PE<GenericError>> {
Ok(self.persy_impl.exists_segment(segment))
}
pub fn solve_segment_id(&self, segment: impl ToSegmentId) -> Result<SegmentId, PE<SegmentError>> {
Ok(self.persy_impl.solve_segment_id(segment)?)
}
pub fn solve_index_id(&self, index: impl ToIndexId) -> Result<IndexId, PE<IndexError>> {
Ok(self.persy_impl.solve_index_id(index)?)
}
pub fn read(&self, segment: impl ToSegmentId, id: &PersyId) -> Result<Option<Vec<u8>>, PE<ReadError>> {
let segment_id = self
.solve_segment_id(segment)
.map_err(|PE::PE(e)| PE::PE(ReadError::from(e)))?;
Ok(self.persy_impl.read(segment_id, &id.0)?)
}
pub fn scan(&self, segment: impl ToSegmentId) -> Result<SegmentIter, PE<SegmentError>> {
let segment_id = self.solve_segment_id(segment)?;
Ok(SegmentIter::new(
self.persy_impl.scan(segment_id)?,
self.persy_impl.clone(),
))
}
pub fn exists_index(&self, index_name: &str) -> Result<bool, PE<GenericError>> {
Ok(self.persy_impl.exists_index(index_name))
}
pub fn get<K, V>(&self, index_name: &str, k: &K) -> Result<ValueIter<V>, PE<IndexOpsError>>
where
K: IndexType,
V: IndexType,
{
let index_id = self
.solve_index_id(index_name)
.map_err(|e| PE::PE(IndexOpsError::from(e.error())))?;
Ok(ValueIter::from(
self.persy_impl
.get::<K::Wrapper, V::Wrapper>(index_id, &k.clone().wrap())?,
))
}
pub fn one<K, V>(&self, index_name: &str, k: &K) -> Result<Option<V>, PE<IndexOpsError>>
where
K: IndexType,
V: IndexType,
{
Ok(self.get(index_name, k)?.next())
}
pub fn range<K, V, R>(&self, index_name: &str, range: R) -> Result<IndexIter<K, V>, PE<IndexOpsError>>
where
K: IndexType,
V: IndexType,
R: RangeBounds<K>,
{
let index_id = self
.solve_index_id(index_name)
.map_err(|e| PE::PE(IndexOpsError::from(e.error())))?;
let rr = PersyImpl::map_index_range_bounds(range);
let (_, raw) = self.persy_impl.range(index_id, rr)?;
Ok(IndexIter::new(raw, self.persy_impl.clone()))
}
pub fn list_segments(&self) -> Result<Vec<(String, SegmentId)>, PE<GenericError>> {
Ok(self.persy_impl.list_segments())
}
pub fn list_indexes(&self) -> Result<Vec<(String, IndexInfo)>, PE<GenericError>> {
Ok(self.persy_impl.list_indexes()?)
}
pub fn snapshot(&self) -> Result<Snapshot, PE<GenericError>> {
Ok(Snapshot::new(self.persy_impl.clone(), self.persy_impl.snapshot()))
}
#[cfg(test)]
pub fn free_file_lock(&self) -> crate::error::PERes<()> {
self.persy_impl.free_file_lock()
}
}
#[cfg(test)]
mod tests {
use crate::{OpenOptions, ReadError, PE};
#[test]
pub fn test_read_id_out_of_file() {
let persy = OpenOptions::new().memory().unwrap();
let mut tx = persy.begin().unwrap();
tx.create_segment("test").unwrap();
let finalizer = tx.prepare().unwrap();
finalizer.commit().unwrap();
let id = crate::PersyId(crate::id::RecRef { page: 10000, pos: 10 });
let read_after = persy.read("test", &id).unwrap();
assert!(read_after.is_none());
}
#[test]
pub fn test_read_id_out_of_page() {
let persy = OpenOptions::new().memory().unwrap();
let mut tx = persy.begin().unwrap();
tx.create_segment("test").unwrap();
let id = tx.insert("test", &[0; 10]).unwrap();
let finalizer = tx.prepare().unwrap();
finalizer.commit().unwrap();
let id = crate::PersyId(crate::id::RecRef {
page: id.0.page,
pos: 2000,
});
let read_after = persy.read("test", &id);
assert!(read_after.is_err());
match read_after.err() {
Some(PE::PE(ReadError::InvalidPersyId(_))) => {}
_ => assert!(false, "wrong error type"),
}
}
}