diff --git a/scylla-cql/src/frame/value_tests.rs b/scylla-cql/src/frame/value_tests.rs index 280d5d055b..0ded4b4ed0 100644 --- a/scylla-cql/src/frame/value_tests.rs +++ b/scylla-cql/src/frame/value_tests.rs @@ -1,7 +1,7 @@ use crate::frame::{response::result::CqlValue, types::RawValue, value::BatchValuesIterator}; use crate::types::serialize::row::{RowSerializationContext, SerializeRow}; use crate::types::serialize::value::SerializeCql; -use crate::types::serialize::{BufBackedCellWriter, BufBackedRowWriter}; +use crate::types::serialize::{CellWriter, RowWriter}; use super::response::result::{ColumnSpec, ColumnType, TableSpec}; use super::value::{ @@ -24,10 +24,8 @@ where let mut result: Vec = Vec::new(); Value::serialize(&val, &mut result).unwrap(); - T::preliminary_type_check(&typ).unwrap(); - let mut new_result: Vec = Vec::new(); - let writer = BufBackedCellWriter::new(&mut new_result); + let writer = CellWriter::new(&mut new_result); SerializeCql::serialize(&val, &typ, writer).unwrap(); assert_eq!(result, new_result); @@ -37,7 +35,7 @@ where fn serialized_only_new(val: T, typ: ColumnType) -> Vec { let mut result: Vec = Vec::new(); - let writer = BufBackedCellWriter::new(&mut result); + let writer = CellWriter::new(&mut result); SerializeCql::serialize(&val, &typ, writer).unwrap(); result } @@ -995,9 +993,8 @@ fn serialize_values( serialized.write_to_request(&mut old_serialized); let ctx = RowSerializationContext { columns }; - ::preliminary_type_check(&ctx).unwrap(); let mut new_serialized = vec![0, 0]; - let mut writer = BufBackedRowWriter::new(&mut new_serialized); + let mut writer = RowWriter::new(&mut new_serialized); ::serialize(&vl, &ctx, &mut writer).unwrap(); let value_count: u16 = writer.value_count().try_into().unwrap(); let is_empty = writer.value_count() == 0; @@ -1014,9 +1011,8 @@ fn serialize_values( fn serialize_values_only_new(vl: T, columns: &[ColumnSpec]) -> Vec { let ctx = RowSerializationContext { columns }; - ::preliminary_type_check(&ctx).unwrap(); let mut serialized = vec![0, 0]; - let mut writer = BufBackedRowWriter::new(&mut serialized); + let mut writer = RowWriter::new(&mut serialized); ::serialize(&vl, &ctx, &mut writer).unwrap(); let value_count: u16 = writer.value_count().try_into().unwrap(); let is_empty = writer.value_count() == 0; diff --git a/scylla-cql/src/types/serialize/mod.rs b/scylla-cql/src/types/serialize/mod.rs index 617fbc5f88..230462759d 100644 --- a/scylla-cql/src/types/serialize/mod.rs +++ b/scylla-cql/src/types/serialize/mod.rs @@ -6,10 +6,7 @@ pub mod row; pub mod value; pub mod writers; -pub use writers::{ - BufBackedCellValueBuilder, BufBackedCellWriter, BufBackedRowWriter, CellValueBuilder, - CellWriter, CountingCellWriter, RowWriter, -}; +pub use writers::{CellValueBuilder, CellWriter, RowWriter}; #[derive(Debug, Clone, Error)] pub struct SerializationError(Arc); diff --git a/scylla-cql/src/types/serialize/row.rs b/scylla-cql/src/types/serialize/row.rs index b5fd862cee..d8702100b6 100644 --- a/scylla-cql/src/types/serialize/row.rs +++ b/scylla-cql/src/types/serialize/row.rs @@ -10,7 +10,7 @@ use crate::frame::value::{SerializedValues, ValueList}; use crate::frame::{response::result::ColumnSpec, types::RawValue}; use super::value::SerializeCql; -use super::{CellWriter, RowWriter, SerializationError}; +use super::{RowWriter, SerializationError}; /// Contains information needed to serialize a row. pub struct RowSerializationContext<'a> { @@ -33,25 +33,11 @@ impl<'a> RowSerializationContext<'a> { } pub trait SerializeRow { - /// Checks if it _might_ be possible to serialize the row according to the - /// information in the context. - /// - /// This function is intended to serve as an optimization in the future, - /// if we were ever to introduce prepared statements parametrized by types. - /// - /// Sometimes, a row cannot be fully type checked right away without knowing - /// the exact values of the columns (e.g. when deserializing to `CqlValue`), - /// but it's fine to do full type checking later in `serialize`. - fn preliminary_type_check(ctx: &RowSerializationContext<'_>) -> Result<(), SerializationError>; - /// Serializes the row according to the information in the given context. - /// - /// The function may assume that `preliminary_type_check` was called, - /// though it must not do anything unsafe if this assumption does not hold. - fn serialize( + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError>; fn is_empty(&self) -> bool; @@ -59,15 +45,10 @@ pub trait SerializeRow { macro_rules! fallback_impl_contents { () => { - fn preliminary_type_check( - _ctx: &RowSerializationContext<'_>, - ) -> Result<(), SerializationError> { - Ok(()) - } - fn serialize( + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { serialize_legacy_row(self, ctx, writer) } @@ -80,8 +61,10 @@ macro_rules! fallback_impl_contents { macro_rules! impl_serialize_row_for_unit { () => { - fn preliminary_type_check( + fn serialize( + &self, ctx: &RowSerializationContext<'_>, + _writer: &mut RowWriter, ) -> Result<(), SerializationError> { if !ctx.columns().is_empty() { return Err(mk_typck_err::( @@ -91,14 +74,6 @@ macro_rules! impl_serialize_row_for_unit { }, )); } - Ok(()) - } - - fn serialize( - &self, - _ctx: &RowSerializationContext<'_>, - _writer: &mut W, - ) -> Result<(), SerializationError> { // Row is empty - do nothing Ok(()) } @@ -120,26 +95,10 @@ impl SerializeRow for [u8; 0] { macro_rules! impl_serialize_row_for_slice { () => { - fn preliminary_type_check( - ctx: &RowSerializationContext<'_>, - ) -> Result<(), SerializationError> { - // While we don't know how many columns will be there during serialization, - // we can at least check that all provided columns match T. - for col in ctx.columns() { - ::preliminary_type_check(&col.typ).map_err(|err| { - mk_typck_err::(BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed { - name: col.name.clone(), - err, - }) - })?; - } - Ok(()) - } - - fn serialize( + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { if ctx.columns().len() != self.len() { return Err(mk_typck_err::( @@ -181,26 +140,10 @@ impl SerializeRow for Vec { macro_rules! impl_serialize_row_for_map { () => { - fn preliminary_type_check( - ctx: &RowSerializationContext<'_>, - ) -> Result<(), SerializationError> { - // While we don't know the column count or their names, - // we can go over all columns and check that their types match T. - for col in ctx.columns() { - ::preliminary_type_check(&col.typ).map_err(|err| { - mk_typck_err::(BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed { - name: col.name.clone(), - err, - }) - })?; - } - Ok(()) - } - - fn serialize( + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { // Unfortunately, column names aren't guaranteed to be unique. // We need to track not-yet-used columns in order to see @@ -219,8 +162,8 @@ macro_rules! impl_serialize_row_for_map { Some(v) => { ::serialize(v, &col.typ, writer.make_cell_writer()) .map_err(|err| { - mk_typck_err::( - BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed { + mk_ser_err::( + BuiltinSerializationErrorKind::ColumnSerializationFailed { name: col.name.clone(), err, }, @@ -267,15 +210,11 @@ impl SerializeRow for HashMap<&str, T, S> { impl_serialize_row_for_map!(); } -impl SerializeRow for &T { - fn preliminary_type_check(ctx: &RowSerializationContext<'_>) -> Result<(), SerializationError> { - ::preliminary_type_check(ctx) - } - - fn serialize( +impl SerializeRow for &T { + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { ::serialize(self, ctx, writer) } @@ -302,34 +241,10 @@ macro_rules! impl_tuple { $length:expr ) => { impl<$($typs: SerializeCql),*> SerializeRow for ($($typs,)*) { - fn preliminary_type_check( - ctx: &RowSerializationContext<'_>, - ) -> Result<(), SerializationError> { - match ctx.columns() { - [$($tidents),*] => { - $( - <$typs as SerializeCql>::preliminary_type_check(&$tidents.typ).map_err(|err| { - mk_typck_err::(BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed { - name: $tidents.name.clone(), - err, - }) - })?; - )* - } - _ => return Err(mk_typck_err::( - BuiltinTypeCheckErrorKind::WrongColumnCount { - actual: $length, - asked_for: ctx.columns().len(), - }, - )), - }; - Ok(()) - } - - fn serialize( + fn serialize( &self, ctx: &RowSerializationContext<'_>, - writer: &mut W, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { let ($($tidents,)*) = match ctx.columns() { [$($tidents),*] => ($($tidents,)*), @@ -445,17 +360,10 @@ macro_rules! impl_serialize_row_via_value_list { 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( + fn serialize( &self, ctx: &$crate::types::serialize::row::RowSerializationContext<'_>, - writer: &mut W, + writer: &mut $crate::types::serialize::writers::RowWriter, ) -> ::std::result::Result<(), $crate::types::serialize::SerializationError> { $crate::types::serialize::row::serialize_legacy_row(self, ctx, writer) } @@ -492,7 +400,7 @@ macro_rules! impl_serialize_row_via_value_list { pub fn serialize_legacy_row( r: &T, ctx: &RowSerializationContext<'_>, - writer: &mut impl RowWriter, + writer: &mut RowWriter, ) -> Result<(), SerializationError> { let serialized = ::serialized(r).map_err(|err| SerializationError(Arc::new(err)))?; @@ -596,12 +504,6 @@ pub enum BuiltinTypeCheckErrorKind { /// A value required by the statement is not provided by the Rust type. ColumnMissingForValue { name: String }, - - /// One of the columns failed to type check. - ColumnTypeCheckFailed { - name: String, - err: SerializationError, - }, } impl Display for BuiltinTypeCheckErrorKind { @@ -622,9 +524,6 @@ impl Display for BuiltinTypeCheckErrorKind { "value for column {name} was provided, but there is no bind marker for this column in the query" ) } - BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed { name, err } => { - write!(f, "failed to check column {name}: {err}") - } } } } @@ -660,7 +559,7 @@ pub enum ValueListToSerializeRowAdapterError { mod tests { use crate::frame::response::result::{ColumnSpec, ColumnType, TableSpec}; use crate::frame::value::{MaybeUnset, SerializedValues, ValueList}; - use crate::types::serialize::BufBackedRowWriter; + use crate::types::serialize::RowWriter; use super::{RowSerializationContext, SerializeRow}; @@ -688,7 +587,7 @@ mod tests { <_ as ValueList>::write_to_request(&row, &mut legacy_data).unwrap(); let mut new_data = Vec::new(); - let mut new_data_writer = BufBackedRowWriter::new(&mut new_data); + let mut new_data_writer = RowWriter::new(&mut new_data); let ctx = RowSerializationContext { columns: &[ col_spec("a", ColumnType::Int), @@ -725,7 +624,7 @@ mod tests { unsorted_row.add_named_value("c", &None::).unwrap(); let mut unsorted_row_data = Vec::new(); - let mut unsorted_row_data_writer = BufBackedRowWriter::new(&mut unsorted_row_data); + let mut unsorted_row_data_writer = RowWriter::new(&mut unsorted_row_data); let ctx = RowSerializationContext { columns: &[ col_spec("a", ColumnType::Int), @@ -740,4 +639,37 @@ mod tests { // Skip the value count assert_eq!(&sorted_row_data[2..], unsorted_row_data); } + + #[test] + fn test_dyn_serialize_row() { + let row = ( + 1i32, + "Ala ma kota", + None::, + MaybeUnset::Unset::, + ); + let ctx = RowSerializationContext { + columns: &[ + col_spec("a", ColumnType::Int), + col_spec("b", ColumnType::Text), + col_spec("c", ColumnType::BigInt), + col_spec("d", ColumnType::Ascii), + ], + }; + + let mut typed_data = Vec::new(); + let mut typed_data_writer = RowWriter::new(&mut typed_data); + <_ as SerializeRow>::serialize(&row, &ctx, &mut typed_data_writer).unwrap(); + + let row = &row as &dyn SerializeRow; + let mut erased_data = Vec::new(); + let mut erased_data_writer = RowWriter::new(&mut erased_data); + <_ as SerializeRow>::serialize(&row, &ctx, &mut erased_data_writer).unwrap(); + + assert_eq!( + typed_data_writer.value_count(), + erased_data_writer.value_count(), + ); + assert_eq!(typed_data, erased_data); + } } diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index 5d81cdb938..37244f7073 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -24,42 +24,28 @@ use crate::frame::value::{ #[cfg(feature = "chrono")] use crate::frame::value::ValueOverflow; -use super::{CellValueBuilder, CellWriter, SerializationError}; +use super::writers::WrittenCellProof; +use super::{CellWriter, SerializationError}; pub trait SerializeCql { - /// Given a CQL type, checks if it _might_ be possible to serialize to that type. - /// - /// This function is intended to serve as an optimization in the future, - /// if we were ever to introduce prepared statements parametrized by types. - /// - /// Some types cannot be type checked without knowing the exact value, - /// this is the case e.g. for `CqlValue`. It's also fine to do it later in - /// `serialize`. - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError>; - /// Serializes the value to given CQL type. - /// - /// The function may assume that `preliminary_type_check` was called, - /// though it must not do anything unsafe if this assumption does not hold. - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result; -} - -macro_rules! impl_exact_preliminary_type_check { - ($($cql:tt),*) => { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - $(ColumnType::$cql)|* => Ok(()), - _ => Err(mk_typck_err::( - typ, - BuiltinTypeCheckErrorKind::MismatchedType { - expected: &[$(ColumnType::$cql),*], - } - )) - } + writer: CellWriter<'b>, + ) -> Result, SerializationError>; +} + +macro_rules! exact_type_check { + ($typ:ident, $($cql:tt),*) => { + match $typ { + $(ColumnType::$cql)|* => {}, + _ => return Err(mk_typck_err::( + $typ, + BuiltinTypeCheckErrorKind::MismatchedType { + expected: &[$(ColumnType::$cql),*], + } + )) } }; } @@ -69,11 +55,11 @@ macro_rules! impl_serialize_via_writer { impl_serialize_via_writer!(|$me, _typ, $writer| $e); }; (|$me:ident, $typ:ident, $writer:ident| $e:expr) => { - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { let $writer = writer; let $typ = typ; let $me = self; @@ -84,24 +70,32 @@ macro_rules! impl_serialize_via_writer { } impl SerializeCql for i8 { - impl_exact_preliminary_type_check!(TinyInt); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, TinyInt); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for i16 { - impl_exact_preliminary_type_check!(SmallInt); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, SmallInt); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for i32 { - impl_exact_preliminary_type_check!(Int); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Int); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for i64 { - impl_exact_preliminary_type_check!(BigInt); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, BigInt); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for BigDecimal { - impl_exact_preliminary_type_check!(Decimal); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Decimal); let mut builder = writer.into_value_builder(); let (value, scale) = me.as_bigint_and_exponent(); let scale: i32 = scale @@ -115,41 +109,41 @@ impl SerializeCql for BigDecimal { }); } impl SerializeCql for CqlDate { - impl_exact_preliminary_type_check!(Date); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Date); writer.set_value(me.0.to_be_bytes().as_slice()).unwrap() }); } impl SerializeCql for CqlTimestamp { - impl_exact_preliminary_type_check!(Timestamp); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Timestamp); writer.set_value(me.0.to_be_bytes().as_slice()).unwrap() }); } impl SerializeCql for CqlTime { - impl_exact_preliminary_type_check!(Time); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Time); writer.set_value(me.0.to_be_bytes().as_slice()).unwrap() }); } #[cfg(feature = "chrono")] impl SerializeCql for NaiveDate { - impl_exact_preliminary_type_check!(Date); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Date); ::serialize(&(*me).into(), typ, writer)? }); } #[cfg(feature = "chrono")] impl SerializeCql for DateTime { - impl_exact_preliminary_type_check!(Timestamp); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Timestamp); ::serialize(&(*me).into(), typ, writer)? }); } #[cfg(feature = "chrono")] impl SerializeCql for NaiveTime { - impl_exact_preliminary_type_check!(Time); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Time); let cql_time = CqlTime::try_from(*me).map_err(|_: ValueOverflow| { mk_ser_err::(typ, BuiltinSerializationErrorKind::ValueOverflow) })?; @@ -158,57 +152,62 @@ impl SerializeCql for NaiveTime { } #[cfg(feature = "chrono")] impl SerializeCql for time::Date { - impl_exact_preliminary_type_check!(Date); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Date); ::serialize(&(*me).into(), typ, writer)? }); } #[cfg(feature = "chrono")] impl SerializeCql for time::OffsetDateTime { - impl_exact_preliminary_type_check!(Timestamp); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Timestamp); ::serialize(&(*me).into(), typ, writer)? }); } #[cfg(feature = "chrono")] impl SerializeCql for time::Time { - impl_exact_preliminary_type_check!(Time); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Time); ::serialize(&(*me).into(), typ, writer)? }); } #[cfg(feature = "secret")] impl SerializeCql for Secret { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - V::preliminary_type_check(typ) - } - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { V::serialize(self.expose_secret(), typ, writer) } } impl SerializeCql for bool { - impl_exact_preliminary_type_check!(Boolean); - impl_serialize_via_writer!(|me, writer| writer.set_value(&[*me as u8]).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Boolean); + writer.set_value(&[*me as u8]).unwrap() + }); } impl SerializeCql for f32 { - impl_exact_preliminary_type_check!(Float); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Float); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for f64 { - impl_exact_preliminary_type_check!(Double); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.to_be_bytes().as_slice()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Double); + writer.set_value(me.to_be_bytes().as_slice()).unwrap() + }); } impl SerializeCql for Uuid { - impl_exact_preliminary_type_check!(Uuid, Timeuuid); - impl_serialize_via_writer!(|me, writer| writer.set_value(me.as_bytes().as_ref()).unwrap()); + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Uuid, Timeuuid); + writer.set_value(me.as_bytes().as_ref()).unwrap() + }); } impl SerializeCql for BigInt { - impl_exact_preliminary_type_check!(Varint); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Varint); // TODO: The allocation here can be avoided and we can reimplement // `to_signed_bytes_be` by using `to_u64_digits` and a bit of custom // logic. Need better tests in order to do this. @@ -218,40 +217,40 @@ impl SerializeCql for BigInt { }); } impl SerializeCql for &str { - impl_exact_preliminary_type_check!(Ascii, Text); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Ascii, Text); writer .set_value(me.as_bytes()) .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } impl SerializeCql for Vec { - impl_exact_preliminary_type_check!(Blob); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Blob); writer .set_value(me.as_ref()) .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } impl SerializeCql for &[u8] { - impl_exact_preliminary_type_check!(Blob); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Blob); writer .set_value(me) .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } impl SerializeCql for [u8; N] { - impl_exact_preliminary_type_check!(Blob); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Blob); writer .set_value(me.as_ref()) .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } impl SerializeCql for IpAddr { - impl_exact_preliminary_type_check!(Inet); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Inet); match me { IpAddr::V4(ip) => writer.set_value(&ip.octets()).unwrap(), IpAddr::V6(ip) => writer.set_value(&ip.octets()).unwrap(), @@ -259,22 +258,19 @@ impl SerializeCql for IpAddr { }); } impl SerializeCql for String { - impl_exact_preliminary_type_check!(Ascii, Text); impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Ascii, Text); writer .set_value(me.as_bytes()) .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } impl SerializeCql for Option { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - T::preliminary_type_check(typ) - } - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { match self { Some(v) => v.serialize(typ, writer), None => Ok(writer.set_null()), @@ -282,20 +278,17 @@ impl SerializeCql for Option { } } impl SerializeCql for Unset { - fn preliminary_type_check(_typ: &ColumnType) -> Result<(), SerializationError> { - Ok(()) // Fits everything - } impl_serialize_via_writer!(|_me, writer| writer.set_unset()); } impl SerializeCql for Counter { - impl_exact_preliminary_type_check!(Counter); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Counter); writer.set_value(me.0.to_be_bytes().as_slice()).unwrap() }); } impl SerializeCql for CqlDuration { - impl_exact_preliminary_type_check!(Duration); - impl_serialize_via_writer!(|me, writer| { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Duration); // TODO: adjust vint_encode to use CellValueBuilder or something like that let mut buf = Vec::with_capacity(27); // worst case size is 27 vint_encode(me.months as i64, &mut buf); @@ -305,14 +298,11 @@ impl SerializeCql for CqlDuration { }); } impl SerializeCql for MaybeUnset { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - V::preliminary_type_check(typ) - } - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { match self { MaybeUnset::Set(v) => v.serialize(typ, writer), MaybeUnset::Unset => Ok(writer.set_unset()), @@ -320,50 +310,29 @@ impl SerializeCql for MaybeUnset { } } impl SerializeCql for &T { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - T::preliminary_type_check(typ) - } - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { T::serialize(*self, typ, writer) } } impl SerializeCql for Box { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - T::preliminary_type_check(typ) - } - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { T::serialize(&**self, typ, writer) } } impl SerializeCql for HashSet { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Set(elt) => V::preliminary_type_check(elt).map_err(|err| { - mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::ElementTypeCheckFailed(err), - ) - }), - _ => Err(mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::NotSetOrList, - )), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_sequence( std::any::type_name::(), self.len(), @@ -374,26 +343,11 @@ impl SerializeCql for HashSet { } } impl SerializeCql for HashMap { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Map(k, v) => { - K::preliminary_type_check(k).map_err(|err| { - mk_typck_err::(typ, MapTypeCheckErrorKind::KeyTypeCheckFailed(err)) - })?; - V::preliminary_type_check(v).map_err(|err| { - mk_typck_err::(typ, MapTypeCheckErrorKind::ValueTypeCheckFailed(err)) - })?; - Ok(()) - } - _ => Err(mk_typck_err::(typ, MapTypeCheckErrorKind::NotMap)), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_mapping( std::any::type_name::(), self.len(), @@ -404,26 +358,11 @@ impl SerializeCql for HashMap< } } impl SerializeCql for BTreeSet { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Set(elt) => V::preliminary_type_check(elt).map_err(|err| { - mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::ElementTypeCheckFailed(err), - ) - }), - _ => Err(mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::NotSetOrList, - )), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_sequence( std::any::type_name::(), self.len(), @@ -434,26 +373,11 @@ impl SerializeCql for BTreeSet { } } impl SerializeCql for BTreeMap { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Map(k, v) => { - K::preliminary_type_check(k).map_err(|err| { - mk_typck_err::(typ, MapTypeCheckErrorKind::KeyTypeCheckFailed(err)) - })?; - V::preliminary_type_check(v).map_err(|err| { - mk_typck_err::(typ, MapTypeCheckErrorKind::ValueTypeCheckFailed(err)) - })?; - Ok(()) - } - _ => Err(mk_typck_err::(typ, MapTypeCheckErrorKind::NotMap)), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_mapping( std::any::type_name::(), self.len(), @@ -464,28 +388,11 @@ impl SerializeCql for BTreeMap { } } impl SerializeCql for Vec { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::List(elt) | ColumnType::Set(elt) => { - T::preliminary_type_check(elt).map_err(|err| { - mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::ElementTypeCheckFailed(err), - ) - }) - } - _ => Err(mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::NotSetOrList, - )), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_sequence( std::any::type_name::(), self.len(), @@ -496,28 +403,11 @@ impl SerializeCql for Vec { } } impl<'a, T: SerializeCql + 'a> SerializeCql for &'a [T] { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::List(elt) | ColumnType::Set(elt) => { - T::preliminary_type_check(elt).map_err(|err| { - mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::ElementTypeCheckFailed(err), - ) - }) - } - _ => Err(mk_typck_err::( - typ, - SetOrListTypeCheckErrorKind::NotSetOrList, - )), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_sequence( std::any::type_name::(), self.len(), @@ -528,39 +418,35 @@ impl<'a, T: SerializeCql + 'a> SerializeCql for &'a [T] { } } impl SerializeCql for CqlValue { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Custom(_) => Err(mk_typck_err::( - typ, - BuiltinTypeCheckErrorKind::CustomTypeUnsupported, - )), - _ => Ok(()), - } - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { serialize_cql_value(self, typ, writer).map_err(fix_cql_value_name_in_err) } } -fn serialize_cql_value( +fn serialize_cql_value<'b>( value: &CqlValue, typ: &ColumnType, - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { + if let ColumnType::Custom(_) = typ { + return Err(mk_typck_err::( + typ, + BuiltinTypeCheckErrorKind::CustomTypeUnsupported, + )); + } match value { - CqlValue::Ascii(a) => check_and_serialize(a, typ, writer), - CqlValue::Boolean(b) => check_and_serialize(b, typ, writer), - CqlValue::Blob(b) => check_and_serialize(b, typ, writer), - CqlValue::Counter(c) => check_and_serialize(c, typ, writer), - CqlValue::Decimal(d) => check_and_serialize(d, typ, writer), - CqlValue::Date(d) => check_and_serialize(d, typ, writer), - CqlValue::Double(d) => check_and_serialize(d, typ, writer), - CqlValue::Duration(d) => check_and_serialize(d, typ, writer), + CqlValue::Ascii(a) => <_ as SerializeCql>::serialize(&a, typ, writer), + CqlValue::Boolean(b) => <_ as SerializeCql>::serialize(&b, typ, writer), + CqlValue::Blob(b) => <_ as SerializeCql>::serialize(&b, typ, writer), + CqlValue::Counter(c) => <_ as SerializeCql>::serialize(&c, typ, writer), + CqlValue::Decimal(d) => <_ as SerializeCql>::serialize(&d, typ, writer), + CqlValue::Date(d) => <_ as SerializeCql>::serialize(&d, typ, writer), + CqlValue::Double(d) => <_ as SerializeCql>::serialize(&d, typ, writer), + CqlValue::Duration(d) => <_ as SerializeCql>::serialize(&d, typ, writer), CqlValue::Empty => { if !typ.supports_special_empty_value() { return Err(mk_typck_err::( @@ -570,13 +456,13 @@ fn serialize_cql_value( } Ok(writer.set_value(&[]).unwrap()) } - CqlValue::Float(f) => check_and_serialize(f, typ, writer), - CqlValue::Int(i) => check_and_serialize(i, typ, writer), - CqlValue::BigInt(b) => check_and_serialize(b, typ, writer), - CqlValue::Text(t) => check_and_serialize(t, typ, writer), - CqlValue::Timestamp(t) => check_and_serialize(t, typ, writer), - CqlValue::Inet(i) => check_and_serialize(i, typ, writer), - CqlValue::List(l) => check_and_serialize(l, typ, writer), + CqlValue::Float(f) => <_ as SerializeCql>::serialize(&f, typ, writer), + CqlValue::Int(i) => <_ as SerializeCql>::serialize(&i, typ, writer), + CqlValue::BigInt(b) => <_ as SerializeCql>::serialize(&b, typ, writer), + CqlValue::Text(t) => <_ as SerializeCql>::serialize(&t, typ, writer), + CqlValue::Timestamp(t) => <_ as SerializeCql>::serialize(&t, typ, writer), + CqlValue::Inet(i) => <_ as SerializeCql>::serialize(&i, typ, writer), + CqlValue::List(l) => <_ as SerializeCql>::serialize(&l, typ, writer), CqlValue::Map(m) => serialize_mapping( std::any::type_name::(), m.len(), @@ -584,16 +470,16 @@ fn serialize_cql_value( typ, writer, ), - CqlValue::Set(s) => check_and_serialize(s, typ, writer), + CqlValue::Set(s) => <_ as SerializeCql>::serialize(&s, typ, writer), CqlValue::UserDefinedType { keyspace, type_name, fields, } => serialize_udt(typ, keyspace, type_name, fields, writer), - CqlValue::SmallInt(s) => check_and_serialize(s, typ, writer), - CqlValue::TinyInt(t) => check_and_serialize(t, typ, writer), - CqlValue::Time(t) => check_and_serialize(t, typ, writer), - CqlValue::Timeuuid(t) => check_and_serialize(t, typ, writer), + CqlValue::SmallInt(s) => <_ as SerializeCql>::serialize(&s, typ, writer), + CqlValue::TinyInt(t) => <_ as SerializeCql>::serialize(&t, typ, writer), + CqlValue::Time(t) => <_ as SerializeCql>::serialize(&t, typ, writer), + CqlValue::Timeuuid(t) => <_ as SerializeCql>::serialize(&t, typ, writer), CqlValue::Tuple(t) => { // We allow serializing tuples that have less fields // than the database tuple, but not the other way around. @@ -619,8 +505,8 @@ fn serialize_cql_value( }; serialize_tuple_like(typ, fields.iter(), t.iter(), writer) } - CqlValue::Uuid(u) => check_and_serialize(u, typ, writer), - CqlValue::Varint(v) => check_and_serialize(v, typ, writer), + CqlValue::Uuid(u) => <_ as SerializeCql>::serialize(&u, typ, writer), + CqlValue::Varint(v) => <_ as SerializeCql>::serialize(&v, typ, writer), } } @@ -666,22 +552,13 @@ fn fix_cql_value_name_in_err(mut err: SerializationError) -> SerializationError err } -fn check_and_serialize( - v: &V, - typ: &ColumnType, - writer: W, -) -> Result { - V::preliminary_type_check(typ)?; - v.serialize(typ, writer) -} - -fn serialize_udt( +fn serialize_udt<'b>( typ: &ColumnType, keyspace: &str, type_name: &str, values: &[(String, Option)], - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { let (dst_type_name, dst_keyspace, field_types) = match typ { ColumnType::UserDefinedType { type_name, @@ -747,12 +624,12 @@ fn serialize_udt( .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow)) } -fn serialize_tuple_like<'t, W: CellWriter>( +fn serialize_tuple_like<'t, 'b>( typ: &ColumnType, field_types: impl Iterator, field_values: impl Iterator>, - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { let mut builder = writer.into_value_builder(); for (index, (el, typ)) in field_values.zip(field_types).enumerate() { @@ -782,47 +659,11 @@ macro_rules! impl_tuple { $length:expr ) => { impl<$($typs: SerializeCql),*> SerializeCql for ($($typs,)*) { - fn preliminary_type_check(typ: &ColumnType) -> Result<(), SerializationError> { - match typ { - ColumnType::Tuple(typs) => match typs.as_slice() { - [$($tidents),*, ..] => { - let index = 0; - $( - <$typs as SerializeCql>::preliminary_type_check($tidents) - .map_err(|err| - mk_typck_err::( - typ, - TupleTypeCheckErrorKind::ElementTypeCheckFailed { - index, - err, - } - ) - )?; - let index = index + 1; - )* - let _ = index; - } - _ => return Err(mk_typck_err::( - typ, - TupleTypeCheckErrorKind::WrongElementCount { - actual: $length, - asked_for: typs.len(), - } - )) - } - _ => return Err(mk_typck_err::( - typ, - TupleTypeCheckErrorKind::NotTuple - )), - }; - Ok(()) - } - - fn serialize( + fn serialize<'b>( &self, typ: &ColumnType, - writer: W, - ) -> Result { + writer: CellWriter<'b>, + ) -> Result, SerializationError> { let ($($tidents,)*) = match typ { ColumnType::Tuple(typs) => match typs.as_slice() { [$($tidents),*] => ($($tidents,)*), @@ -892,13 +733,13 @@ impl_tuples!( 16 ); -fn serialize_sequence<'t, T: SerializeCql + 't, W: CellWriter>( +fn serialize_sequence<'t, 'b, T: SerializeCql + 't>( rust_name: &'static str, len: usize, iter: impl Iterator, typ: &ColumnType, - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { let elt = match typ { ColumnType::List(elt) | ColumnType::Set(elt) => elt, _ => { @@ -936,13 +777,13 @@ fn serialize_sequence<'t, T: SerializeCql + 't, W: CellWriter>( .map_err(|_| mk_ser_err_named(rust_name, typ, BuiltinSerializationErrorKind::SizeOverflow)) } -fn serialize_mapping<'t, K: SerializeCql + 't, V: SerializeCql + 't, W: CellWriter>( +fn serialize_mapping<'t, 'b, K: SerializeCql + 't, V: SerializeCql + 't>( rust_name: &'static str, len: usize, iter: impl Iterator, typ: &ColumnType, - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { let (ktyp, vtyp) = match typ { ColumnType::Map(k, v) => (k, v), _ => { @@ -1009,7 +850,7 @@ fn serialize_mapping<'t, K: SerializeCql + 't, V: SerializeCql + 't, W: CellWrit /// # use scylla_cql::impl_serialize_cql_via_value; /// struct NoGenerics {} /// impl Value for NoGenerics { -/// fn serialize(&self, _buf: &mut Vec) -> Result<(), ValueTooBig> { +/// fn serialize<'b>(&self, _buf: &mut Vec) -> Result<(), ValueTooBig> { /// Ok(()) /// } /// } @@ -1019,7 +860,7 @@ fn serialize_mapping<'t, K: SerializeCql + 't, V: SerializeCql + 't, W: CellWrit /// // struct/enum contains any. /// struct WithGenerics(T, U); /// impl Value for WithGenerics { -/// fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { +/// fn serialize<'b>(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { /// self.0.serialize(buf)?; /// self.1.clone().serialize(buf)?; /// Ok(()) @@ -1035,19 +876,12 @@ macro_rules! impl_serialize_cql_via_value { 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( + fn serialize<'b>( &self, _typ: &$crate::frame::response::result::ColumnType, - writer: W, + writer: $crate::types::serialize::writers::CellWriter<'b>, ) -> ::std::result::Result< - W::WrittenCellProof, + $crate::types::serialize::writers::WrittenCellProof<'b>, $crate::types::serialize::SerializationError, > { $crate::types::serialize::value::serialize_legacy_value(self, writer) @@ -1069,10 +903,10 @@ macro_rules! impl_serialize_cql_via_value { /// /// See [`impl_serialize_cql_via_value`] which generates a boilerplate /// [`SerializeCql`] implementation that uses this function. -pub fn serialize_legacy_value( +pub fn serialize_legacy_value<'b, T: Value>( v: &T, - writer: W, -) -> Result { + writer: CellWriter<'b>, +) -> Result, SerializationError> { // It's an inefficient and slightly tricky but correct implementation. let mut buf = Vec::new(); ::serialize(v, &mut buf).map_err(|err| SerializationError(Arc::new(err)))?; @@ -1324,12 +1158,6 @@ impl Display for BuiltinSerializationErrorKind { pub enum MapTypeCheckErrorKind { /// The CQL type is not a map. NotMap, - - /// Checking the map key type failed. - KeyTypeCheckFailed(SerializationError), - - /// Checking the map value type failed. - ValueTypeCheckFailed(SerializationError), } impl Display for MapTypeCheckErrorKind { @@ -1341,12 +1169,6 @@ impl Display for MapTypeCheckErrorKind { "the CQL type the map was attempted to be serialized to was not map" ) } - MapTypeCheckErrorKind::KeyTypeCheckFailed(err) => { - write!(f, "failed to type check one of the keys: {}", err) - } - MapTypeCheckErrorKind::ValueTypeCheckFailed(err) => { - write!(f, "failed to type check one of the values: {}", err) - } } } } @@ -1388,9 +1210,6 @@ impl Display for MapSerializationErrorKind { pub enum SetOrListTypeCheckErrorKind { /// The CQL type is neither a set not a list. NotSetOrList, - - /// Checking the type of the set/list element failed. - ElementTypeCheckFailed(SerializationError), } impl Display for SetOrListTypeCheckErrorKind { @@ -1402,9 +1221,6 @@ impl Display for SetOrListTypeCheckErrorKind { "the CQL type the tuple was attempted to was neither a set or a list" ) } - SetOrListTypeCheckErrorKind::ElementTypeCheckFailed(err) => { - write!(f, "failed to type check one of the elements: {err}") - } } } } @@ -1447,12 +1263,6 @@ pub enum TupleTypeCheckErrorKind { /// than the corresponding CQL type, but not more. The additional, unknown /// elements will be set to null. WrongElementCount { actual: usize, asked_for: usize }, - - /// One of the tuple elements failed to type check. - ElementTypeCheckFailed { - index: usize, - err: SerializationError, - }, } impl Display for TupleTypeCheckErrorKind { @@ -1466,9 +1276,6 @@ impl Display for TupleTypeCheckErrorKind { f, "wrong tuple element count: CQL type has {asked_for}, the Rust tuple has {actual}" ), - TupleTypeCheckErrorKind::ElementTypeCheckFailed { index, err } => { - write!(f, "element no. {index} failed to type check: {err}") - } } } } @@ -1504,12 +1311,6 @@ pub enum UdtTypeCheckErrorKind { /// The Rust data contains a field that is not present in the UDT UnexpectedFieldInDestination { field_name: String }, - - /// One of the fields failed to type check. - FieldTypeCheckFailed { - field_name: String, - err: SerializationError, - }, } impl Display for UdtTypeCheckErrorKind { @@ -1530,9 +1331,6 @@ impl Display for UdtTypeCheckErrorKind { f, "the field {field_name} present in the Rust data is not present in the CQL type" ), - UdtTypeCheckErrorKind::FieldTypeCheckFailed { field_name, err } => { - write!(f, "field {field_name} failed to type check: {err}") - } } } } @@ -1573,7 +1371,7 @@ pub enum ValueToSerializeCqlAdapterError { mod tests { use crate::frame::response::result::ColumnType; use crate::frame::value::{MaybeUnset, Value}; - use crate::types::serialize::BufBackedCellWriter; + use crate::types::serialize::CellWriter; use super::SerializeCql; @@ -1582,7 +1380,7 @@ mod tests { ::serialize(&v, &mut legacy_data).unwrap(); let mut new_data = Vec::new(); - let new_data_writer = BufBackedCellWriter::new(&mut new_data); + let new_data_writer = CellWriter::new(&mut new_data); ::serialize(&v, &ColumnType::Int, new_data_writer).unwrap(); assert_eq!(legacy_data, new_data); @@ -1594,4 +1392,19 @@ mod tests { check_compat(None::); check_compat(MaybeUnset::Unset::); } + + #[test] + fn test_dyn_serialize_cql() { + let v: i32 = 123; + let mut typed_data = Vec::new(); + let typed_data_writer = CellWriter::new(&mut typed_data); + <_ as SerializeCql>::serialize(&v, &ColumnType::Int, typed_data_writer).unwrap(); + + let v = &v as &dyn SerializeCql; + let mut erased_data = Vec::new(); + let erased_data_writer = CellWriter::new(&mut erased_data); + <_ as SerializeCql>::serialize(&v, &ColumnType::Int, erased_data_writer).unwrap(); + + assert_eq!(typed_data, erased_data); + } } diff --git a/scylla-cql/src/types/serialize/writers.rs b/scylla-cql/src/types/serialize/writers.rs index ecb8a1fcc1..4d350adc75 100644 --- a/scylla-cql/src/types/serialize/writers.rs +++ b/scylla-cql/src/types/serialize/writers.rs @@ -3,115 +3,7 @@ use thiserror::Error; /// An interface that facilitates writing values for a CQL query. -pub trait RowWriter { - type CellWriter<'a>: CellWriter - where - Self: 'a; - - /// Appends a new value to the sequence and returns an object that allows - /// to fill it in. - fn make_cell_writer(&mut self) -> Self::CellWriter<'_>; -} - -/// Represents a handle to a CQL value that needs to be written into. -/// -/// The writer can either be transformed into a ready value right away -/// (via [`set_null`](CellWriter::set_null), -/// [`set_unset`](CellWriter::set_unset) -/// or [`set_value`](CellWriter::set_value) or transformed into -/// the [`CellWriter::ValueBuilder`] in order to gradually initialize -/// the value when the contents are not available straight away. -/// -/// After the value is fully initialized, the handle is consumed and -/// a [`WrittenCellProof`](CellWriter::WrittenCellProof) object is returned -/// in its stead. This is a type-level proof that the value was fully initialized -/// and is used in [`SerializeCql::serialize`](`super::value::SerializeCql::serialize`) -/// in order to enforce the implementor to fully initialize the provided handle -/// to CQL value. -/// -/// Dropping this type without calling any of its methods will result -/// in nothing being written. -pub trait CellWriter { - /// The type of the value builder, returned by the [`CellWriter::set_value`] - /// method. - type ValueBuilder: CellValueBuilder; - - /// An object that serves as a proof that the cell was fully initialized. - /// - /// This type is returned by [`set_null`](CellWriter::set_null), - /// [`set_unset`](CellWriter::set_unset), - /// [`set_value`](CellWriter::set_value) - /// and also [`CellValueBuilder::finish`] - generally speaking, after - /// the value is fully initialized and the `CellWriter` is destroyed. - /// - /// The purpose of this type is to enforce the contract of - /// [`SerializeCql::serialize`](super::value::SerializeCql::serialize): either - /// the method succeeds and returns a proof that it serialized itself - /// into the given value, or it fails and returns an error or panics. - /// The exact type of [`WrittenCellProof`](CellWriter::WrittenCellProof) - /// is not important as the value is not used at all - it's only - /// a compile-time check. - type WrittenCellProof; - - /// Sets this value to be null, consuming this object. - fn set_null(self) -> Self::WrittenCellProof; - - /// Sets this value to represent an unset value, consuming this object. - fn set_unset(self) -> Self::WrittenCellProof; - - /// Sets this value to a non-zero, non-unset value with given contents. - /// - /// Prefer this to [`into_value_builder`](CellWriter::into_value_builder) - /// if you have all of the contents of the value ready up front (e.g. for - /// fixed size types). - /// - /// Fails if the contents size overflows the maximum allowed CQL cell size - /// (which is i32::MAX). - fn set_value(self, contents: &[u8]) -> Result; - - /// Turns this writter into a [`CellValueBuilder`] which can be used - /// to gradually initialize the CQL value. - /// - /// This method should be used if you don't have all of the data - /// up front, e.g. when serializing compound types such as collections - /// or UDTs. - fn into_value_builder(self) -> Self::ValueBuilder; -} - -/// Allows appending bytes to a non-null, non-unset cell. -/// -/// This object needs to be dropped in order for the value to be correctly -/// serialized. Failing to drop this value will result in a payload that will -/// not be parsed by the database correctly, but otherwise should not cause -/// data to be misinterpreted. -pub trait CellValueBuilder { - type SubCellWriter<'a>: CellWriter - where - Self: 'a; - - type WrittenCellProof; - - /// Appends raw bytes to this cell. - fn append_bytes(&mut self, bytes: &[u8]); - - /// Appends a sub-value to the end of the current contents of the cell - /// and returns an object that allows to fill it in. - fn make_sub_writer(&mut self) -> Self::SubCellWriter<'_>; - - /// Finishes serializing the value. - /// - /// Fails if the constructed cell size overflows the maximum allowed - /// CQL cell size (which is i32::MAX). - fn finish(self) -> Result; -} - -/// There was an attempt to produce a CQL value over the maximum size limit (i32::MAX) -#[derive(Debug, Clone, Copy, Error)] -#[error("CQL cell overflowed the maximum allowed size of 2^31 - 1")] -pub struct CellOverflowError; - -/// A row writer backed by a buffer (vec). -pub struct BufBackedRowWriter<'buf> { +pub struct RowWriter<'buf> { // Buffer that this value should be serialized to. buf: &'buf mut Vec, @@ -119,7 +11,7 @@ pub struct BufBackedRowWriter<'buf> { value_count: usize, } -impl<'buf> BufBackedRowWriter<'buf> { +impl<'buf> RowWriter<'buf> { /// Creates a new row writer based on an existing Vec. /// /// The newly created row writer will append data to the end of the vec. @@ -139,64 +31,96 @@ impl<'buf> BufBackedRowWriter<'buf> { pub fn value_count(&self) -> usize { self.value_count } -} - -impl<'buf> RowWriter for BufBackedRowWriter<'buf> { - type CellWriter<'a> = BufBackedCellWriter<'a> where Self: 'a; + /// Appends a new value to the sequence and returns an object that allows + /// to fill it in. #[inline] - fn make_cell_writer(&mut self) -> Self::CellWriter<'_> { + pub fn make_cell_writer(&mut self) -> CellWriter<'_> { self.value_count += 1; - BufBackedCellWriter::new(self.buf) + CellWriter::new(self.buf) } } -/// A cell writer backed by a buffer (vec). -pub struct BufBackedCellWriter<'buf> { +/// Represents a handle to a CQL value that needs to be written into. +/// +/// The writer can either be transformed into a ready value right away +/// (via [`set_null`](CellWriter::set_null), +/// [`set_unset`](CellWriter::set_unset) +/// or [`set_value`](CellWriter::set_value) or transformed into +/// the [`CellValueBuilder`] in order to gradually initialize +/// the value when the contents are not available straight away. +/// +/// After the value is fully initialized, the handle is consumed and +/// a [`WrittenCellProof`] object is returned +/// in its stead. This is a type-level proof that the value was fully initialized +/// and is used in [`SerializeCql::serialize`](`super::value::SerializeCql::serialize`) +/// in order to enforce the implementor to fully initialize the provided handle +/// to CQL value. +/// +/// Dropping this type without calling any of its methods will result +/// in nothing being written. +pub struct CellWriter<'buf> { buf: &'buf mut Vec, } -impl<'buf> BufBackedCellWriter<'buf> { +impl<'buf> CellWriter<'buf> { /// Creates a new cell writer based on an existing Vec. /// /// The newly created row writer will append data to the end of the vec. #[inline] pub fn new(buf: &'buf mut Vec) -> Self { - BufBackedCellWriter { buf } + Self { buf } } -} - -impl<'buf> CellWriter for BufBackedCellWriter<'buf> { - type ValueBuilder = BufBackedCellValueBuilder<'buf>; - - type WrittenCellProof = (); + /// Sets this value to be null, consuming this object. #[inline] - fn set_null(self) { + pub fn set_null(self) -> WrittenCellProof<'buf> { self.buf.extend_from_slice(&(-1i32).to_be_bytes()); + WrittenCellProof::new() } + /// Sets this value to represent an unset value, consuming this object. #[inline] - fn set_unset(self) { + pub fn set_unset(self) -> WrittenCellProof<'buf> { self.buf.extend_from_slice(&(-2i32).to_be_bytes()); + WrittenCellProof::new() } + /// Sets this value to a non-zero, non-unset value with given contents. + /// + /// Prefer this to [`into_value_builder`](CellWriter::into_value_builder) + /// if you have all of the contents of the value ready up front (e.g. for + /// fixed size types). + /// + /// Fails if the contents size overflows the maximum allowed CQL cell size + /// (which is i32::MAX). #[inline] - fn set_value(self, bytes: &[u8]) -> Result<(), CellOverflowError> { - let value_len: i32 = bytes.len().try_into().map_err(|_| CellOverflowError)?; + pub fn set_value(self, contents: &[u8]) -> Result, CellOverflowError> { + let value_len: i32 = contents.len().try_into().map_err(|_| CellOverflowError)?; self.buf.extend_from_slice(&value_len.to_be_bytes()); - self.buf.extend_from_slice(bytes); - Ok(()) + self.buf.extend_from_slice(contents); + Ok(WrittenCellProof::new()) } + /// Turns this writter into a [`CellValueBuilder`] which can be used + /// to gradually initialize the CQL value. + /// + /// This method should be used if you don't have all of the data + /// up front, e.g. when serializing compound types such as collections + /// or UDTs. #[inline] - fn into_value_builder(self) -> Self::ValueBuilder { - BufBackedCellValueBuilder::new(self.buf) + pub fn into_value_builder(self) -> CellValueBuilder<'buf> { + CellValueBuilder::new(self.buf) } } -/// A cell value builder backed by a buffer (vec). -pub struct BufBackedCellValueBuilder<'buf> { +/// Allows appending bytes to a non-null, non-unset cell. +/// +/// This object needs to be dropped in order for the value to be correctly +/// serialized. Failing to drop this value will result in a payload that will +/// not be parsed by the database correctly, but otherwise should not cause +/// data to be misinterpreted. +pub struct CellValueBuilder<'buf> { // Buffer that this value should be serialized to. buf: &'buf mut Vec, @@ -204,7 +128,7 @@ pub struct BufBackedCellValueBuilder<'buf> { starting_pos: usize, } -impl<'buf> BufBackedCellValueBuilder<'buf> { +impl<'buf> CellValueBuilder<'buf> { #[inline] fn new(buf: &'buf mut Vec) -> Self { // "Length" of a [bytes] frame can either be a non-negative i32, @@ -215,194 +139,92 @@ impl<'buf> BufBackedCellValueBuilder<'buf> { // won't be misinterpreted. let starting_pos = buf.len(); buf.extend_from_slice(&(-3i32).to_be_bytes()); - BufBackedCellValueBuilder { buf, starting_pos } + Self { buf, starting_pos } } -} - -impl<'buf> CellValueBuilder for BufBackedCellValueBuilder<'buf> { - type SubCellWriter<'a> = BufBackedCellWriter<'a> - where - Self: 'a; - - type WrittenCellProof = (); + /// Appends raw bytes to this cell. #[inline] - fn append_bytes(&mut self, bytes: &[u8]) { + pub fn append_bytes(&mut self, bytes: &[u8]) { self.buf.extend_from_slice(bytes); } + /// Appends a sub-value to the end of the current contents of the cell + /// and returns an object that allows to fill it in. #[inline] - fn make_sub_writer(&mut self) -> Self::SubCellWriter<'_> { - BufBackedCellWriter::new(self.buf) + pub fn make_sub_writer(&mut self) -> CellWriter<'_> { + CellWriter::new(self.buf) } + /// Finishes serializing the value. + /// + /// Fails if the constructed cell size overflows the maximum allowed + /// CQL cell size (which is i32::MAX). #[inline] - fn finish(self) -> Result<(), CellOverflowError> { + pub fn finish(self) -> Result, CellOverflowError> { let value_len: i32 = (self.buf.len() - self.starting_pos - 4) .try_into() .map_err(|_| CellOverflowError)?; self.buf[self.starting_pos..self.starting_pos + 4] .copy_from_slice(&value_len.to_be_bytes()); - Ok(()) - } -} - -/// A row writer that does not actually write anything, just counts the bytes. -pub struct CountingRowWriter<'buf> { - buf: &'buf mut usize, -} - -impl<'buf> CountingRowWriter<'buf> { - /// Creates a new writer which increments the counter under given reference - /// when bytes are appended. - #[inline] - pub fn new(buf: &'buf mut usize) -> Self { - CountingRowWriter { buf } + Ok(WrittenCellProof::new()) } } -impl<'buf> RowWriter for CountingRowWriter<'buf> { - type CellWriter<'a> = CountingCellWriter<'a> where Self: 'a; - - #[inline] - fn make_cell_writer(&mut self) -> Self::CellWriter<'_> { - CountingCellWriter::new(self.buf) - } -} - -/// A cell writer that does not actually write anything, just counts the bytes. -pub struct CountingCellWriter<'buf> { - buf: &'buf mut usize, -} - -impl<'buf> CountingCellWriter<'buf> { - /// Creates a new writer which increments the counter under given reference - /// when bytes are appended. - #[inline] - fn new(buf: &'buf mut usize) -> Self { - CountingCellWriter { buf } - } +/// An object that indicates a type-level proof that something was written +/// by a [`CellWriter`] or [`CellValueBuilder`] with lifetime parameter `'buf`. +/// +/// This type is returned by [`set_null`](CellWriter::set_null), +/// [`set_unset`](CellWriter::set_unset), +/// [`set_value`](CellWriter::set_value) +/// and also [`CellValueBuilder::finish`] - generally speaking, after +/// the value is fully initialized and the `CellWriter` is destroyed. +/// +/// The purpose of this type is to enforce the contract of +/// [`SerializeCql::serialize`](super::value::SerializeCql::serialize): either +/// the method succeeds and returns a proof that it serialized itself +/// into the given value, or it fails and returns an error or panics. +pub struct WrittenCellProof<'buf> { + /// Using *mut &'buf () is deliberate and makes WrittenCellProof invariant + /// on the 'buf lifetime parameter. + /// Ref: + _phantom: std::marker::PhantomData<*mut &'buf ()>, } -impl<'buf> CellWriter for CountingCellWriter<'buf> { - type ValueBuilder = CountingCellValueBuilder<'buf>; - - type WrittenCellProof = (); - - #[inline] - fn set_null(self) { - *self.buf += 4; - } - - #[inline] - fn set_unset(self) { - *self.buf += 4; - } - +impl<'buf> WrittenCellProof<'buf> { + /// A shorthand for creating the proof. + /// + /// Do not make it public! It's important that only the row writer defined + /// in this module is able to create a proof. #[inline] - fn set_value(self, contents: &[u8]) -> Result<(), CellOverflowError> { - if contents.len() > i32::MAX as usize { - return Err(CellOverflowError); + fn new() -> Self { + WrittenCellProof { + _phantom: std::marker::PhantomData, } - *self.buf += 4 + contents.len(); - Ok(()) - } - - #[inline] - fn into_value_builder(self) -> Self::ValueBuilder { - *self.buf += 4; - CountingCellValueBuilder::new(self.buf) - } -} - -pub struct CountingCellValueBuilder<'buf> { - buf: &'buf mut usize, - - starting_pos: usize, -} - -impl<'buf> CountingCellValueBuilder<'buf> { - /// Creates a new builder which increments the counter under given reference - /// when bytes are appended. - #[inline] - fn new(buf: &'buf mut usize) -> Self { - let starting_pos = *buf; - CountingCellValueBuilder { buf, starting_pos } } } -impl<'buf> CellValueBuilder for CountingCellValueBuilder<'buf> { - type SubCellWriter<'a> = CountingCellWriter<'a> - where - Self: 'a; - - type WrittenCellProof = (); - - #[inline] - fn append_bytes(&mut self, bytes: &[u8]) { - *self.buf += bytes.len(); - } - - #[inline] - fn make_sub_writer(&mut self) -> Self::SubCellWriter<'_> { - CountingCellWriter::new(self.buf) - } - - #[inline] - fn finish(self) -> Result { - if *self.buf - self.starting_pos > i32::MAX as usize { - return Err(CellOverflowError); - } - Ok(()) - } -} +/// There was an attempt to produce a CQL value over the maximum size limit (i32::MAX) +#[derive(Debug, Clone, Copy, Error)] +#[error("CQL cell overflowed the maximum allowed size of 2^31 - 1")] +pub struct CellOverflowError; #[cfg(test)] mod tests { - use crate::types::serialize::writers::CountingRowWriter; - - use super::{ - BufBackedCellWriter, BufBackedRowWriter, CellValueBuilder, CellWriter, CountingCellWriter, - RowWriter, - }; - - // We want to perform the same computation for both buf backed writer - // and counting writer, but Rust does not support generic closures. - // This trait comes to the rescue. - trait CellSerializeCheck { - fn check(&self, writer: W); - } - - fn check_cell_serialize(c: C) -> Vec { - let mut data = Vec::new(); - let writer = BufBackedCellWriter::new(&mut data); - c.check(writer); - - let mut byte_count = 0usize; - let counting_writer = CountingCellWriter::new(&mut byte_count); - c.check(counting_writer); - - assert_eq!(data.len(), byte_count); - data - } + use super::{CellWriter, RowWriter}; #[test] fn test_cell_writer() { - struct Check; - impl CellSerializeCheck for Check { - fn check(&self, writer: W) { - let mut sub_writer = writer.into_value_builder(); - sub_writer.make_sub_writer().set_null(); - sub_writer - .make_sub_writer() - .set_value(&[1, 2, 3, 4]) - .unwrap(); - sub_writer.make_sub_writer().set_unset(); - sub_writer.finish().unwrap(); - } - } + let mut data = Vec::new(); + let writer = CellWriter::new(&mut data); + let mut sub_writer = writer.into_value_builder(); + sub_writer.make_sub_writer().set_null(); + sub_writer + .make_sub_writer() + .set_value(&[1, 2, 3, 4]) + .unwrap(); + sub_writer.make_sub_writer().set_unset(); + sub_writer.finish().unwrap(); - let data = check_cell_serialize(Check); assert_eq!( data, [ @@ -416,14 +238,10 @@ mod tests { #[test] fn test_poisoned_appender() { - struct Check; - impl CellSerializeCheck for Check { - fn check(&self, writer: W) { - let _ = writer.into_value_builder(); - } - } + let mut data = Vec::new(); + let writer = CellWriter::new(&mut data); + let _ = writer.into_value_builder(); - let data = check_cell_serialize(Check); assert_eq!( data, [ @@ -432,35 +250,14 @@ mod tests { ); } - trait RowSerializeCheck { - fn check(&self, writer: &mut W); - } - - fn check_row_serialize(c: C) -> Vec { - let mut data = Vec::new(); - let mut writer = BufBackedRowWriter::new(&mut data); - c.check(&mut writer); - - let mut byte_count = 0usize; - let mut counting_writer = CountingRowWriter::new(&mut byte_count); - c.check(&mut counting_writer); - - assert_eq!(data.len(), byte_count); - data - } - #[test] fn test_row_writer() { - struct Check; - impl RowSerializeCheck for Check { - fn check(&self, writer: &mut W) { - writer.make_cell_writer().set_null(); - writer.make_cell_writer().set_value(&[1, 2, 3, 4]).unwrap(); - writer.make_cell_writer().set_unset(); - } - } + let mut data = Vec::new(); + let mut writer = RowWriter::new(&mut data); + writer.make_cell_writer().set_null(); + writer.make_cell_writer().set_value(&[1, 2, 3, 4]).unwrap(); + writer.make_cell_writer().set_unset(); - let data = check_row_serialize(Check); assert_eq!( data, [