Skip to content

Commit d01b473

Browse files
authored
Merge pull request #894 from muzarski/timeuuid
Introduce `CqlTimeuuid` type
2 parents 4feb6fb + 1958216 commit d01b473

File tree

11 files changed

+343
-31
lines changed

11 files changed

+343
-31
lines changed

docs/source/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
- [Counter](data-types/counter.md)
4040
- [Blob](data-types/blob.md)
4141
- [Inet](data-types/inet.md)
42-
- [Uuid, Timeuuid](data-types/uuid.md)
42+
- [Uuid](data-types/uuid.md)
43+
- [Timeuuid](data-types/timeuuid.md)
4344
- [Date](data-types/date.md)
4445
- [Time](data-types/time.md)
4546
- [Timestamp](data-types/timestamp.md)

docs/source/data-types/data-types.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ Database types and their Rust equivalents:
2020
* `Counter` <----> `value::Counter`
2121
* `Blob` <----> `Vec<u8>`
2222
* `Inet` <----> `std::net::IpAddr`
23-
* `Uuid`, `Timeuuid` <----> `uuid::Uuid`
23+
* `Uuid` <----> `uuid::Uuid`
24+
* `Timeuuid` <----> `value::CqlTimeuuid`
2425
* `Date` <----> `value::CqlDate`, `chrono::NaiveDate`, `time::Date`
2526
* `Time` <----> `value::CqlTime`, `chrono::NaiveTime`, `time::Time`
2627
* `Timestamp` <----> `value::CqlTimestamp`, `chrono::DateTime<Utc>`, `time::OffsetDateTime`
@@ -45,6 +46,7 @@ Database types and their Rust equivalents:
4546
blob
4647
inet
4748
uuid
49+
timeuuid
4850
date
4951
time
5052
timestamp

docs/source/data-types/timeuuid.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Timeuuid
2+
3+
`Timeuuid` is represented as `value::CqlTimeuuid`.
4+
`value::CqlTimeuuid` is a wrapper for `uuid::Uuid` with custom ordering logic
5+
which follows Scylla/Cassandra semantics.
6+
7+
```rust
8+
# extern crate scylla;
9+
# use scylla::Session;
10+
# use std::error::Error;
11+
# use std::str::FromStr;
12+
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
13+
use scylla::IntoTypedRows;
14+
use scylla::frame::value::CqlTimeuuid;
15+
16+
// Insert some timeuuid into the table
17+
let to_insert: CqlTimeuuid = CqlTimeuuid::from_str("8e14e760-7fa8-11eb-bc66-000000000001")?;
18+
session
19+
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
20+
.await?;
21+
22+
// Read timeuuid from the table
23+
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
24+
for row in rows.into_typed::<(CqlTimeuuid,)>() {
25+
let (timeuuid_value,): (CqlTimeuuid,) = row?;
26+
}
27+
}
28+
# Ok(())
29+
# }
30+
```

docs/source/data-types/uuid.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Uuid, Timeuuid
1+
# Uuid
22

3-
`Uuid` and `Timeuuid` are represented as `uuid::Uuid`
3+
`Uuid` is represented as `uuid::Uuid`.
44

