Skip to content

Commit eb14ca8

Browse files
authored
Merge pull request #1088 from wprzytula/new-deserialization-api-yet-another-preparatory-bunch
New deserialization API - yet another bunch of preparations
2 parents afa91f2 + 5cb42fa commit eb14ca8

File tree

3 files changed

+118
-60
lines changed

3 files changed

+118
-60
lines changed

scylla-cql/src/frame/frame_errors.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub use super::request::{
1414
use super::response::result::TableSpec;
1515
use super::response::CqlResponseKind;
1616
use super::TryFromPrimitiveError;
17-
use crate::types::deserialize::DeserializationError;
17+
use crate::types::deserialize::{DeserializationError, TypeCheckError};
1818
use thiserror::Error;
1919

2020
/// An error returned by `parse_response_body_extensions`.
@@ -320,6 +320,8 @@ pub enum RowsParseError {
320320
},
321321
#[error("Malformed rows count: {0}")]
322322
RowsCountParseError(LowLevelDeserializationError),
323+
#[error("Data type check prior to deserialization failed: {0}")]
324+
IncomingDataTypeCheckError(#[from] TypeCheckError),
323325
#[error("Data deserialization failed: {0}")]
324326
DataDeserializationError(#[from] DeserializationError),
325327
}

scylla-cql/src/frame/response/result.rs

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,10 @@ macro_rules! generate_deser_type {
693693
};
694694
}
695695

696-
generate_deser_type!(deser_type_owned, 'static, |buf| types::read_string(buf).map(ToOwned::to_owned));
697-
698-
// This is going to be used for deserializing borrowed result metadata.
699696
generate_deser_type!(_deser_type_borrowed, 'frame, types::read_string);
700697

698+
generate_deser_type!(deser_type_owned, 'static, |buf| types::read_string(buf).map(ToOwned::to_owned));
699+
701700
/// Deserializes a table spec, be it per-column one or a global one,
702701
/// in the borrowed form.
703702
///
@@ -721,70 +720,114 @@ fn mk_col_spec_parse_error(
721720
}
722721
}
723722

724-
/// Deserializes col specs (part of ResultMetadata or PreparedMetadata)
725-
/// in the owned form.
723+
/// Deserializes table spec of a column spec in the borrowed form.
726724
///
727725
/// Checks for equality of table specs across columns, because the protocol
728726
/// does not guarantee that and we want to be sure that the assumption
729727
/// of them being all the same is correct.
728+
/// To this end, the first column's table spec is written to `known_table_spec`
729+
/// and compared with remaining columns' table spec.
730730
///
731-
/// To avoid needless allocations, it is advised to pass `global_table_spec`
731+
/// To avoid needless allocations, it is advised to pass `known_table_spec`
732732
/// in the borrowed form, so that cloning it is cheap.
733-
fn deser_col_specs(
734-
buf: &mut &[u8],
735-
global_table_spec: Option<TableSpec<'_>>,
736-
col_count: usize,
737-
) -> StdResult<Vec<ColumnSpec<'static>>, ColumnSpecParseError> {
738-
let global_table_spec_provided = global_table_spec.is_some();
739-
let mut known_table_spec = global_table_spec;
740-
741-
let mut col_specs = Vec::with_capacity(col_count);
742-
for col_idx in 0..col_count {
743-
let table_spec = match known_table_spec {
744-
// If global table spec was provided, we simply clone it to each column spec.
745-
Some(ref known_spec) if global_table_spec_provided => known_spec.clone(),
746-
747-
// Else, we deserialize the table spec for a column and, if we already know some
748-
// previous spec (i.e. that of the first column), we perform equality check
749-
// against it.
750-
Some(_) | None => {
751-
let table_spec =
752-
deser_table_spec(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
753-
754-
if let Some(ref known_spec) = known_table_spec {
755-
// We assume that for each column, table spec is the same.
756-
// As this is not guaranteed by the CQL protocol specification but only by how
757-
// Cassandra and ScyllaDB work (no support for joins), we perform a sanity check here.
758-
if known_spec.table_name != table_spec.table_name
759-
|| known_spec.ks_name != table_spec.ks_name
760-
{
761-
return Err(mk_col_spec_parse_error(
762-
col_idx,
763-
ColumnSpecParseErrorKind::TableSpecDiffersAcrossColumns(
764-
known_spec.clone().into_owned(),
765-
table_spec.into_owned(),
766-
),
767-
));
768-
}
769-
} else {
770-
// Once we have read the first column spec, we save its table spec
771-
// in order to verify its equality with other columns'.
772-
known_table_spec = Some(table_spec.clone());
733+
fn deser_table_spec_for_col_spec<'frame>(
734+
buf: &'_ mut &'frame [u8],
735+
global_table_spec_provided: bool,
736+
known_table_spec: &'_ mut Option<TableSpec<'frame>>,
737+
col_idx: usize,
738+
) -> StdResult<TableSpec<'frame>, ColumnSpecParseError> {
739+
let table_spec = match known_table_spec {
740+
// If global table spec was provided, we simply clone it to each column spec.
741+
Some(ref known_spec) if global_table_spec_provided => known_spec.clone(),
742+
743+
// Else, we deserialize the table spec for a column and, if we already know some
744+
// previous spec (i.e. that of the first column), we perform equality check
745+
// against it.
746+
Some(_) | None => {
747+
let table_spec =
748+
deser_table_spec(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
749+
750+
if let Some(ref known_spec) = known_table_spec {
751+
// We assume that for each column, table spec is the same.
752+
// As this is not guaranteed by the CQL protocol specification but only by how
753+
// Cassandra and ScyllaDB work (no support for joins), we perform a sanity check here.
754+
if known_spec.table_name != table_spec.table_name
755+
|| known_spec.ks_name != table_spec.ks_name
756+
{
757+
return Err(mk_col_spec_parse_error(
758+
col_idx,
759+
ColumnSpecParseErrorKind::TableSpecDiffersAcrossColumns(
760+
known_spec.clone().into_owned(),
761+
table_spec.into_owned(),
762+
),
763+
));
773764
}
774-
775-
table_spec
765+
} else {
766+
// Once we have read the first column spec, we save its table spec
767+
// in order to verify its equality with other columns'.
768+
*known_table_spec = Some(table_spec.clone());
776769
}
777-
};
778770

779-
let name = types::read_string(buf)
780-
.map_err(|err| mk_col_spec_parse_error(col_idx, err))?
781-
.to_owned();
782-
let typ = deser_type_owned(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
783-
col_specs.push(ColumnSpec::owned(name, typ, table_spec.into_owned()));
784-
}
785-
Ok(col_specs)
771+
table_spec
772+
}
773+
};
774+
775+
Ok(table_spec)
786776
}
787777

778+
macro_rules! generate_deser_col_specs {
779+
($deser_col_specs: ident, $l: lifetime, $deser_type: ident, $make_col_spec: expr $(,)?) => {
780+
/// Deserializes col specs (part of ResultMetadata or PreparedMetadata)
781+
/// in the form mentioned by its name.
782+
///
783+
/// Checks for equality of table specs across columns, because the protocol
784+
/// does not guarantee that and we want to be sure that the assumption
785+
/// of them being all the same is correct.
786+
///
787+
/// To avoid needless allocations, it is advised to pass `global_table_spec`
788+
/// in the borrowed form, so that cloning it is cheap.
789+
fn $deser_col_specs<'frame>(
790+
buf: &mut &'frame [u8],
791+
global_table_spec: Option<TableSpec<'frame>>,
792+
col_count: usize,
793+
) -> StdResult<Vec<ColumnSpec<$l>>, ColumnSpecParseError> {
794+
let global_table_spec_provided = global_table_spec.is_some();
795+
let mut known_table_spec = global_table_spec;
796+
797+
let mut col_specs = Vec::with_capacity(col_count);
798+
for col_idx in 0..col_count {
799+
let table_spec = deser_table_spec_for_col_spec(
800+
buf,
801+
global_table_spec_provided,
802+
&mut known_table_spec,
803+
col_idx,
804+
)?;
805+
806+
let name =
807+
types::read_string(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
808+
let typ = $deser_type(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
809+
let col_spec = $make_col_spec(name, typ, table_spec);
810+
col_specs.push(col_spec);
811+
}
812+
Ok(col_specs)
813+
}
814+
};
815+
}
816+
817+
generate_deser_col_specs!(
818+
_deser_col_specs_borrowed,
819+
'frame,
820+
_deser_type_borrowed,
821+
ColumnSpec::borrowed,
822+
);
823+
824+
generate_deser_col_specs!(
825+
deser_col_specs_owned,
826+
'static,
827+
deser_type_owned,
828+
|name: &str, typ, table_spec: TableSpec| ColumnSpec::owned(name.to_owned(), typ, table_spec.into_owned()),
829+
);
830+
788831
fn deser_result_metadata(
789832
buf: &mut &[u8],
790833
) -> StdResult<(ResultMetadata<'static>, PagingStateResponse), ResultMetadataParseError> {
@@ -800,6 +843,7 @@ fn deser_result_metadata(
800843
let raw_paging_state = has_more_pages
801844
.then(|| types::read_bytes(buf).map_err(ResultMetadataParseError::PagingStateParseError))
802845
.transpose()?;
846+
803847
let paging_state = PagingStateResponse::new_from_raw_bytes(raw_paging_state);
804848

805849
let col_specs = if no_metadata {
@@ -809,7 +853,7 @@ fn deser_result_metadata(
809853
.then(|| deser_table_spec(buf))
810854
.transpose()?;
811855

812-
deser_col_specs(buf, global_table_spec, col_count)?
856+
deser_col_specs_owned(buf, global_table_spec, col_count)?
813857
};
814858

815859
let metadata = ResultMetadata {
@@ -847,7 +891,7 @@ fn deser_prepared_metadata(
847891
.then(|| deser_table_spec(buf))
848892
.transpose()?;
849893

850-
let col_specs = deser_col_specs(buf, global_table_spec, col_count)?;
894+
let col_specs = deser_col_specs_owned(buf, global_table_spec, col_count)?;
851895

852896
Ok(PreparedMetadata {
853897
flags,

scylla/src/transport/errors.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use scylla_cql::{
1919
CqlAuthChallengeParseError, CqlAuthSuccessParseError, CqlAuthenticateParseError,
2020
CqlErrorParseError, CqlEventParseError, CqlRequestSerializationError,
2121
CqlResponseParseError, CqlResultParseError, CqlSupportedParseError,
22-
FrameBodyExtensionsParseError, FrameHeaderParseError,
22+
FrameBodyExtensionsParseError, FrameHeaderParseError, RowsParseError,
2323
},
2424
request::CqlRequestKind,
2525
response::CqlResponseKind,
@@ -178,6 +178,18 @@ impl From<response::Error> for QueryError {
178178
}
179179
}
180180

181+
impl From<RowsParseError> for QueryError {
182+
fn from(err: RowsParseError) -> Self {
183+
let err: CqlResultParseError = err.into();
184+
let err: CqlResponseParseError = err.into();
185+
let err: RequestError = err.into();
186+
let err: UserRequestError = err.into();
187+
let err: QueryError = err.into();
188+
189+
err
190+
}
191+
}
192+
181193
/// Error that occurred during session creation
182194
#[derive(Error, Debug, Clone)]
183195
#[non_exhaustive]

0 commit comments

Comments
 (0)