1use 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#[derive(Clone, Debug)]
63pub struct Row {
64 pub(crate) values: Value,
65 pub(crate) schema: Arc<Schema>,
66}
67
68mod sealed {
69 pub trait ColumnIndex {}
71 impl ColumnIndex for usize {}
72 impl ColumnIndex for &str {}
73 impl ColumnIndex for String {}
74}
75
76pub trait ColumnIndex: sealed::ColumnIndex + std::fmt::Display {
80 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 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 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 let owned_val = std::mem::replace(val, Value::Null);
225 self.convert_value_at(idx, owned_val)
226 }
227
228 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 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}