From c621c8935cabd1f85f79652622de328747a0a75c Mon Sep 17 00:00:00 2001 From: Stephen Ma Date: Wed, 21 Dec 2022 23:48:25 +0000 Subject: [PATCH 1/5] Add serialize and deserialize support for DateTime and secret --- scylla-cql/Cargo.toml | 1 + scylla-cql/src/frame/response/cql_to_rust.rs | 18 +++++++++++++++++- scylla-cql/src/frame/value.rs | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/scylla-cql/Cargo.toml b/scylla-cql/Cargo.toml index 21ae5acd20..46394dc7d2 100644 --- a/scylla-cql/Cargo.toml +++ b/scylla-cql/Cargo.toml @@ -15,6 +15,7 @@ byteorder = "1.3.4" bytes = "1.0.1" num_enum = "0.5" tokio = { version = "1.12", features = ["io-util", "time"] } +secrecy = "0.7.0" snap = "1.0" uuid = "1.0" thiserror = "1.0" diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index bec6cbd5b4..893eaad34d 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -1,8 +1,9 @@ use super::result::{CqlValue, Row}; use crate::frame::value::Counter; use bigdecimal::BigDecimal; -use chrono::{Duration, NaiveDate}; +use chrono::{DateTime, Duration, NaiveDate, TimeZone, Utc}; use num_bigint::BigInt; +use secrecy::{Secret, Zeroize}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::Hash; use std::net::IpAddr; @@ -149,6 +150,21 @@ impl FromCqlVal for crate::frame::value::Timestamp { } } +impl FromCqlVal for DateTime { + fn from_cql(cql_val: CqlValue) -> Result { + cql_val + .as_bigint() + .ok_or(FromCqlValError::BadCqlType) + .map(|timestamp| Utc.timestamp_millis_opt(timestamp).unwrap()) + } +} + +impl + Zeroize> FromCqlVal for Secret { + fn from_cql(cql_val: CqlValue) -> Result { + Ok(Secret::new(FromCqlVal::from_cql(cql_val)?)) + } +} + // Vec::from_cql impl> FromCqlVal for Vec { fn from_cql(cql_val: CqlValue) -> Result { diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 4ca59a314c..694fbeb5ed 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -4,6 +4,7 @@ use bytes::BufMut; use chrono::prelude::*; use chrono::Duration; use num_bigint::BigInt; +use secrecy::{ExposeSecret, Secret, Zeroize}; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::TryInto; @@ -380,6 +381,20 @@ impl Value for Time { } } +impl Value for DateTime { + fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { + buf.put_i32(8); + buf.put_i64(self.timestamp_millis()); + Ok(()) + } +} + +impl Value for Secret { + fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { + self.expose_secret().serialize(buf) + } +} + impl Value for bool { fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { buf.put_i32(1); From 80e7556bbc65b0cb8842f524b3a36bffd05e6898 Mon Sep 17 00:00:00 2001 From: Stephen Ma Date: Wed, 4 Jan 2023 22:35:39 +0000 Subject: [PATCH 2/5] add secret feature --- scylla-cql/Cargo.toml | 2 ++ scylla-cql/src/frame/response/cql_to_rust.rs | 1 + scylla-cql/src/frame/value.rs | 1 + 3 files changed, 4 insertions(+) diff --git a/scylla-cql/Cargo.toml b/scylla-cql/Cargo.toml index 46394dc7d2..b299631eac 100644 --- a/scylla-cql/Cargo.toml +++ b/scylla-cql/Cargo.toml @@ -32,3 +32,5 @@ criterion = "0.3" name = "benchmark" harness = false +[features] +secret = [] \ No newline at end of file diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index 893eaad34d..091d43600d 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -159,6 +159,7 @@ impl FromCqlVal for DateTime { } } +#[cfg(feature = "secret")] impl + Zeroize> FromCqlVal for Secret { fn from_cql(cql_val: CqlValue) -> Result { Ok(Secret::new(FromCqlVal::from_cql(cql_val)?)) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 694fbeb5ed..6cce4acb3f 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -389,6 +389,7 @@ impl Value for DateTime { } } +#[cfg(feature = "secret")] impl Value for Secret { fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { self.expose_secret().serialize(buf) From 3ad66b1f5d9b787b6aa49f591789290bfa7ac1b9 Mon Sep 17 00:00:00 2001 From: Stephen Ma Date: Wed, 4 Jan 2023 22:45:09 +0000 Subject: [PATCH 3/5] unwrap --- scylla-cql/src/frame/response/cql_to_rust.rs | 15 ++++++++++----- scylla-cql/src/frame/value.rs | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index 091d43600d..271686d5b4 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -3,13 +3,15 @@ use crate::frame::value::Counter; use bigdecimal::BigDecimal; use chrono::{DateTime, Duration, NaiveDate, TimeZone, Utc}; use num_bigint::BigInt; -use secrecy::{Secret, Zeroize}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::Hash; use std::net::IpAddr; use thiserror::Error; use uuid::Uuid; +#[cfg(feature = "secret")] +use secrecy::{Secret, Zeroize}; + #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum FromRowError { #[error("{err} in the column with index {column}")] @@ -37,6 +39,8 @@ pub enum FromCqlValError { BadCqlType, #[error("Value is null")] ValIsNull, + #[error("Bad Value")] + BadVal, } /// This trait defines a way to convert CQL Row into some rust type @@ -152,10 +156,11 @@ impl FromCqlVal for crate::frame::value::Timestamp { impl FromCqlVal for DateTime { fn from_cql(cql_val: CqlValue) -> Result { - cql_val - .as_bigint() - .ok_or(FromCqlValError::BadCqlType) - .map(|timestamp| Utc.timestamp_millis_opt(timestamp).unwrap()) + let timestamp = cql_val.as_bigint().ok_or(FromCqlValError::BadCqlType)?; + match Utc.timestamp_millis_opt(timestamp) { + chrono::LocalResult::Single(datetime) => Ok(datetime), + _ => Err(FromCqlValError::BadVal), + } } } diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 6cce4acb3f..cc321c963e 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -4,7 +4,6 @@ use bytes::BufMut; use chrono::prelude::*; use chrono::Duration; use num_bigint::BigInt; -use secrecy::{ExposeSecret, Secret, Zeroize}; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::TryInto; @@ -15,6 +14,9 @@ use uuid::Uuid; use super::response::result::CqlValue; use super::types::vint_encode; +#[cfg(feature = "secret")] +use secrecy::{ExposeSecret, Secret, Zeroize}; + /// Every value being sent in a query must implement this trait /// serialize() should write the Value as [bytes] to the provided buffer pub trait Value { From 65ab94cd538999db35e67f8acf13ba8a85a05c5f Mon Sep 17 00:00:00 2001 From: Stephen Ma Date: Wed, 4 Jan 2023 23:48:00 +0000 Subject: [PATCH 4/5] tests --- scylla-cql/src/frame/response/cql_to_rust.rs | 18 ++++++++++++++++ scylla-cql/src/frame/value_tests.rs | 22 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index 271686d5b4..b0733f7fdc 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -499,6 +499,24 @@ mod tests { ); } + #[test] + fn datetime_from_cql() { + use chrono::{DateTime, Duration, Utc}; + let naivedatetime_utc = NaiveDate::from_ymd_opt(2022, 12, 31) + .unwrap() + .and_hms_opt(2, 0, 0) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + + assert_eq!( + datetime_utc, + DateTime::::from_cql(CqlValue::Timestamp(Duration::milliseconds( + datetime_utc.timestamp_millis() + ))) + .unwrap() + ); + } + #[test] fn uuid_from_cql() { let test_uuid: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001").unwrap(); diff --git a/scylla-cql/src/frame/value_tests.rs b/scylla-cql/src/frame/value_tests.rs index 3d462f9a8f..50348b50f5 100644 --- a/scylla-cql/src/frame/value_tests.rs +++ b/scylla-cql/src/frame/value_tests.rs @@ -101,6 +101,28 @@ fn timestamp_serialization() { } } +#[test] +fn datetime_serialization() { + use chrono::{DateTime, NaiveDateTime, Utc}; + // Datetime is milliseconds since unix epoch represented as i64 + let max_time: i64 = 24 * 60 * 60 * 1_000_000_000 - 1; + + for test_val in &[0, 1, 15, 18463, max_time, max_time + 16] { + let native_datetime = NaiveDateTime::from_timestamp( + *test_val / 1000, + ((*test_val % 1000) as i32 * 1_000_000) as u32, + ); + let test_datetime = DateTime::::from_utc(native_datetime, Utc); + let bytes: Vec = serialized(test_datetime); + + let mut expected_bytes: Vec = vec![0, 0, 0, 8]; + expected_bytes.extend_from_slice(&test_val.to_be_bytes()); + + assert_eq!(bytes, expected_bytes); + assert_eq!(expected_bytes.len(), 12); + } +} + #[test] fn timeuuid_serialization() { // A few random timeuuids generated manually From 4fb141507b3895906fcd9868c3dee5a2f39d2c91 Mon Sep 17 00:00:00 2001 From: Stephen Ma Date: Thu, 5 Jan 2023 07:46:42 +0000 Subject: [PATCH 5/5] fix --- scylla-cql/src/frame/value_tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scylla-cql/src/frame/value_tests.rs b/scylla-cql/src/frame/value_tests.rs index 50348b50f5..7f7135e1c2 100644 --- a/scylla-cql/src/frame/value_tests.rs +++ b/scylla-cql/src/frame/value_tests.rs @@ -108,10 +108,11 @@ fn datetime_serialization() { let max_time: i64 = 24 * 60 * 60 * 1_000_000_000 - 1; for test_val in &[0, 1, 15, 18463, max_time, max_time + 16] { - let native_datetime = NaiveDateTime::from_timestamp( + let native_datetime = NaiveDateTime::from_timestamp_opt( *test_val / 1000, ((*test_val % 1000) as i32 * 1_000_000) as u32, - ); + ) + .expect("invalid or out-of-range datetime"); let test_datetime = DateTime::::from_utc(native_datetime, Utc); let bytes: Vec = serialized(test_datetime);