sparesults/
solution.rs

1//! Definition of [`QuerySolution`] structure and associated utility constructions.
2
3use oxrdf::{Term, Variable, VariableRef};
4use std::fmt;
5use std::iter::Zip;
6use std::ops::Index;
7use std::sync::Arc;
8
9/// Tuple associating variables and terms that are the result of a SPARQL query.
10///
11/// It is the equivalent of a row in SQL.
12///
13/// ```
14/// use sparesults::QuerySolution;
15/// use oxrdf::{Variable, Literal};
16///
17/// let solution = QuerySolution::from((vec![Variable::new("foo")?, Variable::new("bar")?], vec![Some(Literal::from(1).into()), None]));
18/// assert_eq!(solution.get("foo"), Some(&Literal::from(1).into())); // Get the value of the variable ?foo if it exists (here yes).
19/// assert_eq!(solution.get(1), None); // Get the value of the second column if it exists (here no).
20/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
21/// ```
22pub struct QuerySolution {
23    variables: Arc<[Variable]>,
24    values: Vec<Option<Term>>,
25}
26
27impl QuerySolution {
28    /// Returns a value for a given position in the tuple ([`usize`](std::usize)) or a given variable name ([`&str`](std::str), [`Variable`] or [`VariableRef`]).
29    ///
30    /// ```
31    /// use sparesults::QuerySolution;
32    /// use oxrdf::{Variable, Literal};
33    ///
34    /// let solution = QuerySolution::from((vec![Variable::new("foo")?, Variable::new("bar")?], vec![Some(Literal::from(1).into()), None]));
35    /// assert_eq!(solution.get("foo"), Some(&Literal::from(1).into())); // Get the value of the variable ?foo if it exists (here yes).
36    /// assert_eq!(solution.get(1), None); // Get the value of the second column if it exists (here no).
37    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
38    /// ```
39    #[inline]
40    pub fn get(&self, index: impl VariableSolutionIndex) -> Option<&Term> {
41        self.values.get(index.index(self)?).and_then(Option::as_ref)
42    }
43
44    /// The number of variables which could be bound.
45    ///
46    /// It is also the number of columns in the solutions table.
47    ///
48    /// ```
49    /// use oxrdf::{Literal, Variable};
50    /// use sparesults::QuerySolution;
51    ///
52    /// let solution = QuerySolution::from((
53    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
54    ///     vec![Some(Literal::from(1).into()), None],
55    /// ));
56    /// assert_eq!(solution.len(), 2);
57    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
58    /// ```
59    #[inline]
60    pub fn len(&self) -> usize {
61        self.values.len()
62    }
63
64    /// Is there any variable bound in the table?
65    ///
66    /// ```
67    /// use oxrdf::{Literal, Variable};
68    /// use sparesults::QuerySolution;
69    ///
70    /// let solution = QuerySolution::from((
71    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
72    ///     vec![Some(Literal::from(1).into()), None],
73    /// ));
74    /// assert!(!solution.is_empty());
75    ///
76    /// let empty_solution = QuerySolution::from((
77    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
78    ///     vec![None, None],
79    /// ));
80    /// assert!(empty_solution.is_empty());
81    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
82    /// ```
83    #[inline]
84    pub fn is_empty(&self) -> bool {
85        self.values.iter().all(Option::is_none)
86    }
87
88    /// Returns an iterator over bound variables.
89    ///
90    /// ```
91    /// use oxrdf::{Literal, Variable};
92    /// use sparesults::QuerySolution;
93    ///
94    /// let solution = QuerySolution::from((
95    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
96    ///     vec![Some(Literal::from(1).into()), None],
97    /// ));
98    /// assert_eq!(
99    ///     solution.iter().collect::<Vec<_>>(),
100    ///     vec![(&Variable::new("foo")?, &Literal::from(1).into())]
101    /// );
102    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
103    /// ```
104    #[inline]
105    pub fn iter(&self) -> impl Iterator<Item = (&Variable, &Term)> {
106        self.into_iter()
107    }
108
109    /// Returns the ordered slice of variable values.
110    ///
111    /// ```
112    /// use oxrdf::{Literal, Variable};
113    /// use sparesults::QuerySolution;
114    ///
115    /// let solution = QuerySolution::from((
116    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
117    ///     vec![Some(Literal::from(1).into()), None],
118    /// ));
119    /// assert_eq!(solution.values(), &[Some(Literal::from(1).into()), None]);
120    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
121    /// ```
122    #[inline]
123    pub fn values(&self) -> &[Option<Term>] {
124        &self.values
125    }
126
127    /// Returns the ordered slice of the solution variables, bound or not.
128    ///
129    /// ```
130    /// use oxrdf::{Literal, Variable};
131    /// use sparesults::QuerySolution;
132    ///
133    /// let solution = QuerySolution::from((
134    ///     vec![Variable::new("foo")?, Variable::new("bar")?],
135    ///     vec![Some(Literal::from(1).into()), None],
136    /// ));
137    /// assert_eq!(
138    ///     solution.variables(),
139    ///     &[Variable::new("foo")?, Variable::new("bar")?]
140    /// );
141    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
142    /// ```
143    #[inline]
144    pub fn variables(&self) -> &[Variable] {
145        &self.variables
146    }
147}
148
149impl<V: Into<Arc<[Variable]>>, S: Into<Vec<Option<Term>>>> From<(V, S)> for QuerySolution {
150    #[inline]
151    fn from((v, s): (V, S)) -> Self {
152        Self {
153            variables: v.into(),
154            values: s.into(),
155        }
156    }
157}
158
159impl<'a> IntoIterator for &'a QuerySolution {
160    type Item = (&'a Variable, &'a Term);
161    type IntoIter = Iter<'a>;
162
163    #[inline]
164    fn into_iter(self) -> Self::IntoIter {
165        Iter {
166            inner: self.variables.iter().zip(&self.values),
167        }
168    }
169}
170
171impl Index<usize> for QuerySolution {
172    type Output = Term;
173
174    #[allow(clippy::panic)]
175    #[inline]
176    fn index(&self, index: usize) -> &Self::Output {
177        self.get(index)
178            .unwrap_or_else(|| panic!("The column {index} is not set in this solution"))
179    }
180}
181
182impl Index<&str> for QuerySolution {
183    type Output = Term;
184
185    #[allow(clippy::panic)]
186    #[inline]
187    fn index(&self, index: &str) -> &Self::Output {
188        self.get(index)
189            .unwrap_or_else(|| panic!("The variable ?{index} is not set in this solution"))
190    }
191}
192
193impl Index<VariableRef<'_>> for QuerySolution {
194    type Output = Term;
195
196    #[allow(clippy::panic)]
197    #[inline]
198    fn index(&self, index: VariableRef<'_>) -> &Self::Output {
199        self.get(index)
200            .unwrap_or_else(|| panic!("The variable {index} is not set in this solution"))
201    }
202}
203impl Index<Variable> for QuerySolution {
204    type Output = Term;
205
206    #[inline]
207    fn index(&self, index: Variable) -> &Self::Output {
208        self.index(index.as_ref())
209    }
210}
211
212impl Index<&Variable> for QuerySolution {
213    type Output = Term;
214
215    #[inline]
216    fn index(&self, index: &Variable) -> &Self::Output {
217        self.index(index.as_ref())
218    }
219}
220
221impl PartialEq for QuerySolution {
222    fn eq(&self, other: &Self) -> bool {
223        for (k, v) in self.iter() {
224            if other.get(k) != Some(v) {
225                return false;
226            }
227        }
228        for (k, v) in other.iter() {
229            if self.get(k) != Some(v) {
230                return false;
231            }
232        }
233        true
234    }
235}
236
237impl Eq for QuerySolution {}
238
239impl fmt::Debug for QuerySolution {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        f.debug_map().entries(self.iter()).finish()
242    }
243}
244
245/// An iterator over [`QuerySolution`] bound variables.
246///
247/// ```
248/// use oxrdf::{Literal, Variable};
249/// use sparesults::QuerySolution;
250///
251/// let solution = QuerySolution::from((
252///     vec![Variable::new("foo")?, Variable::new("bar")?],
253///     vec![Some(Literal::from(1).into()), None],
254/// ));
255/// assert_eq!(
256///     solution.iter().collect::<Vec<_>>(),
257///     vec![(&Variable::new("foo")?, &Literal::from(1).into())]
258/// );
259/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
260/// ```
261pub struct Iter<'a> {
262    inner: Zip<std::slice::Iter<'a, Variable>, std::slice::Iter<'a, Option<Term>>>,
263}
264
265impl<'a> Iterator for Iter<'a> {
266    type Item = (&'a Variable, &'a Term);
267
268    #[inline]
269    fn next(&mut self) -> Option<Self::Item> {
270        for (variable, value) in &mut self.inner {
271            if let Some(value) = value {
272                return Some((variable, value));
273            }
274        }
275        None
276    }
277
278    #[inline]
279    fn size_hint(&self) -> (usize, Option<usize>) {
280        (0, self.inner.size_hint().1)
281    }
282}
283
284/// A utility trait to get values for a given variable or tuple position.
285///
286/// See [`QuerySolution::get`].
287pub trait VariableSolutionIndex {
288    fn index(self, solution: &QuerySolution) -> Option<usize>;
289}
290
291impl VariableSolutionIndex for usize {
292    #[inline]
293    fn index(self, _: &QuerySolution) -> Option<usize> {
294        Some(self)
295    }
296}
297
298impl VariableSolutionIndex for &str {
299    #[inline]
300    fn index(self, solution: &QuerySolution) -> Option<usize> {
301        solution.variables.iter().position(|v| v.as_str() == self)
302    }
303}
304
305impl VariableSolutionIndex for VariableRef<'_> {
306    #[inline]
307    fn index(self, solution: &QuerySolution) -> Option<usize> {
308        solution.variables.iter().position(|v| *v == self)
309    }
310}
311
312impl VariableSolutionIndex for &Variable {
313    #[inline]
314    fn index(self, solution: &QuerySolution) -> Option<usize> {
315        self.as_ref().index(solution)
316    }
317}
318
319impl VariableSolutionIndex for Variable {
320    #[inline]
321    fn index(self, solution: &QuerySolution) -> Option<usize> {
322        self.as_ref().index(solution)
323    }
324}