ic_dbms_api/dbms/query/
builder.rs

1use std::marker::PhantomData;
2
3use crate::dbms::query::{Filter, OrderDirection, Query};
4use crate::dbms::table::TableSchema;
5
6/// A builder for constructing database [`Query`]es.
7#[derive(Debug, Clone)]
8pub struct QueryBuilder<T>
9where
10    T: TableSchema,
11{
12    query: Query<T>,
13    _marker: PhantomData<T>,
14}
15
16impl<T> Default for QueryBuilder<T>
17where
18    T: TableSchema,
19{
20    fn default() -> Self {
21        Self {
22            query: Query::default(),
23            _marker: PhantomData,
24        }
25    }
26}
27
28impl<T> QueryBuilder<T>
29where
30    T: TableSchema,
31{
32    /// Builds and returns a [`Query`] object based on the current state of the [`QueryBuilder`].
33    pub fn build(self) -> Query<T> {
34        self.query
35    }
36
37    /// Adds a field to select in the query.
38    pub fn field(mut self, field: &'static str) -> Self {
39        match &mut self.query.columns {
40            crate::dbms::query::Select::All => {
41                self.query.columns = crate::dbms::query::Select::Columns(vec![field]);
42            }
43            crate::dbms::query::Select::Columns(cols) if !cols.contains(&field) => {
44                cols.push(field);
45            }
46            _ => {}
47        }
48        self
49    }
50
51    /// Adds multiple fields to select in the query.
52    pub fn fields<I>(mut self, fields: I) -> Self
53    where
54        I: IntoIterator<Item = &'static str>,
55    {
56        for field in fields {
57            self = self.field(field);
58        }
59        self
60    }
61
62    /// Sets the query to select all fields.
63    pub fn all(mut self) -> Self {
64        self.query.columns = crate::dbms::query::Select::All;
65        self
66    }
67
68    /// Adds a relation to eagerly load with the main records.
69    pub fn with(mut self, table_relation: &'static str) -> Self {
70        if !self.query.eager_relations.contains(&table_relation) {
71            self.query.eager_relations.push(table_relation);
72        }
73        self
74    }
75
76    /// Adds an ascending order by clause for the specified field.
77    pub fn order_by_asc(mut self, field: &'static str) -> Self {
78        self.query.order_by.push((field, OrderDirection::Ascending));
79        self
80    }
81
82    /// Adds a descending order by clause for the specified field.
83    pub fn order_by_desc(mut self, field: &'static str) -> Self {
84        self.query
85            .order_by
86            .push((field, OrderDirection::Descending));
87        self
88    }
89
90    /// Sets a limit on the number of records to return.
91    pub fn limit(mut self, limit: usize) -> Self {
92        self.query.limit = Some(limit);
93        self
94    }
95
96    /// Sets an offset for pagination.
97    pub fn offset(mut self, offset: usize) -> Self {
98        self.query.offset = Some(offset);
99        self
100    }
101
102    /// Sets a filter for the query, replacing any existing filter.
103    pub fn filter(mut self, filter: Option<Filter>) -> Self {
104        self.query.filter = filter;
105        self
106    }
107
108    /// Adds a filter to the query, combining with existing filters using AND.
109    pub fn and_where(mut self, filter: Filter) -> Self {
110        self.query.filter = match self.query.filter {
111            Some(existing_filter) => Some(existing_filter.and(filter)),
112            None => Some(filter),
113        };
114        self
115    }
116
117    /// Adds a filter to the query, combining with existing filters using OR.
118    pub fn or_where(mut self, filter: Filter) -> Self {
119        self.query.filter = match self.query.filter {
120            Some(existing_filter) => Some(existing_filter.or(filter)),
121            None => Some(filter),
122        };
123        self
124    }
125}
126
127#[cfg(test)]
128mod tests {
129
130    use super::*;
131    use crate::dbms::value::Value;
132    use crate::tests::User;
133
134    #[test]
135    fn test_default_query_builder() {
136        let query_builder = QueryBuilder::<User>::default();
137        let query = query_builder.build();
138        assert!(matches!(query.columns, crate::dbms::query::Select::All));
139        assert!(query.eager_relations.is_empty());
140        assert!(query.filter.is_none());
141        assert!(query.order_by.is_empty());
142        assert!(query.limit.is_none());
143        assert!(query.offset.is_none());
144    }
145
146    #[test]
147    fn test_should_add_field_to_query_builder() {
148        let query_builder = QueryBuilder::<User>::default().field("id").field("name");
149
150        let query = query_builder.build();
151        assert_eq!(query.columns(), vec!["id", "name"]);
152    }
153
154    #[test]
155    fn test_should_set_fields() {
156        let query_builder = QueryBuilder::<User>::default().fields(["id", "email"]);
157
158        let query = query_builder.build();
159        assert_eq!(query.columns(), vec!["id", "email"]);
160    }
161
162    #[test]
163    fn test_should_set_all_fields() {
164        let query_builder = QueryBuilder::<User>::default().field("id").all();
165
166        let query = query_builder.build();
167        assert!(matches!(query.columns, crate::dbms::query::Select::All));
168    }
169
170    #[test]
171    fn test_should_add_eager_relation() {
172        let query_builder = QueryBuilder::<User>::default().with("posts");
173        let query = query_builder.build();
174        assert_eq!(query.eager_relations, vec!["posts"]);
175    }
176
177    #[test]
178    fn test_should_not_duplicate_eager_relation() {
179        let query_builder = QueryBuilder::<User>::default().with("posts").with("posts");
180        let query = query_builder.build();
181        assert_eq!(query.eager_relations, vec!["posts"]);
182    }
183
184    #[test]
185    fn test_should_add_order_by_clauses() {
186        let query_builder = QueryBuilder::<User>::default()
187            .order_by_asc("name")
188            .order_by_desc("created_at");
189        let query = query_builder.build();
190        assert_eq!(
191            query.order_by,
192            vec![
193                ("name", OrderDirection::Ascending),
194                ("created_at", OrderDirection::Descending)
195            ]
196        );
197    }
198
199    #[test]
200    fn test_should_set_limit_and_offset() {
201        let query_builder = QueryBuilder::<User>::default().limit(10).offset(5);
202        let query = query_builder.build();
203        assert_eq!(query.limit, Some(10));
204        assert_eq!(query.offset, Some(5));
205    }
206
207    #[test]
208    fn test_should_create_filters() {
209        let query = QueryBuilder::<User>::default()
210            .all()
211            .and_where(Filter::eq("id", Value::Uint32(1u32.into())))
212            .or_where(Filter::like("name", "John%"))
213            .build();
214
215        let filter = query.filter.expect("should have filter");
216        if let Filter::Or(left, right) = filter {
217            assert!(matches!(*left, Filter::Eq("id", Value::Uint32(_))));
218            assert!(matches!(*right, Filter::Like("name", _)));
219        } else {
220            panic!("Expected OR filter at the top level");
221        }
222    }
223}