Skip to main content

google_cloud_bigquery/query/
row.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 crate::error::{ConvertError, RowError};
16#[cfg(any(
17    google_cloud_unstable_bigquery_arrow,
18    google_cloud_unstable_bigquery_storage_read
19))]
20use crate::query::from_sql::ArrowCell;
21use crate::query::from_sql::SqlValueInner;
22use crate::query::{FromSql, Schema};
23#[cfg(any(
24    google_cloud_unstable_bigquery_arrow,
25    google_cloud_unstable_bigquery_storage_read
26))]
27use arrow::record_batch::RecordBatch;
28use google_cloud_bigquery_v2::model::TableFieldSchema;
29use std::sync::Arc;
30use wkt::{ListValue, Struct, Value};
31
32pub type Result<T> = std::result::Result<T, RowError>;
33
34/// A container for a single row within a query result set.
35///
36/// [`RowIterator::next()`](crate::query::RowIterator::next) yields a `Row`.
37///
38/// Each `Row` contains parsed cell values and a reference to the table schema.
39///
40/// # Zero-Copy Struct Mapping via Derive Macros
41///
42/// Define typed structs with `#[derive(FromRow)]` to convert rows directly into
43/// your domain types using `TryFrom<Row>` without unnecessary allocations:
44///
45/// ```
46/// # use google_cloud_bigquery::query::{Row, FromRow};
47/// #[derive(FromRow, Debug)]
48/// struct UserStats {
49///     name: String,
50///     count: i64,
51/// }
52///
53/// # fn sample(row: Row) -> anyhow::Result<()> {
54/// let user: UserStats = row.try_into()?;
55/// println!("{}: {}", user.name, user.count);
56/// # Ok(())
57/// # }
58/// ```
59///
60/// # Field Extraction by Name or Index
61///
62/// Retrieve individual cell values by column name (`&str`) or index (`usize`)
63/// using [`get()`](Row::get) or [`take()`](Row::take):
64///
65/// ```
66/// # use google_cloud_bigquery::query::Row;
67/// # fn sample(row: Row) -> anyhow::Result<()> {
68/// let name: String = row.get("name")?;
69/// let age: i64 = row.get(1)?;
70/// println!("{name} is {age} years old");
71/// # Ok(())
72/// # }
73/// ```
74#[derive(Clone, Debug)]
75pub struct Row {
76    pub(crate) inner: RowInner,
77    pub(crate) schema: Arc<Schema>,
78}
79
80#[derive(Clone, Debug)]
81pub(crate) enum RowInner {
82    Json(Vec<SqlValueInner>),
83    #[cfg(any(
84        google_cloud_unstable_bigquery_arrow,
85        google_cloud_unstable_bigquery_storage_read
86    ))]
87    Arrow {
88        batch: Arc<RecordBatch>,
89        row_idx: usize,
90    },
91}
92
93mod sealed {
94    use super::{Row, SqlValueInner};
95    use crate::error::ConvertError;
96    use crate::query::SqlValue;
97
98    /// A sealed trait to prevent external implementation of `ColumnIndex`.
99    pub trait ColumnIndex {
100        /// Returns the index of the column in the given row, if it exists.
101        fn index(&self, row: &Row) -> Option<usize>;
102
103        /// Returns the index of the field in the given Arrow `StructArray`, if it exists.
104        #[cfg(any(
105            google_cloud_unstable_bigquery_arrow,
106            google_cloud_unstable_bigquery_storage_read
107        ))]
108        fn arrow_index(&self, struct_arr: &arrow::array::StructArray) -> Option<usize>;
109
110        /// Takes a value by column index or field name from `SqlValue`.
111        fn take_sql_value(
112            &self,
113            value: &mut SqlValue,
114        ) -> std::result::Result<SqlValue, ConvertError>;
115    }
116
117    impl ColumnIndex for usize {
118        fn index(&self, row: &Row) -> Option<usize> {
119            row.schema.get_field_by_index(*self).map(|_| *self)
120        }
121
122        #[cfg(any(
123            google_cloud_unstable_bigquery_arrow,
124            google_cloud_unstable_bigquery_storage_read
125        ))]
126        fn arrow_index(&self, struct_arr: &arrow::array::StructArray) -> Option<usize> {
127            (*self < struct_arr.num_columns()).then_some(*self)
128        }
129
130        fn take_sql_value(
131            &self,
132            value: &mut SqlValue,
133        ) -> std::result::Result<SqlValue, ConvertError> {
134            match &mut value.inner {
135                SqlValueInner::Struct(entries) => {
136                    let (_, slot) = entries
137                        .get_mut(*self)
138                        .ok_or_else(|| ConvertError::MissingField(self.to_string()))?;
139                    Ok(SqlValue::from_inner(std::mem::replace(
140                        slot,
141                        SqlValueInner::Null,
142                    )))
143                }
144                SqlValueInner::Array(arr) => {
145                    let slot = arr
146                        .get_mut(*self)
147                        .ok_or_else(|| ConvertError::MissingField(self.to_string()))?;
148                    Ok(SqlValue::from_inner(std::mem::replace(
149                        slot,
150                        SqlValueInner::Null,
151                    )))
152                }
153                SqlValueInner::String(s) => {
154                    let arr: Vec<wkt::Value> =
155                        serde_json::from_str(s).map_err(|e| ConvertError::Convert(Box::new(e)))?;
156                    value.inner = SqlValueInner::from_wkt(wkt::Value::Array(arr));
157                    self.take_sql_value(value)
158                }
159                SqlValueInner::Null => Err(ConvertError::NotNull),
160                #[cfg(any(
161                    google_cloud_unstable_bigquery_arrow,
162                    google_cloud_unstable_bigquery_storage_read
163                ))]
164                SqlValueInner::Arrow(cell) => match cell.data_type() {
165                    arrow::datatypes::DataType::Struct(_) => cell
166                        .struct_field_cell(self)
167                        .map(|val| SqlValue::from_inner(SqlValueInner::Arrow(val))),
168                    arrow::datatypes::DataType::List(_)
169                    | arrow::datatypes::DataType::LargeList(_) => cell
170                        .list_element_cell(*self)
171                        .map(|val| SqlValue::from_inner(SqlValueInner::Arrow(val))),
172                    arrow::datatypes::DataType::Utf8 | arrow::datatypes::DataType::LargeUtf8 => {
173                        let s = cell.as_str()?.to_string();
174                        value.inner = SqlValueInner::String(s);
175                        self.take_sql_value(value)
176                    }
177                    _ => Err(ConvertError::TypeMismatch {
178                        expected: "struct, array, or string".to_string(),
179                        got: cell.data_type_str(),
180                    }),
181                },
182                other => Err(ConvertError::type_mismatch(
183                    "struct, array, or string",
184                    other,
185                )),
186            }
187        }
188    }
189
190    impl ColumnIndex for &str {
191        fn index(&self, row: &Row) -> Option<usize> {
192            row.schema.get_field_index_by_name(self)
193        }
194
195        #[cfg(any(
196            google_cloud_unstable_bigquery_arrow,
197            google_cloud_unstable_bigquery_storage_read
198        ))]
199        fn arrow_index(&self, struct_arr: &arrow::array::StructArray) -> Option<usize> {
200            struct_arr.fields().iter().position(|f| f.name() == *self)
201        }
202
203        fn take_sql_value(
204            &self,
205            value: &mut SqlValue,
206        ) -> std::result::Result<SqlValue, ConvertError> {
207            match &mut value.inner {
208                SqlValueInner::Struct(entries) => {
209                    let (_, slot) = entries
210                        .iter_mut()
211                        .find(|(name, _)| name == *self)
212                        .ok_or_else(|| ConvertError::MissingField((*self).to_string()))?;
213                    Ok(SqlValue::from_inner(std::mem::replace(
214                        slot,
215                        SqlValueInner::Null,
216                    )))
217                }
218                SqlValueInner::String(s) => {
219                    let obj: wkt::Struct =
220                        serde_json::from_str(s).map_err(|e| ConvertError::Convert(Box::new(e)))?;
221                    value.inner = SqlValueInner::from_wkt(wkt::Value::Object(obj));
222                    self.take_sql_value(value)
223                }
224                SqlValueInner::Null => Err(ConvertError::NotNull),
225                #[cfg(any(
226                    google_cloud_unstable_bigquery_arrow,
227                    google_cloud_unstable_bigquery_storage_read
228                ))]
229                SqlValueInner::Arrow(cell) => match cell.data_type() {
230                    arrow::datatypes::DataType::Struct(_) => cell
231                        .struct_field_cell(self)
232                        .map(|val| SqlValue::from_inner(SqlValueInner::Arrow(val))),
233                    arrow::datatypes::DataType::Utf8 | arrow::datatypes::DataType::LargeUtf8 => {
234                        let s = cell.as_str()?.to_string();
235                        value.inner = SqlValueInner::String(s);
236                        self.take_sql_value(value)
237                    }
238                    _ => Err(ConvertError::TypeMismatch {
239                        expected: "object or string".to_string(),
240                        got: cell.data_type_str(),
241                    }),
242                },
243                other => Err(ConvertError::type_mismatch("object or string", other)),
244            }
245        }
246    }
247
248    impl ColumnIndex for String {
249        fn index(&self, row: &Row) -> Option<usize> {
250            <&str as ColumnIndex>::index(&self.as_str(), row)
251        }
252
253        #[cfg(any(
254            google_cloud_unstable_bigquery_arrow,
255            google_cloud_unstable_bigquery_storage_read
256        ))]
257        fn arrow_index(&self, struct_arr: &arrow::array::StructArray) -> Option<usize> {
258            self.as_str().arrow_index(struct_arr)
259        }
260
261        fn take_sql_value(
262            &self,
263            value: &mut SqlValue,
264        ) -> std::result::Result<SqlValue, ConvertError> {
265            self.as_str().take_sql_value(value)
266        }
267    }
268}
269
270/// A trait for types that can be used to index into a [`Row`] or [`SqlValue`](crate::query::SqlValue).
271///
272/// This trait is sealed and cannot be implemented for types outside of this crate.
273pub trait ColumnIndex: sealed::ColumnIndex + std::fmt::Display {}
274
275impl ColumnIndex for usize {}
276impl ColumnIndex for &str {}
277impl ColumnIndex for String {}
278
279impl Row {
280    pub(crate) fn try_new(row: Struct, schema: &Arc<Schema>) -> Result<Self> {
281        let values = convert_row(row, schema.fields())?;
282
283        Ok(Self {
284            inner: RowInner::Json(values),
285            schema: schema.clone(),
286        })
287    }
288
289    #[cfg(any(
290        google_cloud_unstable_bigquery_arrow,
291        google_cloud_unstable_bigquery_storage_read
292    ))]
293    pub(crate) fn try_new_from_arrow(
294        batch: &Arc<RecordBatch>,
295        row_idx: usize,
296        schema: &Arc<Schema>,
297    ) -> Result<Self> {
298        if batch.num_columns() != schema.len() {
299            return Err(RowError::InvalidRowFormat(format!(
300                "schema and row cell mismatch (expected {}, got {})",
301                schema.len(),
302                batch.num_columns()
303            )));
304        }
305        if row_idx >= batch.num_rows() {
306            return Err(RowError::InvalidRowFormat(format!(
307                "row index out of bounds (row_idx {}, num_rows {})",
308                row_idx,
309                batch.num_rows()
310            )));
311        }
312
313        Ok(Self {
314            inner: RowInner::Arrow {
315                batch: Arc::clone(batch),
316                row_idx,
317            },
318            schema: schema.clone(),
319        })
320    }
321
322    fn resolve_index<I: ColumnIndex>(&self, col: &I) -> Result<usize> {
323        sealed::ColumnIndex::index(col, self)
324            .ok_or_else(|| RowError::ColumnNotFound(format!("{col}")))
325    }
326
327    fn convert_value_at<T: FromSql>(&self, idx: usize, val: SqlValueInner) -> Result<T> {
328        T::from_value(crate::query::SqlValue::from_inner(val)).map_err(|e| {
329            let (column, sql_type) = self
330                .schema
331                .get_field_by_index(idx)
332                .map(|f| (f.name.clone(), f.r#type.clone()))
333                .unwrap_or_else(|| (idx.to_string(), "UNKNOWN".to_string()));
334            RowError::TypeConversion {
335                column,
336                sql_type,
337                source: e,
338            }
339        })
340    }
341
342    /// Retrieves a value from the row by column name or zero-based index.
343    ///
344    /// The return type must implement [`FromSql`](crate::query::FromSql).
345    ///
346    /// The cell value is cloned from the row data without modifying `self`. If
347    /// you want to take ownership and avoid cloning large values, see
348    /// [`take()`](Row::take).
349    ///
350    /// # Example
351    ///
352    /// ```
353    /// # use google_cloud_bigquery::query::Row;
354    /// # fn sample(row: Row) -> anyhow::Result<()> {
355    /// let msg: String = row.get("msg")?;
356    /// println!("Value: {msg}");
357    /// # Ok(())
358    /// # }
359    /// ```
360    pub fn get<T: FromSql>(&self, index: impl ColumnIndex) -> Result<T> {
361        let idx = self.resolve_index(&index)?;
362        match &self.inner {
363            RowInner::Json(values) => {
364                let val = values.get(idx).ok_or_else(|| RowError::IndexOutOfRange {
365                    index: idx,
366                    len: self.schema.len(),
367                })?;
368                self.convert_value_at(idx, val.clone())
369            }
370            #[cfg(any(
371                google_cloud_unstable_bigquery_arrow,
372                google_cloud_unstable_bigquery_storage_read
373            ))]
374            RowInner::Arrow { batch, row_idx } => {
375                let col = batch
376                    .columns()
377                    .get(idx)
378                    .ok_or_else(|| RowError::IndexOutOfRange {
379                        index: idx,
380                        len: self.schema.len(),
381                    })?;
382                let val = SqlValueInner::Arrow(ArrowCell::new(col.clone(), *row_idx));
383                self.convert_value_at(idx, val)
384            }
385        }
386    }
387
388    /// Takes ownership of a value from the row by column name or zero-based
389    /// index.
390    ///
391    /// This method mutates `self` by extracting the cell value in-place to
392    /// avoid cloning. The extracted cell in the row is replaced with `NULL`.
393    /// Subsequent attempts to read or take the column will treat it as `NULL`
394    /// (returning `Ok(None)` when reading into `Option<T>`, or returning a
395    /// type conversion error for non-nullable types).
396    ///
397    /// <div class="warning">
398    ///
399    /// `take()` removes the value from `self` before converting it to `T`. If
400    /// type conversion fails and returns an error, the original value has
401    /// already been consumed and cannot be recovered from the row.
402    ///
403    /// </div>
404    ///
405    /// If you are not sure of the column's type or need to read the value
406    /// multiple times, use [`get()`](Row::get) instead of `take()`. Use `take()`
407    /// when you are confident of the type and want to avoid cloning large values
408    /// (such as strings, byte buffers, or nested records).
409    ///
410    /// # Example
411    ///
412    /// ```
413    /// # use google_cloud_bigquery::query::Row;
414    /// # fn sample(mut row: Row) -> anyhow::Result<()> {
415    /// let text: String = row.take("big_text")?;
416    /// println!("Length: {}", text.len());
417    ///
418    /// // Subsequent reads treat the column as NULL:
419    /// assert_eq!(row.get::<Option<String>>("big_text")?, None);
420    ///
421    /// // Attempting to read or take again as a non-nullable type fails:
422    /// assert!(row.take::<String>("big_text").is_err());
423    /// # Ok(())
424    /// # }
425    /// ```
426    pub fn take<T: FromSql>(&mut self, index: impl ColumnIndex) -> Result<T> {
427        let idx = self.resolve_index(&index)?;
428        match &mut self.inner {
429            RowInner::Json(values) => {
430                let val = values
431                    .get_mut(idx)
432                    .ok_or_else(|| RowError::IndexOutOfRange {
433                        index: idx,
434                        len: self.schema.len(),
435                    })?;
436
437                // swap out the value in-place to avoid clones
438                let owned_val = std::mem::replace(val, SqlValueInner::Null);
439                self.convert_value_at(idx, owned_val)
440            }
441            #[cfg(any(
442                google_cloud_unstable_bigquery_arrow,
443                google_cloud_unstable_bigquery_storage_read
444            ))]
445            RowInner::Arrow { batch, row_idx } => {
446                let col = batch
447                    .columns()
448                    .get(idx)
449                    .ok_or_else(|| RowError::IndexOutOfRange {
450                        index: idx,
451                        len: self.schema.len(),
452                    })?;
453                let val = SqlValueInner::Arrow(ArrowCell::new(col.clone(), *row_idx));
454                self.convert_value_at(idx, val)
455            }
456        }
457    }
458}
459
460fn convert_row(row: Struct, fields: &[TableFieldSchema]) -> Result<Vec<SqlValueInner>> {
461    let field_list = get_field_list(row)?;
462
463    if field_list.len() != fields.len() {
464        return Err(RowError::InvalidRowFormat(format!(
465            "schema and row cell mismatch (expected {}, got {})",
466            fields.len(),
467            field_list.len()
468        )));
469    }
470
471    field_list
472        .into_iter()
473        .zip(fields)
474        .map(|(cell, field)| convert_value(get_field_value(cell)?, field))
475        .collect()
476}
477
478fn get_field_list(mut row: Struct) -> Result<Vec<Value>> {
479    match row.remove("f") {
480        Some(Value::Array(arr)) => Ok(arr),
481        Some(_) => Err(RowError::InvalidRowFormat("invalid field values".into())),
482        None => Err(RowError::InvalidRowFormat("missing field values".into())),
483    }
484}
485
486fn get_field_value(value: Value) -> Result<Value> {
487    match value {
488        Value::Object(mut obj) => match obj.remove("v") {
489            Some(val) => Ok(val),
490            None => Err(RowError::InvalidRowFormat("missing field value".into())),
491        },
492        _ => Err(RowError::InvalidRowFormat("invalid field value".into())),
493    }
494}
495
496fn convert_value(value: Value, field: &TableFieldSchema) -> Result<SqlValueInner> {
497    match value {
498        Value::Null => Ok(SqlValueInner::Null),
499        Value::String(v) => convert_basic_type(v, &field.name, &field.r#type),
500        Value::Object(v) => convert_nested(v, &field.fields),
501        Value::Array(v) => convert_repeated(v, field),
502        _ => Err(RowError::InvalidRowFormat(format!(
503            "cell value is not an object: value={:?}, field_type={:?}",
504            value, field.r#type
505        ))),
506    }
507}
508
509fn convert_repeated(value: ListValue, field: &TableFieldSchema) -> Result<SqlValueInner> {
510    let arr = value
511        .into_iter()
512        .map(|cell| {
513            // each cell contains a single entry, keyed by "v"
514            let val = get_field_value(cell)?;
515            convert_value(val, field)
516        })
517        .collect::<Result<Vec<_>>>()?;
518    Ok(SqlValueInner::Array(arr))
519}
520
521fn convert_nested(value: Struct, fields: &[TableFieldSchema]) -> Result<SqlValueInner> {
522    let values = convert_row(value, fields)?;
523    let entries = fields
524        .iter()
525        .zip(values)
526        .map(|(field, value)| (field.name.clone(), value))
527        .collect();
528    Ok(SqlValueInner::Struct(entries))
529}
530
531fn convert_basic_type(value: String, field_name: &str, field_type: &str) -> Result<SqlValueInner> {
532    match field_type {
533        "STRING" | "BYTES" | "TIMESTAMP" | "DATE" | "TIME" | "DATETIME" | "NUMERIC"
534        | "BIGNUMERIC" | "BIGINT" | "GEOGRAPHY" | "JSON" | "INTERVAL" | "RANGE" => {
535            Ok(SqlValueInner::String(value))
536        }
537        "INTEGER" | "INT64" => {
538            let num = value.parse::<i64>().map_err(|e| RowError::TypeConversion {
539                column: field_name.to_string(),
540                sql_type: field_type.to_string(),
541                source: ConvertError::Convert(Box::new(e)),
542            })?;
543            Ok(SqlValueInner::Number(serde_json::Number::from(num)))
544        }
545        "FLOAT" | "FLOAT64" => {
546            let num = value.parse::<f64>().map_err(|e| RowError::TypeConversion {
547                column: field_name.to_string(),
548                sql_type: field_type.to_string(),
549                source: ConvertError::Convert(Box::new(e)),
550            })?;
551            match serde_json::Number::from_f64(num) {
552                Some(n) => Ok(SqlValueInner::Number(n)),
553                None => Ok(SqlValueInner::String(value)),
554            }
555        }
556        "BOOLEAN" | "BOOL" => {
557            let b = if value.eq_ignore_ascii_case("true") {
558                true
559            } else if value.eq_ignore_ascii_case("false") {
560                false
561            } else {
562                return Err(RowError::TypeConversion {
563                    column: field_name.to_string(),
564                    sql_type: field_type.to_string(),
565                    source: ConvertError::Convert(
566                        "provided string was not `true` or `false`".into(),
567                    ),
568                });
569            };
570            Ok(SqlValueInner::Bool(b))
571        }
572        _ => Err(RowError::InvalidRowFormat(format!(
573            "unknown field type: {} at column {}",
574            field_type, field_name
575        ))),
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582    use crate as google_cloud_bigquery;
583    use crate::query::FromRow;
584    use google_cloud_bigquery_v2::model::{TableFieldSchema, TableSchema};
585    use google_cloud_type::model::Decimal;
586    use rust_decimal::Decimal as RustDecimal;
587    use serde_json::{Map, json};
588    use test_case::test_case;
589
590    type TestResult = anyhow::Result<()>;
591
592    #[tokio::test]
593    async fn convert_basic_types_from_row() -> TestResult {
594        let raw_row = Map::from_iter([(
595            "f".to_string(),
596            json!([
597                { "v": "James" },
598                { "v": "272793" },
599                { "v": "TRUE" },
600                { "v": null },
601                { "v": "64.0" },
602            ]),
603        )]);
604        let schema = TableSchema::new().set_fields([
605            TableFieldSchema::new()
606                .set_name("name")
607                .set_type("STRING")
608                .set_mode("NULLABLE"),
609            TableFieldSchema::new()
610                .set_name("some_int")
611                .set_type("INTEGER")
612                .set_mode("NULLABLE"),
613            TableFieldSchema::new()
614                .set_name("some_bool")
615                .set_type("BOOLEAN")
616                .set_mode("NULLABLE"),
617            TableFieldSchema::new()
618                .set_name("some_null")
619                .set_type("INTEGER")
620                .set_mode("NULLABLE"),
621            TableFieldSchema::new()
622                .set_name("some_float")
623                .set_type("FLOAT64")
624                .set_mode("NULLABLE"),
625        ]);
626        let schema = Arc::new(Schema::new(schema));
627        let mut row = Row::try_new(raw_row, &schema)?;
628
629        assert_eq!(row.get::<String>(0)?, "James");
630        assert_eq!(row.get::<String>("name")?, "James");
631
632        assert_eq!(row.get::<i32>(1)?, 272793);
633        assert_eq!(row.get::<i32>("some_int")?, 272793);
634        assert_eq!(row.get::<i64>(1)?, 272793);
635        assert_eq!(row.get::<i64>("some_int")?, 272793);
636
637        assert!(row.get::<bool>(2)?);
638        assert!(row.get::<bool>("some_bool")?);
639
640        assert_eq!(row.get::<Option<i64>>(3)?, None);
641        assert_eq!(row.get::<Option<i64>>("some_null")?, None);
642
643        assert_eq!(row.get::<f32>(4)?, 64.0);
644        assert_eq!(row.get::<f32>("some_float")?, 64.0);
645        assert_eq!(row.get::<f64>(4)?, 64.0);
646        assert_eq!(row.get::<f64>("some_float")?, 64.0);
647
648        assert_eq!(row.take::<String>(0)?, "James");
649        assert_eq!(row.get::<Option<String>>(0)?, None);
650
651        assert_eq!(row.take::<i32>(1)?, 272793);
652        assert_eq!(row.get::<Option<i32>>(1)?, None);
653
654        assert!(row.take::<bool>(2)?);
655        assert_eq!(row.get::<Option<bool>>(2)?, None);
656
657        assert_eq!(row.take::<Option<i64>>(3)?, None);
658        assert_eq!(row.get::<Option<i64>>(3)?, None);
659
660        assert_eq!(row.take::<f32>(4)?, 64.0);
661        assert_eq!(row.get::<Option<f32>>(4)?, None);
662
663        Ok(())
664    }
665
666    #[tokio::test]
667    async fn convert_numeric_from_row() -> TestResult {
668        let raw_row = Map::from_iter([(
669            "f".to_string(),
670            json!([
671                { "v": "123.456" },
672                { "v": "99999999999999999999.123456789" },
673                { "v": "99999999999999999999999999999999.123" },
674            ]),
675        )]);
676        let schema = TableSchema::new().set_fields([
677            TableFieldSchema::new()
678                .set_name("price")
679                .set_type("NUMERIC")
680                .set_mode("NULLABLE"),
681            TableFieldSchema::new()
682                .set_name("big_amount")
683                .set_type("BIGNUMERIC")
684                .set_mode("NULLABLE"),
685            TableFieldSchema::new()
686                .set_name("overflow_amount")
687                .set_type("BIGNUMERIC")
688                .set_mode("NULLABLE"),
689        ]);
690        let schema = Arc::new(Schema::new(schema));
691        let mut row = Row::try_new(raw_row, &schema)?;
692
693        assert_eq!(row.get::<Decimal>(0)?, Decimal::new().set_value("123.456"));
694        assert_eq!(
695            row.get::<Decimal>("price")?,
696            Decimal::new().set_value("123.456")
697        );
698
699        assert_eq!(
700            row.get::<Decimal>(1)?,
701            Decimal::new().set_value("99999999999999999999.123456789")
702        );
703        assert_eq!(
704            row.get::<Decimal>("big_amount")?,
705            Decimal::new().set_value("99999999999999999999.123456789")
706        );
707
708        assert_eq!(
709            row.get::<RustDecimal>(0)?,
710            "123.456".parse().expect("valid decimal")
711        );
712        assert_eq!(
713            row.get::<RustDecimal>("price")?,
714            "123.456".parse().expect("valid decimal")
715        );
716
717        assert_eq!(
718            row.get::<RustDecimal>(1)?,
719            "99999999999999999999.123456789"
720                .parse()
721                .expect("valid decimal")
722        );
723        assert_eq!(
724            row.get::<RustDecimal>("big_amount")?,
725            "99999999999999999999.123456789"
726                .parse()
727                .expect("valid decimal")
728        );
729
730        assert!(row.get::<RustDecimal>(2).is_err());
731        assert!(row.get::<RustDecimal>("overflow_amount").is_err());
732
733        assert_eq!(row.take::<Decimal>(0)?, Decimal::new().set_value("123.456"));
734        assert_eq!(row.get::<Option<Decimal>>(0)?, None);
735
736        assert_eq!(
737            row.take::<RustDecimal>(1)?,
738            "99999999999999999999.123456789".parse()?
739        );
740        assert_eq!(row.get::<Option<RustDecimal>>(1)?, None);
741
742        Ok(())
743    }
744
745    #[tokio::test]
746    async fn convert_bytes_from_row() -> TestResult {
747        let raw_row = Map::from_iter([(
748            "f".to_string(),
749            json!([
750                { "v": "AQIDBA==" },
751                { "v": "SGVsbG8=" },
752                { "v": null },
753            ]),
754        )]);
755        let schema = TableSchema::new().set_fields([
756            TableFieldSchema::new()
757                .set_name("payload_vec")
758                .set_type("BYTES")
759                .set_mode("NULLABLE"),
760            TableFieldSchema::new()
761                .set_name("payload_bytes")
762                .set_type("BYTES")
763                .set_mode("NULLABLE"),
764            TableFieldSchema::new()
765                .set_name("null_bytes")
766                .set_type("BYTES")
767                .set_mode("NULLABLE"),
768        ]);
769        let schema = Arc::new(Schema::new(schema));
770        let mut row = Row::try_new(raw_row, &schema)?;
771
772        assert_eq!(row.get::<Vec<u8>>(0)?, vec![1, 2, 3, 4]);
773        assert_eq!(row.get::<Vec<u8>>("payload_vec")?, vec![1, 2, 3, 4]);
774
775        assert_eq!(
776            row.get::<bytes::Bytes>(1)?,
777            bytes::Bytes::from_static(b"Hello")
778        );
779        assert_eq!(
780            row.get::<bytes::Bytes>("payload_bytes")?,
781            bytes::Bytes::from_static(b"Hello")
782        );
783
784        assert_eq!(row.get::<Option<Vec<u8>>>(2)?, None);
785        assert_eq!(row.get::<Option<bytes::Bytes>>("null_bytes")?, None);
786
787        assert_eq!(row.take::<Vec<u8>>(0)?, vec![1, 2, 3, 4]);
788        assert_eq!(row.get::<Option<Vec<u8>>>(0)?, None);
789
790        assert_eq!(
791            row.take::<bytes::Bytes>(1)?,
792            bytes::Bytes::from_static(b"Hello")
793        );
794        assert_eq!(row.get::<Option<bytes::Bytes>>(1)?, None);
795
796        Ok(())
797    }
798
799    #[tokio::test]
800    async fn convert_record_from_row() -> TestResult {
801        let raw_row = Map::from_iter([(
802            "f".to_string(),
803            json!([
804                {
805                    "v": {
806                        "f": [
807                            { "v": "Alice" },
808                            { "v": "25" }
809                        ]
810                    }
811                }
812            ]),
813        )]);
814        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
815            .set_name("user")
816            .set_type("RECORD")
817            .set_mode("NULLABLE")
818            .set_fields([
819                TableFieldSchema::new()
820                    .set_name("name")
821                    .set_type("STRING")
822                    .set_mode("NULLABLE"),
823                TableFieldSchema::new()
824                    .set_name("age")
825                    .set_type("INTEGER")
826                    .set_mode("NULLABLE"),
827            ])]);
828        let schema = Arc::new(Schema::new(schema));
829        let mut row = Row::try_new(raw_row, &schema)?;
830
831        let expected: Struct = serde_json::from_value(json!({
832            "name": "Alice",
833            "age": 25,
834        }))?;
835        assert_eq!(row.get::<Struct>(0)?, expected);
836        assert_eq!(row.get::<Struct>("user")?, expected);
837        assert_eq!(row.get::<Struct>("user".to_string())?, expected);
838        assert_eq!(row.take::<Struct>("user")?, expected);
839        assert_eq!(row.get::<Option<Struct>>("user")?, None);
840
841        Ok(())
842    }
843
844    #[derive(crate::query::FromSql, Debug, PartialEq)]
845    struct JsonPayload {
846        name: String,
847        age: i64,
848    }
849
850    #[tokio::test]
851    async fn convert_json_from_row() -> TestResult {
852        let json_str = json!({"name": "Alice", "age": 30}).to_string();
853        let raw_row = Map::from_iter([(
854            "f".to_string(),
855            json!([
856                { "v": json_str },
857                { "v": null },
858            ]),
859        )]);
860        let schema = TableSchema::new().set_fields([
861            TableFieldSchema::new()
862                .set_name("json_obj")
863                .set_type("JSON")
864                .set_mode("NULLABLE"),
865            TableFieldSchema::new()
866                .set_name("json_null")
867                .set_type("JSON")
868                .set_mode("NULLABLE"),
869        ]);
870        let schema = Arc::new(Schema::new(schema));
871        let row = Row::try_new(raw_row, &schema)?;
872
873        let expected_struct: Struct = serde_json::from_value(json!({
874            "name": "Alice",
875            "age": 30,
876        }))?;
877        assert_eq!(row.get::<String>("json_obj")?, json_str);
878        assert_eq!(row.get::<Struct>("json_obj")?, expected_struct);
879        assert_eq!(
880            row.get::<JsonPayload>("json_obj")?,
881            JsonPayload {
882                name: "Alice".to_string(),
883                age: 30,
884            }
885        );
886        assert_eq!(row.get::<Option<Struct>>("json_null")?, None);
887        assert_eq!(row.get::<Option<JsonPayload>>("json_null")?, None);
888
889        Ok(())
890    }
891
892    #[tokio::test]
893    async fn convert_repeated_from_row() -> TestResult {
894        let raw_row = Map::from_iter([(
895            "f".to_string(),
896            json!([
897                {
898                    "v": [
899                        { "v": "1" },
900                        { "v": "2" },
901                        { "v": "3" }
902                    ]
903                }
904            ]),
905        )]);
906        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
907            .set_name("numbers")
908            .set_type("INTEGER")
909            .set_mode("REPEATED")]);
910        let schema = Arc::new(Schema::new(schema));
911        let mut row = Row::try_new(raw_row, &schema)?;
912
913        assert_eq!(row.get::<Vec<i64>>(0)?, vec![1, 2, 3]);
914        assert_eq!(row.get::<Vec<i64>>("numbers")?, vec![1, 2, 3]);
915        assert_eq!(row.take::<Vec<i64>>("numbers")?, vec![1, 2, 3]);
916        assert_eq!(row.get::<Option<Vec<i64>>>("numbers")?, None);
917
918        Ok(())
919    }
920
921    #[tokio::test]
922    async fn convert_repeated_record_from_row() -> TestResult {
923        let raw_row = Map::from_iter([(
924            "f".to_string(),
925            json!([
926                {
927                    "v": [
928                        {
929                            "v": {
930                                "f": [
931                                    { "v": "Bob" },
932                                    { "v": "28" }
933                                ]
934                            }
935                        },
936                        {
937                            "v": {
938                                "f": [
939                                    { "v": "Charlie" },
940                                    { "v": "31" }
941                                ]
942                            }
943                        }
944                    ]
945                }
946            ]),
947        )]);
948        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
949            .set_name("users")
950            .set_type("RECORD")
951            .set_mode("REPEATED")
952            .set_fields([
953                TableFieldSchema::new()
954                    .set_name("name")
955                    .set_type("STRING")
956                    .set_mode("NULLABLE"),
957                TableFieldSchema::new()
958                    .set_name("age")
959                    .set_type("INTEGER")
960                    .set_mode("NULLABLE"),
961            ])]);
962        let schema = Arc::new(Schema::new(schema));
963        let mut row = Row::try_new(raw_row, &schema)?;
964
965        let expected: Vec<Struct> = serde_json::from_value(json!([
966            {
967                "name": "Bob",
968                "age": 28,
969            },
970            {
971                "name": "Charlie",
972                "age": 31,
973            },
974        ]))?;
975        assert_eq!(row.get::<Vec<Struct>>(0)?, expected);
976        assert_eq!(row.get::<Vec<Struct>>("users")?, expected);
977        assert_eq!(row.take::<Vec<Struct>>("users")?, expected);
978        assert_eq!(row.get::<Option<Vec<Struct>>>("users")?, None);
979
980        Ok(())
981    }
982
983    #[test_case("INTEGER", "123", Value::Number(123.into()); "integer positive")]
984    #[test_case("INTEGER", "-456", Value::Number((-456).into()); "integer negative")]
985    #[test_case("INT64", "9223372036854775807", Value::Number(9223372036854775807_i64.into()); "int64 max")]
986    #[test_case("FLOAT", "123.45", Value::Number(serde_json::Number::from_f64(123.45).unwrap()); "float success")]
987    #[test_case("FLOAT64", "NaN", Value::String("NaN".to_string()); "float NaN")]
988    #[test_case("FLOAT64", "+inf", Value::String("+inf".to_string()); "float positive infinity")]
989    #[test_case("FLOAT64", "-inf", Value::String("-inf".to_string()); "float negative infinity")]
990    #[test_case("BOOLEAN", "true", Value::Bool(true); "boolean true lowercase")]
991    #[test_case("BOOLEAN", "TRUE", Value::Bool(true); "boolean true uppercase")]
992    #[test_case("BOOL", "false", Value::Bool(false); "bool false")]
993    #[test_case("JSON", r#"{"a":1}"#, Value::String(r#"{"a":1}"#.to_string()); "json string")]
994    fn convert_basic_type_cases_success(field_type: &str, value: &str, expected: Value) {
995        let res = convert_basic_type(value.to_string(), "test_col", field_type);
996        let value = res.expect("should succeed");
997        assert_eq!(
998            value,
999            crate::query::from_sql::SqlValueInner::from_wkt(expected)
1000        );
1001    }
1002
1003    #[test_case("INTEGER", "abc"; "integer invalid")]
1004    #[test_case("INT64", "9223372036854775808"; "int64 overflow")]
1005    #[test_case("FLOAT", "abc"; "float invalid")]
1006    #[test_case("BOOL", "invalid"; "bool invalid")]
1007    fn convert_basic_type_cases_conversion_fail(field_type: &str, value: &str) {
1008        let res = convert_basic_type(value.to_string(), "test_col", field_type);
1009        let err = res.unwrap_err();
1010        assert!(matches!(err, RowError::TypeConversion { .. }));
1011    }
1012
1013    #[test]
1014    fn convert_basic_type_invalid_row_format() {
1015        let res = convert_basic_type("value".to_string(), "test_col", "UNKNOWN");
1016        let err = res.unwrap_err();
1017        assert!(matches!(err, RowError::InvalidRowFormat(_)));
1018    }
1019
1020    #[test]
1021    fn convert_value_unsupported_value() {
1022        let field = TableFieldSchema::new()
1023            .set_name("test_col")
1024            .set_type("BOOLEAN")
1025            .set_mode("NULLABLE");
1026        let res = convert_value(Value::Bool(true), &field);
1027        let err = res.unwrap_err();
1028        assert!(matches!(err, RowError::InvalidRowFormat(_)));
1029    }
1030
1031    #[derive(FromRow, Debug, PartialEq)]
1032    struct TestRow {
1033        name: String,
1034        #[bigquery(rename = "custom_int")]
1035        some_int: i64,
1036        some_bool: bool,
1037        some_null: Option<i64>,
1038    }
1039
1040    #[tokio::test]
1041    async fn derive_from_row_success() -> TestResult {
1042        let raw_row = Map::from_iter([(
1043            "f".to_string(),
1044            json!([
1045                { "v": "James" },
1046                { "v": "272793" },
1047                { "v": "TRUE" },
1048                { "v": null },
1049            ]),
1050        )]);
1051        let schema = TableSchema::new().set_fields([
1052            TableFieldSchema::new()
1053                .set_name("name")
1054                .set_type("STRING")
1055                .set_mode("NULLABLE"),
1056            TableFieldSchema::new()
1057                .set_name("custom_int")
1058                .set_type("INTEGER")
1059                .set_mode("NULLABLE"),
1060            TableFieldSchema::new()
1061                .set_name("some_bool")
1062                .set_type("BOOLEAN")
1063                .set_mode("NULLABLE"),
1064            TableFieldSchema::new()
1065                .set_name("some_null")
1066                .set_type("INTEGER")
1067                .set_mode("NULLABLE"),
1068        ]);
1069        let schema = Arc::new(Schema::new(schema));
1070        let row = Row::try_new(raw_row, &schema)?;
1071
1072        let converted_row = TestRow::try_from(row)?;
1073        assert_eq!(
1074            converted_row,
1075            TestRow {
1076                name: "James".to_string(),
1077                some_int: 272793,
1078                some_bool: true,
1079                some_null: None,
1080            }
1081        );
1082
1083        Ok(())
1084    }
1085
1086    #[derive(FromRow, Debug, PartialEq)]
1087    struct RawIdentRow {
1088        r#type: String,
1089        r#match: i64,
1090    }
1091
1092    #[tokio::test]
1093    async fn derive_from_row_raw_identifier() -> TestResult {
1094        let raw_row = Map::from_iter([(
1095            "f".to_string(),
1096            json!([
1097                { "v": "click" },
1098                { "v": "7" },
1099            ]),
1100        )]);
1101        let schema = TableSchema::new().set_fields([
1102            TableFieldSchema::new()
1103                .set_name("type")
1104                .set_type("STRING")
1105                .set_mode("NULLABLE"),
1106            TableFieldSchema::new()
1107                .set_name("match")
1108                .set_type("INTEGER")
1109                .set_mode("NULLABLE"),
1110        ]);
1111        let schema = Arc::new(Schema::new(schema));
1112        let row = Row::try_new(raw_row, &schema)?;
1113
1114        let converted = RawIdentRow::try_from(row)?;
1115        assert_eq!(
1116            converted,
1117            RawIdentRow {
1118                r#type: "click".to_string(),
1119                r#match: 7,
1120            }
1121        );
1122        Ok(())
1123    }
1124
1125    #[tokio::test]
1126    async fn derive_from_row_missing_column() -> TestResult {
1127        let raw_row = Map::from_iter([(
1128            "f".to_string(),
1129            json!([
1130                { "v": "James" },
1131                { "v": "123" },
1132                { "v": "TRUE" },
1133                { "v": null },
1134            ]),
1135        )]);
1136        let schema = TableSchema::new().set_fields([
1137            TableFieldSchema::new()
1138                .set_name("name")
1139                .set_type("STRING")
1140                .set_mode("NULLABLE"),
1141            TableFieldSchema::new()
1142                .set_name("wrong_col")
1143                .set_type("INTEGER")
1144                .set_mode("NULLABLE"),
1145            TableFieldSchema::new()
1146                .set_name("some_bool")
1147                .set_type("BOOLEAN")
1148                .set_mode("NULLABLE"),
1149            TableFieldSchema::new()
1150                .set_name("some_null")
1151                .set_type("INTEGER")
1152                .set_mode("NULLABLE"),
1153        ]);
1154        let schema = Arc::new(Schema::new(schema));
1155        let row = Row::try_new(raw_row, &schema)?;
1156
1157        let err = TestRow::try_from(row).unwrap_err();
1158        assert!(matches!(err, RowError::ColumnNotFound(col) if col == "custom_int"));
1159        Ok(())
1160    }
1161
1162    #[derive(FromRow, Debug, PartialEq)]
1163    struct ShadowedFieldNamesRow {
1164        row: i64,
1165        name: String,
1166    }
1167
1168    #[tokio::test]
1169    async fn derive_from_row_shadowing_field_names() -> TestResult {
1170        let raw_row = Map::from_iter([(
1171            "f".to_string(),
1172            json!([
1173                { "v": "42" },
1174                { "v": "Alice" },
1175            ]),
1176        )]);
1177        let schema = TableSchema::new().set_fields([
1178            TableFieldSchema::new()
1179                .set_name("row")
1180                .set_type("INTEGER")
1181                .set_mode("NULLABLE"),
1182            TableFieldSchema::new()
1183                .set_name("name")
1184                .set_type("STRING")
1185                .set_mode("NULLABLE"),
1186        ]);
1187        let schema = Arc::new(Schema::new(schema));
1188        let row = Row::try_new(raw_row, &schema)?;
1189
1190        let converted = ShadowedFieldNamesRow::try_from(row)?;
1191        assert_eq!(
1192            converted,
1193            ShadowedFieldNamesRow {
1194                row: 42,
1195                name: "Alice".to_string(),
1196            }
1197        );
1198        Ok(())
1199    }
1200
1201    #[derive(FromRow, Debug, PartialEq)]
1202    struct TupleRow(i64, String);
1203
1204    #[derive(crate::query::FromSql, Debug, PartialEq)]
1205    struct AnonTriple(i64, String, bool);
1206
1207    #[derive(crate::query::FromSql, Debug, PartialEq)]
1208    struct NamedZThenA {
1209        z: i64,
1210        a: i64,
1211    }
1212
1213    #[derive(crate::query::FromSql, Debug, PartialEq)]
1214    struct PositionalPair(i64, i64);
1215
1216    #[derive(crate::query::FromSql, Debug, PartialEq)]
1217    struct DupIdNamed {
1218        id: i64,
1219    }
1220
1221    #[tokio::test]
1222    async fn anonymous_struct_preserves_all_fields() -> TestResult {
1223        // Simulates `SELECT STRUCT(10, 'hello', true) AS anon`, where BigQuery
1224        // sets `field.name = ""` for all three subfields.
1225        let raw_row = Map::from_iter([(
1226            "f".to_string(),
1227            json!([
1228                {
1229                    "v": {
1230                        "f": [
1231                            { "v": "10" },
1232                            { "v": "hello" },
1233                            { "v": "true" }
1234                        ]
1235                    }
1236                }
1237            ]),
1238        )]);
1239        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
1240            .set_name("anon")
1241            .set_type("RECORD")
1242            .set_mode("NULLABLE")
1243            .set_fields([
1244                TableFieldSchema::new()
1245                    .set_name("")
1246                    .set_type("INT64")
1247                    .set_mode("NULLABLE"),
1248                TableFieldSchema::new()
1249                    .set_name("")
1250                    .set_type("STRING")
1251                    .set_mode("NULLABLE"),
1252                TableFieldSchema::new()
1253                    .set_name("")
1254                    .set_type("BOOL")
1255                    .set_mode("NULLABLE"),
1256            ])]);
1257        let schema = Arc::new(Schema::new(schema));
1258        let mut row = Row::try_new(raw_row, &schema)?;
1259
1260        let anon: AnonTriple = row.take("anon")?;
1261        assert_eq!(anon, AnonTriple(10, "hello".to_string(), true));
1262        Ok(())
1263    }
1264
1265    #[tokio::test]
1266    async fn struct_preserves_sql_declaration_order_over_alphabetical_order() -> TestResult {
1267        // Simulates `SELECT STRUCT(1 AS z, 2 AS a) AS pair`.
1268        // A BTreeMap would sort `"a"` before `"z"`, scrambling positional order.
1269        let raw_row = Map::from_iter([(
1270            "f".to_string(),
1271            json!([
1272                {
1273                    "v": {
1274                        "f": [
1275                            { "v": "1" },
1276                            { "v": "2" }
1277                        ]
1278                    }
1279                }
1280            ]),
1281        )]);
1282        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
1283            .set_name("pair")
1284            .set_type("RECORD")
1285            .set_mode("NULLABLE")
1286            .set_fields([
1287                TableFieldSchema::new()
1288                    .set_name("z")
1289                    .set_type("INT64")
1290                    .set_mode("NULLABLE"),
1291                TableFieldSchema::new()
1292                    .set_name("a")
1293                    .set_type("INT64")
1294                    .set_mode("NULLABLE"),
1295            ])]);
1296        let schema = Arc::new(Schema::new(schema));
1297        let row = Row::try_new(raw_row, &schema)?;
1298
1299        // Named extraction gets z=1, a=2
1300        let by_name: NamedZThenA = row.get("pair")?;
1301        assert_eq!(by_name, NamedZThenA { z: 1, a: 2 });
1302
1303        // Positional extraction on the same struct gets (1, 2) in SQL order (z then a)
1304        let by_pos: PositionalPair = row.get("pair")?;
1305        assert_eq!(by_pos, PositionalPair(1, 2));
1306        Ok(())
1307    }
1308
1309    #[tokio::test]
1310    async fn duplicate_struct_field_names_match_row_behavior() -> TestResult {
1311        // Simulates `SELECT STRUCT(100 AS id, 200 AS id) AS dup`.
1312        // Name lookup must return the first `"id"` (100), while positional lookup
1313        // can access both `(100, 200)`.
1314        let raw_row = Map::from_iter([(
1315            "f".to_string(),
1316            json!([
1317                {
1318                    "v": {
1319                        "f": [
1320                            { "v": "100" },
1321                            { "v": "200" }
1322                        ]
1323                    }
1324                }
1325            ]),
1326        )]);
1327        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
1328            .set_name("dup")
1329            .set_type("RECORD")
1330            .set_mode("NULLABLE")
1331            .set_fields([
1332                TableFieldSchema::new()
1333                    .set_name("id")
1334                    .set_type("INT64")
1335                    .set_mode("NULLABLE"),
1336                TableFieldSchema::new()
1337                    .set_name("id")
1338                    .set_type("INT64")
1339                    .set_mode("NULLABLE"),
1340            ])]);
1341        let schema = Arc::new(Schema::new(schema));
1342        let row = Row::try_new(raw_row, &schema)?;
1343
1344        let first_id: DupIdNamed = row.get("dup")?;
1345        assert_eq!(first_id, DupIdNamed { id: 100 });
1346
1347        let both_ids: PositionalPair = row.get("dup")?;
1348        assert_eq!(both_ids, PositionalPair(100, 200));
1349        Ok(())
1350    }
1351
1352    #[tokio::test]
1353    async fn derive_from_row_tuple_struct() -> TestResult {
1354        // Simulates `SELECT 42, 'world'` where top-level columns have generated names.
1355        let raw_row = Map::from_iter([(
1356            "f".to_string(),
1357            json!([
1358            { "v": "42" },
1359            { "v": "world" },
1360              ]),
1361        )]);
1362        let schema = TableSchema::new().set_fields([
1363            TableFieldSchema::new()
1364                .set_name("_f0")
1365                .set_type("INT64")
1366                .set_mode("NULLABLE"),
1367            TableFieldSchema::new()
1368                .set_name("_f1")
1369                .set_type("STRING")
1370                .set_mode("NULLABLE"),
1371        ]);
1372        let schema = Arc::new(Schema::new(schema));
1373        let row = Row::try_new(raw_row, &schema)?;
1374
1375        let converted = TupleRow::try_from(row)?;
1376        assert_eq!(converted, TupleRow(42, "world".to_string()));
1377
1378        // here
1379        Ok(())
1380    }
1381
1382    #[derive(FromSql, Debug, PartialEq)]
1383    struct NestedGeneric<U> {
1384        inner_val: U,
1385    }
1386
1387    #[derive(FromRow, Debug, PartialEq)]
1388    struct GenericRow<T: Clone + Default, U: std::fmt::Debug> {
1389        #[bigquery(rename = "custom_val")]
1390        single: T,
1391        optional: Option<T>,
1392        list: Vec<T>,
1393        nested: NestedGeneric<U>,
1394        common: i64,
1395    }
1396
1397    #[tokio::test]
1398    async fn derive_from_row_generic() -> TestResult {
1399        let raw_row = Map::from_iter([(
1400            "f".to_string(),
1401            json!([
1402                { "v": "100" },
1403                { "v": null },
1404                { "v": [{ "v": "1" }, { "v": "2" }, { "v": "3" }] },
1405                {
1406                    "v": {
1407                        "f": [
1408                            { "v": "nested_value" }
1409                        ]
1410                    }
1411                },
1412                { "v": "1" },
1413            ]),
1414        )]);
1415        let schema = TableSchema::new().set_fields([
1416            TableFieldSchema::new()
1417                .set_name("custom_val")
1418                .set_type("INTEGER")
1419                .set_mode("NULLABLE"),
1420            TableFieldSchema::new()
1421                .set_name("optional")
1422                .set_type("INTEGER")
1423                .set_mode("NULLABLE"),
1424            TableFieldSchema::new()
1425                .set_name("list")
1426                .set_type("INTEGER")
1427                .set_mode("REPEATED"),
1428            TableFieldSchema::new()
1429                .set_name("nested")
1430                .set_type("RECORD")
1431                .set_mode("NULLABLE")
1432                .set_fields([TableFieldSchema::new()
1433                    .set_name("inner_val")
1434                    .set_type("STRING")
1435                    .set_mode("NULLABLE")]),
1436            TableFieldSchema::new()
1437                .set_name("common")
1438                .set_type("INTEGER")
1439                .set_mode("NULLABLE"),
1440        ]);
1441        let schema = Arc::new(Schema::new(schema));
1442        let row = Row::try_new(raw_row, &schema)?;
1443
1444        let converted = GenericRow::<i64, String>::try_from(row)?;
1445        assert_eq!(
1446            converted,
1447            GenericRow {
1448                single: 100,
1449                optional: None,
1450                list: vec![1, 2, 3],
1451                nested: NestedGeneric {
1452                    inner_val: "nested_value".to_string(),
1453                },
1454                common: 1,
1455            }
1456        );
1457        Ok(())
1458    }
1459
1460    #[derive(FromRow, Debug, PartialEq)]
1461    struct GenericRowWhere<T>
1462    where
1463        T: std::fmt::Debug + Clone,
1464    {
1465        val: T,
1466    }
1467
1468    #[tokio::test]
1469    async fn derive_from_row_generic_where_clause() -> TestResult {
1470        let raw_row = Map::from_iter([("f".to_string(), json!([{ "v": "hello" }]))]);
1471        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
1472            .set_name("val")
1473            .set_type("STRING")
1474            .set_mode("NULLABLE")]);
1475        let schema = Arc::new(Schema::new(schema));
1476        let row = Row::try_new(raw_row, &schema)?;
1477
1478        let converted = GenericRowWhere::<String>::try_from(row)?;
1479        assert_eq!(
1480            converted,
1481            GenericRowWhere {
1482                val: "hello".to_string(),
1483            }
1484        );
1485        Ok(())
1486    }
1487
1488    #[derive(FromRow, Debug, PartialEq)]
1489    struct GenericRowDefault<T = i64> {
1490        val: T,
1491    }
1492
1493    #[tokio::test]
1494    async fn derive_from_row_generic_default_param() -> TestResult {
1495        let raw_row = Map::from_iter([("f".to_string(), json!([{ "v": "42" }]))]);
1496        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
1497            .set_name("val")
1498            .set_type("INTEGER")
1499            .set_mode("NULLABLE")]);
1500        let schema = Arc::new(Schema::new(schema));
1501        let row = Row::try_new(raw_row, &schema)?;
1502
1503        let converted = GenericRowDefault::try_from(row)?;
1504        assert_eq!(converted, GenericRowDefault { val: 42 });
1505        Ok(())
1506    }
1507
1508    #[derive(FromRow, Debug, PartialEq)]
1509    struct GenericTupleRow<T, U>(T, Option<T>, U);
1510
1511    #[tokio::test]
1512    async fn derive_from_row_generic_tuple_struct() -> TestResult {
1513        let raw_row = Map::from_iter([(
1514            "f".to_string(),
1515            json!([
1516                { "v": "42" },
1517                { "v": null },
1518                { "v": "hello" },
1519            ]),
1520        )]);
1521        let schema = TableSchema::new().set_fields([
1522            TableFieldSchema::new()
1523                .set_name("_f0")
1524                .set_type("INT64")
1525                .set_mode("NULLABLE"),
1526            TableFieldSchema::new()
1527                .set_name("_f1")
1528                .set_type("INT64")
1529                .set_mode("NULLABLE"),
1530            TableFieldSchema::new()
1531                .set_name("_f2")
1532                .set_type("STRING")
1533                .set_mode("NULLABLE"),
1534        ]);
1535        let schema = Arc::new(Schema::new(schema));
1536        let row = Row::try_new(raw_row, &schema)?;
1537
1538        let converted = GenericTupleRow::<i64, String>::try_from(row)?;
1539        assert_eq!(converted, GenericTupleRow(42, None, "hello".to_string()));
1540        Ok(())
1541    }
1542
1543    #[cfg(any(
1544        google_cloud_unstable_bigquery_arrow,
1545        google_cloud_unstable_bigquery_storage_read
1546    ))]
1547    mod arrow_tests {
1548        use super::*;
1549        use crate::query::SqlValue;
1550        use arrow::array::{Int64Array, StringArray};
1551        use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
1552
1553        #[derive(Debug, PartialEq)]
1554        struct ArrowI64(i64);
1555
1556        impl FromSql for ArrowI64 {
1557            fn from_value(value: SqlValue) -> std::result::Result<Self, ConvertError> {
1558                match value.inner {
1559                    SqlValueInner::Arrow(cell) => cell.as_i64().map(Self),
1560                    other => Err(ConvertError::type_mismatch("Int64Array", &other)),
1561                }
1562            }
1563        }
1564
1565        #[derive(Debug, PartialEq)]
1566        struct ArrowString(String);
1567
1568        impl FromSql for ArrowString {
1569            fn from_value(value: SqlValue) -> std::result::Result<Self, ConvertError> {
1570                match value.inner {
1571                    SqlValueInner::Arrow(cell) => cell.as_str().map(|s| Self(s.to_owned())),
1572                    other => Err(ConvertError::type_mismatch("StringArray", &other)),
1573                }
1574            }
1575        }
1576
1577        #[derive(FromRow, Debug, PartialEq)]
1578        struct ArrowUserRow {
1579            name: ArrowString,
1580            age: ArrowI64,
1581        }
1582
1583        fn sample_batch_and_schema() -> anyhow::Result<(Arc<RecordBatch>, Arc<Schema>)> {
1584            let arrow_schema = Arc::new(ArrowSchema::new(vec![
1585                Field::new("name", DataType::Utf8, false),
1586                Field::new("age", DataType::Int64, true),
1587            ]));
1588            let batch = Arc::new(RecordBatch::try_new(
1589                arrow_schema.clone(),
1590                vec![
1591                    Arc::new(StringArray::from(vec!["Alice", "Bob"])),
1592                    Arc::new(Int64Array::from(vec![Some(30), None])),
1593                ],
1594            )?);
1595            let schema = Arc::new(Schema::from_arrow_schema(&arrow_schema));
1596            Ok((batch, schema))
1597        }
1598
1599        #[test]
1600        fn try_new_from_arrow_validation() -> TestResult {
1601            let (batch, schema) = sample_batch_and_schema()?;
1602
1603            let mismatched_schema = Arc::new(Schema::new(
1604                TableSchema::new().set_fields([TableFieldSchema::new()
1605                    .set_name("only_one")
1606                    .set_type("STRING")]),
1607            ));
1608            let err = Row::try_new_from_arrow(&batch, 0, &mismatched_schema).unwrap_err();
1609            assert!(
1610                matches!(&err, RowError::InvalidRowFormat(msg) if msg.contains("expected 1, got 2")),
1611                "unexpected error: {err:?}"
1612            );
1613
1614            let err = Row::try_new_from_arrow(&batch, 2, &schema).unwrap_err();
1615            assert!(
1616                matches!(&err, RowError::InvalidRowFormat(msg) if msg.contains("row_idx 2, num_rows 2")),
1617                "unexpected error: {err:?}"
1618            );
1619
1620            Ok(())
1621        }
1622
1623        #[test]
1624        fn arrow_row_get_take_and_from_row() -> TestResult {
1625            let (batch, schema) = sample_batch_and_schema()?;
1626
1627            let mut row0 = Row::try_new_from_arrow(&batch, 0, &schema)?;
1628            assert_eq!(
1629                row0.get::<ArrowString>("name")?,
1630                ArrowString("Alice".to_string())
1631            );
1632            assert_eq!(row0.get::<ArrowI64>(1)?, ArrowI64(30));
1633            assert_eq!(
1634                row0.take::<ArrowString>(0)?,
1635                ArrowString("Alice".to_string())
1636            );
1637            assert_eq!(row0.take::<ArrowI64>("age")?, ArrowI64(30));
1638
1639            let derived = ArrowUserRow::try_from(Row::try_new_from_arrow(&batch, 0, &schema)?)?;
1640            assert_eq!(
1641                derived,
1642                ArrowUserRow {
1643                    name: ArrowString("Alice".to_string()),
1644                    age: ArrowI64(30),
1645                }
1646            );
1647
1648            let mut row1 = Row::try_new_from_arrow(&batch, 1, &schema)?;
1649            assert_eq!(
1650                row1.get::<ArrowString>("name")?,
1651                ArrowString("Bob".to_string())
1652            );
1653
1654            let err = row1.get::<ArrowI64>("age").unwrap_err();
1655            assert!(
1656                matches!(
1657                    &err,
1658                    RowError::TypeConversion {
1659                        column,
1660                        sql_type,
1661                        source: ConvertError::NotNull,
1662                    } if column == "age" && sql_type == "INTEGER"
1663                ),
1664                "unexpected error: {err:?}"
1665            );
1666
1667            let err = row1.take::<ArrowI64>("missing").unwrap_err();
1668            assert!(
1669                matches!(&err, RowError::ColumnNotFound(col) if col == "missing"),
1670                "unexpected error: {err:?}"
1671            );
1672
1673            let err = row1.get::<ArrowI64>(99).unwrap_err();
1674            assert!(
1675                matches!(&err, RowError::ColumnNotFound(col) if col == "99"),
1676                "unexpected error: {err:?}"
1677            );
1678
1679            Ok(())
1680        }
1681
1682        #[test]
1683        fn try_new_from_arrow_anonymous_and_named_structs() -> TestResult {
1684            use arrow::array::{ArrayRef, BooleanArray, StructArray};
1685            use arrow::datatypes::Fields;
1686
1687            // 1. Anonymous struct: STRUCT(10, 'hello', true)
1688            let anon_fields = Fields::from(vec![
1689                Field::new("", DataType::Int64, false),
1690                Field::new("", DataType::Utf8, false),
1691                Field::new("", DataType::Boolean, false),
1692            ]);
1693            let anon_arr = Arc::new(StructArray::new(
1694                anon_fields.clone(),
1695                vec![
1696                    Arc::new(Int64Array::from(vec![10])) as ArrayRef,
1697                    Arc::new(StringArray::from(vec!["hello"])) as ArrayRef,
1698                    Arc::new(BooleanArray::from(vec![true])) as ArrayRef,
1699                ],
1700                None,
1701            ));
1702
1703            // 2. Named pair struct with SQL declaration order: STRUCT(1 AS z, 2 AS a)
1704            let pair_fields = Fields::from(vec![
1705                Field::new("z", DataType::Int64, false),
1706                Field::new("a", DataType::Int64, false),
1707            ]);
1708            let pair_arr = Arc::new(StructArray::new(
1709                pair_fields.clone(),
1710                vec![
1711                    Arc::new(Int64Array::from(vec![1])) as ArrayRef,
1712                    Arc::new(Int64Array::from(vec![2])) as ArrayRef,
1713                ],
1714                None,
1715            ));
1716
1717            // 3. Duplicate field names: STRUCT(100 AS id, 200 AS id)
1718            let dup_fields = Fields::from(vec![
1719                Field::new("id", DataType::Int64, false),
1720                Field::new("id", DataType::Int64, false),
1721            ]);
1722            let dup_arr = Arc::new(StructArray::new(
1723                dup_fields.clone(),
1724                vec![
1725                    Arc::new(Int64Array::from(vec![100])) as ArrayRef,
1726                    Arc::new(Int64Array::from(vec![200])) as ArrayRef,
1727                ],
1728                None,
1729            ));
1730
1731            let arrow_schema = Arc::new(ArrowSchema::new(vec![
1732                Field::new("anon", DataType::Struct(anon_fields), false),
1733                Field::new("pair", DataType::Struct(pair_fields), false),
1734                Field::new("dup", DataType::Struct(dup_fields), false),
1735            ]));
1736
1737            let batch = Arc::new(RecordBatch::try_new(
1738                arrow_schema,
1739                vec![anon_arr, pair_arr, dup_arr],
1740            )?);
1741
1742            let table_schema = TableSchema::new().set_fields([
1743                TableFieldSchema::new().set_name("anon").set_type("RECORD"),
1744                TableFieldSchema::new().set_name("pair").set_type("RECORD"),
1745                TableFieldSchema::new().set_name("dup").set_type("RECORD"),
1746            ]);
1747            let schema = Arc::new(Schema::new(table_schema));
1748
1749            let mut row = Row::try_new_from_arrow(&batch, 0, &schema)?;
1750
1751            // Anonymous struct extraction
1752            let anon: AnonTriple = row.get("anon")?;
1753            assert_eq!(anon, AnonTriple(10, "hello".to_string(), true));
1754
1755            // Named struct extraction by name and by positional order
1756            let by_name: NamedZThenA = row.get("pair")?;
1757            assert_eq!(by_name, NamedZThenA { z: 1, a: 2 });
1758            let by_pos: PositionalPair = row.get("pair")?;
1759            assert_eq!(by_pos, PositionalPair(1, 2));
1760
1761            // Duplicate fields: name gets first, positional gets both
1762            let first_id: DupIdNamed = row.get("dup")?;
1763            assert_eq!(first_id, DupIdNamed { id: 100 });
1764            let both_ids: PositionalPair = row.get("dup")?;
1765            assert_eq!(both_ids, PositionalPair(100, 200));
1766
1767            // Test taking values mutably
1768            let anon_taken: AnonTriple = row.take("anon")?;
1769            assert_eq!(anon_taken, AnonTriple(10, "hello".to_string(), true));
1770
1771            Ok(())
1772        }
1773
1774        #[derive(FromRow, Debug, PartialEq)]
1775        struct ArrowNestedRow {
1776            numbers: Vec<i64>,
1777            strings: Vec<String>,
1778            pairs: Vec<PositionalPair>,
1779        }
1780
1781        #[test]
1782        fn try_new_from_arrow_list_arrays() -> TestResult {
1783            use arrow::array::{ArrayRef, LargeListArray, ListArray, StructArray};
1784            use arrow::buffer::OffsetBuffer;
1785            use arrow::datatypes::Fields;
1786
1787            // List of integers: [[1, 2, 3], [42]]
1788            let int_values = Arc::new(Int64Array::from(vec![1, 2, 3, 42])) as ArrayRef;
1789            let int_offsets = OffsetBuffer::from_lengths(vec![3, 1]);
1790            let int_list = Arc::new(ListArray::new(
1791                Arc::new(Field::new("element", DataType::Int64, false)),
1792                int_offsets,
1793                int_values,
1794                None,
1795            ));
1796
1797            // LargeList of strings: [["a", "b"], ["c"]]
1798            let str_values = Arc::new(StringArray::from(vec!["a", "b", "c"])) as ArrayRef;
1799            let str_offsets = OffsetBuffer::from_lengths(vec![2, 1]);
1800            let str_large_list = Arc::new(LargeListArray::new(
1801                Arc::new(Field::new("element", DataType::Utf8, false)),
1802                str_offsets,
1803                str_values,
1804                None,
1805            ));
1806
1807            // List of structs: [[{"z": 1, "a": 2}], [{"z": 10, "a": 20}]]
1808            let pair_fields = Fields::from(vec![
1809                Field::new("z", DataType::Int64, false),
1810                Field::new("a", DataType::Int64, false),
1811            ]);
1812            let pair_structs = Arc::new(StructArray::new(
1813                pair_fields.clone(),
1814                vec![
1815                    Arc::new(Int64Array::from(vec![1, 10])) as ArrayRef,
1816                    Arc::new(Int64Array::from(vec![2, 20])) as ArrayRef,
1817                ],
1818                None,
1819            )) as ArrayRef;
1820            let struct_offsets = OffsetBuffer::from_lengths(vec![1, 1]);
1821            let struct_list = Arc::new(ListArray::new(
1822                Arc::new(Field::new("element", DataType::Struct(pair_fields), false)),
1823                struct_offsets,
1824                pair_structs,
1825                None,
1826            ));
1827
1828            let arrow_schema = Arc::new(ArrowSchema::new(vec![
1829                Field::new(
1830                    "numbers",
1831                    DataType::List(Arc::new(Field::new("element", DataType::Int64, false))),
1832                    false,
1833                ),
1834                Field::new(
1835                    "strings",
1836                    DataType::LargeList(Arc::new(Field::new("element", DataType::Utf8, false))),
1837                    false,
1838                ),
1839                Field::new(
1840                    "pairs",
1841                    DataType::List(Arc::new(Field::new(
1842                        "element",
1843                        DataType::Struct(Fields::from(vec![
1844                            Field::new("z", DataType::Int64, false),
1845                            Field::new("a", DataType::Int64, false),
1846                        ])),
1847                        false,
1848                    ))),
1849                    false,
1850                ),
1851            ]));
1852
1853            let batch = Arc::new(RecordBatch::try_new(
1854                arrow_schema,
1855                vec![int_list, str_large_list, struct_list],
1856            )?);
1857
1858            let table_schema = TableSchema::new().set_fields([
1859                TableFieldSchema::new()
1860                    .set_name("numbers")
1861                    .set_type("INTEGER")
1862                    .set_mode("REPEATED"),
1863                TableFieldSchema::new()
1864                    .set_name("strings")
1865                    .set_type("STRING")
1866                    .set_mode("REPEATED"),
1867                TableFieldSchema::new()
1868                    .set_name("pairs")
1869                    .set_type("RECORD")
1870                    .set_mode("REPEATED"),
1871            ]);
1872            let schema = Arc::new(Schema::new(table_schema));
1873
1874            // Row 0
1875            let row0 = Row::try_new_from_arrow(&batch, 0, &schema)?;
1876            let numbers0: Vec<i64> = row0.get("numbers")?;
1877            assert_eq!(numbers0, vec![1, 2, 3]);
1878
1879            let strings0: Vec<String> = row0.get("strings")?;
1880            assert_eq!(strings0, vec!["a".to_string(), "b".to_string()]);
1881
1882            let pairs0_named: Vec<NamedZThenA> = row0.get("pairs")?;
1883            assert_eq!(pairs0_named, vec![NamedZThenA { z: 1, a: 2 }]);
1884
1885            let pairs0_pos: Vec<PositionalPair> = row0.get("pairs")?;
1886            assert_eq!(pairs0_pos, vec![PositionalPair(1, 2)]);
1887
1888            let row0_derived = ArrowNestedRow::try_from(row0)?;
1889            assert_eq!(
1890                row0_derived,
1891                ArrowNestedRow {
1892                    numbers: vec![1, 2, 3],
1893                    strings: vec!["a".to_string(), "b".to_string()],
1894                    pairs: vec![PositionalPair(1, 2)],
1895                }
1896            );
1897
1898            // Row 1
1899            let row1 = Row::try_new_from_arrow(&batch, 1, &schema)?;
1900            let numbers1: Vec<i64> = row1.get("numbers")?;
1901            assert_eq!(numbers1, vec![42]);
1902
1903            let strings1: Vec<String> = row1.get("strings")?;
1904            assert_eq!(strings1, vec!["c".to_string()]);
1905
1906            let pairs1_pos: Vec<PositionalPair> = row1.get("pairs")?;
1907            assert_eq!(pairs1_pos, vec![PositionalPair(10, 20)]);
1908
1909            let row1_derived = ArrowNestedRow::try_from(row1)?;
1910            assert_eq!(
1911                row1_derived,
1912                ArrowNestedRow {
1913                    numbers: vec![42],
1914                    strings: vec!["c".to_string()],
1915                    pairs: vec![PositionalPair(10, 20)],
1916                }
1917            );
1918
1919            Ok(())
1920        }
1921    }
1922}