derive FromRow: sqlx(default) for all fields (#2801)

* derive FromRow: sqlx(default) for all fields

* error in test_struct_default fixed

* derive FromRow: struct level sqlx(default) requires and uses Default implementation

* Update sqlx-core/src/from_row.rs

Co-authored-by: Austin Bonander <austin.bonander@gmail.com>

* Update sqlx-core/src/from_row.rs

Co-authored-by: Austin Bonander <austin.bonander@gmail.com>

* satify cargo fmt

---------

Co-authored-by: Austin Bonander <austin.bonander@gmail.com>
This commit is contained in:
Gregor Giesen 2023-10-18 01:13:40 +02:00 committed by GitHub
parent a7d2703d64
commit 54c5d6bc3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 8 deletions

16
Cargo.lock generated
View File

@ -3066,7 +3066,7 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"anyhow",
"async-std",
@ -3095,7 +3095,7 @@ dependencies = [
[[package]]
name = "sqlx-cli"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"anyhow",
"assert_cmd",
@ -3121,7 +3121,7 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"ahash 0.8.3",
"async-io",
@ -3307,7 +3307,7 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"proc-macro2",
"quote",
@ -3318,7 +3318,7 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"async-std",
"dotenvy",
@ -3343,7 +3343,7 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"atoi",
"base64 0.21.0",
@ -3388,7 +3388,7 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"atoi",
"base64 0.21.0",
@ -3434,7 +3434,7 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"atoi",
"chrono",

View File

@ -91,6 +91,26 @@ 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 the struct has an implementation for [`Default`], you can use the `default``
/// attribute at the struct level rather than for each single field. If a field does not appear in the result,
/// its value is taken from the `Default` implementation for the struct.
/// For example:
///
/// ```rust, ignore
/// #[derive(Default, sqlx::FromRow)]
/// #[sqlx(default)]
/// struct Options {
/// option_a: Option<i32>,
/// option_b: Option<String>,
/// option_c: Option<bool>,
/// }
/// ```
///
/// For a derived `Default` implementation this effectively populates each missing field
/// with `Default::default()`, but a manual `Default` implementation can provide
/// different placeholder values, if applicable.
///
/// This is similar to how `#[serde(default)]` behaves.
/// ### `flatten`
///
/// If you want to handle a field that implements [`FromRow`],

View File

@ -57,6 +57,7 @@ pub struct SqlxContainerAttributes {
pub rename_all: Option<RenameAll>,
pub repr: Option<Ident>,
pub no_pg_array: bool,
pub default: bool,
}
pub struct SqlxChildAttributes {
@ -74,6 +75,7 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContai
let mut type_name = None;
let mut rename_all = None;
let mut no_pg_array = None;
let mut default = None;
for attr in input
.iter()
@ -129,6 +131,10 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContai
)
}
Meta::Path(p) if p.is_ident("default") => {
try_set!(default, true, value)
}
u => fail!(u, "unexpected attribute"),
},
u => fail!(u, "unexpected attribute"),
@ -156,6 +162,7 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result<SqlxContai
type_name,
rename_all,
no_pg_array: no_pg_array.unwrap_or(false),
default: default.unwrap_or(false),
})
}

View File

@ -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> {
@ -148,6 +159,13 @@ fn expand_derive_from_row_struct(
},
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?;
@ -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 {

View File

@ -621,6 +621,41 @@ async fn test_default() -> anyhow::Result<()> {
Ok(())
}
#[cfg(feature = "macros")]
#[sqlx_macros::test]
async fn test_struct_default() -> anyhow::Result<()> {
#[derive(Debug, sqlx::FromRow)]
#[sqlx(default)]
struct HasDefault {
not_default: Option<i32>,
default_a: Option<String>,
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"#)
.fetch_one(&mut conn)
.await?;
println!("{has_default:?}");
assert_eq!(has_default.not_default, Some(1));
assert_eq!(has_default.default_a, None);
assert_eq!(has_default.default_b, Some(0));
Ok(())
}
#[cfg(feature = "macros")]
#[sqlx_macros::test]
async fn test_flatten() -> anyhow::Result<()> {