clickhouse_arrow/native/
convert.rs

1use std::borrow::Cow;
2
3use crate::{Error, Result, Type, Value};
4
5pub mod raw_row;
6pub mod std_deserialize;
7pub mod std_serialize;
8pub use raw_row::*;
9pub mod unit_value;
10
11/// Type alias for the definition of a column for schema creation
12pub type ColumnDefinition<T = Value> = (String, Type, Option<T>);
13
14/// A type that can be converted to a raw `ClickHouse` SQL value.
15pub trait ToSql {
16    /// # Errors
17    fn to_sql(self, type_hint: Option<&Type>) -> Result<Value>;
18}
19
20impl ToSql for Value {
21    fn to_sql(self, _type_hint_: Option<&Type>) -> Result<Value> { Ok(self) }
22}
23
24pub fn unexpected_type(type_: &Type) -> Error {
25    Error::DeserializeError(format!("unexpected type: {type_}"))
26}
27
28/// A type that can be converted from a raw `ClickHouse` SQL value.
29pub trait FromSql: Sized {
30    /// # Errors
31    fn from_sql(type_: &Type, value: Value) -> Result<Self>;
32}
33
34impl FromSql for Value {
35    fn from_sql(_type_: &Type, value: Value) -> Result<Self> { Ok(value) }
36}
37
38/// A row that can be deserialized and serialized from a raw `ClickHouse` SQL value.
39/// Generally this is not implemented manually, but using `clickhouse_arrow_derive::Row`,
40/// i.e. `#[derive(clickhouse_arrow::Row)]`.
41///
42/// # Example
43/// ```rust,ignore
44/// use clickhouse_arrow::Row;
45/// #[derive(Row)]
46/// struct MyRow {
47///     id: String,
48///     name: String,
49///     age: u8
50/// }
51/// ```
52pub trait Row: Sized {
53    /// If `Some`, `serialize_row` and `deserialize_row` MUST return this number of columns
54    const COLUMN_COUNT: Option<usize>;
55
56    /// If `Some`, `serialize_row` and `deserialize_row` MUST have these names
57    fn column_names() -> Option<Vec<Cow<'static, str>>>;
58
59    /// Infers the schema and returns it.
60    fn to_schema() -> Option<Vec<ColumnDefinition<Value>>>;
61
62    /// # Errors
63    fn deserialize_row(map: Vec<(&str, &Type, Value)>) -> Result<Self>;
64
65    /// # Errors
66    fn serialize_row(
67        self,
68        type_hints: &[(String, Type)],
69    ) -> Result<Vec<(Cow<'static, str>, Value)>>;
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_column_definition_alias() {
78        let col_def: ColumnDefinition = ("test".to_string(), Type::Int32, Some(Value::Int32(42)));
79        assert_eq!(col_def.0, "test");
80        assert_eq!(col_def.1, Type::Int32);
81        assert_eq!(col_def.2, Some(Value::Int32(42)));
82    }
83
84    #[test]
85    fn test_column_definition_generic() {
86        let col_def: ColumnDefinition<i32> = ("test".to_string(), Type::Int32, Some(42));
87        assert_eq!(col_def.0, "test");
88        assert_eq!(col_def.1, Type::Int32);
89        assert_eq!(col_def.2, Some(42));
90    }
91
92    #[test]
93    fn test_value_to_sql() {
94        let value = Value::Int32(42);
95        let result = value.to_sql(None).unwrap();
96        assert_eq!(result, Value::Int32(42));
97    }
98
99    #[test]
100    fn test_value_to_sql_with_hint() {
101        let value = Value::String("test".to_string().into_bytes());
102        let result = value.to_sql(Some(&Type::String)).unwrap();
103        assert_eq!(result, Value::String("test".to_string().into_bytes()));
104    }
105
106    #[test]
107    fn test_value_from_sql() {
108        let type_ = Type::Int32;
109        let value = Value::Int32(123);
110        let result = Value::from_sql(&type_, value).unwrap();
111        assert_eq!(result, Value::Int32(123));
112    }
113
114    #[test]
115    fn test_unexpected_type() {
116        let type_ = Type::Int32;
117        let error = unexpected_type(&type_);
118
119        match error {
120            Error::DeserializeError(msg) => {
121                assert!(msg.contains("unexpected type"));
122                assert!(msg.contains("Int32"));
123            }
124            _ => panic!("Expected DeserializeError"),
125        }
126    }
127
128    #[test]
129    fn test_unexpected_type_different_types() {
130        let types =
131            vec![Type::String, Type::Int64, Type::Float32, Type::Array(Box::new(Type::Int32))];
132
133        for type_ in types {
134            let error = unexpected_type(&type_);
135            assert!(matches!(error, Error::DeserializeError(_)));
136        }
137    }
138
139    // Test that the module exports work correctly
140    #[test]
141    fn test_module_exports() {
142        // Test that we can use exported types
143        let _raw_row = RawRow::default();
144
145        // Test that we can use the type alias
146        let _col_def: ColumnDefinition = ("test".to_string(), Type::Int32, None);
147    }
148}