use std::collections::{BTreeMap, BTreeSet};
use rete_core::{
build_pyramid_meta, write_dataset, DictionaryBuilder, GraphIndexBuilder, Rete,
DEFAULT_TILE_BUDGET,
};
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0 >> 16
}
fn upto(&mut self, n: usize) -> usize {
(self.next() as usize) % n
}
}
fn term_pool(n: usize) -> (Vec<String>, Vec<String>, Vec<String>) {
let nodes: Vec<String> = (0..n).map(|i| format!("<http://ex/node/{i}>")).collect();
let preds: Vec<String> = (0..(n / 4).max(1))
.map(|i| format!("<http://ex/p/{i}>"))
.collect();
let mut lits: Vec<String> = (0..(n / 3).max(1))
.map(|i| format!("\"value number {i}\""))
.collect();
lits.push(r#""he said \"hi\" then left""#.to_string());
lits.push(r#""line1\nline2\tindented""#.to_string());
lits.push(r#""back\\slash and a \"quote\"""#.to_string());
(nodes, preds, lits)
}
fn check_seed(seed: u64, n_terms: usize, n_triples: usize, with_pyramid: bool) {
let mut rng = Lcg(seed);
let (nodes, preds, lits) = term_pool(n_terms);
let mut want: BTreeSet<(String, String, String)> = BTreeSet::new();
for _ in 0..n_triples {
let s = nodes[rng.upto(nodes.len())].clone();
let p = preds[rng.upto(preds.len())].clone();
let o = if rng.upto(2) == 0 {
nodes[rng.upto(nodes.len())].clone()
} else {
lits[rng.upto(lits.len())].clone()
};
want.insert((s, p, o));
}
let mut db = DictionaryBuilder::new();
for (s, p, o) in &want {
db.observe(s, p, o);
}
let dict = db.build();
let ids: Vec<_> = want
.iter()
.map(|(s, p, o)| dict.encode(s, p, o).expect("just-observed term"))
.collect();
let mut ib = GraphIndexBuilder::new();
for &t in &ids {
ib.push(t);
}
let image = if with_pyramid {
let (meta, levels) = build_pyramid_meta(&dict, &ids, DEFAULT_TILE_BUDGET);
rete_core::write_file(&dict, &ib.build(), false, &meta, levels)
} else {
rete_core::write_file(&dict, &ib.build(), false, &[], 0)
};
let rete = Rete::open(&image).expect("our own image opens");
let got: BTreeSet<(String, String, String)> = rete.dump(None).into_iter().collect();
assert_eq!(
got, want,
"round-trip mismatch for seed {seed} ({n_triples} triples, pyramid={with_pyramid})"
);
}
#[test]
fn roundtrip_many_shapes() {
for seed in 0..40u64 {
check_seed(seed, 6, 3, false); check_seed(seed, 30, 60, false); check_seed(seed, 80, 300, true); }
}
#[test]
fn roundtrip_single_triple() {
check_seed(12345, 2, 1, false);
}
#[test]
fn roundtrip_at_scale() {
check_seed(2024, 2500, 9000, true);
}
#[test]
fn roundtrip_named_graphs() {
for seed in 0..20u64 {
let mut rng = Lcg(seed);
let (nodes, preds, _lits) = term_pool(30);
let graphs: Vec<String> = (0..4).map(|i| format!("<http://ex/g/{i}>")).collect();
let mut want: BTreeMap<String, BTreeSet<(String, String, String)>> = BTreeMap::new();
for _ in 0..120 {
let g = graphs[rng.upto(graphs.len())].clone();
let s = nodes[rng.upto(nodes.len())].clone();
let p = preds[rng.upto(preds.len())].clone();
let o = nodes[rng.upto(nodes.len())].clone();
want.entry(g).or_default().insert((s, p, o));
}
let mut db = DictionaryBuilder::new();
for set in want.values() {
for (s, p, o) in set {
db.observe(s, p, o);
}
}
let dict = db.build();
let mut named: Vec<(String, _)> = Vec::new();
for (g, set) in &want {
let mut ib = GraphIndexBuilder::new();
for (s, p, o) in set {
ib.push(dict.encode(s, p, o).expect("observed term"));
}
named.push((g.clone(), ib.build()));
}
let default = GraphIndexBuilder::new().build();
let image = write_dataset(&dict, &default, &named, true, &[], 0);
let rete = Rete::open(&image).expect("dataset image opens");
for (g, set) in &want {
let got: BTreeSet<(String, String, String)> = rete.dump(Some(g)).into_iter().collect();
assert_eq!(
&got, set,
"named-graph {g} round-trip mismatch (seed {seed})"
);
}
}
}
#[test]
fn roundtrip_dense_small_alphabet() {
check_seed(999, 4, 500, true);
}