diff --git a/sqlx-core/src/from_row.rs b/sqlx-core/src/from_row.rs index 9c647d370a..8dc5b8c7fa 100644 --- a/sqlx-core/src/from_row.rs +++ b/sqlx-core/src/from_row.rs @@ -1,4 +1,9 @@ -use crate::{error::Error, row::Row}; +use std::marker::PhantomData; + +use crate::{ + error::{Error, UnexpectedNullError}, + row::Row, +}; /// A record that can be built from a row returned by the database. /// @@ -487,3 +492,47 @@ impl_from_row_for_tuple!( (14) -> T15; (15) -> T16; ); + +pub struct Wrapper(pub PhantomData); + +pub trait FromOptRow<'r, R, T> { + fn __from_row(&self, row: &'r R) -> Result; +} + +impl<'r, R, T> FromOptRow<'r, R, Option> for Wrapper> +where + R: Row, + T: FromRow<'r, R>, +{ + fn __from_row(&self, row: &'r R) -> Result, Error> { + let value = T::from_row(row).map(Some); + if let Err(Error::ColumnDecode { source, .. }) = value.as_ref() { + if let Some(UnexpectedNullError) = source.downcast_ref() { + return Ok(None); + } + } + value + } +} + +impl<'r, R, T> FromOptRow<'r, R, T> for &Wrapper +where + R: Row, + T: FromRow<'r, R>, +{ + fn __from_row(&self, row: &'r R) -> Result { + T::from_row(row) + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __from_opt_row { + ($t:ty, $row:expr) => {{ + use std::marker::PhantomData; + use $crate::from_row::{FromOptRow, Wrapper}; + let wrapper = Wrapper(PhantomData::<$t>); + let value = (&wrapper).__from_row($row); + value + }}; +} diff --git a/sqlx-macros-core/src/derives/row.rs b/sqlx-macros-core/src/derives/row.rs index 5f7b6dca70..d46c400778 100644 --- a/sqlx-macros-core/src/derives/row.rs +++ b/sqlx-macros-core/src/derives/row.rs @@ -108,14 +108,12 @@ fn expand_derive_from_row_struct( } // Flatten (true, None, false) => { - predicates.push(parse_quote!(#ty: ::sqlx::FromRow<#lifetime, R>)); - parse_quote!(<#ty as ::sqlx::FromRow<#lifetime, R>>::from_row(__row)) + parse_quote!(::sqlx::__from_opt_row!(#ty, __row)) } // Flatten + Try from (true, Some(try_from), false) => { - predicates.push(parse_quote!(#try_from: ::sqlx::FromRow<#lifetime, R>)); parse_quote!( - <#try_from as ::sqlx::FromRow<#lifetime, R>>::from_row(__row) + ::sqlx::__from_opt_row!(#try_from, __row) .and_then(|v| { <#ty as ::std::convert::TryFrom::<#try_from>>::try_from(v) .map_err(|e| { diff --git a/src/lib.rs b/src/lib.rs index 870fa703c5..0fbf7df945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,8 @@ pub use sqlx_core::connection::{ConnectOptions, Connection}; pub use sqlx_core::database::{self, Database}; pub use sqlx_core::describe::Describe; pub use sqlx_core::executor::{Execute, Executor}; -pub use sqlx_core::from_row::FromRow; +pub use sqlx_core::from_row::{FromRow, Wrapper, FromOptRow}; +pub use sqlx_core::__from_opt_row; pub use sqlx_core::pool::{self, Pool}; #[doc(hidden)] pub use sqlx_core::query::query_with_result as __query_with_result;