Skip to content

Commit

Permalink
derive FromRow: struct level sqlx(default) requires and uses Default …
Browse files Browse the repository at this point in the history
…implementation
  • Loading branch information
grgi committed Oct 8, 2023
1 parent 4f4f57c commit bbe1eaa
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 5 deletions.
7 changes: 4 additions & 3 deletions sqlx-core/src/from_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,13 @@ use crate::{error::Error, row::Row};
/// will set the value of the field `location` to the default value of `Option<String>`,
/// which is `None`.
///
/// Moreover, if all field types have an implementation for [`Default`], you can use the `default``
/// attribute at the struct level rather than each single field.
/// Moreover, if the struct has an implementation for [`Default`], you can use the `default``
/// attribute at the struct level rather than for each single field. This way the default
/// value of each attribute is optained from the particular `Default` implementation.
/// For example:
///
/// ```rust, ignore
/// #[derive(sqlx::FromRow)]
/// #[derive(Default, sqlx::FromRow)]
/// #[sqlx(default)]
/// struct Options {
/// option_a: Option<i32>,
Expand Down
22 changes: 21 additions & 1 deletion sqlx-macros-core/src/derives/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ fn expand_derive_from_row_struct(

let container_attributes = parse_container_attributes(&input.attrs)?;

let default_instance: Option<Stmt>;

if container_attributes.default {
predicates.push(parse_quote!(#ident: ::std::default::Default));
default_instance = Some(parse_quote!(
let __default = #ident::default();
));
} else {
default_instance = None;
}

let reads: Vec<Stmt> = fields
.iter()
.filter_map(|field| -> Option<Stmt> {
Expand Down Expand Up @@ -141,13 +152,20 @@ fn expand_derive_from_row_struct(
},
};

if attributes.default || container_attributes.default {
if attributes.default {
Some(parse_quote!(let #id: #ty = #expr.or_else(|e| match e {
::sqlx::Error::ColumnNotFound(_) => {
::std::result::Result::Ok(Default::default())
},
e => ::std::result::Result::Err(e)
})?;))
} else if container_attributes.default {
Some(parse_quote!(let #id: #ty = #expr.or_else(|e| match e {
::sqlx::Error::ColumnNotFound(_) => {
::std::result::Result::Ok(__default.#id)
},
e => ::std::result::Result::Err(e)
})?;))
} else {
Some(parse_quote!(
let #id: #ty = #expr?;
Expand All @@ -164,6 +182,8 @@ fn expand_derive_from_row_struct(
#[automatically_derived]
impl #impl_generics ::sqlx::FromRow<#lifetime, R> for #ident #ty_generics #where_clause {
fn from_row(row: &#lifetime R) -> ::sqlx::Result<Self> {
#default_instance

#(#reads)*

::std::result::Result::Ok(#ident {
Expand Down
12 changes: 11 additions & 1 deletion tests/postgres/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,16 @@ async fn test_struct_default() -> anyhow::Result<()> {
default_b: Option<i32>,
}

impl Default for HasDefault {
fn default() -> HasDefault {
HasDefault {
not_default: None,
default_a: None,
default_b: Some(0),
}
}
}

let mut conn = new::<Postgres>().await?;

let has_default: HasDefault = sqlx::query_as(r#"SELECT 1 AS not_default"#)
Expand All @@ -641,7 +651,7 @@ async fn test_struct_default() -> anyhow::Result<()> {

assert_eq!(has_default.not_default, Some(1));
assert_eq!(has_default.default_a, None);
assert_eq!(has_default.default_b, None);
assert_eq!(has_default.default_b, Some(0));

Ok(())
}
Expand Down

0 comments on commit bbe1eaa

Please sign in to comment.