use crate::error::{Result, TdbError};
use std::collections::HashSet;
pub type TxId = u64;
pub const TX_ID_COMMITTED: TxId = 0;
#[derive(Debug, thiserror::Error)]
pub enum MvccError {
#[error(
"Serialization conflict on key: snapshot tx={snapshot_tx}, conflicting_writer={writer_tx}"
)]
SerializationConflict {
snapshot_tx: TxId,
writer_tx: TxId,
},
#[error("Write-write conflict on key: existing writer tx={existing_tx}")]
WriteWriteConflict {
existing_tx: TxId,
},
#[error("Unknown transaction: {tx_id}")]
UnknownTransaction {
tx_id: TxId,
},
#[error("Transaction {tx_id} is not active (state: {state})")]
TransactionNotActive {
tx_id: TxId,
state: &'static str,
},
#[error("Deadlock detected; transaction {victim_tx} aborted as victim")]
DeadlockVictim {
victim_tx: TxId,
},
#[error("Lock acquisition failed for transaction {tx_id}")]
LockFailed {
tx_id: TxId,
},
}
impl From<MvccError> for TdbError {
fn from(e: MvccError) -> Self {
TdbError::Transaction(e.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsolationLevel {
ReadCommitted,
RepeatableRead,
Serializable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransactionState {
Active,
Committed,
RolledBack,
Aborted,
}
impl TransactionState {
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Active => "Active",
Self::Committed => "Committed",
Self::RolledBack => "RolledBack",
Self::Aborted => "Aborted",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VersionedKey(pub Vec<u8>);
impl VersionedKey {
pub fn new(key: &[u8]) -> Self {
Self(key.to_vec())
}
}
#[derive(Debug, Clone)]
pub struct VersionedEntry {
pub created_tx_id: TxId,
pub deleted_tx_id: Option<TxId>,
pub value: Vec<u8>,
}
impl VersionedEntry {
pub fn is_tombstone(&self) -> bool {
self.value.is_empty() && self.deleted_tx_id.is_some()
}
pub fn is_visible_at(&self, reader_snapshot_tx: TxId, committed: &HashSet<TxId>) -> bool {
let creator_committed = self.created_tx_id == TX_ID_COMMITTED
|| (self.created_tx_id <= reader_snapshot_tx
&& committed.contains(&self.created_tx_id));
if !creator_committed {
return false;
}
match self.deleted_tx_id {
None => true,
Some(del_tx) => {
!(del_tx <= reader_snapshot_tx && committed.contains(&del_tx))
}
}
}
}
pub struct TransactionContext {
pub tx_id: TxId,
pub start_tx_id: TxId,
pub isolation_level: IsolationLevel,
pub state: TransactionState,
pub write_set: HashSet<VersionedKey>,
pub read_set: HashSet<VersionedKey>,
}
impl TransactionContext {
pub(crate) fn new(tx_id: TxId, start_tx_id: TxId, isolation_level: IsolationLevel) -> Self {
Self {
tx_id,
start_tx_id,
isolation_level,
state: TransactionState::Active,
write_set: HashSet::new(),
read_set: HashSet::new(),
}
}
pub(crate) fn ensure_active(&self) -> Result<()> {
if self.state != TransactionState::Active {
Err(MvccError::TransactionNotActive {
tx_id: self.tx_id,
state: self.state.as_str(),
}
.into())
} else {
Ok(())
}
}
}