use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex, RwLock};
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct TransactionId(pub u64);
impl std::fmt::Display for TransactionId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "tx{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct VersionId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IsolationLevel {
ReadUncommitted,
ReadCommitted,
RepeatableRead,
Serializable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransactionStatus {
Active,
Preparing,
Committed,
RolledBack,
Failed,
}
#[derive(Debug, Clone)]
pub enum WALEntry {
BeginTransaction {
tx_id: TransactionId,
timestamp: DateTime<Utc>,
isolation_level: IsolationLevel,
},
WriteData {
tx_id: TransactionId,
files: Vec<String>,
version: VersionId,
},
CommitTransaction {
tx_id: TransactionId,
timestamp: DateTime<Utc>,
version: VersionId,
},
AbortTransaction {
tx_id: TransactionId,
timestamp: DateTime<Utc>,
reason: String,
},
Checkpoint {
timestamp: DateTime<Utc>,
active_versions: Vec<VersionId>,
},
}
#[derive(Debug, Clone)]
pub struct DataVersion {
pub version_id: VersionId,
pub tx_id: TransactionId,
pub timestamp: DateTime<Utc>,
pub data: HashMap<String, String>,
pub is_committed: bool,
}
#[derive(Debug, Clone)]
pub struct Snapshot {
pub version_id: VersionId,
pub timestamp: DateTime<Utc>,
pub visible_versions: HashSet<VersionId>,
}
#[derive(Debug)]
pub struct VersionManager {
pub versions: HashMap<VersionId, DataVersion>,
pub active_transactions: HashMap<TransactionId, VersionId>,
pub committed_versions: VecDeque<VersionId>,
pub next_version_id: u64,
pub next_tx_id: u64,
pub wal_entries: Vec<WALEntry>,
pub gc_cutoff_version: VersionId,
}
impl VersionManager {
pub fn new() -> Self {
VersionManager {
versions: HashMap::new(),
active_transactions: HashMap::new(),
committed_versions: VecDeque::new(),
next_version_id: 1,
next_tx_id: 1,
wal_entries: Vec::new(),
gc_cutoff_version: VersionId(0),
}
}
pub fn allocate_tx_id(&mut self) -> TransactionId {
let id = TransactionId(self.next_tx_id);
self.next_tx_id += 1;
id
}
pub fn allocate_version_id(&mut self) -> VersionId {
let id = VersionId(self.next_version_id);
self.next_version_id += 1;
id
}
pub fn create_snapshot(&self) -> Snapshot {
Snapshot {
version_id: VersionId(self.next_version_id),
timestamp: Utc::now(),
visible_versions: self.committed_versions.iter().copied().collect(),
}
}
pub fn commit_version(&mut self, version_id: VersionId) -> Result<(), String> {
let version = self
.versions
.get_mut(&version_id)
.ok_or("Version not found")?;
version.is_committed = true;
self.committed_versions.push_back(version_id);
Ok(())
}
pub fn get_visible_data(
&self,
snapshot: &Snapshot,
key: &str,
) -> Option<String> {
for &version_id in snapshot.visible_versions.iter() {
if let Some(version) = self.versions.get(&version_id) {
if let Some(value) = version.data.get(key) {
return Some(value.clone());
}
}
}
None
}
pub fn garbage_collect(&mut self) -> usize {
let cutoff = self.gc_cutoff_version;
let before = self.versions.len();
self.versions.retain(|&version_id, _| version_id >= cutoff);
let after = self.versions.len();
before - after
}
pub fn get_wal_entries_since(&self, timestamp: DateTime<Utc>) -> Vec<WALEntry> {
self.wal_entries
.iter()
.filter(|entry| {
match entry {
WALEntry::BeginTransaction { timestamp: ts, .. }
| WALEntry::CommitTransaction { timestamp: ts, .. }
| WALEntry::AbortTransaction { timestamp: ts, .. }
| WALEntry::Checkpoint { timestamp: ts, .. } => *ts > timestamp,
_ => false,
}
})
.cloned()
.collect()
}
}
#[derive(Debug)]
pub struct Transaction {
pub id: TransactionId,
pub status: TransactionStatus,
pub isolation_level: IsolationLevel,
pub started_at: DateTime<Utc>,
pub snapshot: Snapshot,
pub writes: HashMap<String, String>, pub read_set: HashSet<String>, pub write_set: HashSet<String>, }
impl Transaction {
pub fn begin(
tx_id: TransactionId,
isolation_level: IsolationLevel,
snapshot: Snapshot,
) -> Self {
Transaction {
id: tx_id,
status: TransactionStatus::Active,
isolation_level,
started_at: Utc::now(),
snapshot,
writes: HashMap::new(),
read_set: HashSet::new(),
write_set: HashSet::new(),
}
}
pub fn read(&mut self, key: String) -> Option<String> {
self.read_set.insert(key.clone());
self.writes.get(&key).cloned()
}
pub fn write(&mut self, key: String, value: String) {
self.write_set.insert(key.clone());
self.writes.insert(key, value);
}
pub fn prepare(&mut self) -> Result<(), String> {
if self.status != TransactionStatus::Active {
return Err("Transaction not active".to_string());
}
self.status = TransactionStatus::Preparing;
Ok(())
}
pub fn commit(&mut self) -> Result<(), String> {
if self.status != TransactionStatus::Preparing && self.status != TransactionStatus::Active {
return Err(format!("Cannot commit transaction in {:?} state", self.status));
}
self.status = TransactionStatus::Committed;
Ok(())
}
pub fn rollback(&mut self) -> Result<(), String> {
if self.status == TransactionStatus::Committed {
return Err("Cannot rollback committed transaction".to_string());
}
self.writes.clear();
self.read_set.clear();
self.write_set.clear();
self.status = TransactionStatus::RolledBack;
Ok(())
}
pub fn get_changes(&self) -> &HashMap<String, String> {
&self.writes
}
pub fn has_write_conflict(&self, other_writes: &HashSet<String>) -> bool {
!self.write_set.is_disjoint(other_writes)
}
pub fn has_read_conflict(&self, other_writes: &HashSet<String>) -> bool {
!self.read_set.is_disjoint(other_writes)
}
pub fn elapsed(&self) -> chrono::Duration {
Utc::now() - self.started_at
}
}
pub struct TransactionManager {
pub version_manager: Arc<RwLock<VersionManager>>,
pub active_transactions: Arc<Mutex<HashMap<TransactionId, Transaction>>>,
pub committed_transactions: Arc<Mutex<Vec<TransactionId>>>,
}
impl TransactionManager {
pub fn new() -> Self {
TransactionManager {
version_manager: Arc::new(RwLock::new(VersionManager::new())),
active_transactions: Arc::new(Mutex::new(HashMap::new())),
committed_transactions: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn begin(&self, isolation_level: IsolationLevel) -> Result<TransactionId, String> {
let mut vm = self.version_manager.write().map_err(|_| "Lock failed")?;
let tx_id = vm.allocate_tx_id();
let snapshot = vm.create_snapshot();
let tx = Transaction::begin(tx_id, isolation_level, snapshot);
let mut active = self.active_transactions.lock().map_err(|_| "Lock failed")?;
active.insert(tx_id, tx);
vm.wal_entries.push(WALEntry::BeginTransaction {
tx_id,
timestamp: Utc::now(),
isolation_level,
});
Ok(tx_id)
}
pub fn commit(&self, tx_id: TransactionId) -> Result<(), String> {
let mut active = self
.active_transactions
.lock()
.map_err(|_| "Lock failed")?;
let tx = active
.get_mut(&tx_id)
.ok_or("Transaction not found")?;
tx.prepare()?;
tx.commit()?;
{
let mut vm = self.version_manager.write().map_err(|_| "Lock failed")?;
let version_id = vm.allocate_version_id();
let mut version = DataVersion {
version_id,
tx_id,
timestamp: Utc::now(),
data: tx.get_changes().clone(),
is_committed: false,
};
version.is_committed = true;
vm.versions.insert(version_id, version.clone());
vm.commit_version(version_id)?;
vm.wal_entries.push(WALEntry::CommitTransaction {
tx_id,
timestamp: Utc::now(),
version: version_id,
});
}
let mut committed = self.committed_transactions.lock().map_err(|_| "Lock failed")?;
committed.push(tx_id);
active.remove(&tx_id);
Ok(())
}
pub fn rollback(&self, tx_id: TransactionId) -> Result<(), String> {
let mut active = self
.active_transactions
.lock()
.map_err(|_| "Lock failed")?;
let tx = active
.get_mut(&tx_id)
.ok_or("Transaction not found")?;
tx.rollback()?;
let mut vm = self.version_manager.write().map_err(|_| "Lock failed")?;
vm.wal_entries.push(WALEntry::AbortTransaction {
tx_id,
timestamp: Utc::now(),
reason: "User rollback".to_string(),
});
active.remove(&tx_id);
Ok(())
}
pub fn get_status(&self, tx_id: TransactionId) -> Result<TransactionStatus, String> {
let active = self
.active_transactions
.lock()
.map_err(|_| "Lock failed")?;
active
.get(&tx_id)
.map(|tx| tx.status)
.ok_or("Transaction not found".to_string())
}
pub fn read_as_of(
&self,
key: &str,
timestamp: DateTime<Utc>,
) -> Result<Option<String>, String> {
let vm = self.version_manager.read().map_err(|_| "Lock failed")?;
let mut best_version: Option<&DataVersion> = None;
for version in vm.versions.values() {
if version.timestamp <= timestamp && version.is_committed {
if best_version.is_none()
|| version.timestamp > best_version.unwrap().timestamp
{
best_version = Some(version);
}
}
}
Ok(best_version.and_then(|v| v.data.get(key).cloned()))
}
pub fn get_committed_versions(&self) -> Result<Vec<VersionId>, String> {
let vm = self.version_manager.read().map_err(|_| "Lock failed")?;
Ok(vm.committed_versions.iter().copied().collect())
}
pub fn checkpoint(&self) -> Result<(), String> {
let mut vm = self.version_manager.write().map_err(|_| "Lock failed")?;
let active = self
.active_transactions
.lock()
.map_err(|_| "Lock failed")?;
let active_versions: Vec<VersionId> = active
.values()
.map(|tx| tx.snapshot.version_id)
.collect();
vm.wal_entries.push(WALEntry::Checkpoint {
timestamp: Utc::now(),
active_versions,
});
Ok(())
}
pub fn recover_from_wal(&self) -> Result<usize, String> {
let vm = self.version_manager.read().map_err(|_| "Lock failed")?;
let mut recovered_count = 0;
for entry in &vm.wal_entries {
match entry {
WALEntry::CommitTransaction { .. } => recovered_count += 1,
_ => {}
}
}
Ok(recovered_count)
}
pub fn garbage_collect(&self) -> Result<usize, String> {
let mut vm = self.version_manager.write().map_err(|_| "Lock failed")?;
let active = self
.active_transactions
.lock()
.map_err(|_| "Lock failed")?;
let min_version_id = active
.values()
.map(|tx| tx.snapshot.version_id.0)
.min()
.unwrap_or(u64::MAX);
let new_cutoff = VersionId(min_version_id);
vm.gc_cutoff_version = new_cutoff;
Ok(vm.garbage_collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
}