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// }