From 0642eb5c5c879c657c30cb07db40c924954d2c29 Mon Sep 17 00:00:00 2001 From: Piotr Dulikowski Date: Mon, 4 Dec 2023 18:00:42 +0100 Subject: [PATCH] serialize: provide macro for quick trait impl from legacy Introduces two procedural macros: - impl_serialize_cql_via_value implements SerializeCql based on an existing Value impl, - impl_serialize_row_via_value_list implements SerializeRow based on an existing ValueList impl. The macros are intended to be used to help people gradually migrate their codebase to the new serialization API in case it is not easy to write a SerializeCql/SerializRow impl right from the beginning. --- scylla-cql/src/types/serialize/row.rs | 91 +++++++++++++++++++++++++ scylla-cql/src/types/serialize/value.rs | 86 +++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/scylla-cql/src/types/serialize/row.rs b/scylla-cql/src/types/serialize/row.rs index 4d8713870f..c2d8a45246 100644 --- a/scylla-cql/src/types/serialize/row.rs +++ b/scylla-cql/src/types/serialize/row.rs @@ -359,6 +359,97 @@ impl_tuples!( 16 ); +/// Implements the [`SerializeRow`] trait for a type, provided that the type +/// already implements the legacy +/// [`ValueList`](crate::frame::value::ValueList) trait. +/// +/// # Note +/// +/// The translation from one trait to another encounters a performance penalty +/// and does not utilize the stronger guarantees of `SerializeRow`. Before +/// resorting to this macro, you should consider other options instead: +/// +/// - If the impl was generated using the `ValueList` procedural macro, you +/// should switch to the `SerializeRow` procedural macro. *The new macro +/// behaves differently by default, so please read its documentation first!* +/// - If the impl was written by hand, it is still preferable to rewrite it +/// manually. You have an opportunity to make your serialization logic +/// type-safe and potentially improve performance. +/// +/// Basically, you should consider using the macro if you have a hand-written +/// impl and the moment it is not easy/not desirable to rewrite it. +/// +/// # Example +/// +/// ```rust +/// # use std::borrow::Cow; +/// # use scylla_cql::frame::value::{Value, ValueList, SerializedResult, SerializedValues}; +/// # use scylla_cql::impl_serialize_row_via_value_list; +/// struct NoGenerics {} +/// impl ValueList for NoGenerics { +/// fn serialized(&self) -> SerializedResult<'_> { +/// ().serialized() +/// } +/// } +/// impl_serialize_row_via_value_list!(NoGenerics); +/// +/// // Generic types are also supported. You must specify the bounds if the +/// // struct/enum contains any. +/// struct WithGenerics(T, U); +/// impl ValueList for WithGenerics { +/// fn serialized(&self) -> SerializedResult<'_> { +/// let mut values = SerializedValues::new(); +/// values.add_value(&self.0); +/// values.add_value(&self.1.clone()); +/// Ok(Cow::Owned(values)) +/// } +/// } +/// impl_serialize_row_via_value_list!(WithGenerics); +/// ``` +#[macro_export] +macro_rules! impl_serialize_row_via_value_list { + ($t:ident$(<$($targ:tt $(: $tbound:tt)?),*>)?) => { + impl $(<$($targ $(: $tbound)?),*>)? $crate::types::serialize::row::SerializeRow + for $t$(<$($targ),*>)? + where + Self: $crate::frame::value::ValueList, + { + fn preliminary_type_check( + _ctx: &$crate::types::serialize::row::RowSerializationContext<'_>, + ) -> ::std::result::Result<(), $crate::types::serialize::SerializationError> { + // No-op - the old interface didn't offer type safety + ::std::result::Result::Ok(()) + } + + fn serialize( + &self, + ctx: &$crate::types::serialize::row::RowSerializationContext<'_>, + writer: &mut W, + ) -> ::std::result::Result<(), $crate::types::serialize::SerializationError> { + $crate::types::serialize::row::serialize_legacy_row(self, ctx, writer) + } + } + }; +} + +/// Serializes an object implementing [`ValueList`] by using the [`RowWriter`] +/// interface. +/// +/// The function first serializes the value with [`ValueList::serialized`], then +/// parses the result and serializes it again with given `RowWriter`. In case +/// or serialized values with names, they are converted to serialized values +/// without names, based on the information about the bind markers provided +/// in the [`RowSerializationContext`]. +/// +/// It is a lazy and inefficient way to implement `RowWriter` via an existing +/// `ValueList` impl. +/// +/// Returns an error if `ValueList::serialized` call failed or, in case of +/// named serialized values, some bind markers couldn't be matched to a +/// named value. +/// +/// See [`impl_serialize_row_via_value_list`] which generates a boilerplate +/// [`SerializeRow`] implementation that uses this function. pub fn serialize_legacy_row( r: &T, ctx: &RowSerializationContext<'_>, diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index 9d436e931b..5d81cdb938 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -983,6 +983,92 @@ fn serialize_mapping<'t, K: SerializeCql + 't, V: SerializeCql + 't, W: CellWrit .map_err(|_| mk_ser_err_named(rust_name, typ, BuiltinSerializationErrorKind::SizeOverflow)) } +/// Implements the [`SerializeCql`] trait for a type, provided that the type +/// already implements the legacy [`Value`](crate::frame::value::Value) trait. +/// +/// # Note +/// +/// The translation from one trait to another encounters a performance penalty +/// and does not utilize the stronger guarantees of `SerializeCql`. Before +/// resorting to this macro, you should consider other options instead: +/// +/// - If the impl was generated using the `Value` procedural macro, you should +/// switch to the `SerializeCql` procedural macro. *The new macro behaves +/// differently by default, so please read its documentation first!* +/// - If the impl was written by hand, it is still preferable to rewrite it +/// manually. You have an opportunity to make your serialization logic +/// type-safe and potentially improve performance. +/// +/// Basically, you should consider using the macro if you have a hand-written +/// impl and the moment it is not easy/not desirable to rewrite it. +/// +/// # Example +/// +/// ```rust +/// # use scylla_cql::frame::value::{Value, ValueTooBig}; +/// # use scylla_cql::impl_serialize_cql_via_value; +/// struct NoGenerics {} +/// impl Value for NoGenerics { +/// fn serialize(&self, _buf: &mut Vec) -> Result<(), ValueTooBig> { +/// Ok(()) +/// } +/// } +/// impl_serialize_cql_via_value!(NoGenerics); +/// +/// // Generic types are also supported. You must specify the bounds if the +/// // struct/enum contains any. +/// struct WithGenerics(T, U); +/// impl Value for WithGenerics { +/// fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { +/// self.0.serialize(buf)?; +/// self.1.clone().serialize(buf)?; +/// Ok(()) +/// } +/// } +/// impl_serialize_cql_via_value!(WithGenerics); +/// ``` +#[macro_export] +macro_rules! impl_serialize_cql_via_value { + ($t:ident$(<$($targ:tt $(: $tbound:tt)?),*>)?) => { + impl $(<$($targ $(: $tbound)?),*>)? $crate::types::serialize::value::SerializeCql + for $t$(<$($targ),*>)? + where + Self: $crate::frame::value::Value, + { + fn preliminary_type_check( + _typ: &$crate::frame::response::result::ColumnType, + ) -> ::std::result::Result<(), $crate::types::serialize::SerializationError> { + // No-op - the old interface didn't offer type safety + ::std::result::Result::Ok(()) + } + + fn serialize( + &self, + _typ: &$crate::frame::response::result::ColumnType, + writer: W, + ) -> ::std::result::Result< + W::WrittenCellProof, + $crate::types::serialize::SerializationError, + > { + $crate::types::serialize::value::serialize_legacy_value(self, writer) + } + } + }; +} + +/// Serializes a value implementing [`Value`] by using the [`CellWriter`] +/// interface. +/// +/// The function first serializes the value with [`Value::serialize`], then +/// parses the result and serializes it again with given `CellWriter`. It is +/// a lazy and inefficient way to implement `CellWriter` via an existing `Value` +/// impl. +/// +/// Returns an error if the result of the `Value::serialize` call was not +/// a properly encoded `[value]` as defined in the CQL protocol spec. +/// +/// See [`impl_serialize_cql_via_value`] which generates a boilerplate +/// [`SerializeCql`] implementation that uses this function. pub fn serialize_legacy_value( v: &T, writer: W,