spanner_rs/
result_set.rs

1use std::convert::TryFrom;
2use std::convert::TryInto;
3
4use crate::Error;
5use crate::FromSpanner;
6use crate::StructType;
7use crate::Transaction;
8use crate::Value;
9use google_api_proto::google::spanner::v1 as proto;
10
11/// A trait implemented by types that can index into a row.
12///
13/// Only the crate itself implements this.
14pub trait RowIndex: private::Sealed {
15    #[doc(hidden)]
16    fn index(&self, struct_type: &StructType) -> Option<usize>;
17}
18
19/// Allows indexing into a row using a column index.
20impl RowIndex for usize {
21    fn index(&self, struct_type: &StructType) -> Option<usize> {
22        if *self < struct_type.fields().len() {
23            Some(*self)
24        } else {
25            None
26        }
27    }
28}
29
30/// Allows indexing into a row using a column name.
31impl RowIndex for str {
32    fn index(&self, struct_type: &StructType) -> Option<usize> {
33        struct_type.field_index(self)
34    }
35}
36
37impl<'a, T> RowIndex for &'a T
38where
39    T: RowIndex + ?Sized,
40{
41    fn index(&self, struct_type: &StructType) -> Option<usize> {
42        <T as RowIndex>::index(self, struct_type)
43    }
44}
45
46mod private {
47    pub trait Sealed {}
48
49    impl Sealed for usize {}
50    impl Sealed for str {}
51    impl<'a, T> Sealed for &'a T where T: ?Sized + Sealed {}
52}
53
54/// A row of a result set returned by Cloud Spanner.
55///
56/// Every row of a result set shares the same type.
57pub struct Row<'a> {
58    row_type: &'a StructType,
59    columns: &'a [Value],
60}
61
62impl<'a> Row<'a> {
63    /// Returns the structure of this row (field names and type).
64    pub fn row_type(&'a self) -> &'a StructType {
65        self.row_type
66    }
67
68    /// Returns true when this row has no fields.
69    pub fn is_empty(&'a self) -> bool {
70        self.row_type.fields().is_empty()
71    }
72
73    /// Returns the converted value of the specified column.
74    ///
75    /// An error is returned if the requested column does not exist or if the decoding of the value returns an error.
76    pub fn get<T, R>(&'a self, row_index: R) -> Result<T, Error>
77    where
78        T: FromSpanner<'a>,
79        R: RowIndex + std::fmt::Display,
80    {
81        self.get_impl(&row_index)
82    }
83
84    /// Returns the converted value of the specified column.
85    ///
86    /// # Panics
87    ///
88    /// Panics if the specified index does not exist or if the value cannot be converted to requested type.
89    pub fn get_unchecked<T, R>(&'a self, row_index: R) -> T
90    where
91        T: FromSpanner<'a>,
92        R: RowIndex + std::fmt::Display,
93    {
94        match self.get_impl(&row_index) {
95            Ok(value) => value,
96            Err(error) => panic!(
97                "unexpected error while reading column {}: {}",
98                row_index, error
99            ),
100        }
101    }
102
103    fn get_impl<T, R>(&'a self, row_index: &R) -> Result<T, Error>
104    where
105        T: FromSpanner<'a>,
106        R: RowIndex + std::fmt::Display,
107    {
108        match row_index.index(self.row_type) {
109            None => Err(Error::Codec(format!("no such column {}", row_index))),
110            Some(index) => <T as FromSpanner>::from_spanner_nullable(&self.columns[index]),
111        }
112    }
113}
114
115/// Prints the row's type, but omits the values.
116impl<'a> std::fmt::Debug for Row<'a> {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("Row")
119            .field("columns", &self.row_type)
120            .finish()
121    }
122}
123
124#[derive(Debug)]
125pub(crate) struct Stats {
126    pub(crate) row_count: Option<i64>,
127}
128
129impl TryFrom<proto::ResultSetStats> for Stats {
130    type Error = Error;
131
132    fn try_from(value: proto::ResultSetStats) -> Result<Self, Self::Error> {
133        let row_count = match value.row_count {
134            Some(proto::result_set_stats::RowCount::RowCountExact(exact)) => Ok(Some(exact)),
135            Some(proto::result_set_stats::RowCount::RowCountLowerBound(_)) => Err(Error::Client(
136                "lower bound row count is unsupported".to_string(),
137            )),
138            None => Ok(None),
139        }?;
140        Ok(Self { row_count })
141    }
142}
143
144/// A result set is returned by Cloud Spanner when executing SQL queries.
145///
146/// Contains the structure of each row as well as each row's values.
147/// A result set is not lazy and will eagerly decode all rows in the result set.
148#[derive(Debug)]
149pub struct ResultSet {
150    row_type: StructType,
151    rows: Vec<Vec<Value>>,
152    pub(crate) transaction: Option<Transaction>,
153    pub(crate) stats: Stats,
154}
155
156impl ResultSet {
157    /// Returns an iterator over the rows of this result set.
158    pub fn iter(&self) -> impl Iterator<Item = Row<'_>> {
159        self.rows.iter().map(move |columns| Row {
160            row_type: &self.row_type,
161            columns,
162        })
163    }
164}
165
166impl TryFrom<proto::ResultSet> for ResultSet {
167    type Error = crate::Error;
168
169    fn try_from(value: proto::ResultSet) -> Result<Self, Self::Error> {
170        let stats = value.stats.unwrap_or_default().try_into()?;
171        let metadata = value.metadata.unwrap_or_default();
172        let row_type: StructType = metadata.row_type.unwrap_or_default().try_into()?;
173
174        let rows = value
175            .rows
176            .iter()
177            .map(|row| {
178                row.values
179                    .iter()
180                    .zip(row_type.types())
181                    .map(|(value, tpe)| Value::try_from(tpe, value.clone()))
182                    .collect()
183            })
184            .collect::<Result<Vec<Vec<Value>>, Error>>()?;
185
186        Ok(Self {
187            row_type,
188            rows,
189            transaction: metadata.transaction.map(Transaction::from),
190            stats,
191        })
192    }
193}