spanner_rs/
types.rs

1use google_api_proto::google::spanner::v1::{self as proto, TypeAnnotationCode};
2
3use std::convert::TryFrom;
4
5/// The Cloud Spanner [`Struct`](https://cloud.google.com/spanner/docs/data-types#struct_type) type which is composed of optionally named fields and their data type.
6#[derive(Clone, Debug, Default, PartialEq)]
7pub struct StructType(Vec<(Option<String>, Type)>);
8
9impl StructType {
10    /// Creates a new `StructType` with the provided fields.
11    ///
12    /// Note that Cloud Spanner allows "unnamed" fields. If a provided field name is the empty string,
13    /// it will be converted to a `None` in the resulting `StructType`.
14    pub fn new(fields: Vec<(&str, Type)>) -> Self {
15        Self(
16            fields
17                .into_iter()
18                .map(|(name, tpe)| {
19                    let field_name = if !name.is_empty() {
20                        Some(name.to_string())
21                    } else {
22                        None
23                    };
24                    (field_name, tpe)
25                })
26                .collect(),
27        )
28    }
29
30    /// Returns a reference to this struct's fields.
31    pub fn fields(&self) -> &Vec<(Option<String>, Type)> {
32        &self.0
33    }
34
35    /// Returns an iterator over the names of this struct's fields.
36    pub fn field_names(&self) -> impl Iterator<Item = &Option<String>> {
37        self.0.iter().map(|(name, _)| name)
38    }
39
40    /// Returns an iterator over the types of this struct's fields.
41    pub fn types(&self) -> impl Iterator<Item = &Type> {
42        self.0.iter().map(|(_, tpe)| tpe)
43    }
44
45    /// Returns the index of the provided field name.
46    /// Returns `None` if no field matches the provided name.
47    /// Note that this function ignores unnamed fields.
48    pub fn field_index(&self, field_name: &str) -> Option<usize> {
49        self.0.iter().position(|(name, _)| match name {
50            Some(col) => *col == field_name,
51            None => false,
52        })
53    }
54}
55
56impl TryFrom<proto::StructType> for StructType {
57    type Error = crate::Error;
58
59    fn try_from(value: proto::StructType) -> Result<Self, Self::Error> {
60        StructType::try_from(&value)
61    }
62}
63
64impl TryFrom<&proto::StructType> for StructType {
65    type Error = crate::Error;
66
67    fn try_from(value: &proto::StructType) -> Result<Self, Self::Error> {
68        value
69            .fields
70            .iter()
71            .map(|field| {
72                field
73                    .r#type
74                    .as_ref()
75                    .ok_or_else(|| {
76                        Self::Error::Codec(format!("field '{}' is missing type", field.name))
77                    })
78                    .and_then(Type::try_from)
79                    .map(|tpe| (Some(field.name.clone()), tpe))
80            })
81            .collect::<Result<Vec<(Option<String>, Type)>, Self::Error>>()
82            .map(StructType)
83    }
84}
85
86/// An enumeration of all Cloud Spanner [data types](https://cloud.google.com/spanner/docs/data-types).
87///
88/// Refer to the Cloud Spanner documentation for detailed information about individual data types.
89#[derive(Clone, Debug, PartialEq)]
90pub enum Type {
91    /// The [`BOOL`](https://cloud.google.com/spanner/docs/data-types#boolean_type) data type.
92    ///
93    /// * Storage size: 1 byte
94    Bool,
95
96    /// The [`INT64`](https://cloud.google.com/spanner/docs/data-types#integer_type) data type.
97    ///
98    /// * Storage size: 8 bytes
99    /// * Range: `-9,223,372,036,854,775,808` to `9,223,372,036,854,775,807`
100    Int64,
101
102    /// The [`FLOAT64`](https://cloud.google.com/spanner/docs/data-types#floating_point_types) data type.
103    ///
104    /// Supports the special `NaN`, `+inf` and `-inf` values.
105    ///
106    /// * Storage size: 8 bytes
107    Float64,
108
109    /// The [`STRING`](https://cloud.google.com/spanner/docs/data-types#string_type) data type.
110    ///
111    /// Must be valid UTF-8.
112    ///
113    /// * Storage: the number of bytes in its UTF-8 encoding
114    String,
115
116    /// The [`BYTES`](https://cloud.google.com/spanner/docs/data-types#bytes_type) data type.
117    ///
118    /// * Storage: the number of bytes
119    Bytes,
120
121    /// The [`JSON`](https://cloud.google.com/spanner/docs/data-types#json_type) data type.
122    ///
123    /// Note that the JSON document will be canonicalized before storing. Refer to the Cloud Spanner for details.
124    ///
125    /// * Storage: The number of bytes in UTF-8 encoding of the JSON-formatted string equivalent after canonicalization.
126    #[cfg(feature = "json")]
127    Json,
128
129    /// The [`NUMERIC`](https://cloud.google.com/spanner/docs/data-types#numeric_type) data type.
130    ///
131    /// * Storage: varies between 6 and 22 bytes, except for the value 0 which uses 1 byte.
132    #[cfg(feature = "numeric")]
133    Numeric,
134
135    /// The [`TIMESTAMP`](https://cloud.google.com/spanner/docs/data-types#timestamp_type) data type.
136    ///
137    /// Refer to the Cloud Spanner documentation for details on timezones and format when used in SQL statements.
138    ///
139    /// * Storage: 12 bytes
140    /// * Range: `0001-01-01 00:00:00` to `9999-12-31 23:59:59.999999999` UTC.
141    #[cfg(feature = "temporal")]
142    Timestamp,
143
144    /// The [`DATE`](https://cloud.google.com/spanner/docs/data-types#date_type) data type.
145    ///
146    /// * Storage: 4 bytes
147    /// * Range: `0001-01-01` to `9999-12-31`.
148    /// * Canonical format: `YYYY-[M]M-[D]D`
149    #[cfg(feature = "temporal")]
150    Date,
151
152    /// The [`ARRAY`](https://cloud.google.com/spanner/docs/data-types#array_type) data type.
153    /// Can contain elements of any other type except `Array` (i.e.: arrays of arrays are not allowed).
154    /// Can contain `NULL` elements.
155    /// A `NULL` value of type array and an empty array are different values.
156    ///
157    /// * Storage: the sum of the size of its elements
158    Array(
159        /// The array's element type.
160        Box<Type>,
161    ),
162
163    /// The [`STRUCT`](https://cloud.google.com/spanner/docs/data-types#struct_type) data type.
164    Struct(StructType),
165}
166
167impl Type {
168    /// Creates a new `Type::Array` with elements of the specified type.
169    ///
170    /// # Panics
171    ///
172    /// If the provided type is itself an `Type::Array`.
173    pub fn array(inner: Type) -> Self {
174        if let Type::Array(_) = &inner {
175            panic!("array of array is not supported by Cloud Spanner");
176        }
177        Type::Array(Box::new(inner))
178    }
179
180    /// Creates a new `Type::Struct` with the provided field names and types.
181    pub fn strct(fields: Vec<(&str, Type)>) -> Self {
182        Type::Struct(StructType::new(fields))
183    }
184
185    pub(crate) fn code(&self) -> proto::TypeCode {
186        match self {
187            Type::Bool => proto::TypeCode::Bool,
188            Type::Int64 => proto::TypeCode::Int64,
189            Type::Float64 => proto::TypeCode::Float64,
190            Type::String => proto::TypeCode::String,
191            Type::Bytes => proto::TypeCode::Bytes,
192            #[cfg(feature = "json")]
193            Type::Json => proto::TypeCode::Json,
194            #[cfg(feature = "numeric")]
195            Type::Numeric => proto::TypeCode::Numeric,
196            #[cfg(feature = "temporal")]
197            Type::Timestamp => proto::TypeCode::Timestamp,
198            #[cfg(feature = "temporal")]
199            Type::Date => proto::TypeCode::Date,
200            Type::Array(_) => proto::TypeCode::Array,
201            Type::Struct(_) => proto::TypeCode::Struct,
202        }
203    }
204}
205
206impl TryFrom<proto::Type> for Type {
207    type Error = crate::Error;
208
209    fn try_from(value: proto::Type) -> Result<Self, Self::Error> {
210        Type::try_from(&value)
211    }
212}
213
214impl TryFrom<&proto::Type> for Type {
215    type Error = crate::Error;
216
217    fn try_from(value: &proto::Type) -> Result<Self, Self::Error> {
218        match proto::TypeCode::from_i32(value.code) {
219            Some(proto::TypeCode::Bool) => Ok(Type::Bool),
220            Some(proto::TypeCode::Int64) => Ok(Type::Int64),
221            Some(proto::TypeCode::Float64) => Ok(Type::Float64),
222            Some(proto::TypeCode::String) => Ok(Type::String),
223            Some(proto::TypeCode::Bytes) => Ok(Type::Bytes),
224            #[cfg(feature = "json")]
225            Some(proto::TypeCode::Json) => Ok(Type::Json),
226            #[cfg(not(feature = "json"))]
227            Some(proto::TypeCode::Json) => {
228                panic!("JSON type support is not enabled; use the 'json' feature to enable it")
229            }
230            #[cfg(feature = "numeric")]
231            Some(proto::TypeCode::Numeric) => Ok(Type::Numeric),
232            #[cfg(not(feature = "numeric"))]
233            Some(proto::TypeCode::Numeric) => {
234                panic!(
235                    "NUMERIC type support is not enabled; use the 'numeric' feature to enable it"
236                )
237            }
238            #[cfg(feature = "temporal")]
239            Some(proto::TypeCode::Timestamp) => Ok(Type::Timestamp),
240            #[cfg(not(feature = "temporal"))]
241            Some(proto::TypeCode::Timestamp) => panic!(
242                "TIMESTAMP type support is not enabled; use the 'temporal' feature to enable it"
243            ),
244            #[cfg(feature = "temporal")]
245            Some(proto::TypeCode::Date) => Ok(Type::Date),
246            #[cfg(not(feature = "temporal"))]
247            Some(proto::TypeCode::Date) => {
248                panic!("DATE type support is not enabled; use the 'temporal' feature to enable it")
249            }
250            Some(proto::TypeCode::Array) => value
251                .array_element_type
252                .as_ref()
253                .ok_or_else(|| Self::Error::Codec("missing array element type".to_string()))
254                .and_then(|tpe| Type::try_from(tpe.as_ref()))
255                .map(|tpe| Type::Array(Box::new(tpe))),
256
257            Some(proto::TypeCode::Struct) => value
258                .struct_type
259                .as_ref()
260                .ok_or_else(|| Self::Error::Codec("missing struct type definition".to_string()))
261                .and_then(StructType::try_from)
262                .map(Type::Struct),
263            Some(proto::TypeCode::Unspecified) => {
264                Err(Self::Error::Codec("unspecified type".to_string()))
265            }
266            None => Err(Self::Error::Codec(format!(
267                "unknown type code {}",
268                value.code
269            ))),
270        }
271    }
272}
273
274impl From<&Type> for proto::Type {
275    fn from(value: &Type) -> Self {
276        match value {
277            Type::Array(inner) => proto::Type {
278                code: value.code() as i32,
279                array_element_type: Some(Box::new((*inner).as_ref().into())),
280                struct_type: None,
281                type_annotation: TypeAnnotationCode::Unspecified.into(),
282            },
283            Type::Struct(StructType(fields)) => proto::Type {
284                code: value.code() as i32,
285                array_element_type: None,
286                struct_type: Some(proto::StructType {
287                    fields: fields
288                        .iter()
289                        .map(|(name, tpe)| proto::struct_type::Field {
290                            name: name.clone().unwrap_or_default(),
291                            r#type: Some(tpe.into()),
292                        })
293                        .collect(),
294                }),
295                type_annotation: TypeAnnotationCode::Unspecified.into(),
296            },
297            other => proto::Type {
298                code: other.code() as i32,
299                array_element_type: None,
300                struct_type: None,
301                type_annotation: TypeAnnotationCode::Unspecified.into(),
302            },
303        }
304    }
305}
306
307impl From<Type> for proto::Type {
308    fn from(value: Type) -> Self {
309        From::from(&value)
310    }
311}
312
313#[cfg(test)]
314mod test {
315
316    use google_api_proto::google::spanner::v1 as proto;
317
318    use super::*;
319
320    fn scalar_type(code: proto::TypeCode) -> proto::Type {
321        proto::Type {
322            code: code as i32,
323            array_element_type: None,
324            struct_type: None,
325            type_annotation: TypeAnnotationCode::Unspecified.into(),
326        }
327    }
328
329    fn array_type(underlying: proto::Type) -> proto::Type {
330        proto::Type {
331            code: proto::TypeCode::Array as i32,
332            array_element_type: Some(Box::new(underlying)),
333            struct_type: None,
334            type_annotation: TypeAnnotationCode::Unspecified.into(),
335        }
336    }
337
338    fn struct_type(fields: Vec<(&str, proto::Type)>) -> proto::Type {
339        proto::Type {
340            code: proto::TypeCode::Struct as i32,
341            array_element_type: None,
342            struct_type: Some(proto::StructType {
343                fields: fields
344                    .iter()
345                    .map(|(name, tpe)| proto::struct_type::Field {
346                        name: name.to_string(),
347                        r#type: Some(tpe.clone()),
348                    })
349                    .collect(),
350            }),
351            type_annotation: TypeAnnotationCode::Unspecified.into(),
352        }
353    }
354
355    fn test_scalar(code: proto::TypeCode, expected: Type) {
356        assert_eq!(Type::try_from(scalar_type(code)).unwrap(), expected);
357        assert_eq!(proto::Type::from(expected).code, code as i32)
358    }
359
360    #[test]
361    fn test_try_from_scalar() {
362        test_scalar(proto::TypeCode::Bool, Type::Bool);
363        test_scalar(proto::TypeCode::Int64, Type::Int64);
364        test_scalar(proto::TypeCode::Float64, Type::Float64);
365        test_scalar(proto::TypeCode::String, Type::String);
366        test_scalar(proto::TypeCode::Bytes, Type::Bytes);
367        #[cfg(feature = "json")]
368        test_scalar(proto::TypeCode::Json, Type::Json);
369        #[cfg(feature = "numeric")]
370        test_scalar(proto::TypeCode::Numeric, Type::Numeric);
371        #[cfg(feature = "temporal")]
372        test_scalar(proto::TypeCode::Timestamp, Type::Timestamp);
373        #[cfg(feature = "temporal")]
374        test_scalar(proto::TypeCode::Date, Type::Date);
375    }
376
377    fn test_array_of_scalar(code: proto::TypeCode, inner: Type) {
378        let expected = Type::Array(Box::new(inner.clone()));
379        assert_eq!(
380            Type::try_from(array_type(scalar_type(code))).unwrap(),
381            expected.clone(),
382        );
383        assert_eq!(
384            proto::Type::from(expected.clone()),
385            proto::Type {
386                code: proto::TypeCode::Array as i32,
387                array_element_type: Some(Box::new(inner.into())),
388                struct_type: None,
389                type_annotation: TypeAnnotationCode::Unspecified.into(),
390            }
391        )
392    }
393
394    #[test]
395    fn test_try_from_array() {
396        test_array_of_scalar(proto::TypeCode::Bool, Type::Bool);
397        test_array_of_scalar(proto::TypeCode::Int64, Type::Int64);
398        test_array_of_scalar(proto::TypeCode::Float64, Type::Float64);
399        test_array_of_scalar(proto::TypeCode::String, Type::String);
400        test_array_of_scalar(proto::TypeCode::Bytes, Type::Bytes);
401        #[cfg(feature = "json")]
402        test_array_of_scalar(proto::TypeCode::Json, Type::Json);
403        #[cfg(feature = "numeric")]
404        test_array_of_scalar(proto::TypeCode::Numeric, Type::Numeric);
405        #[cfg(feature = "temporal")]
406        test_array_of_scalar(proto::TypeCode::Timestamp, Type::Timestamp);
407        #[cfg(feature = "temporal")]
408        test_array_of_scalar(proto::TypeCode::Date, Type::Date);
409
410        let invalid = proto::Type {
411            code: proto::TypeCode::Array as i32,
412            array_element_type: None,
413            struct_type: None,
414            type_annotation: TypeAnnotationCode::Unspecified.into(),
415        };
416
417        assert!(Type::try_from(invalid).is_err());
418    }
419
420    #[test]
421    #[should_panic]
422    fn _test_array_of_array_is_illegal() {
423        Type::array(Type::array(Type::Bool));
424    }
425
426    #[test]
427    fn test_try_from_struct() {
428        assert_eq!(
429            Type::try_from(struct_type(vec![])).unwrap(),
430            Type::strct(vec![])
431        );
432        assert_eq!(
433            Type::try_from(struct_type(vec![(
434                "bool",
435                scalar_type(proto::TypeCode::Bool)
436            )]))
437            .unwrap(),
438            Type::strct(vec![("bool", Type::Bool)]),
439        );
440        assert_eq!(
441            Type::try_from(struct_type(vec![(
442                "array_of_bools",
443                array_type(scalar_type(proto::TypeCode::Bool))
444            )]))
445            .unwrap(),
446            Type::strct(vec![("array_of_bools", Type::array(Type::Bool))]),
447        );
448        assert_eq!(
449            Type::try_from(struct_type(vec![
450                ("bool", scalar_type(proto::TypeCode::Bool)),
451                (
452                    "struct",
453                    struct_type(vec![("int64", scalar_type(proto::TypeCode::Int64))])
454                ),
455            ]))
456            .unwrap(),
457            Type::strct(vec![
458                ("bool", Type::Bool),
459                ("struct", Type::strct(vec![("int64", Type::Int64)]))
460            ]),
461        );
462
463        assert_eq!(
464            proto::Type::from(struct_type(vec![(
465                "bool",
466                scalar_type(proto::TypeCode::Bool)
467            )])),
468            proto::Type {
469                code: proto::TypeCode::Struct as i32,
470                array_element_type: None,
471                struct_type: Some(proto::StructType {
472                    fields: vec![proto::struct_type::Field {
473                        name: "bool".to_string(),
474                        r#type: Some(proto::Type {
475                            code: proto::TypeCode::Bool as i32,
476                            array_element_type: None,
477                            struct_type: None,
478                            type_annotation: TypeAnnotationCode::Unspecified.into(),
479                        })
480                    }]
481                }),
482                type_annotation: TypeAnnotationCode::Unspecified.into(),
483            }
484        );
485
486        let invalid = proto::Type {
487            code: proto::TypeCode::Struct as i32,
488            array_element_type: None,
489            struct_type: None,
490            type_annotation: TypeAnnotationCode::Unspecified.into(),
491        };
492
493        assert!(Type::try_from(invalid).is_err());
494    }
495
496    #[test]
497    fn test_column_index() {
498        let strct = StructType(vec![
499            (Some("foo".into()), Type::Bool),
500            (None, Type::Bool),
501            (Some("bar".into()), Type::Bool),
502        ]);
503        assert_eq!(strct.field_index("foo"), Some(0));
504        assert_eq!(strct.field_index("bar"), Some(2));
505        assert_eq!(strct.field_index("not present"), None);
506    }
507}