pub mod concurrency;
pub mod mvcc;
use crate::error::{GraphError, Result};
use crate::graph::{Node, Relationship};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
pub use concurrency::{
ConcurrentTransaction, LockInfo, LockManager, LockStatistics, LockType, LockableResource,
};
pub use mvcc::{MvccManager, MvccStats, MvccTransaction};
pub type TransactionId = u64;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IsolationLevel {
ReadUncommitted,
ReadCommitted,
RepeatableRead,
Serializable,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TransactionState {
Active,
Committed,
RolledBack,
}
pub struct Transaction {
id: TransactionId,
isolation_level: IsolationLevel,
state: TransactionState,
read_timestamp: u64,
write_timestamp: Option<u64>,
}
impl Transaction {
pub fn new(id: TransactionId, isolation_level: IsolationLevel) -> Self {
Transaction {
id,
isolation_level,
state: TransactionState::Active,
read_timestamp: current_timestamp(),
write_timestamp: None,
}
}
pub fn id(&self) -> TransactionId {
self.id
}
pub fn isolation_level(&self) -> IsolationLevel {
self.isolation_level
}
pub fn state(&self) -> TransactionState {
self.state
}
pub fn is_active(&self) -> bool {
self.state == TransactionState::Active
}
pub fn read_timestamp(&self) -> u64 {
self.read_timestamp
}
pub fn write_timestamp(&self) -> Option<u64> {
self.write_timestamp
}
pub fn mark_write(&mut self) {
if self.write_timestamp.is_none() {
self.write_timestamp = Some(current_timestamp());
}
}
pub fn commit(&mut self) -> Result<()> {
if self.state != TransactionState::Active {
return Err(GraphError::Transaction(format!(
"Cannot commit transaction {} in state {:?}",
self.id, self.state
)));
}
self.state = TransactionState::Committed;
Ok(())
}
pub fn rollback(&mut self) -> Result<()> {
if self.state != TransactionState::Active {
return Err(GraphError::Transaction(format!(
"Cannot rollback transaction {} in state {:?}",
self.id, self.state
)));
}
self.state = TransactionState::RolledBack;
Ok(())
}
}
pub struct TransactionManager {
next_transaction_id: AtomicU64,
default_isolation_level: IsolationLevel,
lock_manager: Arc<LockManager>,
mvcc_manager: Option<Arc<MvccManager>>,
}
impl TransactionManager {
pub fn new(default_isolation_level: IsolationLevel) -> Self {
TransactionManager {
next_transaction_id: AtomicU64::new(1),
default_isolation_level,
lock_manager: Arc::new(LockManager::new()),
mvcc_manager: None,
}
}
pub fn new_with_mvcc(default_isolation_level: IsolationLevel) -> Self {
TransactionManager {
next_transaction_id: AtomicU64::new(1),
default_isolation_level,
lock_manager: Arc::new(LockManager::new()),
mvcc_manager: Some(Arc::new(MvccManager::new())),
}
}
pub fn has_mvcc(&self) -> bool {
self.mvcc_manager.is_some()
}
pub fn mvcc(&self) -> Option<&Arc<MvccManager>> {
self.mvcc_manager.as_ref()
}
pub fn begin(&self) -> Transaction {
self.begin_with_isolation(self.default_isolation_level)
}
pub fn begin_with_isolation(&self, isolation_level: IsolationLevel) -> Transaction {
let id = self.next_transaction_id.fetch_add(1, Ordering::SeqCst);
Transaction::new(id, isolation_level)
}
pub fn begin_concurrent(&self) -> ConcurrentTransaction {
let transaction = self.begin();
ConcurrentTransaction::new(transaction, self.lock_manager.clone())
}
pub fn begin_concurrent_with_isolation(
&self,
isolation_level: IsolationLevel,
) -> ConcurrentTransaction {
let transaction = self.begin_with_isolation(isolation_level);
ConcurrentTransaction::new(transaction, self.lock_manager.clone())
}
pub fn default_isolation_level(&self) -> IsolationLevel {
self.default_isolation_level
}
pub fn lock_statistics(&self) -> LockStatistics {
self.lock_manager.get_lock_statistics()
}
pub fn lock_manager(&self) -> &Arc<LockManager> {
&self.lock_manager
}
pub fn begin_mvcc(&self) -> Result<TransactionId> {
let mvcc = self.mvcc_manager.as_ref().ok_or_else(|| {
GraphError::Transaction("MVCC not enabled. Use new_with_mvcc()".to_string())
})?;
Ok(mvcc.begin(self.default_isolation_level))
}
pub fn begin_mvcc_with_isolation(
&self,
isolation_level: IsolationLevel,
) -> Result<TransactionId> {
let mvcc = self.mvcc_manager.as_ref().ok_or_else(|| {
GraphError::Transaction("MVCC not enabled. Use new_with_mvcc()".to_string())
})?;
Ok(mvcc.begin(isolation_level))
}
pub fn mvcc_read_node(
&self,
tx_id: TransactionId,
node_id: crate::graph::Id,
) -> Result<Option<Node>> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.read_node(tx_id, node_id)
}
pub fn mvcc_read_relationship(
&self,
tx_id: TransactionId,
rel_id: crate::graph::Id,
) -> Result<Option<Relationship>> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.read_relationship(tx_id, rel_id)
}
pub fn mvcc_write_node(&self, tx_id: TransactionId, node: Node) -> Result<()> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.write_node(tx_id, node)
}
pub fn mvcc_write_relationship(&self, tx_id: TransactionId, rel: Relationship) -> Result<()> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.write_relationship(tx_id, rel)
}
pub fn mvcc_commit(&self, tx_id: TransactionId) -> Result<()> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.commit(tx_id)
}
pub fn mvcc_rollback(&self, tx_id: TransactionId) -> Result<()> {
let mvcc = self
.mvcc_manager
.as_ref()
.ok_or_else(|| GraphError::Transaction("MVCC not enabled".to_string()))?;
mvcc.rollback(tx_id)
}
pub fn mvcc_stats(&self) -> Option<MvccStats> {
self.mvcc_manager.as_ref().map(|m| m.get_stats())
}
pub fn mvcc_gc(&self) {
if let Some(mvcc) = &self.mvcc_manager {
mvcc.gc();
}
}
}
impl Default for TransactionManager {
fn default() -> Self {
Self::new(IsolationLevel::ReadCommitted)
}
}
fn current_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_lifecycle() {
let manager = TransactionManager::new(IsolationLevel::ReadCommitted);
let mut tx = manager.begin();
assert_eq!(tx.id(), 1);
assert_eq!(tx.isolation_level(), IsolationLevel::ReadCommitted);
assert_eq!(tx.state(), TransactionState::Active);
assert!(tx.is_active());
assert!(tx.write_timestamp().is_none());
tx.mark_write();
assert!(tx.write_timestamp().is_some());
tx.commit().unwrap();
assert_eq!(tx.state(), TransactionState::Committed);
assert!(!tx.is_active());
assert!(tx.commit().is_err());
}
#[test]
fn test_transaction_rollback() {
let manager = TransactionManager::new(IsolationLevel::ReadCommitted);
let mut tx = manager.begin();
assert!(tx.is_active());
tx.rollback().unwrap();
assert_eq!(tx.state(), TransactionState::RolledBack);
assert!(!tx.is_active());
assert!(tx.rollback().is_err());
}
#[test]
fn test_transaction_manager() {
let manager = TransactionManager::new(IsolationLevel::Serializable);
let tx1 = manager.begin();
let tx2 = manager.begin();
assert_eq!(tx1.id(), 1);
assert_eq!(tx2.id(), 2);
assert_eq!(tx1.isolation_level(), IsolationLevel::Serializable);
assert_eq!(tx2.isolation_level(), IsolationLevel::Serializable);
let tx3 = manager.begin_with_isolation(IsolationLevel::ReadUncommitted);
assert_eq!(tx3.id(), 3);
assert_eq!(tx3.isolation_level(), IsolationLevel::ReadUncommitted);
}
}