clickhouse_client/orm/
mod.rs

1//! ORM
2
3mod query;
4
5#[cfg(test)]
6mod tests;
7
8use crate::{
9    error::Error,
10    query::QueryData,
11    schema::TableSchema,
12    value::{Type, Value},
13};
14
15pub use query::*;
16
17/// ORM prelude
18pub mod prelude {
19    pub use super::{ChRecord, Record};
20    pub use crate::{
21        error::Error,
22        schema::TableSchema,
23        value::{ChValue, Type, Value},
24    };
25    pub use clickhouse_client_macros::AsChRecord;
26}
27
28/// A DB record
29#[derive(Debug, Clone)]
30pub struct Record {
31    /// Table name
32    pub table: String,
33    /// Fields
34    pub fields: Vec<RecordField>,
35}
36
37/// A DB record field
38#[derive(Debug, Clone)]
39pub struct RecordField {
40    /// ID
41    pub id: String,
42    /// Is primary key
43    pub primary: bool,
44    /// Type
45    pub ty: Type,
46    /// Value
47    pub value: Value,
48}
49
50impl Record {
51    /// Creates an empty record
52    pub fn new(table: &str) -> Self {
53        Self {
54            table: table.to_string(),
55            fields: vec![],
56        }
57    }
58
59    /// Adds a field
60    pub fn add_field(&mut self, id: &str, primary: bool, ty: Type, value: Value) -> &mut Self {
61        self.fields.push(RecordField {
62            id: id.to_string(),
63            primary,
64            ty,
65            value,
66        });
67        self
68    }
69
70    /// Adds a field
71    pub fn field(mut self, id: &str, primary: bool, ty: Type, value: Value) -> Self {
72        self.fields.push(RecordField {
73            id: id.to_string(),
74            primary,
75            ty,
76            value,
77        });
78        self
79    }
80
81    /// Returns the [TableSchema]
82    pub fn schema(&self) -> TableSchema {
83        let mut table = TableSchema::new(self.table.as_str());
84        for field in &self.fields {
85            table.add_column(field.id.as_str(), field.ty.clone(), field.primary);
86        }
87        table
88    }
89
90    /// Returns all the fields
91    pub fn fields(&self) -> Vec<&RecordField> {
92        self.fields.iter().collect()
93    }
94
95    /// Returns the primary fields
96    pub fn primary_fields(&self) -> Vec<&RecordField> {
97        self.fields.iter().filter(|f| f.primary).collect::<Vec<_>>()
98    }
99
100    /// Returns a record field
101    pub fn get_field(&self, id: &str) -> Option<&RecordField> {
102        self.fields.iter().find(|f| f.id == id)
103    }
104
105    /// Removes a record field and returns ir
106    pub fn remove_field(&mut self, id: &str) -> Option<RecordField> {
107        let i = match self.fields.iter().position(|f| f.id == id) {
108            Some(i) => i,
109            None => return None,
110        };
111        Some(self.fields.remove(i))
112    }
113}
114
115/// A trait to convert from/to a Clickhouse [Record]
116pub trait ChRecord: Sized {
117    /// Returns the Clickhouse schema
118    fn ch_schema() -> TableSchema;
119
120    /// Converts to a [Record]
121    fn into_ch_record(self) -> Record;
122
123    /// Converts from a [Record]
124    fn from_ch_record(record: Record) -> Result<Self, Error>;
125
126    /// Converts records to a [QueryTable]
127    fn to_query_data(records: Vec<Self>) -> QueryData {
128        let schema = Self::ch_schema();
129        let mut table = QueryData::from_schema(&schema);
130        for record in records {
131            let record = record.into_ch_record();
132            let row = record
133                .fields
134                .into_iter()
135                .map(|f| f.value)
136                .collect::<Vec<_>>();
137            table.add_row(row);
138        }
139        table
140    }
141
142    /// Parses multiple records from a [QueryTable]
143    fn from_query_data(data: QueryData) -> Result<Vec<Self>, Error> {
144        let schema = Self::ch_schema();
145        let parts = data.into_parts();
146        let col_names = parts
147            .names
148            .ok_or(Error::new("Missing column names to parse table"))?
149            .into_iter()
150            .map(|n| n.to_string())
151            .collect::<Vec<_>>();
152
153        let mut records = vec![];
154        for row in parts.rows {
155            let mut record = Record::new(&schema.name);
156            for (i, value) in row.into_iter().enumerate() {
157                let id = col_names.get(i).ok_or(Error::new("Invalid column"))?;
158                let col_sch = schema.get_column_by_id(id).ok_or(Error::new(""))?;
159                let primary = col_sch.primary;
160                let ty = col_sch.ty.clone();
161                record.add_field(id, primary, ty, value);
162            }
163            let record = Self::from_ch_record(record)?;
164            records.push(record);
165        }
166
167        Ok(records)
168    }
169}
170
171// // -- TEST --
172
173// use prelude::*;
174// use time::Date;
175// use uuid::Uuid;
176
177// struct TestRecord {
178//     /// ID
179//     id: Uuid,
180//     /// Name
181//     name: String,
182//     /// Count
183//     count: u8,
184//     /// Date
185//     date: Date,
186//     /// Optional
187//     count_opt: Option<u8>,
188// }
189
190// impl ChRecord for TestRecord {
191//     fn ch_schema() -> TableSchema {
192//         TableSchema::new("test_orm")
193//             .column("id", <Uuid>::ch_type(), true)
194//             .column("name", <String>::ch_type(), false)
195//             .column("count", <u8>::ch_type(), false)
196//             .column("date", <Date>::ch_type(), false)
197//             .column("count_opt", <Option<u8>>::ch_type(), false)
198//     }
199
200//     fn into_ch_record(self) -> Record {
201//         Record::new("test_orm")
202//             .field("id", true, <Uuid>::ch_type(), self.id.into_ch_value())
203//             .field(
204//                 "name",
205//                 false,
206//                 <String>::ch_type(),
207//                 self.name.into_ch_value(),
208//             )
209//             .field("count", false, <u8>::ch_type(), self.count.into_ch_value())
210//             .field("date", false, <Date>::ch_type(), self.date.into_ch_value())
211//             .field(
212//                 "count_opt",
213//                 false,
214//                 <Option<u8>>::ch_type(),
215//                 self.count_opt.into_ch_value(),
216//             )
217//     }
218
219//     /// Parses from a Clickhouse record
220//     fn from_ch_record(mut record: Record) -> Result<Self, Error> {
221//         Ok(Self {
222//             id: match record.remove_field("id") {
223//                 Some(field) => <Uuid>::from_ch_value(field.value)?,
224//                 None => return Err(Error::new("Missing field 'id'")),
225//             },
226//             name: match record.remove_field("name") {
227//                 Some(field) => <String>::from_ch_value(field.value)?,
228//                 None => return Err(Error::new("Missing field 'name'")),
229//             },
230//             count: match record.remove_field("count") {
231//                 Some(field) => <u8>::from_ch_value(field.value)?,
232//                 None => return Err(Error::new("Missing field 'count'")),
233//             },
234//             date: match record.remove_field("date") {
235//                 Some(field) => <Date>::from_ch_value(field.value)?,
236//                 None => return Err(Error::new("Missing field 'date'")),
237//             },
238//             count_opt: match record.remove_field("count_opt") {
239//                 Some(field) => <Option<u8>>::from_ch_value(field.value)?,
240//                 None => return Err(Error::new("Missing field 'date'")),
241//             },
242//         })
243//     }
244// }