Skip to content

Commit

Permalink
Add flatten attribute to derive SerializeRow
Browse files Browse the repository at this point in the history
Currently only the `match_by_name` flavor is supported. All the needed
structs/traits to make this work are marked as `#[doc(hidden)]` to not
increase the public API surface. Effort was done to not change any of
the existing API.
  • Loading branch information
nrxus committed Dec 9, 2024
1 parent 92fdd71 commit dc2a19e
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 80 deletions.
116 changes: 116 additions & 0 deletions scylla-cql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,120 @@ pub mod _macro_internal {
pub use crate::types::serialize::{
CellValueBuilder, CellWriter, RowWriter, SerializationError,
};

pub mod ser {
pub mod row {
pub use crate::{
frame::response::result::ColumnSpec,
types::serialize::{
row::{
mk_ser_err, mk_typck_err, BuiltinSerializationErrorKind,
BuiltinTypeCheckError, BuiltinTypeCheckErrorKind, RowSerializationContext,
},
value::SerializeValue,
writers::WrittenCellProof,
RowWriter, SerializationError,
},
};

/// Whether a field used a column to finish its serialization or not
///
/// Used when serializing by name as a single column may not have finished a rust
/// field in the case of a flattened struct
///
/// For now this enum is an implementation detail of `#[derive(SerializeRow)]` when
/// serializing by name
#[derive(Debug)]
#[doc(hidden)]
pub enum FieldStatus {
/// The column finished the serialization for this field
Done,
/// The column was used but there are other fields not yet serialized
NotDone,
/// The column did not belong to this field
NotUsed,
}

/// Represents a set of values that can be sent along a CQL statement when serializing by name
///
/// For now this trait is an implementation detail of `#[derive(SerializeRow)]` when
/// serializing by name
#[doc(hidden)]
pub trait SerializeRowByName {
/// A type that can handle serialization of this struct column-by-column
type Partial<'d>: PartialSerializeRowByName
where
Self: 'd;

/// Returns a type that can serialize this row "column-by-column"
fn partial(&self) -> Self::Partial<'_>;
}

/// How to serialize a row column-by-column
///
/// For now this trait is an implementation detail of `#[derive(SerializeRow)]` when
/// serializing by name
#[doc(hidden)]
pub trait PartialSerializeRowByName {
/// Serializes a single column in the row according to the information in the
/// given context
///
/// It returns whether the column finished the serialization of the struct, did
/// it partially, none of at all, or errored
fn serialize_field(
&mut self,
spec: &ColumnSpec,
writer: &mut RowWriter<'_>,
) -> Result<FieldStatus, SerializationError>;

/// Checks if there are any missing columns to finish the serialization
fn check_missing(self) -> Result<(), SerializationError>;
}

pub struct ByName<'t, T: SerializeRowByName>(pub &'t T);

impl<T: SerializeRowByName> ByName<'_, T> {
/// Serializes all the fields/columns by name
pub fn serialize(
self,
ctx: &RowSerializationContext,
writer: &mut RowWriter<'_>,
) -> Result<(), SerializationError> {
let mut partial = self.0.partial();

for spec in ctx.columns() {
let serialized = partial.serialize_field(spec, writer)?;

if matches!(serialized, FieldStatus::NotUsed) {
return Err(mk_typck_err::<Self>(
BuiltinTypeCheckErrorKind::NoColumnWithName {
name: spec.name().to_owned(),
},
));
}
}

partial.check_missing()?;

Ok(())
}
}

pub fn serialize_column<'b, T>(
value: &impl SerializeValue,
spec: &ColumnSpec,
writer: &'b mut RowWriter<'_>,
) -> Result<WrittenCellProof<'b>, SerializationError> {
let sub_writer = writer.make_cell_writer();
value.serialize(spec.typ(), sub_writer).map_err(|err| {
super::row::mk_ser_err::<T>(
BuiltinSerializationErrorKind::ColumnSerializationFailed {
name: spec.name().to_owned(),
err,
},
)
})
}
}
}
}
58 changes: 56 additions & 2 deletions scylla-cql/src/types/serialize/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@ pub struct BuiltinTypeCheckError {
pub kind: BuiltinTypeCheckErrorKind,
}

fn mk_typck_err<T>(kind: impl Into<BuiltinTypeCheckErrorKind>) -> SerializationError {
#[doc(hidden)]
pub fn mk_typck_err<T>(kind: impl Into<BuiltinTypeCheckErrorKind>) -> SerializationError {
mk_typck_err_named(std::any::type_name::<T>(), kind)
}

Expand All @@ -582,7 +583,8 @@ pub struct BuiltinSerializationError {
pub kind: BuiltinSerializationErrorKind,
}

fn mk_ser_err<T>(kind: impl Into<BuiltinSerializationErrorKind>) -> SerializationError {
#[doc(hidden)]
pub fn mk_ser_err<T>(kind: impl Into<BuiltinSerializationErrorKind>) -> SerializationError {
mk_ser_err_named(std::any::type_name::<T>(), kind)
}

Expand Down Expand Up @@ -1634,4 +1636,56 @@ pub(crate) mod tests {

assert_eq!(reference, row);
}

#[test]
fn test_row_serialization_nested_structs() {
#[derive(SerializeRow, Debug)]
#[scylla(crate = crate)]
struct InnerColumnsOne {
x: i32,
y: f64,
}

#[derive(SerializeRow, Debug)]
#[scylla(crate = crate)]
struct InnerColumnsTwo {
z: bool,
}

#[derive(SerializeRow, Debug)]
#[scylla(crate = crate)]
struct OuterColumns {
#[scylla(flatten)]
inner_one: InnerColumnsOne,
a: String,
#[scylla(flatten)]
inner_two: InnerColumnsTwo,
}

let spec = [
col("a", ColumnType::Text),
col("x", ColumnType::Int),
col("z", ColumnType::Boolean),
col("y", ColumnType::Double),
];

let value = OuterColumns {
inner_one: InnerColumnsOne { x: 5, y: 1.0 },
a: "something".to_owned(),
inner_two: InnerColumnsTwo { z: true },
};

let reference = do_serialize(
(
&value.a,
&value.inner_one.x,
&value.inner_two.z,
&value.inner_one.y,
),
&spec,
);
let row = do_serialize(value, &spec);

assert_eq!(reference, row);
}
}
Loading

0 comments on commit dc2a19e

Please sign in to comment.