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}