wasm_dbms_api/dbms/query/
builder.rs1use crate::dbms::query::{Filter, OrderDirection, Query};
2use crate::prelude::{Join, JoinType};
3
4#[derive(Debug, Default, Clone)]
6pub struct QueryBuilder {
7 query: Query,
8}
9
10impl QueryBuilder {
11 pub fn build(self) -> Query {
13 self.query
14 }
15
16 pub fn field(mut self, field: &str) -> Self {
18 let field = field.to_string();
19 match &mut self.query.columns {
20 crate::dbms::query::Select::All => {
21 self.query.columns = crate::dbms::query::Select::Columns(vec![field]);
22 }
23 crate::dbms::query::Select::Columns(cols) if !cols.contains(&field) => {
24 cols.push(field);
25 }
26 _ => {}
27 }
28 self
29 }
30
31 pub fn fields<I>(mut self, fields: I) -> Self
33 where
34 I: IntoIterator<Item = &'static str>,
35 {
36 for field in fields {
37 self = self.field(field);
38 }
39 self
40 }
41
42 pub fn all(mut self) -> Self {
44 self.query.columns = crate::dbms::query::Select::All;
45 self
46 }
47
48 pub fn with(mut self, table_relation: &str) -> Self {
50 let table_relation = table_relation.to_string();
51 if !self.query.eager_relations.contains(&table_relation) {
52 self.query.eager_relations.push(table_relation);
53 }
54 self
55 }
56
57 pub fn inner_join(self, table: &str, left_col: &str, right_col: &str) -> Self {
59 self.join(JoinType::Inner, table, left_col, right_col)
60 }
61
62 pub fn left_join(self, table: &str, left_col: &str, right_col: &str) -> Self {
64 self.join(JoinType::Left, table, left_col, right_col)
65 }
66
67 pub fn right_join(self, table: &str, left_col: &str, right_col: &str) -> Self {
69 self.join(JoinType::Right, table, left_col, right_col)
70 }
71
72 pub fn full_join(self, table: &str, left_col: &str, right_col: &str) -> Self {
74 self.join(JoinType::Full, table, left_col, right_col)
75 }
76
77 pub fn distinct<S: ToString>(mut self, fields: &[S]) -> Self {
79 self.query.distinct_by = fields.iter().map(|s| s.to_string()).collect();
80 self
81 }
82
83 pub fn group_by<S: ToString>(mut self, fields: &[S]) -> Self {
85 self.query.group_by = fields.iter().map(|s| s.to_string()).collect();
86 self
87 }
88
89 pub fn having(mut self, filter: Filter) -> Self {
91 self.query.having = Some(filter);
92 self
93 }
94
95 pub fn order_by_asc<S: ToString>(mut self, field: S) -> Self {
97 self.query
98 .order_by
99 .push((field.to_string(), OrderDirection::Ascending));
100 self
101 }
102
103 pub fn order_by_desc<S: ToString>(mut self, field: S) -> Self {
105 self.query
106 .order_by
107 .push((field.to_string(), OrderDirection::Descending));
108 self
109 }
110
111 pub fn limit(mut self, limit: usize) -> Self {
113 self.query.limit = Some(limit);
114 self
115 }
116
117 pub fn offset(mut self, offset: usize) -> Self {
119 self.query.offset = Some(offset);
120 self
121 }
122
123 pub fn filter(mut self, filter: Option<Filter>) -> Self {
125 self.query.filter = filter;
126 self
127 }
128
129 pub fn and_where(mut self, filter: Filter) -> Self {
131 self.query.filter = match self.query.filter {
132 Some(existing_filter) => Some(existing_filter.and(filter)),
133 None => Some(filter),
134 };
135 self
136 }
137
138 pub fn or_where(mut self, filter: Filter) -> Self {
140 self.query.filter = match self.query.filter {
141 Some(existing_filter) => Some(existing_filter.or(filter)),
142 None => Some(filter),
143 };
144 self
145 }
146
147 fn join(mut self, join_type: JoinType, table: &str, left_col: &str, right_col: &str) -> Self {
149 self.query.joins.push(Join {
150 join_type,
151 table: table.to_string(),
152 left_column: left_col.to_string(),
153 right_column: right_col.to_string(),
154 });
155 self
156 }
157}
158
159#[cfg(test)]
160mod tests {
161
162 use super::*;
163 use crate::dbms::value::Value;
164 use crate::tests::User;
165
166 #[test]
167 fn test_default_query_builder() {
168 let query_builder = QueryBuilder::default();
169 let query = query_builder.build();
170 assert!(matches!(query.columns, crate::dbms::query::Select::All));
171 assert!(query.eager_relations.is_empty());
172 assert!(query.filter.is_none());
173 assert!(query.order_by.is_empty());
174 assert!(query.limit.is_none());
175 assert!(query.offset.is_none());
176 }
177
178 #[test]
179 fn test_should_add_field_to_query_builder() {
180 let query_builder = QueryBuilder::default().field("id").field("name");
181
182 let query = query_builder.build();
183 assert_eq!(query.columns::<User>(), vec!["id", "name"]);
184 }
185
186 #[test]
187 fn test_should_set_fields() {
188 let query_builder = QueryBuilder::default().fields(["id", "email"]);
189
190 let query = query_builder.build();
191 assert_eq!(query.columns::<User>(), vec!["id", "email"]);
192 }
193
194 #[test]
195 fn test_should_set_all_fields() {
196 let query_builder = QueryBuilder::default().field("id").all();
197
198 let query = query_builder.build();
199 assert!(matches!(query.columns, crate::dbms::query::Select::All));
200 }
201
202 #[test]
203 fn test_should_add_eager_relation() {
204 let query_builder = QueryBuilder::default().with("posts");
205 let query = query_builder.build();
206 assert_eq!(query.eager_relations, vec!["posts"]);
207 }
208
209 #[test]
210 fn test_should_not_duplicate_eager_relation() {
211 let query_builder = QueryBuilder::default().with("posts").with("posts");
212 let query = query_builder.build();
213 assert_eq!(query.eager_relations, vec!["posts"]);
214 }
215
216 #[test]
217 fn test_should_add_order_by_clauses() {
218 let query_builder = QueryBuilder::default()
219 .order_by_asc("name")
220 .order_by_desc("created_at");
221 let query = query_builder.build();
222 assert_eq!(
223 query.order_by,
224 vec![
225 ("name".to_string(), OrderDirection::Ascending),
226 ("created_at".to_string(), OrderDirection::Descending)
227 ]
228 );
229 }
230
231 #[test]
232 fn test_should_distinct_by_fields() {
233 let query_builder = QueryBuilder::default().distinct(&["name", "email"]);
234 let query = query_builder.build();
235 assert_eq!(
236 query.distinct_by,
237 vec!["name".to_string(), "email".to_string()]
238 );
239 }
240
241 #[test]
242 fn test_should_set_group_by_fields() {
243 let query_builder = QueryBuilder::default().group_by(&["category", "status"]);
244 let query = query_builder.build();
245 assert_eq!(
246 query.group_by,
247 vec!["category".to_string(), "status".to_string()]
248 );
249 }
250
251 #[test]
252 fn test_should_set_having_filter() {
253 let filter = Filter::gt("count", Value::Uint32(10u32.into()));
254 let query_builder = QueryBuilder::default().having(filter.clone());
255 let query = query_builder.build();
256 assert_eq!(query.having, Some(filter));
257 }
258
259 #[test]
260 fn test_should_set_limit_and_offset() {
261 let query_builder = QueryBuilder::default().limit(10).offset(5);
262 let query = query_builder.build();
263 assert_eq!(query.limit, Some(10));
264 assert_eq!(query.offset, Some(5));
265 }
266
267 #[test]
268 fn test_should_create_filters() {
269 let query = QueryBuilder::default()
270 .all()
271 .and_where(Filter::eq("id", Value::Uint32(1u32.into())))
272 .or_where(Filter::like("name", "John%"))
273 .build();
274
275 let filter = query.filter.expect("should have filter");
276 if let Filter::Or(left, right) = filter {
277 assert!(matches!(*left, Filter::Eq(id, Value::Uint32(_)) if id == "id"));
278 assert!(matches!(*right, Filter::Like(name, _) if name == "name"));
279 } else {
280 panic!("Expected OR filter at the top level");
281 }
282 }
283
284 #[test]
285 fn test_should_add_inner_join() {
286 let query = QueryBuilder::default()
287 .all()
288 .inner_join("posts", "id", "user")
289 .build();
290 assert_eq!(query.joins.len(), 1);
291 assert_eq!(
292 query.joins[0].join_type,
293 crate::dbms::query::JoinType::Inner
294 );
295 assert_eq!(query.joins[0].table, "posts");
296 assert_eq!(query.joins[0].left_column, "id");
297 assert_eq!(query.joins[0].right_column, "user");
298 }
299
300 #[test]
301 fn test_should_add_left_join() {
302 let query = QueryBuilder::default()
303 .all()
304 .left_join("posts", "id", "user")
305 .build();
306 assert_eq!(query.joins[0].join_type, crate::dbms::query::JoinType::Left);
307 }
308
309 #[test]
310 fn test_should_add_right_join() {
311 let query = QueryBuilder::default()
312 .all()
313 .right_join("posts", "id", "user")
314 .build();
315 assert_eq!(
316 query.joins[0].join_type,
317 crate::dbms::query::JoinType::Right
318 );
319 }
320
321 #[test]
322 fn test_should_add_full_join() {
323 let query = QueryBuilder::default()
324 .all()
325 .full_join("posts", "id", "user")
326 .build();
327 assert_eq!(query.joins[0].join_type, crate::dbms::query::JoinType::Full);
328 }
329
330 #[test]
331 fn test_should_chain_multiple_joins() {
332 let query = QueryBuilder::default()
333 .all()
334 .inner_join("posts", "id", "user")
335 .left_join("comments", "posts.id", "post_id")
336 .build();
337 assert_eq!(query.joins.len(), 2);
338 }
339}