1use base64::Engine;
16use base64::prelude::BASE64_STANDARD;
17#[allow(unused_imports)]
18use wkt::{Struct, Timestamp, Value};
19
20use crate::error::ConvertError;
21
22pub(crate) const BIGQUERY_DATE_FORMAT: &[time::format_description::FormatItem<'static>] =
23 time::macros::format_description!("[year]-[month]-[day]");
24pub(crate) const BIGQUERY_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
25 "[hour padding:none]:[minute padding:none]:[second padding:none]"
26);
27pub(crate) const BIGQUERY_TIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
28 "[hour padding:none]:[minute padding:none]:[second padding:none].[subsecond]"
29);
30pub(crate) const BIGQUERY_DATETIME_FORMAT: &[time::format_description::FormatItem<'static>] =
31 time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
32pub(crate) const BIGQUERY_DATETIME_SUBSEC_FORMAT: &[time::format_description::FormatItem<
33 'static,
34>] = time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]");
35
36#[derive(Clone, Debug, PartialEq)]
37pub(crate) enum SqlValueInner {
38 Null,
39 Bool(bool),
40 Number(serde_json::Number),
41 String(String),
42 Array(Vec<SqlValueInner>),
43 Struct(Vec<(String, SqlValueInner)>),
44}
45
46impl SqlValueInner {
47 pub(crate) fn type_name(&self) -> &'static str {
48 match self {
49 Self::Null => "null",
50 Self::Bool(_) => "bool",
51 Self::Number(_) => "number",
52 Self::String(_) => "string",
53 Self::Array(_) => "array",
54 Self::Struct(_) => "object",
55 }
56 }
57
58 pub(crate) fn from_wkt(value: wkt::Value) -> Self {
59 match value {
60 wkt::Value::Null => Self::Null,
61 wkt::Value::Bool(b) => Self::Bool(b),
62 wkt::Value::Number(n) => Self::Number(n),
63 wkt::Value::String(s) => Self::String(s),
64 wkt::Value::Array(arr) => Self::Array(arr.into_iter().map(Self::from_wkt).collect()),
65 wkt::Value::Object(obj) => Self::Struct(
66 obj.into_iter()
67 .map(|(k, v)| (k, Self::from_wkt(v)))
68 .collect(),
69 ),
70 }
71 }
72}
73
74#[derive(Clone, Debug)]
79pub struct SqlValue {
80 pub(crate) inner: SqlValueInner,
81 _not_unwind_safe: std::marker::PhantomData<dyn Send + Sync + Unpin>,
83}
84
85impl SqlValue {
86 pub(crate) fn from_inner(inner: SqlValueInner) -> Self {
87 Self {
88 inner,
89 _not_unwind_safe: std::marker::PhantomData,
90 }
91 }
92
93 pub fn take<T: FromSql, I: crate::query::ColumnIndex>(
99 &mut self,
100 index: I,
101 ) -> Result<T, ConvertError> {
102 let val = index.take_sql_value(self)?;
103 T::from_value(val)
104 }
105}
106
107pub trait FromSql: Sized {
158 fn from_value(value: SqlValue) -> Result<Self, ConvertError>;
160}
161
162impl FromSql for wkt::Value {
163 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
164 Ok(match value.inner {
165 SqlValueInner::Null => wkt::Value::Null,
166 SqlValueInner::Bool(b) => wkt::Value::Bool(b),
167 SqlValueInner::Number(n) => wkt::Value::Number(n),
168 SqlValueInner::String(s) => wkt::Value::String(s),
169 SqlValueInner::Array(arr) => wkt::Value::Array(
170 arr.into_iter()
171 .map(|v| wkt::Value::from_value(SqlValue::from_inner(v)))
172 .collect::<Result<Vec<_>, _>>()?,
173 ),
174 SqlValueInner::Struct(entries) => wkt::Value::Object(
175 entries
176 .into_iter()
177 .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
178 .collect::<Result<wkt::Struct, ConvertError>>()?,
179 ),
180 })
181 }
182}
183
184impl FromSql for String {
185 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
186 match value.inner {
187 SqlValueInner::String(s) => Ok(s),
188 SqlValueInner::Null => Err(ConvertError::NotNull),
189 other => Err(ConvertError::type_mismatch("string", &other)),
190 }
191 }
192}
193
194impl FromSql for i32 {
195 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
196 match value.inner {
197 SqlValueInner::Number(n) => n
198 .as_i64()
199 .and_then(|v| i32::try_from(v).ok())
200 .ok_or_else(|| ConvertError::Convert("number is not a valid i32".into())),
201 SqlValueInner::String(s) => s
202 .parse::<i32>()
203 .map_err(|e| ConvertError::Convert(Box::new(e))),
204 SqlValueInner::Null => Err(ConvertError::NotNull),
205 other => Err(ConvertError::type_mismatch("number or string", &other)),
206 }
207 }
208}
209
210impl FromSql for i64 {
211 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
212 match value.inner {
213 SqlValueInner::Number(n) => n
214 .as_i64()
215 .ok_or_else(|| ConvertError::Convert("number is not a valid i64".into())),
216 SqlValueInner::String(s) => s
217 .parse::<i64>()
218 .map_err(|e| ConvertError::Convert(Box::new(e))),
219 SqlValueInner::Null => Err(ConvertError::NotNull),
220 other => Err(ConvertError::type_mismatch("number or string", &other)),
221 }
222 }
223}
224
225impl FromSql for f32 {
226 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
227 match value.inner {
228 SqlValueInner::Number(n) => n
229 .as_f64()
230 .map(|v| v as f32)
231 .ok_or_else(|| ConvertError::Convert("number is not a valid f32".into())),
232 SqlValueInner::String(s) => s
233 .parse::<f32>()
234 .map_err(|e| ConvertError::Convert(Box::new(e))),
235 SqlValueInner::Null => Err(ConvertError::NotNull),
236 other => Err(ConvertError::type_mismatch("number or string", &other)),
237 }
238 }
239}
240
241impl FromSql for f64 {
242 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
243 match value.inner {
244 SqlValueInner::Number(n) => n
245 .as_f64()
246 .ok_or_else(|| ConvertError::Convert("number is not a valid f64".into())),
247 SqlValueInner::String(s) => s
248 .parse::<f64>()
249 .map_err(|e| ConvertError::Convert(Box::new(e))),
250 SqlValueInner::Null => Err(ConvertError::NotNull),
251 other => Err(ConvertError::type_mismatch("number or string", &other)),
252 }
253 }
254}
255
256impl FromSql for bool {
257 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
258 match value.inner {
259 SqlValueInner::Bool(b) => Ok(b),
260 SqlValueInner::String(s) => s
261 .parse::<bool>()
262 .map_err(|e| ConvertError::Convert(Box::new(e))),
263 SqlValueInner::Null => Err(ConvertError::NotNull),
264 other => Err(ConvertError::type_mismatch("bool or string", &other)),
265 }
266 }
267}
268
269impl<T: FromSql> FromSql for Option<T> {
270 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
271 match value.inner {
272 SqlValueInner::Null => Ok(None),
273 other => T::from_value(SqlValue::from_inner(other)).map(Some),
274 }
275 }
276}
277
278impl<T: FromSql> FromSql for Vec<T> {
279 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
280 match value.inner {
281 SqlValueInner::Array(arr) => arr
282 .into_iter()
283 .map(|v| T::from_value(SqlValue::from_inner(v)))
284 .collect(),
285 SqlValueInner::Null => Err(ConvertError::NotNull),
286 other => Err(ConvertError::type_mismatch("array", &other)),
287 }
288 }
289}
290
291impl FromSql for wkt::Struct {
292 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
293 match value.inner {
294 SqlValueInner::Struct(entries) => entries
295 .into_iter()
296 .map(|(k, v)| Ok((k, wkt::Value::from_value(SqlValue::from_inner(v))?)))
297 .collect(),
298 SqlValueInner::String(s) => {
299 serde_json::from_str(&s).map_err(|e| ConvertError::Convert(Box::new(e)))
300 }
301 SqlValueInner::Null => Err(ConvertError::NotNull),
302 other => Err(ConvertError::type_mismatch("object or string", &other)),
303 }
304 }
305}
306
307impl FromSql for wkt::Timestamp {
308 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
309 match value.inner {
310 SqlValueInner::String(s) => {
311 let micros = s
312 .parse::<i64>()
313 .map_err(|e| ConvertError::Convert(Box::new(e)))?;
314 timestamp_from_micros(micros)
315 }
316 SqlValueInner::Number(n) => {
317 let micros = n.as_i64().ok_or_else(|| {
318 ConvertError::Convert("timestamp number is not valid i64".into())
319 })?;
320 timestamp_from_micros(micros)
321 }
322 SqlValueInner::Null => Err(ConvertError::NotNull),
323 other => Err(ConvertError::type_mismatch("string or number", &other)),
324 }
325 }
326}
327
328fn timestamp_from_micros(micros: i64) -> Result<wkt::Timestamp, ConvertError> {
329 wkt::Timestamp::new(
330 micros.div_euclid(1_000_000),
331 (micros.rem_euclid(1_000_000) * 1_000) as i32,
332 )
333 .map_err(|e| ConvertError::Convert(Box::new(e)))
334}
335
336impl FromSql for google_cloud_type::model::Date {
337 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
338 match value.inner {
339 SqlValueInner::String(s) => {
340 let date = time::Date::parse(s.as_str(), BIGQUERY_DATE_FORMAT)
341 .map_err(|e| ConvertError::Convert(Box::new(e)))?;
342 Ok(google_cloud_type::model::Date::new()
343 .set_year(date.year())
344 .set_month(u8::from(date.month()) as i32)
345 .set_day(date.day() as i32))
346 }
347 SqlValueInner::Null => Err(ConvertError::NotNull),
348 other => Err(ConvertError::type_mismatch("string", &other)),
349 }
350 }
351}
352
353pub(crate) fn parse_time(s: &str) -> Result<time::Time, ConvertError> {
354 let format = if s.contains('.') {
355 BIGQUERY_TIME_SUBSEC_FORMAT
356 } else {
357 BIGQUERY_TIME_FORMAT
358 };
359 time::Time::parse(s, format).map_err(|e| ConvertError::Convert(Box::new(e)))
360}
361
362impl FromSql for google_cloud_type::model::TimeOfDay {
363 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
364 match value.inner {
365 SqlValueInner::String(s) => {
366 let time = parse_time(s.as_str())?;
367 Ok(google_cloud_type::model::TimeOfDay::new()
368 .set_hours(time.hour() as i32)
369 .set_minutes(time.minute() as i32)
370 .set_seconds(time.second() as i32)
371 .set_nanos(time.nanosecond() as i32))
372 }
373 SqlValueInner::Null => Err(ConvertError::NotNull),
374 other => Err(ConvertError::type_mismatch("string", &other)),
375 }
376 }
377}
378
379impl FromSql for google_cloud_type::model::DateTime {
380 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
381 match value.inner {
382 SqlValueInner::String(s) => {
383 let format = if s.contains('.') {
384 BIGQUERY_DATETIME_SUBSEC_FORMAT
385 } else {
386 BIGQUERY_DATETIME_FORMAT
387 };
388 let dt = time::PrimitiveDateTime::parse(s.as_str(), format)
389 .map_err(|e| ConvertError::Convert(Box::new(e)))?;
390 Ok(google_cloud_type::model::DateTime::new()
391 .set_year(dt.year())
392 .set_month(u8::from(dt.month()) as i32)
393 .set_day(dt.day() as i32)
394 .set_hours(dt.hour() as i32)
395 .set_minutes(dt.minute() as i32)
396 .set_seconds(dt.second() as i32)
397 .set_nanos(dt.nanosecond() as i32))
398 }
399 SqlValueInner::Null => Err(ConvertError::NotNull),
400 other => Err(ConvertError::type_mismatch("string", &other)),
401 }
402 }
403}
404
405impl FromSql for google_cloud_type::model::Decimal {
406 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
407 match value.inner {
408 SqlValueInner::String(s) => Ok(google_cloud_type::model::Decimal::new().set_value(s)),
409 SqlValueInner::Number(n) => {
410 Ok(google_cloud_type::model::Decimal::new().set_value(n.to_string()))
411 }
412 SqlValueInner::Null => Err(ConvertError::NotNull),
413 other => Err(ConvertError::type_mismatch("string or number", &other)),
414 }
415 }
416}
417
418impl FromSql for rust_decimal::Decimal {
419 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
420 match value.inner {
421 SqlValueInner::String(s) => s
422 .trim()
423 .parse::<rust_decimal::Decimal>()
424 .map_err(|e| ConvertError::Convert(Box::new(e))),
425 SqlValueInner::Number(n) => {
426 if let Some(i) = n.as_i64() {
427 Ok(rust_decimal::Decimal::from(i))
428 } else if let Some(u) = n.as_u64() {
429 Ok(rust_decimal::Decimal::from(u))
430 } else {
431 let f = n.as_f64().expect("Number must be i64, u64, or f64");
432 rust_decimal::Decimal::try_from(f)
433 .map_err(|e| ConvertError::Convert(Box::new(e)))
434 }
435 }
436 SqlValueInner::Null => Err(ConvertError::NotNull),
437 other => Err(ConvertError::type_mismatch("string or number", &other)),
438 }
439 }
440}
441
442impl FromSql for Vec<u8> {
443 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
444 match value.inner {
445 SqlValueInner::String(s) => BASE64_STANDARD
446 .decode(s)
447 .map_err(|e| ConvertError::Convert(Box::new(e))),
448 SqlValueInner::Null => Err(ConvertError::NotNull),
449 other => Err(ConvertError::type_mismatch(
450 "string (base64 encoded)",
451 &other,
452 )),
453 }
454 }
455}
456
457impl FromSql for bytes::Bytes {
458 fn from_value(value: SqlValue) -> Result<Self, ConvertError> {
459 Vec::<u8>::from_value(value).map(bytes::Bytes::from)
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate as google_cloud_bigquery;
467 use crate::query::FromSql;
468 use google_cloud_type::model::Decimal;
469 use rust_decimal::Decimal as RustDecimal;
470 use test_case::test_case;
471
472 impl SqlValue {
473 pub(crate) fn new(value: wkt::Value) -> Self {
474 Self::from_inner(SqlValueInner::from_wkt(value))
475 }
476 }
477
478 #[derive(Debug, PartialEq)]
482 enum TestConvertError {
483 NotNull,
484 TypeMismatch(String),
485 Convert(String),
486 MissingField(String),
487 }
488
489 impl TestConvertError {
490 fn type_mismatch(expected: &str) -> Self {
491 Self::TypeMismatch(expected.to_string())
492 }
493 }
494
495 impl From<ConvertError> for TestConvertError {
496 fn from(err: ConvertError) -> Self {
497 match err {
498 ConvertError::NotNull => Self::NotNull,
499 ConvertError::TypeMismatch { expected, .. } => Self::TypeMismatch(expected),
500 ConvertError::Convert(e) => Self::Convert(e.to_string()),
501 ConvertError::MissingField(f) => Self::MissingField(f),
502 }
503 }
504 }
505
506 #[test]
507 fn test_sql_value_traits() {
508 static_assertions::assert_impl_all!(SqlValue: Send, Sync, Unpin, Clone, std::fmt::Debug);
509 static_assertions::assert_not_impl_any!(
510 SqlValue: std::panic::UnwindSafe,
511 std::panic::RefUnwindSafe
512 );
513 }
514
515 #[test_case(wkt::Value::Null => Ok(wkt::Value::Null) ; "value null")]
516 #[test_case(wkt::Value::Bool(true) => Ok(wkt::Value::Bool(true)) ; "value bool")]
517 #[test_case(wkt::Value::Number(42.into()) => Ok(wkt::Value::Number(42.into())) ; "value number")]
518 #[test_case(wkt::Value::String("hello".to_string()) => Ok(wkt::Value::String("hello".to_string())) ; "value string")]
519 #[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")]
520 #[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")]
521 fn test_from_sql_value(value: wkt::Value) -> Result<wkt::Value, TestConvertError> {
522 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
523 }
524
525 #[derive(FromSql, Debug, PartialEq)]
526 struct RawIdentSqlStruct {
527 r#type: String,
528 r#match: i64,
529 }
530
531 #[test]
532 fn test_derive_from_sql_raw_identifier() {
533 let val = wkt::Value::Object(wkt::Struct::from_iter([
534 ("type".to_string(), wkt::Value::String("event".to_string())),
535 ("match".to_string(), wkt::Value::Number(99.into())),
536 ]));
537 let parsed =
538 RawIdentSqlStruct::from_value(SqlValue::new(val)).expect("should strip r# prefix");
539 assert_eq!(
540 parsed,
541 RawIdentSqlStruct {
542 r#type: "event".to_string(),
543 r#match: 99,
544 }
545 );
546 }
547
548 #[test_case(wkt::Value::String("hello".to_string()) => Ok("hello".to_string()) ; "string")]
549 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null string")]
550 #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "type mismatch string")]
551 fn test_from_sql_string(value: wkt::Value) -> Result<String, TestConvertError> {
552 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
553 }
554
555 #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i64 from number")]
556 #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i64 from string")]
557 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i64")]
558 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i64")]
559 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "invalid string as i64")]
560 fn test_from_sql_i64(value: wkt::Value) -> Result<i64, TestConvertError> {
561 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
562 }
563
564 #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f64 from number")]
565 #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f64 from string")]
566 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f64")]
567 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f64")]
568 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid float literal".to_string())) ; "invalid string as f64")]
569 fn test_from_sql_f64(value: wkt::Value) -> Result<f64, TestConvertError> {
570 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
571 }
572
573 #[test_case(wkt::Value::Bool(true) => Ok(true) ; "bool true")]
574 #[test_case(wkt::Value::Bool(false) => Ok(false) ; "bool false")]
575 #[test_case(wkt::Value::String("true".to_string()) => Ok(true) ; "bool from string true")]
576 #[test_case(wkt::Value::String("false".to_string()) => Ok(false) ; "bool from string false")]
577 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bool")]
578 #[test_case(wkt::Value::Number(1.into()) => Err(TestConvertError::type_mismatch("bool or string")) ; "try number as bool")]
579 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("provided string was not `true` or `false`".to_string())) ; "invalid string as bool")]
580 fn test_from_sql_bool(value: wkt::Value) -> Result<bool, TestConvertError> {
581 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
582 }
583
584 #[test_case(wkt::Value::Null => Ok(None) ; "option null")]
585 #[test_case(wkt::Value::Number(123.into()) => Ok(Some(123)) ; "option some i64")]
586 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "option error i64")]
587 fn test_from_sql_option(value: wkt::Value) -> Result<Option<i64>, TestConvertError> {
588 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
589 }
590
591 #[test_case(wkt::Value::Array(vec![wkt::Value::Number(1.into()), wkt::Value::Number(2.into())]) => Ok(vec![1, 2]) ; "vec i64")]
592 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec i64")]
593 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::type_mismatch("array")) ; "vec type mismatch")]
594 #[test_case(wkt::Value::Array(vec![wkt::Value::String("invalid".to_string())]) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "vec element convert error")]
595 fn test_from_sql_vec(value: wkt::Value) -> Result<Vec<i64>, TestConvertError> {
596 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
597 }
598
599 #[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")]
600 #[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")]
601 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "struct null")]
602 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("expected value at line 1 column 1".to_string())) ; "struct invalid json string")]
603 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "struct type mismatch")]
604 fn test_from_sql_struct(value: wkt::Value) -> Result<wkt::Struct, TestConvertError> {
605 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
606 }
607
608 #[test_case(wkt::Value::String("1779982200000000".to_string()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer string")]
609 #[test_case(wkt::Value::Number(1779982200000000i64.into()) => Ok(wkt::Timestamp::new(1779982200, 0).unwrap()) ; "timestamp micro integer number")]
610 #[test_case(wkt::Value::String("2026-05-28T15:30:00Z".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "timestamp rfc3339 string fails")]
611 #[test_case(wkt::Value::Number(serde_json::Number::from_f64(1779982200.5).unwrap()) => Err(TestConvertError::Convert("timestamp number is not valid i64".to_string())) ; "timestamp f64 number fails")]
612 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "timestamp null")]
613 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "timestamp type mismatch")]
614 fn test_from_sql_timestamp(value: wkt::Value) -> Result<wkt::Timestamp, TestConvertError> {
615 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
616 }
617
618 #[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")]
619 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "date null")]
620 #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "date type mismatch")]
621 #[test_case(wkt::Value::String("invalid-date".to_string()) => Err(TestConvertError::Convert("the 'year' component could not be parsed".to_string())) ; "date invalid format")]
622 #[test_case(wkt::Value::String("2026-abc-28".to_string()) => Err(TestConvertError::Convert("the 'month' component could not be parsed".to_string())) ; "date invalid digits")]
623 fn test_from_sql_date(
624 value: wkt::Value,
625 ) -> Result<google_cloud_type::model::Date, TestConvertError> {
626 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
627 }
628
629 #[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")]
630 #[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")]
631 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "time of day null")]
632 #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "time of day type mismatch")]
633 fn test_from_sql_time_of_day(
634 value: wkt::Value,
635 ) -> Result<google_cloud_type::model::TimeOfDay, TestConvertError> {
636 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
637 }
638
639 #[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")]
640 #[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")]
641 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "datetime null")]
642 #[test_case(wkt::Value::Number(123.into()) => Err(TestConvertError::type_mismatch("string")) ; "datetime type mismatch")]
643 fn test_from_sql_datetime(
644 value: wkt::Value,
645 ) -> Result<google_cloud_type::model::DateTime, TestConvertError> {
646 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
647 }
648
649 #[test_case(wkt::Value::Number(123.into()) => Ok(123) ; "i32 from number")]
650 #[test_case(wkt::Value::String("123".to_string()) => Ok(123) ; "i32 from string")]
651 #[test_case(wkt::Value::Number(3_000_000_000i64.into()) => Err(TestConvertError::Convert("number is not a valid i32".to_string())) ; "i32 overflow from number")]
652 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null i32")]
653 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as i32")]
654 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid digit found in string".to_string())) ; "invalid string as i32")]
655 fn test_from_sql_i32(value: wkt::Value) -> Result<i32, TestConvertError> {
656 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
657 }
658
659 #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) => Ok(123.45) ; "f32 from number")]
660 #[test_case(wkt::Value::String("123.45".to_string()) => Ok(123.45) ; "f32 from string")]
661 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null f32")]
662 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("number or string")) ; "try bool as f32")]
663 #[test_case(wkt::Value::String("hello".to_string()) => Err(TestConvertError::Convert("invalid float literal".to_string())) ; "invalid string as f32")]
664 fn test_from_sql_f32(value: wkt::Value) -> Result<f32, TestConvertError> {
665 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
666 }
667
668 #[test_case(wkt::Value::String("123.456".to_string()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from string")]
669 #[test_case(wkt::Value::Number(serde_json::Number::from_f64(123.456).unwrap()) => Ok(Decimal::new().set_value("123.456")) ; "decimal from number")]
670 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null decimal")]
671 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as decimal")]
672 fn test_from_sql_decimal(value: wkt::Value) -> Result<Decimal, TestConvertError> {
673 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
674 }
675
676 #[test_case(wkt::Value::String("123.456".to_string()) => Ok(RustDecimal::from_str_exact("123.456").unwrap()) ; "rust_decimal from string")]
677 #[test_case(wkt::Value::Number((-123i64).into()) => Ok(RustDecimal::from(-123i64)) ; "rust_decimal from i64 number")]
678 #[test_case(wkt::Value::Number(u64::MAX.into()) => Ok(RustDecimal::from(u64::MAX)) ; "rust_decimal from u64 number")]
679 #[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")]
680 #[test_case(wkt::Value::String("99999999999999999999999999999999.123".to_string()) => Err(TestConvertError::Convert("Invalid decimal: overflow from too many digits".to_string())) ; "rust_decimal overflow")]
681 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null rust_decimal")]
682 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string or number")) ; "try bool as rust_decimal")]
683 fn test_from_sql_rust_decimal(value: wkt::Value) -> Result<RustDecimal, TestConvertError> {
684 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
685 }
686
687 #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(vec![1, 2, 3, 4]) ; "vec u8 from base64")]
688 #[test_case(wkt::Value::String("".to_string()) => Ok(vec![]) ; "vec u8 from empty base64")]
689 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null vec u8")]
690 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as vec u8")]
691 fn test_from_sql_vec_u8(value: wkt::Value) -> Result<Vec<u8>, TestConvertError> {
692 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
693 }
694
695 #[test_case(wkt::Value::String("AQIDBA==".to_string()) => Ok(bytes::Bytes::from_static(&[1, 2, 3, 4])) ; "bytes from base64")]
696 #[test_case(wkt::Value::String("".to_string()) => Ok(bytes::Bytes::from_static(&[])) ; "bytes from empty base64")]
697 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null bytes")]
698 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("string (base64 encoded)")) ; "try bool as bytes")]
699 fn test_from_sql_bytes(value: wkt::Value) -> Result<bytes::Bytes, TestConvertError> {
700 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
701 }
702
703 #[test_case("AQIDBA" ; "missing padding")]
704 #[test_case("Not a base64 string" ; "words with spaces")]
705 fn test_from_sql_bytes_invalid_base64(input: &str) {
706 let err = bytes::Bytes::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
707 .unwrap_err();
708 assert!(matches!(err, ConvertError::Convert(_)));
709
710 let err = Vec::<u8>::from_value(SqlValue::new(wkt::Value::String(input.to_string())))
711 .unwrap_err();
712 assert!(matches!(err, ConvertError::Convert(_)));
713 }
714
715 #[derive(FromSql, Debug, PartialEq)]
716 struct TestSqlStruct {
717 name: String,
718 #[bigquery(rename = "custom_int")]
719 some_int: i64,
720 some_bool: bool,
721 }
722
723 #[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")]
724 #[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")]
725 #[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")]
726 #[test_case(wkt::Value::Null => Err(TestConvertError::NotNull) ; "null error")]
727 #[test_case(wkt::Value::String("invalid".to_string()) => Err(TestConvertError::Convert("expected value at line 1 column 1".to_string())) ; "invalid json string")]
728 #[test_case(wkt::Value::Bool(true) => Err(TestConvertError::type_mismatch("object or string")) ; "type mismatch")]
729 fn test_derive_from_sql(value: wkt::Value) -> Result<TestSqlStruct, TestConvertError> {
730 FromSql::from_value(SqlValue::new(value)).map_err(TestConvertError::from)
731 }
732
733 #[derive(FromSql, Debug, PartialEq)]
734 struct ShadowedSqlStruct {
735 iter: i64,
736 obj: i64,
737 arr: i64,
738 value: String,
739 }
740
741 #[test]
742 fn test_derive_from_sql_shadowing_field_names() {
743 let from_obj = ShadowedSqlStruct::from_value(SqlValue::new(wkt::Value::Object(
744 wkt::Struct::from_iter([
745 ("iter".to_string(), wkt::Value::Number(10.into())),
746 ("obj".to_string(), wkt::Value::Number(20.into())),
747 ("arr".to_string(), wkt::Value::Number(30.into())),
748 ("value".to_string(), wkt::Value::String("world".to_string())),
749 ]),
750 )))
751 .expect("should deserialize from object");
752 assert_eq!(
753 from_obj,
754 ShadowedSqlStruct {
755 iter: 10,
756 obj: 20,
757 arr: 30,
758 value: "world".to_string(),
759 }
760 );
761 }
762
763 #[test]
764 fn test_sql_value_take_by_index_and_name() -> anyhow::Result<()> {
765 let mut arr_val = SqlValue::new(wkt::Value::Array(vec![
766 wkt::Value::Number(42.into()),
767 wkt::Value::String("hello".to_string()),
768 ]));
769 assert_eq!(arr_val.take::<i64, _>(0)?, 42);
770 assert_eq!(arr_val.take::<String, _>(1)?, "hello");
771 assert!(matches!(
772 arr_val.take::<i64, _>(2),
773 Err(ConvertError::MissingField(ref f)) if f == "2"
774 ));
775
776 let mut struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
779 ("foo".to_string(), SqlValueInner::Number(10.into())),
780 (
781 "bar".to_string(),
782 SqlValueInner::String("twenty".to_string()),
783 ),
784 ]));
785 assert_eq!(struct_val.take::<i64, _>("foo".to_string())?, 10);
786 assert_eq!(struct_val.take::<Option<i64>, _>("foo")?, None);
788 assert_eq!(struct_val.take::<String, _>(1)?, "twenty");
790
791 let mut json_arr = SqlValue::new(wkt::Value::String(r#"[7, "eight"]"#.to_string()));
793 assert_eq!(json_arr.take::<i64, _>(0)?, 7);
794 assert_eq!(json_arr.take::<String, _>(1)?, "eight");
795
796 let mut invalid_json_arr = SqlValue::new(wkt::Value::String("not-json".to_string()));
798 assert!(matches!(
799 invalid_json_arr.take::<i64, _>(0),
800 Err(ConvertError::Convert(_))
801 ));
802
803 let mut null_val = SqlValue::new(wkt::Value::Null);
805 assert!(matches!(
806 null_val.take::<i64, _>(0),
807 Err(ConvertError::NotNull)
808 ));
809
810 let mut bool_val = SqlValue::new(wkt::Value::Bool(true));
811 assert!(matches!(
812 bool_val.take::<i64, _>(0),
813 Err(ConvertError::TypeMismatch { ref expected, ref got })
814 if expected == "struct, array, or string" && got == "bool"
815 ));
816
817 assert_eq!(SqlValueInner::Null.type_name(), "null");
819 assert_eq!(SqlValueInner::Array(vec![]).type_name(), "array");
820 assert_eq!(SqlValueInner::Struct(vec![]).type_name(), "object");
821
822 Ok(())
823 }
824
825 #[derive(FromSql, Debug, PartialEq)]
826 struct GenericSql<T: Clone + Default, U: std::fmt::Debug> {
827 #[bigquery(rename = "custom_val")]
828 single: T,
829 optional: Option<T>,
830 list: Vec<T>,
831 nested: NestedGenericSql<U>,
832 }
833
834 #[derive(FromSql, Debug, PartialEq)]
835 struct NestedGenericSql<U> {
836 inner_val: U,
837 }
838
839 #[test]
840 fn test_derive_from_sql_generic() {
841 let nested_obj = wkt::Value::Object(wkt::Struct::from_iter([(
842 "inner_val".to_string(),
843 wkt::Value::String("hello".to_string()),
844 )]));
845 let from_obj = GenericSql::<i64, String>::from_value(SqlValue::new(wkt::Value::Object(
846 wkt::Struct::from_iter([
847 ("custom_val".to_string(), wkt::Value::Number(100.into())),
848 ("optional".to_string(), wkt::Value::Null),
849 (
850 "list".to_string(),
851 wkt::Value::Array(vec![
852 wkt::Value::Number(1.into()),
853 wkt::Value::Number(2.into()),
854 wkt::Value::Number(3.into()),
855 ]),
856 ),
857 ("nested".to_string(), nested_obj),
858 ]),
859 )))
860 .expect("should deserialize generic from object");
861
862 assert_eq!(
863 from_obj,
864 GenericSql {
865 single: 100,
866 optional: None,
867 list: vec![1, 2, 3],
868 nested: NestedGenericSql {
869 inner_val: "hello".to_string(),
870 },
871 }
872 );
873 }
874
875 #[derive(FromSql, Debug, PartialEq)]
876 struct GenericSqlWhere<T>
877 where
878 T: std::fmt::Debug + Clone,
879 {
880 val: T,
881 }
882
883 #[test]
884 fn test_derive_from_sql_generic_where_clause() {
885 let val = GenericSqlWhere::<String>::from_value(SqlValue::new(wkt::Value::Object(
886 wkt::Struct::from_iter([("val".to_string(), wkt::Value::String("hello".to_string()))]),
887 )))
888 .expect("should deserialize generic with where clause");
889
890 assert_eq!(
891 val,
892 GenericSqlWhere {
893 val: "hello".to_string(),
894 }
895 );
896 }
897
898 #[derive(FromSql, Debug, PartialEq)]
899 struct GenericSqlDefault<T = i64> {
900 val: T,
901 }
902
903 #[test]
904 fn test_derive_from_sql_generic_default_param() {
905 let val: GenericSqlDefault =
906 GenericSqlDefault::from_value(SqlValue::new(wkt::Value::Object(
907 wkt::Struct::from_iter([("val".to_string(), wkt::Value::Number(42.into()))]),
908 )))
909 .expect("should deserialize generic with default type param");
910
911 assert_eq!(val, GenericSqlDefault { val: 42 });
912 }
913
914 #[derive(FromSql, Debug, PartialEq)]
915 struct GenericTupleSql<T, U>(T, Option<T>, U);
916
917 #[test]
918 fn test_derive_from_sql_generic_tuple_struct() {
919 let struct_val = SqlValue::from_inner(SqlValueInner::Struct(vec![
920 ("".to_string(), SqlValueInner::Number(100.into())),
921 ("".to_string(), SqlValueInner::Null),
922 ("".to_string(), SqlValueInner::String("world".to_string())),
923 ]));
924 let parsed = GenericTupleSql::<i64, String>::from_value(struct_val)
925 .expect("should deserialize generic tuple struct from struct");
926 assert_eq!(parsed, GenericTupleSql(100, None, "world".to_string()));
927
928 let json_val = SqlValue::new(wkt::Value::String(r#"[100, null, "world"]"#.to_string()));
929 let parsed_json = GenericTupleSql::<i64, String>::from_value(json_val)
930 .expect("should deserialize generic tuple struct from JSON array");
931 assert_eq!(parsed_json, GenericTupleSql(100, None, "world".to_string()));
932 }
933}