use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap},
sync::Arc,
};
use proptest::prelude::*;
use super::{
BaseRecords, MergedState, Overlay, OverlayLayer, Snapshot, StateView, WriteOverlay,
test_support::small_base,
};
use crate::{
ElementId, IncidenceId, LabelId, PropertyKeyId, RelationId, RelationTypeId, RoleId,
backing::Base,
freeze::{FreezeStamps, freeze_view},
id::{CheckpointGeneration, CommitSeq},
state::{ElementRecord, IncidenceRecord, NextIds, PropertySubject, RelationRecord},
value::PropertyValue,
};
fn base_next_ids(base: &Base) -> NextIds {
let header = *base.get().header();
NextIds {
element: ElementId::new(header.next_element),
relation: RelationId::new(header.next_relation),
incidence: IncidenceId::new(header.next_incidence),
role: RoleId::new(header.next_role),
label: crate::LabelId::new(header.next_label),
relation_type: crate::RelationTypeId::new(header.next_relation_type),
property_key: PropertyKeyId::new(header.next_property_key),
projection: crate::ProjectionId::new(header.next_projection),
index: crate::IndexId::new(header.next_index),
}
}
#[test]
fn empty_overlay_borrows_base() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let overlay = Overlay::empty(base_next_ids(&base), base.get().catalog().clone());
let view = MergedState::new(&records, &overlay);
assert_eq!(view.element_count(), 3);
for raw in 1u64..=3 {
let record = view.element(ElementId::new(raw)).expect("element present");
assert!(matches!(record, Cow::Borrowed(_)), "base read must borrow");
assert_eq!(record.id, ElementId::new(raw));
}
assert_eq!(view.relation_count(), 2);
assert_eq!(view.incidence_count(), 2);
}
#[test]
fn cow_borrowed_fast_path_owned_override() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let mut write = WriteOverlay::new(base_next_ids(&base), base.get().catalog().clone());
let robot = base.get().catalog().label_id("Robot").expect("robot");
write.add_element_label(&records, ElementId::new(2), robot);
write.tombstone_element(&records, ElementId::new(3));
let fresh = write.create_element().expect("fresh element");
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
let e1 = view.element(ElementId::new(1)).expect("e1");
assert!(matches!(e1, Cow::Borrowed(_)), "base-only id must borrow");
let e2 = view.element(ElementId::new(2)).expect("e2");
assert!(matches!(e2, Cow::Owned(_)), "overridden id must be owned");
assert!(view.element(ElementId::new(3)).is_none(), "tombstone hides");
let fresh_read = view.element(fresh).expect("fresh present");
assert!(
matches!(fresh_read, Cow::Owned(_)),
"fresh id must be owned"
);
}
#[test]
fn cow_property_fast_path() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let name = base.get().catalog().property_key_id("name").expect("name");
let rank = base.get().catalog().property_key_id("rank").expect("rank");
let mut write = WriteOverlay::new(base_next_ids(&base), base.get().catalog().clone());
write.set_property(
&records,
PropertySubject::Element(ElementId::new(1)),
name,
PropertyValue::Text("Alicia".to_owned()),
);
write.remove_property(&records, PropertySubject::Element(ElementId::new(1)), rank);
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
let bob = view
.property(PropertySubject::Element(ElementId::new(2)), name)
.expect("bob name");
assert!(matches!(bob, Cow::Borrowed(_)), "base property must borrow");
assert_eq!(bob.into_owned(), PropertyValue::Text("Bob".to_owned()));
let alicia = view
.property(PropertySubject::Element(ElementId::new(1)), name)
.expect("alicia name");
assert!(matches!(alicia, Cow::Owned(_)), "overlay property is owned");
assert_eq!(
alicia.into_owned(),
PropertyValue::Text("Alicia".to_owned())
);
assert!(
view.property(PropertySubject::Element(ElementId::new(1)), rank)
.is_none(),
"removed property is hidden"
);
}
#[test]
fn tombstone_idempotent() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let mut write = WriteOverlay::new(base_next_ids(&base), base.get().catalog().clone());
let absent = ElementId::new(999);
write.tombstone_element(&records, absent);
write.tombstone_element(&records, ElementId::new(1));
write.tombstone_element(&records, ElementId::new(1));
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
assert!(
view.element(absent).is_none(),
"absent tombstone stays absent"
);
assert!(
view.element(ElementId::new(1)).is_none(),
"double tombstone hides"
);
assert_eq!(view.element_count(), 2);
}
#[test]
fn overlay_records_orphan_property_unvalidated() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let name = base.get().catalog().property_key_id("name").expect("name");
let subject = PropertySubject::Element(ElementId::new(1));
let orphan = PropertyValue::Text("ghost".to_owned());
let mut write = WriteOverlay::new(base_next_ids(&base), base.get().catalog().clone());
write.tombstone_element(&records, ElementId::new(1));
write.set_property(&records, subject, name, orphan.clone());
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
assert!(
view.element(ElementId::new(1)).is_none(),
"subject tombstoned"
);
assert_eq!(
view.property(subject, name).map(Cow::into_owned),
Some(orphan.clone()),
"orphan property is visible through point read"
);
assert!(
view.properties()
.any(|(found_subject, found_key, value)| found_subject == subject
&& found_key == name
&& value.as_ref() == &orphan),
"orphan property is visible through the property iterator"
);
assert!(
view.property_equal(name, &orphan).contains(&subject),
"orphan property is visible through property_equal"
);
}
#[test]
fn snapshot_view_merges() {
let base = Arc::new(small_base());
let next = base_next_ids(&base);
let catalog = base.get().catalog().clone();
let overlay = Arc::new(Overlay::empty(next, catalog));
let base_records = Arc::new(BaseRecords::from_view(base.get()).expect("base records"));
let snapshot = Snapshot::with_shared_base_records(
CheckpointGeneration::new(7),
CommitSeq::new(42),
Arc::clone(&base),
Arc::clone(&overlay),
base_records,
);
assert_eq!(snapshot.generation(), CheckpointGeneration::new(7));
assert_eq!(snapshot.lsn(), CommitSeq::new(42));
let view = snapshot.view();
assert_eq!(view.element_count(), 3);
assert_eq!(view.relation_count(), 2);
}
#[test]
fn with_applied_leaves_parent_frozen() {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let next = base_next_ids(&base);
let catalog = base.get().catalog().clone();
let mut parent_write = WriteOverlay::new(next, catalog);
parent_write.tombstone_element(&records, ElementId::new(1));
let parent = Arc::new(parent_write.freeze());
let parent_pin = Arc::clone(&parent);
let parent_view_before = MergedState::new(&records, &parent_pin);
let parent_count_before = parent_view_before.element_count();
let mut child_write = WriteOverlay::new(parent.next_ids(), parent.catalog().clone());
let fresh = child_write.create_element().expect("fresh");
child_write.tombstone_element(&records, ElementId::new(2));
let child = parent.with_applied(&child_write);
let parent_view_after = MergedState::new(&records, &parent_pin);
assert_eq!(parent_view_after.element_count(), parent_count_before);
assert!(parent_view_after.element(ElementId::new(2)).is_some());
assert!(parent_view_after.element(fresh).is_none());
let child_view = MergedState::new(&records, &child);
assert!(
child_view.element(ElementId::new(1)).is_none(),
"parent tombstone carries"
);
assert!(
child_view.element(ElementId::new(2)).is_none(),
"child tombstone"
);
assert!(child_view.element(fresh).is_some(), "child create");
assert!(child.next_ids().element > parent.next_ids().element);
}
struct Oracle {
elements: HashMap<ElementId, ElementRecord>,
relations: HashMap<RelationId, RelationRecord>,
incidences: HashMap<IncidenceId, IncidenceRecord>,
properties: HashMap<(PropertySubject, PropertyKeyId), PropertyValue>,
}
#[derive(Clone, Debug)]
enum Op {
CreateElement,
CreateRelation,
TombstoneElement(u64),
SetName(u64, String),
RemoveRank(u64),
}
fn apply_to_write(
write: &mut WriteOverlay,
records: &BaseRecords,
name: PropertyKeyId,
rank: PropertyKeyId,
op: &Op,
) {
match op {
Op::CreateElement => {
write.create_element().expect("create element");
}
Op::CreateRelation => {
write.create_relation().expect("create relation");
}
Op::TombstoneElement(raw) => {
write.tombstone_element(records, ElementId::new(1 + (raw % 4)));
}
Op::SetName(raw, text) => {
write.set_property(
records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
name,
PropertyValue::Text(text.clone()),
);
}
Op::RemoveRank(raw) => {
write.remove_property(
records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
rank,
);
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "the oracle apply threads every dimension the overlay tracks (record maps, the two property keys, and the id cursors) so the proptest can mirror exactly one overlay mutation per op"
)]
fn apply_to_oracle(
oracle: &mut Oracle,
name: PropertyKeyId,
rank: PropertyKeyId,
next_element: &mut u64,
next_relation: &mut u64,
op: &Op,
) {
match op {
Op::CreateElement => {
let id = ElementId::new(*next_element);
*next_element += 1;
oracle.elements.insert(
id,
ElementRecord {
id,
labels: BTreeSet::new(),
},
);
}
Op::CreateRelation => {
let id = RelationId::new(*next_relation);
*next_relation += 1;
oracle.relations.insert(
id,
RelationRecord {
id,
relation_type: None,
labels: BTreeSet::new(),
},
);
}
Op::TombstoneElement(raw) => {
let id = ElementId::new(1 + (raw % 4));
oracle.elements.remove(&id);
oracle
.properties
.retain(|(subject, _key), _value| *subject != PropertySubject::Element(id));
}
Op::SetName(raw, text) => {
let id = ElementId::new(1 + (raw % 3));
oracle.properties.insert(
(PropertySubject::Element(id), name),
PropertyValue::Text(text.clone()),
);
}
Op::RemoveRank(raw) => {
let id = ElementId::new(1 + (raw % 3));
oracle
.properties
.remove(&(PropertySubject::Element(id), rank));
}
}
}
fn oracle_from_base(records: &BaseRecords, base_elements: &[ElementRecord]) -> Oracle {
let mut elements = HashMap::new();
for record in base_elements {
elements.insert(record.id, record.clone());
}
let mut relations = HashMap::new();
let mut incidences = HashMap::new();
let mut properties = HashMap::new();
let empty = Overlay::empty(
NextIds {
element: ElementId::new(1),
relation: RelationId::new(1),
incidence: IncidenceId::new(1),
role: RoleId::new(1),
label: crate::LabelId::new(1),
relation_type: crate::RelationTypeId::new(1),
property_key: PropertyKeyId::new(1),
projection: crate::ProjectionId::new(1),
index: crate::IndexId::new(1),
},
crate::Catalog::empty(),
);
let view = MergedState::new(records, &empty);
for relation in view.relations() {
relations.insert(relation.id, relation.into_owned());
}
for incidence in view.incidences() {
incidences.insert(incidence.id, incidence.into_owned());
}
for (subject, key, value) in view.properties() {
properties.insert((subject, key), value.into_owned());
}
Oracle {
elements,
relations,
incidences,
properties,
}
}
#[cfg(miri)]
const MERGE_CASES: u32 = 4;
#[cfg(not(miri))]
const MERGE_CASES: u32 = 256;
proptest! {
#![proptest_config(ProptestConfig::with_cases(MERGE_CASES))]
#[test]
fn merge_matches_oracle(
ops in proptest::collection::vec(
prop_oneof![
Just(Op::CreateElement),
Just(Op::CreateRelation),
any::<u64>().prop_map(Op::TombstoneElement),
(any::<u64>(), "[a-z]{1,5}").prop_map(|(raw, text)| Op::SetName(raw, text)),
any::<u64>().prop_map(Op::RemoveRank),
],
0..24,
)
) {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let name = base.get().catalog().property_key_id("name").expect("name");
let rank = base.get().catalog().property_key_id("rank").expect("rank");
let base_elements: Vec<ElementRecord> = {
let empty = Overlay::empty(base_next_ids(&base), base.get().catalog().clone());
MergedState::new(&records, &empty)
.elements()
.map(Cow::into_owned)
.collect()
};
let mut oracle = oracle_from_base(&records, &base_elements);
let mut write = WriteOverlay::new(base_next_ids(&base), base.get().catalog().clone());
let mut next_element = base_next_ids(&base).element.get();
let mut next_relation = base_next_ids(&base).relation.get();
for op in &ops {
apply_to_write(&mut write, &records, name, rank, op);
apply_to_oracle(&mut oracle, name, rank, &mut next_element, &mut next_relation, op);
}
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
for (id, record) in &oracle.elements {
let read = view.element(*id).map(Cow::into_owned);
prop_assert_eq!(read.as_ref(), Some(record), "element {} mismatch", id.get());
}
for raw in 1u64..=4 {
let id = ElementId::new(raw);
prop_assert_eq!(
view.element(id).is_some(),
oracle.elements.contains_key(&id),
"element {} visibility mismatch",
raw
);
}
let merged_elements: BTreeMap<ElementId, ElementRecord> = view
.elements()
.map(|record| {
let record = record.into_owned();
(record.id, record)
})
.collect();
let oracle_elements: BTreeMap<ElementId, ElementRecord> =
oracle.elements.iter().map(|(id, record)| (*id, record.clone())).collect();
prop_assert_eq!(merged_elements, oracle_elements, "element set mismatch");
let merged_relations: BTreeMap<RelationId, RelationRecord> = view
.relations()
.map(|record| {
let record = record.into_owned();
(record.id, record)
})
.collect();
let oracle_relations: BTreeMap<RelationId, RelationRecord> =
oracle.relations.iter().map(|(id, record)| (*id, record.clone())).collect();
prop_assert_eq!(merged_relations, oracle_relations, "relation set mismatch");
let merged_incidences: BTreeMap<IncidenceId, IncidenceRecord> = view
.incidences()
.map(|record| {
let record = record.into_owned();
(record.id, record)
})
.collect();
let oracle_incidences: BTreeMap<IncidenceId, IncidenceRecord> =
oracle.incidences.iter().map(|(id, record)| (*id, *record)).collect();
prop_assert_eq!(merged_incidences, oracle_incidences, "incidence set mismatch");
let merged_properties: BTreeMap<(PropertySubject, PropertyKeyId), PropertyValue> = view
.properties()
.map(|(subject, key, value)| ((subject, key), value.into_owned()))
.collect();
let oracle_properties: BTreeMap<(PropertySubject, PropertyKeyId), PropertyValue> = oracle
.properties
.iter()
.map(|(pair, value)| (*pair, value.clone()))
.collect();
prop_assert_eq!(merged_properties, oracle_properties, "property set mismatch");
prop_assert_eq!(view.element_count(), oracle.elements.len());
prop_assert_eq!(view.relation_count(), oracle.relations.len());
prop_assert_eq!(view.incidence_count(), oracle.incidences.len());
}
}
#[derive(Clone, Debug)]
enum IndexOp {
LabelElement(u64),
TypeRelation(u64),
SetRank(u64, i64),
RemoveRank(u64),
Tombstone(u64),
TombstoneRelation(u64),
TombstoneIncidence(u64),
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(MERGE_CASES))]
#[test]
fn index_lookups_match_scan_oracle(
ops in proptest::collection::vec(
prop_oneof![
any::<u64>().prop_map(IndexOp::LabelElement),
any::<u64>().prop_map(IndexOp::TypeRelation),
(any::<u64>(), -8i64..8).prop_map(|(raw, value)| IndexOp::SetRank(raw, value)),
any::<u64>().prop_map(IndexOp::RemoveRank),
any::<u64>().prop_map(IndexOp::Tombstone),
any::<u64>().prop_map(IndexOp::TombstoneRelation),
any::<u64>().prop_map(IndexOp::TombstoneIncidence),
],
0..24,
)
) {
let base = small_base();
let records = BaseRecords::from_view(base.get()).expect("base records");
let catalog = base.get().catalog();
let robot = catalog.label_id("Robot").expect("robot label");
let person = catalog.label_id("Person").expect("person label");
let calls = catalog.relation_type_id("calls").expect("calls type");
let name = catalog.property_key_id("name").expect("name key");
let rank = catalog.property_key_id("rank").expect("rank key");
let weight = catalog.property_key_id("weight").expect("weight key");
let slot = catalog.property_key_id("slot").expect("slot key");
let mut write = WriteOverlay::new(base_next_ids(&base), catalog.clone());
for op in &ops {
match op {
IndexOp::LabelElement(raw) => {
write.add_element_label(&records, ElementId::new(1 + (raw % 4)), robot);
}
IndexOp::TypeRelation(raw) => {
write.set_relation_type(&records, RelationId::new(1 + (raw % 2)), calls);
}
IndexOp::SetRank(raw, value) => {
write.set_property(
&records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
rank,
PropertyValue::Integer(*value),
);
}
IndexOp::RemoveRank(raw) => {
write.remove_property(
&records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
rank,
);
}
IndexOp::Tombstone(raw) => {
write.tombstone_element(&records, ElementId::new(1 + (raw % 4)));
}
IndexOp::TombstoneRelation(raw) => {
write.tombstone_relation(&records, RelationId::new(1 + (raw % 2)));
}
IndexOp::TombstoneIncidence(raw) => {
write.tombstone_incidence(&records, IncidenceId::new(1 + (raw % 2)));
}
}
}
let overlay = write.freeze();
let view = MergedState::new(&records, &overlay);
for label in [person, robot] {
prop_assert_eq!(
view.elements_with_label(label),
view.elements_with_label_scan(label),
"label {} membership index != scan",
label.get()
);
}
prop_assert_eq!(
view.relations_with_type(calls),
view.relations_with_type_scan(calls),
"relation-type membership index != scan"
);
let element_ids: Vec<_> = view.elements().map(|record| record.id).collect();
for element in element_ids {
let mut indexed = view.element_incidences(element);
let mut scanned = view.element_incidences_scan(element);
indexed.sort_by_key(|record| record.id);
scanned.sort_by_key(|record| record.id);
prop_assert_eq!(
indexed,
scanned,
"element {} adjacency index != scan",
element.get()
);
}
let relation_ids: Vec<_> = view.relations().map(|record| record.id).collect();
for relation in relation_ids {
let mut indexed = view.relation_incidences(relation);
let mut scanned = view.relation_incidences_scan(relation);
indexed.sort_by_key(|record| record.id);
scanned.sort_by_key(|record| record.id);
prop_assert_eq!(
indexed,
scanned,
"relation {} adjacency index != scan",
relation.get()
);
}
for value in -1i64..=4 {
let probe = PropertyValue::Integer(value);
prop_assert_eq!(
view.property_equal(weight, &probe),
view.property_equal_scan(weight, &probe),
"relation-family equality index != scan for weight={}",
value
);
prop_assert_eq!(
view.property_equal(slot, &probe),
view.property_equal_scan(slot, &probe),
"incidence-family equality index != scan for slot={}",
value
);
}
for value in -9i64..=9 {
let probe = PropertyValue::Integer(value);
prop_assert_eq!(
view.property_equal(rank, &probe),
view.property_equal_scan(rank, &probe),
"equality index != scan for rank={}",
value
);
}
let bob = PropertyValue::Text("Bob".to_owned());
prop_assert_eq!(
view.property_equal(name, &bob),
view.property_equal_scan(name, &bob),
"text equality index != scan"
);
for (lo, hi) in [(-9i64, 9i64), (-3, 3), (0, 0), (2, 1)] {
let min = PropertyValue::Integer(lo);
let max = PropertyValue::Integer(hi);
let mut indexed = view.property_range(rank, &min, &max);
let mut scanned = view.property_range_scan(rank, &min, &max);
indexed.sort();
scanned.sort();
prop_assert_eq!(indexed, scanned, "range index != scan for [{}, {}]", lo, hi);
}
for (rank_value, person_name) in [(-5i64, "Alice"), (0, "Bob"), (7, "Carol")] {
let keys = [rank, name];
let values = [
PropertyValue::Integer(rank_value),
PropertyValue::Text(person_name.to_owned()),
];
let mut indexed = view
.typed_property_composite_equal(&keys, &values)
.expect("composite lookup");
let mut scanned = view.property_composite_equal_scan(&keys, &values);
indexed.sort();
scanned.sort();
prop_assert_eq!(
indexed,
scanned,
"composite index != scan for (rank={}, name={})",
rank_value,
person_name
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(MERGE_CASES))]
#[test]
fn borrowed_index_matches_owned_oracle(
ops in proptest::collection::vec(
prop_oneof![
any::<u64>().prop_map(IndexOp::LabelElement),
any::<u64>().prop_map(IndexOp::TypeRelation),
(any::<u64>(), -8i64..8).prop_map(|(raw, value)| IndexOp::SetRank(raw, value)),
any::<u64>().prop_map(IndexOp::RemoveRank),
any::<u64>().prop_map(IndexOp::Tombstone),
any::<u64>().prop_map(IndexOp::TombstoneRelation),
any::<u64>().prop_map(IndexOp::TombstoneIncidence),
],
0..24,
)
) {
let seed = small_base();
let seed_records = BaseRecords::from_view(seed.get()).expect("seed records");
let catalog = seed.get().catalog();
let robot = catalog.label_id("Robot").expect("robot label");
let person = catalog.label_id("Person").expect("person label");
let calls = catalog.relation_type_id("calls").expect("calls type");
let name = catalog.property_key_id("name").expect("name key");
let rank = catalog.property_key_id("rank").expect("rank key");
let weight = catalog.property_key_id("weight").expect("weight key");
let slot = catalog.property_key_id("slot").expect("slot key");
let mut write = WriteOverlay::new(base_next_ids(&seed), catalog.clone());
for op in &ops {
match op {
IndexOp::LabelElement(raw) => {
write.add_element_label(&seed_records, ElementId::new(1 + (raw % 4)), robot);
}
IndexOp::TypeRelation(raw) => {
write.set_relation_type(&seed_records, RelationId::new(1 + (raw % 2)), calls);
}
IndexOp::SetRank(raw, value) => {
write.set_property(
&seed_records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
rank,
PropertyValue::Integer(*value),
);
}
IndexOp::RemoveRank(raw) => {
write.remove_property(
&seed_records,
PropertySubject::Element(ElementId::new(1 + (raw % 3))),
rank,
);
}
IndexOp::Tombstone(raw) => {
write.tombstone_element(&seed_records, ElementId::new(1 + (raw % 4)));
}
IndexOp::TombstoneRelation(raw) => {
write.tombstone_relation(&seed_records, RelationId::new(1 + (raw % 2)));
}
IndexOp::TombstoneIncidence(raw) => {
write.tombstone_incidence(&seed_records, IncidenceId::new(1 + (raw % 2)));
}
}
}
let folded = write.freeze();
let bytes = freeze_view(
&MergedState::new(&seed_records, &folded),
FreezeStamps { commit_seq: 1, transaction_id: 1, generation: 1 },
)
.expect("freeze folded state");
let base = Arc::new(Base::open_owned_bytes(bytes).expect("open folded base"));
let borrowed = BaseRecords::open(&base).expect("borrowed records");
let owned = BaseRecords::from_view(base.get()).expect("owned records");
let empty = Overlay::empty(base_next_ids(&base), base.get().catalog().clone());
let borrowed_view = MergedState::new(&borrowed, &empty);
let owned_view = MergedState::new(&owned, &empty);
for label in [person, robot, LabelId::new(9999)] {
prop_assert_eq!(
borrowed_view.elements_with_label(label),
owned_view.elements_with_label(label),
"label {} membership borrowed != owned",
label.get()
);
}
for ty in [calls, RelationTypeId::new(9999)] {
prop_assert_eq!(
borrowed_view.relations_with_type(ty),
owned_view.relations_with_type(ty),
"relation-type {} membership borrowed != owned",
ty.get()
);
}
for raw in 1u64..=5 {
let element = ElementId::new(raw);
let mut b = borrowed_view.element_incidences(element);
let mut o = owned_view.element_incidences(element);
b.sort_by_key(|record| record.id);
o.sort_by_key(|record| record.id);
prop_assert_eq!(b, o, "element {} adjacency borrowed != owned", raw);
}
for raw in 1u64..=3 {
let relation = RelationId::new(raw);
let mut b = borrowed_view.relation_incidences(relation);
let mut o = owned_view.relation_incidences(relation);
b.sort_by_key(|record| record.id);
o.sort_by_key(|record| record.id);
prop_assert_eq!(b, o, "relation {} adjacency borrowed != owned", raw);
}
for key in [rank, weight, slot] {
for value in -9i64..=9 {
let probe = PropertyValue::Integer(value);
prop_assert_eq!(
borrowed_view.property_equal(key, &probe),
owned_view.property_equal(key, &probe),
"equality borrowed != owned for key {} value {}",
key.get(),
value
);
}
}
for text in ["Alice", "Bob", "Carol", "Zzz-absent"] {
let probe = PropertyValue::Text(text.to_owned());
prop_assert_eq!(
borrowed_view.property_equal(name, &probe),
owned_view.property_equal(name, &probe),
"text equality borrowed != owned for name {:?}",
text
);
}
for (lo, hi) in [(-9i64, 9i64), (-3, 3), (0, 0), (2, 1), (5, 9)] {
let min = PropertyValue::Integer(lo);
let max = PropertyValue::Integer(hi);
prop_assert_eq!(
borrowed_view.property_range(rank, &min, &max),
owned_view.property_range(rank, &min, &max),
"range borrowed != owned for [{}, {}]",
lo,
hi
);
}
for key in [rank, name, weight, slot, PropertyKeyId::new(9999)] {
prop_assert_eq!(
borrowed_view.property_key_subjects(key),
owned_view.property_key_subjects(key),
"property_key_subjects borrowed != owned for key {}",
key.get()
);
}
prop_assert!(
crate::index::indexes_agree(borrowed.index(), owned.index()),
"borrowed and owned index snapshots disagree"
);
}
}