55
```rust
66
# extern crate scylla;
@@ -11,13 +11,13 @@
1111
use scylla::IntoTypedRows;
1212
use uuid::Uuid;
1313

14-
// Insert some uuid/timeuuid into the table
14+
// Insert some uuid into the table
1515
let to_insert: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001")?;
1616
session
1717
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
1818
.await?;
1919

20-
// Read uuid/timeuuid from the table
20+
// Read uuid from the table
2121
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
2222
for row in rows.into_typed::<(Uuid,)>() {
2323
let (uuid_value,): (Uuid,) = row?;

scylla-cql/src/frame/response/cql_to_rust.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::result::{CqlValue, Row};
2-
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp};
2+
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
33
use bigdecimal::BigDecimal;
44
use num_bigint::BigInt;
55
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
@@ -134,6 +134,7 @@ impl_from_cql_value_from_method!(String, into_string); // String::from_cql<CqlVa
134134
impl_from_cql_value_from_method!(Vec<u8>, into_blob); // Vec<u8>::from_cql<CqlValue>
135135
impl_from_cql_value_from_method!(IpAddr, as_inet); // IpAddr::from_cql<CqlValue>
136136
impl_from_cql_value_from_method!(Uuid, as_uuid); // Uuid::from_cql<CqlValue>
137+
impl_from_cql_value_from_method!(CqlTimeuuid, as_timeuuid); // CqlTimeuuid::from_cql<CqlValue>
137138
impl_from_cql_value_from_method!(BigDecimal, into_decimal); // BigDecimal::from_cql<CqlValue>
138139
impl_from_cql_value_from_method!(CqlDuration, as_cql_duration); // CqlDuration::from_cql<CqlValue>
139140
impl_from_cql_value_from_method!(CqlDate, as_cql_date); // CqlDate::from_cql<CqlValue>
@@ -390,7 +391,7 @@ impl_tuple_from_cql!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14
390391
mod tests {
391392
use super::{CqlValue, FromCqlVal, FromCqlValError, FromRow, FromRowError, Row};
392393
use crate as scylla;
393-
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp};
394+
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
394395
use crate::macros::FromRow;
395396
use bigdecimal::BigDecimal;
396397
use num_bigint::{BigInt, ToBigInt};
@@ -753,16 +754,18 @@ mod tests {
753754

754755
#[test]
755756
fn uuid_from_cql() {
756-
let test_uuid: Uuid = Uuid::parse_str("8e14e760-7fa8-11eb-bc66-000000000001").unwrap();
757+
let uuid_str = "8e14e760-7fa8-11eb-bc66-000000000001";
758+
let test_uuid: Uuid = Uuid::parse_str(uuid_str).unwrap();
759+
let test_time_uuid = CqlTimeuuid::from_str(uuid_str).unwrap();
757760

758761
assert_eq!(
759762
test_uuid,
760763
Uuid::from_cql(CqlValue::Uuid(test_uuid)).unwrap()
761764
);
762765

763766
assert_eq!(
764-
test_uuid,
765-
Uuid::from_cql(CqlValue::Timeuuid(test_uuid)).unwrap()
767+
test_time_uuid,
768+
CqlTimeuuid::from_cql(CqlValue::Timeuuid(test_time_uuid)).unwrap()
766769
);
767770
}
768771

scylla-cql/src/frame/response/result.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::cql_to_rust::{FromRow, FromRowError};
22
use crate::frame::response::event::SchemaChangeEvent;
33
use crate::frame::types::vint_decode;
4-
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp};
4+
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
55
use crate::frame::{frame_errors::ParseError, types};
66
use bigdecimal::BigDecimal;
77
use byteorder::{BigEndian, ReadBytesExt};
@@ -110,7 +110,7 @@ pub enum CqlValue {
110110
TinyInt(i8),
111111
/// Nanoseconds since midnight
112112
Time(CqlTime),
113-
Timeuuid(Uuid),
113+
Timeuuid(CqlTimeuuid),
114114
Tuple(Vec<Option<CqlValue>>),
115115
Uuid(Uuid),
116116
Varint(BigInt),
@@ -231,7 +231,6 @@ impl CqlValue {
231231
pub fn as_uuid(&self) -> Option<Uuid> {
232232
match self {
233233
Self::Uuid(u) => Some(*u),
234-
Self::Timeuuid(u) => Some(*u),
235234
_ => None,
236235
}
237236
}
@@ -285,7 +284,7 @@ impl CqlValue {
285284
}
286285
}
287286

288-
pub fn as_timeuuid(&self) -> Option<Uuid> {
287+
pub fn as_timeuuid(&self) -> Option<CqlTimeuuid> {
289288
match self {
290289
Self::Timeuuid(u) => Some(*u),
291290
_ => None,
@@ -770,7 +769,7 @@ pub fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult<CqlValue,
770769
)));
771770
}
772771
let uuid = uuid::Uuid::from_slice(buf).expect("Deserializing Uuid failed.");
773-
CqlValue::Timeuuid(uuid)
772+
CqlValue::Timeuuid(CqlTimeuuid::from(uuid))
774773
}
775774
Duration => {
776775
let months = i32::try_from(vint_decode(buf)?)?;
@@ -966,7 +965,7 @@ pub fn deserialize(buf: &mut &[u8]) -> StdResult<Result, ParseError> {
966965
#[cfg(test)]
967966
mod tests {
968967
use crate as scylla;
969-
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp};
968+
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
970969
use bigdecimal::BigDecimal;
971970
use num_bigint::BigInt;
972971
use num_bigint::ToBigInt;
@@ -993,9 +992,10 @@ mod tests {
993992
let uuid_serialize = super::deser_cql_value(&ColumnType::Uuid, uuid_slice).unwrap();
994993
assert_eq!(uuid_serialize, CqlValue::Uuid(my_uuid));
995994

995+
let my_timeuuid = CqlTimeuuid::from_str("00000000000000000000000000000001").unwrap();
996996
let time_uuid_serialize =
997997
super::deser_cql_value(&ColumnType::Timeuuid, uuid_slice).unwrap();
998-
assert_eq!(time_uuid_serialize, CqlValue::Timeuuid(my_uuid));
998+
assert_eq!(time_uuid_serialize, CqlValue::Timeuuid(my_timeuuid));
999999

10001000
let my_ip = "::1".parse().unwrap();
10011001
let ip_buf: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
@@ -1754,7 +1754,7 @@ mod tests {
17541754
match cql_val {
17551755
CqlValue::Timeuuid(uuid) => {
17561756
assert_eq!(uuid.as_bytes(), uuid_bytes);
1757-
assert_eq!(Uuid::parse_str(uuid_str).unwrap(), uuid);
1757+
assert_eq!(CqlTimeuuid::from_str(uuid_str).unwrap(), uuid);
17581758
}
17591759
_ => panic!("Timeuuid parsed as wrong CqlValue"),
17601760
}

scylla-cql/src/frame/value.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,170 @@ pub enum MaybeUnset<V> {
5151
Set(V),
5252
}
5353

54+
/// Represents timeuuid (uuid V1) value
55+
///
56+
/// This type has custom comparison logic which follows Scylla/Cassandra semantics.
57+
/// For details, see [`Ord` implementation](#impl-Ord-for-CqlTimeuuid).
58+
#[derive(Debug, Clone, Copy, Eq)]
59+
pub struct CqlTimeuuid(Uuid);
60+
61+
/// [`Uuid`] delegate methods
62+
impl CqlTimeuuid {
63+
pub fn as_bytes(&self) -> &[u8; 16] {
64+
self.0.as_bytes()
65+
}
66+
67+
pub fn as_u128(&self) -> u128 {
68+
self.0.as_u128()
69+
}
70+
71+
pub fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) {
72+
self.0.as_fields()
73+
}
74+
75+
pub fn as_u64_pair(&self) -> (u64, u64) {
76+
self.0.as_u64_pair()
77+
}
78+
79+
pub fn from_slice(b: &[u8]) -> Result<Self, uuid::Error> {
80+
Ok(Self(Uuid::from_slice(b)?))
81+
}
82+
83+
pub fn from_slice_le(b: &[u8]) -> Result<Self, uuid::Error> {
84+
Ok(Self(Uuid::from_slice_le(b)?))
85+
}
86+
87+
pub fn from_bytes(bytes: [u8; 16]) -> Self {
88+
Self(Uuid::from_bytes(bytes))
89+
}
90+
91+
pub fn from_bytes_le(bytes: [u8; 16]) -> Self {
92+
Self(Uuid::from_bytes_le(bytes))
93+
}
94+
95+
pub fn from_fields(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> Self {
96+
Self(Uuid::from_fields(d1, d2, d3, d4))
97+
}
98+
99+
pub fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> Self {
100+
Self(Uuid::from_fields_le(d1, d2, d3, d4))
101+
}
102+
103+
pub fn from_u128(v: u128) -> Self {
104+
Self(Uuid::from_u128(v))
105+
}
106+
107+
pub fn from_u128_le(v: u128) -> Self {
108+
Self(Uuid::from_u128_le(v))
109+
}
110+
111+
pub fn from_u64_pair(high_bits: u64, low_bits: u64) -> Self {
112+
Self(Uuid::from_u64_pair(high_bits, low_bits))
113+
}
114+
}
115+
116+
impl CqlTimeuuid {
117+
/// Read 8 most significant bytes of timeuuid from serialized bytes
118+
fn msb(&self) -> u64 {
119+
// Scylla and Cassandra use a standard UUID memory layout for MSB:
120+
// 4 bytes 2 bytes 2 bytes
121+
// time_low - time_mid - time_hi_and_version
122+
let bytes = self.0.as_bytes();
123+
((bytes[6] & 0x0F) as u64) << 56
124+
| (bytes[7] as u64) << 48
125+
| (bytes[4] as u64) << 40
126+
| (bytes[5] as u64) << 32
127+
| (bytes[0] as u64) << 24
128+
| (bytes[1] as u64) << 16
129+
| (bytes[2] as u64) << 8
130+
| (bytes[3] as u64)
131+
}
132+
133+
fn lsb(&self) -> u64 {
134+
let bytes = self.0.as_bytes();
135+
(bytes[8] as u64) << 56
136+
| (bytes[9] as u64) << 48
137+
| (bytes[10] as u64) << 40
138+
| (bytes[11] as u64) << 32
139+
| (bytes[12] as u64) << 24
140+
| (bytes[13] as u64) << 16
141+
| (bytes[14] as u64) << 8
142+
| (bytes[15] as u64)
143+
}
144+
145+
fn lsb_signed(&self) -> u64 {
146+
self.lsb() ^ 0x8080808080808080
147+
}
148+
}
149+
150+
impl std::str::FromStr for CqlTimeuuid {
151+
type Err = uuid::Error;
152+
153+
fn from_str(s: &str) -> Result<Self, Self::Err> {
154+
Ok(Self(Uuid::from_str(s)?))
155+
}
156+
}
157+
158+
impl std::fmt::Display for CqlTimeuuid {
159+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160+
write!(f, "{}", self.0)
161+
}
162+
}
163+
164+
impl AsRef<Uuid> for CqlTimeuuid {
165+
fn as_ref(&self) -> &Uuid {
166+
&self.0
167+
}
168+
}
169+
170+
impl From<CqlTimeuuid> for Uuid {
171+
fn from(value: CqlTimeuuid) -> Self {
172+
value.0
173+
}
174+
}
175+
176+
impl From<Uuid> for CqlTimeuuid {
177+
fn from(value: Uuid) -> Self {
178+
Self(value)
179+
}
180+
}
181+
182+
/// Compare two values of timeuuid type.
183+
///
184+
/// Cassandra legacy requires:
185+
/// - converting 8 most significant bytes to date, which is then compared.
186+
/// - masking off UUID version from the 8 ms-bytes during compare, to
187+
/// treat possible non-version-1 UUID the same way as UUID.
188+
/// - using signed compare for least significant bits.
189+
impl Ord for CqlTimeuuid {
190+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
191+
let mut res = self.msb().cmp(&other.msb());
192+
if let std::cmp::Ordering::Equal = res {
193+
res = self.lsb_signed().cmp(&other.lsb_signed());
194+
}
195+
res
196+
}
197+
}
198+
199+
impl PartialOrd for CqlTimeuuid {
200+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
201+
Some(self.cmp(other))
202+
}
203+
}
204+
205+
impl PartialEq for CqlTimeuuid {
206+
fn eq(&self, other: &Self) -> bool {
207+
self.cmp(other) == std::cmp::Ordering::Equal
208+
}
209+
}
210+
211+
impl std::hash::Hash for CqlTimeuuid {
212+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
213+
self.lsb_signed().hash(state);
214+
self.msb().hash(state);
215+
}
216+
}
217+
54218
/// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31).
55219
///
56220
/// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch.
@@ -696,6 +860,12 @@ impl Value for Uuid {
696860
}
697861
}
698862

863+
impl Value for CqlTimeuuid {
864+
fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
865+
self.0.serialize(buf)
866+
}
867+
}
868+
699869
impl Value for BigInt {
700870
fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
701871
let serialized = self.to_signed_bytes_be();

0 commit comments

Comments
 (0)