Skip to main content

clickhouse_native_client/column/
uuid.rs

1//! UUID column implementation.
2//!
3//! UUIDs are stored as 128-bit values (two `u64` fields) in the ClickHouse
4//! native wire format. Supports parsing and formatting in the standard
5//! `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` format.
6
7use super::{
8    Column,
9    ColumnRef,
10};
11use crate::{
12    types::Type,
13    Error,
14    Result,
15};
16use bytes::BytesMut;
17use std::sync::Arc;
18
19/// UUID value stored as 128 bits (two `u64` halves).
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Uuid {
22    /// Upper 64 bits of the UUID.
23    pub high: u64,
24    /// Lower 64 bits of the UUID.
25    pub low: u64,
26}
27
28impl Uuid {
29    /// Create a UUID from its high and low 64-bit halves.
30    pub fn new(high: u64, low: u64) -> Self {
31        Self { high, low }
32    }
33
34    /// Parse UUID from string format `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the string is not exactly 32 hex digits (with or
39    /// without dashes) or contains invalid hex characters.
40    pub fn parse(s: &str) -> Result<Self> {
41        let s = s.replace("-", "");
42        if s.len() != 32 {
43            return Err(Error::Protocol(format!(
44                "Invalid UUID format: {}",
45                s
46            )));
47        }
48
49        let high = u64::from_str_radix(&s[0..16], 16).map_err(|e| {
50            Error::Protocol(format!("Invalid UUID hex: {}", e))
51        })?;
52        let low = u64::from_str_radix(&s[16..32], 16).map_err(|e| {
53            Error::Protocol(format!("Invalid UUID hex: {}", e))
54        })?;
55
56        Ok(Self { high, low })
57    }
58
59    /// Format UUID as string: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
60    pub fn as_string(&self) -> String {
61        format!(
62            "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
63            (self.high >> 32) as u32,
64            ((self.high >> 16) & 0xFFFF) as u16,
65            (self.high & 0xFFFF) as u16,
66            (self.low >> 48) as u16,
67            self.low & 0xFFFFFFFFFFFF,
68        )
69    }
70}
71
72impl std::fmt::Display for Uuid {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(f, "{}", self.as_string())
75    }
76}
77
78/// Column for UUID type (stored as two `u64` values per row).
79pub struct ColumnUuid {
80    type_: Type,
81    data: Vec<Uuid>,
82}
83
84impl ColumnUuid {
85    /// Create a new empty UUID column.
86    pub fn new(type_: Type) -> Self {
87        Self { type_, data: Vec::new() }
88    }
89
90    /// Set the column data from a vector of [`Uuid`] values.
91    pub fn with_data(mut self, data: Vec<Uuid>) -> Self {
92        self.data = data;
93        self
94    }
95
96    /// Append a [`Uuid`] value to this column.
97    pub fn append(&mut self, value: Uuid) {
98        self.data.push(value);
99    }
100
101    /// Append a UUID parsed from a string.
102    pub fn append_from_string(&mut self, s: &str) -> Result<()> {
103        let uuid = Uuid::parse(s)?;
104        self.data.push(uuid);
105        Ok(())
106    }
107
108    /// Get the UUID value at the given index.
109    pub fn at(&self, index: usize) -> Uuid {
110        self.data[index]
111    }
112
113    /// Format the UUID at the given index as a string.
114    pub fn as_string(&self, index: usize) -> String {
115        self.data[index].as_string()
116    }
117
118    /// Returns the number of values in this column.
119    pub fn len(&self) -> usize {
120        self.data.len()
121    }
122
123    /// Returns `true` if the column contains no values.
124    pub fn is_empty(&self) -> bool {
125        self.data.is_empty()
126    }
127}
128
129impl Column for ColumnUuid {
130    fn column_type(&self) -> &Type {
131        &self.type_
132    }
133
134    fn size(&self) -> usize {
135        self.data.len()
136    }
137
138    fn clear(&mut self) {
139        self.data.clear();
140    }
141
142    fn reserve(&mut self, new_cap: usize) {
143        self.data.reserve(new_cap);
144    }
145
146    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
147        let other =
148            other.as_any().downcast_ref::<ColumnUuid>().ok_or_else(|| {
149                Error::TypeMismatch {
150                    expected: self.type_.name(),
151                    actual: other.column_type().name(),
152                }
153            })?;
154
155        self.data.extend_from_slice(&other.data);
156        Ok(())
157    }
158
159    fn load_from_buffer(
160        &mut self,
161        buffer: &mut &[u8],
162        rows: usize,
163    ) -> Result<()> {
164        let bytes_needed = rows * 16;
165        if buffer.len() < bytes_needed {
166            return Err(Error::Protocol(format!(
167                "Buffer underflow: need {} bytes for UUID, have {}",
168                bytes_needed,
169                buffer.len()
170            )));
171        }
172
173        // Use bulk copy for performance
174        // Uuid is repr(Rust) but contains two u64 fields in order,
175        // which matches the wire format
176        self.data.reserve(rows);
177        let current_len = self.data.len();
178        unsafe {
179            // Set length first to claim ownership of the memory
180            self.data.set_len(current_len + rows);
181            let dest_ptr =
182                (self.data.as_mut_ptr() as *mut u8).add(current_len * 16);
183            std::ptr::copy_nonoverlapping(
184                buffer.as_ptr(),
185                dest_ptr,
186                bytes_needed,
187            );
188        }
189
190        use bytes::Buf;
191        buffer.advance(bytes_needed);
192        Ok(())
193    }
194
195    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
196        if !self.data.is_empty() {
197            let byte_slice = unsafe {
198                std::slice::from_raw_parts(
199                    self.data.as_ptr() as *const u8,
200                    self.data.len() * 16,
201                )
202            };
203            buffer.extend_from_slice(byte_slice);
204        }
205        Ok(())
206    }
207
208    fn clone_empty(&self) -> ColumnRef {
209        Arc::new(ColumnUuid::new(self.type_.clone()))
210    }
211
212    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
213        if begin + len > self.data.len() {
214            return Err(Error::InvalidArgument(format!(
215                "Slice out of bounds: begin={}, len={}, size={}",
216                begin,
217                len,
218                self.data.len()
219            )));
220        }
221
222        let sliced_data = self.data[begin..begin + len].to_vec();
223        Ok(Arc::new(
224            ColumnUuid::new(self.type_.clone()).with_data(sliced_data),
225        ))
226    }
227
228    fn as_any(&self) -> &dyn std::any::Any {
229        self
230    }
231
232    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
233        self
234    }
235}
236
237#[cfg(test)]
238#[cfg_attr(coverage_nightly, coverage(off))]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_uuid_parse() {
244        let uuid =
245            Uuid::parse("550e8400-e29b-41d4-a716-446655440000").unwrap();
246        assert_eq!(uuid.high, 0x550e8400e29b41d4);
247        assert_eq!(uuid.low, 0xa716446655440000);
248    }
249
250    #[test]
251    fn test_uuid_to_string() {
252        let uuid = Uuid::new(0x550e8400e29b41d4, 0xa716446655440000);
253        assert_eq!(uuid.as_string(), "550e8400-e29b-41d4-a716-446655440000");
254        // Also test Display trait
255        assert_eq!(
256            format!("{}", uuid),
257            "550e8400-e29b-41d4-a716-446655440000"
258        );
259    }
260
261    #[test]
262    fn test_uuid_column_append() {
263        let mut col = ColumnUuid::new(Type::uuid());
264        col.append(Uuid::new(0x123456789abcdef0, 0xfedcba9876543210));
265        col.append(Uuid::new(0, 0));
266
267        assert_eq!(col.len(), 2);
268        assert_eq!(
269            col.at(0),
270            Uuid::new(0x123456789abcdef0, 0xfedcba9876543210)
271        );
272        assert_eq!(col.at(1), Uuid::new(0, 0));
273    }
274
275    #[test]
276    fn test_uuid_column_from_string() {
277        let mut col = ColumnUuid::new(Type::uuid());
278        col.append_from_string("550e8400-e29b-41d4-a716-446655440000")
279            .unwrap();
280
281        assert_eq!(col.len(), 1);
282        assert_eq!(col.as_string(0), "550e8400-e29b-41d4-a716-446655440000");
283    }
284}