tinybase/
query_builder.rs

1use core::any::Any;
2
3use crate::{
4    index::{AnyIndex, Index, IndexType},
5    result::DbResult,
6    table::{Table, TableType},
7    Record,
8};
9
10#[allow(unused_imports)]
11use alloc::{boxed::Box, format, vec, vec::Vec, string::{String, ToString}};
12
13/// A single query condition.
14pub enum QueryCondition<T>
15where
16    T: TableType + 'static,
17{
18    By(Box<dyn AnyIndex<T>>, Box<dyn Any>),
19    And(Box<QueryCondition<T>>, Box<QueryCondition<T>>),
20    Or(Box<QueryCondition<T>>, Box<QueryCondition<T>>),
21}
22
23/// For building and chaining query conditions.
24pub struct ConditionBuilder<T: TableType + 'static>(QueryCondition<T>);
25
26impl<T: TableType + 'static> ConditionBuilder<T> {
27    /// Creates a new query condition using the specified index and value.
28    ///
29    /// # Arguments
30    ///
31    /// * `index` - The index to use for the query.
32    /// * `value` - The value to search for in the index.
33    pub fn by<I: IndexType + 'static>(index: &Index<T, I>, value: I) -> Self {
34        Self(QueryCondition::By(Box::new(index.clone()), Box::new(value)))
35    }
36
37    /// Creates a new query condition representing the logical AND of two existing conditions.
38    ///
39    /// # Arguments
40    ///
41    /// * `left` - The left-hand side condition.
42    /// * `right` - The right-hand side condition.
43    pub fn and(left: Self, right: Self) -> Self {
44        Self(QueryCondition::And(Box::new(left.0), Box::new(right.0)))
45    }
46
47    /// Creates a new query condition representing the logical OR of two existing conditions.
48    ///
49    /// # Arguments
50    ///
51    /// * `left` - The left-hand side condition.
52    /// * `right` - The right-hand side condition.
53    pub fn or(left: Self, right: Self) -> Self {
54        Self(QueryCondition::Or(Box::new(left.0), Box::new(right.0)))
55    }
56
57    /// Builds the final query condition.
58    ///
59    /// # Returns
60    ///
61    /// A new [`QueryCondition`].
62    pub fn build(self) -> QueryCondition<T> {
63        self.0
64    }
65}
66
67impl<T: TableType + 'static> Into<QueryCondition<T>> for ConditionBuilder<T> {
68    fn into(self) -> QueryCondition<T> {
69        self.build()
70    }
71}
72
73/// Builder for building and executing queries.
74pub struct QueryBuilder<T>
75where
76    T: TableType + 'static,
77{
78    table: Table<T>,
79    condition: Option<QueryCondition<T>>,
80}
81
82impl<T> QueryBuilder<T>
83where
84    T: TableType,
85{
86    /// Creates a new query builder for the given table.
87    ///
88    /// # Arguments
89    ///
90    /// * `table` - The table to build the query for.
91    pub fn new(table: &Table<T>) -> Self {
92        Self {
93            table: table.clone(),
94            condition: None,
95        }
96    }
97
98    /// Adds a query condition to the query builder.
99    /// This will overwrite the previous condition (if set).
100    ///
101    /// # Arguments
102    ///
103    /// * `condition` - The condition to add to the query builder.
104    pub fn with_condition<C: Into<QueryCondition<T>>>(mut self, condition: C) -> Self {
105        self.condition = Some(condition.into());
106        self
107    }
108
109    /// Validates the query builder's state.
110    fn check_valid(&self) -> DbResult<()> {
111        match &self.condition {
112            Some(_) => Ok(()),
113            None => Err(crate::result::TinyBaseError::QueryBuilder(
114                "No search condition provided".into(),
115            )),
116        }
117    }
118
119    /// Executes the query and returns the selected records.
120    ///
121    /// # Returns
122    ///
123    /// All selected [`Record`] instances.
124    pub fn select(self) -> DbResult<Vec<Record<T>>> {
125        self.check_valid()?;
126        Self::select_recursive(self.condition.unwrap())
127    }
128
129    /// Updates the records in the table based on the query condition and new value.
130    ///
131    /// # Arguments
132    ///
133    /// * `updater` - Closure to generate the new data based on the old data.
134    ///
135    /// # Returns
136    ///
137    /// All updated [`Record`] instances.
138    pub fn update(self, updater: fn(T) -> T) -> DbResult<Vec<Record<T>>> {
139        self.check_valid()?;
140        let ids: Vec<u64> = Self::select_recursive(self.condition.unwrap())?
141            .iter()
142            .map(|record| record.id)
143            .collect();
144
145        self.table.update(&ids, updater)
146    }
147
148    /// Deletes the records from the table based on the query condition.
149    ///
150    /// # Returns
151    ///
152    /// All deleted [`Record`] instances.
153    pub fn delete(self) -> DbResult<Vec<Record<T>>> {
154        self.check_valid()?;
155        let selected = Self::select_recursive(self.condition.unwrap())?;
156
157        let mut removed = vec![];
158
159        for record in &selected {
160            if let Some(record) = self.table.delete(record.id)? {
161                removed.push(record);
162            }
163        }
164
165        Ok(removed)
166    }
167
168    /// Recursively processes the query conditions and returns the selected records.
169    fn select_recursive(condition: QueryCondition<T>) -> DbResult<Vec<Record<T>>> {
170        match condition {
171            QueryCondition::By(index, value) => index.search(value),
172            QueryCondition::And(left, right) => {
173                let left_records = Self::select_recursive(*left)?;
174                let right_records = Self::select_recursive(*right)?;
175
176                let mut intersection: Vec<Record<T>> = left_records.clone();
177                intersection.retain(|record| {
178                    right_records
179                        .iter()
180                        .any(|other_record| record.id == other_record.id)
181                });
182
183                Ok(intersection)
184            }
185            QueryCondition::Or(left, right) => {
186                let mut records: Vec<Record<T>> =
187                    Self::select_recursive(*left)?.into_iter().collect();
188                records.extend(Self::select_recursive(*right)?.into_iter());
189
190                let mut seen = Vec::new();
191                records.retain(|item| {
192                    if seen.contains(&item.id) {
193                        false
194                    } else {
195                        seen.push(item.id);
196                        true
197                    }
198                });
199
200                Ok(records)
201            }
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use alloc::borrow::ToOwned;
209
210    use super::*;
211    use crate::TinyBase;
212
213    #[test]
214    fn query_builder_select_and() {
215        let db = TinyBase::new(None, true);
216        let table: Table<String> = db.open_table("test_table").unwrap();
217
218        // Create an index for the table
219        let index = table
220            .create_index("name", |value| value.to_owned())
221            .unwrap();
222
223        let length = table.create_index("length", |value| value.len()).unwrap();
224
225        // Insert string values into the table
226        let value1 = table.insert("value1".to_string()).unwrap();
227        table.insert("value2".to_string()).unwrap();
228
229        let result_1 = QueryBuilder::new(&table)
230            .with_condition(ConditionBuilder::and(
231                ConditionBuilder::by(&index, "value1".to_string()),
232                ConditionBuilder::by(&index, "value2".to_string()),
233            ))
234            .select()
235            .expect("Select failed");
236
237        assert_eq!(result_1.len(), 0);
238
239        let result_2 = QueryBuilder::new(&table)
240            .with_condition(ConditionBuilder::and(
241                ConditionBuilder::by(&index, "value1".to_string()),
242                ConditionBuilder::by(&length, 6),
243            ))
244            .select()
245            .expect("Select failed");
246
247        assert_eq!(result_2.len(), 1);
248        assert_eq!(result_2[0].id, value1);
249    }
250
251    #[test]
252    fn query_builder_select_or() {
253        let db = TinyBase::new(None, true);
254        let table: Table<String> = db.open_table("test_table").unwrap();
255
256        // Create an index for the table
257        let index = table
258            .create_index("name", |value| value.to_owned())
259            .unwrap();
260
261        // Insert string values into the table
262        table.insert("value1".to_string()).unwrap();
263        table.insert("value2".to_string()).unwrap();
264
265        let selected_records = QueryBuilder::new(&table)
266            .with_condition(ConditionBuilder::or(
267                ConditionBuilder::by(&index, "value1".to_string()),
268                ConditionBuilder::by(&index, "value2".to_string()),
269            ))
270            .select()
271            .expect("Select failed");
272
273        assert_eq!(selected_records.len(), 2);
274    }
275
276    #[test]
277    fn query_builder_select_combined() {
278        let db = TinyBase::new(None, true);
279        let table: Table<String> = db.open_table("test_table").unwrap();
280
281        // Create an index for the table
282        let name = table
283            .create_index("name", |value| value.to_owned())
284            .unwrap();
285
286        let length = table.create_index("length", |value| value.len()).unwrap();
287
288        // Insert string values into the table
289        table.insert("value1".to_string()).unwrap();
290        table.insert("value2".to_string()).unwrap();
291
292        let selected_records = QueryBuilder::new(&table)
293            .with_condition(ConditionBuilder::and(
294                ConditionBuilder::or(
295                    ConditionBuilder::by(&name, "value1".to_owned()),
296                    ConditionBuilder::by(&name, "value2".to_owned()),
297                ),
298                ConditionBuilder::by(&length, 6),
299            ))
300            .select()
301            .expect("Select failed");
302
303        assert_eq!(selected_records.len(), 2);
304    }
305
306    #[test]
307    fn query_builder_update() {
308        let db = TinyBase::new(None, true);
309        let table: Table<String> = db.open_table("test_table").unwrap();
310
311        // Create an index for the table
312        let index = table
313            .create_index("name", |value| value.to_owned())
314            .unwrap();
315
316        let length = table.create_index("length", |value| value.len()).unwrap();
317
318        // Insert string values into the table
319        table.insert("value1".to_string()).unwrap();
320        table.insert("value2".to_string()).unwrap();
321
322        let updated_records = QueryBuilder::new(&table)
323            .with_condition(ConditionBuilder::and(
324                ConditionBuilder::by(&index, "value1".to_string()),
325                ConditionBuilder::by(&length, 6),
326            ))
327            .update(|_| "updated_value".to_string())
328            .expect("Update failed");
329
330        assert_eq!(updated_records.len(), 1);
331        assert_eq!(updated_records[0].data, "updated_value");
332    }
333
334    #[test]
335    fn query_builder_delete() {
336        let db = TinyBase::new(None, true);
337        let table: Table<String> = db.open_table("test_table").unwrap();
338
339        // Insert string values into the table
340        table.insert("value1".to_string()).unwrap();
341        table.insert("value2".to_string()).unwrap();
342
343        // Create an index for the table
344        let index = table
345            .create_index("name", |value| value.to_owned())
346            .unwrap();
347
348        let deleted_records = QueryBuilder::new(&table)
349            .with_condition(ConditionBuilder::by(&index, "value1".to_string()))
350            .delete()
351            .expect("Update failed");
352
353        assert_eq!(deleted_records.len(), 1);
354
355        // Check if record is really deleted
356        let records = index.select(&"value1".to_string()).expect("Select failed");
357        assert_eq!(records.len(), 0);
358    }
359}