use std::collections::HashMap;
use crate::property_path::TripleStore;
use crate::schema::nquads::Quad;
type GraphKey = Option<String>;
type SubjectIndex = HashMap<String, Vec<(String, String)>>;
pub struct QuadStore {
stores: HashMap<GraphKey, TripleStore>,
subject_index: HashMap<GraphKey, SubjectIndex>,
}
impl QuadStore {
pub fn new() -> Self {
Self {
stores: HashMap::new(),
subject_index: HashMap::new(),
}
}
pub fn insert_quad(&mut self, q: Quad) {
let graph_key = q.graph.clone();
let store = self.stores.entry(graph_key.clone()).or_default();
store.add(q.subject.clone(), q.predicate.clone(), q.object.clone());
self.subject_index
.entry(graph_key)
.or_default()
.entry(q.subject)
.or_default()
.push((q.predicate, q.object));
}
pub fn query_subject(&self, graph: Option<&str>, subject: &str) -> Vec<(String, String)> {
let key = graph.map(str::to_string);
self.subject_index
.get(&key)
.and_then(|idx| idx.get(subject))
.cloned()
.unwrap_or_default()
}
pub fn query_predicate(&self, graph: Option<&str>, predicate: &str) -> Vec<(String, String)> {
let key = graph.map(str::to_string);
self.stores
.get(&key)
.map(|store| {
store
.triples_with_predicate(predicate)
.into_iter()
.map(|(s, o)| (s.to_string(), o.to_string()))
.collect()
})
.unwrap_or_default()
}
pub fn query_object(&self, graph: Option<&str>, object: &str) -> Vec<(String, String)> {
let key = graph.map(str::to_string);
self.stores
.get(&key)
.map(|store| {
let graph_idx = self.subject_index.get(&key);
let predicates: std::collections::HashSet<String> = graph_idx
.map(|idx| {
idx.values()
.flat_map(|pairs| pairs.iter().map(|(p, _)| p.clone()))
.collect()
})
.unwrap_or_default();
predicates
.into_iter()
.flat_map(|pred| {
store
.subjects_to(object, &pred)
.into_iter()
.map(|s| (s.to_string(), pred.clone()))
.collect::<Vec<_>>()
})
.collect()
})
.unwrap_or_default()
}
pub fn iter_graphs(&self) -> impl Iterator<Item = &Option<String>> {
self.stores.keys()
}
pub fn graph_count(&self) -> usize {
self.stores.len()
}
pub fn total_quads(&self) -> usize {
self.stores.values().map(|s| s.len()).sum()
}
pub fn get_store(&self, graph: Option<&str>) -> Option<&TripleStore> {
let key = graph.map(str::to_string);
self.stores.get(&key)
}
pub fn is_empty(&self) -> bool {
self.stores.values().all(|s| s.is_empty())
}
}
impl Default for QuadStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_quad(s: &str, p: &str, o: &str, g: Option<&str>) -> Quad {
Quad {
subject: s.to_string(),
predicate: p.to_string(),
object: o.to_string(),
graph: g.map(|x| x.to_string()),
}
}
#[test]
fn test_per_graph_isolation() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("Alice", "type", "Person", None));
qs.insert_quad(make_quad("Bob", "type", "Agent", Some("http://g1")));
qs.insert_quad(make_quad(
"Carol",
"type",
"Organization",
Some("http://g2"),
));
let default_results = qs.query_subject(None, "Alice");
assert!(!default_results.is_empty());
let g1_results = qs.query_subject(Some("http://g1"), "Alice");
assert!(g1_results.is_empty());
let bob_results = qs.query_subject(Some("http://g1"), "Bob");
assert!(!bob_results.is_empty());
}
#[test]
fn test_iter_graphs() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("x", "p", "o", None));
qs.insert_quad(make_quad("x", "p", "o", Some("http://g1")));
qs.insert_quad(make_quad("x", "p", "o", Some("http://g2")));
let graphs: Vec<_> = qs.iter_graphs().collect();
assert_eq!(graphs.len(), 3);
}
#[test]
fn test_query_predicate() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("Alice", "knows", "Bob", None));
qs.insert_quad(make_quad("Alice", "knows", "Carol", None));
qs.insert_quad(make_quad("Dave", "knows", "Eve", Some("http://g1")));
let pairs = qs.query_predicate(None, "knows");
assert_eq!(pairs.len(), 2);
let g1_pairs = qs.query_predicate(Some("http://g1"), "knows");
assert_eq!(g1_pairs.len(), 1);
let missing = qs.query_predicate(Some("http://missing"), "knows");
assert!(missing.is_empty());
}
#[test]
fn test_query_object() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("Alice", "type", "Person", None));
qs.insert_quad(make_quad("Bob", "type", "Person", None));
qs.insert_quad(make_quad("Carol", "type", "Agent", None));
let persons = qs.query_object(None, "Person");
assert_eq!(persons.len(), 2);
let agents = qs.query_object(None, "Agent");
assert_eq!(agents.len(), 1);
}
#[test]
fn test_default_graph_isolation() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("X", "p", "Y", None));
qs.insert_quad(make_quad("X", "p", "Z", Some("http://named")));
assert_eq!(qs.query_subject(None, "X").len(), 1);
assert_eq!(qs.query_subject(Some("http://named"), "X").len(), 1);
}
#[test]
fn test_graph_count_and_total() {
let mut qs = QuadStore::new();
assert_eq!(qs.graph_count(), 0);
assert_eq!(qs.total_quads(), 0);
assert!(qs.is_empty());
qs.insert_quad(make_quad("a", "b", "c", None));
qs.insert_quad(make_quad("a", "b", "c", Some("http://g1")));
assert_eq!(qs.graph_count(), 2);
assert_eq!(qs.total_quads(), 2);
assert!(!qs.is_empty());
}
#[test]
fn test_subject_returns_correct_predicate_object_pairs() {
let mut qs = QuadStore::new();
qs.insert_quad(make_quad("Alice", "knows", "Bob", None));
qs.insert_quad(make_quad("Alice", "age", "30", None));
let pairs = qs.query_subject(None, "Alice");
assert_eq!(pairs.len(), 2);
let knows_pair = pairs.iter().find(|(p, _)| p == "knows");
assert!(knows_pair.is_some());
assert_eq!(knows_pair.map(|(_, o)| o.as_str()), Some("Bob"));
}
#[test]
fn test_into_quad_store_via_nquads_processor() {
use crate::schema::nquads::NQuadsProcessor;
let nquads = r#"<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> <http://example.org/g1> .
<http://example.org/Bob> <http://example.org/knows> <http://example.org/Charlie> .
<http://example.org/Dave> <http://example.org/age> "42" <http://example.org/g2> .
"#;
let mut processor = NQuadsProcessor::new();
processor.load_nquads(nquads).expect("load_nquads failed");
let qs = processor.into_quad_store();
assert_eq!(qs.graph_count(), 3); assert_eq!(qs.total_quads(), 3);
let alice = qs.query_subject(Some("http://example.org/g1"), "http://example.org/Alice");
assert_eq!(alice.len(), 1);
let bob = qs.query_subject(None, "http://example.org/Bob");
assert_eq!(bob.len(), 1);
}
}