use std::collections::HashSet;
pub type Xid = u64;
pub const XID_NONE: Xid = 0;
#[inline]
pub fn is_visible(
xmin: Xid,
xmax: Xid,
snapshot_xid: Xid,
in_progress: &HashSet<Xid>,
aborted: &HashSet<Xid>,
) -> bool {
if xmin != XID_NONE {
if xmin > snapshot_xid {
return false;
}
if in_progress.contains(&xmin) {
return false;
}
if aborted.contains(&xmin) {
return false;
}
}
if xmax != XID_NONE
&& xmax <= snapshot_xid
&& !in_progress.contains(&xmax)
&& !aborted.contains(&xmax)
{
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
fn empty() -> HashSet<Xid> {
HashSet::new()
}
fn set(xs: &[Xid]) -> HashSet<Xid> {
xs.iter().copied().collect()
}
#[test]
fn pre_mvcc_row_visible_to_every_snapshot() {
for snapshot_xid in [1, 100, 1_000_000] {
assert!(is_visible(
XID_NONE,
XID_NONE,
snapshot_xid,
&empty(),
&empty()
));
}
}
#[test]
fn pre_mvcc_row_with_committed_deleter_is_hidden() {
assert!(!is_visible(XID_NONE, 5, 10, &empty(), &empty()));
}
#[test]
fn pre_mvcc_row_with_in_progress_deleter_stays_visible() {
assert!(is_visible(XID_NONE, 5, 10, &set(&[5]), &empty()));
}
#[test]
fn future_writer_invisible() {
assert!(!is_visible(11, XID_NONE, 10, &empty(), &empty()));
}
#[test]
fn writer_at_snapshot_xid_is_visible() {
assert!(is_visible(10, XID_NONE, 10, &empty(), &empty()));
}
#[test]
fn in_progress_writer_invisible() {
assert!(!is_visible(5, XID_NONE, 10, &set(&[5]), &empty()));
}
#[test]
fn in_progress_writer_unrelated_to_aborted_check() {
assert!(!is_visible(5, XID_NONE, 10, &set(&[5]), &empty()));
}
#[test]
fn aborted_writer_never_visible() {
for snapshot_xid in [5, 10, 100] {
assert!(!is_visible(5, XID_NONE, snapshot_xid, &empty(), &set(&[5])));
}
}
#[test]
fn aborted_writer_takes_precedence_over_pre_mvcc_check() {
assert!(!is_visible(3, XID_NONE, 10, &empty(), &set(&[3])));
}
#[test]
fn committed_past_deleter_hides_row() {
assert!(!is_visible(3, 5, 10, &empty(), &empty()));
}
#[test]
fn future_deleter_keeps_row_visible() {
assert!(is_visible(3, 11, 10, &empty(), &empty()));
}
#[test]
fn deleter_at_snapshot_xid_hides_row() {
assert!(!is_visible(3, 10, 10, &empty(), &empty()));
}
#[test]
fn in_progress_deleter_keeps_row_visible() {
assert!(is_visible(3, 5, 10, &set(&[5]), &empty()));
}
#[test]
fn aborted_deleter_keeps_row_visible() {
assert!(is_visible(3, 5, 10, &empty(), &set(&[5])));
}
#[test]
fn aborted_writer_with_aborted_deleter_still_invisible() {
assert!(!is_visible(3, 5, 10, &empty(), &set(&[3, 5])));
}
#[test]
fn in_progress_writer_with_in_progress_deleter_still_invisible() {
assert!(!is_visible(3, 5, 10, &set(&[3, 5]), &empty()));
}
#[test]
fn writer_committed_deleter_aborted_visible() {
assert!(is_visible(3, 5, 10, &empty(), &set(&[5])));
}
#[test]
fn writer_committed_deleter_in_future_visible() {
assert!(is_visible(3, 50, 10, &empty(), &empty()));
}
#[test]
fn snapshot_xid_zero_pre_mvcc_only() {
assert!(is_visible(XID_NONE, XID_NONE, 0, &empty(), &empty()));
assert!(!is_visible(1, XID_NONE, 0, &empty(), &empty()));
}
#[test]
fn very_large_xids_still_compare_correctly() {
let big = u64::MAX - 10;
assert!(is_visible(big, XID_NONE, big, &empty(), &empty()));
assert!(!is_visible(big + 1, XID_NONE, big, &empty(), &empty()));
}
#[test]
fn empty_in_progress_and_aborted_sets_are_safe() {
assert!(is_visible(5, XID_NONE, 10, &empty(), &empty()));
}
}