use rete_core::{
build_pyramid_meta, eval_query, eval_sparql, eval_sparql_reasoned, write_dataset, write_file,
DictionaryBuilder, GraphIndexBuilder, QueryOutput, Rete, DEFAULT_TILE_BUDGET,
};
const XSD_INT: &str = "<http://www.w3.org/2001/XMLSchema#integer>";
fn build(triples: &[(&str, &str, &str)]) -> Vec<u8> {
let mut db = DictionaryBuilder::new();
for (s, p, o) in triples {
db.observe(s, p, o);
}
let dict = db.build();
let ids: Vec<_> = triples
.iter()
.map(|(s, p, o)| dict.encode(s, p, o).expect("known term"))
.collect();
let mut ib = GraphIndexBuilder::new();
for &t in &ids {
ib.push(t);
}
let (meta, levels) = build_pyramid_meta(&dict, &ids, DEFAULT_TILE_BUDGET);
write_file(&dict, &ib.build(), false, &meta, levels)
}
fn dataset() -> Vec<u8> {
let int = |n: &str| format!("\"{n}\"^^{XSD_INT}");
let t: Vec<(String, String, String)> = vec![
("Alice", "name", "\"Alice\""),
("Bob", "name", "\"Bob\""),
("Carol", "name", "\"Carol\""),
("Dave", "name", "\"Dave\""),
("Eve", "name", "\"Eve\""),
("Alice", "age", &int("30")),
("Bob", "age", &int("25")),
("Carol", "age", &int("35")),
("Dave", "age", &int("40")),
("Alice", "city", "City:NYC"),
("Bob", "city", "City:LA"),
("Carol", "city", "City:NYC"),
("Alice", "knows", "Bob"),
("Bob", "knows", "Carol"),
("Carol", "knows", "Dave"),
("Dave", "knows", "Eve"),
("Alice", "knows", "Carol"),
]
.into_iter()
.map(|(s, p, o)| (iri(s), iri(p), term(o)))
.collect();
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
build(&refs)
}
fn iri(s: &str) -> String {
format!("<http://ex/{s}>")
}
fn term(o: &str) -> String {
if o.starts_with('"') {
o.to_string()
} else if let Some(c) = o.strip_prefix("City:") {
format!("<http://ex/city/{c}>")
} else {
iri(o)
}
}
const PREFIX: &str = "PREFIX ex: <http://ex/> ";
fn col(rete: &Rete, q: &str, var: &str) -> Vec<String> {
let (_, sols) = eval_sparql(rete, &format!("{PREFIX}{q}")).unwrap();
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get(var).cloned()).collect();
v.sort();
v
}
#[test]
fn rdf_star_ingest_header_flag_and_concrete_query() {
use rete_core::ingest::{assemble_dataset, parse};
let rdf = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let nt = format!(
"<http://ex/occ1> {rdf} <http://ex/Swallow> .\n\
<< <http://ex/occ1> {rdf} <http://ex/Swallow> >> <http://ex/recordedBy> \"J. Smith\" .\n\
<< <http://ex/occ1> {rdf} <http://ex/Swallow> >> <http://ex/count> \"5\" .\n"
);
let quads: Vec<_> = parse(&nt)
.unwrap()
.into_iter()
.map(|(s, p, o)| (s, p, o, None))
.collect();
let (image, _) = assemble_dataset(quads, &[]);
let rete = Rete::open(&image).unwrap();
assert!(rete.header().has_quoted_triples());
let who = col(
&rete,
&format!(
"SELECT ?who WHERE {{ << <http://ex/occ1> {rdf} <http://ex/Swallow> >> \
<http://ex/recordedBy> ?who }}"
),
"who",
);
assert_eq!(who, vec!["\"J. Smith\"".to_string()]);
let subj = col(&rete, "SELECT ?s WHERE { ?s <http://ex/count> \"5\" }", "s");
assert_eq!(subj.len(), 1);
assert!(subj[0].starts_with("<<") && subj[0].ends_with(">>"));
let annotated_subjects = col(
&rete,
"SELECT ?s WHERE { ?qt <http://ex/recordedBy> ?who FILTER(isTRIPLE(?qt)) \
BIND(SUBJECT(?qt) AS ?s) }",
"s",
);
assert_eq!(annotated_subjects, vec!["<http://ex/occ1>".to_string()]);
let built = col(
&rete,
"SELECT ?t WHERE { <http://ex/occ1> ?p ?o \
BIND(TRIPLE(<http://ex/occ1>, ?p, ?o) AS ?t) }",
"t",
);
assert_eq!(
built,
vec![format!("<<<http://ex/occ1> {rdf} <http://ex/Swallow>>>")]
);
}
#[test]
fn rdf_star_quoted_triple_patterns() {
use rete_core::ingest::{assemble_dataset, parse};
let rdf = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let nt = format!(
"<http://ex/occ1> {rdf} <http://ex/Swallow> .\n\
<< <http://ex/occ1> {rdf} <http://ex/Swallow> >> <http://ex/recordedBy> \"J. Smith\" .\n\
<http://ex/occ2> {rdf} <http://ex/Robin> .\n\
<< <http://ex/occ2> {rdf} <http://ex/Robin> >> <http://ex/recordedBy> \"A. Jones\" .\n\
<http://ex/occ1> <http://ex/place> \"Wetland\" .\n"
);
let quads: Vec<_> = parse(&nt)
.unwrap()
.into_iter()
.map(|(s, p, o)| (s, p, o, None))
.collect();
let (image, _) = assemble_dataset(quads, &[]);
let rete = Rete::open(&image).unwrap();
let species = col(
&rete,
&format!("SELECT ?o WHERE {{ << ?s {rdf} ?o >> <http://ex/recordedBy> ?who }}"),
"o",
);
assert_eq!(
species,
vec![
"<http://ex/Robin>".to_string(),
"<http://ex/Swallow>".to_string()
]
);
let who = col(
&rete,
&format!(
"SELECT ?who WHERE {{ << ?s {rdf} <http://ex/Swallow> >> <http://ex/recordedBy> ?who }}"
),
"who",
);
assert_eq!(who, vec!["\"J. Smith\"".to_string()]);
let joined = col(
&rete,
&format!(
"SELECT ?who WHERE {{ ?occ <http://ex/place> ?p . \
<< ?occ {rdf} ?sp >> <http://ex/recordedBy> ?who }}"
),
"who",
);
assert_eq!(joined, vec!["\"J. Smith\"".to_string()]);
}
#[test]
fn rdf_star_multiple_annotations_on_one_quoted_triple() {
use rete_core::ingest::{assemble_dataset, parse};
let rdf = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let nt = format!(
"<http://ex/occ1> {rdf} <http://ex/Swallow> .\n\
<< <http://ex/occ1> {rdf} <http://ex/Swallow> >> <http://ex/by> \"Smith\" .\n\
<< <http://ex/occ1> {rdf} <http://ex/Swallow> >> <http://ex/n> \"5\" .\n\
<http://ex/occ2> {rdf} <http://ex/Robin> .\n\
<< <http://ex/occ2> {rdf} <http://ex/Robin> >> <http://ex/by> \"Jones\" .\n\
<http://ex/occ3> {rdf} <http://ex/Swallow> .\n\
<< <http://ex/occ3> {rdf} <http://ex/Swallow> >> <http://ex/by> \"Lee\" .\n\
<< <http://ex/occ3> {rdf} <http://ex/Swallow> >> <http://ex/n> \"3\" .\n"
);
let quads: Vec<_> = parse(&nt)
.unwrap()
.into_iter()
.map(|(s, p, o)| (s, p, o, None))
.collect();
let (image, _) = assemble_dataset(quads, &[]);
let rete = Rete::open(&image).unwrap();
let who = col(
&rete,
&format!(
"SELECT ?who WHERE {{ << ?s {rdf} ?o >> <http://ex/by> ?who ; <http://ex/n> ?count }}"
),
"who",
);
assert_eq!(who, vec!["\"Lee\"".to_string(), "\"Smith\"".to_string()]);
let counts = col(
&rete,
&format!(
"SELECT ?count WHERE {{ << ?s {rdf} ?o >> <http://ex/by> ?who ; <http://ex/n> ?count }}"
),
"count",
);
assert_eq!(counts, vec!["\"3\"".to_string(), "\"5\"".to_string()]);
}
#[test]
fn owl_ql_subclass_reasoning() {
let ty = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("Sparrow"), sub.to_string(), mk("Passerine")),
(mk("Passerine"), sub.to_string(), mk("Bird")),
(mk("Eagle"), sub.to_string(), mk("Raptor")),
(mk("Raptor"), sub.to_string(), mk("Bird")),
(mk("occ1"), ty.to_string(), mk("Sparrow")),
(mk("occ2"), ty.to_string(), mk("Eagle")),
(mk("occ3"), ty.to_string(), mk("Bird")),
];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let sorted = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get("x").cloned()).collect();
v.sort();
v
};
let q = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Bird }}");
assert_eq!(sorted(eval_sparql(&rete, &q).unwrap().1), vec![mk("occ3")]);
assert_eq!(
sorted(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![mk("occ1"), mk("occ2"), mk("occ3")]
);
let qp = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Passerine }}");
assert!(eval_sparql(&rete, &qp).unwrap().1.is_empty());
assert_eq!(
sorted(eval_sparql_reasoned(&rete, &qp).unwrap().1),
vec![mk("occ1")]
);
let ql = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Sparrow }}");
assert_eq!(
sorted(eval_sparql_reasoned(&rete, &ql).unwrap().1),
vec![mk("occ1")]
);
}
#[test]
fn owl_ql_subproperty_reasoning() {
let subp = "<http://www.w3.org/2000/01/rdf-schema#subPropertyOf>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("hasFather"), subp.to_string(), mk("hasParent")),
(mk("hasMother"), subp.to_string(), mk("hasParent")),
(mk("a"), mk("hasFather"), mk("b")),
(mk("c"), mk("hasMother"), mk("d")),
(mk("e"), mk("hasParent"), mk("f")), ];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let pairs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols
.iter()
.map(|b| format!("{}->{}", b.get("x").unwrap(), b.get("y").unwrap()))
.collect();
v.sort();
v
};
let q = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:hasParent ?y }}");
assert_eq!(
pairs(eval_sparql(&rete, &q).unwrap().1),
vec!["<http://ex/e>-><http://ex/f>"]
);
assert_eq!(
pairs(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![
"<http://ex/a>-><http://ex/b>",
"<http://ex/c>-><http://ex/d>",
"<http://ex/e>-><http://ex/f>",
]
);
let ql = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:hasFather ?y }}");
assert_eq!(
pairs(eval_sparql_reasoned(&rete, &ql).unwrap().1),
vec!["<http://ex/a>-><http://ex/b>"]
);
}
#[test]
fn owl_ql_domain_range_reasoning() {
let dom = "<http://www.w3.org/2000/01/rdf-schema#domain>";
let rng = "<http://www.w3.org/2000/01/rdf-schema#range>";
let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("worksAt"), dom.to_string(), mk("Employee")),
(mk("hasCapital"), rng.to_string(), mk("City")),
(mk("Employee"), sub.to_string(), mk("Person")), (mk("alice"), mk("worksAt"), mk("acme")),
(mk("france"), mk("hasCapital"), mk("paris")),
];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let xs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get("x").cloned()).collect();
v.sort();
v.dedup();
v
};
let qe = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Employee }}");
assert!(eval_sparql(&rete, &qe).unwrap().1.is_empty());
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qe).unwrap().1),
vec![mk("alice")]
);
let qc = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:City }}");
assert!(eval_sparql(&rete, &qc).unwrap().1.is_empty());
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qc).unwrap().1),
vec![mk("paris")]
);
let qp = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Person }}");
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qp).unwrap().1),
vec![mk("alice")]
);
}
#[test]
fn owl_ql_inverse_reasoning() {
let inv = "<http://www.w3.org/2002/07/owl#inverseOf>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("hasChild"), inv.to_string(), mk("hasParent")),
(mk("alice"), mk("hasChild"), mk("bob")), (mk("carol"), mk("hasParent"), mk("dave")), ];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let pairs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols
.iter()
.map(|b| format!("{}->{}", b.get("x").unwrap(), b.get("y").unwrap()))
.collect();
v.sort();
v.dedup();
v
};
let q = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:hasParent ?y }}");
assert_eq!(
pairs(eval_sparql(&rete, &q).unwrap().1),
vec!["<http://ex/carol>-><http://ex/dave>"]
);
assert_eq!(
pairs(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![
"<http://ex/bob>-><http://ex/alice>",
"<http://ex/carol>-><http://ex/dave>",
]
);
let qc = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:hasChild ?y }}");
assert_eq!(
pairs(eval_sparql_reasoned(&rete, &qc).unwrap().1),
vec![
"<http://ex/alice>-><http://ex/bob>",
"<http://ex/dave>-><http://ex/carol>",
]
);
}
#[test]
fn owl_ql_existential_reasoning() {
let ty = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
let onp = "<http://www.w3.org/2002/07/owl#onProperty>";
let svf = "<http://www.w3.org/2002/07/owl#someValuesFrom>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("Parent"), sub.to_string(), mk("R1")),
(mk("R1"), onp.to_string(), mk("hasChild")),
(mk("R1"), svf.to_string(), mk("Person")),
(mk("alice"), ty.to_string(), mk("Parent")),
(mk("bob"), mk("hasChild"), mk("carol")),
(mk("carol"), ty.to_string(), mk("Person")),
];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let xs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get("x").cloned()).collect();
v.sort();
v.dedup();
v
};
let q = format!("{PREFIX}SELECT ?x WHERE {{ ?x ex:hasChild ?y }}");
assert_eq!(xs(eval_sparql(&rete, &q).unwrap().1), vec![mk("bob")]);
assert_eq!(
xs(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![mk("alice"), mk("bob")]
);
let qp = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:hasChild ?y }}");
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qp).unwrap().1),
vec![mk("bob")]
);
let qs = format!("{PREFIX}SELECT ?x WHERE {{ ?x ex:hasChild ?y . ?y a ex:Person }}");
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qs).unwrap().1),
vec![mk("bob")]
);
}
#[test]
fn owl_ql_inverse_existential_reasoning() {
let ty = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
let inv = "<http://www.w3.org/2002/07/owl#inverseOf>";
let onp = "<http://www.w3.org/2002/07/owl#onProperty>";
let svf = "<http://www.w3.org/2002/07/owl#someValuesFrom>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("hasChild"), inv.to_string(), mk("hasParent")),
(mk("Parent"), sub.to_string(), mk("R")),
(mk("R"), onp.to_string(), mk("hasChild")),
(mk("R"), svf.to_string(), mk("Person")),
(mk("alice"), ty.to_string(), mk("Parent")),
(mk("bob"), mk("hasParent"), mk("carol")), ];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let xs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get("x").cloned()).collect();
v.sort();
v.dedup();
v
};
let q = format!("{PREFIX}SELECT ?x WHERE {{ ?y ex:hasParent ?x }}");
assert_eq!(xs(eval_sparql(&rete, &q).unwrap().1), vec![mk("carol")]);
assert_eq!(
xs(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![mk("alice"), mk("carol")]
);
let qp = format!("{PREFIX}SELECT ?x ?y WHERE {{ ?y ex:hasParent ?x }}");
assert_eq!(
xs(eval_sparql_reasoned(&rete, &qp).unwrap().1),
vec![mk("carol")]
);
}
#[test]
fn owl_ql_domain_subproperty_composition() {
let dom = "<http://www.w3.org/2000/01/rdf-schema#domain>";
let subp = "<http://www.w3.org/2000/01/rdf-schema#subPropertyOf>";
let mk = |n: &str| format!("<http://ex/{n}>");
let t: Vec<(String, String, String)> = vec![
(mk("hasSalary"), dom.to_string(), mk("Employee")),
(mk("hasBonus"), subp.to_string(), mk("hasSalary")),
(mk("alice"), mk("hasBonus"), mk("v1000")),
(mk("bob"), mk("hasSalary"), mk("v2000")),
];
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
let xs = |sols: Vec<rete_core::Binding>| {
let mut v: Vec<String> = sols.iter().filter_map(|b| b.get("x").cloned()).collect();
v.sort();
v.dedup();
v
};
let q = format!("{PREFIX}SELECT ?x WHERE {{ ?x a ex:Employee }}");
assert!(eval_sparql(&rete, &q).unwrap().1.is_empty());
assert_eq!(
xs(eval_sparql_reasoned(&rete, &q).unwrap().1),
vec![mk("alice"), mk("bob")]
);
}
#[test]
fn rdf12_base_direction_and_version() {
let dir = "http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString";
let plain = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
let refs: Vec<(&str, &str, &str)> = vec![
("<http://ex/s>", "<http://ex/a>", "\"hello\"@en--ltr"), ("<http://ex/s>", "<http://ex/b>", "\"hi\"@en"), ];
let rete = Rete::open(&build(&refs)).unwrap();
assert_eq!(
col(
&rete,
"SELECT ?l WHERE { ex:s ex:a ?x BIND(LANG(?x) AS ?l) }",
"l"
),
vec!["\"en\""]
);
assert_eq!(
col(
&rete,
"SELECT ?d WHERE { ex:s ex:a ?x BIND(DATATYPE(?x) AS ?d) }",
"d"
),
vec![format!("<{dir}>")]
);
assert_eq!(
col(
&rete,
"SELECT ?d WHERE { ex:s ex:b ?x BIND(DATATYPE(?x) AS ?d) }",
"d"
),
vec![format!("<{plain}>")]
);
let (_, sols) = eval_sparql(
&rete,
"VERSION \"1.2\" PREFIX ex: <http://ex/> SELECT ?x WHERE { ex:s ex:a ?x }",
)
.unwrap();
assert_eq!(sols.len(), 1);
}
#[test]
fn property_path_zero_length_semantics() {
let rete = Rete::open(&dataset()).unwrap();
assert_eq!(
col(&rete, "SELECT ?y WHERE { ex:Alice ex:knows? ?y }", "y"),
vec!["<http://ex/Alice>", "<http://ex/Bob>", "<http://ex/Carol>"],
"knows? must include Alice herself (zero-length)"
);
assert_eq!(
col(&rete, "SELECT ?y WHERE { ex:Alice ex:knows* ?y }", "y"),
vec![
"<http://ex/Alice>",
"<http://ex/Bob>",
"<http://ex/Carol>",
"<http://ex/Dave>",
"<http://ex/Eve>",
],
"knows* is the reflexive-transitive closure"
);
assert!(
!col(&rete, "SELECT ?y WHERE { ex:Alice ex:knows+ ?y }", "y")
.contains(&"<http://ex/Alice>".to_string()),
"knows+ must NOT include Alice (no zero-length path)"
);
assert_eq!(
col(&rete, "SELECT ?x WHERE { ?x ex:knows? ex:Carol }", "x"),
vec!["<http://ex/Alice>", "<http://ex/Bob>", "<http://ex/Carol>"],
"reversed knows? must include Carol herself"
);
let (_, pairs) = eval_sparql(
&rete,
&format!("{PREFIX}SELECT ?x ?y WHERE {{ ?x ex:knows* ?y }}"),
)
.unwrap();
for who in ["Alice", "Bob", "Carol", "Dave", "Eve"] {
let iri = format!("<http://ex/{who}>");
assert!(
pairs.iter().any(|b| b["x"] == iri && b["y"] == iri),
"knows* (both unbound) must contain the self-pair for {who}"
);
}
}
#[test]
fn subquery_evaluates_and_joins_with_the_outer_pattern() {
let rete = Rete::open(&dataset()).unwrap();
let direct = col(
&rete,
&format!("{PREFIX}SELECT ?p WHERE {{ ?p ex:knows ?f }}"),
"p",
);
let nested = col(
&rete,
&format!("{PREFIX}SELECT ?p WHERE {{ {{ SELECT ?p WHERE {{ ?p ex:knows ?f }} }} }}"),
"p",
);
assert_eq!(nested, direct);
assert!(direct.contains(&"<http://ex/Alice>".to_string()));
let knowers = col(
&rete,
&format!(
"{PREFIX}SELECT ?f WHERE {{ ex:Alice ex:knows ?f . \
{{ SELECT ?f WHERE {{ ?f ex:knows ?g }} }} }}"
),
"f",
);
assert_eq!(
knowers,
vec![
"<http://ex/Bob>".to_string(),
"<http://ex/Carol>".to_string()
]
);
}
#[test]
fn simple_and_join() {
let rete = Rete::open(&dataset()).unwrap();
assert_eq!(
col(&rete, "SELECT ?f WHERE { ex:Alice ex:knows ?f }", "f"),
vec!["<http://ex/Bob>", "<http://ex/Carol>"]
);
assert_eq!(
col(
&rete,
"SELECT ?z WHERE { ex:Alice ex:knows ?y . ?y ex:knows ?z }",
"z"
),
vec!["<http://ex/Carol>", "<http://ex/Dave>"]
);
}
#[test]
fn filter_optional_and_builtins() {
let rete = Rete::open(&dataset()).unwrap();
assert_eq!(
col(
&rete,
"SELECT ?p WHERE { ?p ex:age ?a . FILTER(?a > 32) }",
"p"
),
vec!["<http://ex/Carol>", "<http://ex/Dave>"]
);
let names = col(
&rete,
"SELECT ?p WHERE { ?p ex:name ?n . OPTIONAL { ?p ex:age ?a } }",
"p",
);
assert_eq!(names.len(), 5);
}
#[test]
fn aggregate_path_order_union_minus() {
let rete = Rete::open(&dataset()).unwrap();
let (_, deg) = eval_sparql(
&rete,
&format!("{PREFIX}SELECT ?p (COUNT(?f) AS ?n) WHERE {{ ?p ex:knows ?f }} GROUP BY ?p"),
)
.unwrap();
let alice = deg.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
assert_eq!(
alice["n"],
"\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
);
assert_eq!(
col(&rete, "SELECT ?y WHERE { ex:Alice ex:knows+ ?y }", "y"),
vec![
"<http://ex/Bob>",
"<http://ex/Carol>",
"<http://ex/Dave>",
"<http://ex/Eve>",
]
);
let (_, oldest) = eval_sparql(
&rete,
&format!("{PREFIX}SELECT ?p WHERE {{ ?p ex:age ?a }} ORDER BY DESC(?a) LIMIT 2"),
)
.unwrap();
assert_eq!(oldest[0]["p"], "<http://ex/Dave>");
assert_eq!(oldest[1]["p"], "<http://ex/Carol>");
assert!(col(
&rete,
"SELECT ?f WHERE { ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }",
"f"
)
.is_empty());
}
fn dataset_3graph() -> Vec<u8> {
let triples = [
("Alice", "type", "Person", None),
("Bob", "type", "Person", None),
("Alice", "knows", "Bob", Some("g/friends")),
("Bob", "knows", "Carol", Some("g/friends")),
("Carol", "city", "NYC", Some("g/facts")),
("Bob", "city", "NYC", Some("g/facts")),
];
let mut db = DictionaryBuilder::new();
for (s, p, o, _) in triples {
db.observe(&iri(s), &iri(p), &term(o));
}
let dict = db.build();
use std::collections::BTreeMap;
let mut def = GraphIndexBuilder::new();
let mut named: BTreeMap<String, GraphIndexBuilder> = BTreeMap::new();
for (s, p, o, g) in triples {
let t = dict.encode(&iri(s), &iri(p), &term(o)).unwrap();
match g {
None => def.push(t),
Some(name) => named.entry(iri(name)).or_default().push(t),
}
}
let named_idx: Vec<(String, _)> = named.into_iter().map(|(g, b)| (g, b.build())).collect();
write_dataset(&dict, &def.build(), &named_idx, true, &[], 0)
}
#[test]
fn dataset_graph_from_describe() {
let rete = Rete::open(&dataset_3graph()).unwrap();
let p = "PREFIX ex: <http://ex/> ";
let (_, s) = eval_sparql(
&rete,
&format!("{p}SELECT ?f WHERE {{ GRAPH <http://ex/g/friends> {{ ex:Alice ex:knows ?f }} }}"),
)
.unwrap();
assert_eq!(s.len(), 1);
assert_eq!(s[0]["f"], "<http://ex/Bob>");
let (_, g) = eval_sparql(
&rete,
&format!("{p}SELECT ?g WHERE {{ GRAPH ?g {{ ?x ex:city <http://ex/NYC> }} }}"),
)
.unwrap();
assert!(g.iter().all(|b| b["g"] == "<http://ex/g/facts>"));
let (_, j) = eval_sparql(
&rete,
&format!(
"{p}SELECT ?f FROM <http://ex/g/friends> FROM <http://ex/g/facts> \
WHERE {{ ex:Alice ex:knows ?f . ?f ex:city <http://ex/NYC> }}"
),
)
.unwrap();
assert_eq!(j.len(), 1);
assert_eq!(j[0]["f"], "<http://ex/Bob>");
match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
QueryOutput::Construct(t) => assert_eq!(t.len(), 1), other => panic!("describe: {other:?}"),
}
}
#[test]
fn ask_construct_exists() {
let rete = Rete::open(&dataset()).unwrap();
match eval_query(&rete, &format!("{PREFIX}ASK {{ ?a ex:knows ?b }}")).unwrap() {
QueryOutput::Ask(b) => assert!(b),
_ => panic!("ask"),
}
match eval_query(
&rete,
&format!("{PREFIX}CONSTRUCT {{ ?b ex:knownBy ?a }} WHERE {{ ?a ex:knows ?b }}"),
)
.unwrap()
{
QueryOutput::Construct(triples) => assert!(triples.contains(&(
"<http://ex/Bob>".into(),
"<http://ex/knownBy>".into(),
"<http://ex/Alice>".into(),
))),
_ => panic!("construct"),
}
let knows_nyc = col(
&rete,
"SELECT ?p WHERE { ?p ex:knows ?f . FILTER EXISTS { ?f ex:city <http://ex/city/NYC> } }",
"p",
);
assert_eq!(knows_nyc, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
}
fn deps() -> Vec<u8> {
let rt = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let t: Vec<(String, String, String)> = vec![
("app", rt, "Application"),
("web", rt, "Library"),
("auth", rt, "Library"),
("logging", rt, "Library"),
("log4x", rt, "Library"),
("safejson", rt, "Library"),
("log4x", "hasVulnerability", "CVE-2099-0001"),
("app", "dependsOn", "web"),
("app", "dependsOn", "auth"),
("web", "dependsOn", "logging"),
("auth", "dependsOn", "logging"),
("auth", "dependsOn", "safejson"),
("logging", "dependsOn", "log4x"),
]
.into_iter()
.map(|(s, p, o)| {
let pred = if p == rt { rt.to_string() } else { iri(p) };
(iri(s), pred, iri(o))
})
.collect();
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
build(&refs)
}
#[test]
fn transitive_dependency_impact() {
let rete = Rete::open(&deps()).unwrap();
let impacted = col(
&rete,
"SELECT DISTINCT ?dependent WHERE { ?dependent ex:dependsOn+ ex:log4x }",
"dependent",
);
assert_eq!(
impacted,
vec![
"<http://ex/app>",
"<http://ex/auth>",
"<http://ex/logging>",
"<http://ex/web>",
]
);
let rt = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
let report = col(
&rete,
&format!(
"SELECT DISTINCT ?lib WHERE {{ \
?lib ex:dependsOn+ ?v . ?v ex:hasVulnerability ?cve . \
?lib {rt} ex:Library }}"
),
"lib",
);
assert_eq!(
report,
vec!["<http://ex/auth>", "<http://ex/logging>", "<http://ex/web>"]
);
}
#[test]
fn datatype_and_lang_builtins() {
let dt = "<http://www.w3.org/2001/XMLSchema#dateTime>";
let t: Vec<(String, String, String)> = vec![
(
"Q1",
"pop",
"\"42\"^^<http://www.w3.org/2001/XMLSchema#integer>",
),
("Q1", "born", &format!("\"2001-05-11T00:00:00Z\"^^{dt}")),
("Q1", "label", "\"Douglas\"@en"),
("Q1", "code", "\"plain\""),
]
.into_iter()
.map(|(s, p, o)| (iri(s), iri(p), term(o)))
.collect();
let refs: Vec<(&str, &str, &str)> = t
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let rete = Rete::open(&build(&refs)).unwrap();
assert_eq!(
col(
&rete,
&format!("SELECT ?p WHERE {{ ex:Q1 ?p ?o FILTER(DATATYPE(?o) = {dt}) }}"),
"p"
),
vec!["<http://ex/born>"]
);
assert_eq!(
col(
&rete,
"SELECT ?p WHERE { ex:Q1 ?p ?o \
FILTER(DATATYPE(?o) = <http://www.w3.org/2001/XMLSchema#string>) }",
"p"
),
vec!["<http://ex/code>"]
);
assert_eq!(
col(
&rete,
"SELECT ?p WHERE { ex:Q1 ?p ?o \
FILTER(DATATYPE(?o) = <http://www.w3.org/1999/02/22-rdf-syntax-ns#langString>) }",
"p"
),
vec!["<http://ex/label>"]
);
assert_eq!(
col(
&rete,
"SELECT ?p WHERE { ex:Q1 ?p ?o FILTER(LANG(?o) = \"en\") }",
"p"
),
vec!["<http://ex/label>"]
);
}
#[test]
fn optional_fat_right_side_stays_correlated() {
let mut triples: Vec<(String, String, String)> = Vec::new();
for i in 0..100_000u32 {
let s = format!("<http://ex/e{i}>");
triples.push((s.clone(), iri("desc"), format!("\"d{i}en\"@en")));
triples.push((s, iri("desc"), format!("\"d{i}fr\"@fr")));
}
for m in ["m0", "m1", "m2"] {
triples.push((iri(m), iri("kind"), iri("Marked")));
}
triples.push((iri("m0"), iri("desc"), "\"m0en\"@en".to_string()));
triples.push((iri("m0"), iri("desc"), "\"m0fr\"@fr".to_string()));
triples.push((iri("m1"), iri("desc"), "\"m1fr\"@fr".to_string())); let refs: Vec<(&str, &str, &str)> = triples
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let image = build(&refs);
let rete = Rete::open(&image).unwrap();
let q = "SELECT ?s ?d WHERE { ?s ex:kind ex:Marked . \
OPTIONAL { ?s ex:desc ?d . FILTER(lang(?d) = \"en\") } }";
let (_, sols) = eval_sparql(&rete, &format!("{PREFIX}{q}")).unwrap();
let mut got: Vec<(String, Option<String>)> = sols
.iter()
.map(|b| (b.get("s").cloned().unwrap(), b.get("d").cloned()))
.collect();
got.sort();
assert_eq!(
got,
vec![
(
"<http://ex/m0>".to_string(),
Some("\"m0en\"@en".to_string())
),
("<http://ex/m1>".to_string(), None),
("<http://ex/m2>".to_string(), None),
]
);
}
#[test]
fn merge_seed_skips_pinpoint_times_fat() {
let mut triples: Vec<(String, String, String)> = Vec::new();
for i in 0..100_000u32 {
let s = format!("<http://ex/e{i}>");
triples.push((s.clone(), iri("desc"), format!("\"d{i}\"")));
}
for m in ["m0", "m1"] {
triples.push((iri(m), iri("kind"), iri("Marked")));
triples.push((iri(m), iri("desc"), format!("\"{m}desc\"")));
}
let refs: Vec<(&str, &str, &str)> = triples
.iter()
.map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
.collect();
let image = build(&refs);
let rete = Rete::open(&image).unwrap();
let q = "SELECT ?s ?d WHERE { ?s ex:kind ex:Marked . ?s ex:desc ?d }";
let (_, sols) = eval_sparql(&rete, &format!("{PREFIX}{q}")).unwrap();
let mut got: Vec<(String, String)> = sols
.iter()
.map(|b| (b.get("s").cloned().unwrap(), b.get("d").cloned().unwrap()))
.collect();
got.sort();
assert_eq!(
got,
vec![
("<http://ex/m0>".to_string(), "\"m0desc\"".to_string()),
("<http://ex/m1>".to_string(), "\"m1desc\"".to_string()),
]
);
}
#[test]
fn contains_pushdown_matches_scan() {
use rete_core::ingest::{assemble_dataset_with_opts, parse};
let nt = r#"<http://ex/e1> <http://ex/label> "Royal Observatory Greenwich" .
<http://ex/e1> <http://ex/kind> <http://ex/Place> .
<http://ex/e2> <http://ex/label> "observatory dome" .
<http://ex/e3> <http://ex/label> "Conservatory of Music" .
<http://ex/e4> <http://ex/label> "plain building" .
<http://ex/e4> <http://ex/note> "hidden observatory reference" .
<http://ex/e5> <http://ex/label> "OBSERVATORY UPPER" .
"#;
let quads: Vec<_> = parse(nt)
.unwrap()
.into_iter()
.map(|(s, p, o)| (s, p, o, None))
.collect();
let (plain, _) =
assemble_dataset_with_opts(quads.clone(), false, false, None, |_, _| Vec::new());
let (indexed, _) = assemble_dataset_with_opts(quads, false, true, None, |_, _| Vec::new());
let rete_plain = Rete::open(&plain).unwrap();
let rete_indexed = Rete::open(&indexed).unwrap();
let queries = [
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(?l, \"Observatory\")) }",
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(LCASE(?l), \"observatory\")) }",
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(?l, \"Observatory Greenwich\")) }",
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(LCASE(?l), \"servatory\")) }",
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(?l, \"observatory\") || CONTAINS(?l, \"building\")) }",
"SELECT ?s WHERE { ?s ex:label ?l . FILTER(CONTAINS(LCASE(?l), \"observatory\") && CONTAINS(LCASE(?l), \"dome\")) }",
];
for q in queries {
let a = col(&rete_plain, q, "s");
let b = col(&rete_indexed, q, "s");
assert_eq!(a, b, "indexed vs plain diverged for {q}");
assert!(
!a.is_empty() || q.contains("dome"),
"unexpected empty for {q}"
);
}
}