kglite 0.17.1

Pure-Rust embedded Cypher knowledge graph engine with in-memory, mmap, and disk storage, and agent-facing schema introspection
Documentation
use super::*;
use serde_json::json;

fn blueprint(ops: serde_json::Value) -> Blueprint {
    serde_json::from_value(json!({"nodes": {}, "compute": ops})).unwrap()
}

#[test]
fn computed_names_are_portable_unique_and_order_independent() {
    let tmp = tempfile::tempdir().unwrap();
    let ops = json!([
        {"op":"derive","from":"A-B","set":{}},
        {"op":"derive","from":"A_B","set":{}},
        {"op":"derive","from":"Name","set":{}},
        {"op":"derive","from":"name","set":{}},
        {"op":"derive","from":"Ordinary","set":{}},
        {"op":"filter","from":"S","into":"X-Y","where":"true"},
        {"op":"filter","from":"S","into":"X_Y","where":"true"},
        {"op":"aggregate","from":"S","into":"Total-A","group_by":[],"agg":{}},
        {"op":"aggregate","from":"S","into":"Total_A","group_by":[],"agg":{}},
        {"op":"chain","from":"S1","edge":"NEXT","group_by":[],"order_by":"id"},
        {"op":"chain","from":"S2","edge":"NEXT","group_by":[],"order_by":"id"},
        {"op":"calendar","type":"Date-A","start":"2026-01-01","end":"2026-01-01"},
        {"op":"calendar","type":"Date_A","start":"2026-01-02","end":"2026-01-02"}
    ]);
    let mut bp = blueprint(ops);
    let a = ComputePaths::new(&bp, tmp.path(), &bp.compute).unwrap();
    bp.compute.reverse();
    let b = ComputePaths::new(&bp, tmp.path(), &bp.compute).unwrap();
    assert_eq!(a.0, b.0);
    assert_eq!(
        a.relative(&Output::Derive("Ordinary".into())),
        "computed/Ordinary_derived.csv"
    );
    let folded: BTreeSet<_> = a.0.values().map(|name| name.to_ascii_lowercase()).collect();
    assert_eq!(folded.len(), a.0.len());
    assert_eq!(a.0.len(), 15);
    assert!(a
        .0
        .values()
        .all(|name| name.starts_with("computed/") && name.len() <= 129));
}

#[test]
fn computed_paths_reserve_existing_entries_and_all_declared_source_kinds() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir(tmp.path().join("computed")).unwrap();
    let reserved = tmp.path().join("computed/T_DERIVED.CSV");
    std::fs::write(&reserved, b"unrelated bytes").unwrap();
    let bp: Blueprint = serde_json::from_value(json!({
        "files":{"source":{"path":"computed/compute_0.csv"}},
        "nodes":{"Outer":{"csv":"computed/compute_1.csv","sub_nodes":{"Inner":{"csv":"computed/compute_2.csv"}},
            "connections":{"junction_edges":{"R":{"csv":"computed/compute_3.csv","source_fk":"id","target":"Outer","target_fk":"id"}}}}},
        "compute":[{"op":"derive","from":"T","set":{}}]
    })).unwrap();
    let paths = ComputePaths::new(&bp, tmp.path(), &bp.compute).unwrap();
    assert_eq!(
        paths.relative(&Output::Derive("T".into())),
        "computed/compute_4.csv"
    );
    assert_eq!(std::fs::read(&reserved).unwrap(), b"unrelated bytes");
}

#[test]
fn repeated_owner_reuses_only_current_map_and_fresh_invocation_reserves_old_output() {
    let tmp = tempfile::tempdir().unwrap();
    std::fs::create_dir(tmp.path().join("computed")).unwrap();
    let bp = blueprint(json!([
        {"op":"derive","from":"T","set":{}}, {"op":"derive","from":"T","set":{}}
    ]));
    let first = ComputePaths::new(&bp, tmp.path(), &bp.compute).unwrap();
    assert_eq!(first.0.len(), 1);
    let relative = first.relative(&Output::Derive("T".into()));
    std::fs::write(tmp.path().join(&relative), b"prior completed output").unwrap();
    assert_eq!(first.relative(&Output::Derive("T".into())), relative);
    let next = ComputePaths::new(&bp, tmp.path(), &bp.compute).unwrap();
    assert_ne!(next.relative(&Output::Derive("T".into())), relative);
    assert_eq!(
        std::fs::read(tmp.path().join(relative)).unwrap(),
        b"prior completed output"
    );
}