Skip to content

Commit

Permalink
support #[scylla(flatten)] when deriving SerializeRow in order
Browse files Browse the repository at this point in the history
any helper types/methods have been created inside the internal macro
module so as not to increase the public API surface area
  • Loading branch information
nrxus committed Dec 24, 2024
1 parent c06dc3d commit 813a6f1
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 107 deletions.
90 changes: 86 additions & 4 deletions scylla-cql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,19 @@ pub mod _macro_internal {
fn check_missing(self) -> Result<(), SerializationError>;
}

pub trait SerializeRowInOrder {
fn serialize_in_order(
&self,
columns: &mut self::ser::row::ByColumn<'_, '_>,
writer: &mut RowWriter<'_>,
) -> Result<(), SerializationError>;
}

pub mod ser {
pub mod row {
use super::super::{PartialSerializeRowByName, SerializeRowByName};
use super::super::{
PartialSerializeRowByName, SerializeRowByName, SerializeRowInOrder,
};
use crate::{
frame::response::result::ColumnSpec,
types::serialize::{
Expand Down Expand Up @@ -172,9 +182,7 @@ pub mod _macro_internal {
}
}

partial.check_missing()?;

Ok(())
partial.check_missing()
}
}

Expand All @@ -193,6 +201,80 @@ pub mod _macro_internal {
)
})
}

pub struct ByColumn<'i, 'c> {
columns: std::slice::Iter<'i, ColumnSpec<'c>>,
}

impl ByColumn<'_, '_> {
pub fn next<'b, T>(
&mut self,
expected: &str,
value: &impl SerializeValue,
writer: &'b mut RowWriter<'_>,
) -> Result<WrittenCellProof<'b>, SerializationError> {
let spec = self.columns.next().ok_or_else(|| {
mk_typck_err::<T>(BuiltinTypeCheckErrorKind::ValueMissingForColumn {
name: expected.to_owned(),
})
})?;

if spec.name() != expected {
return Err(mk_typck_err::<T>(
BuiltinTypeCheckErrorKind::ColumnNameMismatch {
rust_column_name: expected.to_owned(),
db_column_name: spec.name().to_owned(),
},
));
}

serialize_column::<T>(value, spec, writer)
}

pub fn next_skip_name<'b, T>(
&mut self,
expected: &str,
value: &impl SerializeValue,
writer: &'b mut RowWriter<'_>,
) -> Result<WrittenCellProof<'b>, SerializationError> {
let spec = self.columns.next().ok_or_else(|| {
mk_typck_err::<T>(BuiltinTypeCheckErrorKind::ValueMissingForColumn {
name: expected.to_owned(),
})
})?;

serialize_column::<T>(value, spec, writer)
}

pub fn finish<T>(&mut self) -> Result<(), SerializationError> {
let Some(spec) = self.columns.next() else {
return Ok(());
};

Err(mk_typck_err::<T>(
BuiltinTypeCheckErrorKind::NoColumnWithName {
name: spec.name().to_owned(),
},
))
}
}

pub struct InOrder<'t, T: SerializeRowInOrder>(pub &'t T);

impl<T: SerializeRowInOrder> InOrder<'_, T> {
pub fn serialize(
self,
ctx: &RowSerializationContext,
writer: &mut RowWriter<'_>,
) -> Result<(), SerializationError> {
let mut next_serializer = ByColumn {
columns: ctx.columns().iter(),
};

self.0.serialize_in_order(&mut next_serializer, writer)?;
next_serializer.finish::<T>()
}
}
}
}
}
59 changes: 59 additions & 0 deletions scylla-cql/src/types/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1731,4 +1731,63 @@ pub(crate) mod tests {

assert_eq!(reference, row);
}

#[test]
fn test_flatten_row_serialization_with_enforced_order_and_skip_namecheck() {
#[derive(SerializeRow, Debug)]
#[scylla(crate = crate, flavor = "enforce_order")]
struct OuterColumns {
a: String,
#[scylla(flatten)]
inner_one: InnerColumnsOne,
d: i32,
#[scylla(flatten)]
inner_two: InnerColumnsTwo,
}

#[derive(SerializeRow, Debug)]
#[scylla(crate = crate, flavor = "enforce_order", skip_name_checks)]
struct InnerColumnsOne {
potato: bool,
carrot: f32,
}

#[derive(SerializeRow, Debug)]
#[scylla(crate = crate, flavor = "enforce_order")]
struct InnerColumnsTwo {
e: String,
}

let value = OuterColumns {
a: "A".to_owned(),
inner_one: InnerColumnsOne {
potato: false,
carrot: 2.3,
},
d: 32,
inner_two: InnerColumnsTwo { e: "E".to_owned() },
};

let spec = [
col("a", ColumnType::Text),
col("b", ColumnType::Boolean),
col("c", ColumnType::Float),
col("d", ColumnType::Int),
col("e", ColumnType::Text),
];

let reference = do_serialize(
(
&value.a,
&value.inner_one.potato,
&value.inner_one.carrot,
&value.d,
&value.inner_two.e,
),
&spec,
);
let row = do_serialize(value, &spec);

assert_eq!(reference, row);
}
}
131 changes: 28 additions & 103 deletions scylla-macros/src/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,6 @@ impl Context {
}
}

