Repository: scylladb/scylla-rust-driver
Pull request #187 (2021-03-04 11:17:33)

CQL time types representation
## Description

PR changes CQL time types in a more user-friendly way (before they were represented by raw integer).
Now, time is represented by standard Rust from https://doc.rust-lang.org/std/time/index.html.
Date is represented by chrono date.

(Timestamp, because of it specification, stays as it is for now - it is represented by a number by definition)

### Fixes
Fixes issue: #182

## Pre-review checklist

<!--
    Make sure you took care of the issues on the list.
    Put 'x' into those boxes which apply.
    You can also create the PR now and click on all relevant checkboxes.
    See CONTRIBUTING.md for more details.
-->

- [x] I have split my patch into logically separate commits.
- [x] All commit messages clearly explain what they change and why.
- [x] I added relevant tests for new features and bug fixes.
- [x] All commits compile, pass static checks and pass test.
- [x] PR description sums up the changes and reasons why they should be introduced.
- [x] I added appropriate `Fixes:` annotations to PR description.


cvybhu: @psarna
I made some changes and added some tests
I feel it's not the prettiest but passable

### Date
* Changed `CQLValue` representation to `u32` to allow receiving full range of dates allowed by the spec.
`NaiveDate` can still be received using `into_typed` and sent as a bound value
* For cases in which the users wants to use dates outside `NaiveDate` range there is a wrapper `value::Date(u32)` for sending in queries. For receiving the user can just use raw `CQLValue`.
* Added more tests

### Time
* Added range validation
* Changed `std::time::Duration` to `chrono::Duration` to stay consistent with `Timestamp`
* For sending in queries there is a wrapper `value::Time(Duration)`
* Added more tests
* Maybe found a harmless scylla bug? `'-00:00:01'` is parsed as the time `0h 0m 1s`, while trying to insert `'-01:00:00'` fails

### Timestamp
* Changed representation to `chrono::Duration` - it was easy to confuse whether the timestamp was in seconds, milliseconds or nanoseconds. I'm not sure if this was a good decision. Ideally we could probably use something like [`chrono::DateTime`](https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html) but I'm worried about compatibility:
`DateTime` is represented with:
```rust
date: NaiveDate, time: NaiveTime,
 ```
And our timestamp is represented in milliseconds since epoch.
Conversion between these types seems risky.
* Added more tests
* Scylla doesn't accept timestamps before `1970-01-01`, but the [specification](https://github.com/apache/cassandra/blob/78b13cd0e7a33d45c2081bb135e860bbaca7cbe5/doc/native_protocol_v4.spec#L929) allows it, is this intended?
* I tried using example timestamp values from cassandra [documentation](https://cassandra.apache.org/doc/latest/cql/types.html#timestamps), but scylla gives a different timestamp value, I guess it's just incompatible for some reason.

### Timeuuid
* Added tests


dtzxporter: Thoughts about pulling in `chrono` instead ? Its types match more consistently with cassandra:

Example: 
```
    Date(Date<Utc>),
    Time(Duration),
    Timestamp(DateTime<Utc>),
```

Patch:
diff --git a/.github/workflows/cassandra.yml b/.github/workflows/cassandra.yml
deleted file mode 100644
index 54e7a75c..00000000
--- a/.github/workflows/cassandra.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-# This workflow runs all tests on cassandra to ensure full compatibility
-name: Cassandra tests
-
-on:
-  push:
-    branches: [ main ]
-  pull_request:
-    branches: [ main ]
-
-env:
-  CARGO_TERM_COLOR: always
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    services:
-      cassandra:
-        image: cassandra
-        ports:
-          - 9042:9042
-    steps:
-    - uses: actions/checkout@v2
-      # A separate step for building to separate measuring time of compilation and testing
-    - name: Build the project
-      run: cargo build --verbose --tests
-    - name: Run tests on cassandra
-      # test threads must be one because else database tests will run in parallel and will result in flaky tests
-      run: cargo test --verbose -- --test-threads=1
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 8de1b845..771cede6 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -12,6 +12,7 @@ scylla = {path = "../scylla"}
 tokio = {version = "1.1.0", features = ["full"]}
 tracing = "0.1.25"
 tracing-subscriber = "0.2.16"
+chrono = "0.4"
 
 [[example]]
 name = "basic"
@@ -44,3 +45,7 @@ path = "select-paging.rs"
 [[example]]
 name = "user-defined-type"
 path = "user-defined-type.rs"
+
+[[example]]
+name = "cql-time-types"
+path = "cql-time-types.rs"
diff --git a/examples/cql-time-types.rs b/examples/cql-time-types.rs
new file mode 100644
index 00000000..90b9ea0b
--- /dev/null
+++ b/examples/cql-time-types.rs
@@ -0,0 +1,117 @@
+// An example showing how to use time related types in queries
+// Date, Time, Timestamp
+
+use anyhow::Result;
+use chrono::{Duration, NaiveDate};
+use scylla::frame::response::result::CQLValue;
+use scylla::frame::value::{Date, Time, Timestamp};
+use scylla::transport::session::{IntoTypedRows, Session};
+use scylla::SessionBuilder;
+use std::env;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let uri = env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string());
+
+    println!("Connecting to {} ...", uri);
+
+    let session: Session = SessionBuilder::new().known_node(uri).build().await?;
+
+    session.query("CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1}", &[]).await?;
+
+    // Date
+    // Date is a year, month and day in the range -5877641-06-23 to -5877641-06-23
+
+    session
+        .query(
+            "CREATE TABLE IF NOT EXISTS ks.dates (d date primary key)",
+            &[],
+        )
+        .await?;
+
+    // Dates in the range -262145-1-1 to 262143-12-31 can be represented using chrono::NaiveDate
+    let example_date: NaiveDate = NaiveDate::from_ymd(2020, 2, 20);
+
+    session
+        .query("INSERT INTO ks.dates (d) VALUES (?)", (example_date,))
+        .await?;
+
+    if let Some(rows) = session.query("SELECT d from ks.dates", &[]).await? {
+        for row in rows.into_typed::<(NaiveDate,)>() {
+            let (read_date,): (NaiveDate,) = match row {
+                Ok(read_date) => read_date,
+                Err(_) => continue, // We might read a date that does not fit in NaiveDate, skip it
+            };
+
+            println!("Read a date: {:?}", read_date);
+        }
+    }
+
+    // Dates outside this range must be represented in the raw form - an u32 describing days since -5877641-06-23
+    let example_big_date: Date = Date(u32::max_value());
+    session
+        .query("INSERT INTO ks.dates (d) VALUES (?)", (example_big_date,))
+        .await?;
+
+    if let Some(rows) = session.query("SELECT d from ks.dates", &[]).await? {
+        for row in rows {
+            let read_days: u32 = match row.columns[0] {
+                Some(CQLValue::Date(days)) => days,
+                _ => panic!("oh no"),
+            };
+
+            println!("Read a date as raw days: {}", read_days);
+        }
+    }
+
+    // Time - nanoseconds since midnight in range 0..=86399999999999
+    let example_time: Duration = Duration::hours(1);
+
+    session
+        .query(
+            "CREATE TABLE IF NOT EXISTS ks.times (t time primary key)",
+            &[],
+        )
+        .await?;
+
+    // Time as bound value must be wrapped in value::Time to differentiate from Timestamp
+    session
+        .query("INSERT INTO ks.times (t) VALUES (?)", (Time(example_time),))
+        .await?;
+
+    if let Some(rows) = session.query("SELECT t from ks.times", &[]).await? {
+        for row in rows.into_typed::<(Duration,)>() {
+            let (read_time,): (Duration,) = row?;
+
+            println!("Read a time: {:?}", read_time);
+        }
+    }
+
+    // Timestamp - milliseconds since unix epoch - 1970-01-01
+    let example_timestamp: Duration = Duration::hours(1); // This will describe 1970-01-01 01:00:00
+
+    session
+        .query(
+            "CREATE TABLE IF NOT EXISTS ks.timestamps (t timestamp primary key)",
+            &[],
+        )
+        .await?;
+
+    // Timestamp as bound value must be wrapped in value::Timestamp to differentiate from Time
+    session
+        .query(
+            "INSERT INTO ks.timestamps (t) VALUES (?)",
+            (Timestamp(example_timestamp),),
+        )
+        .await?;
+
+    if let Some(rows) = session.query("SELECT t from ks.timestamps", &[]).await? {
+        for row in rows.into_typed::<(Duration,)>() {
+            let (read_time,): (Duration,) = row?;
+
+            println!("Read a timestamp: {:?}", read_time);
+        }
+    }
+
+    Ok(())
+}
diff --git a/scylla/Cargo.toml b/scylla/Cargo.toml
index 8b425e5e..7172248d 100644
--- a/scylla/Cargo.toml
+++ b/scylla/Cargo.toml
@@ -26,6 +26,7 @@ itertools = "0.10.0"
 bigdecimal = "0.2.0"
 num-bigint = "0.3"
 tracing = "0.1.25"
