use std::fmt::Display;
use thiserror::Error;
use super::value::DeserializeValue;
use super::{make_error_replace_rust_name, DeserializationError, FrameSlice, TypeCheckError};
use crate::frame::response::result::{ColumnSpec, ColumnType};
use crate::value::{CqlValue, Row};
#[non_exhaustive]
pub struct RawColumn<'frame, 'metadata> {
pub index: usize,
pub spec: &'metadata ColumnSpec<'metadata>,
pub slice: Option<FrameSlice<'frame>>,
}
#[derive(Clone, Debug)]
pub struct ColumnIterator<'frame, 'metadata> {
specs: std::iter::Enumerate<std::slice::Iter<'metadata, ColumnSpec<'metadata>>>,
slice: FrameSlice<'frame>,
}
impl<'frame, 'metadata> ColumnIterator<'frame, 'metadata> {
#[inline]
pub fn new(specs: &'metadata [ColumnSpec<'metadata>], slice: FrameSlice<'frame>) -> Self {
Self {
specs: specs.iter().enumerate(),
slice,
}
}
#[inline]
pub fn columns_remaining(&self) -> usize {
self.specs.len()
}
}
impl<'frame, 'metadata> Iterator for ColumnIterator<'frame, 'metadata> {
type Item = Result<RawColumn<'frame, 'metadata>, DeserializationError>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (column_index, spec) = self.specs.next()?;
Some(
self.slice
.read_cql_bytes()
.map(|slice| RawColumn {
index: column_index,
spec,
slice,
})
.map_err(|err| {
mk_deser_err::<Self>(
BuiltinDeserializationErrorKind::RawColumnDeserializationFailed {
column_index,
column_name: spec.name().to_owned(),
err: DeserializationError::new(err),
},
)
}),
)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.specs.size_hint()
}
}
pub trait DeserializeRow<'frame, 'metadata>
where
Self: Sized,
{
fn type_check(specs: &[ColumnSpec]) -> Result<(), TypeCheckError>;
fn deserialize(row: ColumnIterator<'frame, 'metadata>) -> Result<Self, DeserializationError>;
}
impl<'frame, 'metadata> DeserializeRow<'frame, 'metadata> for ColumnIterator<'frame, 'metadata> {
#[inline]
fn type_check(_specs: &[ColumnSpec]) -> Result<(), TypeCheckError> {
Ok(())
}
#[inline]
fn deserialize(row: ColumnIterator<'frame, 'metadata>) -> Result<Self, DeserializationError> {
Ok(row)
}
}
make_error_replace_rust_name!(
pub(self),
_typck_error_replace_rust_name,
TypeCheckError,
BuiltinTypeCheckError
);
make_error_replace_rust_name!(
pub,
deser_error_replace_rust_name,
DeserializationError,
BuiltinDeserializationError
);
impl<'frame, 'metadata> DeserializeRow<'frame, 'metadata> for Row {
#[inline]
fn type_check(_specs: &[ColumnSpec]) -> Result<(), TypeCheckError> {
Ok(())
}
#[inline]
fn deserialize(
mut row: ColumnIterator<'frame, 'metadata>,
) -> Result<Self, DeserializationError> {
let mut columns = Vec::with_capacity(row.size_hint().0);
while let Some(column) = row
.next()
.transpose()
.map_err(deser_error_replace_rust_name::<Self>)?
{
columns.push(
<Option<CqlValue>>::deserialize(column.spec.typ(), column.slice).map_err(
|err| {
mk_deser_err::<Self>(
BuiltinDeserializationErrorKind::ColumnDeserializationFailed {
column_index: column.index,
column_name: column.spec.name().to_owned(),
err,
},
)
},
)?,
);
}
Ok(Self { columns })
}
}
macro_rules! impl_tuple {
($($Ti:ident),*; $($idx:literal),*; $($idf:ident),*) => {
impl<'frame, 'metadata, $($Ti),*> DeserializeRow<'frame, 'metadata> for ($($Ti,)*)
where
$($Ti: DeserializeValue<'frame, 'metadata>),*
{
fn type_check(specs: &[ColumnSpec]) -> Result<(), TypeCheckError> {
const TUPLE_LEN: usize = (&[$($idx),*] as &[i32]).len();
let column_types_iter = || specs.iter().map(|spec| spec.typ().clone().into_owned());
if let [$($idf),*] = &specs {
$(
<$Ti as DeserializeValue<'frame, 'metadata>>::type_check($idf.typ())
.map_err(|err| mk_typck_err::<Self>(column_types_iter(), BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed {
column_index: $idx,
column_name: specs[$idx].name().to_owned(),
err
}))?;
)*
Ok(())
} else {
Err(mk_typck_err::<Self>(column_types_iter(), BuiltinTypeCheckErrorKind::WrongColumnCount {
rust_cols: TUPLE_LEN, cql_cols: specs.len()
}))
}
}
fn deserialize(mut row: ColumnIterator<'frame, 'metadata>) -> Result<Self, DeserializationError> {
const TUPLE_LEN: usize = (&[$($idx),*] as &[i32]).len();
let ret = (
$({
let column = row.next().unwrap_or_else(|| unreachable!(
"Typecheck should have prevented this scenario! Column count mismatch: rust type {}, cql row {}",
TUPLE_LEN,
$idx
)).map_err(deser_error_replace_rust_name::<Self>)?;
<$Ti as DeserializeValue<'frame, 'metadata>>::deserialize(column.spec.typ(), column.slice)
.map_err(|err| mk_deser_err::<Self>(BuiltinDeserializationErrorKind::ColumnDeserializationFailed {
column_index: column.index,
column_name: column.spec.name().to_owned(),
err,
}))?
},)*
);
assert!(
row.next().is_none(),
"Typecheck should have prevented this scenario! Column count mismatch: rust type {}, cql row is bigger",
TUPLE_LEN,
);
Ok(ret)
}
}
}
}
use super::value::impl_tuple_multiple;
impl_tuple_multiple!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15;
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15;
t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15
);
#[derive(Debug, Error, Clone)]
#[error("Failed to type check the Rust type {rust_name} against CQL column types {cql_types:?} : {kind}")]
pub struct BuiltinTypeCheckError {
pub rust_name: &'static str,
pub cql_types: Vec<ColumnType<'static>>,
pub kind: BuiltinTypeCheckErrorKind,
}
#[doc(hidden)]
pub fn mk_typck_err<T>(
cql_types: impl IntoIterator<Item = ColumnType<'static>>,
kind: impl Into<BuiltinTypeCheckErrorKind>,
) -> TypeCheckError {
mk_typck_err_named(std::any::type_name::<T>(), cql_types, kind)
}
fn mk_typck_err_named(
name: &'static str,
cql_types: impl IntoIterator<Item = ColumnType<'static>>,
kind: impl Into<BuiltinTypeCheckErrorKind>,
) -> TypeCheckError {
TypeCheckError::new(BuiltinTypeCheckError {
rust_name: name,
cql_types: Vec::from_iter(cql_types),
kind: kind.into(),
})
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum BuiltinTypeCheckErrorKind {
WrongColumnCount {
rust_cols: usize,
cql_cols: usize,
},
ColumnWithUnknownName {
column_index: usize,
column_name: String,
},
ValuesMissingForColumns {
column_names: Vec<&'static str>,
},
ColumnNameMismatch {
field_index: usize,
column_index: usize,
rust_column_name: &'static str,
db_column_name: String,
},
ColumnTypeCheckFailed {
column_index: usize,
column_name: String,
err: TypeCheckError,
},
DuplicatedColumn {
column_index: usize,
column_name: &'static str,
},
}
impl Display for BuiltinTypeCheckErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuiltinTypeCheckErrorKind::WrongColumnCount {
rust_cols,
cql_cols,
} => {
write!(f, "wrong column count: the statement operates on {cql_cols} columns, but the given rust types contains {rust_cols}")
}
BuiltinTypeCheckErrorKind::ColumnWithUnknownName { column_name, column_index } => {
write!(
f,
"the CQL row contains a column {column_name} at column index {column_index}, but the corresponding field is not found in the Rust type",
)
}
BuiltinTypeCheckErrorKind::ValuesMissingForColumns { column_names } => {
write!(
f,
"values for columns {column_names:?} are missing from the DB data but are required by the Rust type"
)
},
BuiltinTypeCheckErrorKind::ColumnNameMismatch {
field_index,
column_index,rust_column_name,
db_column_name
} => write!(
f,
"expected column with name {db_column_name} at column index {column_index}, but the Rust field name at corresponding field index {field_index} is {rust_column_name}",
),
BuiltinTypeCheckErrorKind::ColumnTypeCheckFailed {
column_index,
column_name,
err,
} => write!(
f,
"mismatched types in column {column_name} at index {column_index}: {err}"
),
BuiltinTypeCheckErrorKind::DuplicatedColumn { column_name, column_index } => write!(
f,
"column {column_name} occurs more than once in DB metadata; second occurrence is at column index {column_index}",
),
}
}
}
#[derive(Debug, Error, Clone)]
#[error("Failed to deserialize query result row {rust_name}: {kind}")]
pub struct BuiltinDeserializationError {
pub rust_name: &'static str,
pub kind: BuiltinDeserializationErrorKind,
}
#[doc(hidden)]
pub fn mk_deser_err<T>(kind: impl Into<BuiltinDeserializationErrorKind>) -> DeserializationError {
mk_deser_err_named(std::any::type_name::<T>(), kind)
}
fn mk_deser_err_named(
name: &'static str,
kind: impl Into<BuiltinDeserializationErrorKind>,
) -> DeserializationError {
DeserializationError::new(BuiltinDeserializationError {
rust_name: name,
kind: kind.into(),
})
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum BuiltinDeserializationErrorKind {
ColumnDeserializationFailed {
column_index: usize,
column_name: String,
err: DeserializationError,
},
RawColumnDeserializationFailed {
column_index: usize,
column_name: String,
err: DeserializationError,
},
}
impl Display for BuiltinDeserializationErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuiltinDeserializationErrorKind::ColumnDeserializationFailed {
column_index,
column_name,
err,
} => {
write!(
f,
"failed to deserialize column {column_name} at index {column_index}: {err}"
)
}
BuiltinDeserializationErrorKind::RawColumnDeserializationFailed {
column_index,
column_name,
err,
} => {
write!(
f,
"failed to deserialize raw column {column_name} at index {column_index} (most probably due to invalid column structure inside a row): {err}"
)
}
}
}
}
#[cfg(test)]
#[path = "row_tests.rs"]
pub(crate) mod tests;
fn _test_struct_deserialization_name_check_skip_requires_enforce_order() {}
fn _test_struct_deserialization_skip_name_check_conflicts_with_rename() {}
fn _test_struct_deserialization_skip_rename_collision_with_field() {}
fn _test_struct_deserialization_rename_collision_with_another_rename() {}