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
15#[cfg(any(
16    google_cloud_unstable_bigquery_arrow,
17    google_cloud_unstable_bigquery_storage_read
18))]
19pub(crate) use super::arrow::ArrowCell;
20use base64::Engine;
21use base64::prelude::BASE64_STANDARD;
22#[allow(unused_imports)]
23use wkt::{Struct, Timestamp, Value};
24
25use crate::error::ConvertError;
26
27pub(crate) const BIGQUERY_DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
28    time::macros::format_description!("[year]-[month]-[day]");
29pub(crate) const BIGQUERY_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
30    "[hour padding:none]:[minute padding:none]:[second padding:none]"
31);
32pub(crate) const BIGQUERY_TIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
33    "[hour padding:none]:[minute padding:none]:[second padding:none].[subsecond]"
34);
35pub(crate) const BIGQUERY_DATETIME_FORMAT: &[time::format_description::FormatItem<'static>] =
36    time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
37pub(crate) const BIGQUERY_DATETIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<
38    'static,
39>] = time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]");
40
41#[derive(Clone, Debug, PartialEq)]
42pub(crate) enum SqlValueInner {
43    Null,
44    Bool(bool),
45    Number(serde_json::Number),
46    String(String),
47    Array(Vec<SqlValueInner>),
48    Struct(Vec<(String, SqlValueInner)>),
49    #[cfg(any(
50        google_cloud_unstable_bigquery_arrow,
51        google_cloud_unstable_bigquery_storage_read
52    ))]
53    #[cfg_attr(not(test), allow(dead_code))]
54    Arrow(ArrowCell),
55}
56
57impl SqlValueInner {
58    pub(crate) fn type_name(&self) -> String {
59        match self {
60            Self::Null => "null".to_string(),
61            Self::Bool(_) => "bool".to_string(),
62            Self::Number(_) => "number".to_string(),
63            Self::String(_) => "string".to_string(),
64            Self::Array(_) => "array".to_string(),
65            Self::Struct(_) => "object".to_string(),
66            #[cfg(any(
67                google_cloud_unstable_bigquery_arrow,
68                google_cloud_unstable_bigquery_storage_read
69            ))]
70            Self::Arrow(cell) => cell.data_type_str(),
71        }
72    }
73
74    pub(crate) fn from_wkt(value: wkt::Value) -> Self {
75        match value {
76            wkt::Value::Null => Self::Null,
77            wkt::Value::Bool(b) => Self::Bool(b),
78            wkt::Value::Number(n) => Self::Number(n),
79            wkt::Value::String(s) => Self::String(s),
80            wkt::Value::Array(arr) => Self::Array(arr.into_iter().map(Self::from_wkt).collect()),
81            wkt::Value::Object(obj) => Self::Struct(
82                obj.into_iter()
83                    .map(|(k, v)| (k, Self::from_wkt(v)))
84                    .collect(),
85            ),
86        }
87    }
88}
89
90/// An opaque representation of a BigQuery SQL cell value.
91///
92/// Passed to [`FromSql::from_value`] when converting query result cells
93/// into Rust types.
94#[derive(Clone, Debug)]
95pub struct SqlValue {
96    pub(crate) inner: SqlValueInner,
97    // Opts out of `UnwindSafe` and `RefUnwindSafe` while preserving `Send + Sync + Unpin`.
98    _not_unwind_safe: std::marker::PhantomData<dyn Send + Sync + Unpin>,
99}
100
101impl SqlValue {
102    pub(crate) fn from_inner(inner: SqlValueInner) -> Self {
103        Self {
104            inner,
105            _not_unwind_safe: std::marker::PhantomData,
106        }
107    }
108
109    /// Takes ownership of a field by name (`&str`) or zero-based index (`usize`)
110    /// from a `STRUCT`, `ARRAY`, or `JSON` value and converts it to `T`.
111    ///
112    /// Used by the [`#[derive(FromSql)]`](derive@crate::query::FromSql) macro to
113    /// extract struct fields.
114    pub fn take<T: FromSql, I: crate::query::ColumnIndex>(
115        &mut self,
116        index: I,
117    ) -> Result<T, ConvertError> {
118        let val = index.take_sql_value(self)?;
119        T::from_value(val)
120    }
121}
122
123/// A trait for converting BigQuery value representations into Rust
124/// types.
125///
126/// The BigQuery client uses [`wkt::Value`] by default, but will support
127/// Arrow record batches via the Storage Read API integration in the future.
128///
129/// <div class="warning">
130///
131/// **Do not implement this trait directly.**
132///
133/// This trait is not intended for manual implementation. New methods (such as
134/// Arrow conversion methods) may be added in future versions, which would break
135/// manual implementations. To deserialize custom structs, use [`#[derive(FromSql)]`](derive@crate::query::FromSql)
136/// or [`#[derive(FromRow)]`](crate::query::FromRow) instead.
137///
138/// </div>
139///
140/// [`Row::get()`](crate::query::Row::get) or [`Row::take()`](crate::query::Row::take)
141/// use this trait to convert cell values, and the [`FromRow`](crate::query::FromRow)
142/// derive macro uses it for field deserialization.
143///
144/// # Supported Types
145///
146/// Built-in implementations include:
147/// - Numbers: `i32`, `i64`, `f32`, `f64`, [`Decimal`](rust_decimal::Decimal)
148/// - Text & Bytes: `String`, `Vec<u8>` (decoded from base64), [`Bytes`](bytes::Bytes)
149/// - Dates & Times: [`Timestamp`](wkt::Timestamp), [`Date`](google_cloud_type::model::Date), [`TimeOfDay`](google_cloud_type::model::TimeOfDay), [`DateTime`](google_cloud_type::model::DateTime)
150/// - Intervals: [`Interval`](crate::datatypes::Interval)
151/// - Collections: `Option<T>` (for `NULL`), `Vec<T>` (for repeated fields), [`Range<T>`](crate::datatypes::Range) (for `RANGE` types)
152/// - Raw JSON: [`Value`](wkt::Value), [`Struct`](wkt::Struct)
153///
154/// # Example
155///
156/// ```
157/// # use google_cloud_bigquery::client::BigQuery;
158/// # async fn sample(client: BigQuery) -> anyhow::Result<()> {
159/// let mut rows = client
160///     .query("SELECT 12345 AS integer_col, 'foo' AS string_col")
161///     .until_done()
162///     .await?
163///     .read();
164///
165/// while let Some(row) = rows.next().await.transpose()? {
166///     let num: i64 = row.get("integer_col")?;
167///     let txt: String = row.get("string_col")?;
168///     println!("{txt}: {num}");
169/// }
170/// # Ok(())
171/// # }
172/// ```
173pub trait FromSql: Sized {
174    /// Converts a BigQuery [`SqlValue`] into the implementing type.
175    fn from_value(value: SqlValue) -> Result<Self, ConvertError>;
176}
177
178impl FromSql for wkt::Value {
179    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
180        Ok(match value.inner {
181            SqlValueInner::Null => wkt::Value::Null,
182            SqlValueInner::Bool(b) => wkt::Value::Bool(b),
183            SqlValueInner::Number(n) => wkt::Value::Number(n),
184            SqlValueInner::String(s) => wkt::Value::String(s),
185            SqlValueInner::Array(arr) => wkt::Value::Array(
186                arr.into_iter()
187                    .map(|v| wkt::Value::from_value(SqlValue::from_inner(v)))
188                    .collect::<Result<Vec<_>, _>>()?,
189            ),
190            SqlValueInner::Struct(entries) => wkt::Value::Object(
191                entries
192                    .into_iter()
193                    .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
194                    .collect::<Result<wkt::Struct, ConvertError>>()?,
195            ),
196            #[cfg(any(
197                google_cloud_unstable_bigquery_arrow,
198                google_cloud_unstable_bigquery_storage_read
199            ))]
200            SqlValueInner::Arrow(_) => {
201                // TODO(#7032): Implement wkt::Value conversion for Arrow cells.
202                return Err(ConvertError::Convert(
203                    "Arrow to wkt::Value conversion is not yet implemented".into(),
204                ));
205            }
206        })
207    }
208}
209
210impl FromSql for String {
211    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
212        match value.inner {
213            SqlValueInner::String(s) => Ok(s),
214            SqlValueInner::Null => Err(ConvertError::NotNull),
215            #[cfg(any(
216                google_cloud_unstable_bigquery_arrow,
217                google_cloud_unstable_bigquery_storage_read
218            ))]
219            SqlValueInner::Arrow(cell) => cell.as_str().map(str::to_owned),
220            other => Err(ConvertError::type_mismatch("string", &other)),
221        }
222    }
223}
224
225impl FromSql for i32 {
226    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
227        match value.inner {
228            SqlValueInner::Number(n) => n
229                .as_i64()
230                .and_then(|v| i32::try_from(v).ok())
231                .ok_or_else(|| ConvertError::Convert("number is not a valid i32".into())),
232            SqlValueInner::String(s) => s
233                .parse::<i32>()
234                .map_err(|e| ConvertError::Convert(Box::new(e))),
235            SqlValueInner::Null => Err(ConvertError::NotNull),
236            #[cfg(any(
237                google_cloud_unstable_bigquery_arrow,
238                google_cloud_unstable_bigquery_storage_read
239            ))]
240            SqlValueInner::Arrow(cell) => cell.as_i32(),
241            other => Err(ConvertError::type_mismatch("number or string", &other)),
242        }
243    }
244}
245
246impl FromSql for i64 {
247    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
248        match value.inner {
249            SqlValueInner::Number(n) => n
250                .as_i64()
251                .ok_or_else(|| ConvertError::Convert("number is not a valid i64".into())),
252            SqlValueInner::String(s) => s
253                .parse::<i64>()
254                .map_err(|e| ConvertError::Convert(Box::new(e))),
255            SqlValueInner::Null => Err(ConvertError::NotNull),
256            #[cfg(any(
257                google_cloud_unstable_bigquery_arrow,
258                google_cloud_unstable_bigquery_storage_read
259            ))]
260            SqlValueInner::Arrow(cell) => cell.as_i64(),
261            other => Err(ConvertError::type_mismatch("number or string", &other)),
262        }
263    }
264}
265
266impl FromSql for f32 {
267    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
268        match value.inner {
269            SqlValueInner::Number(n) => n
270                .as_f64()
271                .map(|v| v as f32)
272                .ok_or_else(|| ConvertError::Convert("number is not a valid f32".into())),
273            SqlValueInner::String(s) => s
274                .parse::<f32>()
275                .map_err(|e| ConvertError::Convert(Box::new(e))),
276            SqlValueInner::Null => Err(ConvertError::NotNull),
277            #[cfg(any(
278                google_cloud_unstable_bigquery_arrow,
279                google_cloud_unstable_bigquery_storage_read
280            ))]
281            SqlValueInner::Arrow(cell) => cell.as_f32(),
282            other => Err(ConvertError::type_mismatch("number or string", &other)),
283        }
284    }
285}
286
287impl FromSql for f64 {
288    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
289        match value.inner {
290            SqlValueInner::Number(n) => n
291                .as_f64()
292                .ok_or_else(|| ConvertError::Convert("number is not a valid f64".into())),
293            SqlValueInner::String(s) => s
294                .parse::<f64>()
295                .map_err(|e| ConvertError::Convert(Box::new(e))),
296            SqlValueInner::Null => Err(ConvertError::NotNull),
297            #[cfg(any(
298                google_cloud_unstable_bigquery_arrow,
299                google_cloud_unstable_bigquery_storage_read
300            ))]
301            SqlValueInner::Arrow(cell) => cell.as_f64(),
302            other => Err(ConvertError::type_mismatch("number or string", &other)),
303        }
304    }
305}
306
307impl FromSql for bool {
308    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
309        match value.inner {
310            SqlValueInner::Bool(b) => Ok(b),
311            SqlValueInner::String(s) => s
312                .parse::<bool>()
313                .map_err(|e| ConvertError::Convert(Box::new(e))),
314            SqlValueInner::Null => Err(ConvertError::NotNull),
315            #[cfg(any(
316                google_cloud_unstable_bigquery_arrow,
317                google_cloud_unstable_bigquery_storage_read
318            ))]
319            SqlValueInner::Arrow(cell) => cell.as_bool(),
320            other => Err(ConvertError::type_mismatch("bool or string", &other)),
321        }
322    }
323}
324
325impl<T: FromSql> FromSql for Option<T> {
326    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
327        match value.inner {
328            SqlValueInner::Null => Ok(None),
329            #[cfg(any(
330                google_cloud_unstable_bigquery_arrow,
331                google_cloud_unstable_bigquery_storage_read
332            ))]
333            SqlValueInner::Arrow(cell) if cell.is_null() => Ok(None),
334            other => T::from_value(SqlValue::from_inner(other)).map(Some),
335        }
336    }
337}
338
339impl<T: FromSql> FromSql for Vec<T> {
340    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
341        match value.inner {
342            SqlValueInner::Array(arr) => arr
343                .into_iter()
344                .map(|v| T::from_value(SqlValue::from_inner(v)))
345                .collect(),
346            SqlValueInner::Null => Err(ConvertError::NotNull),
347            #[cfg(any(
348                google_cloud_unstable_bigquery_arrow,
349                google_cloud_unstable_bigquery_storage_read
350            ))]
351            SqlValueInner::Arrow(cell) => {
352                let value_arr = cell.list_array_ref()?;
353                (0..value_arr.len())
354                    .map(|i| {
355                        let elem_cell = ArrowCell::new(value_arr.clone(), i);
356                        T::from_value(SqlValue::from_inner(SqlValueInner::Arrow(elem_cell)))
357                    })
358                    .collect()
359            }
360            other => Err(ConvertError::type_mismatch("array", &other)),
361        }
362    }
363}
364
365impl FromSql for wkt::Struct {
366    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
367        match value.inner {
368            SqlValueInner::Struct(entries) => entries
369                .into_iter()
370                .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
371                .collect(),
372            SqlValueInner::String(s) => {
373                serde_json::from_str(&s).map_err(|e| ConvertError::Convert(Box::new(e)))
374            }
375            SqlValueInner::Null => Err(ConvertError::NotNull),
376            #[cfg(any(
377                google_cloud_unstable_bigquery_arrow,
378                google_cloud_unstable_bigquery_storage_read
379            ))]
380            SqlValueInner::Arrow(cell) => {
381                let s = cell.as_str()?;
382                serde_json::from_str(s).map_err(|e| ConvertError::Convert(Box::new(e)))
383            }
384            other => Err(ConvertError::type_mismatch("object or string", &other)),
385        }
386    }
387}
388
389impl FromSql for wkt::Timestamp {
390    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
391        match value.inner {
392            SqlValueInner::String(s) => {
393                let micros = s
394                    .parse::<i64>()
395                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
396                timestamp_from_micros(micros)
397            }
398            SqlValueInner::Number(n) => {
399                let micros = n.as_i64().ok_or_else(|| {
400                    ConvertError::Convert("timestamp number is not valid i64".into())
401                })?;
402                timestamp_from_micros(micros)
403            }
404            SqlValueInner::Null => Err(ConvertError::NotNull),
405            #[cfg(any(
406                google_cloud_unstable_bigquery_arrow,
407                google_cloud_unstable_bigquery_storage_read
408            ))]
409            SqlValueInner::Arrow(cell) => {
410                let micros = cell.as_timestamp_micros()?;
411                timestamp_from_micros(micros)
412            }
413            other => Err(ConvertError::type_mismatch("string or number", &other)),
414        }
415    }
416}
417
418fn timestamp_from_micros(micros: i64) -> Result<wkt::Timestamp, ConvertError> {
419    wkt::Timestamp::new(
420        micros.div_euclid(1_000_000),
421        (micros.rem_euclid(1_000_000) * 1_000) as i32,
422    )
423    .map_err(|e| ConvertError::Convert(Box::new(e)))
424}
425
426impl FromSql for google_cloud_type::model::Date {
427    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
428        match value.inner {
429            SqlValueInner::String(s) => {
430                let date = time::Date::parse(s.as_str(), BIGQUERY_DATE_FORMAT)
431                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
432                Ok(google_cloud_type::model::Date::new()
433                    .set_year(date.year())
434                    .set_month(u8::from(date.month()) as i32)
435                    .set_day(date.day() as i32))
436            }
437            SqlValueInner::Null => Err(ConvertError::NotNull),
438            #[cfg(any(
439                google_cloud_unstable_bigquery_arrow,
440                google_cloud_unstable_bigquery_storage_read
441            ))]
442            SqlValueInner::Arrow(cell) => {
443                let days = cell.as_date32()?;
444                let date = time::OffsetDateTime::from_unix_timestamp(i64::from(days) * 86_400)
445                    .map_err(|e| ConvertError::Convert(Box::new(e)))?
446                    .date();
447                Ok(google_cloud_type::model::Date::new()
448                    .set_year(date.year())
449                    .set_month(i32::from(u8::from(date.month())))
450                    .set_day(i32::from(date.day())))
451            }
452            other => Err(ConvertError::type_mismatch("string", &other)),
453        }
454    }
455}
456
457pub(crate) fn parse_time(s: &str) -> Result<time::Time, ConvertError> {
458    let format = if s.contains('.') {
459        BIGQUERY_TIME_SUBSEC_FORMAT
460    } else {
461        BIGQUERY_TIME_FORMAT
462    };
463    time::Time::parse(s, format).map_err(|e| ConvertError::Convert(Box::new(e)))
464}
465
466impl FromSql for google_cloud_type::model::TimeOfDay {
467    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
468        match value.inner {
469            SqlValueInner::String(s) => {
470                let time = parse_time(s.as_str())?;
471                Ok(google_cloud_type::model::TimeOfDay::new()
472                    .set_hours(time.hour() as i32)
473                    .set_minutes(time.minute() as i32)
474                    .set_seconds(time.second() as i32)
475                    .set_nanos(time.nanosecond() as i32))
476            }
477            SqlValueInner::Null => Err(ConvertError::NotNull),
478            #[cfg(any(
479                google_cloud_unstable_bigquery_arrow,
480                google_cloud_unstable_bigquery_storage_read
481            ))]
482            SqlValueInner::Arrow(cell) => {
483                let micros = cell.as_time64_micros()?;
484                if !(0..86_400_000_000).contains(&micros) {
485                    return Err(ConvertError::Convert(
486                        format!("time64 microseconds out of range: {micros}").into(),
487                    ));
488                }
489                let nanos = (micros % 1_000_000) * 1_000;
490                let total_secs = micros / 1_000_000;
491                let seconds = total_secs % 60;
492                let total_mins = total_secs / 60;
493                let minutes = total_mins % 60;
494                let hours = total_mins / 60;
495                Ok(google_cloud_type::model::TimeOfDay::new()
496                    .set_hours(hours as i32)
497                    .set_minutes(minutes as i32)
498                    .set_seconds(seconds as i32)
499                    .set_nanos(nanos as i32))
500            }
501            other => Err(ConvertError::type_mismatch("string", &other)),
502        }
503    }
504}
505
506impl FromSql for google_cloud_type::model::DateTime {
507    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
508        match value.inner {
509            SqlValueInner::String(s) => {
510                let format = if s.contains('.') {
511                    BIGQUERY_DATETIME_SUBSEC_FORMAT
512                } else {
513                    BIGQUERY_DATETIME_FORMAT
514                };
515                let dt = time::PrimitiveDateTime::parse(s.as_str(), format)
516                    .map_err(|e| ConvertError::Convert(Box::new(e)))?;
517                Ok(google_cloud_type::model::DateTime::new()
518                    .set_year(dt.year())
519                    .set_month(u8::from(dt.month()) as i32)
520                    .set_day(dt.day() as i32)
521                    .set_hours(dt.hour() as i32)
522                    .set_minutes(dt.minute() as i32)
523                    .set_seconds(dt.second() as i32)
524                    .set_nanos(dt.nanosecond() as i32))
525            }
526            SqlValueInner::Null => Err(ConvertError::NotNull),
527            #[cfg(any(
528                google_cloud_unstable_bigquery_arrow,
529                google_cloud_unstable_bigquery_storage_read
530            ))]
531            SqlValueInner::Arrow(cell) => {
532                let micros = cell.as_timestamp_micros()?;
533                let odt =
534                    time::OffsetDateTime::from_unix_timestamp_nanos(i128::from(micros) * 1_000)
535                        .map_err(|e| ConvertError::Convert(Box::new(e)))?;
536                Ok(google_cloud_type::model::DateTime::new()
537                    .set_year(odt.year())
538                    .set_month(i32::from(u8::from(odt.month())))
539                    .set_day(i32::from(odt.day()))
540                    .set_hours(i32::from(odt.hour()))
541                    .set_minutes(i32::from(odt.minute()))
542                    .set_seconds(i32::from(odt.second()))
543                    .set_nanos(odt.nanosecond() as i32))
544            }
545            other => Err(ConvertError::type_mismatch("string", &other)),
546        }
547    }
548}
549
550impl FromSql for google_cloud_type::model::Decimal {
551    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
552        match value.inner {
553            SqlValueInner::String(s) => Ok(google_cloud_type::model::Decimal::new().set_value(s)),
554            SqlValueInner::Number(n) => {
555                Ok(google_cloud_type::model::Decimal::new().set_value(n.to_string()))
556            }
557            SqlValueInner::Null => Err(ConvertError::NotNull),
558            #[cfg(any(
559                google_cloud_unstable_bigquery_arrow,
560                google_cloud_unstable_bigquery_storage_read
561            ))]
562            SqlValueInner::Arrow(cell) => cell
563                .as_decimal_str()
564                .map(|s| google_cloud_type::model::Decimal::new().set_value(s)),
565            other => Err(ConvertError::type_mismatch("string or number", &other)),
566        }
567    }
568}
569
570impl FromSql for rust_decimal::Decimal {
571    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
572        match value.inner {
573            SqlValueInner::String(s) => s
574                .trim()
575                .parse::<rust_decimal::Decimal>()
576                .map_err(|e| ConvertError::Convert(Box::new(e))),
577            SqlValueInner::Number(n) => {
578                if let Some(i) = n.as_i64() {
579                    Ok(rust_decimal::Decimal::from(i))
580                } else if let Some(u) = n.as_u64() {
581                    Ok(rust_decimal::Decimal::from(u))
582                } else {
583                    let f = n.as_f64().expect("Number must be i64, u64, or f64");
584                    rust_decimal::Decimal::try_from(f)
585                        .map_err(|e| ConvertError::Convert(Box::new(e)))
586                }
587            }
588            SqlValueInner::Null => Err(ConvertError::NotNull),
589            #[cfg(any(
590                google_cloud_unstable_bigquery_arrow,
591                google_cloud_unstable_bigquery_storage_read
592            ))]
593            SqlValueInner::Arrow(cell) => {
594                if let Some((val, scale)) = cell.as_decimal128_with_scale()
595                    && let Ok(d) = rust_decimal::Decimal::try_from_i128_with_scale(val, scale)
596                {
597                    return Ok(d.normalize());
598                }
599                cell.as_decimal_str()?
600                    .parse::<rust_decimal::Decimal>()
601                    .map(|d| d.normalize())
602                    .map_err(|e| ConvertError::Convert(Box::new(e)))
603            }
604            other => Err(ConvertError::type_mismatch("string or number", &other)),
605        }
606    }
607}
608
609impl FromSql for Vec<u8> {
610    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
611        match value.inner {
612            SqlValueInner::String(s) => BASE64_STANDARD
613                .decode(s)
614                .map_err(|e| ConvertError::Convert(Box::new(e))),
615            SqlValueInner::Null => Err(ConvertError::NotNull),
616            #[cfg(any(
617                google_cloud_unstable_bigquery_arrow,
618                google_cloud_unstable_bigquery_storage_read
619            ))]
620            SqlValueInner::Arrow(cell) => cell.as_bytes().map(<[u8]>::to_vec),
621            other => Err(ConvertError::type_mismatch(
622                "string (base64 encoded)",
623                &other,
624            )),
625        }
626    }
627}
628
629impl FromSql for bytes::Bytes {
630    fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
631        Vec::<u8>::from_value(value).map(bytes::Bytes::from)
632    }
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638    use crate as google_cloud_bigquery;
639    use crate::query::FromSql;
640    use google_cloud_type::model::Decimal;
641    use rust_decimal::Decimal as RustDecimal;
642    use test_case::test_case;
643
644    impl SqlValue {
645        pub(crate) fn new(value: wkt::Value) -> Self {
646            Self::from_inner(SqlValueInner::from_wkt(value))
647        }
648    }
649
650    // Test-only representation of `ConvertError` that implements `PartialEq`.
651    // This allows testing error outcomes using `test_case` assertions without
652    // implementing `PartialEq` on the production `ConvertError`.
653    #[derive(Debug, PartialEq)]
654    enum TestConvertError {
655        NotNull,
656        TypeMismatch(String),
657        Convert,
658        MissingField(String),
659    }
660
661    impl TestConvertError {
662        fn type_mismatch(expected: &str) -> Self {
663            Self::TypeMismatch(expected.to_string())
664        }
665    }
666
667    impl From<ConvertError> for TestConvertError {
668        fn from(err: ConvertError) -> Self {
669            match err {
670                ConvertError::NotNull => Self::NotNull,
671                ConvertError::TypeMismatch { expected, .. } => Self::TypeMismatch(expected),
672                ConvertError::Convert(_) => Self::Convert,
673                ConvertError::MissingField(f) => Self::MissingField(f),
674            }
675        }
676    }
677
678    #[test]
679    fn test_sql_value_traits() {
680        static_assertions::assert_impl_all!(SqlValue: Send, Sync, Unpin, Clone, std::fmt::Debug);
681        static_assertions::assert_not_impl_any!(
682            SqlValue: std::panic::UnwindSafe,
683            std::panic::RefUnwindSafe
684        );
685    }
686
687    #[test_case(wkt::Value::Null => Ok(wkt::Value::Null) ; "value null")]
688    #[test_case(wkt::Value::Bool(true) => Ok(wkt::Value::Bool(true)) ; "value bool")]
689    #[test_case(wkt::Value::Number(42.into()) => Ok(wkt::Value::Number(42.into())) ; "value number")]
690    #[test_case(wkt::Value::String("hello".to_string()) => Ok(wkt::Value::String("hello".to_string())) ; "value string")]
691    #[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")]
692    #[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")]
693    fn test_from_sql_value(value: wkt::Value) -> Result<wkt::Value, TestConvertError> {
694        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
695    }
696
697    #[derive(FromSql, Debug, PartialEq)]
698    struct RawIdentSqlStruct {
699        r#type: String,
700        r#match: i64,
701    }
702
703    #[test]
704    fn test_derive_from_sql_raw_identifier() {
705        let val = wkt::Value::Object(wkt::Struct::from_iter([
706            ("type".to_string(), wkt::Value::String("event".to_string())),
707            ("match".to_string(), wkt::Value::Number(99.into())),
708        ]));
709        let parsed =
710            RawIdentSqlStruct::from_value(SqlValue::new(val)).expect("should strip r# prefix");
711        assert_eq!(
712            parsed,
713            RawIdentSqlStruct {
714                r#type: "event".to_string(),
715                r#match: 99,
716            }
717        );
718    }
719
720    #[test_case(wkt::Value::String("hello".to_string()) => Ok("hello".to_string()) ; "string")]
721    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null string")]
722    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "type mismatch string")]
723    fn test_from_sql_string(value: wkt::Value) -> Result<String, TestConvertError> {
724        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
725    }
726
727    #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i64 from number")]
728    #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i64 from string")]
729    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i64")]
730    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i64")]
731    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "invalid string as i64")]
732    fn test_from_sql_i64(value: wkt::Value) -> Result<i64, TestConvertError> {
733        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
734    }
735
736    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f64 from number")]
737    #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f64 from string")]
738    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f64")]
739    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f64")]
740    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "invalid string as f64")]
741    fn test_from_sql_f64(value: wkt::Value) -> Result<f64, TestConvertError> {
742        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
743    }
744
745    #[test_case(wkt::Value::Bool(true) => Ok(true) ; "bool true")]
746    #[test_case(wkt::Value::Bool(false) => Ok(false) ; "bool false")]
747    #[test_case(wkt::Value::String("true".to_string()) => Ok(true) ; "bool from string true")]
748    #[test_case(wkt::Value::String("false".to_string()) => Ok(false) ; "bool from string false")]
749    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bool")]
750    #[test_case(wkt::Value::Number(1.into()) => Err(TestConvertError::type_mismatch("bool or string")) ; "try number as bool")]
751    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "invalid string as bool")]
752    fn test_from_sql_bool(value: wkt::Value) -> Result<bool, TestConvertError> {
753        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
754    }
755
756    #[test_case(wkt::Value::Null => Ok(None) ; "option null")]
757    #[test_case(wkt::Value::Number(123.into()) => Ok(Some(123)) ; "option some i64")]
758    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "option error i64")]
759    fn test_from_sql_option(value: wkt::Value) -> Result<Option<i64>, TestConvertError> {
760        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
761    }
762
763    #[test_case(wkt::Value::Array(vec![wkt::Value::Number(1.into()), wkt::Value::Number(2.into())]) => Ok(vec![1, 2]) ; "vec i64")]
764    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec i64")]
765    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::type_mismatch("array")) ; "vec type mismatch")]
766    #[test_case(wkt::Value::Array(vec![wkt::Value::String("invalid".to_string())]) => Err(TestConvertError::Convert) ; "vec element convert error")]
767    fn test_from_sql_vec(value: wkt::Value) -> Result<Vec<i64>, TestConvertError> {
768        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
769    }
770
771    #[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")]
772    #[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")]
773    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "struct null")]
774    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "struct invalid json string")]
775    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "struct type mismatch")]
776    fn test_from_sql_struct(value: wkt::Value) -> Result<wkt::Struct, TestConvertError> {
777        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
778    }
779
780    #[test_case(wkt::Value::String("1779982200000000".to_string()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer string")]
781    #[test_case(wkt::Value::Number(1779982200000000i64.into()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer number")]
782    #[test_case(wkt::Value::String("2026-05-28T15:30:00Z".to_string()) => Err(TestConvertError::Convert) ; "timestamp rfc3339 string fails")]
783    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(1779982200.5).unwrap()) => Err(TestConvertError::Convert) ; "timestamp f64 number fails")]
784    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "timestamp null")]
785    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "timestamp type mismatch")]
786    fn test_from_sql_timestamp(value: wkt::Value) -> Result<wkt::Timestamp, TestConvertError> {
787        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
788    }
789
790    #[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")]
791    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null date")]
792    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "date type mismatch")]
793    #[test_case(wkt::Value::String("invalid-date".to_string()) => Err(TestConvertError::Convert) ; "date invalid format")]
794    #[test_case(wkt::Value::String("2026-abc-28".to_string()) => Err(TestConvertError::Convert) ; "date invalid digits")]
795    fn test_from_sql_date(
796        value: wkt::Value,
797    ) -> Result<google_cloud_type::model::Date, TestConvertError> {
798        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
799    }
800
801    #[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")]
802    #[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")]
803    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "time of day null")]
804    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "time of day type mismatch")]
805    fn test_from_sql_time_of_day(
806        value: wkt::Value,
807    ) -> Result<google_cloud_type::model::TimeOfDay, TestConvertError> {
808        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
809    }
810
811    #[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")]
812    #[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")]
813    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "datetime null")]
814    #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "datetime type mismatch")]
815    fn test_from_sql_datetime(
816        value: wkt::Value,
817    ) -> Result<google_cloud_type::model::DateTime, TestConvertError> {
818        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
819    }
820
821    #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i32 from number")]
822    #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i32 from string")]
823    #[test_case(wkt::Value::Number(3_000_000_000i64.into()) => Err(TestConvertError::Convert) ; "i32 overflow from number")]
824    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i32")]
825    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i32")]
826    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "invalid string as i32")]
827    fn test_from_sql_i32(value: wkt::Value) -> Result<i32, TestConvertError> {
828        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
829    }
830
831    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f32 from number")]
832    #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f32 from string")]
833    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f32")]
834    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f32")]
835    #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert) ; "invalid string as f32")]
836    fn test_from_sql_f32(value: wkt::Value) -> Result<f32, TestConvertError> {
837        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
838    }
839
840    #[test_case(wkt::Value::String("123.456".to_string()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from string")]
841    #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.456).unwrap()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from number")]
842    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null decimal")]
843    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as decimal")]
844    fn test_from_sql_decimal(value: wkt::Value) -> Result<Decimal, TestConvertError> {
845        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
846    }
847
848    #[test_case(wkt::Value::String("123.456".to_string()) => Ok(RustDecimal::from_str_exact("123.456").unwrap()) ; "rust_decimal from string")]
849    #[test_case(wkt::Value::Number((-123i64).into()) => Ok(RustDecimal::from(-123i64)) ; "rust_decimal from i64 number")]
850    #[test_case(wkt::Value::Number(u64::MAX.into()) => Ok(RustDecimal::from(u64::MAX)) ; "rust_decimal from u64 number")]
851    #[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")]
852    #[test_case(wkt::Value::String("99999999999999999999999999999999.123".to_string()) => Err(TestConvertError::Convert) ; "rust_decimal overflow")]
853    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null rust_decimal")]
854    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as rust_decimal")]
855    fn test_from_sql_rust_decimal(value: wkt::Value) -> Result<RustDecimal, TestConvertError> {
856        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
857    }
858
859    #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(vec![1, 2, 3, 4]) ; "vec u8 from base64")]
860    #[test_case(wkt::Value::String("".to_string()) => Ok(vec![]) ; "vec u8 from empty base64")]
861    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec u8")]
862    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as vec u8")]
863    fn test_from_sql_vec_u8(value: wkt::Value) -> Result<Vec<u8>, TestConvertError> {
864        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
865    }
866
867    #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(bytes::Bytes::from_static(&[1, 2, 3, 4])) ; "bytes from base64")]
868    #[test_case(wkt::Value::String("".to_string()) => Ok(bytes::Bytes::from_static(&[])) ; "bytes from empty base64")]
869    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bytes")]
870    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as bytes")]
871    fn test_from_sql_bytes(value: wkt::Value) -> Result<bytes::Bytes, TestConvertError> {
872        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
873    }
874
875    #[test_case("AQIDBA" ; "missing padding")]
876    #[test_case("Not a base64 string" ; "words with spaces")]
877    fn test_from_sql_bytes_invalid_base64(input: &str) {
878        let err = bytes::Bytes::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
879            .unwrap_err();
880        assert!(matches!(err, ConvertError::Convert(_)));
881
882        let err = Vec::<u8>::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
883            .unwrap_err();
884        assert!(matches!(err, ConvertError::Convert(_)));
885    }
886
887    #[derive(FromSql, Debug, PartialEq)]
888    struct TestSqlStruct {
889        name: String,
890        #[bigquery(rename = "custom_int")]
891        some_int: i64,
892        some_bool: bool,
893    }
894
895    #[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")]
896    #[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")]
897    #[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")]
898    #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null error")]
899    #[test_case(wkt::Value::String("invalid".to_string()) => Err(TestConvertError::Convert) ; "invalid json string")]
900    #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "type mismatch")]
901    fn test_derive_from_sql(value: wkt::Value) -> Result<TestSqlStruct, TestConvertError> {
902        FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
903    }
904
905    #[derive(FromSql, Debug, PartialEq)]
906    struct ShadowedSqlStruct {
907        iter: i64,
908        obj: i64,
909        arr: i64,
910        value: String,
911    }
912
913    #[test]
914    fn test_derive_from_sql_shadowing_field_names() {
915        let from_obj = ShadowedSqlStruct::from_value(SqlValue::new(wkt::Value::Object(
916            wkt::Struct::from_iter([
917                ("iter".to_string(), wkt::Value::Number(10.into())),
918                ("obj".to_string(), wkt::Value::Number(20.into())),
919                ("arr".to_string(), wkt::Value::Number(30.into())),
920                ("value".to_string(), wkt::Value::String("world".to_string())),
921            ]),
922        )))
923        .expect("should deserialize from object");
924        assert_eq!(
925            from_obj,
926            ShadowedSqlStruct {
927                iter: 10,
928                obj: 20,
929                arr: 30,
930                value: "world".to_string(),
931            }
932        );
933    }
934
935    #[test]
936    fn test_sql_value_take_by_index_and_name() -> anyhow::Result<()> {
937        let mut arr_val = SqlValue::new(wkt::Value::Array(vec![
938            wkt::Value::Number(42.into()),
939            wkt::Value::String("hello".to_string()),
940        ]));
941        assert_eq!(arr_val.take::<i64, _>(0)?, 42);
942        assert_eq!(arr_val.take::<String, _>(1)?, "hello");
943        assert!(matches!(
944            arr_val.take::<i64, _>(2),
945            Err(ConvertError::MissingField(ref f)) if f == "2"
946        ));
947
948        // Taking "foo" (at index 0) must replace slot 0 with Null in-place
949        // without shifting index 1 ("bar") down to index 0.
950        let mut struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
951            ("foo".to_string(), SqlValueInner::Number(10.into())),
952            (
953                "bar".to_string(),
954                SqlValueInner::String("twenty".to_string()),
955            ),
956        ]));
957        assert_eq!(struct_val.take::<i64, _>("foo".to_string())?, 10);
958        // Subsequent reads of "foo" see Null (returning Ok(None) for Option<T>, matching Row::take)
959        assert_eq!(struct_val.take::<Option<i64>, _>("foo")?, None);
960        // Index 1 is still "bar" (not shifted)
961        assert_eq!(struct_val.take::<String, _>(1)?, "twenty");
962
963        // Positional take(usize) on a JSON array string
964        let mut json_arr = SqlValue::new(wkt::Value::String(r#"[7, "eight"]"#.to_string()));
965        assert_eq!(json_arr.take::<i64, _>(0)?, 7);
966        assert_eq!(json_arr.take::<String, _>(1)?, "eight");
967
968        // Invalid JSON string with positional take(usize)
969        let mut invalid_json_arr = SqlValue::new(wkt::Value::String("not-json".to_string()));
970        assert!(matches!(
971            invalid_json_arr.take::<i64, _>(0),
972            Err(ConvertError::Convert(_))
973        ));
974
975        // Positional take(usize) on Null and primitive Bool
976        let mut null_val = SqlValue::new(wkt::Value::Null);
977        assert!(matches!(
978            null_val.take::<i64, _>(0),
979            Err(ConvertError::NotNull)
980        ));
981
982        let mut bool_val = SqlValue::new(wkt::Value::Bool(true));
983        assert!(matches!(
984            bool_val.take::<i64, _>(0),
985            Err(ConvertError::TypeMismatch { ref expected, ref got })
986                if expected == "struct, array, or string" && got == "bool"
987        ));
988
989        // Cover SqlValueInner::type_name for Null, Array, and Struct
990        assert_eq!(SqlValueInner::Null.type_name(), "null");
991        assert_eq!(SqlValueInner::Array(vec![]).type_name(), "array");
992        assert_eq!(SqlValueInner::Struct(vec![]).type_name(), "object");
993
994        Ok(())
995    }
996
997    #[derive(FromSql, Debug, PartialEq)]
998    struct GenericSql<T: Clone + Default, U: std::fmt::Debug> {
999        #[bigquery(rename = "custom_val")]
1000        single: T,
1001        optional: Option<T>,
1002        list: Vec<T>,
1003        nested: NestedGenericSql<U>,
1004    }
1005
1006    #[derive(FromSql, Debug, PartialEq)]
1007    struct NestedGenericSql<U> {
1008        inner_val: U,
1009    }
1010
1011    #[test]
1012    fn test_derive_from_sql_generic() {
1013        let nested_obj = wkt::Value::Object(wkt::Struct::from_iter([(
1014            "inner_val".to_string(),
1015            wkt::Value::String("hello".to_string()),
1016        )]));
1017        let from_obj = GenericSql::<i64, String>::from_value(SqlValue::new(wkt::Value::Object(
1018            wkt::Struct::from_iter([
1019                ("custom_val".to_string(), wkt::Value::Number(100.into())),
1020                ("optional".to_string(), wkt::Value::Null),
1021                (
1022                    "list".to_string(),
1023                    wkt::Value::Array(vec![
1024                        wkt::Value::Number(1.into()),
1025                        wkt::Value::Number(2.into()),
1026                        wkt::Value::Number(3.into()),
1027                    ]),
1028                ),
1029                ("nested".to_string(), nested_obj),
1030            ]),
1031        )))
1032        .expect("should deserialize generic from object");
1033
1034        assert_eq!(
1035            from_obj,
1036            GenericSql {
1037                single: 100,
1038                optional: None,
1039                list: vec![1, 2, 3],
1040                nested: NestedGenericSql {
1041                    inner_val: "hello".to_string(),
1042                },
1043            }
1044        );
1045    }
1046
1047    #[derive(FromSql, Debug, PartialEq)]
1048    struct GenericSqlWhere<T>
1049    where
1050        T: std::fmt::Debug + Clone,
1051    {
1052        val: T,
1053    }
1054
1055    #[test]
1056    fn test_derive_from_sql_generic_where_clause() {
1057        let val = GenericSqlWhere::<String>::from_value(SqlValue::new(wkt::Value::Object(
1058            wkt::Struct::from_iter([("val".to_string(), wkt::Value::String("hello".to_string()))]),
1059        )))
1060        .expect("should deserialize generic with where clause");
1061
1062        assert_eq!(
1063            val,
1064            GenericSqlWhere {
1065                val: "hello".to_string(),
1066            }
1067        );
1068    }
1069
1070    #[derive(FromSql, Debug, PartialEq)]
1071    struct GenericSqlDefault<T = i64> {
1072        val: T,
1073    }
1074
1075    #[test]
1076    fn test_derive_from_sql_generic_default_param() {
1077        let val: GenericSqlDefault =
1078            GenericSqlDefault::from_value(SqlValue::new(wkt::Value::Object(
1079                wkt::Struct::from_iter([("val".to_string(), wkt::Value::Number(42.into()))]),
1080            )))
1081            .expect("should deserialize generic with default type param");
1082
1083        assert_eq!(val, GenericSqlDefault { val: 42 });
1084    }
1085
1086    #[derive(FromSql, Debug, PartialEq)]
1087    struct GenericTupleSql<T, U>(T, Option<T>, U);
1088
1089    #[test]
1090    fn test_derive_from_sql_generic_tuple_struct() {
1091        let struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
1092            ("".to_string(), SqlValueInner::Number(100.into())),
1093            ("".to_string(), SqlValueInner::Null),
1094            ("".to_string(), SqlValueInner::String("world".to_string())),
1095        ]));
1096        let parsed = GenericTupleSql::<i64, String>::from_value(struct_val)
1097            .expect("should deserialize generic tuple struct from struct");
1098        assert_eq!(parsed, GenericTupleSql(100, None, "world".to_string()));
1099
1100        let json_val = SqlValue::new(wkt::Value::String(r#"[100, null, "world"]"#.to_string()));
1101        let parsed_json = GenericTupleSql::<i64, String>::from_value(json_val)
1102            .expect("should deserialize generic tuple struct from JSON array");
1103        assert_eq!(parsed_json, GenericTupleSql(100, None, "world".to_string()));
1104    }
1105
1106    #[cfg(any(
1107        google_cloud_unstable_bigquery_arrow,
1108        google_cloud_unstable_bigquery_storage_read
1109    ))]
1110    mod arrow_tests {
1111        use super::*;
1112        use arrow::array::{
1113            ArrayRef, BinaryArray, BooleanArray, Date32Array, Decimal128Array, Decimal256Array,
1114            Float32Array, Float64Array, Int32Array, Int64Array, LargeBinaryArray, LargeListArray,
1115            LargeStringArray, ListArray, StringArray, StructArray, Time64MicrosecondArray,
1116            Time64NanosecondArray, TimestampMicrosecondArray, TimestampMillisecondArray,
1117            TimestampNanosecondArray, TimestampSecondArray,
1118        };
1119        use arrow::buffer::{NullBuffer, OffsetBuffer};
1120        use arrow::datatypes::{DataType, Field, Fields, i256};
1121        use std::sync::Arc;
1122        use test_case::test_case;
1123
1124        fn arrow_val(arr: ArrayRef, row_idx: usize) -> SqlValue {
1125            SqlValue::from_inner(SqlValueInner::Arrow(ArrowCell::new(arr, row_idx)))
1126        }
1127
1128        fn sample_list_i64(values: Vec<Option<i64>>, nulls: Option<NullBuffer>) -> ArrayRef {
1129            let len = values.len();
1130            let val_arr: ArrayRef = Arc::new(Int64Array::from(values));
1131            let offsets = OffsetBuffer::from_lengths(vec![len]);
1132            Arc::new(ListArray::new(
1133                Arc::new(Field::new("item", DataType::Int64, true)),
1134                offsets,
1135                val_arr,
1136                nulls,
1137            ))
1138        }
1139
1140        fn sample_large_list_i64(values: Vec<Option<i64>>) -> ArrayRef {
1141            let len = values.len();
1142            let val_arr: ArrayRef = Arc::new(Int64Array::from(values));
1143            let offsets = OffsetBuffer::from_lengths(vec![len]);
1144            Arc::new(LargeListArray::new(
1145                Arc::new(Field::new("item", DataType::Int64, true)),
1146                offsets,
1147                val_arr,
1148                None,
1149            ))
1150        }
1151
1152        fn decimal128_array(values: Vec<Option<i128>>, precision: u8, scale: i8) -> ArrayRef {
1153            Arc::new(
1154                Decimal128Array::from(values)
1155                    .with_precision_and_scale(precision, scale)
1156                    .unwrap(),
1157            )
1158        }
1159
1160        fn decimal256_array(values: Vec<Option<i256>>, precision: u8, scale: i8) -> ArrayRef {
1161            Arc::new(
1162                Decimal256Array::from(values)
1163                    .with_precision_and_scale(precision, scale)
1164                    .unwrap(),
1165            )
1166        }
1167
1168        #[test_case(Arc::new(BooleanArray::from(vec![true])) => "Boolean" ; "arrow boolean")]
1169        #[test_case(Arc::new(Int64Array::from(vec![42])) => "Int64" ; "arrow int64")]
1170        #[test_case(Arc::new(Float64Array::from(vec![3.25])) => "Float64" ; "arrow float64")]
1171        #[test_case(Arc::new(StringArray::from(vec!["hello"])) => "Utf8" ; "arrow utf8")]
1172        fn test_sql_value_inner_arrow_type_name(arr: ArrayRef) -> String {
1173            let val = SqlValueInner::Arrow(ArrowCell::new(arr, 0));
1174            let err = ConvertError::type_mismatch("expected_type", &val);
1175            assert!(matches!(
1176                err,
1177                ConvertError::TypeMismatch { ref expected, ref got }
1178                    if expected == "expected_type" && got == &val.type_name()
1179            ));
1180            val.type_name()
1181        }
1182
1183        // TODO(#7032): Remove this test once wkt::Value conversion for Arrow cells is implemented.
1184        #[test]
1185        fn test_from_sql_value_arrow_not_implemented() {
1186            let arr: ArrayRef = Arc::new(Int64Array::from(vec![42]));
1187            let err = wkt::Value::from_value(arrow_val(arr, 0)).unwrap_err();
1188            assert!(matches!(err, ConvertError::Convert(_)));
1189            assert!(err.to_string().contains("not yet implemented"), "{err}");
1190        }
1191
1192        #[test_case(Arc::new(BooleanArray::from(vec![Some(true)])) => Ok(true) ; "bool true")]
1193        #[test_case(Arc::new(BooleanArray::from(vec![Some(false)])) => Ok(false) ; "bool false")]
1194        #[test_case(Arc::new(BooleanArray::from(vec![None])) => Err(TestConvertError::NotNull) ; "bool null")]
1195        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("BooleanArray")) ; "bool type mismatch")]
1196        fn test_from_sql_arrow_bool(arr: ArrayRef) -> Result<bool, TestConvertError> {
1197            bool::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1198        }
1199
1200        #[test_case(Arc::new(Int32Array::from(vec![Some(123)])) => Ok(123) ; "i32 from Int32Array")]
1201        #[test_case(Arc::new(Int64Array::from(vec![Some(456)])) => Ok(456) ; "i32 from Int64Array")]
1202        #[test_case(Arc::new(Int64Array::from(vec![Some(i64::from(i32::MAX) + 1)])) => Err(TestConvertError::Convert) ; "i32 overflow from Int64Array")]
1203        #[test_case(Arc::new(Int32Array::from(vec![None])) => Err(TestConvertError::NotNull) ; "i32 null")]
1204        #[test_case(Arc::new(BooleanArray::from(vec![true])) => Err(TestConvertError::type_mismatch("Int64Array or Int32Array")) ; "i32 type mismatch")]
1205        fn test_from_sql_arrow_i32(arr: ArrayRef) -> Result<i32, TestConvertError> {
1206            i32::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1207        }
1208
1209        #[test_case(Arc::new(Int64Array::from(vec![Some(42)])) => Ok(42) ; "i64 valid")]
1210        #[test_case(Arc::new(Int64Array::from(vec![None])) => Err(TestConvertError::NotNull) ; "i64 null")]
1211        #[test_case(Arc::new(BooleanArray::from(vec![true])) => Err(TestConvertError::type_mismatch("Int64Array")) ; "i64 type mismatch")]
1212        fn test_from_sql_arrow_i64(arr: ArrayRef) -> Result<i64, TestConvertError> {
1213            i64::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1214        }
1215
1216        #[test_case(Arc::new(Float32Array::from(vec![Some(1.5_f32)])) => Ok(1.5) ; "f32 from Float32Array")]
1217        #[test_case(Arc::new(Float64Array::from(vec![Some(2.5_f64)])) => Ok(2.5) ; "f32 from Float64Array")]
1218        #[test_case(Arc::new(Float32Array::from(vec![None])) => Err(TestConvertError::NotNull) ; "f32 null")]
1219        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("Float64Array or Float32Array")) ; "f32 type mismatch")]
1220        fn test_from_sql_arrow_f32(arr: ArrayRef) -> Result<f32, TestConvertError> {
1221            f32::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1222        }
1223
1224        #[test_case(Arc::new(Float64Array::from(vec![Some(3.25)])) => Ok(3.25) ; "f64 valid")]
1225        #[test_case(Arc::new(Float64Array::from(vec![None])) => Err(TestConvertError::NotNull) ; "f64 null")]
1226        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("Float64Array")) ; "f64 type mismatch")]
1227        fn test_from_sql_arrow_f64(arr: ArrayRef) -> Result<f64, TestConvertError> {
1228            f64::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1229        }
1230
1231        #[test_case(Arc::new(StringArray::from(vec![Some("hello")])) => Ok("hello".to_string()) ; "string from StringArray")]
1232        #[test_case(Arc::new(LargeStringArray::from(vec![Some("world")])) => Ok("world".to_string()) ; "string from LargeStringArray")]
1233        #[test_case(Arc::new(StringArray::from(vec![None::<&str>])) => Err(TestConvertError::NotNull) ; "string null")]
1234        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("StringArray or LargeStringArray")) ; "string type mismatch")]
1235        fn test_from_sql_arrow_string(arr: ArrayRef) -> Result<String, TestConvertError> {
1236            String::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1237        }
1238
1239        #[test_case(Arc::new(BinaryArray::from(vec![Some(b"abc".as_slice())])) => Ok(b"abc".to_vec()) ; "vec u8 from BinaryArray")]
1240        #[test_case(Arc::new(LargeBinaryArray::from(vec![Some(b"xyz".as_slice())])) => Ok(b"xyz".to_vec()) ; "vec u8 from LargeBinaryArray")]
1241        #[test_case(Arc::new(BinaryArray::from(vec![None::<&[u8]>])) => Err(TestConvertError::NotNull) ; "vec u8 null")]
1242        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("BinaryArray or LargeBinaryArray")) ; "vec u8 type mismatch")]
1243        fn test_from_sql_arrow_vec_u8(arr: ArrayRef) -> Result<Vec<u8>, TestConvertError> {
1244            Vec::<u8>::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1245        }
1246
1247        #[test_case(Arc::new(BinaryArray::from(vec![Some(b"abc".as_slice())])) => Ok(bytes::Bytes::from_static(b"abc")) ; "bytes from BinaryArray")]
1248        #[test_case(Arc::new(LargeBinaryArray::from(vec![Some(b"xyz".as_slice())])) => Ok(bytes::Bytes::from_static(b"xyz")) ; "bytes from LargeBinaryArray")]
1249        #[test_case(Arc::new(BinaryArray::from(vec![None::<&[u8]>])) => Err(TestConvertError::NotNull) ; "bytes null")]
1250        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("BinaryArray or LargeBinaryArray")) ; "bytes type mismatch")]
1251        fn test_from_sql_arrow_bytes(arr: ArrayRef) -> Result<bytes::Bytes, TestConvertError> {
1252            bytes::Bytes::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1253        }
1254
1255        #[test_case(decimal128_array(vec![Some(123_456)], 10, 3) => Ok(Decimal::new().set_value("123.456")) ; "decimal from Decimal128Array")]
1256        #[test_case(decimal128_array(vec![Some(-123_456)], 10, 3) => Ok(Decimal::new().set_value("-123.456")) ; "decimal negative from Decimal128Array")]
1257        #[test_case(decimal256_array(vec![Some(i256::from_i128(789_012))], 20, 3) => Ok(Decimal::new().set_value("789.012")) ; "decimal from Decimal256Array")]
1258        #[test_case(decimal128_array(vec![None], 10, 3) => Err(TestConvertError::NotNull) ; "decimal null Decimal128Array")]
1259        #[test_case(decimal256_array(vec![None], 20, 3) => Err(TestConvertError::NotNull) ; "decimal null Decimal256Array")]
1260        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("Decimal128Array or Decimal256Array")) ; "decimal type mismatch")]
1261        fn test_from_sql_arrow_decimal(arr: ArrayRef) -> Result<Decimal, TestConvertError> {
1262            Decimal::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1263        }
1264
1265        #[test_case(decimal128_array(vec![Some(123_456)], 10, 3) => Ok(RustDecimal::from_str_exact("123.456").unwrap()) ; "rust_decimal from Decimal128Array")]
1266        #[test_case(decimal128_array(vec![Some(-123_456)], 10, 3) => Ok(RustDecimal::from_str_exact("-123.456").unwrap()) ; "rust_decimal negative from Decimal128Array")]
1267        #[test_case(decimal128_array(vec![Some(100_000_000_000_000_000_000_000_000_000_i128)], 38, 9) => Ok(RustDecimal::from_str_exact("100000000000000000000").unwrap()) ; "rust_decimal from Decimal128Array with trailing zeros exceeding 96 bits unscaled")]
1268        #[test_case(decimal128_array(vec![Some(12)], 10, -2) => Ok(RustDecimal::from(1200)) ; "rust_decimal from Decimal128Array with negative scale")]
1269        #[test_case(decimal128_array(vec![Some(i128::MAX)], 38, 9) => Err(TestConvertError::Convert) ; "rust_decimal overflow from Decimal128Array")]
1270        #[test_case(decimal256_array(vec![Some(i256::from_string("123456789012345678900000000000000000000000000000").unwrap())], 76, 38) => Ok(RustDecimal::from_str_exact("1234567890.123456789").unwrap()) ; "rust_decimal from Decimal256Array with trailing zeros")]
1271        #[test_case(decimal256_array(vec![Some(i256::from_string("4200000000000000000000000000000000000000").unwrap())], 76, 38) => Ok(RustDecimal::from(42)) ; "rust_decimal from Decimal256Array integer with trailing zeros")]
1272        #[test_case(decimal256_array(vec![Some(i256::from_i128(12))], 20, -2) => Ok(RustDecimal::from(1200)) ; "rust_decimal from Decimal256Array without decimal point")]
1273        #[test_case(decimal256_array(vec![Some(i256::MAX)], 76, 38) => Err(TestConvertError::Convert) ; "rust_decimal overflow from Decimal256Array")]
1274        #[test_case(decimal128_array(vec![None], 10, 3) => Err(TestConvertError::NotNull) ; "rust_decimal null Decimal128Array")]
1275        #[test_case(decimal256_array(vec![None], 20, 3) => Err(TestConvertError::NotNull) ; "rust_decimal null Decimal256Array")]
1276        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("Decimal128Array or Decimal256Array")) ; "rust_decimal type mismatch")]
1277        fn test_from_sql_arrow_rust_decimal(
1278            arr: ArrayRef,
1279        ) -> Result<RustDecimal, TestConvertError> {
1280            RustDecimal::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1281        }
1282
1283        #[test_case(
1284            decimal128_array(vec![Some(42_500_000_000)], 38, 9)
1285            => ("42.5".to_string(), 1)
1286            ; "Decimal128 fractional trailing zeros normalized"
1287        )]
1288        #[test_case(
1289            decimal128_array(vec![Some(42_000_000_000)], 38, 9)
1290            => ("42".to_string(), 0)
1291            ; "Decimal128 integer trailing zeros normalized"
1292        )]
1293        #[test_case(
1294            decimal256_array(vec![Some(i256::from_string("4250000000000000000000000000000000000000").unwrap())], 76, 38)
1295            => ("42.5".to_string(), 1)
1296            ; "Decimal256 fractional trailing zeros normalized"
1297        )]
1298        fn test_from_sql_arrow_rust_decimal_scale(arr: ArrayRef) -> (String, u32) {
1299            let d = RustDecimal::from_value(arrow_val(arr, 0)).unwrap();
1300            (d.to_string(), d.scale())
1301        }
1302
1303        #[test_case(Arc::new(Int64Array::from(vec![Some(42)])) => Ok(Some(42)) ; "option some i64")]
1304        #[test_case(Arc::new(Int64Array::from(vec![None])) => Ok(None) ; "option null")]
1305        #[test_case(Arc::new(BooleanArray::from(vec![Some(true)])) => Err(TestConvertError::type_mismatch("Int64Array")) ; "option type mismatch")]
1306        fn test_from_sql_arrow_option(arr: ArrayRef) -> Result<Option<i64>, TestConvertError> {
1307            Option::<i64>::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1308        }
1309
1310        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![Some(1_779_982_200_123_456)])) => Ok(wkt::Timestamp::new(1_779_982_200, 123_456_000).unwrap()) ; "timestamp microsecond")]
1311        #[test_case(Arc::new(TimestampMillisecondArray::from(vec![Some(1_779_982_200_123)])) => Ok(wkt::Timestamp::new(1_779_982_200, 123_000_000).unwrap()) ; "timestamp millisecond")]
1312        #[test_case(Arc::new(TimestampNanosecondArray::from(vec![Some(1_779_982_200_123_456_789)])) => Ok(wkt::Timestamp::new(1_779_982_200, 123_456_000).unwrap()) ; "timestamp nanosecond")]
1313        #[test_case(Arc::new(TimestampSecondArray::from(vec![Some(1_779_982_200)])) => Ok(wkt::Timestamp::new(1_779_982_200, 0).unwrap()) ; "timestamp second")]
1314        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![Some(-1)])) => Ok(wkt::Timestamp::new(-1, 999_999_000).unwrap()) ; "timestamp pre epoch")]
1315        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![None])) => Err(TestConvertError::NotNull) ; "timestamp null")]
1316        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("TimestampArray")) ; "timestamp type mismatch")]
1317        fn test_from_sql_arrow_timestamp(
1318            arr: ArrayRef,
1319        ) -> Result<wkt::Timestamp, TestConvertError> {
1320            wkt::Timestamp::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1321        }
1322
1323        #[test_case(Arc::new(Date32Array::from(vec![Some(20_601)])) => Ok(google_cloud_type::model::Date::new().set_year(2026).set_month(5).set_day(28)) ; "date valid")]
1324        #[test_case(Arc::new(Date32Array::from(vec![Some(0)])) => Ok(google_cloud_type::model::Date::new().set_year(1970).set_month(1).set_day(1)) ; "date epoch")]
1325        #[test_case(Arc::new(Date32Array::from(vec![Some(i32::MAX)])) => Err(TestConvertError::Convert) ; "date out of range")]
1326        #[test_case(Arc::new(Date32Array::from(vec![None])) => Err(TestConvertError::NotNull) ; "date null")]
1327        #[test_case(Arc::new(Int32Array::from(vec![20_601])) => Err(TestConvertError::type_mismatch("Date32Array")) ; "date type mismatch")]
1328        fn test_from_sql_arrow_date(
1329            arr: ArrayRef,
1330        ) -> Result<google_cloud_type::model::Date, TestConvertError> {
1331            google_cloud_type::model::Date::from_value(arrow_val(arr, 0))
1332                .map_err(TestConvertError::from)
1333        }
1334
1335        #[test_case(Arc::new(Time64MicrosecondArray::from(vec![Some(55_800_000_000)])) => Ok(google_cloud_type::model::TimeOfDay::new().set_hours(15).set_minutes(30).set_seconds(0).set_nanos(0)) ; "time of day valid")]
1336        #[test_case(Arc::new(Time64MicrosecondArray::from(vec![Some(55_800_123_456)])) => 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")]
1337        #[test_case(Arc::new(Time64NanosecondArray::from(vec![Some(55_800_123_456_789)])) => Ok(google_cloud_type::model::TimeOfDay::new().set_hours(15).set_minutes(30).set_seconds(0).set_nanos(123_456_000)) ; "time of day nanosecond")]
1338        #[test_case(Arc::new(Time64MicrosecondArray::from(vec![Some(-1)])) => Err(TestConvertError::Convert) ; "time of day negative")]
1339        #[test_case(Arc::new(Time64MicrosecondArray::from(vec![Some(86_400_000_000)])) => Err(TestConvertError::Convert) ; "time of day 24h out of range")]
1340        #[test_case(Arc::new(Time64MicrosecondArray::from(vec![None])) => Err(TestConvertError::NotNull) ; "time of day null")]
1341        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("Time64MicrosecondArray")) ; "time of day type mismatch")]
1342        fn test_from_sql_arrow_time_of_day(
1343            arr: ArrayRef,
1344        ) -> Result<google_cloud_type::model::TimeOfDay, TestConvertError> {
1345            google_cloud_type::model::TimeOfDay::from_value(arrow_val(arr, 0))
1346                .map_err(TestConvertError::from)
1347        }
1348
1349        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![Some(1_779_982_200_000_000)])) => 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")]
1350        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![Some(1_779_982_200_123_456)])) => 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")]
1351        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![Some(i64::MAX)])) => Err(TestConvertError::Convert) ; "datetime out of range")]
1352        #[test_case(Arc::new(TimestampMicrosecondArray::from(vec![None])) => Err(TestConvertError::NotNull) ; "datetime null")]
1353        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("TimestampArray")) ; "datetime type mismatch")]
1354        fn test_from_sql_arrow_datetime(
1355            arr: ArrayRef,
1356        ) -> Result<google_cloud_type::model::DateTime, TestConvertError> {
1357            google_cloud_type::model::DateTime::from_value(arrow_val(arr, 0))
1358                .map_err(TestConvertError::from)
1359        }
1360
1361        #[test_case(sample_list_i64(vec![Some(10), Some(20), Some(30)], None) => Ok(vec![10, 20, 30]) ; "vec from ListArray")]
1362        #[test_case(sample_large_list_i64(vec![Some(40), Some(50)]) => Ok(vec![40, 50]) ; "vec from LargeListArray")]
1363        #[test_case(sample_list_i64(vec![], None) => Ok(vec![]) ; "vec empty ListArray")]
1364        #[test_case(sample_list_i64(vec![Some(10)], Some(NullBuffer::from(vec![false]))) => Err(TestConvertError::NotNull) ; "vec null ListArray")]
1365        #[test_case(sample_list_i64(vec![Some(10), None], None) => Err(TestConvertError::NotNull) ; "vec null element in non-optional Vec")]
1366        #[test_case(Arc::new(Int64Array::from(vec![1])) => Err(TestConvertError::type_mismatch("ListArray or LargeListArray")) ; "vec type mismatch")]
1367        fn test_from_sql_arrow_vec_i64(arr: ArrayRef) -> Result<Vec<i64>, TestConvertError> {
1368            Vec::<i64>::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1369        }
1370
1371        #[test_case(sample_list_i64(vec![Some(10), None, Some(30)], None) => Ok(vec![Some(10), None, Some(30)]) ; "vec option i64 with null element")]
1372        fn test_from_sql_arrow_vec_option_i64(
1373            arr: ArrayRef,
1374        ) -> Result<Vec<Option<i64>>, TestConvertError> {
1375            Vec::<Option<i64>>::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1376        }
1377
1378        #[test]
1379        fn test_sql_value_take_arrow_list_by_index() -> anyhow::Result<()> {
1380            let mut list_val = arrow_val(sample_list_i64(vec![Some(10), Some(20)], None), 0);
1381            assert_eq!(list_val.take::<i64, _>(0)?, 10);
1382            assert_eq!(list_val.take::<i64, _>(1)?, 20);
1383            assert!(matches!(
1384                list_val.take::<i64, _>(2),
1385                Err(ConvertError::MissingField(ref f)) if f == "2"
1386            ));
1387
1388            Ok(())
1389        }
1390
1391        fn sample_test_struct_array(nulls: Option<NullBuffer>) -> ArrayRef {
1392            let fields = Fields::from(vec![
1393                Field::new("name", DataType::Utf8, false),
1394                Field::new("custom_int", DataType::Int64, false),
1395                Field::new("some_bool", DataType::Boolean, false),
1396            ]);
1397            Arc::new(StructArray::new(
1398                fields,
1399                vec![
1400                    Arc::new(StringArray::from(vec!["James"])) as ArrayRef,
1401                    Arc::new(Int64Array::from(vec![272_793])) as ArrayRef,
1402                    Arc::new(BooleanArray::from(vec![true])) as ArrayRef,
1403                ],
1404                nulls,
1405            ))
1406        }
1407
1408        fn sample_missing_field_struct_array() -> ArrayRef {
1409            let fields = Fields::from(vec![
1410                Field::new("name", DataType::Utf8, false),
1411                Field::new("some_bool", DataType::Boolean, false),
1412            ]);
1413            Arc::new(StructArray::new(
1414                fields,
1415                vec![
1416                    Arc::new(StringArray::from(vec!["James"])) as ArrayRef,
1417                    Arc::new(BooleanArray::from(vec![true])) as ArrayRef,
1418                ],
1419                None,
1420            ))
1421        }
1422
1423        fn sample_tuple_struct_array(nulls: Option<NullBuffer>) -> ArrayRef {
1424            let fields = Fields::from(vec![
1425                Field::new("", DataType::Int64, false),
1426                Field::new("", DataType::Int64, true),
1427                Field::new("", DataType::Utf8, false),
1428            ]);
1429            Arc::new(StructArray::new(
1430                fields,
1431                vec![
1432                    Arc::new(Int64Array::from(vec![Some(100)])) as ArrayRef,
1433                    Arc::new(Int64Array::from(vec![None])) as ArrayRef,
1434                    Arc::new(StringArray::from(vec!["world"])) as ArrayRef,
1435                ],
1436                nulls,
1437            ))
1438        }
1439
1440        fn sample_short_tuple_struct_array() -> ArrayRef {
1441            let fields = Fields::from(vec![
1442                Field::new("", DataType::Int64, false),
1443                Field::new("", DataType::Int64, true),
1444            ]);
1445            Arc::new(StructArray::new(
1446                fields,
1447                vec![
1448                    Arc::new(Int64Array::from(vec![Some(100)])) as ArrayRef,
1449                    Arc::new(Int64Array::from(vec![None])) as ArrayRef,
1450                ],
1451                None,
1452            ))
1453        }
1454
1455        #[test_case(sample_test_struct_array(None) => Ok(TestSqlStruct { name: "James".to_string(), some_int: 272_793, some_bool: true }) ; "struct from StructArray")]
1456        #[test_case(Arc::new(StringArray::from(vec![r#"{"name": "James", "custom_int": 272793, "some_bool": true}"#])) => Ok(TestSqlStruct { name: "James".to_string(), some_int: 272_793, some_bool: true }) ; "struct from StringArray json")]
1457        #[test_case(Arc::new(LargeStringArray::from(vec![r#"{"name": "James", "custom_int": 272793, "some_bool": true}"#])) => Ok(TestSqlStruct { name: "James".to_string(), some_int: 272_793, some_bool: true }) ; "struct from LargeStringArray json")]
1458        #[test_case(sample_missing_field_struct_array() => Err(TestConvertError::MissingField("custom_int".to_string())) ; "struct missing field")]
1459        #[test_case(sample_test_struct_array(Some(NullBuffer::from(vec![false]))) => Err(TestConvertError::NotNull) ; "struct null StructArray")]
1460        #[test_case(Arc::new(StringArray::from(vec!["invalid"])) => Err(TestConvertError::Convert) ; "struct invalid json string")]
1461        #[test_case(Arc::new(BooleanArray::from(vec![true])) => Err(TestConvertError::type_mismatch("object or string")) ; "struct type mismatch")]
1462        fn test_derive_from_sql_arrow_named_struct(
1463            arr: ArrayRef,
1464        ) -> Result<TestSqlStruct, TestConvertError> {
1465            TestSqlStruct::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1466        }
1467
1468        #[test_case(sample_tuple_struct_array(None) => Ok(GenericTupleSql(100, None, "world".to_string())) ; "tuple struct from StructArray")]
1469        #[test_case(Arc::new(StringArray::from(vec![r#"[100, null, "world"]"#])) => Ok(GenericTupleSql(100, None, "world".to_string())) ; "tuple struct from StringArray json")]
1470        #[test_case(sample_short_tuple_struct_array() => Err(TestConvertError::MissingField("2".to_string())) ; "tuple struct missing field")]
1471        #[test_case(sample_tuple_struct_array(Some(NullBuffer::from(vec![false]))) => Err(TestConvertError::NotNull) ; "tuple struct null StructArray")]
1472        #[test_case(Arc::new(BooleanArray::from(vec![true])) => Err(TestConvertError::type_mismatch("struct, array, or string")) ; "tuple struct type mismatch")]
1473        fn test_derive_from_sql_arrow_tuple_struct(
1474            arr: ArrayRef,
1475        ) -> Result<GenericTupleSql<i64, String>, TestConvertError> {
1476            GenericTupleSql::<i64, String>::from_value(arrow_val(arr, 0))
1477                .map_err(TestConvertError::from)
1478        }
1479
1480        #[test_case(Arc::new(StringArray::from(vec![Some(r#"{"a": 1}"#)])) => Ok(wkt::Struct::from_iter([("a".to_string(), wkt::Value::Number(1.into()))])) ; "wkt struct from StringArray")]
1481        #[test_case(Arc::new(LargeStringArray::from(vec![Some(r#"{"a": 1}"#)])) => Ok(wkt::Struct::from_iter([("a".to_string(), wkt::Value::Number(1.into()))])) ; "wkt struct from LargeStringArray")]
1482        #[test_case(Arc::new(StringArray::from(vec![None::<&str>])) => Err(TestConvertError::NotNull) ; "wkt struct null")]
1483        #[test_case(Arc::new(StringArray::from(vec![Some("invalid")])) => Err(TestConvertError::Convert) ; "wkt struct invalid json")]
1484        #[test_case(Arc::new(BooleanArray::from(vec![true])) => Err(TestConvertError::type_mismatch("StringArray or LargeStringArray")) ; "wkt struct type mismatch")]
1485        fn test_from_sql_arrow_wkt_struct(arr: ArrayRef) -> Result<wkt::Struct, TestConvertError> {
1486            wkt::Struct::from_value(arrow_val(arr, 0)).map_err(TestConvertError::from)
1487        }
1488    }
1489}