clickhouse-native-client 0.1.0

Async ClickHouse client using the native TCP protocol with LZ4/ZSTD compression and TLS support
Documentation
//! UUID column implementation.
//!
//! UUIDs are stored as 128-bit values (two `u64` fields) in the ClickHouse
//! native wire format. Supports parsing and formatting in the standard
//! `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` format.

use super::{
    Column,
    ColumnRef,
};
use crate::{
    types::Type,
    Error,
    Result,
};
use bytes::BytesMut;
use std::sync::Arc;

/// UUID value stored as 128 bits (two `u64` halves).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Uuid {
    /// Upper 64 bits of the UUID.
    pub high: u64,
    /// Lower 64 bits of the UUID.
    pub low: u64,
}

impl Uuid {
    /// Create a UUID from its high and low 64-bit halves.
    pub fn new(high: u64, low: u64) -> Self {
        Self { high, low }
    }

    /// Parse UUID from string format `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`.
    ///
    /// # Errors
    ///
    /// Returns an error if the string is not exactly 32 hex digits (with or
    /// without dashes) or contains invalid hex characters.
    pub fn parse(s: &str) -> Result<Self> {
        let s = s.replace("-", "");
        if s.len() != 32 {
            return Err(Error::Protocol(format!(
                "Invalid UUID format: {}",
                s
            )));
        }

        let high = u64::from_str_radix(&s[0..16], 16).map_err(|e| {
            Error::Protocol(format!("Invalid UUID hex: {}", e))
        })?;
        let low = u64::from_str_radix(&s[16..32], 16).map_err(|e| {
            Error::Protocol(format!("Invalid UUID hex: {}", e))
        })?;

        Ok(Self { high, low })
    }

    /// Format UUID as string: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    pub fn as_string(&self) -> String {
        format!(
            "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
            (self.high >> 32) as u32,
            ((self.high >> 16) & 0xFFFF) as u16,
            (self.high & 0xFFFF) as u16,
            (self.low >> 48) as u16,
            self.low & 0xFFFFFFFFFFFF,
        )
    }
}

impl std::fmt::Display for Uuid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_string())
    }
}

/// Column for UUID type (stored as two `u64` values per row).
pub struct ColumnUuid {
    type_: Type,
    data: Vec<Uuid>,
}

impl ColumnUuid {
    /// Create a new empty UUID column.
    pub fn new(type_: Type) -> Self {
        Self { type_, data: Vec::new() }
    }

    /// Set the column data from a vector of [`Uuid`] values.
    pub fn with_data(mut self, data: Vec<Uuid>) -> Self {
        self.data = data;
        self
    }

    /// Append a [`Uuid`] value to this column.
    pub fn append(&mut self, value: Uuid) {
        self.data.push(value);
    }

    /// Append a UUID parsed from a string.
    pub fn append_from_string(&mut self, s: &str) -> Result<()> {
        let uuid = Uuid::parse(s)?;
        self.data.push(uuid);
        Ok(())
    }

    /// Get the UUID value at the given index.
    pub fn at(&self, index: usize) -> Uuid {
        self.data[index]
    }

    /// Format the UUID at the given index as a string.
    pub fn as_string(&self, index: usize) -> String {
        self.data[index].as_string()
    }

    /// Returns the number of values in this column.
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Returns `true` if the column contains no values.
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

impl Column for ColumnUuid {
    fn column_type(&self) -> &Type {
        &self.type_
    }

    fn size(&self) -> usize {
        self.data.len()
    }

    fn clear(&mut self) {
        self.data.clear();
    }

    fn reserve(&mut self, new_cap: usize) {
        self.data.reserve(new_cap);
    }

    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
        let other =
            other.as_any().downcast_ref::<ColumnUuid>().ok_or_else(|| {
                Error::TypeMismatch {
                    expected: self.type_.name(),
                    actual: other.column_type().name(),
                }
            })?;

        self.data.extend_from_slice(&other.data);
        Ok(())
    }

    fn load_from_buffer(
        &mut self,
        buffer: &mut &[u8],
        rows: usize,
    ) -> Result<()> {
        let bytes_needed = rows * 16;
        if buffer.len() < bytes_needed {
            return Err(Error::Protocol(format!(
                "Buffer underflow: need {} bytes for UUID, have {}",
                bytes_needed,
                buffer.len()
            )));
        }

        // Use bulk copy for performance
        // Uuid is repr(Rust) but contains two u64 fields in order,
        // which matches the wire format
        self.data.reserve(rows);
        let current_len = self.data.len();
        unsafe {
            // Set length first to claim ownership of the memory
            self.data.set_len(current_len + rows);
            let dest_ptr =
                (self.data.as_mut_ptr() as *mut u8).add(current_len * 16);
            std::ptr::copy_nonoverlapping(
                buffer.as_ptr(),
                dest_ptr,
                bytes_needed,
            );
        }

        use bytes::Buf;
        buffer.advance(bytes_needed);
        Ok(())
    }

    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
        if !self.data.is_empty() {
            let byte_slice = unsafe {
                std::slice::from_raw_parts(
                    self.data.as_ptr() as *const u8,
                    self.data.len() * 16,
                )
            };
            buffer.extend_from_slice(byte_slice);
        }
        Ok(())
    }

    fn clone_empty(&self) -> ColumnRef {
        Arc::new(ColumnUuid::new(self.type_.clone()))
    }

    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
        if begin + len > self.data.len() {
            return Err(Error::InvalidArgument(format!(
                "Slice out of bounds: begin={}, len={}, size={}",
                begin,
                len,
                self.data.len()
            )));
        }

        let sliced_data = self.data[begin..begin + len].to_vec();
        Ok(Arc::new(
            ColumnUuid::new(self.type_.clone()).with_data(sliced_data),
        ))
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use super::*;

    #[test]
    fn test_uuid_parse() {
        let uuid =
            Uuid::parse("550e8400-e29b-41d4-a716-446655440000").unwrap();
        assert_eq!(uuid.high, 0x550e8400e29b41d4);
        assert_eq!(uuid.low, 0xa716446655440000);
    }

    #[test]
    fn test_uuid_to_string() {
        let uuid = Uuid::new(0x550e8400e29b41d4, 0xa716446655440000);
        assert_eq!(uuid.as_string(), "550e8400-e29b-41d4-a716-446655440000");
        // Also test Display trait
        assert_eq!(
            format!("{}", uuid),
            "550e8400-e29b-41d4-a716-446655440000"
        );
    }

    #[test]
    fn test_uuid_column_append() {
        let mut col = ColumnUuid::new(Type::uuid());
        col.append(Uuid::new(0x123456789abcdef0, 0xfedcba9876543210));
        col.append(Uuid::new(0, 0));

        assert_eq!(col.len(), 2);
        assert_eq!(
            col.at(0),
            Uuid::new(0x123456789abcdef0, 0xfedcba9876543210)
        );
        assert_eq!(col.at(1), Uuid::new(0, 0));
    }

    #[test]
    fn test_uuid_column_from_string() {
        let mut col = ColumnUuid::new(Type::uuid());
        col.append_from_string("550e8400-e29b-41d4-a716-446655440000")
            .unwrap();

        assert_eq!(col.len(), 1);
        assert_eq!(col.as_string(0), "550e8400-e29b-41d4-a716-446655440000");
    }
}