use linkrs_core::types::Timestamp;
use linkrs_transaction::{TimestampSlot, VersionManager};
pub struct Visibility;
impl Visibility {
#[inline]
pub fn row_live(
snapshot: Timestamp,
create_ts: Timestamp,
delete_ts: Option<Timestamp>,
) -> bool {
Self::is_visible(snapshot, create_ts, delete_ts)
}
#[inline]
pub fn is_visible(
snapshot: Timestamp,
create_ts: Timestamp,
delete_ts: Option<Timestamp>,
) -> bool {
if create_ts > snapshot {
return false;
}
match delete_ts {
Some(del) => snapshot < del,
None => true,
}
}
#[inline]
pub fn is_column_visible(snapshot: Timestamp, create_ts: Timestamp) -> bool {
create_ts <= snapshot
}
#[inline]
pub fn is_edge_visible(
snapshot: Timestamp,
create_ts: Timestamp,
delete_ts: Timestamp,
) -> bool {
create_ts <= snapshot && snapshot < delete_ts
}
#[inline]
pub fn is_version_visible(snapshot: Timestamp, start_ts: Timestamp, end_ts: Timestamp) -> bool {
start_ts <= snapshot && snapshot < end_ts
}
#[inline]
pub fn is_gc_eligible(end_ts: Timestamp, safe: Timestamp) -> bool {
end_ts <= safe
}
}
#[derive(Clone, Copy)]
pub struct PendingGate<'a> {
manager: &'a VersionManager,
own_write: Option<Timestamp>,
}
impl<'a> PendingGate<'a> {
pub fn new(manager: &'a VersionManager, own_write: Option<Timestamp>) -> Self {
Self { manager, own_write }
}
pub fn is_foreign_pending(&self, snapshot: Timestamp, stamp: Timestamp) -> bool {
if stamp > snapshot {
return false;
}
if stamp <= self.manager.read_timestamp() {
return false;
}
if Some(stamp) == self.own_write {
return false;
}
matches!(
self.manager.timestamp_slot(stamp),
TimestampSlot::Pending | TimestampSlot::Aborted
)
}
pub fn is_create_visible(&self, snapshot: Timestamp, create_ts: Timestamp) -> bool {
if create_ts > snapshot {
return false;
}
if create_ts <= self.manager.read_timestamp() {
return true;
}
if Some(create_ts) == self.own_write {
return true;
}
matches!(
self.manager.timestamp_slot(create_ts),
TimestampSlot::Committed | TimestampSlot::Vanished
)
}
pub fn effective_delete(
&self,
snapshot: Timestamp,
delete_ts: Option<Timestamp>,
) -> Option<Timestamp> {
let delete_ts = delete_ts?;
if snapshot < delete_ts {
return None;
}
if delete_ts <= self.manager.read_timestamp() {
return Some(delete_ts);
}
if Some(delete_ts) == self.own_write {
return Some(delete_ts);
}
match self.manager.timestamp_slot(delete_ts) {
TimestampSlot::Committed | TimestampSlot::Vanished => Some(delete_ts),
TimestampSlot::Pending | TimestampSlot::Aborted => None,
}
}
pub fn is_row_visible(
&self,
snapshot: Timestamp,
create_ts: Timestamp,
delete_ts: Option<Timestamp>,
) -> bool {
if !self.is_create_visible(snapshot, create_ts) {
return false;
}
self.effective_delete(snapshot, delete_ts).is_none()
}
pub fn is_edge_visible(
&self,
snapshot: Timestamp,
create_ts: Timestamp,
delete_ts: Timestamp,
) -> bool {
let delete = if delete_ts == Timestamp::MAX {
None
} else {
Some(delete_ts)
};
self.is_row_visible(snapshot, create_ts, delete)
}
}
pub struct VisibilityGuard<'a> {
snapshot: Timestamp,
gate: PendingGate<'a>,
}
impl<'a> VisibilityGuard<'a> {
pub(crate) fn new(snapshot: Timestamp, gate: PendingGate<'a>) -> Self {
Self { snapshot, gate }
}
pub fn snapshot(&self) -> Timestamp {
self.snapshot
}
#[inline]
pub(crate) fn is_row_visible(
&self,
create_ts: Timestamp,
delete_ts: Option<Timestamp>,
) -> bool {
self.gate
.is_row_visible(self.snapshot, create_ts, delete_ts)
}
}
#[cfg(test)]
mod tests {
use super::Visibility;
#[test]
fn visible_when_created_before_snapshot() {
assert!(Visibility::is_visible(10, 5, None));
assert!(!Visibility::is_visible(4, 5, None));
}
#[test]
fn not_visible_when_deleted() {
assert!(!Visibility::is_visible(10, 5, Some(10)));
assert!(Visibility::is_visible(9, 5, Some(10)));
}
#[test]
fn column_visible() {
assert!(Visibility::is_column_visible(10, 10));
assert!(!Visibility::is_column_visible(9, 10));
}
#[test]
fn version_interval() {
assert!(Visibility::is_version_visible(5, 5, 10));
assert!(!Visibility::is_version_visible(10, 5, 10));
assert!(!Visibility::is_version_visible(4, 5, 10));
}
#[test]
fn gc_eligibility_matches_visibility_dual() {
assert!(Visibility::is_gc_eligible(10, 10));
assert!(Visibility::is_gc_eligible(9, 10));
assert!(!Visibility::is_gc_eligible(11, 10));
assert!(!Visibility::is_visible(10, 5, Some(10)));
}
#[test]
fn gate_hides_foreign_pending_create_but_keeps_own_write() {
use super::PendingGate;
use linkrs_transaction::VersionManager;
let vm = VersionManager::new();
let own = vm.acquire_insert_timestamp().expect("own write");
let foreign = vm.acquire_insert_timestamp().expect("foreign write");
let snapshot = foreign;
let foreign_view = PendingGate::new(&vm, Some(own));
assert!(!foreign_view.is_create_visible(snapshot, foreign));
assert!(!foreign_view.is_row_visible(snapshot, foreign, None));
assert!(foreign_view.is_foreign_pending(snapshot, foreign));
assert!(foreign_view.is_create_visible(snapshot, own));
assert!(foreign_view.is_row_visible(snapshot, own, None));
assert!(!foreign_view.is_foreign_pending(snapshot, own));
vm.commit_ordered(foreign).expect("ordered commit");
vm.commit_ordered(own).expect("ordered commit");
let later = PendingGate::new(&vm, None);
assert!(later.is_create_visible(snapshot, foreign));
assert!(!later.is_foreign_pending(snapshot, foreign));
}
#[test]
fn gate_ignores_foreign_pending_delete() {
use super::PendingGate;
use linkrs_transaction::VersionManager;
let vm = VersionManager::new();
let create = vm.acquire_insert_timestamp().expect("create");
vm.commit_ordered(create).expect("ordered commit");
let delete = vm.acquire_insert_timestamp().expect("delete");
let snapshot = delete;
let gate = PendingGate::new(&vm, None);
assert_eq!(gate.effective_delete(snapshot, Some(delete)), None);
assert!(gate.is_row_visible(snapshot, create, Some(delete)));
vm.abort_write_timestamp(delete);
assert!(!gate.is_foreign_pending(snapshot, delete));
}
#[test]
fn gate_trusts_frontier_and_vanished_slots() {
use super::PendingGate;
use linkrs_transaction::VersionManager;
let vm = VersionManager::new();
let committed = vm.acquire_insert_timestamp().expect("write");
vm.commit_ordered(committed).expect("ordered commit");
let gate = PendingGate::new(&vm, None);
assert!(gate.is_create_visible(committed, committed));
assert!(!gate.is_create_visible(committed - 1, committed));
assert!(!gate.is_foreign_pending(committed, committed));
}
}