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