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 order_by_asc(mut self, field: &str) -> Self {
79 self.query
80 .order_by
81 .push((field.to_string(), OrderDirection::Ascending));
82 self
83 }
84
85 pub fn order_by_desc(mut self, field: &str) -> Self {
87 self.query
88 .order_by
89 .push((field.to_string(), OrderDirection::Descending));
90 self
91 }
92
93 pub fn limit(mut self, limit: usize) -> Self {
95 self.query.limit = Some(limit);
96 self
97 }
98
99 pub fn offset(mut self, offset: usize) -> Self {
101 self.query.offset = Some(offset);
102 self
103 }
104
105 pub fn filter(mut self, filter: Option<Filter>) -> Self {
107 self.query.filter = filter;
108 self
109 }
110
111 pub fn and_where(mut self, filter: Filter) -> Self {
113 self.query.filter = match self.query.filter {
114 Some(existing_filter) => Some(existing_filter.and(filter)),
115 None => Some(filter),
116 };
117 self
118 }
119
120 pub fn or_where(mut self, filter: Filter) -> Self {
122 self.query.filter = match self.query.filter {
123 Some(existing_filter) => Some(existing_filter.or(filter)),
124 None => Some(filter),
125 };
126 self
127 }
128
129 fn join(mut self, join_type: JoinType, table: &str, left_col: &str, right_col: &str) -> Self {
131 self.query.joins.push(Join {
132 join_type,
133 table: table.to_string(),
134 left_column: left_col.to_string(),
135 right_column: right_col.to_string(),
136 });
137 self
138 }
139}
140
141#[cfg(test)]
142mod tests {
143
144 use super::*;
145 use crate::dbms::value::Value;
146 use crate::tests::User;
147
148 #[test]
149 fn test_default_query_builder() {
150 let query_builder = QueryBuilder::default();
151 let query = query_builder.build();
152 assert!(matches!(query.columns, crate::dbms::query::Select::All));
153 assert!(query.eager_relations.is_empty());
154 assert!(query.filter.is_none());
155 assert!(query.order_by.is_empty());
156 assert!(query.limit.is_none());
157 assert!(query.offset.is_none());
158 }
159
160 #[test]
161 fn test_should_add_field_to_query_builder() {
162 let query_builder = QueryBuilder::default().field("id").field("name");
163
164 let query = query_builder.build();
165 assert_eq!(query.columns::<User>(), vec!["id", "name"]);
166 }
167
168 #[test]
169 fn test_should_set_fields() {
170 let query_builder = QueryBuilder::default().fields(["id", "email"]);
171
172 let query = query_builder.build();
173 assert_eq!(query.columns::<User>(), vec!["id", "email"]);
174 }
175
176 #[test]
177 fn test_should_set_all_fields() {
178 let query_builder = QueryBuilder::default().field("id").all();
179
180 let query = query_builder.build();
181 assert!(matches!(query.columns, crate::dbms::query::Select::All));
182 }
183
184 #[test]
185 fn test_should_add_eager_relation() {
186 let query_builder = QueryBuilder::default().with("posts");
187 let query = query_builder.build();
188 assert_eq!(query.eager_relations, vec!["posts"]);
189 }
190
191 #[test]
192 fn test_should_not_duplicate_eager_relation() {
193 let query_builder = QueryBuilder::default().with("posts").with("posts");
194 let query = query_builder.build();
195 assert_eq!(query.eager_relations, vec!["posts"]);
196 }
197
198 #[test]
199 fn test_should_add_order_by_clauses() {
200 let query_builder = QueryBuilder::default()
201 .order_by_asc("name")
202 .order_by_desc("created_at");
203 let query = query_builder.build();
204 assert_eq!(
205 query.order_by,
206 vec![
207 ("name".to_string(), OrderDirection::Ascending),
208 ("created_at".to_string(), OrderDirection::Descending)
209 ]
210 );
211 }
212
213 #[test]
214 fn test_should_set_limit_and_offset() {
215 let query_builder = QueryBuilder::default().limit(10).offset(5);
216 let query = query_builder.build();
217 assert_eq!(query.limit, Some(10));
218 assert_eq!(query.offset, Some(5));
219 }
220
221 #[test]
222 fn test_should_create_filters() {
223 let query = QueryBuilder::default()
224 .all()
225 .and_where(Filter::eq("id", Value::Uint32(1u32.into())))
226 .or_where(Filter::like("name", "John%"))
227 .build();
228
229 let filter = query.filter.expect("should have filter");
230 if let Filter::Or(left, right) = filter {
231 assert!(matches!(*left, Filter::Eq(id, Value::Uint32(_)) if id == "id"));
232 assert!(matches!(*right, Filter::Like(name, _) if name == "name"));
233 } else {
234 panic!("Expected OR filter at the top level");
235 }
236 }
237
238 #[test]
239 fn test_should_add_inner_join() {
240 let query = QueryBuilder::default()
241 .all()
242 .inner_join("posts", "id", "user")
243 .build();
244 assert_eq!(query.joins.len(), 1);
245 assert_eq!(
246 query.joins[0].join_type,
247 crate::dbms::query::JoinType::Inner
248 );
249 assert_eq!(query.joins[0].table, "posts");
250 assert_eq!(query.joins[0].left_column, "id");
251 assert_eq!(query.joins[0].right_column, "user");
252 }
253
254 #[test]
255 fn test_should_add_left_join() {
256 let query = QueryBuilder::default()
257 .all()
258 .left_join("posts", "id", "user")
259 .build();
260 assert_eq!(query.joins[0].join_type, crate::dbms::query::JoinType::Left);
261 }
262
263 #[test]
264 fn test_should_add_right_join() {
265 let query = QueryBuilder::default()
266 .all()
267 .right_join("posts", "id", "user")
268 .build();
269 assert_eq!(
270 query.joins[0].join_type,
271 crate::dbms::query::JoinType::Right
272 );
273 }
274
275 #[test]
276 fn test_should_add_full_join() {
277 let query = QueryBuilder::default()
278 .all()
279 .full_join("posts", "id", "user")
280 .build();
281 assert_eq!(query.joins[0].join_type, crate::dbms::query::JoinType::Full);
282 }
283
284 #[test]
285 fn test_should_chain_multiple_joins() {
286 let query = QueryBuilder::default()
287 .all()
288 .inner_join("posts", "id", "user")
289 .left_join("comments", "posts.id", "post_id")
290 .build();
291 assert_eq!(query.joins.len(), 2);
292 }
293}