kglite 0.17.11

Pure-Rust embedded Cypher knowledge graph engine with in-memory, mmap, and disk storage, and agent-facing schema introspection
Documentation
use super::*;
use crate::graph::dir_graph::index_layer::LayeredIndex;
use crate::graph::dir_graph::range_index_layer::LayeredRangeIndex;
use std::collections::{BTreeMap, HashMap};
use std::ops::Bound::{Excluded, Included, Unbounded};

fn indexed(values: Vec<Value>) -> DirGraph {
    let mut graph = DirGraph::new();
    let mut hash = HashMap::new();
    let mut range = BTreeMap::new();
    for (slot, value) in values.into_iter().enumerate() {
        let nodes = vec![NodeIndex::new(slot)];
        hash.insert(value.clone(), nodes.clone());
        range.insert(value, nodes);
    }
    graph
        .property_indices
        .insert(("N".into(), "v".into()), LayeredIndex::from(hash));
    graph
        .range_indices
        .insert(("N".into(), "v".into()), LayeredRangeIndex::from(range));
    graph
}
fn slots(hits: Option<Vec<NodeIndex>>) -> Vec<usize> {
    let mut values: Vec<_> = hits
        .expect("the ordinary query must use the index")
        .into_iter()
        .map(NodeIndex::index)
        .collect();
    values.sort_unstable();
    values
}

#[test]
fn numeric_equality_index_admits_every_equivalent_variant() {
    let graph = indexed(vec![
        Value::UniqueId(1),
        Value::Int64(1),
        Value::Float64(1.0),
        Value::Int64(2),
    ]);
    for probe in [Value::UniqueId(1), Value::Int64(1), Value::Float64(1.0)] {
        assert_eq!(
            slots(graph.lookup_by_index("N", "v", &probe)),
            vec![0, 1, 2]
        );
    }
    assert_eq!(
        slots(graph.lookup_by_index("N", "v", &Value::Int64(9))),
        Vec::<usize>::new()
    );
    for probe in [
        Value::Int64(1 << 53),
        Value::Float64(f64::INFINITY),
        Value::List(vec![Value::Null]),
    ] {
        assert!(graph.lookup_by_index("N", "v", &probe).is_none());
    }
}

/// The index answer is treated as complete (no post-verification), so its key
/// set has to mirror predicate equality exactly. A date equals midnight on
/// that date, so a probe for either must find both.
#[test]
fn temporal_equality_index_admits_the_date_and_its_midnight() {
    let date = chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
    let graph = indexed(vec![
        Value::DateTime(date),
        Value::Timestamp(date.and_hms_opt(0, 0, 0).unwrap()),
        Value::Timestamp(date.and_hms_opt(12, 0, 0).unwrap()),
        Value::DateTime(chrono::NaiveDate::from_ymd_opt(2024, 3, 16).unwrap()),
    ]);
    for probe in [
        Value::DateTime(date),
        Value::Timestamp(date.and_hms_opt(0, 0, 0).unwrap()),
    ] {
        assert_eq!(slots(graph.lookup_by_index("N", "v", &probe)), vec![0, 1]);
    }
    assert_eq!(
        slots(graph.lookup_by_index(
            "N",
            "v",
            &Value::Timestamp(date.and_hms_opt(12, 0, 0).unwrap())
        )),
        vec![2]
    );
}

#[test]
fn numeric_range_translates_bounds_without_losing_variants() {
    let graph = indexed(vec![
        Value::UniqueId(1),
        Value::Int64(1),
        Value::Float64(1.0),
        Value::Float64(1.5),
        Value::Int64(2),
        Value::Null,
        Value::Float64(f64::NAN),
    ]);
    let one = Value::Float64(1.0);
    let two = Value::Int64(2);
    assert_eq!(
        slots(graph.lookup_range("N", "v", Included(&one), Included(&one))),
        vec![0, 1, 2]
    );
    assert_eq!(
        slots(graph.lookup_range("N", "v", Excluded(&one), Excluded(&two))),
        vec![3]
    );
    assert_eq!(
        slots(graph.lookup_range("N", "v", Unbounded, Included(&one))),
        vec![0, 1, 2, 5]
    );
    assert_eq!(
        slots(graph.lookup_range("N", "v", Excluded(&two), Unbounded)),
        Vec::<usize>::new()
    );
    assert_eq!(
        slots(graph.lookup_range("N", "v", Included(&two), Included(&one))),
        Vec::<usize>::new()
    );
    assert!(graph
        .lookup_range("N", "v", Included(&Value::Int64(1 << 53)), Unbounded)
        .is_none());
}

#[test]
fn string_index_expands_existing_wrapper_equivalence() {
    let graph = indexed(vec![
        Value::String("Oslo".into()),
        Value::String("[\"Oslo\"]".into()),
        Value::String("Bergen".into()),
    ]);
    for probe in ["Oslo", "[\"Oslo\"]"] {
        assert_eq!(
            slots(graph.lookup_by_index("N", "v", &Value::String(probe.into()))),
            vec![0, 1]
        );
    }
    let oslo = Value::String("Oslo".into());
    assert_eq!(
        slots(graph.lookup_range("N", "v", Included(&oslo), Included(&oslo))),
        vec![0]
    );
}

#[test]
fn soft_alias_ranges_decline_despite_stored_property_hits() {
    let mut graph = indexed(vec![Value::String("Ann".into())]);
    let index = graph
        .range_indices
        .remove(&("N".into(), "v".into()))
        .unwrap();
    graph
        .range_indices
        .insert(("N".into(), "name".into()), index);
    let lower = Value::String("A".into());
    assert!(graph
        .lookup_range("N", "name", Included(&lower), Unbounded)
        .is_none());
}

