use topodb::*;
fn spec() -> IndexSpec {
IndexSpec::default()
}
const JUNE: i64 = 1_780_300_800_000; const AUGUST: i64 = 1_785_542_400_000; const SEPT: i64 = 1_788_220_800_000;
const JULY: i64 = 1_782_864_000_000; const MID_AUG: i64 = 1_786_752_000_000; const OCT: i64 = 1_790_812_800_000;
fn valid_at(rec: &EdgeRecord, t: i64) -> bool {
rec.valid_from <= t && rec.valid_to.is_none_or(|vt| vt > t)
}
fn believed_at(rec: &EdgeRecord, t: i64) -> bool {
rec.recorded_at <= t && rec.superseded_at.is_none_or(|st| st > t)
}
fn two_nodes(db: &Db, s: ScopeId) -> (NodeId, NodeId) {
let a = NodeId::new();
let b = NodeId::new();
let mk = |id| Op::CreateNode {
id,
scope: Scope::Id(s),
label: "Entity".into(),
props: Props::new(),
};
db.submit(vec![mk(a), mk(b)]).unwrap();
(a, b)
}
#[test]
fn recorded_at_is_the_write_instant_never_the_caller() {
let dir = tempfile::tempdir().unwrap();
let db = Db::open_with(dir.path().join("t.redb"), spec()).unwrap();
let s = ScopeId::new();
let (a, b) = two_nodes(&db, s);
let e = EdgeId::new();
db.submit_at(
vec![Op::CreateEdge {
id: e,
scope: Scope::Id(s),
ty: "works_at".into(),
from: a,
to: b,
props: Props::new(),
valid_from: Some(JUNE),
recorded_at: Some(1), }],
AUGUST,
)
.unwrap();
let rec = db
.edges_from(&ScopeSet::of(&[s]), a, None, None, false, TimeAxis::Valid)
.unwrap()
.pop()
.unwrap();
assert_eq!(rec.valid_from, JUNE, "world time honors the caller");
assert_eq!(rec.recorded_at, AUGUST, "belief time is the write instant");
assert_eq!(rec.superseded_at, None);
}
#[test]
fn close_diverges_the_axes_with_a_backdated_valid_to() {
let dir = tempfile::tempdir().unwrap();
let db = Db::open_with(dir.path().join("t.redb"), spec()).unwrap();
let s = ScopeId::new();
let (a, b) = two_nodes(&db, s);
let e = EdgeId::new();
db.submit_at(
vec![Op::CreateEdge {
id: e,
scope: Scope::Id(s),
ty: "works_at".into(),
from: a,
to: b,
props: Props::new(),
valid_from: Some(JUNE),
recorded_at: None,
}],
JUNE,
)
.unwrap();
db.submit_at(
vec![Op::CloseEdge {
id: e,
valid_to: Some(AUGUST), superseded_at: Some(2), }],
SEPT, )
.unwrap();
let rec = db
.edges_from(&ScopeSet::of(&[s]), a, None, None, false, TimeAxis::Valid)
.unwrap()
.pop()
.unwrap();
assert_eq!(rec.valid_to, Some(AUGUST));
assert_eq!(rec.superseded_at, Some(SEPT));
let dumped = db
.debug_dump_edges()
.into_iter()
.find(|r| r.id == e)
.unwrap();
assert_eq!(dumped.valid_from, JUNE);
assert_eq!(dumped.valid_to, Some(AUGUST));
assert_eq!(
dumped.recorded_at, JUNE,
"recorded_at from the create, unaffected by close"
);
assert_eq!(dumped.superseded_at, Some(SEPT));
}
#[test]
fn late_recorded_fact_answers_differ_by_axis() {
let dir = tempfile::tempdir().unwrap();
let db = Db::open_with(dir.path().join("t.redb"), spec()).unwrap();
let s = ScopeId::new();
let scopes = ScopeSet::of(&[s]);
let (a, b) = two_nodes(&db, s);
let e = EdgeId::new();
db.submit_at(
vec![Op::CreateEdge {
id: e,
scope: Scope::Id(s),
ty: "works_at".into(),
from: a,
to: b,
props: Props::new(),
valid_from: Some(JUNE), recorded_at: None,
}],
AUGUST, )
.unwrap();
let base = TraversalQuery {
scopes: scopes.clone(),
seeds: vec![a],
max_hops: 1,
edge_types: None,
direction: Direction::Out,
as_of: None,
time_axis: TimeAxis::Valid,
};
let hits = |q: &TraversalQuery| db.traverse(q).unwrap().edges.iter().any(|r| r.id == e);
assert!(
hits(&TraversalQuery {
as_of: Some(JULY),
time_axis: TimeAxis::Valid,
..base.clone()
}),
"valid axis at July: world truth already held since June"
);
assert!(
!hits(&TraversalQuery {
as_of: Some(JULY),
time_axis: TimeAxis::Recorded,
..base.clone()
}),
"recorded axis at July: not written until August"
);
assert!(
hits(&TraversalQuery {
as_of: Some(SEPT),
time_axis: TimeAxis::Recorded,
..base.clone()
}),
"recorded axis at September: written by then"
);
let rec = db
.edges_from(&scopes, a, None, None, false, TimeAxis::Valid)
.unwrap()
.pop()
.unwrap();
assert!(
valid_at(&rec, JULY),
"edges_from parity: valid axis at July"
);
assert!(
!believed_at(&rec, JULY),
"edges_from parity: recorded axis at July"
);
assert!(
believed_at(&rec, SEPT),
"edges_from parity: recorded axis at September"
);
}
#[test]
fn divergent_close_answers_differ_by_axis() {
let dir = tempfile::tempdir().unwrap();
let db = Db::open_with(dir.path().join("t.redb"), spec()).unwrap();
let s = ScopeId::new();
let scopes = ScopeSet::of(&[s]);
let (a, b) = two_nodes(&db, s);
let e = EdgeId::new();
db.submit_at(
vec![Op::CreateEdge {
id: e,
scope: Scope::Id(s),
ty: "works_at".into(),
from: a,
to: b,
props: Props::new(),
valid_from: Some(JUNE),
recorded_at: None,
}],
JUNE,
)
.unwrap();
db.submit_at(
vec![Op::CloseEdge {
id: e,
valid_to: Some(AUGUST), superseded_at: None,
}],
SEPT, )
.unwrap();
let rec = db
.edges_from(&scopes, a, None, None, false, TimeAxis::Valid)
.unwrap()
.pop()
.unwrap();
assert!(
!valid_at(&rec, MID_AUG),
"valid axis mid-August: world says gone since August"
);
assert!(
!valid_at(&rec, OCT),
"valid axis October: still gone in the world"
);
assert!(
believed_at(&rec, MID_AUG),
"recorded axis mid-August: not superseded (in belief) until September"
);
assert!(
!believed_at(&rec, OCT),
"recorded axis October: superseded by September"
);
let base = TraversalQuery {
scopes,
seeds: vec![a],
max_hops: 1,
edge_types: None,
direction: Direction::Out,
as_of: Some(MID_AUG),
time_axis: TimeAxis::Valid,
};
let hits = |q: &TraversalQuery| db.traverse(q).unwrap().edges.iter().any(|r| r.id == e);
assert!(
!hits(&TraversalQuery {
time_axis: TimeAxis::Valid,
..base.clone()
}),
"traverse, valid axis mid-August: world says gone"
);
assert!(
hits(&TraversalQuery {
time_axis: TimeAxis::Recorded,
..base.clone()
}),
"traverse, recorded axis mid-August: still believed"
);
}