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};
16use crate::query::{FromSql, Schema};
17use std::sync::Arc;
18use wkt::{ListValue, Struct, Value};
19
20pub type Result<T> = std::result::Result<T, RowError>;
21
22/// A container for a single row within a query result set.
23///
24/// [`RowIterator::next()`](crate::query::RowIterator::next) yields a `Row`.
25///
26/// Each `Row` contains parsed cell values and a reference to the table schema.
27///
28/// # Zero-Copy Struct Mapping via Derive Macros
29///
30/// Define typed structs with `#[derive(FromRow)]` to convert rows directly into
31/// your domain types using `TryFrom<Row>` without unnecessary allocations:
32///
33/// ```
34/// # use google_cloud_bigquery::query::{Row, FromRow};
35/// #[derive(FromRow, Debug)]
36/// struct UserStats {
37///     name: String,
38///     count: i64,
39/// }
40///
41/// # fn sample(row: Row) -> anyhow::Result<()> {
42/// let user: UserStats = row.try_into()?;
43/// println!("{}: {}", user.name, user.count);
44/// # Ok(())
45/// # }
46/// ```
47///
48/// # Field Extraction by Name or Index
49///
50/// Retrieve individual cell values by column name (`&str`) or index (`usize`)
51/// using [`get()`](Row::get), [`try_get()`](Row::try_get), or
52/// [`take()`](Row::take):
53///
54/// ```
55/// # use google_cloud_bigquery::query::Row;
56/// # fn sample(row: Row) {
57/// let name: String = row.get("name");
58/// let age: i64 = row.get(1);
59/// println!("{name} is {age} years old");
60/// # }
61/// ```
62#[derive(Clone, Debug)]
63pub struct Row {
64    pub(crate) values: Value,
65    pub(crate) schema: Arc<Schema>,
66}
67
68mod sealed {
69    /// A sealed trait to prevent external implementation of `ColumnIndex`.
70    pub trait ColumnIndex {}
71    impl ColumnIndex for usize {}
72    impl ColumnIndex for &str {}
73    impl ColumnIndex for String {}
74}
75
76/// A trait for types that can be used to index into a [`Row`].
77///
78/// This trait is sealed and cannot be implemented for types outside of this crate.
79pub trait ColumnIndex: sealed::ColumnIndex + std::fmt::Display {
80    /// Returns the index of the column in the given row, if it exists.
81    fn index(&self, row: &Row) -> Option<usize>;
82}
83
84impl ColumnIndex for usize {
85    fn index(&self, row: &Row) -> Option<usize> {
86        row.schema.get_field_by_index(*self).map(|_| *self)
87    }
88}
89
90impl ColumnIndex for &str {
91    fn index(&self, row: &Row) -> Option<usize> {
92        row.schema.get_field_index_by_name(self)
93    }
94}
95
96impl ColumnIndex for String {
97    fn index(&self, row: &Row) -> Option<usize> {
98        self.as_str().index(row)
99    }
100}
101
102impl Row {
103    pub(crate) fn try_new(row: Struct, schema: &Arc<Schema>) -> Result<Self> {
104        let field_list = get_field_list(row)?;
105
106        if field_list.len() != schema.len() {
107            return Err(RowError::InvalidRowFormat(format!(
108                "schema and row cell mismatch (expected {}, got {})",
109                schema.len(),
110                field_list.len()
111            )));
112        }
113
114        let mut values = ListValue::new();
115        for (i, cell) in field_list.into_iter().enumerate() {
116            let value = get_field_value(cell)?;
117            match schema.get_field_by_index(i) {
118                Some(f) => {
119                    let field_name = &f.name;
120                    let field_type = &f.r#type;
121                    let schema = Arc::new(Schema::new_from_field(f.clone()));
122                    let value = convert_value(value, field_name, field_type, &schema)?;
123                    values.push(value);
124                }
125                None => continue,
126            }
127        }
128
129        Ok(Self {
130            values: Value::Array(values),
131            schema: schema.clone(),
132        })
133    }
134
135    fn resolve_index<I: ColumnIndex>(&self, col: &I) -> Result<usize> {
136        col.index(self)
137            .ok_or_else(|| RowError::ColumnNotFound(format!("{col}")))
138    }
139
140    fn convert_value_at<T: FromSql>(&self, idx: usize, val: Value) -> Result<T> {
141        T::from_sql(val).map_err(|e| {
142            let field_name = self
143                .schema
144                .get_field_by_index(idx)
145                .map(|f| f.name.clone())
146                .unwrap_or_else(|| idx.to_string());
147            RowError::TypeConversion {
148                column: field_name,
149                source: e,
150            }
151        })
152    }
153
154    /// Attempts to retrieve a value from the row by column name or zero-based
155    /// index.
156    ///
157    /// The return type must implement [`FromSql`](crate::query::FromSql).
158    ///
159    /// # Errors
160    ///
161    /// Returns [`RowError::ColumnNotFound`](crate::error::RowError::ColumnNotFound)
162    /// if the column does not exist,
163    /// [`RowError::IndexOutOfRange`](crate::error::RowError::IndexOutOfRange) if
164    /// the index exceeds schema bounds, or
165    /// [`RowError::TypeConversion`](crate::error::RowError::TypeConversion) if
166    /// the value cannot be converted to `T`.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// # use google_cloud_bigquery::query::Row;
172    /// # fn sample(row: Row) -> anyhow::Result<()> {
173    /// let msg: String = row.try_get("msg")?;
174    /// println!("Value: {msg}");
175    /// # Ok(())
176    /// # }
177    /// ```
178    pub fn try_get<T: FromSql, I: ColumnIndex>(&self, index: I) -> Result<T> {
179        let idx = self.resolve_index(&index)?;
180        let val = self
181            .values
182            .get(idx)
183            .ok_or_else(|| RowError::IndexOutOfRange {
184                index: idx,
185                len: self.schema.len(),
186            })?;
187
188        self.convert_value_at(idx, val.clone())
189    }
190
191    /// Takes ownership of a value from the row by column name or zero-based
192    /// index.
193    ///
194    /// This replaces the cell value in the row with `Value::Null` in-place to
195    /// avoid cloning. Attempting to read the column again after calling `take()`
196    /// yields `Value::Null`.
197    ///
198    /// # Errors
199    ///
200    /// Returns the same errors as [`try_get()`](Row::try_get).
201    ///
202    /// # Example
203    ///
204    /// ```
205    /// # use google_cloud_bigquery::query::Row;
206    /// # fn sample(mut row: Row) -> anyhow::Result<()> {
207    /// let text: String = row.take("big_text")?;
208    /// println!("Length: {}", text.len());
209    /// # Ok(())
210    /// # }
211    /// ```
212    pub fn take<T: FromSql, I: ColumnIndex>(&mut self, index: I) -> Result<T> {
213        let idx = self.resolve_index(&index)?;
214
215        let val = self
216            .values
217            .get_mut(idx)
218            .ok_or_else(|| RowError::IndexOutOfRange {
219                index: idx,
220                len: self.schema.len(),
221            })?;
222
223        // swap out the value in-place to avoid clones
224        let owned_val = std::mem::replace(val, Value::Null);
225        self.convert_value_at(idx, owned_val)
226    }
227
228    /// Retrieves a value from the row by column name or zero-based index.
229    ///
230    /// # Panics
231    ///
232    /// Panics if the column does not exist or if the value cannot be converted
233    /// to type `T`.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// # use google_cloud_bigquery::query::Row;
239    /// # fn sample(row: Row) {
240    /// let count: i64 = row.get("count");
241    /// println!("Count: {count}");
242    /// # }
243    /// ```
244    pub fn get<T: FromSql, I: ColumnIndex>(&self, index: I) -> T {
245        self.try_get(index).unwrap()
246    }
247}
248
249fn get_field_list(mut row: Struct) -> Result<Vec<Value>> {
250    match row.remove("f") {
251        Some(Value::Array(arr)) => Ok(arr),
252        Some(_) => Err(RowError::InvalidRowFormat("invalid field values".into())),
253        None => Err(RowError::InvalidRowFormat("missing field values".into())),
254    }
255}
256
257fn get_field_value(value: Value) -> Result<Value> {
258    match value {
259        Value::Object(mut obj) => match obj.remove("v") {
260            Some(val) => Ok(val),
261            None => Err(RowError::InvalidRowFormat("missing field value".into())),
262        },
263        _ => Err(RowError::InvalidRowFormat("invalid field value".into())),
264    }
265}
266
267fn convert_value(
268    value: Value,
269    field_name: &str,
270    field_type: &str,
271    schema: &Arc<Schema>,
272) -> Result<Value> {
273    match value {
274        Value::Null => Ok(Value::Null),
275        Value::String(v) => convert_basic_type(v, field_name, field_type),
276        Value::Object(v) => convert_nested(v, schema),
277        Value::Array(v) => convert_repeated(v, field_name, field_type, schema),
278        _ => Err(RowError::InvalidRowFormat(format!(
279            "cell value is not an object: value={:?}, field_type={:?}",
280            value, field_type
281        ))),
282    }
283}
284
285fn convert_repeated(
286    value: ListValue,
287    field_name: &str,
288    field_type: &str,
289    schema: &Arc<Schema>,
290) -> Result<Value> {
291    let mut values = ListValue::new();
292    for cell in value {
293        // each cell contains a single entry, keyed by "v"
294        let val = get_field_value(cell)?;
295        let v = convert_value(val, field_name, field_type, schema)?;
296        values.push(v);
297    }
298    Ok(Value::Array(values))
299}
300
301fn convert_nested(value: Struct, schema: &Arc<Schema>) -> Result<Value> {
302    let row = Row::try_new(value, schema)?;
303    let mut obj = Struct::new();
304    if let Value::Array(list) = row.values {
305        for (i, val) in list.into_iter().enumerate() {
306            if let Some(field) = schema.get_field_by_index(i) {
307                obj.insert(field.name.clone(), val);
308            }
309        }
310    }
311    Ok(Value::Object(obj))
312}
313
314fn convert_basic_type(value: String, field_name: &str, field_type: &str) -> Result<Value> {
315    match field_type {
316        "STRING" | "BYTES" | "TIMESTAMP" | "DATE" | "TIME" | "DATETIME" | "NUMERIC"
317        | "BIGNUMERIC" | "BIGINT" | "GEOGRAPHY" | "JSON" | "INTERVAL" | "RANGE" => {
318            Ok(Value::String(value))
319        }
320        "INTEGER" | "INT64" => {
321            let num = value.parse::<i64>().map_err(|e| RowError::TypeConversion {
322                column: field_name.to_string(),
323                source: ConvertError::Convert(Box::new(e)),
324            })?;
325            Ok(Value::Number(serde_json::Number::from(num)))
326        }
327        "FLOAT" | "FLOAT64" => {
328            let num = value.parse::<f64>().map_err(|e| RowError::TypeConversion {
329                column: field_name.to_string(),
330                source: ConvertError::Convert(Box::new(e)),
331            })?;
332            match serde_json::Number::from_f64(num) {
333                Some(n) => Ok(Value::Number(n)),
334                None => Ok(Value::String(value)),
335            }
336        }
337        "BOOLEAN" | "BOOL" => {
338            let b = if value.eq_ignore_ascii_case("true") {
339                true
340            } else if value.eq_ignore_ascii_case("false") {
341                false
342            } else {
343                return Err(RowError::TypeConversion {
344                    column: field_name.to_string(),
345                    source: ConvertError::Convert(
346                        "provided string was not `true` or `false`".into(),
347                    ),
348                });
349            };
350            Ok(Value::Bool(b))
351        }
352        _ => Err(RowError::InvalidRowFormat(format!(
353            "unknown field type: {} at column {}",
354            field_type, field_name
355        ))),
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use crate as google_cloud_bigquery;
363    use crate::query::FromRow;
364    use google_cloud_bigquery_v2::model::{TableFieldSchema, TableSchema};
365    use google_cloud_type::model::Decimal;
366    use rust_decimal::Decimal as RustDecimal;
367    use serde_json::{Map, json};
368    use test_case::test_case;
369
370    type TestResult = anyhow::Result<()>;
371
372    #[tokio::test]
373    async fn convert_basic_types_from_row() -> TestResult {
374        let raw_row = Map::from_iter([(
375            "f".to_string(),
376            json!([
377                { "v": "James" },
378                { "v": "272793" },
379                { "v": "TRUE" },
380                { "v": null },
381                { "v": "64.0" },
382            ]),
383        )]);
384        let schema = TableSchema::new().set_fields([
385            TableFieldSchema::new()
386                .set_name("name")
387                .set_type("STRING")
388                .set_mode("NULLABLE"),
389            TableFieldSchema::new()
390                .set_name("some_int")
391                .set_type("INTEGER")
392                .set_mode("NULLABLE"),
393            TableFieldSchema::new()
394                .set_name("some_bool")
395                .set_type("BOOLEAN")
396                .set_mode("NULLABLE"),
397            TableFieldSchema::new()
398                .set_name("some_null")
399                .set_type("INTEGER")
400                .set_mode("NULLABLE"),
401            TableFieldSchema::new()
402                .set_name("some_float")
403                .set_type("FLOAT64")
404                .set_mode("NULLABLE"),
405        ]);
406        let schema = Arc::new(Schema::new(schema));
407        let mut row = Row::try_new(raw_row, &schema)?;
408
409        assert_eq!(row.get::<String, _>(0), "James");
410        assert_eq!(row.get::<String, _>("name"), "James");
411
412        assert_eq!(row.get::<i32, _>(1), 272793);
413        assert_eq!(row.get::<i32, _>("some_int"), 272793);
414        assert_eq!(row.get::<i64, _>(1), 272793);
415        assert_eq!(row.get::<i64, _>("some_int"), 272793);
416
417        assert!(row.get::<bool, _>(2));
418        assert!(row.get::<bool, _>("some_bool"));
419
420        assert_eq!(row.get::<Option<i64>, _>(3), None);
421        assert_eq!(row.get::<Option<i64>, _>("some_null"), None);
422
423        assert_eq!(row.get::<f32, _>(4), 64.0);
424        assert_eq!(row.get::<f32, _>("some_float"), 64.0);
425        assert_eq!(row.get::<f64, _>(4), 64.0);
426        assert_eq!(row.get::<f64, _>("some_float"), 64.0);
427
428        assert_eq!(row.take::<String, _>(0)?, "James");
429        assert_eq!(row.try_get::<Option<String>, _>(0)?, None);
430
431        assert_eq!(row.take::<i32, _>(1)?, 272793);
432        assert_eq!(row.try_get::<Option<i32>, _>(1)?, None);
433
434        assert!(row.take::<bool, _>(2)?);
435        assert_eq!(row.try_get::<Option<bool>, _>(2)?, None);
436
437        assert_eq!(row.take::<Option<i64>, _>(3)?, None);
438        assert_eq!(row.try_get::<Option<i64>, _>(3)?, None);
439
440        assert_eq!(row.take::<f32, _>(4)?, 64.0);
441        assert_eq!(row.try_get::<Option<f32>, _>(4)?, None);
442
443        Ok(())
444    }
445
446    #[tokio::test]
447    async fn convert_numeric_from_row() -> TestResult {
448        let raw_row = Map::from_iter([(
449            "f".to_string(),
450            json!([
451                { "v": "123.456" },
452                { "v": "99999999999999999999.123456789" },
453                { "v": "99999999999999999999999999999999.123" },
454            ]),
455        )]);
456        let schema = TableSchema::new().set_fields([
457            TableFieldSchema::new()
458                .set_name("price")
459                .set_type("NUMERIC")
460                .set_mode("NULLABLE"),
461            TableFieldSchema::new()
462                .set_name("big_amount")
463                .set_type("BIGNUMERIC")
464                .set_mode("NULLABLE"),
465            TableFieldSchema::new()
466                .set_name("overflow_amount")
467                .set_type("BIGNUMERIC")
468                .set_mode("NULLABLE"),
469        ]);
470        let schema = Arc::new(Schema::new(schema));
471        let mut row = Row::try_new(raw_row, &schema)?;
472
473        assert_eq!(
474            row.get::<Decimal, _>(0),
475            Decimal::new().set_value("123.456")
476        );
477        assert_eq!(
478            row.get::<Decimal, _>("price"),
479            Decimal::new().set_value("123.456")
480        );
481
482        assert_eq!(
483            row.get::<Decimal, _>(1),
484            Decimal::new().set_value("99999999999999999999.123456789")
485        );
486        assert_eq!(
487            row.get::<Decimal, _>("big_amount"),
488            Decimal::new().set_value("99999999999999999999.123456789")
489        );
490
491        assert_eq!(
492            row.get::<RustDecimal, _>(0),
493            "123.456".parse().expect("valid decimal")
494        );
495        assert_eq!(
496            row.get::<RustDecimal, _>("price"),
497            "123.456".parse().expect("valid decimal")
498        );
499
500        assert_eq!(
501            row.get::<RustDecimal, _>(1),
502            "99999999999999999999.123456789"
503                .parse()
504                .expect("valid decimal")
505        );
506        assert_eq!(
507            row.get::<RustDecimal, _>("big_amount"),
508            "99999999999999999999.123456789"
509                .parse()
510                .expect("valid decimal")
511        );
512
513        assert!(row.try_get::<RustDecimal, _>(2).is_err());
514        assert!(row.try_get::<RustDecimal, _>("overflow_amount").is_err());
515
516        assert_eq!(
517            row.take::<Decimal, _>(0)?,
518            Decimal::new().set_value("123.456")
519        );
520        assert_eq!(row.try_get::<Option<Decimal>, _>(0)?, None);
521
522        assert_eq!(
523            row.take::<RustDecimal, _>(1)?,
524            "99999999999999999999.123456789".parse()?
525        );
526        assert_eq!(row.try_get::<Option<RustDecimal>, _>(1)?, None);
527
528        Ok(())
529    }
530
531    #[tokio::test]
532    async fn convert_bytes_from_row() -> TestResult {
533        let raw_row = Map::from_iter([(
534            "f".to_string(),
535            json!([
536                { "v": "AQIDBA==" },
537                { "v": "SGVsbG8=" },
538                { "v": null },
539            ]),
540        )]);
541        let schema = TableSchema::new().set_fields([
542            TableFieldSchema::new()
543                .set_name("payload_vec")
544                .set_type("BYTES")
545                .set_mode("NULLABLE"),
546            TableFieldSchema::new()
547                .set_name("payload_bytes")
548                .set_type("BYTES")
549                .set_mode("NULLABLE"),
550            TableFieldSchema::new()
551                .set_name("null_bytes")
552                .set_type("BYTES")
553                .set_mode("NULLABLE"),
554        ]);
555        let schema = Arc::new(Schema::new(schema));
556        let mut row = Row::try_new(raw_row, &schema)?;
557
558        assert_eq!(row.get::<Vec<u8>, _>(0), vec![1, 2, 3, 4]);
559        assert_eq!(row.get::<Vec<u8>, _>("payload_vec"), vec![1, 2, 3, 4]);
560
561        assert_eq!(
562            row.get::<bytes::Bytes, _>(1),
563            bytes::Bytes::from_static(b"Hello")
564        );
565        assert_eq!(
566            row.get::<bytes::Bytes, _>("payload_bytes"),
567            bytes::Bytes::from_static(b"Hello")
568        );
569
570        assert_eq!(row.get::<Option<Vec<u8>>, _>(2), None);
571        assert_eq!(row.get::<Option<bytes::Bytes>, _>("null_bytes"), None);
572
573        assert_eq!(row.take::<Vec<u8>, _>(0)?, vec![1, 2, 3, 4]);
574        assert_eq!(row.try_get::<Option<Vec<u8>>, _>(0)?, None);
575
576        assert_eq!(
577            row.take::<bytes::Bytes, _>(1)?,
578            bytes::Bytes::from_static(b"Hello")
579        );
580        assert_eq!(row.try_get::<Option<bytes::Bytes>, _>(1)?, None);
581
582        Ok(())
583    }
584
585    #[tokio::test]
586    async fn convert_record_from_row() -> TestResult {
587        let raw_row = Map::from_iter([(
588            "f".to_string(),
589            json!([
590                {
591                    "v": {
592                        "f": [
593                            { "v": "Alice" },
594                            { "v": "25" }
595                        ]
596                    }
597                }
598            ]),
599        )]);
600        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
601            .set_name("user")
602            .set_type("RECORD")
603            .set_mode("NULLABLE")
604            .set_fields([
605                TableFieldSchema::new()
606                    .set_name("name")
607                    .set_type("STRING")
608                    .set_mode("NULLABLE"),
609                TableFieldSchema::new()
610                    .set_name("age")
611                    .set_type("INTEGER")
612                    .set_mode("NULLABLE"),
613            ])]);
614        let schema = Arc::new(Schema::new(schema));
615        let mut row = Row::try_new(raw_row, &schema)?;
616
617        let expected: Struct = serde_json::from_value(json!({
618            "name": "Alice",
619            "age": 25,
620        }))?;
621        assert_eq!(row.get::<Struct, _>(0), expected);
622        assert_eq!(row.get::<Struct, _>("user"), expected);
623        assert_eq!(row.take::<Struct, _>("user")?, expected);
624        assert_eq!(row.try_get::<Option<Struct>, _>("user")?, None);
625
626        Ok(())
627    }
628
629    #[tokio::test]
630    async fn convert_repeated_from_row() -> TestResult {
631        let raw_row = Map::from_iter([(
632            "f".to_string(),
633            json!([
634                {
635                    "v": [
636                        { "v": "1" },
637                        { "v": "2" },
638                        { "v": "3" }
639                    ]
640                }
641            ]),
642        )]);
643        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
644            .set_name("numbers")
645            .set_type("INTEGER")
646            .set_mode("REPEATED")]);
647        let schema = Arc::new(Schema::new(schema));
648        let mut row = Row::try_new(raw_row, &schema)?;
649
650        assert_eq!(row.get::<Vec<i64>, _>(0), vec![1, 2, 3]);
651        assert_eq!(row.get::<Vec<i64>, _>("numbers"), vec![1, 2, 3]);
652        assert_eq!(row.take::<Vec<i64>, _>("numbers")?, vec![1, 2, 3]);
653        assert_eq!(row.try_get::<Option<Vec<i64>>, _>("numbers")?, None);
654
655        Ok(())
656    }
657
658    #[tokio::test]
659    async fn convert_repeated_record_from_row() -> TestResult {
660        let raw_row = Map::from_iter([(
661            "f".to_string(),
662            json!([
663                {
664                    "v": [
665                        {
666                            "v": {
667                                "f": [
668                                    { "v": "Bob" },
669                                    { "v": "28" }
670                                ]
671                            }
672                        },
673                        {
674                            "v": {
675                                "f": [
676                                    { "v": "Charlie" },
677                                    { "v": "31" }
678                                ]
679                            }
680                        }
681                    ]
682                }
683            ]),
684        )]);
685        let schema = TableSchema::new().set_fields([TableFieldSchema::new()
686            .set_name("users")
687            .set_type("RECORD")
688            .set_mode("REPEATED")
689            .set_fields([
690                TableFieldSchema::new()
691                    .set_name("name")
692                    .set_type("STRING")
693                    .set_mode("NULLABLE"),
694                TableFieldSchema::new()
695                    .set_name("age")
696                    .set_type("INTEGER")
697                    .set_mode("NULLABLE"),
698            ])]);
699        let schema = Arc::new(Schema::new(schema));
700        let mut row = Row::try_new(raw_row, &schema)?;
701
702        let expected: Vec<Struct> = serde_json::from_value(json!([
703            {
704                "name": "Bob",
705                "age": 28,
706            },
707            {
708                "name": "Charlie",
709                "age": 31,
710            },
711        ]))?;
712        assert_eq!(row.get::<Vec<Struct>, _>(0), expected);
713        assert_eq!(row.get::<Vec<Struct>, _>("users"), expected);
714        assert_eq!(row.take::<Vec<Struct>, _>("users")?, expected);
715        assert_eq!(row.try_get::<Option<Vec<Struct>>, _>("users")?, None);
716
717        Ok(())
718    }
719
720    #[test_case("INTEGER", "123", Value::Number(123.into()); "integer positive")]
721    #[test_case("INTEGER", "-456", Value::Number((-456).into()); "integer negative")]
722    #[test_case("INT64", "9223372036854775807", Value::Number(9223372036854775807_i64.into()); "int64 max")]
723    #[test_case("FLOAT", "123.45", Value::Number(serde_json::Number::from_f64(123.45).unwrap()); "float success")]
724    #[test_case("FLOAT64", "NaN", Value::String("NaN".to_string()); "float NaN")]
725    #[test_case("FLOAT64", "+inf", Value::String("+inf".to_string()); "float positive infinity")]
726    #[test_case("FLOAT64", "-inf", Value::String("-inf".to_string()); "float negative infinity")]
727    #[test_case("BOOLEAN", "true", Value::Bool(true); "boolean true lowercase")]
728    #[test_case("BOOLEAN", "TRUE", Value::Bool(true); "boolean true uppercase")]
729    #[test_case("BOOL", "false", Value::Bool(false); "bool false")]
730    fn convert_basic_type_cases_success(field_type: &str, value: &str, expected: Value) {
731        let res = convert_basic_type(value.to_string(), "test_col", field_type);
732        let value = res.expect("should succeed");
733        assert_eq!(value, expected);
734    }
735
736    #[test_case("INTEGER", "abc"; "integer invalid")]
737    #[test_case("INT64", "9223372036854775808"; "int64 overflow")]
738    #[test_case("FLOAT", "abc"; "float invalid")]
739    #[test_case("BOOL", "invalid"; "bool invalid")]
740    fn convert_basic_type_cases_conversion_fail(field_type: &str, value: &str) {
741        let res = convert_basic_type(value.to_string(), "test_col", field_type);
742        let err = res.unwrap_err();
743        assert!(matches!(err, RowError::TypeConversion { .. }));
744    }
745
746    #[test]
747    fn convert_basic_type_invalid_row_format() {
748        let res = convert_basic_type("value".to_string(), "test_col", "UNKNOWN");
749        let err = res.unwrap_err();
750        assert!(matches!(err, RowError::InvalidRowFormat(_)));
751    }
752
753    #[derive(FromRow, Debug, PartialEq)]
754    struct TestRow {
755        name: String,
756        #[bigquery(rename = "custom_int")]
757        some_int: i64,
758        some_bool: bool,
759        some_null: Option<i64>,
760    }
761
762    #[tokio::test]
763    async fn derive_from_row_success() -> TestResult {
764        let raw_row = Map::from_iter([(
765            "f".to_string(),
766            json!([
767                { "v": "James" },
768                { "v": "272793" },
769                { "v": "TRUE" },
770                { "v": null },
771            ]),
772        )]);
773        let schema = TableSchema::new().set_fields([
774            TableFieldSchema::new()
775                .set_name("name")
776                .set_type("STRING")
777                .set_mode("NULLABLE"),
778            TableFieldSchema::new()
779                .set_name("custom_int")
780                .set_type("INTEGER")
781                .set_mode("NULLABLE"),
782            TableFieldSchema::new()
783                .set_name("some_bool")
784                .set_type("BOOLEAN")
785                .set_mode("NULLABLE"),
786            TableFieldSchema::new()
787                .set_name("some_null")
788                .set_type("INTEGER")
789                .set_mode("NULLABLE"),
790        ]);
791        let schema = Arc::new(Schema::new(schema));
792        let row = Row::try_new(raw_row, &schema)?;
793
794        let converted_row = TestRow::try_from(row)?;
795        assert_eq!(
796            converted_row,
797            TestRow {
798                name: "James".to_string(),
799                some_int: 272793,
800                some_bool: true,
801                some_null: None,
802            }
803        );
804
805        Ok(())
806    }
807
808    #[tokio::test]
809    async fn derive_from_row_missing_column() -> TestResult {
810        let raw_row = Map::from_iter([(
811            "f".to_string(),
812            json!([
813                { "v": "James" },
814                { "v": "123" },
815                { "v": "TRUE" },
816                { "v": null },
817            ]),
818        )]);
819        let schema = TableSchema::new().set_fields([
820            TableFieldSchema::new()
821                .set_name("name")
822                .set_type("STRING")
823                .set_mode("NULLABLE"),
824            TableFieldSchema::new()
825                .set_name("wrong_col")
826                .set_type("INTEGER")
827                .set_mode("NULLABLE"),
828            TableFieldSchema::new()
829                .set_name("some_bool")
830                .set_type("BOOLEAN")
831                .set_mode("NULLABLE"),
832            TableFieldSchema::new()
833                .set_name("some_null")
834                .set_type("INTEGER")
835                .set_mode("NULLABLE"),
836        ]);
837        let schema = Arc::new(Schema::new(schema));
838        let row = Row::try_new(raw_row, &schema)?;
839
840        let err = TestRow::try_from(row).unwrap_err();
841        assert!(matches!(err, RowError::ColumnNotFound(col) if col == "custom_int"));
842        Ok(())
843    }
844}