Skip to main content

google_cloud_spanner/
types.rs

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// TODO(#4969)
16#![allow(dead_code)]
17
18use crate::generated::gapic_dataplane::model;
19use crate::generated::gapic_dataplane::model::TypeAnnotationCode;
20use crate::google::spanner::v1 as spanner_v1;
21use gaxi::prost::ConvertError;
22use std::sync::LazyLock;
23
24/// Spanner type definition.
25#[derive(Clone, Debug, PartialEq, Default)]
26#[repr(transparent)]
27pub struct Type(pub(crate) model::Type);
28
29macro_rules! define_type_code {
30    ($($variant:ident = $val:expr),* $(,)?) => {
31        /// Spanner type code.
32        ///
33        /// Maps directly to the gRPC type codes defined in
34        /// [`crate::generated::gapic_dataplane::model::TypeCode`].
35        #[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Default)]
36        #[repr(i32)]
37        #[non_exhaustive]
38        pub enum TypeCode {
39            /// Not specified.
40            #[default]
41            Unspecified = 0,
42            $(
43                #[doc = concat!("Spanner `", stringify!($variant), "` data type.")]
44                $variant = $val
45            ),*,
46            /// An unknown or unsupported type code value.
47            Unknown(i32),
48        }
49
50        impl From<i32> for TypeCode {
51            fn from(value: i32) -> Self {
52                match value {
53                    0 => TypeCode::Unspecified,
54                    $($val => TypeCode::$variant),*,
55                    v => TypeCode::Unknown(v),
56                }
57            }
58        }
59
60        impl From<TypeCode> for i32 {
61            fn from(value: TypeCode) -> Self {
62                match value {
63                    TypeCode::Unspecified => 0,
64                    $(TypeCode::$variant => $val),*,
65                    TypeCode::Unknown(v) => v,
66                }
67            }
68        }
69
70        impl From<crate::generated::gapic_dataplane::model::TypeCode> for TypeCode {
71            fn from(value: crate::generated::gapic_dataplane::model::TypeCode) -> Self {
72                 match value.value() {
73                    Some(v) => v.into(),
74                    None => TypeCode::Unspecified,
75                }
76            }
77        }
78
79        impl From<TypeCode> for crate::generated::gapic_dataplane::model::TypeCode {
80            fn from(value: TypeCode) -> Self {
81                let v: i32 = value.into();
82                v.into()
83            }
84        }
85    };
86}
87
88// The values here must match the values in the `google.spanner.v1.TypeCode` enum.
89// We cannot use the generated constants directly because they are not exposed as public constants.
90// See https://github.com/googleapis/googleapis/blob/master/google/spanner/v1/type.proto
91define_type_code!(
92    Bool = 1,
93    Int64 = 2,
94    Float64 = 3,
95    Float32 = 15,
96    Timestamp = 4,
97    Date = 5,
98    String = 6,
99    Bytes = 7,
100    Array = 8,
101    Struct = 9,
102    Numeric = 10,
103    Json = 11,
104    Proto = 13,
105    Enum = 14,
106    Interval = 16,
107    Uuid = 17,
108);
109
110impl From<model::Type> for Type {
111    fn from(value: model::Type) -> Self {
112        Type(value)
113    }
114}
115
116impl From<Type> for model::Type {
117    fn from(value: Type) -> Self {
118        value.0
119    }
120}
121
122impl From<spanner_v1::Type> for Type {
123    fn from(value: spanner_v1::Type) -> Self {
124        use gaxi::prost::FromProto;
125        value.cnv().unwrap_or_default().into()
126    }
127}
128
129impl Type {
130    /// Returns the type code.
131    pub fn code(&self) -> TypeCode {
132        self.0.code.clone().into()
133    }
134
135    /// Returns the element type of the array, or `None` if this type is not an array.
136    pub fn array_element_type(&self) -> Option<Type> {
137        self.0
138            .array_element_type
139            .as_deref()
140            .map(|t| Type(t.clone()))
141    }
142
143    /// Returns the struct type metadata, or `None` if this type is not a struct.
144    ///
145    /// When `code() == TypeCode::Struct`, this provides the field names and
146    /// their types. This is essential for reconstructing named fields from the
147    /// positional `ListValue` wire format that Spanner uses for STRUCT values.
148    pub fn struct_type(&self) -> Option<&model::StructType> {
149        self.0.struct_type.as_deref()
150    }
151
152    /// Safely reinterprets a reference to the inner model type as a reference to Type.
153    /// Logical safety is guaranteed by #[repr(transparent)].
154    pub(crate) fn from_ref(v: &model::Type) -> &Self {
155        // SAFETY: Type is #[repr(transparent)] wrapper around model::Type.
156        unsafe { &*(v as *const model::Type as *const Type) }
157    }
158}
159
160impl gaxi::prost::ToProto<i32> for TypeCode {
161    type Output = i32;
162
163    fn to_proto(self) -> Result<i32, ConvertError> {
164        let internal: crate::generated::gapic_dataplane::model::TypeCode = self.into();
165
166        internal.to_proto()
167    }
168}
169
170impl gaxi::prost::ToProto<crate::generated::gapic_dataplane::model::Type> for Type {
171    type Output = crate::generated::gapic_dataplane::model::Type;
172
173    fn to_proto(self) -> Result<crate::generated::gapic_dataplane::model::Type, ConvertError> {
174        Ok(self.0)
175    }
176}
177
178static TYPE_INT64: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Int64));
179static TYPE_STRING: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::String));
180static TYPE_BOOL: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Bool));
181static TYPE_FLOAT32: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Float32));
182static TYPE_FLOAT64: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Float64));
183static TYPE_JSON: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Json));
184static TYPE_BYTES: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Bytes));
185static TYPE_TIMESTAMP: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Timestamp));
186static TYPE_DATE: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Date));
187static TYPE_NUMERIC: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Numeric));
188static TYPE_UUID: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Uuid));
189static TYPE_INTERVAL: LazyLock<Type> = LazyLock::new(|| create_type(TypeCode::Interval));
190static TYPE_PG_NUMERIC: LazyLock<Type> = LazyLock::new(|| {
191    let mut t = create_type(TypeCode::Numeric);
192    t.0.type_annotation = TypeAnnotationCode::PgNumeric;
193    t
194});
195static TYPE_PG_JSONB: LazyLock<Type> = LazyLock::new(|| {
196    let mut t = create_type(TypeCode::Json);
197    t.0.type_annotation = TypeAnnotationCode::PgJsonb;
198    t
199});
200static TYPE_PG_OID: LazyLock<Type> = LazyLock::new(|| {
201    let mut t = create_type(TypeCode::Int64);
202    t.0.type_annotation = TypeAnnotationCode::PgOid;
203    t
204});
205
206/// Returns a `Type` representing `INT64` (GoogleSQL) or `bigint` (PostgreSQL).
207pub fn int64() -> Type {
208    TYPE_INT64.clone()
209}
210
211/// Returns a `Type` representing `STRING` (GoogleSQL) or `character varying` (PostgreSQL).
212pub fn string() -> Type {
213    TYPE_STRING.clone()
214}
215
216/// Returns a `Type` representing `BOOL` (GoogleSQL) or `boolean` (PostgreSQL).
217pub fn bool() -> Type {
218    TYPE_BOOL.clone()
219}
220
221/// Returns a `Type` representing `FLOAT32` (GoogleSQL) or `real` (PostgreSQL).
222pub fn float32() -> Type {
223    TYPE_FLOAT32.clone()
224}
225
226/// Returns a `Type` representing `FLOAT64` (GoogleSQL) or `double precision` (PostgreSQL).
227pub fn float64() -> Type {
228    TYPE_FLOAT64.clone()
229}
230
231/// Returns a `Type` representing `JSON` (GoogleSQL).
232pub fn json() -> Type {
233    TYPE_JSON.clone()
234}
235
236/// Returns a `Type` representing `BYTES` (GoogleSQL) or `bytea` (PostgreSQL).
237pub fn bytes() -> Type {
238    TYPE_BYTES.clone()
239}
240
241/// Returns a `Type` representing `TIMESTAMP` (GoogleSQL) or `timestamp with time zone` (PostgreSQL).
242pub fn timestamp() -> Type {
243    TYPE_TIMESTAMP.clone()
244}
245
246/// Returns a `Type` representing `DATE` (GoogleSQL) or `date` (PostgreSQL).
247pub fn date() -> Type {
248    TYPE_DATE.clone()
249}
250
251/// Returns a `Type` representing `NUMERIC` (GoogleSQL) or `numeric` (PostgreSQL).
252pub fn numeric() -> Type {
253    TYPE_NUMERIC.clone()
254}
255
256/// Returns a `Type` representing `UUID` (GoogleSQL) or `uuid` (PostgreSQL).
257pub fn uuid() -> Type {
258    TYPE_UUID.clone()
259}
260
261/// Returns a `Type` representing `INTERVAL` (GoogleSQL) or `interval` (PostgreSQL).
262pub fn interval() -> Type {
263    TYPE_INTERVAL.clone()
264}
265
266/// Returns a `Type` representing `numeric` (PostgreSQL).
267pub fn pg_numeric() -> Type {
268    TYPE_PG_NUMERIC.clone()
269}
270
271/// Returns a `Type` representing `jsonb` (PostgreSQL).
272pub fn pg_jsonb() -> Type {
273    TYPE_PG_JSONB.clone()
274}
275
276/// Returns a `Type` representing `oid` (PostgreSQL).
277pub fn pg_oid() -> Type {
278    TYPE_PG_OID.clone()
279}
280
281/// Returns a `Type` representing `ARRAY<t>` (GoogleSQL) or `t[]` (PostgreSQL).
282pub fn array(element_type: Type) -> Type {
283    let mut t = create_type(TypeCode::Array);
284    t.0.array_element_type = Some(Box::new(element_type.0));
285    t
286}
287
288pub(crate) fn create_type(code: TypeCode) -> Type {
289    Type(crate::generated::gapic_dataplane::model::Type {
290        code: code.into(),
291        array_element_type: None,
292        struct_type: None,
293        type_annotation: TypeAnnotationCode::Unspecified,
294        proto_type_fqn: String::new(),
295        _unknown_fields: Default::default(),
296    })
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use std::hash::Hash;
303
304    #[test]
305    fn test_type_code_round_trip() {
306        let codes = vec![
307            TypeCode::Unspecified,
308            TypeCode::Bool,
309            TypeCode::Int64,
310            TypeCode::Float64,
311            TypeCode::Float32,
312            TypeCode::Timestamp,
313            TypeCode::Date,
314            TypeCode::String,
315            TypeCode::Bytes,
316            TypeCode::Array,
317            TypeCode::Struct,
318            TypeCode::Numeric,
319            TypeCode::Json,
320            TypeCode::Proto,
321            TypeCode::Enum,
322            TypeCode::Interval,
323            TypeCode::Uuid,
324        ];
325
326        for code in codes {
327            let i: i32 = code.into();
328            let c: TypeCode = i.into();
329            assert_eq!(code, c);
330
331            let generated: crate::generated::gapic_dataplane::model::TypeCode = code.into();
332            let back: TypeCode = generated.into();
333            assert_eq!(code, back);
334        }
335    }
336
337    #[test]
338    fn test_unknown_type_code() {
339        let i = 999;
340        let code: TypeCode = i.into();
341        assert_eq!(code, TypeCode::Unknown(i));
342        assert_eq!(i32::from(code), i);
343    }
344
345    #[test]
346    fn test_unknown_type_code_from_generated() {
347        use crate::generated::gapic_dataplane::model::type_code::UnknownValue;
348        use wkt::internal::UnknownEnumValue;
349
350        let i = 999;
351        let unknown = UnknownValue(UnknownEnumValue::Integer(i));
352        let generated = crate::generated::gapic_dataplane::model::TypeCode::UnknownValue(unknown);
353        let code: TypeCode = generated.into();
354        assert_eq!(code, TypeCode::Unknown(i));
355    }
356
357    #[test]
358    fn test_simple_types() {
359        assert_eq!(int64().code(), TypeCode::Int64);
360        assert_eq!(string().code(), TypeCode::String);
361        assert_eq!(bool().code(), TypeCode::Bool);
362        assert_eq!(float64().code(), TypeCode::Float64);
363        assert_eq!(bytes().code(), TypeCode::Bytes);
364        assert_eq!(timestamp().code(), TypeCode::Timestamp);
365        assert_eq!(date().code(), TypeCode::Date);
366        assert_eq!(numeric().code(), TypeCode::Numeric);
367        assert_eq!(json().code(), TypeCode::Json);
368        assert_eq!(float32().code(), TypeCode::Float32);
369        assert_eq!(uuid().code(), TypeCode::Uuid);
370        assert_eq!(interval().code(), TypeCode::Interval);
371        // PG types are tested in test_pg_types
372    }
373
374    #[test]
375    fn test_default_type() {
376        let t = Type::default();
377        assert_eq!(t.code(), TypeCode::Unspecified);
378        assert_eq!(
379            t.0.code,
380            crate::generated::gapic_dataplane::model::TypeCode::Unspecified
381        );
382    }
383
384    #[test]
385    fn test_to_proto_traits() {
386        use gaxi::prost::ToProto;
387        let t = int64();
388        let proto: crate::generated::gapic_dataplane::model::Type = t.clone().to_proto().unwrap();
389        assert_eq!(
390            proto.code,
391            crate::generated::gapic_dataplane::model::TypeCode::Int64
392        );
393
394        let code = TypeCode::Int64;
395        let proto_code: i32 = code.to_proto().unwrap();
396        assert_eq!(proto_code, 2);
397    }
398
399    #[test]
400    fn test_from_type_traits() {
401        let internal_type = crate::generated::gapic_dataplane::model::Type {
402            code: crate::generated::gapic_dataplane::model::TypeCode::Bool,
403            ..Default::default()
404        };
405        let t: Type = internal_type.clone().into();
406        assert_eq!(t.code(), TypeCode::Bool);
407
408        let back: crate::generated::gapic_dataplane::model::Type = t.into();
409        assert_eq!(back.code, internal_type.code);
410    }
411
412    #[test]
413    fn test_array_type() {
414        let t = array(int64());
415        assert_eq!(t.code(), TypeCode::Array);
416        assert_eq!(
417            t.0.array_element_type.unwrap().code,
418            crate::generated::gapic_dataplane::model::TypeCode::Int64
419        );
420    }
421
422    #[test]
423    fn test_pg_types() {
424        assert_eq!(pg_numeric().code(), TypeCode::Numeric);
425        assert_eq!(
426            pg_numeric().0.type_annotation,
427            TypeAnnotationCode::PgNumeric
428        );
429
430        assert_eq!(pg_jsonb().code(), TypeCode::Json);
431        assert_eq!(pg_jsonb().0.type_annotation, TypeAnnotationCode::PgJsonb);
432
433        assert_eq!(pg_oid().code(), TypeCode::Int64);
434        assert_eq!(pg_oid().0.type_annotation, TypeAnnotationCode::PgOid);
435    }
436
437    #[test]
438    fn test_array_element_type() {
439        let arr = array(int64());
440        assert_eq!(arr.array_element_type(), Some(int64()));
441
442        // Non-array types should return None
443        assert_eq!(int64().array_element_type(), None);
444        assert_eq!(string().array_element_type(), None);
445        assert_eq!(Type::default().array_element_type(), None);
446    }
447
448    #[test]
449    fn test_auto_traits() {
450        static_assertions::assert_impl_all!(Type: Send, Sync, Clone, std::fmt::Debug);
451        static_assertions::assert_impl_all!(
452            TypeCode: Send,
453            Sync,
454            Clone,
455            Copy,
456            std::fmt::Debug,
457            PartialEq,
458            Eq,
459            Hash
460        );
461    }
462}