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