Skip to content

Commit

Permalink
serialize: provide macro for quick trait impl from legacy
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
piodul committed Dec 4, 2023
1 parent 0932444 commit 7def91e
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
91 changes: 91 additions & 0 deletions scylla-cql/src/types/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: Clone>(T, U);
/// impl<T: Value, U: Clone + Value> ValueList for WithGenerics<T, U> {
/// 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<T, U: Clone>);
/// ```
#[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<W: $crate::types::serialize::writers::RowWriter>(
&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<T: ValueList>(
r: &T,
ctx: &RowSerializationContext<'_>,
Expand Down
86 changes: 86 additions & 0 deletions scylla-cql/src/types/serialize/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>) -> 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: Clone>(T, U);
/// impl<T: Value, U: Clone + Value> Value for WithGenerics<T, U> {
/// fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
/// self.0.serialize(buf)?;
/// self.1.clone().serialize(buf)?;
/// Ok(())
/// }
/// }
/// impl_serialize_cql_via_value!(WithGenerics<T, U: Clone>);
/// ```
#[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<W: $crate::types::serialize::writers::CellWriter>(
&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<T: Value, W: CellWriter>(
v: &T,
writer: W,
Expand Down

0 comments on commit 7def91e

Please sign in to comment.