+chrono = "0.4"
 
 [dev-dependencies]
 criterion = "0.3"
diff --git a/scylla/src/frame/response/cql_to_rust.rs b/scylla/src/frame/response/cql_to_rust.rs
index a4165814..64a6367d 100644
--- a/scylla/src/frame/response/cql_to_rust.rs
+++ b/scylla/src/frame/response/cql_to_rust.rs
@@ -1,6 +1,7 @@
 use super::result::{CQLValue, Row};
 use crate::frame::value::Counter;
 use bigdecimal::BigDecimal;
+use chrono::{Duration, NaiveDate};
 use num_bigint::BigInt;
 use std::collections::HashMap;
 use std::hash::Hash;
@@ -77,7 +78,7 @@ impl_from_cql_val!(Counter, as_counter); // Counter::from_cql<CQLValue>
 impl_from_cql_val!(i16, as_smallint); // i16::from_cql<CQLValue>
 impl_from_cql_val!(BigInt, into_varint); // BigInt::from_cql<CQLValue>
 impl_from_cql_val!(i8, as_tinyint); // i8::from_cql<CQLValue>
-impl_from_cql_val!(u32, as_date); // u32::from_cql<CQLValue>
+impl_from_cql_val!(NaiveDate, as_date); // NaiveDate::from_cql<CQLValue>
 impl_from_cql_val!(f32, as_float); // f32::from_cql<CQLValue>
 impl_from_cql_val!(f64, as_double); // f64::from_cql<CQLValue>
 impl_from_cql_val!(bool, as_boolean); // bool::from_cql<CQLValue>
@@ -85,6 +86,7 @@ impl_from_cql_val!(String, into_string); // String::from_cql<CQLValue>
 impl_from_cql_val!(IpAddr, as_inet); // IpAddr::from_cql<CQLValue>
 impl_from_cql_val!(Uuid, as_uuid); // Uuid::from_cql<CQLValue>
 impl_from_cql_val!(BigDecimal, into_decimal); // BigDecimal::from_cql<CQLValue>
