mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-06 13:49:48 +00:00
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:
parent
a7d2703d64
commit
54c5d6bc3c
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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`],
|
||||
|
||||
@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<()> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user