vertep_store/graph.rs
1//! The graph store contract: RDF quads in, SPARQL out.
2
3use oxrdf::{NamedNode, Quad, Term, Triple};
4
5use crate::error::StoreError;
6
7/// Which RDF version a graph store speaks for statements about statements.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum RdfCapability {
10 /// RDF 1.2: triple terms and reifiers (`rdf:reifies`) can be written and queried.
11 Rdf12,
12 /// RDF 1.1 only: annotations use classic `rdf:Statement` reification
13 /// ([`crate::reification`]).
14 Rdf11,
15}
16
17/// What a graph store declares about itself, read before writing anything that depends on it.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct GraphCapabilities {
20 /// RDF 1.2 triple terms, or the RDF 1.1 fallback.
21 pub rdf: RdfCapability,
22 /// The default graph of a query is the union of all graphs (true on tiramemsu), instead of
23 /// only the statements outside named graphs (SPARQL's default). Portable queries use
24 /// `GRAPH` and do not depend on it.
25 pub union_default_graph: bool,
26 /// IRI prefixes of predicates the store writes for its own bookkeeping (graph memberships,
27 /// derivation links). Generic readers, such as annotation decoding, skip them.
28 pub reserved_namespaces: Vec<String>,
29}
30
31impl GraphCapabilities {
32 /// Capabilities of a plain SPARQL dataset with the given RDF version.
33 pub fn plain(rdf: RdfCapability) -> Self {
34 GraphCapabilities {
35 rdf,
36 union_default_graph: false,
37 reserved_namespaces: Vec::new(),
38 }
39 }
40
41 /// True when `predicate` is in one of the reserved namespaces.
42 pub fn is_reserved(&self, predicate: &str) -> bool {
43 self.reserved_namespaces
44 .iter()
45 .any(|ns| predicate.starts_with(ns.as_str()))
46 }
47}
48
49/// A table of SPARQL solutions: variable names in projection order and rows of optional terms
50/// (`None` is unbound).
51#[derive(Clone, Debug, Default, PartialEq, Eq)]
52pub struct Solutions {
53 /// The variable names, without `?`.
54 pub variables: Vec<String>,
55 /// The rows, each parallel to `variables`.
56 pub rows: Vec<Vec<Option<Term>>>,
57}
58
59impl Solutions {
60 /// The column of variable `var`.
61 pub fn column(&self, var: &str) -> Option<usize> {
62 self.variables.iter().position(|v| v == var)
63 }
64
65 /// The value of `var` in row `row`, if bound.
66 pub fn get(&self, row: usize, var: &str) -> Option<&Term> {
67 let c = self.column(var)?;
68 self.rows.get(row)?.get(c)?.as_ref()
69 }
70
71 /// Every bound value of `var`, in row order.
72 pub fn values(&self, var: &str) -> Vec<Term> {
73 let Some(c) = self.column(var) else {
74 return Vec::new();
75 };
76 self.rows
77 .iter()
78 .filter_map(|r| r.get(c).cloned().flatten())
79 .collect()
80 }
81
82 /// The number of rows.
83 pub fn len(&self) -> usize {
84 self.rows.len()
85 }
86
87 /// True when there are no rows.
88 pub fn is_empty(&self) -> bool {
89 self.rows.is_empty()
90 }
91}
92
93/// The result of a SPARQL query.
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum QueryResults {
96 /// A `SELECT` result.
97 Solutions(Solutions),
98 /// An `ASK` answer.
99 Boolean(bool),
100 /// A `CONSTRUCT` or `DESCRIBE` graph.
101 Graph(Vec<Triple>),
102}
103
104impl QueryResults {
105 /// The solutions of a `SELECT`.
106 ///
107 /// # Errors
108 ///
109 /// [`StoreError::Invalid`] for another result form.
110 pub fn into_solutions(self) -> Result<Solutions, StoreError> {
111 match self {
112 QueryResults::Solutions(s) => Ok(s),
113 _ => Err(StoreError::invalid("expected SELECT solutions")),
114 }
115 }
116
117 /// The answer of an `ASK`.
118 ///
119 /// # Errors
120 ///
121 /// [`StoreError::Invalid`] for another result form.
122 pub fn into_boolean(self) -> Result<bool, StoreError> {
123 match self {
124 QueryResults::Boolean(b) => Ok(b),
125 _ => Err(StoreError::invalid("expected an ASK answer")),
126 }
127 }
128
129 /// The triples of a `CONSTRUCT`.
130 ///
131 /// # Errors
132 ///
133 /// [`StoreError::Invalid`] for another result form.
134 pub fn into_graph(self) -> Result<Vec<Triple>, StoreError> {
135 match self {
136 QueryResults::Graph(g) => Ok(g),
137 _ => Err(StoreError::invalid("expected a CONSTRUCT graph")),
138 }
139 }
140}
141
142/// A graph store: accepts RDF quads and answers SPARQL 1.1 queries, and declares whether it
143/// supports RDF 1.2 triple terms.
144///
145/// Each call is one request: a store need not offer compare-and-set or transactions across
146/// requests, which is why the journal is a separate contract. Writes of quads without blank
147/// nodes are idempotent: inserting a quad twice leaves the store as inserting it once.
148// @tg: implements:: [[openspec:storage#Graph Store Contract]]
149pub trait GraphStore: Send + Sync {
150 /// What the store declares about itself.
151 fn capabilities(&self) -> GraphCapabilities;
152
153 /// Whether the store supports RDF 1.2 triple terms.
154 fn rdf_capability(&self) -> RdfCapability {
155 self.capabilities().rdf
156 }
157
158 /// Adds `quads`.
159 ///
160 /// # Errors
161 ///
162 /// [`StoreError::Unavailable`] when the store cannot be reached, [`StoreError::Invalid`]
163 /// for a quad it cannot hold, [`StoreError::Backend`] otherwise.
164 fn insert_quads(&self, quads: &[Quad]) -> Result<(), StoreError>;
165
166 /// Removes `quads` (absent ones are ignored). Blank nodes cannot be named for removal.
167 ///
168 /// # Errors
169 ///
170 /// As [`GraphStore::insert_quads`].
171 fn remove_quads(&self, quads: &[Quad]) -> Result<(), StoreError>;
172
173 /// Removes every statement of the named graph `graph`.
174 ///
175 /// # Errors
176 ///
177 /// As [`GraphStore::insert_quads`].
178 fn clear_graph(&self, graph: &NamedNode) -> Result<(), StoreError>;
179
180 /// Runs a SPARQL 1.1 query (`SELECT`, `ASK`, `CONSTRUCT`, `DESCRIBE`).
181 ///
182 /// # Errors
183 ///
184 /// [`StoreError::Invalid`] for invalid query text, and the errors of
185 /// [`GraphStore::insert_quads`].
186 fn query(&self, sparql: &str) -> Result<QueryResults, StoreError>;
187}
188
189impl<G: GraphStore + ?Sized> GraphStore for std::sync::Arc<G> {
190 fn capabilities(&self) -> GraphCapabilities {
191 (**self).capabilities()
192 }
193 fn insert_quads(&self, quads: &[Quad]) -> Result<(), StoreError> {
194 (**self).insert_quads(quads)
195 }
196 fn remove_quads(&self, quads: &[Quad]) -> Result<(), StoreError> {
197 (**self).remove_quads(quads)
198 }
199 fn clear_graph(&self, graph: &NamedNode) -> Result<(), StoreError> {
200 (**self).clear_graph(graph)
201 }
202 fn query(&self, sparql: &str) -> Result<QueryResults, StoreError> {
203 (**self).query(sparql)
204 }
205}