+impl_from_cql_val!(Duration, as_duration); // Duration::from_cql<CQLValue>
 
 // Vec<T>::from_cql<CQLValue>
 impl<T: FromCQLVal<CQLValue>> FromCQLVal<CQLValue> for Vec<T> {
@@ -200,17 +202,12 @@ mod tests {
     use crate::frame::value::Counter;
     use crate::macros::FromRow;
     use bigdecimal::BigDecimal;
+    use chrono::{Duration, NaiveDate};
     use num_bigint::{BigInt, ToBigInt};
     use std::net::{IpAddr, Ipv4Addr};
     use std::str::FromStr;
     use uuid::Uuid;
 
-    #[test]
-    fn uuid_from_cql() {
-        let my_uuid = Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap();
-        assert_eq!(Ok(my_uuid), Uuid::from_cql(CQLValue::Uuid(my_uuid)));
-    }
-
     #[test]
     fn i32_from_cql() {
         assert_eq!(Ok(1234), i32::from_cql(CQLValue::Int(1234)));
@@ -287,6 +284,63 @@ mod tests {
         assert_eq!(Ok(counter), Counter::from_cql(CQLValue::Counter(counter)));
     }
 
+    #[test]
+    fn naive_date_from_cql() {
+        let unix_epoch: CQLValue = CQLValue::Date(2_u32.pow(31));
+        assert_eq!(
+            Ok(NaiveDate::from_ymd(1970, 1, 1)),
+            NaiveDate::from_cql(unix_epoch)
+        );
+
+        let before_epoch: CQLValue = CQLValue::Date(2_u32.pow(31) - 30);
+        assert_eq!(
+            Ok(NaiveDate::from_ymd(1969, 12, 2)),
+            NaiveDate::from_cql(before_epoch)
+        );
+
+        let after_epoch: CQLValue = CQLValue::Date(2_u32.pow(31) + 30);
+        assert_eq!(
+            Ok(NaiveDate::from_ymd(1970, 1, 31)),
+            NaiveDate::from_cql(after_epoch)
+        );
+
+        let min_date: CQLValue = CQLValue::Date(0);
+        assert!(NaiveDate::from_cql(min_date).is_err());
+
+        let max_date: CQLValue = CQLValue::Date(u32::max_value());
+        assert!(NaiveDate::from_cql(max_date).is_err());
+    }
+
+    #[test]
+    fn duration_from_cql() {
+        let time_duration = Duration::nanoseconds(86399999999999);
+        assert_eq!(
+            time_duration,
+            Duration::from_cql(CQLValue::Time(time_duration)).unwrap(),
+        );
+
+        let timestamp_duration = Duration::milliseconds(i64::min_value());
+        assert_eq!(
+            timestamp_duration,
+            Duration::from_cql(CQLValue::Timestamp(timestamp_duration)).unwrap(),
+        );
+    }
+
+    #[test]
+    fn uuid_from_cql() {
+        let test_uuid: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001").unwrap();
+
+        assert_eq!(
+            test_uuid,
+            Uuid::from_cql(CQLValue::Uuid(test_uuid)).unwrap()
+        );
+
+        assert_eq!(
+            test_uuid,
+            Uuid::from_cql(CQLValue::Timeuuid(test_uuid)).unwrap()
+        );
+    }
+
     #[test]
     fn vec_from_cql() {
         let cql_val = CQLValue::Set(vec![CQLValue::Int(1), CQLValue::Int(2), CQLValue::Int(3)]);
diff --git a/scylla/src/frame/response/result.rs b/scylla/src/frame/response/result.rs
index ca9535df..4e53fdc2 100644
--- a/scylla/src/frame/response/result.rs
+++ b/scylla/src/frame/response/result.rs
@@ -4,6 +4,8 @@ use crate::frame::{frame_errors::ParseError, types};
 use bigdecimal::BigDecimal;
 use byteorder::{BigEndian, ReadBytesExt};
 use bytes::{Buf, Bytes};
+use chrono::prelude::*;
+use chrono::Duration;
 use num_bigint::BigInt;
 use std::{
     collections::BTreeMap,
@@ -75,14 +77,17 @@ pub enum CQLValue {
     Boolean(bool),
     Blob(Vec<u8>),
     Counter(Counter),
-    Date(u32),
     Decimal(BigDecimal),
+    /// Days since -5877641-06-23 i.e. 2^31 days before unix epoch
+    /// Can be converted to chrono::NaiveDate (-262145-1-1 to 262143-12-31) using as_date
+    Date(u32),
     Double(f64),
     Float(f32),
     Int(i32),
     BigInt(i64),
     Text(String),
-    Timestamp(i64),
+    /// Milliseconds since unix epoch
+    Timestamp(Duration),
     Inet(IpAddr),
     List(Vec<CQLValue>),
     Map(Vec<(CQLValue, CQLValue)>),
@@ -94,7 +99,8 @@ pub enum CQLValue {
     },
     SmallInt(i16),
     TinyInt(i8),
-    Time(u64),
+    /// Nanoseconds since midnight
+    Time(Duration),
     Timeuuid(Uuid),
     Tuple(Vec<CQLValue>),
     Uuid(Uuid),
@@ -109,15 +115,23 @@ impl CQLValue {
         }
     }
 
-    pub fn as_timestamp(&self) -> Option<i64> {
-        match self {
-            Self::Timestamp(i) => Some(*i),
-            _ => None,
-        }
+    pub fn as_date(&self) -> Option<NaiveDate> {
+        // Days since -5877641-06-23 i.e. 2^31 days before unix epoch
+        let date_days: u32 = match self {
+            CQLValue::Date(days) => *days,
+            _ => return None,
+        };
+
+        // date_days is u32 then converted to i64
+        // then we substract 2^31 - this can't panic
+        let days_since_epoch = Duration::days(date_days.into()) - Duration::days(1 << 31);
+
+        NaiveDate::from_ymd(1970, 1, 1).checked_add_signed(days_since_epoch)
     }
 
-    pub fn as_time(&self) -> Option<u64> {
+    pub fn as_duration(&self) -> Option<Duration> {
         match self {
+            Self::Timestamp(i) => Some(*i),
             Self::Time(i) => Some(*i),
             _ => None,
         }
@@ -130,13 +144,6 @@ impl CQLValue {
         }
     }
 
-    pub fn as_date(&self) -> Option<u32> {
-        match self {
-            Self::Date(i) => Some(*i),
-            _ => None,
-        }
-    }
-
     pub fn as_boolean(&self) -> Option<bool> {
         match self {
             Self::Boolean(i) => Some(*i),
@@ -154,6 +161,7 @@ impl CQLValue {
     pub fn as_uuid(&self) -> Option<Uuid> {
         match self {
             Self::Uuid(u) => Some(*u),
+            Self::Timeuuid(u) => Some(*u),
             _ => None,
         }
     }
@@ -507,7 +515,9 @@ fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult<CQLValue, Par
                     buf.len()
                 )));
             }
-            CQLValue::Date(buf.read_u32::<BigEndian>()?)
+
+            let date_value = buf.read_u32::<BigEndian>()?;
+            CQLValue::Date(date_value)
         }
         Counter => {
             if buf.len() != 8 {
@@ -588,7 +598,9 @@ fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult<CQLValue, Par
                     buf.len()
                 )));
             }
-            CQLValue::Timestamp(buf.read_i64::<BigEndian>()?)
+            let millis = buf.read_i64::<BigEndian>()?;
+
+            CQLValue::Timestamp(Duration::milliseconds(millis))
         }
         Time => {
             if buf.len() != 8 {
@@ -597,7 +609,16 @@ fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult<CQLValue, Par
                     buf.len()
                 )));
             }
