Skip to content

Commit

Permalink
chrono: add support for chronov3
Browse files Browse the repository at this point in the history
  • Loading branch information
muzarski committed Apr 25, 2024
1 parent fba8b29 commit 1abe8d5
Show file tree
Hide file tree
Showing 8 changed files with 505 additions and 3 deletions.
4 changes: 3 additions & 1 deletion scylla-cql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ thiserror = "1.0"
num-bigint-03 = { package = "num-bigint", version = "0.3", optional = true }
num-bigint-04 = { package = "num-bigint", version = "0.4", optional = true }
bigdecimal-04 = { package = "bigdecimal", version = "0.4", optional = true }
chrono-03 = { package = "chrono", version = "0.3", default-features = false, optional = true }
chrono-04 = { package = "chrono", version = "0.4.32", default-features = false, optional = true }
lz4_flex = { version = "0.11.1" }
async-trait = "0.1.57"
Expand All @@ -39,11 +40,12 @@ harness = false
[features]
secrecy-08 = ["dep:secrecy-08"]
time-03 = ["dep:time-03"]
chrono-03 = ["dep:chrono-03"]
chrono-04 = ["dep:chrono-04"]
num-bigint-03 = ["dep:num-bigint-03"]
num-bigint-04 = ["dep:num-bigint-04"]
bigdecimal-04 = ["dep:bigdecimal-04"]
full-serialization = ["chrono-04", "time-03", "secrecy-08", "num-bigint-03", "num-bigint-04", "bigdecimal-04"]
full-serialization = ["chrono-03", "chrono-04", "time-03", "secrecy-08", "num-bigint-03", "num-bigint-04", "bigdecimal-04"]

[lints.rust]
unreachable_pub = "warn"
124 changes: 124 additions & 0 deletions scylla-cql/src/frame/response/cql_to_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ impl FromCqlVal<CqlValue> for bigdecimal_04::BigDecimal {
}
}

#[cfg(feature = "chrono-03")]
impl FromCqlVal<CqlValue> for chrono_03::NaiveDate {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
match cql_val {
CqlValue::Date(cql_date) => cql_date.try_into().map_err(|_| FromCqlValError::BadVal),
_ => Err(FromCqlValError::BadCqlType),
}
}
}

#[cfg(feature = "chrono-04")]
impl FromCqlVal<CqlValue> for chrono_04::NaiveDate {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
Expand All @@ -195,6 +205,16 @@ impl FromCqlVal<CqlValue> for time_03::Date {
}
}

#[cfg(feature = "chrono-03")]
impl FromCqlVal<CqlValue> for chrono_03::NaiveTime {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
match cql_val {
CqlValue::Time(cql_time) => cql_time.try_into().map_err(|_| FromCqlValError::BadVal),
_ => Err(FromCqlValError::BadCqlType),
}
}
}

#[cfg(feature = "chrono-04")]
impl FromCqlVal<CqlValue> for chrono_04::NaiveTime {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
Expand All @@ -215,6 +235,17 @@ impl FromCqlVal<CqlValue> for time_03::Time {
}
}

#[cfg(feature = "chrono-03")]
impl FromCqlVal<CqlValue> for chrono_03::DateTime<chrono_03::UTC> {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
cql_val
.as_cql_timestamp()
.ok_or(FromCqlValError::BadCqlType)?
.try_into()
.map_err(|_| FromCqlValError::BadVal)
}
}

