Skip to content

Commit

Permalink
Merge pull request #862 from piodul/implement-for-new-serialization
Browse files Browse the repository at this point in the history
serialization: implement new serialization traits for the currently supported types
  • Loading branch information
piodul authored Dec 5, 2023
2 parents 2f78d46 + 0642eb5 commit bdcf349
Show file tree
Hide file tree
Showing 7 changed files with 2,739 additions and 132 deletions.
25 changes: 25 additions & 0 deletions scylla-cql/src/frame/response/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,31 @@ pub enum CqlValue {
Varint(BigInt),
}

impl ColumnType {
// Returns true if the type allows a special, empty value in addition to its
// natural representation. For example, bigint represents a 32-bit integer,
// but it can also hold a 0-bit empty value.
//
// It looks like Cassandra 4.1.3 rejects empty values for some more types than
// Scylla: date, time, smallint and tinyint. We will only check against
// Scylla's set of types supported for empty values as it's smaller;
// with Cassandra, some rejects will just have to be rejected on the db side.
pub(crate) fn supports_special_empty_value(&self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
ColumnType::Counter
| ColumnType::Duration
| ColumnType::List(_)
| ColumnType::Map(_, _)
| ColumnType::Set(_)
| ColumnType::UserDefinedType { .. }
| ColumnType::Custom(_) => false,

_ => true,
}
}
}

impl CqlValue {
pub fn as_ascii(&self) -> Option<&String> {
match self {
Expand Down
46 changes: 26 additions & 20 deletions scylla-cql/src/frame/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub trait Value {
#[error("Value too big to be sent in a request - max 2GiB allowed")]
pub struct ValueTooBig;

#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[error("Value is too large to fit in the CQL type")]
pub struct ValueOverflow;

/// Represents an unset value
pub struct Unset;

Expand All @@ -40,7 +44,7 @@ pub struct Counter(pub i64);

/// Enum providing a way to represent a value that might be unset
#[derive(Clone, Copy)]
pub enum MaybeUnset<V: Value> {
pub enum MaybeUnset<V> {
Unset,
Set(V),
}
Expand Down Expand Up @@ -78,7 +82,7 @@ impl From<NaiveDate> for CqlDate {

#[cfg(feature = "chrono")]
impl TryInto<NaiveDate> for CqlDate {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<NaiveDate, Self::Error> {
let days_since_unix_epoch = self.0 as i64 - (1 << 31);
Expand All @@ -90,7 +94,7 @@ impl TryInto<NaiveDate> for CqlDate {
NaiveDate::from_yo_opt(1970, 1)
.unwrap()
.checked_add_signed(duration_since_unix_epoch)
.ok_or(ValueTooBig)
.ok_or(ValueOverflow)
}
}

Expand All @@ -103,19 +107,19 @@ impl From<DateTime<Utc>> for CqlTimestamp {

#[cfg(feature = "chrono")]
impl TryInto<DateTime<Utc>> for CqlTimestamp {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<DateTime<Utc>, Self::Error> {
match Utc.timestamp_millis_opt(self.0) {
chrono::LocalResult::Single(datetime) => Ok(datetime),
_ => Err(ValueTooBig),
_ => Err(ValueOverflow),
}
}
}

#[cfg(feature = "chrono")]
impl TryFrom<NaiveTime> for CqlTime {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_from(value: NaiveTime) -> Result<Self, Self::Error> {
let nanos = value
Expand All @@ -127,23 +131,23 @@ impl TryFrom<NaiveTime> for CqlTime {
if nanos <= 86399999999999 {
Ok(Self(nanos))
} else {
Err(ValueTooBig)
Err(ValueOverflow)
}
}
}

#[cfg(feature = "chrono")]
impl TryInto<NaiveTime> for CqlTime {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<NaiveTime, Self::Error> {
let secs = (self.0 / 1_000_000_000)
.try_into()
.map_err(|_| ValueTooBig)?;
.map_err(|_| ValueOverflow)?;
let nanos = (self.0 % 1_000_000_000)
.try_into()
.map_err(|_| ValueTooBig)?;
NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or(ValueTooBig)
.map_err(|_| ValueOverflow)?;
NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or(ValueOverflow)
}
}

Expand All @@ -167,17 +171,17 @@ impl From<time::Date> for CqlDate {

#[cfg(feature = "time")]
impl TryInto<time::Date> for CqlDate {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<time::Date, Self::Error> {
const JULIAN_DAY_OFFSET: i64 =
(1 << 31) - time::OffsetDateTime::UNIX_EPOCH.date().to_julian_day() as i64;

let julian_days = (self.0 as i64 - JULIAN_DAY_OFFSET)
.try_into()
.map_err(|_| ValueTooBig)?;
.map_err(|_| ValueOverflow)?;

time::Date::from_julian_day(julian_days).map_err(|_| ValueTooBig)
time::Date::from_julian_day(julian_days).map_err(|_| ValueOverflow)
}
}

Expand Down Expand Up @@ -209,11 +213,11 @@ impl From<time::OffsetDateTime> for CqlTimestamp {

#[cfg(feature = "time")]
impl TryInto<time::OffsetDateTime> for CqlTimestamp {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<time::OffsetDateTime, Self::Error> {
time::OffsetDateTime::from_unix_timestamp_nanos(self.0 as i128 * 1_000_000)
.map_err(|_| ValueTooBig)
.map_err(|_| ValueOverflow)
}
}

Expand All @@ -231,7 +235,7 @@ impl From<time::Time> for CqlTime {

#[cfg(feature = "time")]
impl TryInto<time::Time> for CqlTime {
type Error = ValueTooBig;
type Error = ValueOverflow;

fn try_into(self) -> Result<time::Time, Self::Error> {
let h = self.0 / 3_600_000_000_000;
Expand All @@ -240,12 +244,12 @@ impl TryInto<time::Time> for CqlTime {
let n = self.0 % 1_000_000_000;

time::Time::from_hms_nano(
h.try_into().map_err(|_| ValueTooBig)?,
h.try_into().map_err(|_| ValueOverflow)?,
m as u8,
s as u8,
n as u32,
)
.map_err(|_| ValueTooBig)
.map_err(|_| ValueOverflow)
}
}

Expand Down Expand Up @@ -631,7 +635,9 @@ impl Value for time::OffsetDateTime {
#[cfg(feature = "chrono")]
impl Value for NaiveTime {
fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
CqlTime::try_from(*self)?.serialize(buf)
CqlTime::try_from(*self)
.map_err(|_| ValueTooBig)?
.serialize(buf)
}
}

Expand Down
Loading

0 comments on commit bdcf349

Please sign in to comment.