-            CQLValue::Time(buf.read_u64::<BigEndian>()?)
+            let nanoseconds: i64 = buf.read_i64::<BigEndian>()?;
+
+            // Valid values are in the range 0 to 86399999999999
+            if !(0..=86399999999999).contains(&nanoseconds) {
+                return Err(ParseError::BadData(format! {
+                    "Invalid time value only 0 to 86399999999999 allowed: {}.", nanoseconds
+                }));
+            }
+
+            CQLValue::Time(Duration::nanoseconds(nanoseconds))
         }
         Timeuuid => {
             if buf.len() != 16 {
@@ -779,6 +800,8 @@ mod tests {
     use crate as scylla;
     use crate::frame::value::Counter;
     use bigdecimal::BigDecimal;
+    use chrono::Duration;
+    use chrono::NaiveDate;
     use num_bigint::BigInt;
     use num_bigint::ToBigInt;
     use scylla::frame::response::result::{CQLValue, ColumnType};
@@ -1038,4 +1061,178 @@ mod tests {
         assert_eq!(CQLValue::Int(2), decoded[1].0);
         assert_eq!(CQLValue::Int(3), decoded[1].1);
     }
+
+    #[test]
+    fn date_deserialize() {
+        // Date is correctly parsed from a 4 byte array
+        let four_bytes: [u8; 4] = [12, 23, 34, 45];
+        let date: CQLValue =
+            super::deser_cql_value(&ColumnType::Date, &mut four_bytes.as_ref()).unwrap();
+        assert_eq!(date, CQLValue::Date(u32::from_be_bytes(four_bytes)));
+
+        // Date is parsed as u32 not i32, u32::max_value() is u32::max_value()
+        let date: CQLValue = super::deser_cql_value(
+            &ColumnType::Date,
+            &mut u32::max_value().to_be_bytes().as_ref(),
+        )
+        .unwrap();
+        assert_eq!(date, CQLValue::Date(u32::max_value()));
+
+        // Trying to parse a 0, 3 or 5 byte array fails
+        super::deser_cql_value(&ColumnType::Date, &mut [].as_ref()).unwrap_err();
+        super::deser_cql_value(&ColumnType::Date, &mut [1, 2, 3].as_ref()).unwrap_err();
+        super::deser_cql_value(&ColumnType::Date, &mut [1, 2, 3, 4, 5].as_ref()).unwrap_err();
+
+        // 2^31 when converted to NaiveDate is 1970-01-01
+        let unix_epoch: NaiveDate = NaiveDate::from_ymd(1970, 1, 1);
+        let date: CQLValue =
+            super::deser_cql_value(&ColumnType::Date, &mut 2_u32.pow(31).to_be_bytes().as_ref())
+                .unwrap();
+
+        assert_eq!(date.as_date().unwrap(), unix_epoch);
+
+        // 2^31 - 30 when converted to NaiveDate is 1969-12-02
+        let before_epoch: NaiveDate = NaiveDate::from_ymd(1969, 12, 2);
+        let date: CQLValue = super::deser_cql_value(
+            &ColumnType::Date,
+            &mut (2_u32.pow(31) - 30).to_be_bytes().as_ref(),
+        )
+        .unwrap();
+
+        assert_eq!(date.as_date().unwrap(), before_epoch);
+
+        // 2^31 + 30 when converted to NaiveDate is 1970-01-31
+        let after_epoch: NaiveDate = NaiveDate::from_ymd(1970, 1, 31);
+        let date: CQLValue = super::deser_cql_value(
+            &ColumnType::Date,
+            &mut (2_u32.pow(31) + 30).to_be_bytes().as_ref(),
+        )
+        .unwrap();
+
+        assert_eq!(date.as_date().unwrap(), after_epoch);
+
+        // 0 and u32::max_value() is out of NaiveDate range, fails with an error, not panics
+        assert!(
+            super::deser_cql_value(&ColumnType::Date, &mut 0_u32.to_be_bytes().as_ref())
+                .unwrap()
+                .as_date()
+                .is_none()
+        );
+
+        assert!(super::deser_cql_value(
+            &ColumnType::Date,
+            &mut u32::max_value().to_be_bytes().as_ref()
+        )
+        .unwrap()
+        .as_date()
+        .is_none());
+
+        // It's hard to test NaiveDate more because it involves calculating days between calendar dates
+        // There are more tests using database queries that should cover it
+    }
+
+    #[test]
+    fn test_time_deserialize() {
+        // Time is an i64 - nanoseconds since midnight
+        // in range 0..=86399999999999
+
+        let max_time: i64 = 24 * 60 * 60 * 1_000_000_000 - 1;
+        assert_eq!(max_time, 86399999999999);
+
+        // Check that basic values are deserialized correctly
+        for test_val in [0, 1, 18463, max_time].iter() {
+            let bytes: [u8; 8] = test_val.to_be_bytes();
+            let cql_value: CQLValue =
+                super::deser_cql_value(&ColumnType::Time, &mut &bytes[..]).unwrap();
+            assert_eq!(cql_value, CQLValue::Time(Duration::nanoseconds(*test_val)));
+        }
+
+        // Negative values cause an error
+        // Values bigger than 86399999999999 cause an error
+        for test_val in [-1, i64::min_value(), max_time + 1, i64::max_value()].iter() {
+            let bytes: [u8; 8] = test_val.to_be_bytes();
+            super::deser_cql_value(&ColumnType::Time, &mut &bytes[..]).unwrap_err();
+        }
+
+        // chrono::Duration has enough precision to represent nanoseconds accurately
+        assert_eq!(Duration::nanoseconds(1).num_nanoseconds().unwrap(), 1);
+        assert_eq!(
+            Duration::nanoseconds(7364737473).num_nanoseconds().unwrap(),
+            7364737473
+        );
+        assert_eq!(
+            Duration::nanoseconds(86399999999999)
+                .num_nanoseconds()
+                .unwrap(),
+            86399999999999
+        );
+    }
+
+    #[test]
+    fn test_timestamp_deserialize() {
+        // Timestamp is an i64 - milliseconds since unix epoch
+
+        // Check that test values are deserialized correctly
+        for test_val in &[
+            0,
+            -1,
+            1,
+            74568745,
+            -4584658,
+            i64::min_value(),
+            i64::max_value(),
+        ] {
+            let bytes: [u8; 8] = test_val.to_be_bytes();
+            let cql_value: CQLValue =
+                super::deser_cql_value(&ColumnType::Timestamp, &mut &bytes[..]).unwrap();
+            assert_eq!(
+                cql_value,
+                CQLValue::Timestamp(Duration::milliseconds(*test_val))
+            );
+
+            // Check that Duration converted back to i64 is correct
+            assert_eq!(
+                Duration::milliseconds(*test_val).num_milliseconds(),
+                *test_val
+            );
+        }
+    }
+
+    #[test]
+    fn test_timeuuid_deserialize() {
+        // A few random timeuuids generated manually
+        let tests = [
+            (
+                "8e14e760-7fa8-11eb-bc66-000000000001",
+                [
+                    0x8e, 0x14, 0xe7, 0x60, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+                ],
+            ),
+            (
+                "9b349580-7fa8-11eb-bc66-000000000001",
+                [
+                    0x9b, 0x34, 0x95, 0x80, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+                ],
+            ),
+            (
+                "5d74bae0-7fa3-11eb-bc66-000000000001",
+                [
+                    0x5d, 0x74, 0xba, 0xe0, 0x7f, 0xa3, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+                ],
+            ),
+        ];
+
+        for (uuid_str, uuid_bytes) in &tests {
+            let cql_val: CQLValue =
+                super::deser_cql_value(&ColumnType::Timeuuid, &mut &uuid_bytes[..]).unwrap();
+
+            match cql_val {
+                CQLValue::Timeuuid(uuid) => {
+                    assert_eq!(uuid.as_bytes(), uuid_bytes);
+                    assert_eq!(Uuid::parse_str(uuid_str).unwrap(), uuid);
+                }
+                _ => panic!("Timeuuid parsed as wrong CQLValue"),
+            }
+        }
+    }
 }
diff --git a/scylla/src/frame/value.rs b/scylla/src/frame/value.rs
index 0205e207..86521c34 100644
--- a/scylla/src/frame/value.rs
+++ b/scylla/src/frame/value.rs
@@ -1,5 +1,7 @@
 use bigdecimal::BigDecimal;
 use bytes::BufMut;
+use chrono::prelude::*;
+use chrono::Duration;
 use num_bigint::BigInt;
 use std::borrow::Cow;
 use std::collections::HashMap;
@@ -30,6 +32,21 @@ pub enum MaybeUnset<V: Value> {
     Set(V),
 }
 
+/// Wrapper that allows to send dates outside of NaiveDate range (-262145-1-1 to 262143-12-31)
+/// Days since -5877641-06-23 i.e. 2^31 days before unix epoch
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct Date(pub u32);
+
+/// Wrapper used to differentiate between Time and Timestamp as sending values
+/// Milliseconds since unix epoch
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct Timestamp(pub Duration);
+
+/// Wrapper used to differentiate between Time and Timestamp as sending values
+/// Nanoseconds since midnight
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct Time(pub Duration);
+
 /// Keeps a buffer with serialized Values
 /// Allows adding new Values and iterating over serialized ones
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -218,6 +235,47 @@ impl Value for BigDecimal {
     }
 }
 
+impl Value for NaiveDate {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        buf.put_i32(4);
+        let unix_epoch = NaiveDate::from_ymd(1970, 1, 1);
+
+        let days: u32 = self
+            .signed_duration_since(unix_epoch)
+            .num_days()
+            .checked_add(1 << 31)
+            .and_then(|days| days.try_into().ok()) // convert to u32
+            .ok_or(ValueTooBig)?;
+
+        buf.put_u32(days);
+        Ok(())
+    }
+}
+
+impl Value for Date {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        buf.put_i32(4);
+        buf.put_u32(self.0);
+        Ok(())
+    }
+}
+
+impl Value for Timestamp {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        buf.put_i32(8);
+        buf.put_i64(self.0.num_milliseconds());
+        Ok(())
+    }
+}
+
+impl Value for Time {
+    fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
+        buf.put_i32(8);
+        buf.put_i64(self.0.num_nanoseconds().ok_or(ValueTooBig)?);
+        Ok(())
+    }
+}
+
 impl Value for bool {
     fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
         buf.put_i32(1);


Test patch:
diff --git a/scylla/src/frame/cql_types_test.rs b/scylla/src/frame/cql_types_test.rs
index 603b116d..7e8f301f 100644
--- a/scylla/src/frame/cql_types_test.rs
+++ b/scylla/src/frame/cql_types_test.rs
@@ -2,15 +2,18 @@ use crate::cql_to_rust::FromCQLVal;
 use crate::frame::response::result::CQLValue;
 use crate::frame::value::Counter;
 use crate::frame::value::Value;
+use crate::frame::value::{Date, Time, Timestamp};
 use crate::transport::session::IntoTypedRows;
 use crate::transport::session::Session;
 use crate::SessionBuilder;
 use bigdecimal::BigDecimal;
+use chrono::{Duration, NaiveDate};
 use num_bigint::BigInt;
 use std::cmp::PartialEq;
 use std::env;
 use std::fmt::Debug;
 use std::str::FromStr;
+use uuid::Uuid;
 
 // Used to prepare a table for test
 // Creates keyspace ks
@@ -184,3 +187,387 @@ async fn test_counter() {
         assert_eq!(read_values, vec![expected_value]);
     }
 }