#[cfg(feature = "chrono-04")]
impl FromCqlVal<CqlValue> for chrono_04::DateTime<chrono_04::Utc> {
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
Expand Down Expand Up @@ -523,6 +554,36 @@ mod tests {
assert_eq!(Ok(counter), Counter::from_cql(CqlValue::Counter(counter)));
}

#[cfg(feature = "chrono-03")]
#[test]
fn naive_date_03_from_cql() {
use chrono_03::NaiveDate;

let unix_epoch: CqlValue = CqlValue::Date(CqlDate(2_u32.pow(31)));
assert_eq!(
Ok(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()),
NaiveDate::from_cql(unix_epoch)
);

let before_epoch: CqlValue = CqlValue::Date(CqlDate(2_u32.pow(31) - 30));
assert_eq!(
Ok(NaiveDate::from_ymd_opt(1969, 12, 2).unwrap()),
NaiveDate::from_cql(before_epoch)
);

let after_epoch: CqlValue = CqlValue::Date(CqlDate(2_u32.pow(31) + 30));
assert_eq!(
Ok(NaiveDate::from_ymd_opt(1970, 1, 31).unwrap()),
NaiveDate::from_cql(after_epoch)
);

let min_date: CqlValue = CqlValue::Date(CqlDate(0));
assert!(NaiveDate::from_cql(min_date).is_err());

let max_date: CqlValue = CqlValue::Date(CqlDate(u32::MAX));
assert!(NaiveDate::from_cql(max_date).is_err());
}

#[cfg(feature = "chrono-04")]
#[test]
fn naive_date_04_from_cql() {
Expand Down Expand Up @@ -631,6 +692,50 @@ mod tests {
assert_eq!(time_ns, CqlTime::from_cql(cql_value).unwrap().0);
}

#[cfg(feature = "chrono-03")]
#[test]
fn naive_time_03_from_cql() {
use chrono_03::NaiveTime;

// Midnight
let midnight = CqlValue::Time(CqlTime(0));
assert_eq!(
Ok(NaiveTime::from_hms(0, 0, 0)),
NaiveTime::from_cql(midnight)
);

// 7:15:21.123456789
let morning = CqlValue::Time(CqlTime(
(7 * 3600 + 15 * 60 + 21) * 1_000_000_000 + 123_456_789,
));
assert_eq!(
Ok(NaiveTime::from_hms_nano_opt(7, 15, 21, 123_456_789).unwrap()),
NaiveTime::from_cql(morning)
);

// 23:59:59.999999999
let late_night = CqlValue::Time(CqlTime(
(23 * 3600 + 59 * 60 + 59) * 1_000_000_000 + 999_999_999,
));
assert_eq!(
Ok(NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap()),
NaiveTime::from_cql(late_night)
);

// Bad values. Since value is out of `chrono_04::NaiveTime` range, it should return `BadVal` error
let bad_time1 = CqlValue::Time(CqlTime(-1));
assert_eq!(Err(FromCqlValError::BadVal), NaiveTime::from_cql(bad_time1));
let bad_time2 = CqlValue::Time(CqlTime(i64::MAX));
assert_eq!(Err(FromCqlValError::BadVal), NaiveTime::from_cql(bad_time2));

// Different CQL type. Since value can't be casted, it should return `BadCqlType` error
let bad_type = CqlValue::Double(0.5);
assert_eq!(
Err(FromCqlValError::BadCqlType),
NaiveTime::from_cql(bad_type)
);
}

#[cfg(feature = "chrono-04")]
#[test]
fn naive_time_04_from_cql() {
Expand Down Expand Up @@ -731,6 +836,25 @@ mod tests {
);
}

#[cfg(feature = "chrono-03")]
#[test]
fn datetime_03_from_cql() {
use chrono_03::{DateTime, NaiveDate, UTC};
let naivedatetime_utc = NaiveDate::from_ymd_opt(2022, 12, 31)
.unwrap()
.and_hms_opt(2, 0, 0)
.unwrap();
let datetime_utc = DateTime::<UTC>::from_utc(naivedatetime_utc, UTC);

assert_eq!(
datetime_utc,
DateTime::<UTC>::from_cql(CqlValue::Timestamp(CqlTimestamp(
datetime_utc.timestamp() * 1000 + datetime_utc.timestamp_subsec_millis() as i64
)))
.unwrap()
);
}

#[cfg(feature = "chrono-04")]
#[test]
fn datetime_04_from_cql() {
Expand Down
168 changes: 168 additions & 0 deletions scylla-cql/src/frame/response/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ impl CqlValue {
}
}

#[cfg(test)]
#[cfg(feature = "chrono-03")]
fn as_naive_date_03(&self) -> Option<chrono_03::NaiveDate> {
self.as_cql_date().and_then(|date| date.try_into().ok())
}

#[cfg(test)]
#[cfg(feature = "chrono-04")]
fn as_naive_date_04(&self) -> Option<chrono_04::NaiveDate> {
Expand All @@ -171,6 +177,12 @@ impl CqlValue {
}
}

#[cfg(test)]
#[cfg(feature = "chrono-03")]
fn as_datetime_03(&self) -> Option<chrono_03::DateTime<chrono_03::UTC>> {
self.as_cql_timestamp().and_then(|ts| ts.try_into().ok())
}

#[cfg(test)]
#[cfg(feature = "chrono-04")]
fn as_datetime_04(&self) -> Option<chrono_04::DateTime<chrono_04::Utc>> {
Expand All @@ -189,6 +201,12 @@ impl CqlValue {
}
}

#[cfg(test)]
#[cfg(feature = "chrono-03")]
fn as_naive_time_03(&self) -> Option<chrono_03::NaiveTime> {
self.as_cql_time().and_then(|ts| ts.try_into().ok())
}

#[cfg(test)]
#[cfg(feature = "chrono-04")]
fn as_naive_time_04(&self) -> Option<chrono_04::NaiveTime> {
Expand Down Expand Up @@ -1362,6 +1380,55 @@ mod tests {
assert_eq!(date.as_cql_date(), Some(max_date));
}

#[cfg(feature = "chrono-03")]
#[test]
fn test_naive_date_03_from_cql() {
use chrono_03::NaiveDate;

// 2^31 when converted to NaiveDate is 1970-01-01
let unix_epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
let date =
super::deser_cql_value(&ColumnType::Date, &mut (1u32 << 31).to_be_bytes().as_ref())
.unwrap();

assert_eq!(date.as_naive_date_03(), Some(unix_epoch));

// 2^31 - 30 when converted to NaiveDate is 1969-12-02
let before_epoch = NaiveDate::from_ymd_opt(1969, 12, 2).unwrap();
let date = super::deser_cql_value(
&ColumnType::Date,
&mut ((1u32 << 31) - 30).to_be_bytes().as_ref(),
)
.unwrap();

assert_eq!(date.as_naive_date_03(), Some(before_epoch));

// 2^31 + 30 when converted to NaiveDate is 1970-01-31
let after_epoch = NaiveDate::from_ymd_opt(1970, 1, 31).unwrap();
let date = super::deser_cql_value(
&ColumnType::Date,
&mut ((1u32 << 31) + 30).to_be_bytes().as_ref(),
)
.unwrap();

assert_eq!(date.as_naive_date_03(), Some(after_epoch));

// 0 and u32::MAX are out of NaiveDate range, fails with an error, not panics
assert_eq!(
super::deser_cql_value(&ColumnType::Date, &mut 0_u32.to_be_bytes().as_ref())
.unwrap()
.as_naive_date_04(),
None
);

assert_eq!(
super::deser_cql_value(&ColumnType::Date, &mut u32::MAX.to_be_bytes().as_ref())
.unwrap()
.as_naive_date_04(),
None
);
}

#[cfg(feature = "chrono-04")]
#[test]
fn test_naive_date_04_from_cql() {
Expand Down Expand Up @@ -1485,6 +1552,45 @@ mod tests {
}
}

#[cfg(feature = "chrono-03")]
#[test]
fn test_naive_time_03_from_cql() {
use chrono_03::NaiveTime;

// 0 when converted to NaiveTime is 0:0:0.0
let midnight = NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap();
let time =
super::deser_cql_value(&ColumnType::Time, &mut (0i64).to_be_bytes().as_ref()).unwrap();

assert_eq!(time.as_naive_time_03(), Some(midnight));

// 10:10:30.500,000,001
let (h, m, s, n) = (10, 10, 30, 500_000_001);
let midnight = NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap();
let time = super::deser_cql_value(
&ColumnType::Time,
&mut ((h as i64 * 3600 + m as i64 * 60 + s as i64) * 1_000_000_000 + n as i64)
.to_be_bytes()
.as_ref(),
)
.unwrap();

assert_eq!(time.as_naive_time_03(), Some(midnight));

// 23:59:59.999,999,999
let (h, m, s, n) = (23, 59, 59, 999_999_999);
let midnight = NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap();
let time = super::deser_cql_value(
&ColumnType::Time,
&mut ((h as i64 * 3600 + m as i64 * 60 + s as i64) * 1_000_000_000 + n as i64)
.to_be_bytes()
.as_ref(),
)
.unwrap();

assert_eq!(time.as_naive_time_03(), Some(midnight));
}

#[cfg(feature = "chrono-04")]
#[test]
fn test_naive_time_04_from_cql() {
Expand Down Expand Up @@ -1577,6 +1683,68 @@ mod tests {
}
}

#[cfg(feature = "chrono-03")]
#[test]
fn test_datetime_03_from_cql() {
use chrono_03::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, UTC};

// 0 when converted to DateTime is 1970-01-01 0:00:00.00
let unix_epoch = DateTime::from_utc(NaiveDateTime::from_timestamp(0, 0), UTC);
let date = super::deser_cql_value(&ColumnType::Timestamp, &mut 0i64.to_be_bytes().as_ref())
.unwrap();

assert_eq!(date.as_datetime_03(), Some(unix_epoch));

// When converted to NaiveDateTime, this is 1969-12-01 11:29:29.5
let timestamp: i64 = -((((30 * 24 + 12) * 60 + 30) * 60 + 30) * 1000 + 500);
let before_epoch = DateTime::from_utc(
NaiveDateTime::new(
NaiveDate::from_ymd_opt(1969, 12, 1).unwrap(),
NaiveTime::from_hms_milli_opt(11, 29, 29, 500).unwrap(),
),
UTC,
);
let date = super::deser_cql_value(
&ColumnType::Timestamp,
&mut timestamp.to_be_bytes().as_ref(),
)
.unwrap();

assert_eq!(date.as_datetime_03(), Some(before_epoch));

// when converted to NaiveDateTime, this is is 1970-01-31 12:30:30.5
let timestamp: i64 = (((30 * 24 + 12) * 60 + 30) * 60 + 30) * 1000 + 500;
let after_epoch = DateTime::from_utc(
NaiveDateTime::new(
NaiveDate::from_ymd_opt(1970, 1, 31).unwrap(),
NaiveTime::from_hms_milli_opt(12, 30, 30, 500).unwrap(),
),
UTC,
);
let date = super::deser_cql_value(
&ColumnType::Timestamp,
&mut timestamp.to_be_bytes().as_ref(),
)
.unwrap();

assert_eq!(date.as_datetime_03(), Some(after_epoch));

// 0 and u32::MAX are out of NaiveDate range, fails with an error, not panics
assert_eq!(
super::deser_cql_value(&ColumnType::Timestamp, &mut i64::MIN.to_be_bytes().as_ref())
.unwrap()
.as_datetime_04(),
None
);

assert_eq!(
super::deser_cql_value(&ColumnType::Timestamp, &mut i64::MAX.to_be_bytes().as_ref())
.unwrap()
.as_datetime_04(),
None
);
}

#[cfg(feature = "chrono-04")]
#[test]
fn test_datetime_04_from_cql() {
Expand Down
Loading

0 comments on commit 1abe8d5

Please sign in to comment.