selene-db-graph 1.3.0

In-memory property-graph storage core (ArcSwap + imbl CoW, label/typed indexes, write funnel) for selene-db.
Documentation
use super::*;

#[test]
fn apply_node_create_populates_matching_indexes() {
    let label = db_string("pi.create.label").unwrap();
    let age = db_string("pi.create.age").unwrap();
    let name = db_string("pi.create.name").unwrap();
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));
    indexes.insert((label.clone(), name.clone()), entry(TypedIndexKind::String));
    let props = property_map([
        (age.clone(), Value::Int(30)),
        (
            name.clone(),
            Value::String(db_string("pi.create.ada").unwrap()),
        ),
    ]);

    apply_node_create(&mut indexes, &LabelSet::single(label.clone()), &props, 0).unwrap();

    assert!(rows(&indexes, label.clone(), age, &Value::Int(30)).contains(0));
    assert!(
        rows(
            &indexes,
            label,
            name,
            &Value::String(db_string("pi.create.ada").unwrap())
        )
        .contains(0)
    );
}

#[test]
fn apply_node_delete_removes_matching_entries() {
    let label = db_string("pi.delete.label").unwrap();
    let age = db_string("pi.delete.age").unwrap();
    let props = property_map([(age.clone(), Value::Int(30))]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));
    apply_node_create(&mut indexes, &LabelSet::single(label.clone()), &props, 4).unwrap();

    apply_node_delete(&mut indexes, &LabelSet::single(label.clone()), &props, 4).unwrap();

    assert!(rows(&indexes, label, age, &Value::Int(30)).is_empty());
}

#[test]
fn apply_node_update_with_label_add_inserts_relevant_property() {
    let label = db_string("pi.update.label-add").unwrap();
    let age = db_string("pi.update.label-add.age").unwrap();
    let props = property_map([(age.clone(), Value::Int(41))]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));

    apply_node_update(
        &mut indexes,
        &LabelSet::new(),
        &props,
        &LabelSet::single(label.clone()),
        &props,
        8,
    )
    .unwrap();

    assert!(rows(&indexes, label, age, &Value::Int(41)).contains(8));
}

#[test]
fn apply_node_update_with_label_remove_deletes_relevant_property() {
    let label = db_string("pi.update.label-remove").unwrap();
    let age = db_string("pi.update.label-remove.age").unwrap();
    let props = property_map([(age.clone(), Value::Int(41))]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));
    apply_node_create(&mut indexes, &LabelSet::single(label.clone()), &props, 8).unwrap();

    apply_node_update(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &props,
        &LabelSet::new(),
        &props,
        8,
    )
    .unwrap();

    assert!(rows(&indexes, label, age, &Value::Int(41)).is_empty());
}

#[test]
fn apply_node_update_with_property_set_moves_rows_between_keys() {
    let label = db_string("pi.update.prop-set").unwrap();
    let age = db_string("pi.update.prop-set.age").unwrap();
    let old_props = property_map([(age.clone(), Value::Int(41))]);
    let new_props = property_map([(age.clone(), Value::Int(42))]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));
    apply_node_create(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        8,
    )
    .unwrap();

    apply_node_update(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        &LabelSet::single(label.clone()),
        &new_props,
        8,
    )
    .unwrap();

    assert!(rows(&indexes, label.clone(), age.clone(), &Value::Int(41)).is_empty());
    assert!(rows(&indexes, label, age, &Value::Int(42)).contains(8));
}

#[test]
fn apply_node_update_with_property_remove_drops_row() {
    let label = db_string("pi.update.prop-remove").unwrap();
    let age = db_string("pi.update.prop-remove.age").unwrap();
    let old_props = property_map([(age.clone(), Value::Int(41))]);
    let new_props = PropertyMap::new();
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));
    apply_node_create(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        8,
    )
    .unwrap();

    apply_node_update(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        &LabelSet::single(label.clone()),
        &new_props,
        8,
    )
    .unwrap();

    assert!(rows(&indexes, label, age, &Value::Int(41)).is_empty());
}

#[test]
fn kind_mismatch_skips_commit_update() {
    let label = db_string("pi.kind.label").unwrap();
    let age = db_string("pi.kind.age").unwrap();
    let props = property_map([(
        age.clone(),
        Value::String(db_string("pi.kind.old").unwrap()),
    )]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));

    apply_node_create(&mut indexes, &LabelSet::single(label.clone()), &props, 0).unwrap();

    assert_eq!(indexes.get(&(label, age)).unwrap().index.cardinality(), 0);
}

#[test]
fn null_values_are_skipped() {
    let label = db_string("pi.null.label").unwrap();
    let age = db_string("pi.null.age").unwrap();
    let props = property_map([(age.clone(), Value::Null)]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age.clone()), entry(TypedIndexKind::I64));

    apply_node_create(&mut indexes, &LabelSet::single(label.clone()), &props, 0).unwrap();

    assert_eq!(indexes.get(&(label, age)).unwrap().index.cardinality(), 0);
}

#[test]
fn untouched_indexes_keep_their_arc() {
    let label = db_string("pi.cow.label").unwrap();
    let age = db_string("pi.cow.age").unwrap();
    let name = db_string("pi.cow.name").unwrap();
    let old_props = property_map([
        (age.clone(), Value::Int(1)),
        (
            name.clone(),
            Value::String(db_string("pi.cow.ada").unwrap()),
        ),
    ]);
    let new_props = property_map([
        (age.clone(), Value::Int(2)),
        (
            name.clone(),
            Value::String(db_string("pi.cow.ada").unwrap()),
        ),
    ]);
    let mut indexes = PropertyIndexMap::default();
    indexes.insert((label.clone(), age), entry(TypedIndexKind::I64));
    indexes.insert((label.clone(), name.clone()), entry(TypedIndexKind::String));
    apply_node_create(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        0,
    )
    .unwrap();
    let name_index = Arc::clone(&indexes.get(&(label.clone(), name.clone())).unwrap().index);

    apply_node_update(
        &mut indexes,
        &LabelSet::single(label.clone()),
        &old_props,
        &LabelSet::single(label.clone()),
        &new_props,
        0,
    )
    .unwrap();

    assert!(Arc::ptr_eq(
        &name_index,
        &indexes.get(&(label, name)).unwrap().index
    ));
}

#[test]
fn apply_property_diff_remove_is_covered_by_update_shape() {
    let key = db_string("pi.diff.key").unwrap();
    let diff = PropertyDiff::new([], [key.clone()]).unwrap();
    assert_eq!(diff.removed.iter().cloned().collect::<Vec<_>>(), vec![key]);
}