1use google_api_proto::google::spanner::v1::{self as proto, TypeAnnotationCode};
2
3use std::convert::TryFrom;
4
5#[derive(Clone, Debug, Default, PartialEq)]
7pub struct StructType(Vec<(Option<String>, Type)>);
8
9impl StructType {
10 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 pub fn fields(&self) -> &Vec<(Option<String>, Type)> {
32 &self.0
33 }
34
35 pub fn field_names(&self) -> impl Iterator<Item = &Option<String>> {
37 self.0.iter().map(|(name, _)| name)
38 }
39
40 pub fn types(&self) -> impl Iterator<Item = &Type> {
42 self.0.iter().map(|(_, tpe)| tpe)
43 }
44
45 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#[derive(Clone, Debug, PartialEq)]
90pub enum Type {
91 Bool,
95
96 Int64,
101
102 Float64,
108
109 String,
115
116 Bytes,
120
121 #[cfg(feature = "json")]
127 Json,
128
129 #[cfg(feature = "numeric")]
133 Numeric,
134
135 #[cfg(feature = "temporal")]
142 Timestamp,
143
144 #[cfg(feature = "temporal")]
150 Date,
151
152 Array(
159 Box<Type>,
161 ),
162
163 Struct(StructType),
165}
166
167impl Type {
168 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 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}