mod scan;
mod write_set;
#[cfg(test)]
mod conflict_tests;
#[cfg(test)]
mod history_tests;
#[cfg(test)]
mod serializable_tests;
#[cfg(test)]
mod tests;
use tessarikv_constants::{MAX_KEY_BYTES, MAX_VALUE_BYTES};
use tessarikv_types::{KeyspaceId, SequenceNumber};
use crate::db::{Check, Reads};
use crate::{
Db, Error, Isolation, ReadOptions, Result, Snapshot, TransactionOptions, WriteOptions,
};
use write_set::{Own, WriteSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Savepoint(usize);
pub struct Transaction<'db> {
db: &'db Db,
snapshot: Snapshot<'db>,
writes: WriteSet,
reads: Option<Reads>,
}
impl Db {
pub fn begin(&self) -> Result<Transaction<'_>> {
self.begin_with(&TransactionOptions::default())
}
pub fn begin_with(&self, options: &TransactionOptions) -> Result<Transaction<'_>> {
let reads = match options.isolation {
Isolation::Snapshot => None,
Isolation::Serializable if self.checks_reads() => Some(Reads::default()),
Isolation::Serializable => {
return Err(Error::InvalidArgument {
what: "isolation",
reason: "serializable transactions are not enabled for this database",
});
}
};
Ok(Transaction {
db: self,
snapshot: self.snapshot()?,
writes: WriteSet::default(),
reads,
})
}
}
impl Transaction<'_> {
pub fn sequence(&self) -> SequenceNumber {
self.snapshot.sequence()
}
pub async fn get(
&mut self,
keyspace: KeyspaceId,
key: &[u8],
options: &ReadOptions,
) -> Result<Option<Vec<u8>>> {
self.db.check_keyspace(keyspace)?;
match self.writes.lookup(keyspace, key) {
Own::Value(value) => Ok(Some(value.to_vec())),
Own::Deleted => Ok(None),
Own::Untouched => {
if let Some(reads) = &mut self.reads {
reads.key(keyspace, key);
}
self.snapshot.get(keyspace, key, options).await
}
}
}
pub async fn multi_get<K: AsRef<[u8]>>(
&mut self,
keyspace: KeyspaceId,
keys: impl IntoIterator<Item = K>,
options: &ReadOptions,
) -> Result<Vec<Option<Vec<u8>>>> {
let mut values = Vec::new();
for key in keys {
values.push(self.get(keyspace, key.as_ref(), options).await?);
}
Ok(values)
}
pub fn put(&mut self, keyspace: KeyspaceId, key: &[u8], value: &[u8]) -> Result<()> {
self.db.check_keyspace(keyspace)?;
check_size("key", key.len(), MAX_KEY_BYTES)?;
check_size("value", value.len(), MAX_VALUE_BYTES)?;
self.writes.put(keyspace, key, value);
Ok(())
}
pub fn delete(&mut self, keyspace: KeyspaceId, key: &[u8]) -> Result<()> {
self.db.check_keyspace(keyspace)?;
check_size("key", key.len(), MAX_KEY_BYTES)?;
self.writes.delete(keyspace, key);
Ok(())
}
pub fn delete_range(&mut self, keyspace: KeyspaceId, start: &[u8], end: &[u8]) -> Result<()> {
self.db.check_keyspace(keyspace)?;
if start >= end {
return Err(Error::InvalidArgument {
what: "range delete",
reason: "the start is not below the end",
});
}
check_size("range start", start.len(), MAX_KEY_BYTES)?;
check_size("range end", end.len(), MAX_KEY_BYTES)?;
self.writes.delete_range(keyspace, start, end);
Ok(())
}
pub fn savepoint(&self) -> Savepoint {
Savepoint(self.writes.len())
}
pub fn rollback_to(&mut self, savepoint: Savepoint) {
self.writes.truncate(savepoint.0);
}
pub async fn commit(self, options: &WriteOptions) -> Result<SequenceNumber> {
let batch = self.writes.batch()?;
let check = Check {
snapshot: self.snapshot.sequence().get(),
reads: self.reads.as_ref(),
};
self.db.commit_batch(&batch, options, Some(check)).await
}
}
fn check_size(what: &'static str, size: usize, limit: usize) -> Result<()> {
if size > limit {
return Err(Error::TooLarge {
what,
size: u64::try_from(size).unwrap_or(u64::MAX),
limit: u64::try_from(limit).unwrap_or(u64::MAX),
});
}
Ok(())
}