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