1use crate::dialect::Dialect;
27use crate::query::QueryBuilder;
28use crate::query::{AggExpr, HavingOp};
29use crate::value::Value;
30use std::collections::HashMap;
31
32#[derive(Clone)]
34struct AnonymousModel;
35
36impl crate::model::Model for AnonymousModel {
37 type PrimaryKey = i64;
38 fn table_name() -> &'static str {
39 ""
40 }
41 fn pk(&self) -> Self::PrimaryKey {
42 0
43 }
44 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
45}
46
47pub struct Db {
51 qb: QueryBuilder<AnonymousModel>,
52}
53
54impl Db {
55 pub fn new(dialect: Box<dyn Dialect>) -> Self {
57 Self {
58 qb: QueryBuilder::new(dialect),
59 }
60 }
61
62 #[must_use]
64 pub fn name(mut self, table: impl Into<String>) -> Self {
65 self.qb = self.qb.table(table);
66 self
67 }
68
69 pub fn select(mut self, columns: Vec<&str>) -> Result<Self, crate::DbError> {
71 self.qb = self.qb.select(columns)?;
72 Ok(self)
73 }
74
75 #[must_use]
77 pub fn select_expr(mut self, columns: Vec<&str>) -> Self {
78 self.qb = self.qb.select_expr(columns);
79 self
80 }
81
82 #[must_use]
84 pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
85 self.qb = self.qb.where_eq(field, value);
86 self
87 }
88
89 #[must_use]
91 pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
92 self.qb = self.qb.where_ne(field, value);
93 self
94 }
95
96 #[must_use]
98 pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
99 self.qb = self.qb.where_gt(field, value);
100 self
101 }
102
103 #[must_use]
105 pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
106 self.qb = self.qb.where_ge(field, value);
107 self
108 }
109
110 #[must_use]
112 pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
113 self.qb = self.qb.where_lt(field, value);
114 self
115 }
116
117 #[must_use]
119 pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
120 self.qb = self.qb.where_le(field, value);
121 self
122 }
123
124 #[must_use]
126 pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
127 self.qb = self.qb.where_like(field, pattern);
128 self
129 }
130
131 #[must_use]
133 pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
134 self.qb = self.qb.or_where_eq(field, value);
135 self
136 }
137
138 #[must_use]
140 pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
141 self.qb = self.qb.or_where_ne(field, value);
142 self
143 }
144
145 #[must_use]
147 pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
148 self.qb = self.qb.or_where_gt(field, value);
149 self
150 }
151
152 #[must_use]
154 pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
155 self.qb = self.qb.or_where_ge(field, value);
156 self
157 }
158
159 #[must_use]
161 pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
162 self.qb = self.qb.or_where_lt(field, value);
163 self
164 }
165
166 #[must_use]
168 pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
169 self.qb = self.qb.or_where_le(field, value);
170 self
171 }
172
173 #[must_use]
175 pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
176 self.qb = self.qb.or_where_like(field, pattern);
177 self
178 }
179
180 #[must_use]
182 pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
183 self.qb = self.qb.where_in(field, values);
184 self
185 }
186
187 #[must_use]
189 pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
190 self.qb = self.qb.where_not_in(field, values);
191 self
192 }
193
194 #[must_use]
196 pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
197 self.qb = self.qb.where_between(field, start, end);
198 self
199 }
200
201 #[must_use]
203 pub fn where_null(mut self, field: impl Into<String>) -> Self {
204 self.qb = self.qb.where_null(field);
205 self
206 }
207
208 #[must_use]
210 pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
211 self.qb = self.qb.where_not_null(field);
212 self
213 }
214
215 #[must_use]
217 pub fn order_by(mut self, field: impl Into<String>) -> Self {
218 self.qb = self.qb.order_by(field);
219 self
220 }
221
222 #[must_use]
224 pub fn order_desc(mut self, field: impl Into<String>) -> Self {
225 self.qb = self.qb.order_desc(field);
226 self
227 }
228
229 #[must_use]
231 pub fn group_by(mut self, field: impl Into<String>) -> Self {
232 self.qb = self.qb.group_by(field);
233 self
234 }
235
236 pub fn having(
238 mut self,
239 agg: AggExpr,
240 op: HavingOp,
241 value: Value,
242 ) -> Result<Self, crate::DbError> {
243 self.qb = self.qb.having(agg, op, value)?;
244 Ok(self)
245 }
246
247 #[must_use]
249 pub fn limit(mut self, limit: usize) -> Self {
250 self.qb = self.qb.limit(limit);
251 self
252 }
253
254 #[must_use]
256 pub fn offset(mut self, offset: usize) -> Self {
257 self.qb = self.qb.offset(offset);
258 self
259 }
260
261 #[must_use]
263 pub fn page(mut self, page: usize, page_size: usize) -> Self {
264 self.qb = self.qb.page(page, page_size);
265 self
266 }
267
268 #[must_use]
270 pub fn join_inner(
271 mut self,
272 table: impl Into<String>,
273 on_left: impl Into<String>,
274 on_right: impl Into<String>,
275 ) -> Self {
276 self.qb = self.qb.join_inner(table, on_left, on_right);
277 self
278 }
279
280 #[must_use]
282 pub fn join_left(
283 mut self,
284 table: impl Into<String>,
285 on_left: impl Into<String>,
286 on_right: impl Into<String>,
287 ) -> Self {
288 self.qb = self.qb.join_left(table, on_left, on_right);
289 self
290 }
291
292 #[must_use]
294 pub fn join_right(
295 mut self,
296 table: impl Into<String>,
297 on_left: impl Into<String>,
298 on_right: impl Into<String>,
299 ) -> Self {
300 self.qb = self.qb.join_right(table, on_left, on_right);
301 self
302 }
303
304 pub fn build_select(&self) -> String {
306 self.qb.build_select()
307 }
308
309 pub fn build_insert(&self, data: &HashMap<String, Value>) -> String {
311 self.qb.build_insert(data)
312 }
313
314 pub fn build_update(&self, data: &HashMap<String, Value>) -> String {
316 self.qb.build_update(data)
317 }
318
319 pub fn build_delete(&self) -> String {
321 self.qb.build_delete()
322 }
323
324 pub fn build_count(&self) -> String {
326 self.qb.build_count()
327 }
328
329 pub fn build_exists(&self) -> String {
331 self.qb.build_exists()
332 }
333
334 pub fn build_max(&self, field: &str) -> String {
336 self.qb.build_max(field)
337 }
338
339 pub fn build_min(&self, field: &str) -> String {
341 self.qb.build_min(field)
342 }
343
344 pub fn build_sum(&self, field: &str) -> String {
346 self.qb.build_sum(field)
347 }
348
349 pub fn build_avg(&self, field: &str) -> String {
351 self.qb.build_avg(field)
352 }
353}
354
355#[cfg(test)]
356#[allow(deprecated)]
357mod tests {
358 use super::*;
359 use crate::db_type::DbType;
360 use crate::dialect::get_dialect;
361
362 fn mysql() -> Box<dyn Dialect> {
363 get_dialect(DbType::MySQL).expect("MySQL dialect")
364 }
365
366 fn pg() -> Box<dyn Dialect> {
367 get_dialect(DbType::PostgreSQL).expect("PG dialect")
368 }
369
370 #[test]
371 fn db_name_basic_select() {
372 let sql = Db::new(mysql()).name("users").build_select();
373 assert_eq!(sql, "SELECT * FROM `users`");
374 }
375
376 #[test]
377 fn db_name_with_where_and_limit() {
378 let sql = Db::new(mysql())
379 .name("users")
380 .where_gt("age", Value::I64(18))
381 .order_desc("id")
382 .limit(10)
383 .build_select();
384 assert!(sql.contains("SELECT * FROM `users`"));
385 assert!(sql.contains("WHERE `age` > 18"));
386 assert!(sql.contains("ORDER BY `id` DESC"));
387 assert!(sql.contains("LIMIT 10"));
388 }
389
390 #[test]
391 fn db_name_insert() {
392 let mut data = HashMap::new();
393 data.insert("name".to_string(), Value::String("Alice".to_string()));
394 data.insert("age".to_string(), Value::I64(30));
395 let sql = Db::new(mysql()).name("users").build_insert(&data);
396 assert!(sql.starts_with("INSERT INTO `users`"));
397 assert!(sql.contains("`name`"));
398 assert!(sql.contains("`age`"));
399 assert!(sql.contains("'Alice'"));
400 assert!(sql.contains("30"));
401 }
402
403 #[test]
404 fn db_name_update_with_where() {
405 let mut data = HashMap::new();
406 data.insert("name".to_string(), Value::String("Bob".to_string()));
407 let sql = Db::new(mysql())
408 .name("users")
409 .where_eq("id", Value::I64(1))
410 .build_update(&data);
411 assert!(sql.starts_with("UPDATE `users` SET"));
412 assert!(sql.contains("`name` = 'Bob'"));
413 assert!(sql.contains("WHERE `id` = 1"));
414 }
415
416 #[test]
417 fn db_name_delete_with_where() {
418 let sql = Db::new(mysql())
419 .name("users")
420 .where_eq("id", Value::I64(1))
421 .build_delete();
422 assert!(sql.contains("DELETE FROM `users` WHERE `id` = 1"));
423 }
424
425 #[test]
426 fn db_name_count() {
427 let sql = Db::new(mysql())
428 .name("users")
429 .where_gt("age", Value::I64(18))
430 .build_count();
431 assert!(sql.contains("SELECT COUNT(*)"));
432 assert!(sql.contains("FROM `users`"));
433 assert!(sql.contains("WHERE `age` > 18"));
434 }
435
436 #[test]
437 fn db_name_with_in_clause() {
438 let sql = Db::new(mysql())
439 .name("users")
440 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
441 .build_select();
442 assert!(sql.contains("WHERE `id` IN (1, 2, 3)"));
443 }
444
445 #[test]
446 fn db_name_with_between() {
447 let sql = Db::new(mysql())
448 .name("orders")
449 .where_between("amount", Value::I64(100), Value::I64(1000))
450 .build_select();
451 assert!(sql.contains("`amount` BETWEEN 100 AND 1000"));
452 }
453
454 #[test]
455 fn db_name_pg_dialect() {
456 let sql = Db::new(pg()).name("users").build_select();
457 assert_eq!(sql, "SELECT * FROM \"users\"");
458 }
459
460 #[test]
461 fn db_name_join_inner() {
462 let sql = Db::new(mysql())
463 .name("orders")
464 .join_inner("users", "orders.user_id", "users.id")
465 .build_select();
466 assert!(sql.contains("INNER JOIN `users` ON `orders.user_id` = `users.id`"));
467 }
468
469 #[test]
470 fn db_name_pagination() {
471 let sql = Db::new(mysql()).name("users").page(3, 20).build_select();
472 assert!(sql.contains("LIMIT 20"));
474 assert!(sql.contains("OFFSET 40"));
475 }
476
477 #[test]
478 fn db_name_aggregate_functions() {
479 let db = Db::new(mysql())
480 .name("orders")
481 .where_eq("status", Value::String("paid".into()));
482 assert!(db.build_sum("amount").contains("SUM(`amount`)"));
483 assert!(db.build_max("amount").contains("MAX(`amount`)"));
484 assert!(db.build_min("amount").contains("MIN(`amount`)"));
485 assert!(db.build_avg("amount").contains("AVG(`amount`)"));
486 assert!(db.build_exists().contains("SELECT EXISTS("));
487 }
488
489 #[test]
490 fn db_name_chained_or_where() {
491 let sql = Db::new(mysql())
492 .name("users")
493 .where_lt("age", Value::I64(18))
494 .or_where_gt("age", Value::I64(65))
495 .build_select();
496 assert!(sql.contains("WHERE (`age` < 18 OR `age` > 65)"));
497 }
498
499 #[test]
500 fn db_name_group_having() {
501 let sql = Db::new(mysql())
502 .name("orders")
503 .select_expr(vec!["user_id", "COUNT(*) as cnt"])
504 .group_by("user_id")
505 .having(AggExpr::CountStar, HavingOp::Gt, Value::I64(5))
506 .expect("valid aggregate")
507 .build_select();
508 assert!(sql.contains("GROUP BY `user_id`"));
509 assert!(sql.contains("HAVING COUNT(*) > 5"));
510 }
511}