rudof_rdf/rdf_core/query/query_solution.rs
1use crate::rdf_core::{
2 Rdf,
3 query::{VarName, VariableSolutionIndex},
4};
5use serde::Serialize;
6
7/// Represents a single solution from a SPARQL SELECT query.
8///
9/// A query solution is analogous to a row in a SQL result set. It contains
10/// bindings for SPARQL variables, mapping each variable to an RDF term (or
11/// indicating the variable is unbound).
12///
13/// Each solution maintains:
14/// - An ordered list of variable names (the "columns")
15/// - A corresponding list of optional term values (a value of `None` indicates that the variable is unbound in this solution, which can occur with OPTIONAL patterns or UNION queries).
16///
17/// # Type Parameters
18///
19/// * `S` - The RDF graph type implementing [`Rdf`]
20#[derive(Debug, Clone)]
21pub struct QuerySolution<S: Rdf> {
22 /// The ordered list of variable names in this solution.
23 variables: Vec<VarName>,
24 /// The term values bound to each variable.
25 values: Vec<Option<S::Term>>,
26}
27
28impl<S: Rdf> QuerySolution<S> {
29 /// Creates a new query solution from variables and values.
30 ///
31 /// The variables and values vectors must have the same length, with each
32 /// value corresponding to the variable at the same index.
33 ///
34 /// # Arguments
35 ///
36 /// * `variables` - The ordered list of variable names
37 /// * `values` - The ordered list of optional term values
38 pub fn new(variables: Vec<VarName>, values: Vec<Option<S::Term>>) -> QuerySolution<S> {
39 QuerySolution { variables, values }
40 }
41
42 /// Finds and returns the term bound to a variable in this solution.
43 ///
44 /// This method accepts any type implementing [`VariableSolutionIndex`],
45 /// allowing lookup by variable name, position, or custom index types.
46 ///
47 /// # Arguments
48 ///
49 /// * `index` - The variable index (name string, position, or VarName reference)
50 pub fn find_solution(&self, index: impl VariableSolutionIndex<S>) -> Option<&S::Term> {
51 match self.values.get(index.index(self)?) {
52 Some(value) => value.as_ref(),
53 None => None,
54 }
55 }
56
57 /// Returns an iterator over the variable names in this solution.
58 ///
59 /// The iterator yields references to [`VarName`] instances in the order
60 /// they appear in the solution (matching the SELECT clause order).
61 pub fn variables_iter(&self) -> impl Iterator<Item = &VarName> {
62 self.variables.iter()
63 }
64
65 /// Returns a reference to the vector of variable names.
66 pub fn variables(&self) -> &Vec<VarName> {
67 &self.variables
68 }
69
70 /// Converts this solution to use a different RDF type.
71 ///
72 /// This method transforms a solution from one RDF implementation to another
73 /// by applying a conversion function to each bound term. The variable names
74 /// are preserved, and unbound variables remain unbound.
75 ///
76 /// # Type Parameters
77 ///
78 /// * `T` - The target RDF type
79 /// * `F` - The term conversion function type
80 ///
81 /// # Arguments
82 ///
83 /// * `cnv_term` - A function that converts terms from `S::Term` to `T::Term`
84 pub fn convert<T: Rdf, F>(&self, cnv_term: F) -> QuerySolution<T>
85 where
86 F: Fn(&S::Term) -> T::Term,
87 {
88 let cnv_values: Vec<Option<T::Term>> = self.values.iter().map(|s| s.as_ref().map(&cnv_term)).collect();
89 QuerySolution {
90 variables: self.variables.clone(),
91 values: cnv_values,
92 }
93 }
94
95 /// Returns a human-readable string representation of this solution.
96 ///
97 /// The output shows each variable with its bound value (or "()" for unbound
98 /// variables), one per line in the format: `?variable -> value`
99 pub fn show(&self) -> String {
100 let mut result = String::new();
101 for var in self.variables.iter() {
102 let value = match self.find_solution(var) {
103 None => "()".to_string(),
104 Some(v) => format!("{v}"),
105 };
106 result.push_str(format!("{var} -> {value}\n").as_str())
107 }
108 result
109 }
110}
111
112impl<S: Rdf, V: Into<Vec<VarName>>, T: Into<Vec<Option<S::Term>>>> From<(V, T)> for QuerySolution<S> {
113 /// Constructs a query solution from a tuple of variables and values.
114 ///
115 /// This convenience implementation allows creating solutions using tuple
116 /// syntax, automatically converting compatible types into the required
117 /// vector types.
118 ///
119 /// # Arguments
120 ///
121 /// * Tuple of (variables, values) where both elements are convertible
122 /// to their respective vector types
123 fn from((v, s): (V, T)) -> Self {
124 Self {
125 variables: v.into(),
126 values: s.into(),
127 }
128 }
129}
130
131impl<S: Rdf> Serialize for QuerySolution<S> {
132 /// Serializes the query solution as a map of variable names to term strings.
133 ///
134 /// Unbound variables (None values) are **omitted** from the serialized output
135 /// rather than being represented as null. This matches common SPARQL JSON
136 /// result format conventions.
137 fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
138 where
139 Ser: serde::Serializer,
140 {
141 use serde::ser::SerializeMap;
142 let mut map = serializer.serialize_map(Some(self.variables.len()))?;
143 for (var, value) in self.variables.iter().zip(self.values.iter()) {
144 if let Some(term) = value {
145 let str = format!("{term}");
146 map.serialize_entry(&var.as_str(), &str)?;
147 }
148 }
149 map.end()
150 }
151}