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 selene_core::{
    DbString, EdgeId, GraphId, LabelSet, NodeId, PropertyMap, Value, VectorValue, db_string,
};

use super::*;
use crate::SharedGraph;

fn key(name: &str) -> DbString {
    db_string(name).expect("test string is valid")
}

fn vector(values: &[f32]) -> Value {
    Value::Vector(VectorValue::new(values.to_vec()).expect("test vector is valid"))
}

fn props(pairs: impl IntoIterator<Item = (DbString, Value)>) -> PropertyMap {
    PropertyMap::from_pairs(pairs).expect("property map is valid")
}

fn empty_node(mutator: &mut Mutator<'_, '_>) -> NodeId {
    mutator
        .create_node(LabelSet::new(), PropertyMap::new())
        .expect("create_node ok")
}

fn payload_node(mutator: &mut Mutator<'_, '_>) -> NodeId {
    mutator
        .create_node(
            LabelSet::single(key("payload.clear.node")),
            props([(key("embedding"), vector(&[1.0, 2.0, 3.0]))]),
        )
        .expect("create_node ok")
}

fn payload_edge(mutator: &mut Mutator<'_, '_>, source: NodeId, target: NodeId) -> EdgeId {
    mutator
        .create_edge(
            key("payload.clear.edge"),
            source,
            target,
            props([(key("embedding"), vector(&[4.0, 5.0, 6.0]))]),
        )
        .expect("create_edge ok")
}

#[test]
fn delete_node_clears_dead_row_payload_but_keeps_id_mapping() {
    let shared = SharedGraph::new(GraphId::new(1));
    let mut txn = shared.begin_write();
    let id = {
        let mut mutator = txn.mutator();
        let id = payload_node(&mut mutator);
        mutator.delete_node(id).expect("delete_node ok");
        id
    };

    txn.commit().expect("commit ok");
    let graph = shared.read();
    let row = graph
        .row_for_node_id(id)
        .expect("dead id remains mapped")
        .get() as usize;
    assert!(!graph.node_store.is_alive(row as u32));
    assert_eq!(graph.node_store.row_to_id.get(row).copied(), Some(id));
    assert!(
        graph
            .node_store
            .labels
            .get(row)
            .expect("dead row keeps column slot")
            .is_empty()
    );
    assert!(
        graph
            .node_store
            .properties
            .get(row)
            .expect("dead row keeps column slot")
            .is_empty()
    );
}

#[test]
fn delete_edge_clears_dead_row_payload_but_keeps_id_mapping() {
    let shared = SharedGraph::new(GraphId::new(1));
    let mut txn = shared.begin_write();
    let (source, target, edge) = {
        let mut mutator = txn.mutator();
        let source = empty_node(&mut mutator);
        let target = empty_node(&mut mutator);
        let edge = payload_edge(&mut mutator, source, target);
        mutator.delete_edge(edge).expect("delete_edge ok");
        (source, target, edge)
    };

    txn.commit().expect("commit ok");
    let graph = shared.read();
    let row = graph
        .row_for_edge_id(edge)
        .expect("dead id remains mapped")
        .get() as usize;
    assert!(graph.is_node_alive(source));
    assert!(graph.is_node_alive(target));
    assert!(!graph.edge_store.is_alive(row as u32));
    assert_eq!(graph.edge_store.row_to_id.get(row).copied(), Some(edge));
    assert_eq!(
        graph
            .edge_store
            .label
            .get(row)
            .expect("dead row keeps column slot")
            .as_str(),
        ""
    );
    assert_eq!(
        graph.edge_store.source.get(row).copied(),
        Some(NodeId::TOMBSTONE)
    );
    assert_eq!(
        graph.edge_store.target.get(row).copied(),
        Some(NodeId::TOMBSTONE)
    );
    assert!(
        graph
            .edge_store
            .properties
            .get(row)
            .expect("dead row keeps column slot")
            .is_empty()
    );
}