use crate::types::{Result, TransactionId, Value, VelociError};
use parking_lot::RwLock;
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
static GLOBAL_TXN_ID: AtomicU64 = AtomicU64::new(1);
pub type Timestamp = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct VersionInfo {
pub xmin: TransactionId,
pub xmax: TransactionId,
pub created_at: Timestamp,
pub deleted_at: Timestamp,
}
impl VersionInfo {
pub fn new(xmin: TransactionId, created_at: Timestamp) -> Self {
Self {
xmin,
xmax: 0,
created_at,
deleted_at: 0,
}
}
pub fn is_visible(&self, snapshot: &Snapshot) -> bool {
if self.created_at > snapshot.timestamp {
return false;
}
if self.deleted_at > 0 && self.deleted_at <= snapshot.timestamp {
return false;
}
if !snapshot.is_transaction_visible(self.xmin) {
return false;
}
if self.xmax > 0 && snapshot.is_transaction_visible(self.xmax) {
return false;
}
true
}
pub fn mark_deleted(&mut self, xmax: TransactionId, deleted_at: Timestamp) {
self.xmax = xmax;
self.deleted_at = deleted_at;
}
}
#[derive(Debug, Clone)]
pub struct VersionedRecord {
pub key: i64,
pub versions: Vec<RecordVersion>,
}
#[derive(Debug, Clone)]
pub struct RecordVersion {
pub version_info: VersionInfo,
pub data: Vec<Value>,
}
impl RecordVersion {
pub fn new(xmin: TransactionId, created_at: Timestamp, data: Vec<Value>) -> Self {
Self {
version_info: VersionInfo::new(xmin, created_at),
data,
}
}
pub fn is_visible(&self, snapshot: &Snapshot) -> bool {
self.version_info.is_visible(snapshot)
}
}
impl VersionedRecord {
pub fn new(key: i64) -> Self {
Self {
key,
versions: Vec::new(),
}
}
pub fn add_version(&mut self, version: RecordVersion) {
self.versions.push(version);
}
pub fn get_visible_version(&self, snapshot: &Snapshot) -> Option<&RecordVersion> {
self.versions
.iter()
.rev()
.find(|v| v.is_visible(snapshot))
}
pub fn mark_deleted(&mut self, xmax: TransactionId, deleted_at: Timestamp) -> Result<()> {
if let Some(version) = self.versions.last_mut() {
version.version_info.mark_deleted(xmax, deleted_at);
Ok(())
} else {
Err(VelociError::NotFound("No versions to delete".to_string()))
}
}
pub fn vacuum(&mut self, min_active_timestamp: Timestamp) {
self.versions.retain(|v| {
v.version_info.deleted_at == 0 || v.version_info.deleted_at >= min_active_timestamp
});
if self.versions.is_empty() && !self.versions.is_empty() {
self.versions.truncate(1);
}
}
}
#[derive(Debug, Clone)]
pub struct Snapshot {
pub timestamp: Timestamp,
pub active_transactions: Vec<TransactionId>,
pub txn_id: TransactionId,
}
impl Snapshot {
pub fn new(timestamp: Timestamp, txn_id: TransactionId, active_transactions: Vec<TransactionId>) -> Self {
Self {
timestamp,
txn_id,
active_transactions,
}
}
pub fn is_transaction_visible(&self, txn_id: TransactionId) -> bool {
if txn_id == self.txn_id {
return true;
}
if txn_id >= self.txn_id {
return false;
}
!self.active_transactions.contains(&txn_id)
}
}
pub struct MvccManager {
current_timestamp: AtomicU64,
active_snapshots: Arc<RwLock<HashMap<TransactionId, Snapshot>>>,
version_store: Arc<dashmap::DashMap<String, Arc<RwLock<BTreeMap<i64, VersionedRecord>>>>>,
committed_transactions: Arc<RwLock<BTreeMap<TransactionId, Timestamp>>>,
}
impl MvccManager {
pub fn new() -> Self {
Self {
current_timestamp: AtomicU64::new(1),
active_snapshots: Arc::new(RwLock::new(HashMap::new())),
version_store: Arc::new(dashmap::DashMap::new()),
committed_transactions: Arc::new(RwLock::new(BTreeMap::new())),
}
}
pub fn begin_transaction(&self) -> Snapshot {
let txn_id = GLOBAL_TXN_ID.fetch_add(1, Ordering::SeqCst);
let timestamp = self.current_timestamp.fetch_add(1, Ordering::SeqCst);
let snapshot = {
let snapshots = self.active_snapshots.read();
let active_transactions: Vec<TransactionId> = snapshots.keys().copied().collect();
Snapshot::new(timestamp, txn_id, active_transactions)
};
self.active_snapshots.write().insert(txn_id, snapshot.clone());
snapshot
}
pub fn commit_transaction(&self, snapshot: &Snapshot) -> Result<()> {
let commit_timestamp = self.current_timestamp.fetch_add(1, Ordering::SeqCst);
self.committed_transactions
.write()
.insert(snapshot.txn_id, commit_timestamp);
self.active_snapshots.write().remove(&snapshot.txn_id);
Ok(())
}
pub fn abort_transaction(&self, snapshot: &Snapshot) -> Result<()> {
self.active_snapshots.write().remove(&snapshot.txn_id);
Ok(())
}
pub fn insert_version(
&self,
table_name: &str,
key: i64,
data: Vec<Value>,
snapshot: &Snapshot,
) -> Result<()> {
let created_at = self.current_timestamp.load(Ordering::SeqCst);
let version = RecordVersion::new(snapshot.txn_id, created_at, data);
let table_store = self.version_store
.entry(table_name.to_string())
.or_insert_with(|| Arc::new(RwLock::new(BTreeMap::new())));
let mut table_map = table_store.write();
let record = table_map.entry(key).or_insert_with(|| VersionedRecord::new(key));
record.add_version(version);
Ok(())
}
pub fn read_version(
&self,
table_name: &str,
key: i64,
snapshot: &Snapshot,
) -> Result<Option<Vec<Value>>> {
if let Some(table_store) = self.version_store.get(table_name) {
let table_map = table_store.read();
if let Some(record) = table_map.get(&key) {
if let Some(version) = record.get_visible_version(snapshot) {
return Ok(Some(version.data.clone()));
}
}
}
Ok(None)
}
pub fn delete_version(
&self,
table_name: &str,
key: i64,
snapshot: &Snapshot,
) -> Result<()> {
let deleted_at = self.current_timestamp.fetch_add(1, Ordering::SeqCst);
if let Some(table_store) = self.version_store.get(table_name) {
let mut table_map = table_store.write();
if let Some(record) = table_map.get_mut(&key) {
record.mark_deleted(snapshot.txn_id, deleted_at)?;
return Ok(());
}
}
Err(VelociError::NotFound(format!("Record {} not found", key)))
}
pub fn scan_table(
&self,
table_name: &str,
snapshot: &Snapshot,
) -> Result<Vec<(i64, Vec<Value>)>> {
if let Some(table_store) = self.version_store.get(table_name) {
let table_map = table_store.read();
let mut results = Vec::new();
for (key, record) in table_map.iter() {
if let Some(version) = record.get_visible_version(snapshot) {
results.push((*key, version.data.clone()));
}
}
Ok(results)
} else {
Ok(Vec::new())
}
}
pub fn vacuum(&self) {
let min_active_timestamp = {
let snapshots = self.active_snapshots.read();
snapshots
.values()
.map(|s| s.timestamp)
.min()
.unwrap_or(self.current_timestamp.load(Ordering::SeqCst))
};
{
let mut committed = self.committed_transactions.write();
committed.retain(|_, &mut ts| ts >= min_active_timestamp);
}
for table_entry in self.version_store.iter() {
let mut table_map = table_entry.value().write();
for record in table_map.values_mut() {
record.vacuum(min_active_timestamp);
}
} }
pub fn get_stats(&self) -> MvccStats {
let active_snapshots = self.active_snapshots.read().len();
let mut total_records = 0;
let mut total_versions = 0;
for table_entry in self.version_store.iter() {
let table_map = table_entry.value().read();
total_records += table_map.len();
for record in table_map.values() {
total_versions += record.versions.len();
}
}
MvccStats {
active_snapshots,
total_records,
total_versions,
version_bloat: if total_records > 0 {
total_versions as f64 / total_records as f64
} else {
0.0
},
}
}
}
impl Default for MvccManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MvccStats {
pub active_snapshots: usize,
pub total_records: usize,
pub total_versions: usize,
pub version_bloat: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Value;
#[test]
fn test_mvcc_snapshot_isolation() {
let mvcc = MvccManager::new();
let snapshot1 = mvcc.begin_transaction();
mvcc.insert_version(
"users",
1,
vec![Value::Integer(1), Value::Text("Alice".to_string())],
&snapshot1,
).unwrap();
mvcc.commit_transaction(&snapshot1).unwrap();
let snapshot2 = mvcc.begin_transaction();
let result = mvcc.read_version("users", 1, &snapshot2).unwrap();
assert!(result.is_some());
let data = result.unwrap();
assert_eq!(data.len(), 2);
assert_eq!(data[1], Value::Text("Alice".to_string()));
}
#[test]
fn test_mvcc_concurrent_reads() {
let mvcc = MvccManager::new();
let snapshot_setup = mvcc.begin_transaction();
mvcc.insert_version(
"products",
1,
vec![Value::Integer(1), Value::Text("Laptop".to_string())],
&snapshot_setup,
).unwrap();
mvcc.commit_transaction(&snapshot_setup).unwrap();
let snapshot_r1 = mvcc.begin_transaction();
let snapshot_r2 = mvcc.begin_transaction();
let result1 = mvcc.read_version("products", 1, &snapshot_r1).unwrap();
let result2 = mvcc.read_version("products", 1, &snapshot_r2).unwrap();
assert!(result1.is_some());
assert!(result2.is_some());
assert_eq!(result1, result2);
}
#[test]
fn test_mvcc_version_visibility() {
let mvcc = MvccManager::new();
let snapshot1 = mvcc.begin_transaction();
mvcc.insert_version(
"test",
1,
vec![Value::Integer(100)],
&snapshot1,
).unwrap();
let snapshot2 = mvcc.begin_transaction();
mvcc.commit_transaction(&snapshot1).unwrap();
let result = mvcc.read_version("test", 1, &snapshot2).unwrap();
assert!(result.is_none());
let snapshot3 = mvcc.begin_transaction();
let result = mvcc.read_version("test", 1, &snapshot3).unwrap();
assert!(result.is_some());
}
#[test]
fn test_mvcc_vacuum() {
let mvcc = MvccManager::new();
let snapshot1 = mvcc.begin_transaction();
mvcc.insert_version("test", 1, vec![Value::Integer(1)], &snapshot1).unwrap();
mvcc.commit_transaction(&snapshot1).unwrap();
let snapshot2 = mvcc.begin_transaction();
mvcc.insert_version("test", 1, vec![Value::Integer(2)], &snapshot2).unwrap();
mvcc.commit_transaction(&snapshot2).unwrap();
let snapshot3 = mvcc.begin_transaction();
mvcc.insert_version("test", 1, vec![Value::Integer(3)], &snapshot3).unwrap();
mvcc.commit_transaction(&snapshot3).unwrap();
let stats_before = mvcc.get_stats();
assert!(stats_before.total_versions >= 3);
mvcc.vacuum();
let stats_after = mvcc.get_stats();
assert!(stats_after.total_versions >= 1); }
}