use crate::types::{Timestamp, TxId};
#[derive(Debug, Clone)]
pub struct VersionedRecord<T> {
pub data: T,
pub txid: TxId,
pub commit_ts: Timestamp,
pub prev: Option<Box<VersionedRecord<T>>>,
pub deleted: bool,
}
impl<T> VersionedRecord<T> {
pub fn new(data: T, txid: TxId, commit_ts: Timestamp) -> Self {
Self {
data,
txid,
commit_ts,
prev: None,
deleted: false,
}
}
pub fn with_prev(
data: T,
txid: TxId,
commit_ts: Timestamp,
prev: Box<VersionedRecord<T>>,
) -> Self {
Self {
data,
txid,
commit_ts,
prev: Some(prev),
deleted: false,
}
}
pub fn deletion(
data: T,
txid: TxId,
commit_ts: Timestamp,
prev: Option<Box<VersionedRecord<T>>>,
) -> Self {
Self {
data,
txid,
commit_ts,
prev,
deleted: true,
}
}
pub fn chain_depth(&self) -> usize {
let mut depth = 1;
let mut current = self.prev.as_ref();
while let Some(prev) = current {
depth += 1;
current = prev.prev.as_ref();
}
depth
}
}
pub fn is_visible<T>(version: &VersionedRecord<T>, snapshot_ts: Timestamp, txid: TxId) -> bool {
if version.txid == 0 && version.commit_ts == 0 {
return true;
}
if version.txid == txid {
return true;
}
if version.commit_ts == 0 {
return false;
}
version.commit_ts < snapshot_ts
}
pub fn visible_version<T>(
head: &VersionedRecord<T>,
snapshot_ts: Timestamp,
txid: TxId,
) -> Option<&VersionedRecord<T>> {
if head.prev.is_none() {
if is_visible(head, snapshot_ts, txid) {
return Some(head);
}
return None;
}
let mut current = Some(head);
while let Some(version) = current {
if is_visible(version, snapshot_ts, txid) {
return Some(version);
}
current = version.prev.as_deref();
}
None
}
pub fn visible_version_mut<T>(
head: &mut VersionedRecord<T>,
snapshot_ts: Timestamp,
txid: TxId,
) -> Option<&mut VersionedRecord<T>> {
if head.prev.is_none() {
if is_visible(head, snapshot_ts, txid) {
return Some(head);
}
return None;
}
if is_visible(head, snapshot_ts, txid) {
return Some(head);
}
let mut current = head.prev.as_mut();
while let Some(version) = current {
if is_visible(version, snapshot_ts, txid) {
return Some(version);
}
current = version.prev.as_mut();
}
None
}
pub fn node_exists<T>(
version: Option<&VersionedRecord<T>>,
snapshot_ts: Timestamp,
txid: TxId,
) -> bool {
let Some(head) = version else {
return false;
};
let visible = visible_version(head, snapshot_ts, txid);
match visible {
Some(v) => !v.deleted,
None => false,
}
}
pub fn edge_exists<T: EdgeLike>(
version: Option<&VersionedRecord<T>>,
snapshot_ts: Timestamp,
txid: TxId,
) -> bool {
let Some(head) = version else {
return false;
};
let visible = visible_version(head, snapshot_ts, txid);
match visible {
Some(v) => v.data.is_added(),
None => false,
}
}
pub trait EdgeLike {
fn is_added(&self) -> bool;
}
impl EdgeLike for crate::types::EdgeVersionData {
fn is_added(&self) -> bool {
self.added
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_visible_own_write() {
let version = VersionedRecord::new(42, 1, 0);
assert!(is_visible(&version, 100, 1));
assert!(!is_visible(&version, 100, 2));
}
#[test]
fn test_is_visible_committed() {
let version = VersionedRecord::new(42, 1, 10);
assert!(is_visible(&version, 20, 2));
assert!(!is_visible(&version, 10, 2));
assert!(!is_visible(&version, 5, 2));
}
#[test]
fn test_visible_version_single() {
let version = VersionedRecord::new(42, 1, 10);
let visible = visible_version(&version, 20, 2);
assert!(visible.is_some());
assert_eq!(visible.expect("expected value").data, 42);
let not_visible = visible_version(&version, 5, 2);
assert!(not_visible.is_none());
}
#[test]
fn test_visible_version_chain() {
let v1 = VersionedRecord::new(1, 1, 10);
let v2 = VersionedRecord::with_prev(2, 2, 20, Box::new(v1));
let v3 = VersionedRecord::with_prev(3, 3, 30, Box::new(v2));
let visible = visible_version(&v3, 35, 100);
assert_eq!(visible.expect("expected value").data, 3);
let visible = visible_version(&v3, 25, 100);
assert_eq!(visible.expect("expected value").data, 2);
let visible = visible_version(&v3, 15, 100);
assert_eq!(visible.expect("expected value").data, 1);
let visible = visible_version(&v3, 5, 100);
assert!(visible.is_none());
}
#[test]
fn test_node_exists_deleted() {
let v1 = VersionedRecord::new("created", 1, 10);
let v2 = VersionedRecord::deletion("deleted", 2, 20, Some(Box::new(v1)));
assert!(node_exists(Some(&v2), 15, 100));
assert!(!node_exists(Some(&v2), 25, 100));
}
#[test]
fn test_chain_depth() {
let v1 = VersionedRecord::new(1, 1, 10);
assert_eq!(v1.chain_depth(), 1);
let v2 = VersionedRecord::with_prev(2, 2, 20, Box::new(v1));
assert_eq!(v2.chain_depth(), 2);
let v3 = VersionedRecord::with_prev(3, 3, 30, Box::new(v2));
assert_eq!(v3.chain_depth(), 3);
}
#[derive(Debug, Clone)]
struct TestEdge {
added: bool,
}
impl EdgeLike for TestEdge {
fn is_added(&self) -> bool {
self.added
}
}
#[test]
fn test_edge_exists() {
let v1 = VersionedRecord::new(TestEdge { added: true }, 1, 10);
assert!(edge_exists(Some(&v1), 15, 100));
let v2 = VersionedRecord::with_prev(TestEdge { added: false }, 2, 20, Box::new(v1));
assert!(edge_exists(Some(&v2), 15, 100));
assert!(!edge_exists(Some(&v2), 25, 100));
}
#[test]
fn test_no_version() {
assert!(!node_exists::<i32>(None, 100, 1));
struct DummyEdge;
impl EdgeLike for DummyEdge {
fn is_added(&self) -> bool {
false
}
}
assert!(!edge_exists::<DummyEdge>(None, 100, 1));
}
}