use oxilite::io::RdfFormat;
use oxilite::model::{GraphName, NamedNode, Term};
use oxilite::schema::{Registration, SchemaRole};
use oxilite::sparql::{QueryOptions, Reasoning};
use oxilite::store::Store;
use oxilite_core::{QueryOutput, SyncBackend};
const PREFIXES: &str = "@prefix ex: <http://example.com/> . \
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . \
@prefix owl: <http://www.w3.org/2002/07/owl#> . \
@prefix sh: <http://www.w3.org/ns/shacl#> . \
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ";
fn store() -> Store {
Store::new().unwrap()
}
fn load<B: SyncBackend + Send + Sync + 'static>(store: &Store<B>, trig: &str) {
store
.load_from_slice(RdfFormat::TriG, format!("{PREFIXES}{trig}").as_bytes())
.unwrap();
}
fn graph(iri: &str) -> GraphName {
NamedNode::new(iri).unwrap().into()
}
fn values<B: SyncBackend + Send + Sync + 'static>(
store: &Store<B>,
query: &str,
options: &QueryOptions,
) -> Vec<String> {
let q = format!(
"PREFIX ex: <http://example.com/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \
PREFIX owl: <http://www.w3.org/2002/07/owl#> {query}"
);
match store.query_output(q.as_str(), options).unwrap() {
QueryOutput::Solutions { rows, .. } => {
let mut v: Vec<String> = rows
.iter()
.filter_map(|r| r.first().cloned().flatten())
.map(|t: Term| t.to_string())
.collect();
v.sort();
v
}
_ => panic!("expected solutions"),
}
}
fn rdfs() -> QueryOptions {
QueryOptions {
reasoning: Reasoning::Rdfs,
union_default_graph: true,
..QueryOptions::default()
}
}
#[test]
fn registers_lists_and_unregisters() {
let store = store();
load(&store, "GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal }");
let g = graph("http://example.com/onto");
store
.register_schema_graph(
g.as_ref(),
SchemaRole::Ontology,
&Registration::new()
.with_iri(NamedNode::new("http://example.com/onto").unwrap())
.with_version("v1")
.with_imports([NamedNode::new("http://example.com/base").unwrap()]),
)
.unwrap();
let listed = store.schema_graphs().unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].graph, g);
assert_eq!(listed[0].role, SchemaRole::Ontology);
assert_eq!(listed[0].registration.version.as_deref(), Some("v1"));
assert_eq!(listed[0].registration.imports.len(), 1);
assert!(listed[0].registration.active);
assert_eq!(
values(
&store,
"SELECT ?o WHERE { GRAPH <http://example.com/onto> { ex:Dog rdfs:subClassOf ?o } }",
&QueryOptions::default()
),
["<http://example.com/Animal>"]
);
assert!(store.unregister_schema_graph(g.as_ref()).unwrap());
assert!(store.schema_graphs().unwrap().is_empty());
assert_eq!(store.len().unwrap(), 1);
}
#[test]
fn reasoning_is_scoped_to_active_ontologies() {
let store = store();
load(
&store,
"GRAPH ex:good { ex:Dog rdfs:subClassOf ex:Animal } \
GRAPH ex:bad { ex:Rock rdfs:subClassOf ex:Animal } \
ex:rex a ex:Dog . ex:granite a ex:Rock .",
);
let q = "SELECT ?x WHERE { ?x a ex:Animal }";
assert_eq!(
values(&store, q, &rdfs()),
["<http://example.com/granite>", "<http://example.com/rex>"]
);
let good = graph("http://example.com/good");
let bad = graph("http://example.com/bad");
store
.register_schema_graph(good.as_ref(), SchemaRole::Ontology, &Registration::new())
.unwrap();
store
.register_schema_graph(bad.as_ref(), SchemaRole::Ontology, &Registration::new())
.unwrap();
assert_eq!(
values(&store, q, &rdfs()),
["<http://example.com/granite>", "<http://example.com/rex>"]
);
assert!(store.set_schema_graph_active(bad.as_ref(), false).unwrap());
assert_eq!(values(&store, q, &rdfs()), ["<http://example.com/rex>"]);
assert!(store.set_schema_graph_active(bad.as_ref(), true).unwrap());
assert_eq!(
values(&store, q, &rdfs()),
["<http://example.com/granite>", "<http://example.com/rex>"]
);
}
#[test]
fn axioms_outside_registered_ontologies_are_ignored() {
let store = store();
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } \
GRAPH ex:notes { ex:Rock rdfs:subClassOf ex:Animal } \
ex:rex a ex:Dog . ex:granite a ex:Rock .",
);
store
.register_schema_graph(
graph("http://example.com/onto").as_ref(),
SchemaRole::Ontology,
&Registration::new(),
)
.unwrap();
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()),
["<http://example.com/rex>"]
);
}
#[test]
fn schema_graphs_can_be_hidden() {
let store = store();
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } ex:rex a ex:Dog .",
);
let visible = QueryOptions {
union_default_graph: true,
..QueryOptions::default()
};
let hidden = QueryOptions {
include_schema_graphs: false,
..visible.clone()
};
let q = "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }";
assert_eq!(values(&store, q, &visible), values(&store, q, &hidden));
store
.register_schema_graph(
graph("http://example.com/onto").as_ref(),
SchemaRole::Ontology,
&Registration::new(),
)
.unwrap();
assert_eq!(
values(&store, q, &visible),
["\"6\"^^<http://www.w3.org/2001/XMLSchema#integer>"]
);
assert_eq!(
values(&store, q, &hidden),
["\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>"]
);
store
.set_schema_graph_active(graph("http://example.com/onto").as_ref(), false)
.unwrap();
assert_eq!(
values(&store, q, &hidden),
["\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>"]
);
}
#[test]
fn drop_removes_registration_and_quads() {
let store = store();
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } ex:rex a ex:Dog .",
);
let g = graph("http://example.com/onto");
store
.register_schema_graph(g.as_ref(), SchemaRole::Ontology, &Registration::new())
.unwrap();
assert_eq!(store.drop_schema_graph(g.as_ref()).unwrap(), 1);
assert!(store.schema_graphs().unwrap().is_empty());
assert_eq!(store.len().unwrap(), 1);
assert!(values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()).is_empty());
}
const SHAPES: &str = "GRAPH ex:shapes { \
ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; \
sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ] ; \
sh:property [ sh:path ex:status ; sh:in ( \"on\" \"off\" ) ] ; \
sh:property [ sh:path ex:name ; sh:pattern \"^[A-Z]\" ] ; \
sh:property [ sh:path ex:employer ; sh:class ex:Company ] . }";
#[test]
fn shape_index_is_compiled_on_write() {
let store = store();
load(&store, SHAPES);
let index = store.shape_index().unwrap();
let person = NamedNode::new("http://example.com/Person").unwrap();
let age = index
.get(&person, &NamedNode::new("http://example.com/age").unwrap())
.expect("age shape");
assert_eq!(
age.datatype.as_ref().map(|d| d.as_str()),
Some("http://www.w3.org/2001/XMLSchema#integer")
);
assert_eq!(age.min, Some(1));
assert_eq!(age.max, Some(1));
assert!(!age.relationship);
let status = index
.get(
&person,
&NamedNode::new("http://example.com/status").unwrap(),
)
.expect("status shape");
let mut got: Vec<String> = status.values_in.iter().map(Term::to_string).collect();
got.sort();
assert_eq!(got, ["\"off\"", "\"on\""]);
let name = index
.get(&person, &NamedNode::new("http://example.com/name").unwrap())
.expect("name shape");
assert_eq!(name.pattern.as_deref(), Some("^[A-Z]"));
let employer = index
.get(
&person,
&NamedNode::new("http://example.com/employer").unwrap(),
)
.expect("employer shape");
assert!(employer.relationship);
}
#[test]
fn shape_index_follows_deletions_and_scope() {
let store = store();
load(&store, SHAPES);
load(
&store,
"GRAPH ex:other { ex:OtherShape sh:targetClass ex:Thing ; \
sh:property [ sh:path ex:label ; sh:minCount 1 ] . }",
);
let thing = NamedNode::new("http://example.com/Thing").unwrap();
let label = NamedNode::new("http://example.com/label").unwrap();
assert!(store.shape_index().unwrap().get(&thing, &label).is_some());
store
.register_schema_graph(
graph("http://example.com/shapes").as_ref(),
SchemaRole::Shacl,
&Registration::new(),
)
.unwrap();
assert!(store.shape_index().unwrap().get(&thing, &label).is_none());
store
.update(
"PREFIX sh: <http://www.w3.org/ns/shacl#> \
DELETE WHERE { GRAPH <http://example.com/shapes> { ?s sh:targetClass ?o } }",
)
.unwrap();
assert!(store.shape_index().unwrap().is_empty());
}
#[test]
fn shapes_targeting_the_same_path_merge() {
let store = store();
load(
&store,
"ex:A sh:targetClass ex:Person ; sh:property [ sh:path ex:age ; sh:minCount 1 ] . \
ex:B sh:targetClass ex:Person ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ] .",
);
let shape = store
.shape_index()
.unwrap()
.get(
&NamedNode::new("http://example.com/Person").unwrap(),
&NamedNode::new("http://example.com/age").unwrap(),
)
.cloned()
.expect("merged shape");
assert_eq!(shape.min, Some(1));
assert_eq!(
shape.datatype.as_ref().map(|d| d.as_str()),
Some("http://www.w3.org/2001/XMLSchema#integer")
);
}
#[test]
fn registry_survives_reopening() {
let path = std::env::temp_dir().join(format!("oxilite-registry-{}", std::process::id()));
let _ = std::fs::remove_file(&path);
let g = graph("http://example.com/onto");
{
let store = Store::open(&path).unwrap();
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } ex:rex a ex:Dog .",
);
store
.register_schema_graph(
g.as_ref(),
SchemaRole::Ontology,
&Registration::new().with_version("v2"),
)
.unwrap();
}
let store = Store::open(&path).unwrap();
let listed = store.schema_graphs().unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].graph, g);
assert_eq!(listed[0].registration.version.as_deref(), Some("v2"));
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()),
["<http://example.com/rex>"]
);
drop(store);
let _ = std::fs::remove_file(&path);
}
fn with_graphs(store: &Store) {
load(
store,
"GRAPH ex:o1 { ex:Dog rdfs:subClassOf ex:Animal } \
GRAPH ex:o2 { ex:Dog rdfs:subClassOf ex:Plant } \
GRAPH ex:base { ex:Animal rdfs:subClassOf ex:Being . ex:Plant rdfs:subClassOf ex:Being } \
GRAPH ex:a { ex:rex a ex:Dog } \
GRAPH ex:b { ex:fido a ex:Dog }",
);
}
#[test]
fn ontologies_apply_to_mapped_graphs() {
let store = store();
with_graphs(&store);
let register = |o: &str, to: &[&str]| {
store
.register_schema_graph(
graph(o).as_ref(),
SchemaRole::Ontology,
&Registration::new().applies_to(to.iter().map(|g| graph(g))),
)
.unwrap();
};
register("http://example.com/o1", &["http://example.com/a"]);
register("http://example.com/o2", &["http://example.com/b"]);
let animals = "SELECT ?x WHERE { ?x a ex:Animal }";
let plants = "SELECT ?x WHERE { ?x a ex:Plant }";
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/rex>"]
);
assert_eq!(
values(&store, plants, &rdfs()),
["<http://example.com/fido>"]
);
assert_eq!(
values(
&store,
"SELECT ?x WHERE { GRAPH ?g { ?x a ex:Plant } }",
&rdfs()
),
["<http://example.com/fido>"]
);
register("http://example.com/base", &[]);
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Being }", &rdfs()),
["<http://example.com/fido>", "<http://example.com/rex>"]
);
register("http://example.com/o1", &[]);
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/fido>", "<http://example.com/rex>"]
);
assert_eq!(
store
.schema_graphs_for(graph("http://example.com/b").as_ref(), SchemaRole::Ontology)
.unwrap(),
[
graph("http://example.com/base"),
graph("http://example.com/o1"),
graph("http://example.com/o2")
]
);
assert_eq!(
store
.schema_graphs_for(graph("http://example.com/a").as_ref(), SchemaRole::Ontology)
.unwrap(),
[
graph("http://example.com/base"),
graph("http://example.com/o1")
]
);
}
#[test]
fn registry_is_rdf_in_the_schema_graph() {
let store = store();
with_graphs(&store);
store
.update(
"PREFIX oxl: <https://oxilite.dev/ns#> INSERT DATA { GRAPH <oxilite:schema> { \
<http://example.com/o2> a oxl:OntologyGraph ; oxl:appliesTo <http://example.com/b> ; oxl:version \"7\" } }",
)
.unwrap();
let listed = store.schema_graphs().unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].registration.version.as_deref(), Some("7"));
assert_eq!(
listed[0].registration.applies_to,
[graph("http://example.com/b")]
);
assert!(listed[0].registration.active);
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Plant }", &rdfs()),
["<http://example.com/fido>"],
"a SPARQL registration scopes reasoning like the API"
);
store
.register_schema_graph(
graph("http://example.com/o1").as_ref(),
SchemaRole::Ontology,
&Registration::new()
.with_version("v1")
.applies_to([GraphName::DefaultGraph]),
)
.unwrap();
let ask = |q: &str| {
matches!(
store
.query_output(
format!("PREFIX oxl: <https://oxilite.dev/ns#> ASK {{ GRAPH <oxilite:schema> {{ {q} }} }}").as_str(),
&QueryOptions::default()
)
.unwrap(),
QueryOutput::Boolean(true)
)
};
assert!(ask("<http://example.com/o1> a oxl:OntologyGraph ; oxl:version \"v1\" ; oxl:active true ; oxl:appliesTo oxl:DefaultGraph ; oxl:loadedAt ?t"));
assert!(store
.set_schema_graph_active(graph("http://example.com/o2").as_ref(), false)
.unwrap());
assert!(ask("<http://example.com/o2> oxl:active false"));
assert!(values(&store, "SELECT ?x WHERE { ?x a ex:Plant }", &rdfs()).is_empty());
let hidden = QueryOptions {
include_schema_graphs: false,
..QueryOptions::default()
};
assert!(
values(&store, "SELECT ?g WHERE { GRAPH ?g { ?s ?p ?o } }", &hidden)
.iter()
.all(|g| g != "<oxilite:schema>" && g != "<http://example.com/o1>")
);
assert!(oxilite::schema::VOCABULARY.contains("oxl:appliesTo"));
}
#[test]
fn version_one_registry_is_migrated() {
use oxilite_core::sql::Request;
let path = std::env::temp_dir().join(format!("oxilite-registry-v1-{}", std::process::id()));
let _ = std::fs::remove_file(&path);
{
let store = Store::open(&path).unwrap();
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } \
GRAPH ex:other { ex:Rock rdfs:subClassOf ex:Animal } ex:rex a ex:Dog . ex:granite a ex:Rock .",
);
}
let onto = oxilite_core::encoding::named_node_id("http://example.com/onto");
let backend = oxilite::rusqlite::RusqliteBackend::open(&path).unwrap();
backend
.execute(&Request::atomic(vec![
"DROP TABLE tbox_closure".into(),
"CREATE TABLE tbox_closure (kind INTEGER NOT NULL, sub INTEGER NOT NULL, sup INTEGER NOT NULL, PRIMARY KEY (kind, sup, sub)) WITHOUT ROWID, STRICT".into(),
"CREATE INDEX tbox_closure_sub ON tbox_closure(kind, sub, sup)".into(),
"CREATE TABLE schema_graphs (g INTEGER PRIMARY KEY, role INTEGER NOT NULL, iri TEXT, version TEXT, sha256 TEXT, imports TEXT, active INTEGER NOT NULL DEFAULT 1, loaded_at REAL NOT NULL) STRICT".into(),
format!("INSERT INTO schema_graphs VALUES ({onto}, 1, NULL, 'v1', NULL, 'http://example.com/base', 1, 0)").into(),
"UPDATE oxilite_meta SET value = '1' WHERE key = 'schema_version'".into(),
]))
.unwrap();
drop(backend);
let store = Store::open(&path).unwrap();
let listed = store.schema_graphs().unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].graph, graph("http://example.com/onto"));
assert_eq!(listed[0].role, SchemaRole::Ontology);
assert_eq!(listed[0].registration.version.as_deref(), Some("v1"));
assert_eq!(
listed[0].registration.imports,
[NamedNode::new("http://example.com/base").unwrap()]
);
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()),
["<http://example.com/rex>"],
"the migrated registration scopes reasoning"
);
drop(store);
let backend = oxilite::rusqlite::RusqliteBackend::open(&path).unwrap();
let r = backend
.execute(&Request::read(vec![
"SELECT count(*) FROM sqlite_master WHERE name = 'schema_graphs'".into(),
"SELECT value FROM oxilite_meta WHERE key = 'schema_version'".into(),
]))
.unwrap();
assert_eq!(r[0].rows[0][0].as_i64(), Some(0));
assert_eq!(r[1].rows[0][0].as_str(), Some("2"));
drop(backend);
let _ = std::fs::remove_file(&path);
}
#[test]
fn blank_store_starts_with_system_graphs() {
let options = oxilite::StoreOptions {
system_graphs: true,
..Default::default()
};
let store = Store::with_backend_and_options(
oxilite::rusqlite::RusqliteBackend::memory().unwrap(),
&options,
)
.unwrap();
let all = QueryOptions {
union_default_graph: true,
..QueryOptions::default()
};
let graphs = values(
&store,
"SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }",
&all,
);
assert_eq!(graphs, ["<oxilite:schema>", "<oxilite:vocabulary>"]);
assert!(store.system_graphs_installed().unwrap());
assert!(!store.install_system_graphs().unwrap(), "already current");
assert_eq!(
values(
&store,
"SELECT ?c WHERE { GRAPH <oxilite:vocabulary> { ?c rdfs:subClassOf <https://oxilite.dev/ns#SchemaGraph> } }",
&all
)
.len(),
3,
"the role classes; oxl:SystemGraph is not a schema graph"
);
assert!(store.schema_graphs().unwrap().is_empty());
load(
&store,
"GRAPH ex:onto { ex:Dog rdfs:subClassOf ex:Animal } ex:rex a ex:Dog .",
);
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()),
["<http://example.com/rex>"]
);
let hidden = QueryOptions {
include_schema_graphs: false,
..all.clone()
};
assert_eq!(
values(
&store,
"SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }",
&hidden
),
["<http://example.com/onto>"]
);
let plain = Store::new().unwrap();
load(&plain, "ex:a ex:b ex:c .");
assert!(!plain.system_graphs_installed().unwrap());
assert!(plain.install_system_graphs().unwrap());
assert!(plain.system_graphs_installed().unwrap());
assert!(plain.schema_graphs().unwrap().is_empty());
}
#[test]
fn imports_bring_registered_ontologies_into_scope() {
let store = store();
load(
&store,
"GRAPH ex:zoo { ex:Dog rdfs:subClassOf ex:Mammal } \
GRAPH ex:core { ex:Mammal rdfs:subClassOf ex:Animal } \
GRAPH ex:a { ex:rex a ex:Dog } \
GRAPH ex:b { ex:tom a ex:Mammal }",
);
let register = |o: &str, r: Registration| {
store
.register_schema_graph(graph(o).as_ref(), SchemaRole::Ontology, &r)
.unwrap();
};
let core = || Registration::new().applies_to([graph("http://example.com/b")]);
let zoo = || Registration::new().applies_to([graph("http://example.com/a")]);
let animals = "SELECT ?x WHERE { ?x a ex:Animal }";
register("http://example.com/core", core());
register("http://example.com/zoo", zoo());
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/tom>"]
);
register(
"http://example.com/zoo",
zoo().with_imports([NamedNode::new("http://example.com/core").unwrap()]),
);
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/rex>", "<http://example.com/tom>"]
);
assert_eq!(
values(
&store,
"SELECT ?p WHERE { GRAPH <oxilite:schema> { ex:zoo ?p ex:core } }",
&QueryOptions::default()
),
["<https://oxilite.dev/ns#imports>"]
);
register("http://example.com/zoo", zoo());
load(
&store,
"GRAPH <oxilite:schema> { ex:zoo owl:imports ex:core }",
);
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/rex>", "<http://example.com/tom>"]
);
assert_eq!(
store
.schema_graphs()
.unwrap()
.iter()
.find(|g| g.graph == graph("http://example.com/zoo"))
.unwrap()
.registration
.imports,
[NamedNode::new("http://example.com/core").unwrap()]
);
register("http://example.com/zoo", zoo());
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/tom>"]
);
register(
"http://example.com/core",
core().with_iri(NamedNode::new("http://example.com/coreOnto").unwrap()),
);
load(
&store,
"GRAPH ex:zoo { ex:zooOnto owl:imports ex:coreOnto }",
);
assert_eq!(
values(&store, animals, &rdfs()),
["<http://example.com/rex>", "<http://example.com/tom>"]
);
load(&store, "GRAPH ex:core { ex:coreOnto owl:imports ex:zoo }");
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Mammal }", &rdfs()),
["<http://example.com/rex>", "<http://example.com/tom>"]
);
store
.set_schema_graph_active(graph("http://example.com/core").as_ref(), false)
.unwrap();
assert!(values(&store, animals, &rdfs()).is_empty());
store
.set_schema_graph_active(graph("http://example.com/zoo").as_ref(), false)
.unwrap();
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Mammal }", &rdfs()),
["<http://example.com/tom>"],
"only the asserted mammal"
);
}
#[test]
fn active_flag_reads_alike_everywhere() {
let store = store();
with_graphs(&store);
store
.register_schema_graph(
graph("http://example.com/o1").as_ref(),
SchemaRole::Ontology,
&Registration::new().applies_to([graph("http://example.com/a")]),
)
.unwrap();
let set = |o: &str| {
store
.update(
format!(
"PREFIX oxl: <https://oxilite.dev/ns#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \
DELETE WHERE {{ GRAPH <oxilite:schema> {{ <http://example.com/o2> ?p ?v }} }} ; \
INSERT DATA {{ GRAPH <oxilite:schema> {{ \
<http://example.com/o2> a oxl:OntologyGraph ; oxl:appliesTo <http://example.com/b> ; oxl:active {o} }} }}"
)
.as_str(),
)
.unwrap();
};
let plants = "SELECT ?x WHERE { ?x a ex:Plant }";
let o2 = |store: &Store| {
store
.schema_graphs()
.unwrap()
.into_iter()
.find(|e| e.graph == graph("http://example.com/o2"))
.unwrap()
};
set("\"false\"");
assert!(o2(&store).registration.active);
assert_eq!(
values(&store, plants, &rdfs()),
["<http://example.com/fido>"]
);
let problems = store.registry_problems().unwrap();
assert_eq!(problems.len(), 1, "{problems:?}");
assert!(problems[0].contains("is not an <http://www.w3.org/2001/XMLSchema#boolean>"));
set("\"0\"^^xsd:boolean");
assert!(!o2(&store).registration.active);
assert!(values(&store, plants, &rdfs()).is_empty());
assert!(store.registry_problems().unwrap().is_empty());
}
#[test]
fn remapping_keeps_roles_and_description() {
let store = store();
with_graphs(&store);
store
.update(
"PREFIX oxl: <https://oxilite.dev/ns#> INSERT DATA { GRAPH <oxilite:schema> { \
<http://example.com/o1> a oxl:OntologyGraph , oxl:ShapesGraph ; oxl:version \"3\" ; \
oxl:appliesTo <http://example.com/a> } }",
)
.unwrap();
let listed = store.schema_graphs().unwrap();
assert_eq!(
listed.iter().map(|e| e.role).collect::<Vec<_>>(),
[SchemaRole::Ontology, SchemaRole::Shacl]
);
assert!(store
.set_schema_graph_targets(
graph("http://example.com/o1").as_ref(),
&[graph("http://example.com/b")]
)
.unwrap());
let listed = store.schema_graphs().unwrap();
assert_eq!(listed.len(), 2);
for e in &listed {
assert_eq!(e.registration.applies_to, [graph("http://example.com/b")]);
assert_eq!(e.registration.version.as_deref(), Some("3"));
}
assert_eq!(
values(&store, "SELECT ?x WHERE { ?x a ex:Animal }", &rdfs()),
["<http://example.com/fido>"]
);
assert!(store
.set_schema_graph_targets(graph("http://example.com/o1").as_ref(), &[])
.unwrap());
assert!(store.schema_graphs().unwrap()[0]
.registration
.applies_to
.is_empty());
assert_eq!(
values(
&store,
"SELECT ?t WHERE { GRAPH <oxilite:schema> { <http://example.com/o1> <https://oxilite.dev/ns#appliesTo> ?t } }",
&QueryOptions::default()
),
["<https://oxilite.dev/ns#AllGraphs>"]
);
assert!(!store
.set_schema_graph_targets(graph("http://example.com/nope").as_ref(), &[])
.unwrap());
assert!(store.registry_problems().unwrap().is_empty());
}