Skip to main content

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}