+
+#[tokio::test]
+async fn test_naive_date() {
+    let session: Session = init_test("naive_date", "date").await;
+
+    let min_naive_date: NaiveDate = chrono::naive::MIN_DATE;
+    assert_eq!(min_naive_date, NaiveDate::from_ymd(-262144, 1, 1));
+
+    let max_naive_date: NaiveDate = chrono::naive::MAX_DATE;
+    assert_eq!(max_naive_date, NaiveDate::from_ymd(262143, 12, 31));
+
+    let tests = [
+        // Basic test values
+        ("0000-1-1", Some(NaiveDate::from_ymd(0000, 1, 1))),
+        ("1970-01-01", Some(NaiveDate::from_ymd(1970, 1, 1))),
+        ("2020-03-07", Some(NaiveDate::from_ymd(2020, 3, 7))),
+        ("1337-4-5", Some(NaiveDate::from_ymd(1337, 4, 5))),
+        ("-1-12-31", Some(NaiveDate::from_ymd(-1, 12, 31))),
+        // min/max values allowed by NaiveDate
+        ("-262144-1-1", Some(min_naive_date)),
+        ("262143-12-31", Some(max_naive_date)),
+        // 1 less/more than min/max values allowed by NaiveDate
+        ("-262145-12-31", None),
+        ("262144-1-1", None),
+        // min/max values allowed by the database
+        ("-5877641-06-23", None),
+        ("5881580-07-11", None),
+    ];
+
+    for (date_text, date) in tests.iter() {
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.naive_date (id, val) VALUES (0, '{}')",
+                    date_text
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let read_date: Option<NaiveDate> = session
+            .query("SELECT val from ks.naive_date", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(NaiveDate,)>()
+            .next()
+            .unwrap()
+            .ok()
+            .map(|row| row.0);
+
+        assert_eq!(read_date, *date);
+
+        // If date is representable by NaiveDate try inserting it and reading again
+        if let Some(naive_date) = date {
+            session
+                .query(
+                    "INSERT INTO ks.naive_date (id, val) VALUES (0, ?)",
+                    (naive_date,),
+                )
+                .await
+                .unwrap();
+
+            let (read_date,): (NaiveDate,) = session
+                .query("SELECT val from ks.naive_date", &[])
+                .await
+                .unwrap()
+                .unwrap()
+                .into_typed::<(NaiveDate,)>()
+                .next()
+                .unwrap()
+                .unwrap();
+            assert_eq!(read_date, *naive_date);
+        }
+    }
+
+    // 1 less/more than min/max values allowed by the database should cause error
+    session
+        .query(
+            "INSERT INTO ks.naive_date (id, val) VALUES (0, '-5877641-06-22')",
+            &[],
+        )
+        .await
+        .unwrap_err();
+
+    session
+        .query(
+            "INSERT INTO ks.naive_date (id, val) VALUES (0, '5881580-07-12')",
+            &[],
+        )
+        .await
+        .unwrap_err();
+}
+
+#[tokio::test]
+async fn test_date() {
+    // Tests value::Date which allows to insert dates outside NaiveDate range
+
+    let session: Session = init_test("date_tests", "date").await;
+
+    let tests = [
+        ("1970-01-01", Date(2_u32.pow(31))),
+        ("1969-12-02", Date(2_u32.pow(31) - 30)),
+        ("1970-01-31", Date(2_u32.pow(31) + 30)),
+        ("-5877641-06-23", Date(0)),
+        ("5881580-07-11", Date(u32::max_value())),
+    ];
+
+    for (date_text, date) in &tests {
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.date_tests (id, val) VALUES (0, '{}')",
+                    date_text
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let read_date: Date = session
+            .query("SELECT val from ks.date_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()[0]
+            .columns[0]
+            .as_ref()
+            .map(|cql_val| match cql_val {
+                CQLValue::Date(days) => Date(*days),
+                _ => panic!(),
+            })
+            .unwrap();
+
+        assert_eq!(read_date, *date);
+    }
+}
+
+#[tokio::test]
+async fn test_time() {
+    // Time is an i64 - nanoseconds since midnight
+    // in range 0..=86399999999999
+
+    let session: Session = init_test("time_tests", "time").await;
+
+    let max_time: i64 = 24 * 60 * 60 * 1_000_000_000 - 1;
+    assert_eq!(max_time, 86399999999999);
+
+    let tests = [
+        ("00:00:00", Duration::nanoseconds(0)),
+        ("01:01:01", Duration::seconds(60 * 60 + 60 + 1)),
+        ("00:00:00.000000000", Duration::nanoseconds(0)),
+        ("00:00:00.000000001", Duration::nanoseconds(1)),
+        ("23:59:59.999999999", Duration::nanoseconds(max_time)),
+    ];
+
+    for (time_str, time_duration) in &tests {
+        // Insert time as a string and verify that it matches
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.time_tests (id, val) VALUES (0, '{}')",
+                    time_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let (read_time,): (Duration,) = session
+            .query("SELECT val from ks.time_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Duration,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_time, *time_duration);
+
+        // Insert time as a bound Time value and verify that it matches
+        session
+            .query(
+                "INSERT INTO ks.time_tests (id, val) VALUES (0, ?)",
+                (Time(*time_duration),),
+            )
+            .await
+            .unwrap();
+
+        let (read_time,): (Duration,) = session
+            .query("SELECT val from ks.time_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Duration,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_time, *time_duration);
+    }
+
+    // Tests with invalid time values
+    // Make sure that database rejects them
+    let invalid_tests = [
+        "-01:00:00",
+        // "-00:00:01", - actually this gets parsed as 0h 0m 1s, looks like a harmless bug
+        //"0", - this is invalid in scylla but valid in cassandra
+        //"86399999999999",
+        "24:00:00.000000000",
+        "00:00:00.0000000001",
+        "23:59:59.9999999999",
+    ];
+
+    for time_str in &invalid_tests {
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.time_tests (id, val) VALUES (0, '{}')",
+                    time_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap_err();
+    }
+}
+
+#[tokio::test]
+async fn test_timestamp() {
+    let session: Session = init_test("timestamp_tests", "timestamp").await;
+
+    let epoch_date = NaiveDate::from_ymd(1970, 1, 1);
+
+    //let before_epoch = NaiveDate::from_ymd(1333, 4, 30);
+    //let before_epoch_offset = before_epoch.signed_duration_since(epoch_date);
+
+    let after_epoch = NaiveDate::from_ymd(2020, 3, 8);
+    let after_epoch_offset = after_epoch.signed_duration_since(epoch_date);
+
+    let tests = [
+        ("0", Duration::milliseconds(0)),
+        (
+            "9223372036854775807",
+            Duration::milliseconds(i64::max_value()),
+        ),
+        (
+            "-9223372036854775808",
+            Duration::milliseconds(i64::min_value()),
+        ),
+        ("1970-01-01", Duration::milliseconds(0)),
+        ("2020-03-08", after_epoch_offset),
+        // Scylla rejects timestamps before 1970-01-01, but the specification says it shouldn't
+        // https://github.com/apache/cassandra/blob/78b13cd0e7a33d45c2081bb135e860bbaca7cbe5/doc/native_protocol_v4.spec#L929
+        // Scylla bug?
+        // ("1333-04-30", before_epoch_offset),
+        // Example taken from https://cassandra.apache.org/doc/latest/cql/types.html
+        // Doesn't work 0_o - Scylla's fault?
+        //("2011-02-03T04:05:00.000+0000", Duration::milliseconds(1299038700000)),
+    ];
+
+    for (timestamp_str, timestamp_duration) in &tests {
+        // Insert timestamp as a string and verify that it matches
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.timestamp_tests (id, val) VALUES (0, '{}')",
+                    timestamp_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let (read_timestamp,): (Duration,) = session
+            .query("SELECT val from ks.timestamp_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Duration,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_timestamp, *timestamp_duration);
+
+        // Insert timestamp as a bound Timestamp value and verify that it matches
+        session
+            .query(
+                "INSERT INTO ks.timestamp_tests (id, val) VALUES (0, ?)",
+                (Timestamp(*timestamp_duration),),
+            )
+            .await
+            .unwrap();
+
+        let (read_timestamp,): (Duration,) = session
+            .query("SELECT val from ks.timestamp_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Duration,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_timestamp, *timestamp_duration);
+    }
+}
+
+#[tokio::test]
+async fn test_timeuuid() {
+    let session: Session = init_test("timeuuid_tests", "timeuuid").await;
+
+    // A few random timeuuids generated manually
+    let tests = [
+        (
+            "8e14e760-7fa8-11eb-bc66-000000000001",
+            [
+                0x8e, 0x14, 0xe7, 0x60, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+            ],
+        ),
+        (
+            "9b349580-7fa8-11eb-bc66-000000000001",
+            [
+                0x9b, 0x34, 0x95, 0x80, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+            ],
+        ),
+        (
+            "5d74bae0-7fa3-11eb-bc66-000000000001",
+            [
+                0x5d, 0x74, 0xba, 0xe0, 0x7f, 0xa3, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+            ],
+        ),
+    ];
+
+    for (timeuuid_str, timeuuid_bytes) in &tests {
+        // Insert timeuuid as a string and verify that it matches
+        session
+            .query(
+                format!(
+                    "INSERT INTO ks.timeuuid_tests (id, val) VALUES (0, {})",
+                    timeuuid_str
+                ),
+                &[],
+            )
+            .await
+            .unwrap();
+
+        let (read_timeuuid,): (Uuid,) = session
+            .query("SELECT val from ks.timeuuid_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Uuid,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_timeuuid.as_bytes(), timeuuid_bytes);
+
+        // Insert timeuuid as a bound value and verify that it matches
+        let test_uuid: Uuid = Uuid::from_slice(timeuuid_bytes.as_ref()).unwrap();
+        session
+            .query(
+                "INSERT INTO ks.timeuuid_tests (id, val) VALUES (0, ?)",
+                (test_uuid,),
+            )
+            .await
+            .unwrap();
+
+        let (read_timeuuid,): (Uuid,) = session
+            .query("SELECT val from ks.timeuuid_tests", &[])
+            .await
+            .unwrap()
+            .unwrap()
+            .into_typed::<(Uuid,)>()
+            .next()
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(read_timeuuid.as_bytes(), timeuuid_bytes);
+    }
+}
diff --git a/scylla/src/frame/value_tests.rs b/scylla/src/frame/value_tests.rs
index 76d3671e..c3120ea8 100644
--- a/scylla/src/frame/value_tests.rs
+++ b/scylla/src/frame/value_tests.rs
@@ -1,10 +1,13 @@
 use super::value::{
-    BatchValues, MaybeUnset, SerializeValuesError, SerializedResult, SerializedValues, Unset,
-    Value, ValueList, ValueTooBig,
+    BatchValues, Date, MaybeUnset, SerializeValuesError, SerializedResult, SerializedValues, Time,
+    Timestamp, Unset, Value, ValueList, ValueTooBig,
 };
 use bytes::BufMut;
+use chrono::Duration;
+use chrono::NaiveDate;
 use std::borrow::Cow;
 use std::convert::TryInto;
+use uuid::Uuid;
 
 fn serialized(val: impl Value) -> Vec<u8> {
     let mut result: Vec<u8> = Vec::new();
@@ -26,6 +29,112 @@ fn basic_serialization() {
     assert_eq!(serialized("abc".to_string()), vec![0, 0, 0, 3, 97, 98, 99]);
 }
 
+#[test]
+fn naive_date_serialization() {
+    // 1970-01-31 is 2^31
+    let unix_epoch: NaiveDate = NaiveDate::from_ymd(1970, 1, 1);
+    assert_eq!(serialized(unix_epoch), vec![0, 0, 0, 4, 128, 0, 0, 0]);
+    assert_eq!(2_u32.pow(31).to_be_bytes(), [128, 0, 0, 0]);
+
+    // 1969-12-02 is 2^31 - 30
+    let before_epoch: NaiveDate = NaiveDate::from_ymd(1969, 12, 2);
+    assert_eq!(
+        serialized(before_epoch),
+        vec![0, 0, 0, 4, 127, 255, 255, 226]
+    );
+    assert_eq!((2_u32.pow(31) - 30).to_be_bytes(), [127, 255, 255, 226]);
+
+    // 1970-01-31 is 2^31 + 30
+    let after_epoch: NaiveDate = NaiveDate::from_ymd(1970, 1, 31);
+    assert_eq!(serialized(after_epoch), vec![0, 0, 0, 4, 128, 0, 0, 30]);
+    assert_eq!((2_u32.pow(31) + 30).to_be_bytes(), [128, 0, 0, 30]);
+}
+
+#[test]
+fn date_serialization() {
+    assert_eq!(serialized(Date(0)), vec![0, 0, 0, 4, 0, 0, 0, 0]);
+    assert_eq!(
+        serialized(Date(u32::max_value())),
+        vec![0, 0, 0, 4, 255, 255, 255, 255]
+    );
+}
+
+#[test]
+fn time_serialization() {
+    // Time is an i64 - nanoseconds since midnight
+    // in range 0..=86399999999999
+
+    let max_time: i64 = 24 * 60 * 60 * 1_000_000_000 - 1;
+    assert_eq!(max_time, 86399999999999);
+
+    // Check that basic values are serialized correctly
+    // Invalid values are also serialized correctly - database will respond with an error
+    for test_val in [0, 1, 15, 18463, max_time, -1, -324234, max_time + 16].iter() {
+        let test_time: Time = Time(Duration::nanoseconds(*test_val));
+        let bytes: Vec<u8> = serialized(test_time);
+
+        let mut expected_bytes: Vec<u8> = 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);
+    }
+
+    // Durations so long that nanoseconds don't fit in i64 cause an error
+    let long_time = Time(Duration::milliseconds(i64::max_value()));
+    assert_eq!(long_time.serialize(&mut Vec::new()), Err(ValueTooBig));
+}
+
+#[test]
+fn timestamp_serialization() {
+    // Timestamp is milliseconds since unix epoch represented as i64
+
+    for test_val in &[
+        0,
+        -1,
+        1,
+        -45345346,
+        453451,
+        i64::min_value(),
+        i64::max_value(),
+    ] {
+        let test_timestamp: Timestamp = Timestamp(Duration::milliseconds(*test_val));
+        let bytes: Vec<u8> = serialized(test_timestamp);
+
+        let mut expected_bytes: Vec<u8> = 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
+    let tests = [
+        [
+            0x8e, 0x14, 0xe7, 0x60, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+        ],
+        [
+            0x9b, 0x34, 0x95, 0x80, 0x7f, 0xa8, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+        ],
+        [
+            0x5d, 0x74, 0xba, 0xe0, 0x7f, 0xa3, 0x11, 0xeb, 0xbc, 0x66, 0, 0, 0, 0, 0, 0x01,
+        ],
+    ];
+
+    for uuid_bytes in &tests {
+        let uuid = Uuid::from_slice(uuid_bytes.as_ref()).unwrap();
+        let uuid_serialized: Vec<u8> = serialized(uuid);
+
+        let mut expected_serialized: Vec<u8> = vec![0, 0, 0, 16];
+        expected_serialized.extend_from_slice(uuid_bytes.as_ref());
+
+        assert_eq!(uuid_serialized, expected_serialized);
+    }
+}
+
 #[test]
 fn option_value() {
     assert_eq!(serialized(Some(32_i32)), vec![0, 0, 0, 4, 0, 0, 0, 32]);