Skip to content

Commit 71f9e7a

Browse files
committed
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
1 parent e0c35c1 commit 71f9e7a

File tree

1 file changed

+74
-1
lines changed

1 file changed

+74
-1
lines changed

scylla-cql/src/frame/value.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,53 @@ pub enum MaybeUnset<V> {
5252
}
5353

5454
/// Represents timeuuid (uuid V1) value
55-
#[derive(Debug, Clone, Copy)]
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)]
5659
pub struct CqlTimeuuid(Uuid);
5760

61+
/// [`Uuid`] delegate methods
62+
impl CqlTimeuuid {
63+
pub fn as_bytes(&self) -> &[u8; 16] {
64+
self.0.as_bytes()
65+
}
66+
}
67+
68+
impl CqlTimeuuid {
69+
/// Read 8 most significant bytes of timeuuid from serialized bytes
70+
fn msb(&self) -> u64 {
71+
// Scylla and Cassandra use a standard UUID memory layout for MSB:
72+
// 4 bytes 2 bytes 2 bytes
73+
// time_low - time_mid - time_hi_and_version
74+
let bytes = self.0.as_bytes();
75+
((bytes[6] & 0x0F) as u64) << 56
76+
| (bytes[7] as u64) << 48
77+
| (bytes[4] as u64) << 40
78+
| (bytes[5] as u64) << 32
79+
| (bytes[0] as u64) << 24
80+
| (bytes[1] as u64) << 16
81+
| (bytes[2] as u64) << 8
82+
| (bytes[3] as u64)
83+
}
84+
85+
fn lsb(&self) -> u64 {
86+
let bytes = self.0.as_bytes();
87+
(bytes[8] as u64) << 56
88+
| (bytes[9] as u64) << 48
89+
| (bytes[10] as u64) << 40
90+
| (bytes[11] as u64) << 32
91+
| (bytes[12] as u64) << 24
92+
| (bytes[13] as u64) << 16
93+
| (bytes[14] as u64) << 8
94+
| (bytes[15] as u64)
95+
}
96+
97+
fn lsb_signed(&self) -> u64 {
98+
self.lsb() ^ 0x8080808080808080
99+
}
100+
}
101+
58102
impl std::str::FromStr for CqlTimeuuid {
59103
type Err = uuid::Error;
60104

@@ -81,6 +125,35 @@ impl From<Uuid> for CqlTimeuuid {
81125
}
82126
}
83127

128+
/// Compare two values of timeuuid type.
129+
///
130+
/// Cassandra legacy requires:
131+
/// - converting 8 most significant bytes to date, which is then compared.
132+
/// - masking off UUID version from the 8 ms-bytes during compare, to
133+
/// treat possible non-version-1 UUID the same way as UUID.
134+
/// - using signed compare for least significant bits.
135+
impl Ord for CqlTimeuuid {
136+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
137+
let mut res = self.msb().cmp(&other.msb());
138+
if let std::cmp::Ordering::Equal = res {
139+
res = self.lsb_signed().cmp(&other.lsb_signed());
140+
}
141+
res
142+
}
143+
}
144+
145+
impl PartialOrd for CqlTimeuuid {
146+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
147+
Some(self.cmp(other))
148+
}
149+
}
150+
151+
impl PartialEq for CqlTimeuuid {
152+
fn eq(&self, other: &Self) -> bool {
153+
self.cmp(other) == std::cmp::Ordering::Equal
154+
}
155+
}
156+
84157
/// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31).
85158
///
86159
/// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch.

0 commit comments

Comments
 (0)