// `flatten` annotations is not yet supported outside of `match_by_name`
if !matches!(self.attributes.flavor, Flavor::MatchByName) {
if let Some(field) = self.fields.iter().find(|f| f.attrs.flatten) {
let err = darling::Error::custom(
"the `flatten` annotations is only supported wit the `match_by_name` flavor",
)
.with_span(&field.ident);
errors.push(err);
}
}

// Check that no renames are attempted on flattened fields
let rename_flatten_errors = self
.fields
Expand Down Expand Up @@ -189,34 +178,6 @@ impl Context {
errors.finish()?;
Ok(())
}

fn generate_mk_typck_err(&self) -> syn::Stmt {
let crate_path = self.attributes.crate_path();
parse_quote! {
let mk_typck_err = |kind: #crate_path::BuiltinRowTypeCheckErrorKind| -> #crate_path::SerializationError {
#crate_path::SerializationError::new(
#crate_path::BuiltinRowTypeCheckError {
rust_name: ::std::any::type_name::<Self>(),
kind,
}
)
};
}
}

fn generate_mk_ser_err(&self) -> syn::Stmt {
let crate_path = self.attributes.crate_path();
parse_quote! {
let mk_ser_err = |kind: #crate_path::BuiltinRowSerializationErrorKind| -> #crate_path::SerializationError {
#crate_path::SerializationError::new(
#crate_path::BuiltinRowSerializationError {
rust_name: ::std::any::type_name::<Self>(),
kind,
}
)
};
}
}
}

trait Generator {
Expand Down Expand Up @@ -396,73 +357,26 @@ struct ColumnOrderedGenerator<'a> {

impl Generator for ColumnOrderedGenerator<'_> {
fn generate_serialize(&self) -> syn::TraitItemFn {
let mut statements: Vec<syn::Stmt> = Vec::new();

let crate_path = self.ctx.attributes.crate_path();

// Declare a helper lambda for creating errors
statements.push(self.ctx.generate_mk_typck_err());
statements.push(self.ctx.generate_mk_ser_err());

// Create an iterator over fields
statements.push(parse_quote! {
let mut column_iter = ctx.columns().iter();
});

// Serialize each field
for field in self.ctx.fields.iter() {
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 }
let struct_name = &self.ctx.struct_name;
let (impl_generics, ty_generics, where_clause) = self.ctx.generics.split_for_impl();
let columns = self.ctx.fields.iter().map(|f| -> syn::Stmt {
let field = &f.ident;
if f.attrs.flatten {
syn::parse_quote! {
<_ as #crate_path::SerializeRowInOrder>::serialize_in_order(&self.#field, columns, writer)?;
}
} else {
parse_quote! { true }
};
statements.push(parse_quote! {
match column_iter.next() {
Some(spec) => {
if #name_check_expression {
let cell_writer = #crate_path::RowWriter::make_cell_writer(writer);
match <#typ as #crate_path::SerializeValue>::serialize(&self.#rust_field_ident, spec.typ(), cell_writer) {
Ok(_proof) => {},
Err(err) => {
return ::std::result::Result::Err(mk_ser_err(
#crate_path::BuiltinRowSerializationErrorKind::ColumnSerializationFailed {
name: <_ as ::std::borrow::ToOwned>::to_owned(spec.name()),
err,
}
));
}
}
} else {
return ::std::result::Result::Err(mk_typck_err(
#crate_path::BuiltinRowTypeCheckErrorKind::ColumnNameMismatch {
rust_column_name: <_ as ::std::string::ToString>::to_string(#rust_field_name),
db_column_name: <_ as ::std::borrow::ToOwned>::to_owned(spec.name()),
}
));
}
let column = f.column_name();
if self.ctx.attributes.skip_name_checks {
syn::parse_quote! {
columns.next_skip_name::<Self>(#column, &self.#field, writer)?;
}
None => {
return ::std::result::Result::Err(mk_typck_err(
#crate_path::BuiltinRowTypeCheckErrorKind::ValueMissingForColumn {
name: <_ as ::std::string::ToString>::to_string(#rust_field_name),
}
));
} else {
syn::parse_quote! {
columns.next::<Self>(#column, &self.#field, writer)?;
}
}
});
}

// Check whether there are some columns remaining
statements.push(parse_quote! {
if let Some(spec) = column_iter.next() {
return ::std::result::Result::Err(mk_typck_err(
#crate_path::BuiltinRowTypeCheckErrorKind::NoColumnWithName {
name: <_ as ::std::borrow::ToOwned>::to_owned(spec.name()),
}
));
}
});

Expand All @@ -472,8 +386,19 @@ impl Generator for ColumnOrderedGenerator<'_> {
ctx: &#crate_path::RowSerializationContext,
writer: &mut #crate_path::RowWriter<'b>,
) -> ::std::result::Result<(), #crate_path::SerializationError> {
#(#statements)*
::std::result::Result::Ok(())
#[allow(non_local_definitions)]
impl #impl_generics #crate_path::SerializeRowInOrder for #struct_name #ty_generics #where_clause {
fn serialize_in_order(
&self,
columns: &mut #crate_path::ser::row::ByColumn<'_, '_>,
writer: &mut #crate_path::RowWriter<'_>,
) -> Result<(), #crate_path::SerializationError> {
#(#columns)*
Ok(())
}
}

#crate_path::ser::row::InOrder(self).serialize(ctx, writer)
}
}
}
Expand Down

0 comments on commit 813a6f1

Please sign in to comment.