use super::nitrite_transaction::NitriteTransaction;
use crate::common::LockRegistry;
use crate::errors::{ErrorKind, NitriteError, NitriteResult};
use crate::nitrite::Nitrite;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct Session {
inner: Arc<SessionInner>,
}
impl Session {
pub fn new(db: Nitrite, lock_registry: LockRegistry) -> Self {
Session {
inner: Arc::new(SessionInner::new(db, lock_registry)),
}
}
pub fn id(&self) -> &str {
self.inner.id()
}
pub fn is_active(&self) -> bool {
self.inner.is_active()
}
pub fn begin_transaction(&self) -> NitriteResult<NitriteTransaction> {
self.inner.begin_transaction()
}
pub fn active_transactions(&self) -> Vec<String> {
self.inner.active_transactions()
}
pub fn close(&self) -> NitriteResult<()> {
self.inner.close()
}
}
struct SessionInner {
id: String,
active: Arc<AtomicBool>,
transactions: Arc<Mutex<HashMap<String, NitriteTransaction>>>,
db: Nitrite,
lock_registry: LockRegistry,
}
impl SessionInner {
pub fn new(db: Nitrite, lock_registry: LockRegistry) -> Self {
SessionInner {
id: Uuid::new_v4().to_string(),
active: Arc::new(AtomicBool::new(true)),
transactions: Arc::new(Mutex::new(HashMap::new())),
db,
lock_registry,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::SeqCst)
}
pub fn begin_transaction(&self) -> NitriteResult<NitriteTransaction> {
self.check_active()?;
let tx = NitriteTransaction::new(self.db.clone(), self.lock_registry.clone())?;
let tx_id = tx.id().to_string();
self.transactions.lock().insert(tx_id, tx.clone());
Ok(tx)
}
pub fn active_transactions(&self) -> Vec<String> {
self.transactions.lock().keys().cloned().collect()
}
pub fn close(&self) -> NitriteResult<()> {
if self
.active
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_err()
{
return Ok(());
}
let mut txs = self.transactions.lock();
for (_, tx) in txs.drain() {
let _ = tx.rollback();
}
Ok(())
}
fn check_active(&self) -> NitriteResult<()> {
if !self.is_active() {
return Err(NitriteError::new(
"Session is closed",
ErrorKind::InvalidOperation,
));
}
Ok(())
}
}
impl Drop for Session {
fn drop(&mut self) {
let _ = self.close();
}
}
#[cfg(test)]
#[allow(clippy::assertions_on_constants)] mod tests {
use super::*;
fn create_test_db() -> Nitrite {
Nitrite::builder().open_or_create(None, None).unwrap()
}
#[test]
fn test_session_creation() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(!session.id().is_empty());
assert!(session.is_active());
}
#[test]
fn test_session_unique_ids() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session1 = Session::new(db.clone(), lock_registry.clone());
let session2 = Session::new(db, lock_registry);
assert_ne!(session1.id(), session2.id());
}
#[test]
fn test_session_id() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let id = session.id();
assert!(!id.is_empty());
assert_eq!(id.len(), 36); }
#[test]
fn test_session_clone() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session1 = Session::new(db, lock_registry);
let session2 = session1.clone();
assert_eq!(session1.id(), session2.id());
assert_eq!(session1.is_active(), session2.is_active());
}
#[test]
fn test_session_clone_shares_state() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session1 = Session::new(db.clone(), lock_registry.clone());
let session2 = session1.clone();
let _tx1 = session1.begin_transaction().unwrap();
let txs = session2.active_transactions();
assert_eq!(txs.len(), 1);
}
#[test]
fn test_session_deref() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let id = session.id();
assert!(!id.is_empty());
}
#[test]
fn test_session_initially_active() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(session.is_active());
}
#[test]
fn test_session_close() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let result = session.close();
assert!(result.is_ok());
assert!(!session.is_active());
}
#[test]
fn test_session_close_idempotent() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let result1 = session.close();
let result2 = session.close();
assert!(result1.is_ok());
assert!(result2.is_ok());
assert!(!session.is_active());
}
#[test]
fn test_session_drop_calls_close() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _id = session.id().to_string();
let _tx = session.begin_transaction().unwrap();
assert!(!session.active_transactions().is_empty());
drop(session);
}
#[test]
fn test_begin_transaction() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx = session.begin_transaction();
assert!(tx.is_ok());
let _tx = tx.unwrap();
assert!(true);
}
#[test]
fn test_multiple_transactions() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
assert_ne!(tx1.id(), tx2.id());
}
#[test]
fn test_transactions_tracked() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert_eq!(session.active_transactions().len(), 0);
let _tx1 = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 1);
let _tx2 = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 2);
}
#[test]
fn test_transaction_ids_match() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
let active = session.active_transactions();
assert!(active.contains(&tx1.id().to_string()));
assert!(active.contains(&tx2.id().to_string()));
}
#[test]
fn test_begin_transaction_on_closed_session() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
session.close().unwrap();
let result = session.begin_transaction();
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(*err.kind(), ErrorKind::InvalidOperation);
assert!(err.message().contains("Session is closed"));
}
#[test]
fn test_closed_session_error_message() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
session.close().unwrap();
let result = session.begin_transaction();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.message().contains("closed") || err.message().contains("Session"));
}
#[test]
fn test_active_transactions_empty() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let active = session.active_transactions();
assert_eq!(active.len(), 0);
}
#[test]
fn test_active_transactions_count() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx1 = session.begin_transaction().unwrap();
let _tx2 = session.begin_transaction().unwrap();
let _tx3 = session.begin_transaction().unwrap();
let active = session.active_transactions();
assert_eq!(active.len(), 3);
}
#[test]
fn test_active_transactions_ids() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
let active = session.active_transactions();
assert!(active.contains(&tx1.id().to_string()));
assert!(active.contains(&tx2.id().to_string()));
assert_eq!(active.len(), 2);
}
#[test]
fn test_active_transactions_on_closed_session() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx = session.begin_transaction().unwrap();
session.close().unwrap();
let active = session.active_transactions();
assert_eq!(active.len(), 0);
}
#[test]
fn test_close_with_active_transactions() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 1);
let result = session.close();
assert!(result.is_ok());
assert_eq!(session.active_transactions().len(), 0);
}
#[test]
fn test_close_with_multiple_transactions() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx1 = session.begin_transaction().unwrap();
let _tx2 = session.begin_transaction().unwrap();
let _tx3 = session.begin_transaction().unwrap();
let result = session.close();
assert!(result.is_ok());
assert_eq!(session.active_transactions().len(), 0);
}
#[test]
fn test_close_clears_transactions() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx1 = session.begin_transaction().unwrap();
let _tx2 = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 2);
session.close().unwrap();
assert_eq!(session.active_transactions().len(), 0);
}
#[test]
fn test_atomic_active_flag() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(session.is_active());
session.close().unwrap();
assert!(!session.is_active());
}
#[test]
fn test_transaction_map_protected() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx1 = session.begin_transaction().unwrap();
let _tx2 = session.begin_transaction().unwrap();
let count1 = session.active_transactions().len();
let count2 = session.active_transactions().len();
assert_eq!(count1, count2);
assert_eq!(count1, 2);
}
#[test]
fn test_arc_shared_state() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session1 = Session::new(db, lock_registry);
let session2 = session1.clone();
let session3 = session1.clone();
session1.begin_transaction().unwrap();
assert_eq!(session2.active_transactions().len(), 1);
assert_eq!(session3.active_transactions().len(), 1);
}
#[test]
fn test_session_id_stable() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let id1 = session.id();
let id2 = session.id();
let id3 = session.id();
assert_eq!(id1, id2);
assert_eq!(id2, id3);
}
#[test]
fn test_state_never_reactivates() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(session.is_active());
session.close().unwrap();
assert!(!session.is_active());
assert!(!session.is_active());
}
#[test]
fn test_lock_registry_preserved() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db.clone(), lock_registry.clone());
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
assert_ne!(tx1.id(), tx2.id());
}
#[test]
fn test_database_reference_preserved() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db.clone(), lock_registry);
let _tx1 = session.begin_transaction().unwrap();
let _tx2 = session.begin_transaction().unwrap();
assert!(session.is_active());
}
#[test]
fn test_transaction_independence() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
assert_ne!(tx1.id(), tx2.id());
assert!(true);
}
#[test]
fn test_begin_transaction_returns_correct_tx() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx = session.begin_transaction().unwrap();
let tx_id = tx.id().to_string();
let active = session.active_transactions();
assert!(active.contains(&tx_id));
}
#[test]
fn test_many_transactions() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
for _ in 0..10 {
session.begin_transaction().unwrap();
}
assert_eq!(session.active_transactions().len(), 10);
}
#[test]
fn test_session_after_transaction_lifecycle() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let tx = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 1);
tx.close();
assert!(session.is_active());
}
#[test]
fn test_session_state_after_close() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
let _tx = session.begin_transaction().unwrap();
session.close().unwrap();
assert!(!session.is_active());
assert!(session.begin_transaction().is_err());
}
#[test]
fn test_check_active_validation() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(session.begin_transaction().is_ok());
session.close().unwrap();
assert!(session.begin_transaction().is_err());
}
#[test]
fn test_complete_session_lifecycle() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session = Session::new(db, lock_registry);
assert!(session.is_active());
let tx1 = session.begin_transaction().unwrap();
let tx2 = session.begin_transaction().unwrap();
assert_eq!(session.active_transactions().len(), 2);
assert!(session
.active_transactions()
.contains(&tx1.id().to_string()));
assert!(session
.active_transactions()
.contains(&tx2.id().to_string()));
session.close().unwrap();
assert!(!session.is_active());
assert_eq!(session.active_transactions().len(), 0);
}
#[test]
fn test_session_with_clones() {
let db = create_test_db();
let lock_registry = LockRegistry::new();
let session1 = Session::new(db, lock_registry);
let session2 = session1.clone();
let session3 = session1.clone();
assert_eq!(session1.id(), session2.id());
assert_eq!(session2.id(), session3.id());
assert!(session1.is_active());
assert!(session2.is_active());
assert!(session3.is_active());
let _tx = session1.begin_transaction().unwrap();
assert_eq!(session1.active_transactions().len(), 1);
assert_eq!(session2.active_transactions().len(), 1);
assert_eq!(session3.active_transactions().len(), 1);
session2.close().unwrap();
assert!(!session1.is_active());
assert!(!session3.is_active());
}
}