From e0c35c15830d49ad194ed88cbff9f5e0ee99b786 Mon Sep 17 00:00:00 2001 From: muzarski Date: Thu, 21 Dec 2023 04:43:51 +0100 Subject: [PATCH 01/10] scylla-cql: introduce CqlTimeuuid type Introduced a `CqlTimeuuid` type which is a wrapper for v1 uuids. --- scylla-cql/src/frame/value.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 3c17c8629f..13c5efc3a5 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -51,6 +51,36 @@ pub enum MaybeUnset { Set(V), } +/// Represents timeuuid (uuid V1) value +#[derive(Debug, Clone, Copy)] +pub struct CqlTimeuuid(Uuid); + +impl std::str::FromStr for CqlTimeuuid { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result { + Ok(Self(Uuid::from_str(s)?)) + } +} + +impl AsRef for CqlTimeuuid { + fn as_ref(&self) -> &Uuid { + &self.0 + } +} + +impl From for Uuid { + fn from(value: CqlTimeuuid) -> Self { + value.0 + } +} + +impl From for CqlTimeuuid { + fn from(value: Uuid) -> Self { + Self(value) + } +} + /// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31). /// /// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch. From 71f9e7a9e7a962632ba6a79c051b51faaff93b46 Mon Sep 17 00:00:00 2001 From: muzarski Date: Thu, 21 Dec 2023 05:07:19 +0100 Subject: [PATCH 02/10] timeuuid: implement strong ordering on CqlTimeuuid By default, uuids are compared in lexicographical order. However, scylla (and cassandra) use different comparison semantics for timeuuids (v1 uuids). Timeuuids contain an encoded timestamp within 8 most-significant bytes. Scylla compares timeuuids in a following manner: - compare the timestamps retrieved from 8 msb - if timestamps are equal, perform a signed comparison of 8 lsb --- scylla-cql/src/frame/value.rs | 75 ++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 13c5efc3a5..9d9606198e 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -52,9 +52,53 @@ pub enum MaybeUnset { } /// Represents timeuuid (uuid V1) value -#[derive(Debug, Clone, Copy)] +/// +/// This type has custom comparison logic which follows Scylla/Cassandra semantics. +/// For details, see [`Ord` implementation](#impl-Ord-for-CqlTimeuuid). +#[derive(Debug, Clone, Copy, Eq)] pub struct CqlTimeuuid(Uuid); +/// [`Uuid`] delegate methods +impl CqlTimeuuid { + pub fn as_bytes(&self) -> &[u8; 16] { + self.0.as_bytes() + } +} + +impl CqlTimeuuid { + /// Read 8 most significant bytes of timeuuid from serialized bytes + fn msb(&self) -> u64 { + // Scylla and Cassandra use a standard UUID memory layout for MSB: + // 4 bytes 2 bytes 2 bytes + // time_low - time_mid - time_hi_and_version + let bytes = self.0.as_bytes(); + ((bytes[6] & 0x0F) as u64) << 56 + | (bytes[7] as u64) << 48 + | (bytes[4] as u64) << 40 + | (bytes[5] as u64) << 32 + | (bytes[0] as u64) << 24 + | (bytes[1] as u64) << 16 + | (bytes[2] as u64) << 8 + | (bytes[3] as u64) + } + + fn lsb(&self) -> u64 { + let bytes = self.0.as_bytes(); + (bytes[8] as u64) << 56 + | (bytes[9] as u64) << 48 + | (bytes[10] as u64) << 40 + | (bytes[11] as u64) << 32 + | (bytes[12] as u64) << 24 + | (bytes[13] as u64) << 16 + | (bytes[14] as u64) << 8 + | (bytes[15] as u64) + } + + fn lsb_signed(&self) -> u64 { + self.lsb() ^ 0x8080808080808080 + } +} + impl std::str::FromStr for CqlTimeuuid { type Err = uuid::Error; @@ -81,6 +125,35 @@ impl From for CqlTimeuuid { } } +/// Compare two values of timeuuid type. +/// +/// Cassandra legacy requires: +/// - converting 8 most significant bytes to date, which is then compared. +/// - masking off UUID version from the 8 ms-bytes during compare, to +/// treat possible non-version-1 UUID the same way as UUID. +/// - using signed compare for least significant bits. +impl Ord for CqlTimeuuid { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let mut res = self.msb().cmp(&other.msb()); + if let std::cmp::Ordering::Equal = res { + res = self.lsb_signed().cmp(&other.lsb_signed()); + } + res + } +} + +impl PartialOrd for CqlTimeuuid { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for CqlTimeuuid { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == std::cmp::Ordering::Equal + } +} + /// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31). /// /// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch. From 8d81a6b5d70fbc5bc27cf03d109c06040c7138e7 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 19 Jan 2024 06:14:57 +0100 Subject: [PATCH 03/10] timeuuid: implement Hash for CqlTimeuuid --- scylla-cql/src/frame/value.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 9d9606198e..dc4bc888b7 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -154,6 +154,13 @@ impl PartialEq for CqlTimeuuid { } } +impl std::hash::Hash for CqlTimeuuid { + fn hash(&self, state: &mut H) { + self.lsb_signed().hash(state); + self.msb().hash(state); + } +} + /// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31). /// /// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch. From abc8d283fb0dbecee228a8e7d631729a6a917dd1 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 19 Jan 2024 06:19:59 +0100 Subject: [PATCH 04/10] tests: add test for timeuuid ordering properties --- scylla-cql/src/frame/value_tests.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scylla-cql/src/frame/value_tests.rs b/scylla-cql/src/frame/value_tests.rs index cb6a94ee49..e168e8c4fb 100644 --- a/scylla-cql/src/frame/value_tests.rs +++ b/scylla-cql/src/frame/value_tests.rs @@ -1,3 +1,4 @@ +use crate::frame::value::CqlTimeuuid; use crate::frame::{response::result::CqlValue, types::RawValue, value::LegacyBatchValuesIterator}; use crate::types::serialize::batch::{BatchValues, BatchValuesIterator, LegacyBatchValuesAdapter}; use crate::types::serialize::row::{RowSerializationContext, SerializeRow}; @@ -12,9 +13,11 @@ use super::value::{ use bigdecimal::BigDecimal; use bytes::BufMut; use num_bigint::BigInt; +use std::collections::hash_map::DefaultHasher; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::hash::{BuildHasherDefault, Hasher}; +use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; use std::{borrow::Cow, convert::TryInto}; use uuid::Uuid; @@ -538,6 +541,24 @@ fn timeuuid_serialization() { } } +#[test] +fn timeuuid_ordering_properties() { + let x = CqlTimeuuid::from_str("00000000-0000-1000-8080-808080808080").unwrap(); + let y = CqlTimeuuid::from_str("00000000-0000-2000-8080-808080808080").unwrap(); + + let cmp_res = x.cmp(&y); + assert_eq!(std::cmp::Ordering::Equal, cmp_res); + + assert_eq!(x, y); + + let compute_hash = |x: &CqlTimeuuid| { + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + }; + assert_eq!(compute_hash(&x), compute_hash(&y)); +} + #[test] fn cqlduration_serialization() { let duration = CqlDuration { From 9e2ac1fcdab48465275dfd2997cfe40b5cd4df57 Mon Sep 17 00:00:00 2001 From: muzarski Date: Thu, 21 Dec 2023 05:55:02 +0100 Subject: [PATCH 05/10] timeuuid: add missing delegate methods --- scylla-cql/src/frame/value.rs | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index dc4bc888b7..4397986b58 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -63,6 +63,54 @@ impl CqlTimeuuid { pub fn as_bytes(&self) -> &[u8; 16] { self.0.as_bytes() } + + pub fn as_u128(&self) -> u128 { + self.0.as_u128() + } + + pub fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) { + self.0.as_fields() + } + + pub fn as_u64_pair(&self) -> (u64, u64) { + self.0.as_u64_pair() + } + + pub fn from_slice(b: &[u8]) -> Result { + Ok(Self(Uuid::from_slice(b)?)) + } + + pub fn from_slice_le(b: &[u8]) -> Result { + Ok(Self(Uuid::from_slice_le(b)?)) + } + + pub fn from_bytes(bytes: [u8; 16]) -> Self { + Self(Uuid::from_bytes(bytes)) + } + + pub fn from_bytes_le(bytes: [u8; 16]) -> Self { + Self(Uuid::from_bytes_le(bytes)) + } + + pub fn from_fields(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> Self { + Self(Uuid::from_fields(d1, d2, d3, d4)) + } + + pub fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> Self { + Self(Uuid::from_fields_le(d1, d2, d3, d4)) + } + + pub fn from_u128(v: u128) -> Self { + Self(Uuid::from_u128(v)) + } + + pub fn from_u128_le(v: u128) -> Self { + Self(Uuid::from_u128_le(v)) + } + + pub fn from_u64_pair(high_bits: u64, low_bits: u64) -> Self { + Self(Uuid::from_u64_pair(high_bits, low_bits)) + } } impl CqlTimeuuid { From 6b870a387ada4fece08a53acd07a4b49cc0c0890 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 22 Dec 2023 07:03:28 +0100 Subject: [PATCH 06/10] timeuuid: implement fmt::Display for CqlTimeuuid --- scylla-cql/src/frame/value.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 4397986b58..c8bb542678 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -155,6 +155,12 @@ impl std::str::FromStr for CqlTimeuuid { } } +impl std::fmt::Display for CqlTimeuuid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + impl AsRef for CqlTimeuuid { fn as_ref(&self) -> &Uuid { &self.0 From d11e8c312dade63b31845be069b0f51212834e93 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 22 Dec 2023 07:29:48 +0100 Subject: [PATCH 07/10] cql: wrap CqlTimeuuid in CqlValue::Timeuuid --- scylla-cql/src/frame/response/cql_to_rust.rs | 13 ++++++++----- scylla-cql/src/frame/response/result.rs | 17 +++++++++-------- scylla-cql/src/frame/value.rs | 6 ++++++ scylla-cql/src/types/serialize/value.rs | 8 +++++++- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/scylla-cql/src/frame/response/cql_to_rust.rs b/scylla-cql/src/frame/response/cql_to_rust.rs index 19fc72cbe2..fb9b5bf249 100644 --- a/scylla-cql/src/frame/response/cql_to_rust.rs +++ b/scylla-cql/src/frame/response/cql_to_rust.rs @@ -1,5 +1,5 @@ use super::result::{CqlValue, Row}; -use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp}; +use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid}; use bigdecimal::BigDecimal; use num_bigint::BigInt; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; @@ -134,6 +134,7 @@ impl_from_cql_value_from_method!(String, into_string); // String::from_cql, into_blob); // Vec::from_cql impl_from_cql_value_from_method!(IpAddr, as_inet); // IpAddr::from_cql impl_from_cql_value_from_method!(Uuid, as_uuid); // Uuid::from_cql +impl_from_cql_value_from_method!(CqlTimeuuid, as_timeuuid); // CqlTimeuuid::from_cql impl_from_cql_value_from_method!(BigDecimal, into_decimal); // BigDecimal::from_cql impl_from_cql_value_from_method!(CqlDuration, as_cql_duration); // CqlDuration::from_cql impl_from_cql_value_from_method!(CqlDate, as_cql_date); // CqlDate::from_cql @@ -390,7 +391,7 @@ impl_tuple_from_cql!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 mod tests { use super::{CqlValue, FromCqlVal, FromCqlValError, FromRow, FromRowError, Row}; use crate as scylla; - use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp}; + use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid}; use crate::macros::FromRow; use bigdecimal::BigDecimal; use num_bigint::{BigInt, ToBigInt}; @@ -753,7 +754,9 @@ mod tests { #[test] fn uuid_from_cql() { - let test_uuid: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001").unwrap(); + let uuid_str = "8e14e760-7fa8-11eb-bc66-000000000001"; + let test_uuid: Uuid = Uuid::parse_str(uuid_str).unwrap(); + let test_time_uuid = CqlTimeuuid::from_str(uuid_str).unwrap(); assert_eq!( test_uuid, @@ -761,8 +764,8 @@ mod tests { ); assert_eq!( - test_uuid, - Uuid::from_cql(CqlValue::Timeuuid(test_uuid)).unwrap() + test_time_uuid, + CqlTimeuuid::from_cql(CqlValue::Timeuuid(test_time_uuid)).unwrap() ); } diff --git a/scylla-cql/src/frame/response/result.rs b/scylla-cql/src/frame/response/result.rs index f516d6e510..b5c78a46cc 100644 --- a/scylla-cql/src/frame/response/result.rs +++ b/scylla-cql/src/frame/response/result.rs @@ -1,7 +1,7 @@ use crate::cql_to_rust::{FromRow, FromRowError}; use crate::frame::response::event::SchemaChangeEvent; use crate::frame::types::vint_decode; -use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp}; +use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid}; use crate::frame::{frame_errors::ParseError, types}; use bigdecimal::BigDecimal; use byteorder::{BigEndian, ReadBytesExt}; @@ -110,7 +110,7 @@ pub enum CqlValue { TinyInt(i8), /// Nanoseconds since midnight Time(CqlTime), - Timeuuid(Uuid), + Timeuuid(CqlTimeuuid), Tuple(Vec>), Uuid(Uuid), Varint(BigInt), @@ -231,7 +231,7 @@ impl CqlValue { pub fn as_uuid(&self) -> Option { match self { Self::Uuid(u) => Some(*u), - Self::Timeuuid(u) => Some(*u), + Self::Timeuuid(u) => Some(*u.as_ref()), _ => None, } } @@ -285,7 +285,7 @@ impl CqlValue { } } - pub fn as_timeuuid(&self) -> Option { + pub fn as_timeuuid(&self) -> Option { match self { Self::Timeuuid(u) => Some(*u), _ => None, @@ -770,7 +770,7 @@ pub fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult { let months = i32::try_from(vint_decode(buf)?)?; @@ -966,7 +966,7 @@ pub fn deserialize(buf: &mut &[u8]) -> StdResult { #[cfg(test)] mod tests { use crate as scylla; - use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp}; + use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid}; use bigdecimal::BigDecimal; use num_bigint::BigInt; use num_bigint::ToBigInt; @@ -993,9 +993,10 @@ mod tests { let uuid_serialize = super::deser_cql_value(&ColumnType::Uuid, uuid_slice).unwrap(); assert_eq!(uuid_serialize, CqlValue::Uuid(my_uuid)); + let my_timeuuid = CqlTimeuuid::from_str("00000000000000000000000000000001").unwrap(); let time_uuid_serialize = super::deser_cql_value(&ColumnType::Timeuuid, uuid_slice).unwrap(); - assert_eq!(time_uuid_serialize, CqlValue::Timeuuid(my_uuid)); + assert_eq!(time_uuid_serialize, CqlValue::Timeuuid(my_timeuuid)); let my_ip = "::1".parse().unwrap(); let ip_buf: Vec = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; @@ -1754,7 +1755,7 @@ mod tests { match cql_val { CqlValue::Timeuuid(uuid) => { assert_eq!(uuid.as_bytes(), uuid_bytes); - assert_eq!(Uuid::parse_str(uuid_str).unwrap(), uuid); + assert_eq!(CqlTimeuuid::from_str(uuid_str).unwrap(), uuid); } _ => panic!("Timeuuid parsed as wrong CqlValue"), } diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index c8bb542678..22a66f6732 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -860,6 +860,12 @@ impl Value for Uuid { } } +impl Value for CqlTimeuuid { + fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { + self.0.serialize(buf) + } +} + impl Value for BigInt { fn serialize(&self, buf: &mut Vec) -> Result<(), ValueTooBig> { let serialized = self.to_signed_bytes_be(); diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index 9e5a691be6..c057f4ba02 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -20,7 +20,7 @@ use secrecy::{ExposeSecret, Secret, Zeroize}; use crate::frame::response::result::{ColumnType, CqlValue}; use crate::frame::types::vint_encode; use crate::frame::value::{ - Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, MaybeUnset, Unset, Value, + Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, MaybeUnset, Unset, Value, }; #[cfg(feature = "chrono")] @@ -227,6 +227,12 @@ impl SerializeCql for Uuid { writer.set_value(me.as_bytes().as_ref()).unwrap() }); } +impl SerializeCql for CqlTimeuuid { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Timeuuid); + writer.set_value(me.as_bytes().as_ref()).unwrap() + }); +} impl SerializeCql for BigInt { impl_serialize_via_writer!(|me, typ, writer| { exact_type_check!(typ, Varint); From 5432ebae1d7303d4baa427da98d79f3e98612e88 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 22 Dec 2023 07:32:22 +0100 Subject: [PATCH 08/10] cql: remove native timeuuid <-> uuid::Uuid mapping Before this commit, users were able to map native timeuuid type to rust `uuid::Uuid` type and vice-versa. After this change, users are only allowed to use `value::CqlTimeuuid` type to work with scylla's native timeuuid type. Changes: - timeuuid test needed to be adjusted, so it maps columns to `CqlTimeuuid` instead of `uuid::Uuid` - TracingEvent struct needs to be adjusted as `event_id` column of `system_traces.events` table is of `timeuuid` type --- scylla-cql/src/frame/response/result.rs | 1 - scylla-cql/src/types/serialize/value.rs | 2 +- scylla/src/tracing.rs | 6 +++--- scylla/src/transport/cql_types_test.rs | 12 ++++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/scylla-cql/src/frame/response/result.rs b/scylla-cql/src/frame/response/result.rs index b5c78a46cc..0833cda62a 100644 --- a/scylla-cql/src/frame/response/result.rs +++ b/scylla-cql/src/frame/response/result.rs @@ -231,7 +231,6 @@ impl CqlValue { pub fn as_uuid(&self) -> Option { match self { Self::Uuid(u) => Some(*u), - Self::Timeuuid(u) => Some(*u.as_ref()), _ => None, } } diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index c057f4ba02..8abe509585 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -223,7 +223,7 @@ impl SerializeCql for f64 { } impl SerializeCql for Uuid { impl_serialize_via_writer!(|me, typ, writer| { - exact_type_check!(typ, Uuid, Timeuuid); + exact_type_check!(typ, Uuid); writer.set_value(me.as_bytes().as_ref()).unwrap() }); } diff --git a/scylla/src/tracing.rs b/scylla/src/tracing.rs index 7753e4bd91..53019e7865 100644 --- a/scylla/src/tracing.rs +++ b/scylla/src/tracing.rs @@ -1,7 +1,7 @@ use itertools::Itertools; +use scylla_cql::frame::value::CqlTimeuuid; use std::collections::HashMap; use std::net::IpAddr; -use uuid::Uuid; use crate::cql_to_rust::{FromRow, FromRowError}; use crate::frame::response::result::Row; @@ -26,7 +26,7 @@ pub struct TracingInfo { /// A single event happening during a traced query #[derive(Debug, Clone, PartialEq, Eq)] pub struct TracingEvent { - pub event_id: Uuid, + pub event_id: CqlTimeuuid, pub activity: Option, pub source: Option, pub source_elapsed: Option, @@ -85,7 +85,7 @@ impl FromRow for TracingInfo { impl FromRow for TracingEvent { fn from_row(row: Row) -> Result { let (event_id, activity, source, source_elapsed, thread) = <( - Uuid, + CqlTimeuuid, Option, Option, Option, diff --git a/scylla/src/transport/cql_types_test.rs b/scylla/src/transport/cql_types_test.rs index 1ab0997728..029cdb3876 100644 --- a/scylla/src/transport/cql_types_test.rs +++ b/scylla/src/transport/cql_types_test.rs @@ -9,13 +9,13 @@ use crate::transport::session::Session; use crate::utils::test_utils::unique_keyspace_name; use bigdecimal::BigDecimal; use num_bigint::BigInt; +use scylla_cql::frame::value::CqlTimeuuid; use scylla_cql::types::serialize::value::SerializeCql; use scylla_macros::SerializeCql; use std::cmp::PartialEq; use std::fmt::Debug; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::str::FromStr; -use uuid::Uuid; // Used to prepare a table for test // Creates a new keyspace @@ -1111,13 +1111,13 @@ async fn test_timeuuid() { .await .unwrap(); - let (read_timeuuid,): (Uuid,) = session + let (read_timeuuid,): (CqlTimeuuid,) = session .query("SELECT val from timeuuid_tests", &[]) .await .unwrap() .rows .unwrap() - .into_typed::<(Uuid,)>() + .into_typed::<(CqlTimeuuid,)>() .next() .unwrap() .unwrap(); @@ -1125,7 +1125,7 @@ async fn test_timeuuid() { 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(); + let test_uuid: CqlTimeuuid = CqlTimeuuid::from_slice(timeuuid_bytes.as_ref()).unwrap(); session .query( "INSERT INTO timeuuid_tests (id, val) VALUES (0, ?)", @@ -1134,13 +1134,13 @@ async fn test_timeuuid() { .await .unwrap(); - let (read_timeuuid,): (Uuid,) = session + let (read_timeuuid,): (CqlTimeuuid,) = session .query("SELECT val from timeuuid_tests", &[]) .await .unwrap() .rows .unwrap() - .into_typed::<(Uuid,)>() + .into_typed::<(CqlTimeuuid,)>() .next() .unwrap() .unwrap(); From 0db658355e97d73eb0c7585a723a6adde1ad0489 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 22 Dec 2023 07:40:20 +0100 Subject: [PATCH 09/10] tests: add timeuuid runtime ordering test --- scylla/src/transport/cql_types_test.rs | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/scylla/src/transport/cql_types_test.rs b/scylla/src/transport/cql_types_test.rs index 029cdb3876..d90a169268 100644 --- a/scylla/src/transport/cql_types_test.rs +++ b/scylla/src/transport/cql_types_test.rs @@ -8,6 +8,7 @@ use crate::transport::session::IntoTypedRows; use crate::transport::session::Session; use crate::utils::test_utils::unique_keyspace_name; use bigdecimal::BigDecimal; +use itertools::Itertools; use num_bigint::BigInt; use scylla_cql::frame::value::CqlTimeuuid; use scylla_cql::types::serialize::value::SerializeCql; @@ -1149,6 +1150,84 @@ async fn test_timeuuid() { } } +#[tokio::test] +async fn test_timeuuid_ordering() { + let session: Session = create_new_session_builder().build().await.unwrap(); + let ks = unique_keyspace_name(); + + session + .query( + format!( + "CREATE KEYSPACE IF NOT EXISTS {} WITH REPLICATION = \ + {{'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}}", + ks + ), + &[], + ) + .await + .unwrap(); + session.use_keyspace(ks, false).await.unwrap(); + + session + .query( + "CREATE TABLE tab (p int, t timeuuid, PRIMARY KEY (p, t))", + (), + ) + .await + .unwrap(); + + // Timeuuid values, sorted in the same order as Scylla/Cassandra sorts them. + let sorted_timeuuid_vals: Vec = vec![ + CqlTimeuuid::from_str("00000000-0000-1000-8080-808080808080").unwrap(), + CqlTimeuuid::from_str("00000000-0000-1000-ffff-ffffffffffff").unwrap(), + CqlTimeuuid::from_str("00000000-0000-1000-0000-000000000000").unwrap(), + CqlTimeuuid::from_str("fed35080-0efb-11ee-a1ca-00006490e9a4").unwrap(), + CqlTimeuuid::from_str("00000257-0efc-11ee-9547-00006490e9a6").unwrap(), + CqlTimeuuid::from_str("ffffffff-ffff-1fff-ffff-ffffffffffef").unwrap(), + CqlTimeuuid::from_str("ffffffff-ffff-1fff-ffff-ffffffffffff").unwrap(), + CqlTimeuuid::from_str("ffffffff-ffff-1fff-0000-000000000000").unwrap(), + CqlTimeuuid::from_str("ffffffff-ffff-1fff-7f7f-7f7f7f7f7f7f").unwrap(), + ]; + + // Generate all permutations. + let perms = Itertools::permutations(sorted_timeuuid_vals.iter(), sorted_timeuuid_vals.len()) + .collect::>(); + // Ensure that all of the permutations were generated. + assert_eq!(362880, perms.len()); + + // Verify that Scylla really sorts timeuuids as defined in sorted_timeuuid_vals + let prepared = session + .prepare("INSERT INTO tab (p, t) VALUES (0, ?)") + .await + .unwrap(); + for timeuuid_val in &perms[0] { + session.execute(&prepared, (timeuuid_val,)).await.unwrap(); + } + + let scylla_order_timeuuids: Vec = session + .query("SELECT t FROM tab WHERE p = 0", ()) + .await + .unwrap() + .rows_typed::<(CqlTimeuuid,)>() + .unwrap() + .map(|r| r.unwrap().0) + .collect(); + + assert_eq!(sorted_timeuuid_vals, scylla_order_timeuuids); + + for perm in perms { + // Test if rust timeuuid values are sorted in the same way as in Scylla + let mut rust_sorted_timeuuids: Vec = perm + .clone() + .into_iter() + .map(|x| x.to_owned()) + .collect::>(); + rust_sorted_timeuuids.sort(); + + assert_eq!(sorted_timeuuid_vals, rust_sorted_timeuuids); + } +} + #[tokio::test] async fn test_inet() { let session: Session = init_test("inet_tests", "inet").await; From 19582164ba3073caf957e236c810189c6c705b12 Mon Sep 17 00:00:00 2001 From: muzarski Date: Fri, 22 Dec 2023 07:58:10 +0100 Subject: [PATCH 10/10] docs: update uuid/timeuuid info --- docs/source/SUMMARY.md | 3 ++- docs/source/data-types/data-types.md | 4 +++- docs/source/data-types/timeuuid.md | 30 ++++++++++++++++++++++++++++ docs/source/data-types/uuid.md | 8 ++++---- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 docs/source/data-types/timeuuid.md diff --git a/docs/source/SUMMARY.md b/docs/source/SUMMARY.md index 051aca6062..63639364fa 100644 --- a/docs/source/SUMMARY.md +++ b/docs/source/SUMMARY.md @@ -39,7 +39,8 @@ - [Counter](data-types/counter.md) - [Blob](data-types/blob.md) - [Inet](data-types/inet.md) - - [Uuid, Timeuuid](data-types/uuid.md) + - [Uuid](data-types/uuid.md) + - [Timeuuid](data-types/timeuuid.md) - [Date](data-types/date.md) - [Time](data-types/time.md) - [Timestamp](data-types/timestamp.md) diff --git a/docs/source/data-types/data-types.md b/docs/source/data-types/data-types.md index 6fe7c70e07..7962e5680e 100644 --- a/docs/source/data-types/data-types.md +++ b/docs/source/data-types/data-types.md @@ -20,7 +20,8 @@ Database types and their Rust equivalents: * `Counter` <----> `value::Counter` * `Blob` <----> `Vec` * `Inet` <----> `std::net::IpAddr` -* `Uuid`, `Timeuuid` <----> `uuid::Uuid` +* `Uuid` <----> `uuid::Uuid` +* `Timeuuid` <----> `value::CqlTimeuuid` * `Date` <----> `value::CqlDate`, `chrono::NaiveDate`, `time::Date` * `Time` <----> `value::CqlTime`, `chrono::NaiveTime`, `time::Time` * `Timestamp` <----> `value::CqlTimestamp`, `chrono::DateTime`, `time::OffsetDateTime` @@ -45,6 +46,7 @@ Database types and their Rust equivalents: blob inet uuid + timeuuid date time timestamp diff --git a/docs/source/data-types/timeuuid.md b/docs/source/data-types/timeuuid.md new file mode 100644 index 0000000000..2e4679f9e8 --- /dev/null +++ b/docs/source/data-types/timeuuid.md @@ -0,0 +1,30 @@ +# Timeuuid + +`Timeuuid` is represented as `value::CqlTimeuuid`. +`value::CqlTimeuuid` is a wrapper for `uuid::Uuid` with custom ordering logic +which follows Scylla/Cassandra semantics. + +```rust +# extern crate scylla; +# use scylla::Session; +# use std::error::Error; +# use std::str::FromStr; +# async fn check_only_compiles(session: &Session) -> Result<(), Box> { +use scylla::IntoTypedRows; +use scylla::frame::value::CqlTimeuuid; + +// Insert some timeuuid into the table +let to_insert: CqlTimeuuid = CqlTimeuuid::from_str("8e14e760-7fa8-11eb-bc66-000000000001")?; +session + .query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,)) + .await?; + +// Read timeuuid from the table +if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows { + for row in rows.into_typed::<(CqlTimeuuid,)>() { + let (timeuuid_value,): (CqlTimeuuid,) = row?; + } +} +# Ok(()) +# } +``` \ No newline at end of file diff --git a/docs/source/data-types/uuid.md b/docs/source/data-types/uuid.md index c3cfde2725..84ab1c2d1a 100644 --- a/docs/source/data-types/uuid.md +++ b/docs/source/data-types/uuid.md @@ -1,6 +1,6 @@ -# Uuid, Timeuuid +# Uuid -`Uuid` and `Timeuuid` are represented as `uuid::Uuid` +`Uuid` is represented as `uuid::Uuid`. ```rust # extern crate scylla; @@ -11,13 +11,13 @@ use scylla::IntoTypedRows; use uuid::Uuid; -// Insert some uuid/timeuuid into the table +// Insert some uuid into the table let to_insert: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001")?; session .query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,)) .await?; -// Read uuid/timeuuid from the table +// Read uuid from the table if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows { for row in rows.into_typed::<(Uuid,)>() { let (uuid_value,): (Uuid,) = row?;