fn composite_indexed(properties: &[String], tuples: Vec<Vec<Value>>) -> DirGraph {
    let mut graph = DirGraph::new();
    let mut index = HashMap::new();
    for (slot, values) in tuples.into_iter().enumerate() {
        index
            .entry(CompositeValue(values))
            .or_insert_with(Vec::new)
            .push(NodeIndex::new(slot));
    }
    graph
        .composite_indices
        .insert(("N".into(), properties.to_vec()), LayeredIndex::from(index));
    graph
}

#[test]
fn composite_predicate_admits_all_numeric_tuples_and_keeps_raw_keys_exact() {
    let names = vec!["a".into(), "b".into()];
    let first = [Value::Int64(1), Value::Float64(1.0), Value::UniqueId(1)];
    let second = [Value::Int64(2), Value::Float64(2.0), Value::UniqueId(2)];
    let tuples = first
        .iter()
        .flat_map(|a| second.iter().map(move |b| vec![a.clone(), b.clone()]))
        .collect();
    let graph = composite_indexed(&names, tuples);
    for a in &first {
        for b in &second {
            assert_eq!(
                slots(graph.lookup_by_composite_predicate("N", &names, &[a.clone(), b.clone()])),
                (0..9).collect::<Vec<_>>()
            );
            assert_eq!(
                slots(graph.lookup_by_composite_predicate(
                    "N",
                    &["b".into(), "a".into()],
                    &[b.clone(), a.clone()]
                )),
                (0..9).collect::<Vec<_>>()
            );
        }
    }
    assert_eq!(
        slots(graph.lookup_by_composite_index("N", &names, &[first[0].clone(), second[0].clone()])),
        vec![0]
    );
    assert_eq!(
        slots(graph.lookup_by_composite_predicate(
            "N",
            &names,
            &[Value::Int64(9), Value::Int64(2)]
        )),
        Vec::<usize>::new()
    );
}

#[test]
fn composite_predicate_string_families_are_not_transitive() {
    let names = vec!["a".into(), "b".into()];
    let plain = Value::String("Oslo".into());
    let wrapped = Value::String("[\"Oslo\"]".into());
    let nested = Value::String("[\"[\"Oslo\"]\"]".into());
    let graph = composite_indexed(
        &names,
        vec![
            vec![plain.clone(), Value::Int64(1)],
            vec![wrapped.clone(), Value::Float64(1.0)],
            vec![nested, Value::UniqueId(1)],
        ],
    );
    assert_eq!(
        slots(graph.lookup_by_composite_predicate("N", &names, &[plain, Value::Int64(1)])),
        vec![0, 1]
    );
    assert_eq!(
        slots(graph.lookup_by_composite_predicate("N", &names, &[wrapped, Value::Int64(1)])),
        vec![0, 1, 2]
    );
}

#[test]
fn composite_predicate_declines_unproved_domains_without_partial_hits() {
    let names = vec!["a".into(), "b".into()];
    let graph = composite_indexed(&names, vec![vec![Value::Int64(1), Value::Int64(2)]]);
    for value in [Value::Int64(1 << 53), Value::List(vec![Value::Null])] {
        assert!(graph
            .lookup_by_composite_predicate("N", &names, &[Value::Int64(1), value])
            .is_none());
    }
    for value in [Value::Null, Value::Float64(f64::NAN)] {
        assert!(
            slots(graph.lookup_by_composite_predicate("N", &names, &[Value::Int64(1), value]))
                .is_empty()
        );
    }
    assert!(graph
        .lookup_by_composite_predicate("Absent", &names, &[Value::Null, Value::Int64(2)])
        .is_none());
    assert!(graph
        .lookup_by_composite_predicate("N", &names, &[Value::Int64(1)])
        .is_none());
}

#[test]
fn composite_predicate_probe_cap_is_all_or_nothing() {
    for (width, admitted) in [(6, true), (7, false)] {
        let names: Vec<_> = (0..width).map(|i| format!("p{i}")).collect();
        let values = vec![Value::String("plain".into()); width];
        let graph = composite_indexed(&names, vec![values.clone()]);
        let hits = graph.lookup_by_composite_predicate("N", &names, &values);
        if admitted {
            assert_eq!(slots(hits), vec![0]);
        } else {
            assert!(hits.is_none());
        }
    }
}

#[test]
fn composite_predicate_keeps_registered_aliases_and_declines_soft_aliases() {
    let names = vec!["rid".into(), "title_alias".into()];
    let values = vec![Value::Int64(1), Value::String("Oslo".into())];
    let mut graph = composite_indexed(&names, vec![values.clone()]);
    std::sync::Arc::make_mut(&mut graph.id_field_aliases).insert("N".into(), "rid".into());
    std::sync::Arc::make_mut(&mut graph.title_field_aliases)
        .insert("N".into(), "title_alias".into());
    assert_eq!(
        slots(graph.lookup_by_composite_predicate("N", &names, &values)),
        vec![0]
    );
    let names = vec!["name".into(), "v".into()];
    let values = vec![Value::String("Oslo".into()), Value::Boolean(true)];
    let graph = composite_indexed(&names, vec![values.clone()]);
    assert!(graph
        .lookup_by_composite_predicate("N", &names, &values)
        .is_none());
}