Skip to content

Commit

Permalink
DeserializeValue: default_when_null attribute support
Browse files Browse the repository at this point in the history
By default, if DB provides null value in serialized data and the
corresponding Rust type expects non-null value, deserialization fails.
Instead, if `default_when_null` is specified for a field,
deserialization yields `Default::default()`.
This is important in production: when added a field to a UDT,
the existing UDT instances have the new field filled with null, even if
the data represented makes no sense to allow nulls.
By using `default_when_null`, clients can handle such situation without
using `Option` in their Rust struct.
  • Loading branch information
wprzytula committed Jun 27, 2024
1 parent cef8406 commit 9330d41
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 3 deletions.
39 changes: 36 additions & 3 deletions scylla-macros/src/deserialize/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ struct Field {
#[darling(rename = "allow_missing")]
default_when_missing: bool,

// If true, then - if this field is present among UDT fields metadata
// but at the same time missing from serialized data or set to null
// - it will be initialized to Default::default().
#[darling(default)]
default_when_null: bool,

// If set, then deserializes from the UDT field with this particular name
// instead of the Rust field name.
#[darling(default)]
Expand Down Expand Up @@ -367,6 +373,7 @@ impl<'sd> DeserializeAssumeOrderGenerator<'sd> {
let deserializer = field.deserialize_target();
let constraint_lifetime = self.0.constraint_lifetime();
let default_when_missing = field.default_when_missing;
let default_when_null = field.default_when_null;
let skip_name_checks = self.0.attrs.skip_name_checks;

let deserialize: syn::Expr = parse_quote! {
Expand All @@ -380,6 +387,20 @@ impl<'sd> DeserializeAssumeOrderGenerator<'sd> {
))?
};

let maybe_default_deserialize: syn::Expr = if default_when_null {
parse_quote! {
if value.is_none() {
::std::default::Default::default()
} else {
#deserialize
}
}
} else {
parse_quote! {
#deserialize
}
};

// Action performed in case of field name mismatch.
let name_mismatch: syn::Expr = if default_when_missing {
parse_quote! {
Expand All @@ -406,12 +427,12 @@ impl<'sd> DeserializeAssumeOrderGenerator<'sd> {

let maybe_name_check_and_deserialize_or_save: syn::Expr = if skip_name_checks {
parse_quote! {
#deserialize
#maybe_default_deserialize
}
} else {
parse_quote! {
if #cql_name_literal == cql_field_name {
#deserialize
#maybe_default_deserialize
} else {
#name_mismatch
}
Expand Down Expand Up @@ -723,6 +744,18 @@ impl<'sd> DeserializeUnorderedGenerator<'sd> {
))?
};

let deserialize_action: syn::Expr = if field.default_when_null {
parse_quote! {
if value.is_some() {
#do_deserialize
} else {
::std::default::Default::default()
}
}
} else {
do_deserialize
};

parse_quote! {
{
assert!(
Expand All @@ -738,7 +771,7 @@ impl<'sd> DeserializeUnorderedGenerator<'sd> {
let value = value.flatten();

#deserialize_field = ::std::option::Option::Some(
#do_deserialize
#deserialize_action
);
}
}
Expand Down
5 changes: 5 additions & 0 deletions scylla/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ pub use scylla_cql::macros::SerializeRow;
/// If the UDT definition does not contain this field, it will be initialized
/// with `Default::default()`.
///
/// `#[scylla(default_when_null)]`
///
/// If the value of the field received from DB is null, the field will be
/// initialized with `Default::default()`.
///
/// `#[scylla(rename = "field_name")`
///
/// By default, the generated implementation will try to match the Rust field
Expand Down

0 comments on commit 9330d41

Please sign in to comment.