Skip to main content

google_cloud_bigquery/query/
from_sql.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
15use base64::Engine;
16use base64::prelude::BASE64_STANDARD;
17#[allow(unused_imports)]
18use wkt::{Struct, Timestamp, Value};
19
20use crate::error::ConvertError;
21
22pub(crate) const BIGQUERY_DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
23    time::macros::format_description!("[year]-[month]-[day]");
24pub(crate) const BIGQUERY_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
25    "[hour padding:none]:[minute padding:none]:[second padding:none]"
26);
27pub(crate) const BIGQUERY_TIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
28    "[hour padding:none]:[minute padding:none]:[second padding:none].[subsecond]"
29);
30pub(crate) const BIGQUERY_DATETIME_FORMAT: &[time::format_description::FormatItem<'static>] =
31    time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
32pub(crate) const BIGQUERY_DATETIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<
33    'static,
34>] = time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]");
35
36#[derive(Clone, Debug, PartialEq)]
37pub(crate) enum SqlValueInner {
38    Null,
39    Bool(bool),
40    Number(serde_json::Number),
41    String(String),
42    Array(Vec<SqlValueInner>),
43    Struct(Vec<(String, SqlValueInner)>),
44}
45
46impl SqlValueInner {
47    pub(crate) fn type_name(&self) -> &'static str {
48        match self {
49            Self::Null => "null",
50            Self::Bool(_) => "bool",
51            Self::Number(_) => "number",
52            Self::String(_) => "string",
53            Self::Array(_) => "array",
54            Self::Struct(_) => "object",
55        }
56    }
57
58    pub(crate) fn from_wkt(value: wkt::Value) -> Self {
59        match value {
60            wkt::Value::Null => Self::Null,
61            wkt::Value::Bool(b) => Self::Bool(b),
62            wkt::Value::Number(n) => Self::Number(n),
63            wkt::Value::String(s) => Self::String(s),
64            wkt::Value::Array(arr) => Self::Array(arr.into_iter().map(Self::from_wkt).collect()),
65            wkt::Value::Object(obj) => Self::Struct(
66                obj.into_iter()
67                    .map(|(k, v)| (k, Self::from_wkt(v)))
68                    .collect(),
69            ),
70        }
71    }
72}
73
74/// An opaque representation of a BigQuery SQL cell value.
75///
76/// Passed to [`FromSql::from_value`] when converting query result cells
77/// into Rust types.
78#[derive(Clone, Debug)]
79pub struct SqlValue {
80    pub(crate) inner: SqlValueInner,
81    // Opts out of `UnwindSafe` and `RefUnwindSafe` while preserving `Send + Sync + Unpin`.
82    _not_unwind_safe: std::marker::PhantomData<dyn Send + Sync + Unpin>,
83}
84
85impl SqlValue {
86    pub(crate) fn from_inner(inner: SqlValueInner) -> Self {
87        Self {
88            inner,
89            _not_unwind_safe: std::marker::PhantomData,
90        }
91    }
92
93    /// Takes ownership of a field by name (`&str`) or zero-based index (`usize`)
94    /// from a `STRUCT`, `ARRAY`, or `JSON` value and converts it to `T`.
95    ///
96    /// Used by the [`#[derive(FromSql)]`](derive@crate::query::FromSql) macro to
97    /// extract struct fields.
98    pub fn take<T: FromSql, I: crate::query::ColumnIndex>(
99        &mut self,
100        index: I,
101    ) -> Result<T, ConvertError> {
102        let val = index.take_sql_value(self)?;
103        T::from_value(val)
104    }
105}
106
107/// A trait for converting BigQuery value representations into Rust
108/// types.
109///
110/// The BigQuery client uses [`wkt::Value`] by default, but will support
111/// Arrow record batches via the Storage Read API integration in the future.
112///
113/// <div class="warning">
114///
115/// **Do not implement this trait directly.**
116///
117/// This trait is not intended for manual implementation. New methods (such as
118/// Arrow conversion methods) may be added in future versions, which would break
119/// manual implementations. To deserialize custom structs, use [`#[derive(FromSql)]`](derive@crate::query::FromSql)
120/// or [`#[derive(FromRow)]`](crate::query::FromRow) instead.
121///
122/// </div>
123///
124/// [`Row::get()`](crate::query::Row::get) or [`Row::take()`](crate::query::Row::take)
125/// use this trait to convert cell values, and the [`FromRow`](crate::query::FromRow)
126/// derive macro uses it for field deserialization.
127///
128/// # Supported Types
129///
130/// Built-in implementations include:
131/// - Numbers: `i32`, `i64`, `f32`, `f64`, [`Decimal`](rust_decimal::Decimal)
132/// - Text & Bytes: `String`, `Vec<u8>` (decoded from base64), [`Bytes`](bytes::Bytes)
133/// - Dates & Times: [`Timestamp`](wkt::Timestamp), [`Date`](google_cloud_type::model::Date), [`TimeOfDay`](google_cloud_type::model::TimeOfDay), [`DateTime`](google_cloud_type::model::DateTime)
134/// - Intervals: [`Interval`](crate::datatypes::Interval)
135/// - Collections: `Option<T>` (for `NULL`), `Vec<T>` (for repeated fields), [`Range<T>`](crate::datatypes::Range) (for `RANGE` types)
136/// - Raw JSON: [`Value`](wkt::Value), [`Struct`](wkt::Struct)
137///
138/// # Example
139///
140/// ```
141/// # use google_cloud_bigquery::client::BigQuery;
142/// # async fn sample(client: BigQuery) -> anyhow::Result<()> {
143/// let mut rows = client
144///     .query("SELECT 12345 AS integer_col, 'foo' AS string_col")
145///     .until_done()
146///     .await?
147///     .read();
148///
149/// while let Some(row) = rows.next().await.transpose()? {
150///     let num: i64 = row.get("integer_col")?;
151///     let txt: String = row.get("string_col")?;
152///     println!("{txt}: {num}");
153/// }
154/// # Ok(())
155/// # }
156/// ```
157pub trait FromSql: Sized {
158    /// Converts a BigQuery [`SqlValue`] into the implementing type.
159    fn from_value(value: SqlValue) -> Result<Self, ConvertError>;
160}
161
162impl FromSql for wkt::Value {
163    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
164        Ok(match value.inner {
165            SqlValueInner::Null => wkt::Value::Null,
166            SqlValueInner::Bool(b) => wkt::Value::Bool(b),
167            SqlValueInner::Number(n) => wkt::Value::Number(n),
168            SqlValueInner::String(s) => wkt::Value::String(s),
169            SqlValueInner::Array(arr) => wkt::Value::Array(
170                arr.into_iter()
171                    .map(|v| wkt::Value::from_value(SqlValue::from_inner(v)))
172                    .collect::<Result<Vec<_>, _>>()?,
173            ),
174            SqlValueInner::Struct(entries) => wkt::Value::Object(
175                entries
176                    .into_iter()
177                    .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
178                    .collect::<Result<wkt::Struct, ConvertError>>()?,
179            ),
180        })
181    }
182}
183
184impl FromSql for String {
185    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
186        match value.inner {
187            SqlValueInner::String(s) => Ok(s),
188            SqlValueInner::Null => Err(ConvertError::NotNull),
189            other => Err(ConvertError::type_mismatch("string", &other)),
190        }
191    }
192}
193
194impl FromSql for i32 {
195    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
196        match value.inner {
197            SqlValueInner::Number(n) => n
198                .as_i64()
199                .and_then(|v| i32::try_from(v).ok())
200                .ok_or_else(|| ConvertError::Convert("number is not a valid i32".into())),
201            SqlValueInner::String(s) => s
202                .parse::<i32>()
203                .map_err(|e| ConvertError::Convert(Box::new(e))),
204            SqlValueInner::Null => Err(ConvertError::NotNull),
205            other => Err(ConvertError::type_mismatch("number or string", &other)),
206        }
207    }
208}
209
210impl FromSql for i64 {
211    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
212        match value.inner {
213            SqlValueInner::Number(n) => n
214                .as_i64()
215                .ok_or_else(|| ConvertError::Convert("number is not a valid i64".into())),
216            SqlValueInner::String(s) => s
217                .parse::<i64>()
218                .map_err(|e| ConvertError::Convert(Box::new(e))),
219            SqlValueInner::Null => Err(ConvertError::NotNull),
220            other => Err(ConvertError::type_mismatch("number or string", &other)),
221        }
222    }
223}
224
225impl FromSql for f32 {
226    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
227        match value.inner {
228            SqlValueInner::Number(n) => n
229                .as_f64()
230                .map(|v| v as f32)
231                .ok_or_else(|| ConvertError::Convert("number is not a valid f32".into())),
232            SqlValueInner::String(s) => s
233                .parse::<f32>()
234                .map_err(|e| ConvertError::Convert(Box::new(e))),
235            SqlValueInner::Null => Err(ConvertError::NotNull),
236            other => Err(ConvertError::type_mismatch("number or string", &other)),
237        }
238    }
239}
240
241impl FromSql for f64 {
242    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
243        match value.inner {
244            SqlValueInner::Number(n) => n
245                .as_f64()
246                .ok_or_else(|| ConvertError::Convert("number is not a valid f64".into())),
247            SqlValueInner::String(s) => s
248                .parse::<f64>()
249                .map_err(|e| ConvertError::Convert(Box::new(e))),
250            SqlValueInner::Null => Err(ConvertError::NotNull),
251            other => Err(ConvertError::type_mismatch("number or string", &other)),
252        }
253    }
254}
255
256impl FromSql for bool {
257    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
258        match value.inner {
259            SqlValueInner::Bool(b) => Ok(b),
260            SqlValueInner::String(s) => s
261                .parse::<bool>()
262                .map_err(|e| ConvertError::Convert(Box::new(e))),
263            SqlValueInner::Null => Err(ConvertError::NotNull),
264            other => Err(ConvertError::type_mismatch("bool or string", &other)),
265        }
266    }
267}
268
269impl<T: FromSql> FromSql for Option<T> {
270    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
271        match value.inner {
272            SqlValueInner::Null => Ok(None),
273            other => T::from_value(SqlValue::from_inner(other)).map(Some),
274        }
275    }
276}
277
278impl<T: FromSql> FromSql for Vec<T> {
279    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
280        match value.inner {
281            SqlValueInner::Array(arr) => arr
282                .into_iter()
283                .map(|v| T::from_value(SqlValue::from_inner(v)))
284                .collect(),
285            SqlValueInner::Null => Err(ConvertError::NotNull),
286            other => Err(ConvertError::type_mismatch("array", &other)),
287        }
288    }
289}
290
291impl FromSql for wkt::Struct {
292    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
293        match value.inner {
294            SqlValueInner::Struct(entries) => entries
295                .into_iter()
296                .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
297                .collect(),
298            SqlValueInner::String(s) => {
299                serde_json::from_str(&s).map_err(|e| ConvertError::Convert(Box::new(e)))
300            }
301            SqlValueInner::Null => Err(ConvertError::NotNull),
302            other => Err(ConvertError::type_mismatch("object or string", &other)),
303        }
304    }
305}
306
307impl FromSql for wkt::Timestamp {
308    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
309        match value.inner {
310            SqlValueInner::String(s) => {
311                let micros = s
312                    .parse::<i64>()
313                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
314                timestamp_from_micros(micros)
315            }
316            SqlValueInner::Number(n) => {
317                let micros = n.as_i64().ok_or_else(|| {
318                    ConvertError::Convert("timestamp number is not valid i64".into())
319                })?;
320                timestamp_from_micros(micros)
321            }
322            SqlValueInner::Null => Err(ConvertError::NotNull),
323            other => Err(ConvertError::type_mismatch("string or number", &other)),
324        }
325    }
326}
327
328fn timestamp_from_micros(micros: i64) -> Result<wkt::Timestamp, ConvertError> {
329    wkt::Timestamp::new(
330        micros.div_euclid(1_000_000),
331        (micros.rem_euclid(1_000_000) * 1_000) as i32,
332    )
333    .map_err(|e| ConvertError::Convert(Box::new(e)))
334}
335
336impl FromSql for google_cloud_type::model::Date {
337    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
338        match value.inner {
339            SqlValueInner::String(s) => {
340                let date = time::Date::parse(s.as_str(), BIGQUERY_DATE_FORMAT)
341                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
342                Ok(google_cloud_type::model::Date::new()
343                    .set_year(date.year())
344                    .set_month(u8::from(date.month()) as i32)
345                    .set_day(date.day() as i32))
346            }
347            SqlValueInner::Null => Err(ConvertError::NotNull),
348            other => Err(ConvertError::type_mismatch("string", &other)),
349        }
350    }
351}
352
353pub(crate) fn parse_time(s: &str) -> Result<time::Time, ConvertError> {
354    let format = if s.contains('.') {
355        BIGQUERY_TIME_SUBSEC_FORMAT
356    } else {
357        BIGQUERY_TIME_FORMAT
358    };
359    time::Time::parse(s, format).map_err(|e| ConvertError::Convert(Box::new(e)))
360}
361
362impl FromSql for google_cloud_type::model::TimeOfDay {
363    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
364        match value.inner {
365            SqlValueInner::String(s) => {
366                let time = parse_time(s.as_str())?;
367                Ok(google_cloud_type::model::TimeOfDay::new()
368                    .set_hours(time.hour() as i32)
369                    .set_minutes(time.minute() as i32)
370                    .set_seconds(time.second() as i32)
371                    .set_nanos(time.nanosecond() as i32))
372            }
373            SqlValueInner::Null => Err(ConvertError::NotNull),
374            other => Err(ConvertError::type_mismatch("string", &other)),
375        }
376    }
377}
378
379impl FromSql for google_cloud_type::model::DateTime {
380    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
381        match value.inner {
382            SqlValueInner::String(s) => {
383                let format = if s.contains('.') {
384                    BIGQUERY_DATETIME_SUBSEC_FORMAT
385                } else {
386                    BIGQUERY_DATETIME_FORMAT
387                };
388                let dt = time::PrimitiveDateTime::parse(s.as_str(), format)
389                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
390                Ok(google_cloud_type::model::DateTime::new()
391                    .set_year(dt.year())
392                    .set_month(u8::from(dt.month()) as i32)
393                    .set_day(dt.day() as i32)
394                    .set_hours(dt.hour() as i32)
395                    .set_minutes(dt.minute() as i32)
396                    .set_seconds(dt.second() as i32)
397                    .set_nanos(dt.nanosecond() as i32))
398            }
399            SqlValueInner::Null => Err(ConvertError::NotNull),
400            other => Err(ConvertError::type_mismatch("string", &other)),
401        }
402    }
403}
404
405impl FromSql for google_cloud_type::model::Decimal {
406    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
407        match value.inner {
408            SqlValueInner::String(s) => Ok(google_cloud_type::model::Decimal::new().set_value(s)),
409            SqlValueInner::Number(n) => {
410                Ok(google_cloud_type::model::Decimal::new().set_value(n.to_string()))
411            }
412            SqlValueInner::Null => Err(ConvertError::NotNull),
413            other => Err(ConvertError::type_mismatch("string or number", &other)),
414        }
415    }
416}
417
418impl FromSql for rust_decimal::Decimal {
419    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
420        match value.inner {
421            SqlValueInner::String(s) => s
422                .trim()
423                .parse::<rust_decimal::Decimal>()
424                .map_err(|e| ConvertError::Convert(Box::new(e))),
425            SqlValueInner::Number(n) => {
426                if let Some(i) = n.as_i64() {
427                    Ok(rust_decimal::Decimal::from(i))
428                } else if let Some(u) = n.as_u64() {
429                    Ok(rust_decimal::Decimal::from(u))
430                } else {
431                    let f = n.as_f64().expect("Number must be i64, u64, or f64");
432                    rust_decimal::Decimal::try_from(f)
433                        .map_err(|e| ConvertError::Convert(Box::new(e)))
434                }
435            }
436            SqlValueInner::Null => Err(ConvertError::NotNull),
437            other => Err(ConvertError::type_mismatch("string or number", &other)),
438        }
439    }
440}
441
442impl FromSql for Vec<u8> {
443    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
444        match value.inner {
445            SqlValueInner::String(s) => BASE64_STANDARD
446                .decode(s)
447                .map_err(|e| ConvertError::Convert(Box::new(e))),
448            SqlValueInner::Null => Err(ConvertError::NotNull),
449            other => Err(ConvertError::type_mismatch(
450                "string (base64 encoded)",
451                &other,
452            )),
453        }
454    }
455}
456
457impl FromSql for bytes::Bytes {
458    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
459        Vec::<u8>::from_value(value).map(bytes::Bytes::from)
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466    use crate as google_cloud_bigquery;
467    use crate::query::FromSql;
468    use google_cloud_type::model::Decimal;
469    use rust_decimal::Decimal as RustDecimal;
470    use test_case::test_case;
471
472    impl SqlValue {
473        pub(crate) fn new(value: wkt::Value) -> Self {
474            Self::from_inner(SqlValueInner::from_wkt(value))
475        }
476    }
477
478    // Test-only representation of `ConvertError` that implements `PartialEq`.
479    // This allows testing error outcomes using `test_case` assertions without
480    // implementing `PartialEq` on the production `ConvertError`.
481    #[derive(Debug, PartialEq)]
482    enum TestConvertError {
483        NotNull,
484        TypeMismatch(String),
485        Convert(String),
486        MissingField(String),
487    }
488
489    impl TestConvertError {
490        fn type_mismatch(expected: &str) -> Self {
491            Self::TypeMismatch(expected.to_string())
492        }
493    }
494
495    impl From<ConvertError> for TestConvertError {
496        fn from(err: ConvertError) -> Self {
497            match err {
498                ConvertError::NotNull => Self::NotNull,
499                ConvertError::TypeMismatch { expected, .. } => Self::TypeMismatch(expected),
500                ConvertError::Convert(e) => Self::Convert(e.to_string()),
501                ConvertError::MissingField(f) => Self::MissingField(f),
502            }
503        }
504    }
505
506    #[test]
507    fn test_sql_value_traits() {
508        static_assertions::assert_impl_all!(SqlValue: Send, Sync, Unpin, Clone, std::fmt::Debug);
509        static_assertions::assert_not_impl_any!(
510            SqlValue: std::panic::UnwindSafe,
511            std::panic::RefUnwindSafe
512        );
513    }
514
515    #[test_case(wkt::Value::Null => Ok(wkt::Value::Null) ; "value null")]
516    #[test_case(wkt::Value::Bool(true) => Ok(wkt::Value::Bool(true)) ; "value bool")]
517    #[test_case(wkt::Value::Number(42.into()) => Ok(wkt::Value::Number(42.into())) ; "value number")]
518    #[test_case(wkt::Value::String("hello".to_string()) => Ok(wkt::Value::String("hello".to_string())) ; "value string")]
519    #[test_case(wkt::Value::Array(vec![wkt::Value::Number(1.into()), wkt::Value::Bool(false)]) => Ok(wkt::Value::Array(vec![wkt::Value::Number(1.into()), wkt::Value::Bool(false)])) ; "value array")]
520    #[test_case(wkt::Value::Object(wkt::Struct::from_iter([("k".to_string(), wkt::Value::String("v".to_string()))])) => Ok(wkt::Value::Object(wkt::Struct::from_iter([("k".to_string(), wkt::Value::String("v".to_string()))]))) ; "value struct")]
521    fn test_from_sql_value(value: wkt::Value) -> Result<wkt::Value, TestConvertError> {
522        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
523    }
524
525    #[derive(FromSql, Debug, PartialEq)]
526    struct RawIdentSqlStruct {
527        r#type: String,
528        r#match: i64,
529    }
530
531    #[test]
532    fn test_derive_from_sql_raw_identifier() {
533        let val = wkt::Value::Object(wkt::Struct::from_iter([
534            ("type".to_string(), wkt::Value::String("event".to_string())),
535            ("match".to_string(), wkt::Value::Number(99.into())),
536        ]));
537        let parsed =
538            RawIdentSqlStruct::from_value(SqlValue::new(val)).expect("should strip r# prefix");
539        assert_eq!(
540            parsed,
541            RawIdentSqlStruct {
542                r#type: "event".to_string(),
543                r#match: 99,
544            }
545        );
546    }
547
548    #[test_case(wkt::Value::String("hello".to_string()) => Ok("hello".to_string()) ; "string")]
549    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null string")]
550    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "type mismatch string")]
551    fn test_from_sql_string(value: wkt::Value) -> Result<String, TestConvertError> {
552        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
553    }
554
555    #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i64 from number")]
556    #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i64 from string")]
557    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i64")]
558    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i64")]
559    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "invalid string as i64")]
560    fn test_from_sql_i64(value: wkt::Value) -> Result<i64, TestConvertError> {
561        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
562    }
563
564    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f64 from number")]
565    #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f64 from string")]
566    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f64")]
567    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f64")]
568    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid float literal".to_string())) ; "invalid string as f64")]
569    fn test_from_sql_f64(value: wkt::Value) -> Result<f64, TestConvertError> {
570        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
571    }
572
573    #[test_case(wkt::Value::Bool(true) => Ok(true) ; "bool true")]
574    #[test_case(wkt::Value::Bool(false) => Ok(false) ; "bool false")]
575    #[test_case(wkt::Value::String("true".to_string()) => Ok(true) ; "bool from string true")]
576    #[test_case(wkt::Value::String("false".to_string()) => Ok(false) ; "bool from string false")]
577    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bool")]
578    #[test_case(wkt::Value::Number(1.into()) => Err(TestConvertError::type_mismatch("bool or string")) ; "try number as bool")]
579    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("provided string was not `true` or `false`".to_string())) ; "invalid string as bool")]
580    fn test_from_sql_bool(value: wkt::Value) -> Result<bool, TestConvertError> {
581        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
582    }
583
584    #[test_case(wkt::Value::Null => Ok(None) ; "option null")]
585    #[test_case(wkt::Value::Number(123.into()) => Ok(Some(123)) ; "option some i64")]
586    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "option error i64")]
587    fn test_from_sql_option(value: wkt::Value) -> Result<Option<i64>, TestConvertError> {
588        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
589    }
590
591    #[test_case(wkt::Value::Array(vec![wkt::Value::Number(1.into()), wkt::Value::Number(2.into())]) => Ok(vec![1, 2]) ; "vec i64")]
592    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec i64")]
593    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::type_mismatch("array")) ; "vec type mismatch")]
594    #[test_case(wkt::Value::Array(vec![wkt::Value::String("invalid".to_string())]) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "vec element convert error")]
595    fn test_from_sql_vec(value: wkt::Value) -> Result<Vec<i64>, TestConvertError> {
596        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
597    }
598
599    #[test_case(wkt::Value::Object(wkt::Struct::from_iter([("a".to_string(), wkt::Value::Number(1.into()))])) => Ok(wkt::Struct::from_iter([("a".to_string(), wkt::Value::Number(1.into()))])) ; "struct ok")]
600    #[test_case(wkt::Value::String(r#"{"a": 1}"#.to_string()) => Ok(wkt::Struct::from_iter([("a".to_string(), wkt::Value::Number(1.into()))])) ; "struct from json string")]
601    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "struct null")]
602    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("expected value at line 1 column 1".to_string())) ; "struct invalid json string")]
603    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "struct type mismatch")]
604    fn test_from_sql_struct(value: wkt::Value) -> Result<wkt::Struct, TestConvertError> {
605        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
606    }
607
608    #[test_case(wkt::Value::String("1779982200000000".to_string()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer string")]
609    #[test_case(wkt::Value::Number(1779982200000000i64.into()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer number")]
610    #[test_case(wkt::Value::String("2026-05-28T15:30:00Z".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "timestamp rfc3339 string fails")]
611    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(1779982200.5).unwrap()) => Err(TestConvertError::Convert("timestamp number is not valid i64".to_string())) ; "timestamp f64 number fails")]
612    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "timestamp null")]
613    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "timestamp type mismatch")]
614    fn test_from_sql_timestamp(value: wkt::Value) -> Result<wkt::Timestamp, TestConvertError> {
615        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
616    }
617
618    #[test_case(wkt::Value::String("2026-05-28".to_string()) => Ok(google_cloud_type::model::Date::new().set_year(2026).set_month(5).set_day(28)) ; "date valid")]
619    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "date null")]
620    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "date type mismatch")]
621    #[test_case(wkt::Value::String("invalid-date".to_string()) => Err(TestConvertError::Convert("the 'year' component could not be parsed".to_string())) ; "date invalid format")]
622    #[test_case(wkt::Value::String("2026-abc-28".to_string()) => Err(TestConvertError::Convert("the 'month' component could not be parsed".to_string())) ; "date invalid digits")]
623    fn test_from_sql_date(
624        value: wkt::Value,
625    ) -> Result<google_cloud_type::model::Date, TestConvertError> {
626        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
627    }
628
629    #[test_case(wkt::Value::String("15:30:00".to_string()) => Ok(google_cloud_type::model::TimeOfDay::new().set_hours(15).set_minutes(30).set_seconds(0).set_nanos(0)) ; "time of day valid")]
630    #[test_case(wkt::Value::String("15:30:00.123456".to_string()) => Ok(google_cloud_type::model::TimeOfDay::new().set_hours(15).set_minutes(30).set_seconds(0).set_nanos(123_456_000)) ; "time of day fractional")]
631    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "time of day null")]
632    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "time of day type mismatch")]
633    fn test_from_sql_time_of_day(
634        value: wkt::Value,
635    ) -> Result<google_cloud_type::model::TimeOfDay, TestConvertError> {
636        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
637    }
638
639    #[test_case(wkt::Value::String("2026-05-28T15:30:00".to_string()) => Ok(google_cloud_type::model::DateTime::new().set_year(2026).set_month(5).set_day(28).set_hours(15).set_minutes(30).set_seconds(0).set_nanos(0)) ; "datetime without subseconds")]
640    #[test_case(wkt::Value::String("2026-05-28T15:30:00.123456".to_string()) => Ok(google_cloud_type::model::DateTime::new().set_year(2026).set_month(5).set_day(28).set_hours(15).set_minutes(30).set_seconds(0).set_nanos(123_456_000)) ; "datetime with subseconds")]
641    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "datetime null")]
642    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "datetime type mismatch")]
643    fn test_from_sql_datetime(
644        value: wkt::Value,
645    ) -> Result<google_cloud_type::model::DateTime, TestConvertError> {
646        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
647    }
648
649    #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i32 from number")]
650    #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i32 from string")]
651    #[test_case(wkt::Value::Number(3_000_000_000i64.into()) => Err(TestConvertError::Convert("number is not a valid i32".to_string())) ; "i32 overflow from number")]
652    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i32")]
653    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i32")]
654    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "invalid string as i32")]
655    fn test_from_sql_i32(value: wkt::Value) -> Result<i32, TestConvertError> {
656        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
657    }
658
659    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f32 from number")]
660    #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f32 from string")]
661    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f32")]
662    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f32")]
663    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid float literal".to_string())) ; "invalid string as f32")]
664    fn test_from_sql_f32(value: wkt::Value) -> Result<f32, TestConvertError> {
665        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
666    }
667
668    #[test_case(wkt::Value::String("123.456".to_string()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from string")]
669    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.456).unwrap()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from number")]
670    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null decimal")]
671    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as decimal")]
672    fn test_from_sql_decimal(value: wkt::Value) -> Result<Decimal, TestConvertError> {
673        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
674    }
675
676    #[test_case(wkt::Value::String("123.456".to_string()) => Ok(RustDecimal::from_str_exact("123.456").unwrap()) ; "rust_decimal from string")]
677    #[test_case(wkt::Value::Number((-123i64).into()) => Ok(RustDecimal::from(-123i64)) ; "rust_decimal from i64 number")]
678    #[test_case(wkt::Value::Number(u64::MAX.into()) => Ok(RustDecimal::from(u64::MAX)) ; "rust_decimal from u64 number")]
679    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.456).unwrap()) => Ok(RustDecimal::from_str_exact("123.456").unwrap()) ; "rust_decimal from f64 number")]
680    #[test_case(wkt::Value::String("99999999999999999999999999999999.123".to_string()) => Err(TestConvertError::Convert("Invalid decimal: overflow from too many digits".to_string())) ; "rust_decimal overflow")]
681    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null rust_decimal")]
682    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as rust_decimal")]
683    fn test_from_sql_rust_decimal(value: wkt::Value) -> Result<RustDecimal, TestConvertError> {
684        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
685    }
686
687    #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(vec![1, 2, 3, 4]) ; "vec u8 from base64")]
688    #[test_case(wkt::Value::String("".to_string()) => Ok(vec![]) ; "vec u8 from empty base64")]
689    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec u8")]
690    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as vec u8")]
691    fn test_from_sql_vec_u8(value: wkt::Value) -> Result<Vec<u8>, TestConvertError> {
692        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
693    }
694
695    #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(bytes::Bytes::from_static(&[1, 2, 3, 4])) ; "bytes from base64")]
696    #[test_case(wkt::Value::String("".to_string()) => Ok(bytes::Bytes::from_static(&[])) ; "bytes from empty base64")]
697    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bytes")]
698    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as bytes")]
699    fn test_from_sql_bytes(value: wkt::Value) -> Result<bytes::Bytes, TestConvertError> {
700        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
701    }
702
703    #[test_case("AQIDBA" ; "missing padding")]
704    #[test_case("Not a base64 string" ; "words with spaces")]
705    fn test_from_sql_bytes_invalid_base64(input: &str) {
706        let err = bytes::Bytes::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
707            .unwrap_err();
708        assert!(matches!(err, ConvertError::Convert(_)));
709
710        let err = Vec::<u8>::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
711            .unwrap_err();
712        assert!(matches!(err, ConvertError::Convert(_)));
713    }
714
715    #[derive(FromSql, Debug, PartialEq)]
716    struct TestSqlStruct {
717        name: String,
718        #[bigquery(rename = "custom_int")]
719        some_int: i64,
720        some_bool: bool,
721    }
722
723    #[test_case(wkt::Value::Object(wkt::Struct::from_iter([("name".to_string(), wkt::Value::String("James".to_string())), ("custom_int".to_string(), wkt::Value::Number(272793.into())), ("some_bool".to_string(), wkt::Value::Bool(true))])) => Ok(TestSqlStruct { name: "James".to_string(), some_int: 272793, some_bool: true }) ; "object success")]
724    #[test_case(wkt::Value::String(r#"{"name": "James", "custom_int": 272793, "some_bool": true}"#.to_string()) => Ok(TestSqlStruct { name: "James".to_string(), some_int: 272793, some_bool: true }) ; "json string success")]
725    #[test_case(wkt::Value::Object(wkt::Struct::from_iter([("name".to_string(), wkt::Value::String("James".to_string())), ("some_bool".to_string(), wkt::Value::Bool(true))])) => Err(TestConvertError::MissingField("custom_int".to_string())) ; "missing field")]
726    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null error")]
727    #[test_case(wkt::Value::String("invalid".to_string()) => Err(TestConvertError::Convert("expected value at line 1 column 1".to_string())) ; "invalid json string")]
728    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "type mismatch")]
729    fn test_derive_from_sql(value: wkt::Value) -> Result<TestSqlStruct, TestConvertError> {
730        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
731    }
732
733    #[derive(FromSql, Debug, PartialEq)]
734    struct ShadowedSqlStruct {
735        iter: i64,
736        obj: i64,
737        arr: i64,
738        value: String,
739    }
740
741    #[test]
742    fn test_derive_from_sql_shadowing_field_names() {
743        let from_obj = ShadowedSqlStruct::from_value(SqlValue::new(wkt::Value::Object(
744            wkt::Struct::from_iter([
745                ("iter".to_string(), wkt::Value::Number(10.into())),
746                ("obj".to_string(), wkt::Value::Number(20.into())),
747                ("arr".to_string(), wkt::Value::Number(30.into())),
748                ("value".to_string(), wkt::Value::String("world".to_string())),
749            ]),
750        )))
751        .expect("should deserialize from object");
752        assert_eq!(
753            from_obj,
754            ShadowedSqlStruct {
755                iter: 10,
756                obj: 20,
757                arr: 30,
758                value: "world".to_string(),
759            }
760        );
761    }
762
763    #[test]
764    fn test_sql_value_take_by_index_and_name() -> anyhow::Result<()> {
765        let mut arr_val = SqlValue::new(wkt::Value::Array(vec![
766            wkt::Value::Number(42.into()),
767            wkt::Value::String("hello".to_string()),
768        ]));
769        assert_eq!(arr_val.take::<i64, _>(0)?, 42);
770        assert_eq!(arr_val.take::<String, _>(1)?, "hello");
771        assert!(matches!(
772            arr_val.take::<i64, _>(2),
773            Err(ConvertError::MissingField(ref f)) if f == "2"
774        ));
775
776        // Taking "foo" (at index 0) must replace slot 0 with Null in-place
777        // without shifting index 1 ("bar") down to index 0.
778        let mut struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
779            ("foo".to_string(), SqlValueInner::Number(10.into())),
780            (
781                "bar".to_string(),
782                SqlValueInner::String("twenty".to_string()),
783            ),
784        ]));
785        assert_eq!(struct_val.take::<i64, _>("foo".to_string())?, 10);
786        // Subsequent reads of "foo" see Null (returning Ok(None) for Option<T>, matching Row::take)
787        assert_eq!(struct_val.take::<Option<i64>, _>("foo")?, None);
788        // Index 1 is still "bar" (not shifted)
789        assert_eq!(struct_val.take::<String, _>(1)?, "twenty");
790
791        // Positional take(usize) on a JSON array string
792        let mut json_arr = SqlValue::new(wkt::Value::String(r#"[7, "eight"]"#.to_string()));
793        assert_eq!(json_arr.take::<i64, _>(0)?, 7);
794        assert_eq!(json_arr.take::<String, _>(1)?, "eight");
795
796        // Invalid JSON string with positional take(usize)
797        let mut invalid_json_arr = SqlValue::new(wkt::Value::String("not-json".to_string()));
798        assert!(matches!(
799            invalid_json_arr.take::<i64, _>(0),
800            Err(ConvertError::Convert(_))
801        ));
802
803        // Positional take(usize) on Null and primitive Bool
804        let mut null_val = SqlValue::new(wkt::Value::Null);
805        assert!(matches!(
806            null_val.take::<i64, _>(0),
807            Err(ConvertError::NotNull)
808        ));
809
810        let mut bool_val = SqlValue::new(wkt::Value::Bool(true));
811        assert!(matches!(
812            bool_val.take::<i64, _>(0),
813            Err(ConvertError::TypeMismatch { ref expected, ref got })
814                if expected == "struct, array, or string" && got == "bool"
815        ));
816
817        // Cover SqlValueInner::type_name for Null, Array, and Struct
818        assert_eq!(SqlValueInner::Null.type_name(), "null");
819        assert_eq!(SqlValueInner::Array(vec![]).type_name(), "array");
820        assert_eq!(SqlValueInner::Struct(vec![]).type_name(), "object");
821
822        Ok(())
823    }
824
825    #[derive(FromSql, Debug, PartialEq)]
826    struct GenericSql<T: Clone + Default, U: std::fmt::Debug> {
827        #[bigquery(rename = "custom_val")]
828        single: T,
829        optional: Option<T>,
830        list: Vec<T>,
831        nested: NestedGenericSql<U>,
832    }
833
834    #[derive(FromSql, Debug, PartialEq)]
835    struct NestedGenericSql<U> {
836        inner_val: U,
837    }
838
839    #[test]
840    fn test_derive_from_sql_generic() {
841        let nested_obj = wkt::Value::Object(wkt::Struct::from_iter([(
842            "inner_val".to_string(),
843            wkt::Value::String("hello".to_string()),
844        )]));
845        let from_obj = GenericSql::<i64, String>::from_value(SqlValue::new(wkt::Value::Object(
846            wkt::Struct::from_iter([
847                ("custom_val".to_string(), wkt::Value::Number(100.into())),
848                ("optional".to_string(), wkt::Value::Null),
849                (
850                    "list".to_string(),
851                    wkt::Value::Array(vec![
852                        wkt::Value::Number(1.into()),
853                        wkt::Value::Number(2.into()),
854                        wkt::Value::Number(3.into()),
855                    ]),
856                ),
857                ("nested".to_string(), nested_obj),
858            ]),
859        )))
860        .expect("should deserialize generic from object");
861
862        assert_eq!(
863            from_obj,
864            GenericSql {
865                single: 100,
866                optional: None,
867                list: vec![1, 2, 3],
868                nested: NestedGenericSql {
869                    inner_val: "hello".to_string(),
870                },
871            }
872        );
873    }
874
875    #[derive(FromSql, Debug, PartialEq)]
876    struct GenericSqlWhere<T>
877    where
878        T: std::fmt::Debug + Clone,
879    {
880        val: T,
881    }
882
883    #[test]
884    fn test_derive_from_sql_generic_where_clause() {
885        let val = GenericSqlWhere::<String>::from_value(SqlValue::new(wkt::Value::Object(
886            wkt::Struct::from_iter([("val".to_string(), wkt::Value::String("hello".to_string()))]),
887        )))
888        .expect("should deserialize generic with where clause");
889
890        assert_eq!(
891            val,
892            GenericSqlWhere {
893                val: "hello".to_string(),
894            }
895        );
896    }
897
898    #[derive(FromSql, Debug, PartialEq)]
899    struct GenericSqlDefault<T = i64> {
900        val: T,
901    }
902
903    #[test]
904    fn test_derive_from_sql_generic_default_param() {
905        let val: GenericSqlDefault =
906            GenericSqlDefault::from_value(SqlValue::new(wkt::Value::Object(
907                wkt::Struct::from_iter([("val".to_string(), wkt::Value::Number(42.into()))]),
908            )))
909            .expect("should deserialize generic with default type param");
910
911        assert_eq!(val, GenericSqlDefault { val: 42 });
912    }
913
914    #[derive(FromSql, Debug, PartialEq)]
915    struct GenericTupleSql<T, U>(T, Option<T>, U);
916
917    #[test]
918    fn test_derive_from_sql_generic_tuple_struct() {
919        let struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
920            ("".to_string(), SqlValueInner::Number(100.into())),
921            ("".to_string(), SqlValueInner::Null),
922            ("".to_string(), SqlValueInner::String("world".to_string())),
923        ]));
924        let parsed = GenericTupleSql::<i64, String>::from_value(struct_val)
925            .expect("should deserialize generic tuple struct from struct");
926        assert_eq!(parsed, GenericTupleSql(100, None, "world".to_string()));
927
928        let json_val = SqlValue::new(wkt::Value::String(r#"[100, null, "world"]"#.to_string()));
929        let parsed_json = GenericTupleSql::<i64, String>::from_value(json_val)
930            .expect("should deserialize generic tuple struct from JSON array");
931        assert_eq!(parsed_json, GenericTupleSql(100, None, "world".to_string()));
932    }
933}