Skip to content

Commit

Permalink
scylla-macros: add skip_name_checks attribute to SerializeRow
Browse files Browse the repository at this point in the history
Introduces an attribute to `SerializeRow` macro which causes name checks
to be skipped when serializing in `enforce_order` flavor. The motivation
is the same as in the case of `SerializeCql` - the old `ValueList` macro
didn't have access to the bind marker names and types, and disabling the
name check might make it easier for some to transition off the old
macro.
  • Loading branch information
piodul committed Dec 15, 2023
1 parent 344112a commit 39908a6
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
10 changes: 10 additions & 0 deletions scylla-cql/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ pub use scylla_macros::SerializeCql;
/// macro itself, so in those cases the user must provide an alternative path
/// to either the `scylla` or `scylla-cql` crate.
///
/// `#[scylla(skip_name_checks)]
///
/// _Specific only to the `enforce_order` flavor._
///
/// Skips checking Rust field names against names of the columns / bind markers.
/// With this annotation, the generated implementation will allow mismatch
/// between Rust struct field names and the column / bind markers, i.e. it's
/// OK if i-th Rust struct field has a different name than the column / bind
/// marker. The values are still being type-checked.
///
/// # Field attributes
///
/// `#[scylla(rename = "column_or_bind_marker_name")]`
Expand Down
23 changes: 23 additions & 0 deletions scylla-cql/src/types/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,4 +1429,27 @@ mod tests {

assert_eq!(reference, row);
}

#[derive(SerializeRow, Debug)]
#[scylla(crate = crate, flavor = "enforce_order", skip_name_checks)]
struct TestRowWithSkippedNameChecks {
a: String,
b: i32,
}

#[test]
fn test_row_serialization_with_skipped_name_checks() {
let spec = [col("a", ColumnType::Text), col("x", ColumnType::Int)];

let reference = do_serialize(("Ala ma kota", 42i32), &spec);
let row = do_serialize(
TestRowWithSkippedNameChecks {
a: "Ala ma kota".to_owned(),
b: 42,
},
&spec,
);

assert_eq!(reference, row);
}
}
36 changes: 33 additions & 3 deletions scylla-macros/src/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ struct Attributes {

#[darling(default)]
flavor: Flavor,

#[darling(default)]
skip_name_checks: bool,
}

impl Attributes {
Expand Down Expand Up @@ -74,7 +77,7 @@ pub fn derive_serialize_row(tokens_input: TokenStream) -> Result<syn::ItemImpl,
})
.collect::<Result<_, _>>()?;
let ctx = Context { attributes, fields };
ctx.validate()?;
ctx.validate(&input.ident)?;

let gen: Box<dyn Generator> = match ctx.attributes.flavor {
Flavor::MatchByName => Box::new(ColumnSortingGenerator { ctx: &ctx }),
Expand All @@ -94,9 +97,31 @@ pub fn derive_serialize_row(tokens_input: TokenStream) -> Result<syn::ItemImpl,
}

impl Context {
fn validate(&self) -> Result<(), syn::Error> {
fn validate(&self, struct_ident: &syn::Ident) -> Result<(), syn::Error> {
let mut errors = darling::Error::accumulator();

if self.attributes.skip_name_checks {
// Skipping name checks is only available in enforce_order mode
if self.attributes.flavor != Flavor::EnforceOrder {
let err = darling::Error::custom(
"the `skip_name_checks` attribute is only allowed with the `enforce_order` flavor",
)
.with_span(struct_ident);
errors.push(err);
}

// `rename` annotations don't make sense with skipped name checks
for field in self.fields.iter() {
if field.attrs.rename.is_some() {
let err = darling::Error::custom(
"the `rename` annotations don't make sense with `skip_name_checks` attribute",
)
.with_span(&field.ident);
errors.push(err);
}
}
}

// Check for name collisions
let mut used_names = HashMap::<String, &Field>::new();
for field in self.fields.iter() {
Expand Down Expand Up @@ -298,10 +323,15 @@ impl<'a> Generator for ColumnOrderedGenerator<'a> {
let rust_field_ident = &field.ident;
let rust_field_name = field.column_name();
let typ = &field.ty;
let name_check_expression: syn::Expr = if !self.ctx.attributes.skip_name_checks {
parse_quote! { spec.name == #rust_field_name }
} else {
parse_quote! { true }
};
statements.push(parse_quote! {
match column_iter.next() {
Some(spec) => {
if spec.name == #rust_field_name {
if #name_check_expression {
let cell_writer = #crate_path::RowWriter::make_cell_writer(writer);
match <#typ as #crate_path::SerializeCql>::serialize(&self.#rust_field_ident, &spec.typ, cell_writer) {
Ok(_proof) => {},
Expand Down

0 comments on commit 39908a6

Please sign in to comment.