wasccgraph_common/
lib.rs

1//! # Common types (GraphDB)
2//!
3//! A set of common types that largely support the `ResultSet` type, a wrapper
4//! around results that come back from a graph database that supports dynamic,
5//! strongly-typed tuple extraction.
6//!
7//! These types are mostly copied wholesale from the RedisGraph client library
8//! that can be found at https://github.com/malte-v/redisgraph-rs
9
10use std::collections::HashMap;
11
12#[macro_use]
13extern crate serde_derive;
14
15mod conversions;
16mod errors;
17pub mod protocol;
18
19pub use crate::errors::GraphResult;
20pub use errors::GraphError;
21
22pub const CAPID_GRAPHDB: &str = "wascc:graphdb";
23
24/// Represents the return data from a graph. You shouldn't have to use this
25/// type directly, but rather extract rows and columns via vectors of tuples
26/// and pattern matching/destructing
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct ResultSet {
29    /// The columns of this result set.
30    ///     
31    /// Empty if the response did not contain any return values.
32    pub columns: Vec<Column>,
33    /// Contains statistics messages from the response.
34    pub statistics: Statistics,
35}
36
37/// Human-readable statistics that are optionally returned with each query
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct Statistics(pub Vec<String>);
40
41impl ResultSet {
42    /// Returns the number of rows in the result set.
43    pub fn num_columns(&self) -> usize {
44        self.columns.len()
45    }
46
47    /// Returns the number of columns in the result set.
48    pub fn num_rows(&self) -> usize {
49        match self.columns.get(0) {
50            Some(first_column) => first_column.len(),
51            None => 0,
52        }
53    }
54
55    /// Returns the scalar at the given position.
56    ///
57    /// Returns an error if the value at the given position is not a scalar
58    /// or if the position is out of bounds.
59    pub fn get_scalar(&self, row_idx: usize, column_idx: usize) -> GraphResult<&Scalar> {
60        match self.columns.get(column_idx) {
61            Some(column) => match column {
62                Column::Scalars(cells) => match cells.get(row_idx) {
63                    Some(cell) => Ok(cell),
64                    None => client_type_error!(
65                        "failed to get scalar: row index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
66                    ),
67                },
68                any => client_type_error!(
69                    "failed to get scalar: expected column of scalars, found {:?}",
70                    any
71                ),
72            }
73            None => client_type_error!(
74                "failed to get scalar: column index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
75            ),
76        }
77    }
78
79    /// Returns the node at the given position.
80    ///
81    /// Returns an error if the value at the given position is not a node
82    /// or if the position is out of bounds.
83    pub fn get_node(&self, row_idx: usize, column_idx: usize) -> GraphResult<&Node> {
84        match self.columns.get(column_idx) {
85            Some(column) => match column {
86                Column::Nodes(cells) => match cells.get(row_idx) {
87                    Some(cell) => Ok(cell),
88                    None => client_type_error!(
89                        "failed to get node: row index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
90                    ),
91                },
92                any => client_type_error!(
93                    "failed to get node: expected column of nodes, found {:?}",
94                    any
95                ),
96            }
97            None => client_type_error!(
98                "failed to get node: column index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
99            ),
100        }
101    }
102
103    /// Returns the relation at the given position.
104    ///
105    /// Returns an error if the value at the given position is not a relation
106    /// or if the position is out of bounds.
107    pub fn get_relation(&self, row_idx: usize, column_idx: usize) -> GraphResult<&Relation> {
108        match self.columns.get(column_idx) {
109            Some(column) => match column {
110                Column::Relations(cells) => match cells.get(row_idx) {
111                    Some(cell) => Ok(cell),
112                    None => client_type_error!(
113                        "failed to get relation: row index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
114                    ),
115                },
116                any => client_type_error!(
117                    "failed to get relation: expected column of relations, found {:?}",
118                    any
119                ),
120            }
121            None => client_type_error!(
122                "failed to get relation: column index out of bounds: the len is {:?} but the index is {:?}", self.columns.len(), column_idx,
123            ),
124        }
125    }
126}
127
128impl FromTable for ResultSet {
129    fn from_table(result_set: &ResultSet) -> GraphResult<Self> {
130        Ok(result_set.clone())
131    }
132}
133
134impl<T: FromRow> FromTable for Vec<T> {
135    fn from_table(result_set: &ResultSet) -> GraphResult<Self> {
136        let num_rows = result_set.num_rows();
137        let mut ret = Self::with_capacity(num_rows);
138
139        for i in 0..num_rows {
140            ret.push(T::from_row(result_set, i)?);
141        }
142
143        Ok(ret)
144    }
145}
146
147pub trait FromTable: Sized {
148    fn from_table(result_set: &ResultSet) -> GraphResult<Self>;
149}
150
151/// Implemented by types that can be constructed from a row in a [`ResultSet`](../result_set/struct.ResultSet.html).
152pub trait FromRow: Sized {
153    fn from_row(result_set: &ResultSet, row_idx: usize) -> GraphResult<Self>;
154}
155
156/// Implemented by types that can be constructed from a cell in a [`ResultSet`](../result_set/struct.ResultSet.html).
157pub trait FromCell: Sized {
158    fn from_cell(result_set: &ResultSet, row_idx: usize, column_idx: usize) -> GraphResult<Self>;
159}
160
161/// A single column of the result set.
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub enum Column {
164    Scalars(Vec<Scalar>),
165    Nodes(Vec<Node>),
166    Relations(Vec<Relation>),
167}
168
169impl Column {
170    /// Returns the length of this column.
171    pub fn len(&self) -> usize {
172        match self {
173            Self::Scalars(cells) => cells.len(),
174            Self::Nodes(cells) => cells.len(),
175            Self::Relations(cells) => cells.len(),
176        }
177    }
178
179    /// Returns `true` if this column is empty.
180    pub fn is_empty(&self) -> bool {
181        self.len() == 0
182    }
183}
184
185#[derive(Serialize, Debug, Deserialize)]
186enum ColumnType {
187    Unknown = 0,
188    Scalar = 1,
189    Node = 2,
190    Relation = 3,
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct Relation {
195    /// The type name of this relation.
196    pub type_name: String,
197    /// The properties of this relation.
198    pub properties: HashMap<String, Scalar>,
199}
200
201/// A scalar value returned by the Graph provider
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub enum Scalar {
204    Nil,
205    Boolean(bool),
206    Integer(i64),
207    Double(f64),
208    String(GraphString), // A string returned by the graph DB
209}
210
211/// The valid types of scalars
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213enum ScalarType {
214    Unknown = 0,
215    Nil = 1,
216    String = 2,
217    Integer = 3,
218    Boolean = 4,
219    Double = 5,
220}
221
222/// A string returned by the graph DB as a vector of bytes
223#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
224pub struct GraphString(pub Vec<u8>);
225
226// Methods to round-trip between regular strings and GraphStrings
227
228impl From<String> for GraphString {
229    fn from(string: String) -> Self {
230        Self(string.into_bytes())
231    }
232}
233
234impl From<Vec<u8>> for GraphString {
235    fn from(bytes: Vec<u8>) -> Self {
236        Self(bytes)
237    }
238}
239
240impl From<GraphString> for Vec<u8> {
241    fn from(redis_string: GraphString) -> Self {
242        redis_string.0
243    }
244}
245
246// A node returned by the Graph DB provider
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub struct Node {
249    /// The labels attached to this node.
250    pub labels: Vec<String>,
251    /// The properties of this node.
252    pub properties: HashMap<String, Scalar>,
253}
254
255// Macro generates generic "From" implementations to allow
256// tuples/vecs-of-tuples to be extracted from various types
257//
258// Altered version of https://github.com/mitsuhiko/redis-rs/blob/master/src/types.rs#L1080
259macro_rules! impl_row_for_tuple {
260    () => ();
261    ($($name:ident,)+) => (
262        #[doc(hidden)]
263        impl<$($name: FromCell),*> FromRow for ($($name,)*) {
264            // we have local variables named T1 as dummies and those
265            // variables are unused.
266            #[allow(non_snake_case, unused_variables, clippy::eval_order_dependence)]
267            fn from_row(result_set: &ResultSet, row_idx: usize) -> GraphResult<($($name,)*)> {
268                // hacky way to count the tuple size
269                let mut n = 0;
270                $(let $name = (); n += 1;)*
271                if result_set.num_columns() != n {
272                    return client_type_error!(
273                        "failed to construct tuple: tuple has {:?} entries but result table has {:?} columns",
274                        n,
275                        result_set.num_columns()
276                    );
277                }
278
279                // this is pretty ugly too. The { i += 1; i - 1 } is rust's
280                // postfix increment :)
281                let mut i = 0;
282                Ok(($({let $name = (); $name::from_cell(result_set, row_idx, { i += 1; i - 1 })?},)*))
283            }
284        }
285        impl_row_for_tuple_peel!($($name,)*);
286    )
287}
288
289// Support for the recursive macro calls
290macro_rules! impl_row_for_tuple_peel {
291    ($name:ident, $($other:ident,)*) => (impl_row_for_tuple!($($other,)*);)
292}
293
294// The library supports tuples of up to 12 items
295impl_row_for_tuple! { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, }
296
297// Row and column indices default to zero for lower-level values
298impl<T: FromCell> FromRow for T {
299    fn from_row(result_set: &ResultSet, row_idx: usize) -> GraphResult<Self> {
300        T::from_cell(result_set, row_idx, 0)
301    }
302}
303
304impl<T: FromRow> FromTable for T {
305    fn from_table(result_set: &ResultSet) -> GraphResult<Self> {
306        T::from_row(result_set, 0)
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use super::*;
313
314    // Verifies that we can extract the tuples we expect from the raw ResultSet
315    // structure and that the various return types are automatically converted
316    #[test]
317    fn tuple_extraction_test() {
318        let (name, birth_year): (String, u32) = fake_query("fake query").unwrap();
319        assert_eq!("tester", name);
320        assert_eq!(1985, birth_year);
321    }
322
323    #[test]
324    fn vec_tuple_extraction_test() {
325        let res: Vec<(String, u32)> = fake_vec_query("foo").unwrap();
326        assert_eq!(("tester".to_string(), 1985), res[0]);
327        assert_eq!(("test2".to_string(), 1986), res[1]);
328    }
329
330    fn fake_vec_query<T: FromTable>(_query: &str) -> GraphResult<T> {
331        query_with_statistics2().map(|(value, _)| value)
332    }
333
334    fn fake_query<T: FromTable>(_query: &str) -> GraphResult<T> {
335        query_with_statistics().map(|(value, _)| value)
336    }
337
338    fn query_with_statistics<T: FromTable>() -> GraphResult<(T, Statistics)> {
339        let result_set = get_result_set()?;
340        let value = T::from_table(&result_set)?;
341        Ok((value, result_set.statistics))
342    }
343
344    fn query_with_statistics2<T: FromTable>() -> GraphResult<(T, Statistics)> {
345        let result_set = get_result_set2()?;
346        let value = T::from_table(&result_set)?;
347        Ok((value, result_set.statistics))
348    }
349
350    fn get_result_set() -> GraphResult<ResultSet> {
351        Ok(ResultSet {
352            statistics: Statistics(vec![]),
353            columns: vec![
354                Column::Scalars(vec![Scalar::String(GraphString::from(
355                    "tester".to_string(),
356                ))]),
357                Column::Scalars(vec![Scalar::Integer(1985)]),
358            ],
359        })
360    }
361
362    fn get_result_set2() -> GraphResult<ResultSet> {
363        Ok(ResultSet {
364            statistics: Statistics(vec![]),
365            columns: vec![
366                Column::Scalars(vec![
367                    Scalar::String(GraphString::from("tester".to_string())),
368                    Scalar::String(GraphString::from("test2".to_string())),
369                ]),
370                Column::Scalars(vec![Scalar::Integer(1985), Scalar::Integer(1986)]),
371            ],
372        })
373    }
374}