bottle_orm/query_builder.rs
1//! # Query Builder Module
2//!
3//! This module provides a fluent interface for constructing and executing SQL queries.
4//! It handles SELECT, INSERT, filtering (WHERE), pagination (LIMIT/OFFSET), and ordering operations
5//! with type-safe parameter binding across different database drivers.
6//!
7//! ## Features
8//!
9//! - **Fluent API**: Chainable methods for building complex queries
10//! - **Type-Safe Binding**: Automatic parameter binding with support for multiple types
11//! - **Multi-Driver Support**: Works with PostgreSQL, MySQL, and SQLite
12//! - **UUID Support**: Full support for UUID versions 1-7
13//! - **Pagination**: Built-in LIMIT/OFFSET support with helper methods
14//! - **Custom Filters**: Support for manual SQL construction with closures
15//!
16//! ## Example Usage
17//!
18//! ```rust,ignore
19//! use bottle_orm::{Database, Model};
20//!
21//!
22//! // Simple query
23//! let users: Vec<User> = db.model::<User>()
24//! .filter("age", ">=", 18)
25//! .order("created_at DESC")
26//! .limit(10)
27//! .scan()
28//! .await?;
29//!
30//! // Query with UUID filter
31//! let user_id = Uuid::new_v4();
32//! let user: User = db.model::<User>()
33//! .filter("id", "=", user_id)
34//! .first()
35//! .await?;
36//!
37//! // Insert a new record
38//! let new_user = User {
39//! id: Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)),
40//! username: "john_doe".to_string(),
41//! age: 25,
42//! };
43//! db.model::<User>().insert(&new_user).await?;
44//! ```
45
46// ============================================================================
47// External Crate Imports
48// ============================================================================
49
50use futures::future::BoxFuture;
51use heck::ToSnakeCase;
52use sqlx::{Any, Arguments, Decode, Encode, Type, any::AnyArguments};
53use std::marker::PhantomData;
54
55
56// ============================================================================
57// Internal Crate Imports
58// ============================================================================
59
60use crate::{
61 AnyImpl, Error,
62 any_struct::FromAnyRow,
63 database::{Connection, Drivers},
64 model::{ColumnInfo, Model},
65 temporal::{self, is_temporal_type},
66 value_binding::ValueBinder,
67};
68
69// ============================================================================
70// Type Aliases
71// ============================================================================
72
73/// A type alias for filter closures that support manual SQL construction and argument binding.
74///
75/// Filter functions receive the following parameters:
76/// 1. `&mut String` - The SQL query buffer being built
77/// 2. `&mut AnyArguments` - The argument container for binding values
78/// 3. `&Drivers` - The current database driver (determines placeholder syntax)
79/// 4. `&mut usize` - The argument counter (for PostgreSQL `$n` placeholders)
80///
81/// ## Example
82///
83/// ```rust,ignore
84/// let custom_filter: FilterFn = Box::new(|query, args, driver, counter| {
85/// query.push_str(" AND age > ");
86/// match driver {
87/// Drivers::Postgres => {
88/// query.push_str(&format!("${}", counter));
89/// *counter += 1;
90/// }
91/// _ => query.push('?'),
92/// }
93/// args.add(18);
94/// });
95/// });\n/// ```
96pub type FilterFn = Box<dyn Fn(&mut String, &mut AnyArguments<'_>, &Drivers, &mut usize) + Send + Sync>;
97
98// ============================================================================
99// Comparison Operators Enum
100// ============================================================================
101
102/// Type-safe comparison operators for filter conditions.
103///
104/// Use these instead of string operators for autocomplete support and type safety.
105///
106/// # Example
107///
108/// ```rust,ignore
109/// use bottle_orm::Op;
110///
111/// db.model::<User>()
112/// .filter(user_fields::AGE, Op::Gte, 18)
113/// .filter(user_fields::NAME, Op::Like, "%John%")
114/// .scan()
115/// .await?;
116/// ```
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum Op {
119 /// Equal: `=`
120 Eq,
121 /// Not Equal: `!=` or `<>`
122 Ne,
123 /// Greater Than: `>`
124 Gt,
125 /// Greater Than or Equal: `>=`
126 Gte,
127 /// Less Than: `<`
128 Lt,
129 /// Less Than or Equal: `<=`
130 Lte,
131 /// SQL LIKE pattern matching
132 Like,
133 /// SQL NOT LIKE pattern matching
134 NotLike,
135 /// SQL IN (for arrays/lists)
136 In,
137 /// SQL NOT IN
138 NotIn,
139 /// SQL BETWEEN
140 Between,
141 /// SQL NOT BETWEEN
142 NotBetween,
143}
144
145impl Op {
146 /// Converts the operator to its SQL string representation.
147 pub fn as_sql(&self) -> &'static str {
148 match self {
149 Op::Eq => "=",
150 Op::Ne => "!=",
151 Op::Gt => ">",
152 Op::Gte => ">=",
153 Op::Lt => "<",
154 Op::Lte => "<=",
155 Op::Like => "LIKE",
156 Op::NotLike => "NOT LIKE",
157 Op::In => "IN",
158 Op::NotIn => "NOT IN",
159 Op::Between => "BETWEEN",
160 Op::NotBetween => "NOT BETWEEN",
161 }
162 }
163}
164
165// ============================================================================
166// QueryBuilder Struct
167// ============================================================================
168
169/// A fluent Query Builder for constructing SQL queries.
170///
171/// `QueryBuilder` provides a type-safe, ergonomic interface for building and executing
172/// SQL queries across different database backends. It supports filtering, ordering,
173/// pagination, and both SELECT and INSERT operations.
174///
175/// ## Type Parameter
176///
177/// * `'a` - Lifetime of the database reference (used for PhantomData)
178/// * `T` - The Model type this query operates on
179/// * `E` - The connection type (Database or Transaction)
180///
181/// ## Fields
182///
183/// * `db` - Reference to the database connection pool or transaction
184/// * `table_name` - Static string containing the table name
185/// * `columns_info` - Metadata about each column in the table
186/// * `columns` - List of column names in snake_case format
187/// * `select_columns` - Specific columns to select (empty = SELECT *)
188/// * `where_clauses` - List of filter functions to apply
189/// * `order_clauses` - List of ORDER BY clauses
190/// * `limit` - Maximum number of rows to return
191/// * `offset` - Number of rows to skip (for pagination)
192/// * `_marker` - PhantomData to bind the generic type T
193pub struct QueryBuilder<T, E> {
194 /// Reference to the database connection pool
195 pub(crate) tx: E,
196
197 /// Database driver type
198 pub(crate) driver: Drivers,
199
200 /// Name of the database table (in original case)
201 pub(crate) table_name: &'static str,
202
203 pub(crate) alias: Option<String>,
204
205 /// Metadata information about each column
206 pub(crate) columns_info: Vec<ColumnInfo>,
207
208 /// List of column names (in snake_case)
209 pub(crate) columns: Vec<String>,
210
211 /// Specific columns to select (empty means SELECT *)
212 pub(crate) select_columns: Vec<String>,
213
214 /// Collection of WHERE clause filter functions
215 pub(crate) where_clauses: Vec<FilterFn>,
216
217 /// Collection of ORDER BY clauses
218 pub(crate) order_clauses: Vec<String>,
219
220 /// Collection of JOIN clause to filter entry tables
221 pub(crate) joins_clauses: Vec<FilterFn>,
222
223 /// Map of table names to their aliases used in JOINS
224 pub(crate) join_aliases: std::collections::HashMap<String, String>,
225
226 /// Maximum number of rows to return (LIMIT)
227 pub(crate) limit: Option<usize>,
228
229 /// Number of rows to skip (OFFSET)
230 pub(crate) offset: Option<usize>,
231
232 /// Activate debug mode in query
233 pub(crate) debug_mode: bool,
234
235 /// Clauses for GROUP BY
236 pub(crate) group_by_clauses: Vec<String>,
237
238 /// Clauses for HAVING
239 pub(crate) having_clauses: Vec<FilterFn>,
240
241 /// Distinct flag
242 pub(crate) is_distinct: bool,
243
244 /// Columns to omit from the query results (inverse of select_columns)
245 pub(crate) omit_columns: Vec<String>,
246
247 /// Whether to include soft-deleted records in query results
248 pub(crate) with_deleted: bool,
249
250 /// UNION and UNION ALL clauses
251 pub(crate) union_clauses: Vec<(String, FilterFn)>,
252
253 /// PhantomData to bind the generic type T
254 pub(crate) _marker: PhantomData<T>,
255}
256
257// ============================================================================
258// QueryBuilder Implementation
259// ============================================================================
260
261impl<T, E> QueryBuilder<T, E>
262where
263 T: Model + Send + Sync + Unpin + AnyImpl,
264 E: Connection,
265{
266 // ========================================================================
267 // Constructor
268 // ========================================================================
269
270 /// Creates a new QueryBuilder instance.
271 ///
272 /// This constructor is typically called internally via `db.model::<T>()`.
273 /// You rarely need to call this directly.
274 ///
275 /// # Arguments
276 ///
277 /// * `db` - Reference to the database connection
278 /// * `table_name` - Name of the table to query
279 /// * `columns_info` - Metadata about table columns
280 /// * `columns` - List of column names
281 ///
282 /// # Returns
283 ///
284 /// A new `QueryBuilder` instance ready for query construction
285 ///
286 /// # Example
287 ///
288 /// ```rust,ignore
289 /// // Usually called via db.model::<User>()
290 /// let query = db.model::<User>();
291 /// ```
292 pub fn new(
293 tx: E,
294 driver: Drivers,
295 table_name: &'static str,
296 columns_info: Vec<ColumnInfo>,
297 columns: Vec<String>,
298 ) -> Self {
299 // Pre-populate omit_columns with globally omitted columns (from #[orm(omit)] attribute)
300 let omit_columns: Vec<String> =
301 columns_info.iter().filter(|c| c.omit).map(|c| c.name.to_snake_case()).collect();
302
303 Self {
304 tx,
305 alias: None,
306 driver,
307 table_name,
308 columns_info,
309 columns,
310 debug_mode: false,
311 select_columns: Vec::new(),
312 where_clauses: Vec::new(),
313 order_clauses: Vec::new(),
314 joins_clauses: Vec::new(),
315 join_aliases: std::collections::HashMap::new(),
316 group_by_clauses: Vec::new(),
317 having_clauses: Vec::new(),
318 is_distinct: false,
319 omit_columns,
320 limit: None,
321 offset: None,
322 with_deleted: false,
323 union_clauses: Vec::new(),
324 _marker: PhantomData,
325 }
326 }
327
328 /// Returns the table name or alias if set.
329 pub(crate) fn get_table_identifier(&self) -> String {
330 self.alias.clone().unwrap_or_else(|| self.table_name.to_snake_case())
331 }
332
333 // ========================================================================
334 // Query Building Methods
335 // ========================================================================
336
337 /// Internal helper to add a WHERE clause with a specific join operator.
338 fn filter_internal<V>(mut self, joiner: &str, col: &'static str, op: Op, value: V) -> Self
339 where
340 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
341 {
342 let op_str = op.as_sql();
343 let table_id = self.get_table_identifier();
344 // Check if the column exists in the main table to avoid ambiguous references in JOINS
345 let is_main_col = self.columns.contains(&col.to_snake_case());
346 let joiner_owned = joiner.to_string();
347 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
348 query.push_str(&joiner_owned);
349 if let Some((table, column)) = col.split_once(".") {
350 // If explicit table prefix is provided, use it
351 query.push_str(&format!("\"{}\".\"{}\"", table, column));
352 } else if is_main_col {
353 // If it's a known column of the main table, apply the table name/alias prefix
354 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
355 } else {
356 // Otherwise leave it unqualified so the DB can resolve it (or fail if ambiguous)
357 query.push_str(&format!("\"{}\"", col));
358 }
359 query.push(' ');
360 query.push_str(op_str);
361 query.push(' ');
362
363 // Handle different placeholder syntaxes based on database driver
364 match driver {
365 // PostgreSQL uses numbered placeholders: $1, $2, $3, ...
366 Drivers::Postgres => {
367 query.push_str(&format!("${}", arg_counter));
368 *arg_counter += 1;
369 }
370 // MySQL and SQLite use question mark placeholders: ?
371 _ => query.push('?'),
372 }
373
374 // Bind the value to the query
375 let _ = args.add(value.clone());
376 });
377
378 self.where_clauses.push(clause);
379 self
380 }
381
382 /// Adds a WHERE IN (SUBQUERY) clause to the query.
383 ///
384 /// This allows for filtering a column based on the results of another query.
385 ///
386 /// # Example
387 /// ```rust,ignore
388 /// let subquery = db.model::<Post>().select("user_id").filter("views", ">", 1000);
389 /// db.model::<User>().filter_subquery("id", Op::In, subquery).scan().await?;
390 /// ```
391 pub fn filter_subquery<S, SE>(mut self, col: &'static str, op: Op, mut subquery: QueryBuilder<S, SE>) -> Self
392 where
393 S: Model + Send + Sync + Unpin + AnyImpl + 'static,
394 SE: Connection + 'static,
395 {
396 subquery.apply_soft_delete_filter();
397 let table_id = self.get_table_identifier();
398 let is_main_col = self.columns.contains(&col.to_snake_case());
399 let op_str = op.as_sql();
400
401 let clause: FilterFn = Box::new(move |query, args, _driver, arg_counter| {
402 query.push_str(" AND ");
403 if let Some((table, column)) = col.split_once(".") {
404 query.push_str(&format!("\"{}\".\"{}\"", table, column));
405 } else if is_main_col {
406 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
407 } else {
408 query.push_str(&format!("\"{}\"", col));
409 }
410 query.push_str(&format!(" {} (", op_str));
411
412 subquery.write_select_sql::<S>(query, args, arg_counter);
413 query.push_str(")");
414 });
415
416 self.where_clauses.push(clause);
417 self
418 }
419
420 /// Truncates the table associated with this Model.
421 ///
422 /// Uses TRUNCATE TABLE for Postgres/MySQL and DELETE FROM for SQLite.
423 ///
424 /// # Returns
425 /// * `Ok(())` on success
426 /// * `Err(sqlx::Error)` on database failure
427 pub async fn truncate(self) -> Result<(), sqlx::Error> {
428 let table_name = self.table_name.to_snake_case();
429 let query = match self.driver {
430 Drivers::Postgres | Drivers::MySQL => format!("TRUNCATE TABLE \"{}\"", table_name),
431 Drivers::SQLite => format!("DELETE FROM \"{}\"", table_name),
432 };
433
434 if self.debug_mode {
435 log::debug!("SQL: {}", query);
436 }
437
438 self.tx.execute(&query, AnyArguments::default()).await?;
439
440 // For SQLite, reset auto-increment if exists
441 if matches!(self.driver, Drivers::SQLite) {
442 let _ = self.tx.execute(&format!("DELETE FROM sqlite_sequence WHERE name='{}'", table_name), AnyArguments::default()).await;
443 }
444
445 Ok(())
446 }
447
448 /// Combines the results of this query with another query using UNION.
449 pub fn union(self, other: QueryBuilder<T, E>) -> Self where T: AnyImpl + 'static, E: 'static {
450 self.union_internal("UNION", other)
451 }
452
453 /// Combines the results of this query with another query using UNION ALL.
454 pub fn union_all(self, other: QueryBuilder<T, E>) -> Self where T: AnyImpl + 'static, E: 'static {
455 self.union_internal("UNION ALL", other)
456 }
457
458 fn union_internal(mut self, op: &str, mut other: QueryBuilder<T, E>) -> Self where T: AnyImpl + 'static, E: 'static {
459 other.apply_soft_delete_filter();
460 let op_owned = op.to_string();
461
462 self.union_clauses.push((op_owned.clone(), Box::new(move |query: &mut String, args: &mut AnyArguments<'_>, _driver: &Drivers, arg_counter: &mut usize| {
463 query.push_str(" ");
464 query.push_str(&op_owned);
465 query.push_str(" ");
466 other.write_select_sql::<T>(query, args, arg_counter);
467 })));
468 self
469 }
470
471 /// Internal helper to write the SELECT SQL to a string buffer.
472 pub(crate) fn write_select_sql<R: AnyImpl>(
473 &self,
474 query: &mut String,
475 args: &mut AnyArguments,
476 arg_counter: &mut usize,
477 ) {
478 query.push_str("SELECT ");
479
480 if self.is_distinct {
481 query.push_str("DISTINCT ");
482 }
483
484 query.push_str(&self.select_args_sql::<R>().join(", "));
485
486 // Build FROM clause
487 query.push_str(" FROM \"");
488 query.push_str(&self.table_name.to_snake_case());
489 query.push_str("\" ");
490 if let Some(alias) = &self.alias {
491 query.push_str(&format!("{} ", alias));
492 }
493
494 if !self.joins_clauses.is_empty() {
495 for join_clause in &self.joins_clauses {
496 query.push(' ');
497 join_clause(query, args, &self.driver, arg_counter);
498 }
499 }
500
501 query.push_str(" WHERE 1=1");
502
503 // Apply WHERE clauses
504 for clause in &self.where_clauses {
505 clause(query, args, &self.driver, arg_counter);
506 }
507
508 // Apply GROUP BY
509 if !self.group_by_clauses.is_empty() {
510 query.push_str(&format!(" GROUP BY {}", self.group_by_clauses.join(", ")));
511 }
512
513 // Apply HAVING
514 if !self.having_clauses.is_empty() {
515 query.push_str(" HAVING 1=1");
516 for clause in &self.having_clauses {
517 clause(query, args, &self.driver, arg_counter);
518 }
519 }
520
521 // Apply ORDER BY clauses
522 if !self.order_clauses.is_empty() {
523 query.push_str(&format!(" ORDER BY {}", self.order_clauses.join(", ")));
524 }
525
526 // Apply LIMIT clause
527 if let Some(limit) = self.limit {
528 query.push_str(" LIMIT ");
529 match self.driver {
530 Drivers::Postgres => {
531 query.push_str(&format!("${}", arg_counter));
532 *arg_counter += 1;
533 }
534 _ => query.push('?'),
535 }
536 let _ = args.add(limit as i64);
537 }
538
539 // Apply OFFSET clause
540 if let Some(offset) = self.offset {
541 query.push_str(" OFFSET ");
542 match self.driver {
543 Drivers::Postgres => {
544 query.push_str(&format!("${}", arg_counter));
545 *arg_counter += 1;
546 }
547 _ => query.push('?'),
548 }
549 let _ = args.add(offset as i64);
550 }
551
552 // Apply UNION clauses
553 for (_op, clause) in &self.union_clauses {
554 clause(query, args, &self.driver, arg_counter);
555 }
556 }
557
558 /// Adds a WHERE clause to the query.
559 ///
560 /// This method adds a filter condition to the query. Multiple filters can be chained
561 /// and will be combined with AND operators. The value is bound as a parameter to
562 /// prevent SQL injection.
563 ///
564 /// # Type Parameters
565 ///
566 /// * `V` - The type of the value to filter by. Must be encodable for SQL queries.
567 ///
568 /// # Arguments
569 ///
570 /// * `col` - The column name to filter on
571 /// * `op` - The comparison operator (e.g., "=", ">", "LIKE", "IN")
572 /// * `value` - The value to compare against
573 ///
574 /// # Example
575 ///
576 /// ```rust,ignore
577 /// query.filter("age", Op::Gte, 18)
578 /// ```
579 pub fn filter<V>(self, col: &'static str, op: Op, value: V) -> Self
580 where
581 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
582 {
583 self.filter_internal(" AND ", col, op, value)
584 }
585
586 /// Adds an OR WHERE clause to the query.
587 ///
588 /// # Example
589 ///
590 /// ```rust,ignore
591 /// query.filter("age", Op::Lt, 18).or_filter("active", Op::Eq, false)
592 /// ```
593 pub fn or_filter<V>(self, col: &'static str, op: Op, value: V) -> Self
594 where
595 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
596 {
597 self.filter_internal(" OR ", col, op, value)
598 }
599
600 /// Adds an AND NOT WHERE clause to the query.
601 pub fn not_filter<V>(self, col: &'static str, op: Op, value: V) -> Self
602 where
603 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
604 {
605 self.filter_internal(" AND NOT ", col, op, value)
606 }
607
608 /// Adds an OR NOT WHERE clause to the query.
609 pub fn or_not_filter<V>(self, col: &'static str, op: Op, value: V) -> Self
610 where
611 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
612 {
613 self.filter_internal(" OR NOT ", col, op, value)
614 }
615
616 /// Adds a BETWEEN clause to the query.
617 ///
618 /// # Arguments
619 ///
620 /// * `col` - The column name
621 /// * `start` - The start value of the range
622 /// * `end` - The end value of the range
623 ///
624 /// # Example
625 ///
626 /// ```rust,ignore
627 /// query.between("age", 18, 30)
628 /// // SQL: AND "age" BETWEEN 18 AND 30
629 /// ```
630 pub fn between<V>(mut self, col: &'static str, start: V, end: V) -> Self
631 where
632 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
633 {
634 let table_id = self.get_table_identifier();
635 let is_main_col = self.columns.contains(&col.to_snake_case());
636 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
637 query.push_str(" AND ");
638 if let Some((table, column)) = col.split_once(".") {
639 query.push_str(&format!("\"{}\".\"{}\"", table, column));
640 } else if is_main_col {
641 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
642 } else {
643 query.push_str(&format!("\"{}\"", col));
644 }
645 query.push_str(" BETWEEN ");
646
647 match driver {
648 Drivers::Postgres => {
649 query.push_str(&format!("${} AND ${}", arg_counter, *arg_counter + 1));
650 *arg_counter += 2;
651 }
652 _ => query.push_str("? AND ?"),
653 }
654
655 let _ = args.add(start.clone());
656 let _ = args.add(end.clone());
657 });
658 self.where_clauses.push(clause);
659 self
660 }
661
662 /// Adds an OR BETWEEN clause to the query.
663 ///
664 /// # Example
665 ///
666 /// ```rust,ignore
667 /// query.between("age", 18, 30).or_between("salary", 5000, 10000)
668 /// ```
669 pub fn or_between<V>(mut self, col: &'static str, start: V, end: V) -> Self
670 where
671 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
672 {
673 let table_id = self.get_table_identifier();
674 let is_main_col = self.columns.contains(&col.to_snake_case());
675 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
676 query.push_str(" OR ");
677 if let Some((table, column)) = col.split_once(".") {
678 query.push_str(&format!("\"{}\".\"{}\"", table, column));
679 } else if is_main_col {
680 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
681 } else {
682 query.push_str(&format!("\"{}\"", col));
683 }
684 query.push_str(" BETWEEN ");
685
686 match driver {
687 Drivers::Postgres => {
688 query.push_str(&format!("${} AND ${}", arg_counter, *arg_counter + 1));
689 *arg_counter += 2;
690 }
691 _ => query.push_str("? AND ?"),
692 }
693
694 let _ = args.add(start.clone());
695 let _ = args.add(end.clone());
696 });
697 self.where_clauses.push(clause);
698 self
699 }
700
701 /// Adds an IN list clause to the query.
702 ///
703 /// # Example
704 ///
705 /// ```rust,ignore
706 /// query.in_list("status", vec!["active", "pending"])
707 /// // SQL: AND "status" IN ('active', 'pending')
708 /// ```
709 pub fn in_list<V>(mut self, col: &'static str, values: Vec<V>) -> Self
710 where
711 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
712 {
713 if values.is_empty() {
714 // WHERE 1=0 to ensure empty result
715 let clause: FilterFn = Box::new(|query, _, _, _| {
716 query.push_str(" AND 1=0");
717 });
718 self.where_clauses.push(clause);
719 return self;
720 }
721
722 let table_id = self.get_table_identifier();
723 let is_main_col = self.columns.contains(&col.to_snake_case());
724 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
725 query.push_str(" AND ");
726 if let Some((table, column)) = col.split_once(".") {
727 query.push_str(&format!("\"{}\".\"{}\"", table, column));
728 } else if is_main_col {
729 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
730 } else {
731 query.push_str(&format!("\"{}\"", col));
732 }
733 query.push_str(" IN (");
734
735 let mut placeholders = Vec::new();
736 for _ in &values {
737 match driver {
738 Drivers::Postgres => {
739 placeholders.push(format!("${}", arg_counter));
740 *arg_counter += 1;
741 }
742 _ => placeholders.push("?".to_string()),
743 }
744 }
745 query.push_str(&placeholders.join(", "));
746 query.push(')');
747
748 for val in &values {
749 let _ = args.add(val.clone());
750 }
751 });
752 self.where_clauses.push(clause);
753 self
754 }
755
756 /// Adds an OR IN list clause to the query.
757 pub fn or_in_list<V>(mut self, col: &'static str, values: Vec<V>) -> Self
758 where
759 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
760 {
761 if values.is_empty() {
762 return self;
763 }
764
765 let table_id = self.get_table_identifier();
766 let is_main_col = self.columns.contains(&col.to_snake_case());
767 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
768 query.push_str(" OR ");
769 if let Some((table, column)) = col.split_once(".") {
770 query.push_str(&format!("\"{}\".\"{}\"", table, column));
771 } else if is_main_col {
772 query.push_str(&format!("\"{}\".\"{}\"", table_id, col));
773 } else {
774 query.push_str(&format!("\"{}\"", col));
775 }
776 query.push_str(" IN (");
777
778 let mut placeholders = Vec::new();
779 for _ in &values {
780 match driver {
781 Drivers::Postgres => {
782 placeholders.push(format!("${}", arg_counter));
783 *arg_counter += 1;
784 }
785 _ => placeholders.push("?".to_string()),
786 }
787 }
788 query.push_str(&placeholders.join(", "));
789 query.push(')');
790
791 for val in &values {
792 let _ = args.add(val.clone());
793 }
794 });
795 self.where_clauses.push(clause);
796 self
797 }
798
799 /// Groups filters inside parentheses with an AND operator.
800 pub fn group<F>(mut self, f: F) -> Self
801 where
802 F: FnOnce(Self) -> Self,
803 {
804 let old_clauses = std::mem::take(&mut self.where_clauses);
805 self = f(self);
806 let group_clauses = std::mem::take(&mut self.where_clauses);
807 self.where_clauses = old_clauses;
808
809 if !group_clauses.is_empty() {
810 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
811 query.push_str(" AND (1=1");
812 for c in &group_clauses {
813 c(query, args, driver, arg_counter);
814 }
815 query.push_str(")");
816 });
817 self.where_clauses.push(clause);
818 }
819 self
820 }
821
822 /// Groups filters inside parentheses with an OR operator.
823 pub fn or_group<F>(mut self, f: F) -> Self
824 where
825 F: FnOnce(Self) -> Self,
826 {
827 let old_clauses = std::mem::take(&mut self.where_clauses);
828 self = f(self);
829 let group_clauses = std::mem::take(&mut self.where_clauses);
830 self.where_clauses = old_clauses;
831
832 if !group_clauses.is_empty() {
833 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
834 query.push_str(" OR (1=1");
835 for c in &group_clauses {
836 c(query, args, driver, arg_counter);
837 }
838 query.push_str(")");
839 });
840 self.where_clauses.push(clause);
841 }
842 self
843 }
844
845 /// Adds a raw WHERE clause with a placeholder and a single value.
846 ///
847 /// This allows writing raw SQL conditions with a `?` placeholder.
848 /// To use multiple placeholders with different types, chain multiple `where_raw` calls.
849 ///
850 /// # Arguments
851 ///
852 /// * `sql` - Raw SQL string with one `?` placeholder (e.g., "age > ?")
853 /// * `value` - Value to bind
854 ///
855 /// # Example
856 ///
857 /// ```rust,ignore
858 /// db.model::<User>()
859 /// .where_raw("name = ?", "Alice".to_string())
860 /// .where_raw("age >= ?", 18)
861 /// .scan()
862 /// .await?;
863 /// ```
864 pub fn where_raw<V>(mut self, sql: &str, value: V) -> Self
865 where
866 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
867 {
868 self.where_clauses.push(self.create_raw_clause(" AND ", sql, value));
869 self
870 }
871
872 /// Adds a raw OR WHERE clause with a placeholder.
873 pub fn or_where_raw<V>(mut self, sql: &str, value: V) -> Self
874 where
875 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
876 {
877 self.where_clauses.push(self.create_raw_clause(" OR ", sql, value));
878 self
879 }
880
881 /// Internal helper to create a raw SQL clause with a single value.
882 fn create_raw_clause<V>(&self, joiner: &'static str, sql: &str, value: V) -> FilterFn
883 where
884 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
885 {
886 let sql_owned = sql.to_string();
887 Box::new(move |query, args, driver, arg_counter| {
888 query.push_str(joiner);
889
890 let mut processed_sql = sql_owned.clone();
891 if let Some(pos) = processed_sql.find('?') {
892 let placeholder = match driver {
893 Drivers::Postgres => {
894 let p = format!("${}", arg_counter);
895 *arg_counter += 1;
896 p
897 }
898 _ => "?".to_string(),
899 };
900 processed_sql.replace_range(pos..pos + 1, &placeholder);
901 }
902
903 query.push_str(&processed_sql);
904 let _ = args.add(value.clone());
905 })
906 }
907
908 /// Adds an equality filter to the query.
909 ///
910 /// This is a convenience wrapper around `filter()` for simple equality checks.
911 /// It is equivalent to calling `filter(col, "=", value)`.
912 ///
913 /// # Type Parameters
914 ///
915 /// * `V` - The type of the value to compare against.
916 ///
917 /// # Arguments
918 ///
919 /// * `col` - The column name to filter on.
920 /// * `value` - The value to match.
921 ///
922 /// # Example
923 ///
924 /// ```rust,ignore
925 /// // Equivalent to filter("age", Op::Eq, 18)
926 /// query.equals("age", 18)
927 /// ```
928 pub fn equals<V>(self, col: &'static str, value: V) -> Self
929 where
930 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
931 {
932 self.filter(col, Op::Eq, value)
933 }
934
935 /// Adds an ORDER BY clause to the query.
936 ///
937 /// Specifies the sort order for the query results. Multiple order clauses
938 /// can be added and will be applied in the order they were added.
939 ///
940 /// # Arguments
941 ///
942 /// * `order` - The ORDER BY expression (e.g., "created_at DESC", "age ASC, name DESC")
943 ///
944 /// # Example
945 ///
946 /// ```rust,ignore
947 /// // Single column ascending (ASC is default)
948 /// query.order("age")
949 ///
950 /// // Single column descending
951 /// query.order("created_at DESC")
952 ///
953 /// // Multiple columns
954 /// query.order("age DESC, username ASC")
955 ///
956 /// // Chain multiple order clauses
957 /// query
958 /// .order("priority DESC")
959 /// .order("created_at ASC")
960 /// ```
961 pub fn order(mut self, order: &str) -> Self {
962 self.order_clauses.push(order.to_string());
963 self
964 }
965
966 /// Defines a SQL alias for the primary table in the query.
967 ///
968 /// This method allows you to set a short alias for the model's underlying table.
969 /// It is highly recommended when writing complex queries with multiple `JOIN` clauses,
970 /// preventing the need to repeat the full table name in `.filter()`, `.equals()`, or `.select()`.
971 ///
972 /// # Arguments
973 ///
974 /// * `alias` - A string slice representing the alias to be used (e.g., "u", "rp").
975 ///
976 /// # Example
977 ///
978 /// ```rust,ignore
979 /// // Using 'u' as an alias for the User table
980 /// let results = db.model::<User>()
981 /// .alias("u")
982 /// .join("role_permissions rp", "rp.role_id = u.role")
983 /// .equals("u.id", user_id)
984 /// .select("u.username, rp.permission_id")
985 /// .scan_as::<UserPermissionDTO>()
986 /// .await?;
987 /// ```
988 pub fn alias(mut self, alias: &str) -> Self {
989 self.alias = Some(alias.to_string());
990 self
991 }
992
993 /// Placeholder for eager loading relationships (preload).
994 ///
995 /// This method is reserved for future implementation of relationship preloading.
996 /// Currently, it returns `self` unchanged to maintain the fluent interface.
997 ///
998 /// # Future Implementation
999 ///
1000 /// Will support eager loading of related models to avoid N+1 query problems:
1001 ///
1002 /// ```rust,ignore
1003 /// // Future usage example
1004 /// query.preload("posts").preload("comments")
1005 /// ```
1006 // pub fn preload(self) -> Self {
1007 // // TODO: Implement relationship preloading
1008 // self
1009 // }
1010
1011 /// Activates debug mode for this query.
1012 ///
1013 /// When enabled, the generated SQL query will be logged using the `log` crate
1014 /// at the `DEBUG` level before execution.
1015 ///
1016 /// # Note
1017 ///
1018 /// To see the output, you must initialize a logger in your application (e.g., using `env_logger`)
1019 /// and configure it to display `debug` logs for `bottle_orm`.
1020 ///
1021 /// # Example
1022 ///
1023 /// ```rust,ignore
1024 /// db.model::<User>()
1025 /// .filter("active", "=", true)
1026 /// .debug() // Logs SQL: SELECT * FROM "user" WHERE "active" = $1
1027 /// .scan()
1028 /// .await?;
1029 /// ```
1030 pub fn debug(mut self) -> Self {
1031 self.debug_mode = true;
1032 self
1033 }
1034
1035 /// Adds an IS NULL filter for the specified column.
1036 ///
1037 /// # Arguments
1038 ///
1039 /// * `col` - The column name to check for NULL
1040 ///
1041 /// # Example
1042 ///
1043 /// ```rust,ignore
1044 /// db.model::<User>()
1045 /// .is_null("deleted_at")
1046 /// .scan()
1047 /// .await?;
1048 /// // SQL: SELECT * FROM "user" WHERE "deleted_at" IS NULL
1049 /// ```
1050 pub fn is_null(mut self, col: &str) -> Self {
1051 let col_owned = col.to_string();
1052 let table_id = self.get_table_identifier();
1053 let is_main_col = self.columns.contains(&col_owned.to_snake_case());
1054 let clause: FilterFn = Box::new(move |query, _args, _driver, _arg_counter| {
1055 query.push_str(" AND ");
1056 if let Some((table, column)) = col_owned.split_once(".") {
1057 query.push_str(&format!("\"{}\".\"{}\"", table, column));
1058 } else if is_main_col {
1059 query.push_str(&format!("\"{}\".\"{}\"", table_id, col_owned));
1060 } else {
1061 query.push_str(&format!("\"{}\"", col_owned));
1062 }
1063 query.push_str(" IS NULL");
1064 });
1065 self.where_clauses.push(clause);
1066 self
1067 }
1068
1069 /// Adds an IS NOT NULL filter for the specified column.
1070 ///
1071 /// # Arguments
1072 ///
1073 /// * `col` - The column name to check for NOT NULL
1074 ///
1075 /// # Example
1076 ///
1077 /// ```rust,ignore
1078 /// db.model::<User>()
1079 /// .is_not_null("email")
1080 /// .scan()
1081 /// .await?;
1082 /// // SQL: SELECT * FROM "user" WHERE "email" IS NOT NULL
1083 /// ```
1084 pub fn is_not_null(mut self, col: &str) -> Self {
1085 let col_owned = col.to_string();
1086 let table_id = self.get_table_identifier();
1087 let is_main_col = self.columns.contains(&col_owned.to_snake_case());
1088 let clause: FilterFn = Box::new(move |query, _args, _driver, _arg_counter| {
1089 query.push_str(" AND ");
1090 if let Some((table, column)) = col_owned.split_once(".") {
1091 query.push_str(&format!("\"{}\".\"{}\"", table, column));
1092 } else if is_main_col {
1093 query.push_str(&format!("\"{}\".\"{}\"", table_id, col_owned));
1094 } else {
1095 query.push_str(&format!("\"{}\"", col_owned));
1096 }
1097 query.push_str(" IS NOT NULL");
1098 });
1099 self.where_clauses.push(clause);
1100 self
1101 }
1102
1103 /// Includes soft-deleted records in query results.
1104 ///
1105 /// By default, queries on models with a `#[orm(soft_delete)]` column exclude
1106 /// records where that column is not NULL. This method disables that filter.
1107 ///
1108 /// # Example
1109 ///
1110 /// ```rust,ignore
1111 /// // Get all users including deleted ones
1112 /// db.model::<User>()
1113 /// .with_deleted()
1114 /// .scan()
1115 /// .await?;
1116 /// ```
1117 pub fn with_deleted(mut self) -> Self {
1118 self.with_deleted = true;
1119 self
1120 }
1121
1122 /// Placeholder for JOIN operations.
1123 ///
1124 /// This method is reserved for future implementation of SQL JOINs.
1125 /// Currently, it returns `self` unchanged to maintain the fluent interface.
1126 ///
1127 /// # Future Implementation
1128 ///
1129 /// Will support various types of JOINs (INNER, LEFT, RIGHT, FULL):
1130 ///
1131 /// ```rust,ignore
1132 /// Adds a JOIN clause to the query.
1133 ///
1134 /// # Arguments
1135 ///
1136 /// * `table` - The name of the table to join.
1137 /// * `s_query` - The ON clause condition (e.g., "users.id = posts.user_id").
1138 ///
1139 /// # Example
1140 ///
1141 /// ```rust,ignore
1142 /// query.join("posts", "users.id = posts.user_id")
1143 /// ```
1144 pub fn join(self, table: &str, s_query: &str) -> Self {
1145 self.join_generic("", table, s_query)
1146 }
1147
1148 /// Internal helper for specific join types
1149 fn join_generic(mut self, join_type: &str, table: &str, s_query: &str) -> Self {
1150 let table_owned = table.to_string();
1151 let join_type_owned = join_type.to_string();
1152
1153 let trimmed_value = s_query.replace(" ", "");
1154 let values = trimmed_value.split_once("=");
1155 let mut parsed_query = s_query.to_string();
1156
1157 if let Some((first, second)) = values {
1158 // Try to parse table.column = table.column
1159 if let Some((t1, c1)) = first.split_once('.') {
1160 if let Some((t2, c2)) = second.split_once('.') {
1161 parsed_query = format!("\"{}\".\"{}\" = \"{}\".\"{}\"", t1, c1, t2, c2);
1162 }
1163 }
1164 }
1165
1166 if let Some((table_name, alias)) = table.split_once(" ") {
1167 self.join_aliases.insert(table_name.to_snake_case(), alias.to_string());
1168 } else {
1169 self.join_aliases.insert(table.to_snake_case(), table.to_string());
1170 }
1171
1172 self.joins_clauses.push(Box::new(move |query, _args, _driver, _arg_counter| {
1173 if let Some((table_name, alias)) = table_owned.split_once(" ") {
1174 query.push_str(&format!("{} JOIN \"{}\" {} ON {}", join_type_owned, table_name, alias, parsed_query));
1175 } else {
1176 query.push_str(&format!("{} JOIN \"{}\" ON {}", join_type_owned, table_owned, parsed_query));
1177 }
1178 }));
1179 self
1180 }
1181
1182 /// Adds a raw JOIN clause with a placeholder and a bound value.
1183 ///
1184 /// This is useful for joining tables with conditions that involve external values.
1185 ///
1186 /// # Example
1187 ///
1188 /// ```rust,ignore
1189 /// db.model::<Permissions>()
1190 /// .join_raw("role_permissions rp", "rp.role_id = ?", role_id)
1191 /// .scan()
1192 /// .await?;
1193 /// ```
1194 pub fn join_raw<V>(self, table: &str, on: &str, value: V) -> Self
1195 where
1196 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1197 {
1198 self.join_generic_raw("", table, on, value)
1199 }
1200
1201 /// Adds a raw LEFT JOIN clause with a placeholder and a bound value.
1202 ///
1203 /// # Example
1204 ///
1205 /// ```rust,ignore
1206 /// query.left_join_raw("posts", "posts.user_id = ?", user_id)
1207 /// ```
1208 pub fn left_join_raw<V>(self, table: &str, on: &str, value: V) -> Self
1209 where
1210 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1211 {
1212 self.join_generic_raw("LEFT", table, on, value)
1213 }
1214
1215 /// Adds a raw RIGHT JOIN clause with a placeholder and a bound value.
1216 ///
1217 /// # Example
1218 ///
1219 /// ```rust,ignore
1220 /// query.right_join_raw("users", "users.id = ?", user_id)
1221 /// ```
1222 pub fn right_join_raw<V>(self, table: &str, on: &str, value: V) -> Self
1223 where
1224 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1225 {
1226 self.join_generic_raw("RIGHT", table, on, value)
1227 }
1228
1229 /// Adds a raw INNER JOIN clause with a placeholder and a bound value.
1230 ///
1231 /// # Example
1232 ///
1233 /// ```rust,ignore
1234 /// query.inner_join_raw("accounts", "accounts.user_id = ?", user_id)
1235 /// ```
1236 pub fn inner_join_raw<V>(self, table: &str, on: &str, value: V) -> Self
1237 where
1238 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1239 {
1240 self.join_generic_raw("INNER", table, on, value)
1241 }
1242
1243 /// Adds a raw FULL JOIN clause with a placeholder and a bound value.
1244 ///
1245 /// # Example
1246 ///
1247 /// ```rust,ignore
1248 /// query.full_join_raw("profiles", "profiles.user_id = ?", user_id)
1249 /// ```
1250 pub fn full_join_raw<V>(self, table: &str, on: &str, value: V) -> Self
1251 where
1252 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1253 {
1254 self.join_generic_raw("FULL", table, on, value)
1255 }
1256
1257 /// Internal helper for raw join types
1258 fn join_generic_raw<V>(mut self, join_type: &str, table: &str, on: &str, value: V) -> Self
1259 where
1260 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1261 {
1262 let table_owned = table.to_string();
1263 let on_owned = on.to_string();
1264 let join_type_owned = join_type.to_string();
1265
1266 if let Some((table_name, alias)) = table.split_once(" ") {
1267 self.join_aliases.insert(table_name.to_snake_case(), alias.to_string());
1268 } else {
1269 self.join_aliases.insert(table.to_snake_case(), table.to_string());
1270 }
1271
1272 self.joins_clauses.push(Box::new(move |query, args, driver, arg_counter| {
1273 if let Some((table_name, alias)) = table_owned.split_once(" ") {
1274 query.push_str(&format!("{} JOIN \"{}\" {} ON ", join_type_owned, table_name, alias));
1275 } else {
1276 query.push_str(&format!("{} JOIN \"{}\" ON ", join_type_owned, table_owned));
1277 }
1278
1279 let mut processed_on = on_owned.clone();
1280 if let Some(pos) = processed_on.find('?') {
1281 let placeholder = match driver {
1282 Drivers::Postgres => {
1283 let p = format!("${}", arg_counter);
1284 *arg_counter += 1;
1285 p
1286 }
1287 _ => "?".to_string(),
1288 };
1289 processed_on.replace_range(pos..pos + 1, &placeholder);
1290 }
1291
1292 query.push_str(&processed_on);
1293 let _ = args.add(value.clone());
1294 }));
1295 self
1296 }
1297
1298 /// Adds a LEFT JOIN clause.
1299 ///
1300 /// Performs a LEFT JOIN with another table. Returns all records from the left table,
1301 /// and the matched records from the right table (or NULL if no match).
1302 ///
1303 /// # Arguments
1304 ///
1305 /// * `table` - The name of the table to join with
1306 /// * `on` - The join condition (e.g., "users.id = posts.user_id")
1307 ///
1308 /// # Example
1309 ///
1310 /// ```rust,ignore
1311 /// // Get all users and their posts (if any)
1312 /// let users_with_posts = db.model::<User>()
1313 /// .left_join("posts", "users.id = posts.user_id")
1314 /// .scan()
1315 /// .await?;
1316 /// ```
1317 pub fn left_join(self, table: &str, on: &str) -> Self {
1318 self.join_generic("LEFT", table, on)
1319 }
1320
1321 /// Adds a RIGHT JOIN clause.
1322 ///
1323 /// Performs a RIGHT JOIN with another table. Returns all records from the right table,
1324 /// and the matched records from the left table (or NULL if no match).
1325 ///
1326 /// # Arguments
1327 ///
1328 /// * `table` - The name of the table to join with
1329 /// * `on` - The join condition
1330 ///
1331 /// # Example
1332 ///
1333 /// ```rust,ignore
1334 /// let posts_with_users = db.model::<Post>()
1335 /// .right_join("users", "posts.user_id = users.id")
1336 /// .scan()
1337 /// .await?;
1338 /// ```
1339 pub fn right_join(self, table: &str, on: &str) -> Self {
1340 self.join_generic("RIGHT", table, on)
1341 }
1342
1343 /// Adds an INNER JOIN clause.
1344 ///
1345 /// Performs an INNER JOIN with another table. Returns records that have matching
1346 /// values in both tables.
1347 ///
1348 /// # Arguments
1349 ///
1350 /// * `table` - The name of the table to join with
1351 /// * `on` - The join condition
1352 ///
1353 /// # Example
1354 ///
1355 /// ```rust,ignore
1356 /// // Get only users who have posts
1357 /// let active_users = db.model::<User>()
1358 /// .inner_join("posts", "users.id = posts.user_id")
1359 /// .scan()
1360 /// .await?;
1361 /// ```
1362 pub fn inner_join(self, table: &str, on: &str) -> Self {
1363 self.join_generic("INNER", table, on)
1364 }
1365
1366 /// Adds a FULL JOIN clause.
1367 ///
1368 /// Performs a FULL OUTER JOIN. Returns all records when there is a match in
1369 /// either left or right table.
1370 ///
1371 /// # Arguments
1372 ///
1373 /// * `table` - The name of the table to join with
1374 /// * `on` - The join condition
1375 ///
1376 /// # Example
1377 ///
1378 /// ```rust,ignore
1379 /// query.full_join("profiles", "profiles.user_id = users.id")
1380 /// ```
1381 pub fn full_join(self, table: &str, on: &str) -> Self {
1382 self.join_generic("FULL", table, on)
1383 }
1384
1385 /// Marks the query to return DISTINCT results.
1386 ///
1387 /// Adds the `DISTINCT` keyword to the SELECT statement, ensuring that unique
1388 /// rows are returned.
1389 ///
1390 /// # Example
1391 ///
1392 /// ```rust,ignore
1393 /// // Get unique ages of users
1394 /// let unique_ages: Vec<i32> = db.model::<User>()
1395 /// .select("age")
1396 /// .distinct()
1397 /// .scan()
1398 /// .await?;
1399 /// ```
1400 pub fn distinct(mut self) -> Self {
1401 self.is_distinct = true;
1402 self
1403 }
1404
1405 /// Adds a GROUP BY clause to the query.
1406 ///
1407 /// Groups rows that have the same values into summary rows. Often used with
1408 /// aggregate functions (COUNT, MAX, MIN, SUM, AVG).
1409 ///
1410 /// # Arguments
1411 ///
1412 /// * `columns` - Comma-separated list of columns to group by
1413 ///
1414 /// # Example
1415 ///
1416 /// ```rust,ignore
1417 /// // Count users by age group
1418 /// let stats: Vec<(i32, i64)> = db.model::<User>()
1419 /// .select("age, COUNT(*)")
1420 /// .group_by("age")
1421 /// .scan()
1422 /// .await?;
1423 /// ```
1424 pub fn group_by(mut self, columns: &str) -> Self {
1425 self.group_by_clauses.push(columns.to_string());
1426 self
1427 }
1428
1429 /// Adds a HAVING clause to the query.
1430 ///
1431 /// Used to filter groups created by `group_by`. Similar to `filter` (WHERE),
1432 /// but operates on grouped records and aggregate functions.
1433 ///
1434 /// # Arguments
1435 ///
1436 /// * `col` - The column or aggregate function to filter on
1437 /// * `op` - Comparison operator
1438 /// * `value` - Value to compare against
1439 ///
1440 /// # Example
1441 ///
1442 /// ```rust,ignore
1443 /// // Get ages with more than 5 users
1444 /// let popular_ages = db.model::<User>()
1445 /// .select("age, COUNT(*)")
1446 /// .group_by("age")
1447 /// .having("COUNT(*)", Op::Gt, 5)
1448 /// .scan()
1449 /// .await?;
1450 /// ```
1451 pub fn having<V>(mut self, col: &'static str, op: Op, value: V) -> Self
1452 where
1453 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
1454 {
1455 let op_str = op.as_sql();
1456 let clause: FilterFn = Box::new(move |query, args, driver, arg_counter| {
1457 query.push_str(" AND ");
1458 query.push_str(col);
1459 query.push(' ');
1460 query.push_str(op_str);
1461 query.push(' ');
1462
1463 match driver {
1464 Drivers::Postgres => {
1465 query.push_str(&format!("${}", arg_counter));
1466 *arg_counter += 1;
1467 }
1468 _ => query.push('?'),
1469 }
1470 let _ = args.add(value.clone());
1471 });
1472
1473 self.having_clauses.push(clause);
1474 self
1475 }
1476
1477 /// Returns the COUNT of rows matching the query.
1478 ///
1479 /// A convenience method that automatically sets `SELECT COUNT(*)` and returns
1480 /// the result as an `i64`.
1481 ///
1482 /// # Returns
1483 ///
1484 /// * `Ok(i64)` - The count of rows
1485 /// * `Err(sqlx::Error)` - Database error
1486 ///
1487 /// # Example
1488 ///
1489 /// ```rust,ignore
1490 /// let user_count = db.model::<User>().count().await?;
1491 /// ```
1492 pub async fn count(mut self) -> Result<i64, sqlx::Error> {
1493 self.select_columns = vec!["COUNT(*)".to_string()];
1494 self.scalar::<i64>().await
1495 }
1496
1497 /// Returns the SUM of the specified column.
1498 ///
1499 /// Calculates the sum of a numeric column.
1500 ///
1501 /// # Arguments
1502 ///
1503 /// * `column` - The column to sum
1504 ///
1505 /// # Example
1506 ///
1507 /// ```rust,ignore
1508 /// let total_age: i64 = db.model::<User>().sum("age").await?;
1509 /// ```
1510 pub async fn sum<N>(mut self, column: &str) -> Result<N, sqlx::Error>
1511 where
1512 N: FromAnyRow + AnyImpl + for<'r> Decode<'r, Any> + Type<Any> + Send + Unpin,
1513 {
1514 self.select_columns = vec![format!("SUM({})", column)];
1515 self.scalar::<N>().await
1516 }
1517
1518 /// Returns the AVG of the specified column.
1519 ///
1520 /// Calculates the average value of a numeric column.
1521 ///
1522 /// # Arguments
1523 ///
1524 /// * `column` - The column to average
1525 ///
1526 /// # Example
1527 ///
1528 /// ```rust,ignore
1529 /// let avg_age: f64 = db.model::<User>().avg("age").await?;
1530 /// ```
1531 pub async fn avg<N>(mut self, column: &str) -> Result<N, sqlx::Error>
1532 where
1533 N: FromAnyRow + AnyImpl + for<'r> Decode<'r, Any> + Type<Any> + Send + Unpin,
1534 {
1535 self.select_columns = vec![format!("AVG({})", column)];
1536 self.scalar::<N>().await
1537 }
1538
1539 /// Returns the MIN of the specified column.
1540 ///
1541 /// Finds the minimum value in a column.
1542 ///
1543 /// # Arguments
1544 ///
1545 /// * `column` - The column to check
1546 ///
1547 /// # Example
1548 ///
1549 /// ```rust,ignore
1550 /// let min_age: i32 = db.model::<User>().min("age").await?;
1551 /// ```
1552 pub async fn min<N>(mut self, column: &str) -> Result<N, sqlx::Error>
1553 where
1554 N: FromAnyRow + AnyImpl + for<'r> Decode<'r, Any> + Type<Any> + Send + Unpin,
1555 {
1556 self.select_columns = vec![format!("MIN({})", column)];
1557 self.scalar::<N>().await
1558 }
1559
1560 /// Returns the MAX of the specified column.
1561 ///
1562 /// Finds the maximum value in a column.
1563 ///
1564 /// # Arguments
1565 ///
1566 /// * `column` - The column to check
1567 ///
1568 /// # Example
1569 ///
1570 /// ```rust,ignore
1571 /// let max_age: i32 = db.model::<User>().max("age").await?;
1572 /// ```
1573 pub async fn max<N>(mut self, column: &str) -> Result<N, sqlx::Error>
1574 where
1575 N: FromAnyRow + AnyImpl + for<'r> Decode<'r, Any> + Type<Any> + Send + Unpin,
1576 {
1577 self.select_columns = vec![format!("MAX({})", column)];
1578 self.scalar::<N>().await
1579 }
1580
1581 /// Applies pagination with validation and limits.
1582 ///
1583 /// This is a convenience method that combines `limit()` and `offset()` with
1584 /// built-in validation and maximum value enforcement for safer pagination.
1585 ///
1586 /// # Arguments
1587 ///
1588 /// * `max_value` - Maximum allowed items per page
1589 /// * `default` - Default value if `value` exceeds `max_value`
1590 /// * `page` - Zero-based page number
1591 /// * `value` - Requested items per page
1592 ///
1593 /// # Returns
1594 ///
1595 /// * `Ok(Self)` - The updated QueryBuilder with pagination applied
1596 /// * `Err(Error)` - If `value` is negative
1597 ///
1598 /// # Pagination Logic
1599 ///
1600 /// 1. Validates that `value` is non-negative
1601 /// 2. If `value` > `max_value`, uses `default` instead
1602 /// 3. Calculates offset as: `value * page`
1603 /// 4. Sets limit to `value`
1604 ///
1605 /// # Example
1606 ///
1607 /// ```rust,ignore
1608 /// // Page 0 with 10 items (page 1 in 1-indexed systems)
1609 /// query.pagination(100, 20, 0, 10)? // LIMIT 10 OFFSET 0
1610 ///
1611 /// // Page 2 with 25 items (page 3 in 1-indexed systems)
1612 /// query.pagination(100, 20, 2, 25)? // LIMIT 25 OFFSET 50
1613 ///
1614 /// // Request too many items, falls back to default
1615 /// query.pagination(100, 20, 0, 150)? // LIMIT 20 OFFSET 0 (150 > 100)
1616 ///
1617 /// // Error: negative value
1618 /// query.pagination(100, 20, 0, -10)? // Returns Error
1619 /// ```
1620 pub fn pagination(mut self, max_value: usize, default: usize, page: usize, value: isize) -> Result<Self, Error> {
1621 // Validate that value is non-negative
1622 if value < 0 {
1623 return Err(Error::InvalidArgument("value cannot be negative".into()));
1624 }
1625
1626 let mut f_value = value as usize;
1627
1628 // Enforce maximum value limit
1629 if f_value > max_value {
1630 f_value = default;
1631 }
1632
1633 // Apply offset and limit
1634 self = self.offset(f_value * page);
1635 self = self.limit(f_value);
1636
1637 Ok(self)
1638 }
1639
1640 /// Selects specific columns to return.
1641 ///
1642 /// By default, queries use `SELECT *` to return all columns. This method
1643 /// allows you to specify exactly which columns should be returned.
1644 ///
1645 /// **Note:** Columns are pushed exactly as provided, without automatic
1646 /// snake_case conversion, allowing for aliases and raw SQL fragments.
1647 ///
1648 /// # Arguments
1649 ///
1650 /// * `columns` - Comma-separated list of column names to select
1651 ///
1652 /// # Example
1653 ///
1654 /// ```rust,ignore
1655 /// // Select single column
1656 /// query.select("id")
1657 ///
1658 /// // Select multiple columns
1659 /// query.select("id, username, email")
1660 ///
1661 /// // Select with SQL functions and aliases (now supported)
1662 /// query.select("COUNT(*) as total_count")
1663 /// ```
1664 pub fn select(mut self, columns: &str) -> Self {
1665 self.select_columns.push(columns.to_string());
1666 self
1667 }
1668
1669 /// Excludes specific columns from the query results.
1670 ///
1671 /// This is the inverse of `select()`. Instead of specifying which columns to include,
1672 /// you specify which columns to exclude. All other columns will be returned.
1673 ///
1674 /// # Arguments
1675 ///
1676 /// * `columns` - Comma-separated list of column names to exclude
1677 ///
1678 /// # Priority
1679 ///
1680 /// If both `select()` and `omit()` are used, `select()` takes priority.
1681 ///
1682 /// # Example
1683 ///
1684 /// ```rust,ignore
1685 /// // Exclude password from results
1686 /// let user = db.model::<User>()
1687 /// .omit("password")
1688 /// .first()
1689 /// .await?;
1690 ///
1691 /// // Exclude multiple fields
1692 /// let user = db.model::<User>()
1693 /// .omit("password, secret_token")
1694 /// .first()
1695 /// .await?;
1696 ///
1697 /// // Using with generated field constants (autocomplete support)
1698 /// let user = db.model::<User>()
1699 /// .omit(user_fields::PASSWORD)
1700 /// .first()
1701 /// .await?;
1702 /// ```
1703 pub fn omit(mut self, columns: &str) -> Self {
1704 for col in columns.split(',') {
1705 self.omit_columns.push(col.trim().to_snake_case());
1706 }
1707 self
1708 }
1709
1710 /// Sets the query offset (pagination).
1711 ///
1712 /// Specifies the number of rows to skip before starting to return rows.
1713 /// Commonly used in combination with `limit()` for pagination.
1714 ///
1715 /// # Arguments
1716 ///
1717 /// * `offset` - Number of rows to skip
1718 ///
1719 /// # Example
1720 ///
1721 /// ```rust,ignore
1722 /// // Skip first 20 rows
1723 /// query.offset(20)
1724 ///
1725 /// // Pagination: page 3 with 10 items per page
1726 /// query.limit(10).offset(20) // Skip 2 pages = 20 items
1727 /// ```
1728 pub fn offset(mut self, offset: usize) -> Self {
1729 self.offset = Some(offset);
1730 self
1731 }
1732
1733 /// Sets the maximum number of records to return.
1734 ///
1735 /// Limits the number of rows returned by the query. Essential for pagination
1736 /// and preventing accidentally fetching large result sets.
1737 ///
1738 /// # Arguments
1739 ///
1740 /// * `limit` - Maximum number of rows to return
1741 ///
1742 /// # Example
1743 ///
1744 /// ```rust,ignore
1745 /// // Return at most 10 rows
1746 /// query.limit(10)
1747 ///
1748 /// // Pagination: 50 items per page
1749 /// query.limit(50).offset(page * 50)
1750 /// ```
1751 pub fn limit(mut self, limit: usize) -> Self {
1752 self.limit = Some(limit);
1753 self
1754 }
1755
1756 // ========================================================================
1757 // Insert Operation
1758 // ========================================================================
1759
1760 /// Inserts a new record into the database based on the model instance.
1761 ///
1762 /// This method serializes the model into a SQL INSERT statement with proper
1763 /// type handling for primitives, dates, UUIDs, and other supported types.
1764 ///
1765 /// # Type Binding Strategy
1766 ///
1767 /// The method uses string parsing as a temporary solution for type binding.
1768 /// Values are converted to strings via the model's `to_map()` method, then
1769 /// parsed back to their original types for proper SQL binding.
1770 ///
1771 /// # Supported Types for Insert
1772 ///
1773 /// - **Integers**: `i32`, `i64` (INTEGER, BIGINT)
1774 /// - **Boolean**: `bool` (BOOLEAN)
1775 /// - **Float**: `f64` (DOUBLE PRECISION)
1776 /// - **Text**: `String` (TEXT, VARCHAR)
1777 /// - **UUID**: `Uuid` (UUID) - All versions 1-7 supported
1778 /// - **DateTime**: `DateTime<Utc>` (TIMESTAMPTZ)
1779 /// - **NaiveDateTime**: (TIMESTAMP)
1780 /// - **NaiveDate**: (DATE)
1781 /// - **NaiveTime**: (TIME)
1782 ///
1783 /// # Arguments
1784 ///
1785 /// * `model` - Reference to the model instance to insert
1786 ///
1787 /// # Returns
1788 ///
1789 /// * `Ok(&Self)` - Reference to self for method chaining
1790 /// * `Err(sqlx::Error)` - Database error during insertion
1791 ///
1792 /// # Example
1793 ///
1794 /// ```rust,ignore
1795 ///
1796 /// use chrono::Utc;
1797 ///
1798 /// let new_user = User {
1799 /// id: Uuid::new_v4(),
1800 /// username: "john_doe".to_string(),
1801 /// email: "john@example.com".to_string(),
1802 /// age: 25,
1803 /// active: true,
1804 /// created_at: Utc::now(),
1805 /// };
1806 ///
1807 /// db.model::<User>().insert(&new_user).await?;
1808 /// ```
1809 pub fn insert<'b>(&'b mut self, model: &'b T) -> BoxFuture<'b, Result<(), sqlx::Error>> {
1810 Box::pin(async move {
1811 // Serialize model to a HashMap of column_name -> string_value
1812 let data_map = Model::to_map(model);
1813
1814 // Early return if no data to insert
1815 if data_map.is_empty() {
1816 return Ok(());
1817 }
1818
1819 let table_name = self.table_name.to_snake_case();
1820 let columns_info = <T as Model>::columns();
1821
1822 let mut target_columns = Vec::new();
1823 let mut bindings: Vec<(String, &str)> = Vec::new();
1824
1825 // Build column list and collect values with their SQL types
1826 for (col_name, value) in data_map {
1827 // Strip the "r#" prefix if present (for Rust keywords used as field names)
1828 let col_name_clean = col_name.strip_prefix("r#").unwrap_or(&col_name).to_snake_case();
1829 target_columns.push(format!("\"{}\"", col_name_clean));
1830
1831 // Find the SQL type for this column
1832 let sql_type = columns_info.iter().find(|c| c.name == col_name).map(|c| c.sql_type).unwrap_or("TEXT");
1833
1834 bindings.push((value, sql_type));
1835 }
1836
1837 // Generate placeholders with proper type casting for PostgreSQL
1838 let placeholders: Vec<String> = bindings
1839 .iter()
1840 .enumerate()
1841 .map(|(i, (_, sql_type))| match self.driver {
1842 Drivers::Postgres => {
1843 let idx = i + 1;
1844 // PostgreSQL requires explicit type casting for some types
1845 if temporal::is_temporal_type(sql_type) {
1846 // Use temporal module for type casting
1847 format!("${}{}", idx, temporal::get_postgres_type_cast(sql_type))
1848 } else {
1849 match *sql_type {
1850 "UUID" => format!("${}::UUID", idx),
1851 "JSONB" | "jsonb" => format!("${}::JSONB", idx),
1852 _ => format!("${}", idx),
1853 }
1854 }
1855 }
1856 // MySQL and SQLite use simple ? placeholders
1857 _ => "?".to_string(),
1858 })
1859 .collect();
1860
1861 // Construct the INSERT query
1862 let query_str = format!(
1863 "INSERT INTO \"{}\" ({}) VALUES ({})",
1864 table_name,
1865 target_columns.join(", "),
1866 placeholders.join(", ")
1867 );
1868
1869 if self.debug_mode {
1870 log::debug!("SQL: {}", query_str);
1871 }
1872
1873 let mut args = AnyArguments::default();
1874
1875 // Bind values using the optimized value_binding module
1876 for (val_str, sql_type) in bindings {
1877 if args.bind_value(&val_str, sql_type, &self.driver).is_err() {
1878 let _ = args.add(val_str);
1879 }
1880 }
1881
1882 // Execute the INSERT query
1883 self.tx.execute(&query_str, args).await?;
1884 Ok(())
1885 })
1886 }
1887
1888 /// Inserts multiple records into the database in a single batch operation.
1889 ///
1890 /// This is significantly faster than performing individual inserts in a loop
1891 /// as it generates a single SQL statement with multiple VALUES groups.
1892 ///
1893 /// # Type Binding Strategy
1894 ///
1895 /// Similar to the single record `insert`, this method uses string parsing for
1896 /// type binding. It ensures that all columns defined in the model are included
1897 /// in the insert statement, providing NULL for any missing optional values.
1898 ///
1899 /// # Arguments
1900 ///
1901 /// * `models` - A slice of model instances to insert
1902 ///
1903 /// # Returns
1904 ///
1905 /// * `Ok(())` - Successfully inserted all records
1906 /// * `Err(sqlx::Error)` - Database error during insertion
1907 ///
1908 /// # Example
1909 ///
1910 /// ```rust,ignore
1911 /// let users = vec![
1912 /// User { username: "alice".to_string(), ... },
1913 /// User { username: "bob".to_string(), ... },
1914 /// ];
1915 ///
1916 /// db.model::<User>().batch_insert(&users).await?;
1917 /// ```
1918 pub fn batch_insert<'b>(&'b mut self, models: &'b [T]) -> BoxFuture<'b, Result<(), sqlx::Error>> {
1919 Box::pin(async move {
1920 if models.is_empty() {
1921 return Ok(());
1922 }
1923
1924 let table_name = self.table_name.to_snake_case();
1925 let columns_info = <T as Model>::columns();
1926
1927 // Collect all column names for the INSERT statement
1928 // We use all columns defined in the model to ensure consistency across the batch
1929 let target_columns: Vec<String> = columns_info
1930 .iter()
1931 .map(|c| {
1932 let col_name_clean = c.name.strip_prefix("r#").unwrap_or(c.name).to_snake_case();
1933 format!("\"{}\"", col_name_clean)
1934 })
1935 .collect();
1936
1937 let mut value_groups = Vec::new();
1938 let mut bind_index = 1;
1939
1940 // Generate placeholders for all models
1941 for _ in models {
1942 let mut placeholders = Vec::new();
1943 for col in &columns_info {
1944 match self.driver {
1945 Drivers::Postgres => {
1946 let p = if temporal::is_temporal_type(col.sql_type) {
1947 format!("${}{}", bind_index, temporal::get_postgres_type_cast(col.sql_type))
1948 } else {
1949 match col.sql_type {
1950 "UUID" => format!("${}::UUID", bind_index),
1951 "JSONB" | "jsonb" => format!("${}::JSONB", bind_index),
1952 _ => format!("${}", bind_index),
1953 }
1954 };
1955 placeholders.push(p);
1956 bind_index += 1;
1957 }
1958 _ => {
1959 placeholders.push("?".to_string());
1960 }
1961 }
1962 }
1963 value_groups.push(format!("({})", placeholders.join(", ")));
1964 }
1965
1966 let query_str = format!(
1967 "INSERT INTO \"{}\" ({}) VALUES {}",
1968 table_name,
1969 target_columns.join(", "),
1970 value_groups.join(", ")
1971 );
1972
1973 if self.debug_mode {
1974 log::debug!("SQL Batch: {}", query_str);
1975 }
1976
1977 let mut args = AnyArguments::default();
1978
1979 for model in models {
1980 let data_map = Model::to_map(model);
1981 for col in &columns_info {
1982 let val_opt = data_map.get(col.name);
1983 let sql_type = col.sql_type;
1984
1985 if let Some(val_str) = val_opt {
1986 if args.bind_value(val_str, sql_type, &self.driver).is_err() {
1987 let _ = args.add(val_str.clone());
1988 }
1989 } else {
1990 // Bind NULL for missing values
1991 let _ = args.add(None::<String>);
1992 }
1993 }
1994 }
1995
1996 // Execute the batch INSERT query
1997 self.tx.execute(&query_str, args).await?;
1998 Ok(())
1999 })
2000 }
2001
2002 /// Inserts a record or updates it if a conflict occurs (UPSERT).
2003 ///
2004 /// This method provides a cross-database way to perform "Insert or Update" operations.
2005 /// It uses `ON CONFLICT` for PostgreSQL and SQLite, and `ON DUPLICATE KEY UPDATE` for MySQL.
2006 ///
2007 /// # Arguments
2008 ///
2009 /// * `model` - The model instance to insert or update
2010 /// * `conflict_columns` - Columns that trigger the conflict (e.g., primary key or unique columns)
2011 /// * `update_columns` - Columns to update when a conflict occurs
2012 ///
2013 /// # Returns
2014 ///
2015 /// * `Ok(u64)` - The number of rows affected
2016 /// * `Err(sqlx::Error)` - Database error
2017 ///
2018 /// # Example
2019 ///
2020 /// ```rust,ignore
2021 /// let user = User { id: 1, username: "alice".to_string(), age: 25 };
2022 ///
2023 /// // If id 1 exists, update username and age
2024 /// db.model::<User>().upsert(&user, &["id"], &["username", "age"]).await?;
2025 /// ```
2026 pub fn upsert<'b>(
2027 &'b mut self,
2028 model: &'b T,
2029 conflict_columns: &'b [&'b str],
2030 update_columns: &'b [&'b str],
2031 ) -> BoxFuture<'b, Result<u64, sqlx::Error>> {
2032 Box::pin(async move {
2033 let data_map = Model::to_map(model);
2034 if data_map.is_empty() {
2035 return Ok(0);
2036 }
2037
2038 let table_name = self.table_name.to_snake_case();
2039 let columns_info = <T as Model>::columns();
2040
2041 let mut target_columns = Vec::new();
2042 let mut bindings: Vec<(String, &str)> = Vec::new();
2043
2044 // Build INSERT part
2045 for (col_name, value) in &data_map {
2046 let col_name_clean = col_name.strip_prefix("r#").unwrap_or(col_name).to_snake_case();
2047 target_columns.push(format!("\"{}\"", col_name_clean));
2048
2049 let sql_type = columns_info.iter().find(|c| {
2050 let c_clean = c.name.strip_prefix("r#").unwrap_or(c.name);
2051 c_clean == *col_name || c_clean.to_snake_case() == col_name_clean
2052 }).map(|c| c.sql_type).unwrap_or("TEXT");
2053 bindings.push((value.clone(), sql_type));
2054 }
2055
2056 let mut arg_counter = 1;
2057 let mut placeholders = Vec::new();
2058 for (_, sql_type) in &bindings {
2059 match self.driver {
2060 Drivers::Postgres => {
2061 let p = if temporal::is_temporal_type(sql_type) {
2062 format!("${}{}", arg_counter, temporal::get_postgres_type_cast(sql_type))
2063 } else {
2064 match *sql_type {
2065 "UUID" => format!("${}::UUID", arg_counter),
2066 "JSONB" | "jsonb" => format!("${}::JSONB", arg_counter),
2067 _ => format!("${}", arg_counter),
2068 }
2069 };
2070 placeholders.push(p);
2071 arg_counter += 1;
2072 }
2073 _ => {
2074 placeholders.push("?".to_string());
2075 }
2076 }
2077 }
2078
2079 let mut query_str = format!(
2080 "INSERT INTO \"{}\" ({}) VALUES ({})",
2081 table_name,
2082 target_columns.join(", "),
2083 placeholders.join(", ")
2084 );
2085
2086 // Build Conflict/Update part
2087 match self.driver {
2088 Drivers::Postgres | Drivers::SQLite => {
2089 let conflict_cols_str = conflict_columns
2090 .iter()
2091 .map(|c| format!("\"{}\"", c.to_snake_case()))
2092 .collect::<Vec<_>>()
2093 .join(", ");
2094
2095 query_str.push_str(&format!(" ON CONFLICT ({}) DO UPDATE SET ", conflict_cols_str));
2096
2097 let mut update_clauses = Vec::new();
2098 let mut update_bindings = Vec::new();
2099
2100 for col in update_columns {
2101 let col_snake = col.to_snake_case();
2102 if let Some((_key, val_str)) = data_map.iter().find(|(k, _)| {
2103 let k_clean = k.strip_prefix("r#").unwrap_or(*k);
2104 k_clean == *col || k_clean.to_snake_case() == col_snake
2105 }) {
2106 let sql_type = columns_info.iter().find(|c| {
2107 let c_clean = c.name.strip_prefix("r#").unwrap_or(c.name);
2108 c_clean == *col || c_clean.to_snake_case() == col_snake
2109 }).map(|c| c.sql_type).unwrap_or("TEXT");
2110
2111 let placeholder = match self.driver {
2112 Drivers::Postgres => {
2113 let p = if temporal::is_temporal_type(sql_type) {
2114 format!("${}{}", arg_counter, temporal::get_postgres_type_cast(sql_type))
2115 } else {
2116 match sql_type {
2117 "UUID" => format!("${}::UUID", arg_counter),
2118 "JSONB" | "jsonb" => format!("${}::JSONB", arg_counter),
2119 _ => format!("${}", arg_counter),
2120 }
2121 };
2122 arg_counter += 1;
2123 p
2124 }
2125 _ => "?".to_string(),
2126 };
2127 update_clauses.push(format!("\"{}\" = {}", col_snake, placeholder));
2128 update_bindings.push((val_str.clone(), sql_type));
2129 }
2130 }
2131 if update_clauses.is_empty() {
2132 query_str.push_str(" NOTHING");
2133 } else {
2134 query_str.push_str(&update_clauses.join(", "));
2135 }
2136 bindings.extend(update_bindings);
2137 }
2138 Drivers::MySQL => {
2139 query_str.push_str(" ON DUPLICATE KEY UPDATE ");
2140 let mut update_clauses = Vec::new();
2141 for col in update_columns {
2142 let col_snake = col.to_snake_case();
2143 update_clauses.push(format!("\"{}\" = VALUES(\"{}\")", col_snake, col_snake));
2144 }
2145 query_str.push_str(&update_clauses.join(", "));
2146 }
2147 }
2148
2149 if self.debug_mode {
2150 log::debug!("SQL Upsert: {}", query_str);
2151 }
2152
2153 let mut args = AnyArguments::default();
2154 for (val_str, sql_type) in bindings {
2155 if args.bind_value(&val_str, sql_type, &self.driver).is_err() {
2156 let _ = args.add(val_str);
2157 }
2158 }
2159
2160 let result = self.tx.execute(&query_str, args).await?;
2161 Ok(result.rows_affected())
2162 })
2163 }
2164
2165 // ========================================================================
2166 // Query Execution Methods
2167 // ========================================================================
2168
2169 /// Returns the generated SQL string for debugging purposes.
2170 ///
2171 /// This method constructs the SQL query string without executing it.
2172 /// Useful for debugging and logging query construction. Note that this
2173 /// shows placeholders (?, $1, etc.) rather than actual bound values.
2174 ///
2175 /// # Returns
2176 ///
2177 /// A `String` containing the SQL query that would be executed
2178 ///
2179 /// # Example
2180 ///
2181 /// ```rust,ignore
2182 /// let query = db.model::<User>()
2183 /// .filter("age", ">=", 18)
2184 /// .order("created_at DESC")
2185 /// .limit(10);
2186 ///
2187 /// println!("SQL: {}", query.to_sql());
2188 /// // Output: SELECT * FROM "user" WHERE 1=1 AND "age" >= $1 ORDER BY created_at DESC
2189 /// ```
2190 pub fn to_sql(&self) -> String {
2191 let mut query = String::new();
2192 let mut args = AnyArguments::default();
2193 let mut arg_counter = 1;
2194
2195 self.write_select_sql::<T>(&mut query, &mut args, &mut arg_counter);
2196 query
2197 }
2198
2199 /// Generates the list of column selection SQL arguments.
2200 ///
2201 /// This helper function constructs the column list for the SELECT statement.
2202 /// It handles:
2203 /// 1. Mapping specific columns if `select_columns` is set.
2204 /// 2. Defaulting to all columns from the struct `R` if no columns are specified.
2205 /// 3. applying `to_json(...)` casting for temporal types when using `AnyImpl` structs,
2206 /// ensuring compatibility with the `FromAnyRow` deserialization logic.
2207 fn select_args_sql<R: AnyImpl>(&self) -> Vec<String> {
2208 let struct_cols = R::columns();
2209 let table_id = self.get_table_identifier();
2210 let main_table_snake = self.table_name.to_snake_case();
2211
2212 // If the struct has no columns (e.g. primitive type for scalar queries),
2213 // just return the manual selects as is.
2214 if struct_cols.is_empty() {
2215 if self.select_columns.is_empty() {
2216 return vec!["*".to_string()];
2217 }
2218 return self.select_columns.clone();
2219 }
2220
2221 let mut args = Vec::new();
2222
2223 // Flatten multi-column strings in select_columns
2224 let mut flat_selects = Vec::new();
2225 for s in &self.select_columns {
2226 if s.contains(',') {
2227 for sub in s.split(',') {
2228 flat_selects.push(sub.trim().to_string());
2229 }
2230 } else {
2231 flat_selects.push(s.trim().to_string());
2232 }
2233 }
2234
2235 let has_main_star = flat_selects.iter().any(|s| s == "*" || s == &format!("{}.*", table_id) || s == &format!("{}.*", main_table_snake));
2236
2237 if self.select_columns.is_empty() || has_main_star {
2238 // Select all columns from R
2239 for col_info in struct_cols {
2240 let col_name = col_info.column;
2241 let col_snake = col_name.strip_prefix("r#").unwrap_or(col_name).to_snake_case();
2242
2243 let mut table_to_use = table_id.clone();
2244 if !col_info.table.is_empty() {
2245 let c_table_snake = col_info.table.to_snake_case();
2246 if c_table_snake == main_table_snake {
2247 table_to_use = table_id.clone();
2248 } else if let Some(alias) = self.join_aliases.get(&c_table_snake) {
2249 table_to_use = alias.clone();
2250 }
2251 }
2252
2253 let is_omitted = self.omit_columns.contains(&col_snake);
2254 let table_to_alias = if !col_info.table.is_empty() {
2255 col_info.table.to_snake_case()
2256 } else {
2257 main_table_snake.clone()
2258 };
2259
2260 if is_omitted {
2261 let placeholder = match col_info.sql_type {
2262 "TEXT" | "VARCHAR" | "CHAR" | "STRING" => "'omited'",
2263 "TIMESTAMP" | "TIMESTAMPTZ" | "DATE" | "TIME" => "'1970-01-01'",
2264 "INTEGER" | "INT" | "BIGINT" => "0",
2265 "REAL" | "FLOAT" | "DOUBLE" | "DECIMAL" => "0.0",
2266 "BOOLEAN" | "BOOL" => "false",
2267 "UUID" => "'00000000-0000-0000-0000-000000000000'",
2268 _ => "NULL",
2269 };
2270 args.push(format!("{} AS \"{}__{}\"", placeholder, table_to_alias, col_snake));
2271 } else if is_temporal_type(col_info.sql_type) && matches!(self.driver, Drivers::Postgres) {
2272 args.push(format!("to_json(\"{}\".\"{}\") #>> '{{}}' AS \"{}__{}\"", table_to_use, col_snake, table_to_alias, col_snake));
2273 } else {
2274 args.push(format!("\"{}\".\"{}\" AS \"{}__{}\"", table_to_use, col_snake, table_to_alias, col_snake));
2275 }
2276 }
2277 } else {
2278 // Handle explicit selects
2279 let mut matched_flat_indices = std::collections::HashSet::new();
2280 let mut matched_struct_indices = std::collections::HashSet::new();
2281
2282 // First pass: try to match manual selects (including aliases) to struct columns
2283 for (f_idx, s) in flat_selects.iter().enumerate() {
2284 let s_lower = s.to_lowercase();
2285
2286 // If it's an alias "expr as alias", check if alias matches a struct column
2287 if let Some((_expr, alias)) = s_lower.split_once(" as ") {
2288 let clean_alias = alias.trim().trim_matches('"').trim_matches('\'');
2289 for (s_idx, col_info) in struct_cols.iter().enumerate() {
2290 let col_name = col_info.column;
2291 let col_snake = col_name.strip_prefix("r#").unwrap_or(col_name).to_snake_case();
2292
2293 if clean_alias == col_name || clean_alias == &col_snake {
2294 // Match! Use the manual select as-is
2295 args.push(s.clone());
2296 matched_flat_indices.insert(f_idx);
2297 matched_struct_indices.insert(s_idx);
2298 break;
2299 }
2300 }
2301 } else {
2302 // Direct match "column" or "table.column"
2303 for (s_idx, col_info) in struct_cols.iter().enumerate() {
2304 let col_name = col_info.column;
2305 let col_snake = col_name.strip_prefix("r#").unwrap_or(col_name).to_snake_case();
2306
2307 if s == col_name || s == &col_snake || s == &format!("{}.{}", table_id, col_name) || s == &format!("{}.{}", table_id, col_snake) {
2308 // Match! Use standardized quoted select
2309 let mut table_to_use = table_id.clone();
2310 if !col_info.table.is_empty() {
2311 let c_table_snake = col_info.table.to_snake_case();
2312 if c_table_snake == main_table_snake {
2313 table_to_use = table_id.clone();
2314 } else if let Some(alias) = self.join_aliases.get(&c_table_snake) {
2315 table_to_use = alias.clone();
2316 }
2317 }
2318
2319 let table_to_alias = if !col_info.table.is_empty() {
2320 col_info.table.to_snake_case()
2321 } else {
2322 main_table_snake.clone()
2323 };
2324
2325 if is_temporal_type(col_info.sql_type) && matches!(self.driver, Drivers::Postgres) {
2326 args.push(format!("to_json(\"{}\".\"{}\") #>> '{{}}' AS \"{}__{}\"", table_to_use, col_snake, table_to_alias, col_snake));
2327 } else {
2328 args.push(format!("\"{}\".\"{}\" AS \"{}__{}\"", table_to_use, col_snake, table_to_alias, col_snake));
2329 }
2330 matched_flat_indices.insert(f_idx);
2331 matched_struct_indices.insert(s_idx);
2332 break;
2333 }
2334 }
2335 }
2336 }
2337
2338 // Second pass: add remaining manual selects
2339 for (idx, s) in flat_selects.iter().enumerate() {
2340 if !matched_flat_indices.contains(&idx) {
2341 args.push(s.clone());
2342 }
2343 }
2344 }
2345
2346 if args.is_empty() {
2347 vec!["*".to_string()]
2348 } else {
2349 args
2350 }
2351 }
2352
2353 /// Executes the query and returns a list of results.
2354 ///
2355 /// This method builds and executes a SELECT query with all accumulated filters,
2356 /// ordering, and pagination settings. It returns all matching rows as a vector.
2357 ///
2358 /// # Type Parameters
2359 ///
2360 /// * `R` - The result type. Must implement `FromRow` for deserialization from database rows.
2361 ///
2362 /// # Returns
2363 ///
2364 /// * `Ok(Vec<R>)` - Vector of results (empty if no matches)
2365 /// * `Err(sqlx::Error)` - Database error during query execution
2366 ///
2367 /// # Example
2368 ///
2369 /// ```rust,ignore
2370 /// // Get all adult users, ordered by age, limited to 10
2371 /// let users: Vec<User> = db.model::<User>()
2372 /// .filter("age", ">=", 18)
2373 /// .order("age DESC")
2374 /// .limit(10)
2375 /// .scan()
2376 /// .await?;
2377 ///
2378 /// // Get users by UUID
2379 /// let user_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?;
2380 /// let users: Vec<User> = db.model::<User>()
2381 /// .filter("id", "=", user_id)
2382 /// .scan()
2383 /// .await?;
2384 ///
2385 /// // Empty result is Ok
2386 /// let results: Vec<User> = db.model::<User>()
2387 /// .filter("age", ">", 200)
2388 /// .scan()
2389 /// .await?; // Returns empty Vec, not an error
2390 /// ```
2391 pub async fn scan<R>(mut self) -> Result<Vec<R>, sqlx::Error>
2392 where
2393 R: FromAnyRow + AnyImpl + Send + Unpin,
2394 {
2395 self.apply_soft_delete_filter();
2396 let mut query = String::new();
2397 let mut args = AnyArguments::default();
2398 let mut arg_counter = 1;
2399
2400 self.write_select_sql::<R>(&mut query, &mut args, &mut arg_counter);
2401
2402 if self.debug_mode {
2403 log::debug!("SQL: {}", query);
2404 }
2405
2406 let rows = self.tx.fetch_all(&query, args).await?;
2407 let mut result = Vec::with_capacity(rows.len());
2408 for row in rows {
2409 result.push(R::from_any_row(&row)?);
2410 }
2411 Ok(result)
2412 }
2413
2414 /// Executes the query and maps the result to a custom DTO.
2415 pub async fn scan_as<R>(mut self) -> Result<Vec<R>, sqlx::Error>
2416 where
2417 R: FromAnyRow + AnyImpl + Send + Unpin,
2418 {
2419 self.apply_soft_delete_filter();
2420 let mut query = String::new();
2421 let mut args = AnyArguments::default();
2422 let mut arg_counter = 1;
2423
2424 self.write_select_sql::<R>(&mut query, &mut args, &mut arg_counter);
2425
2426 if self.debug_mode {
2427 log::debug!("SQL: {}", query);
2428 }
2429
2430 let rows = self.tx.fetch_all(&query, args).await?;
2431 let mut result = Vec::with_capacity(rows.len());
2432 for row in rows {
2433 result.push(R::from_any_row(&row)?);
2434 }
2435 Ok(result)
2436 }
2437
2438 /// Executes the query and returns only the first result.
2439 pub async fn first<R>(mut self) -> Result<R, sqlx::Error>
2440 where
2441 R: FromAnyRow + AnyImpl + Send + Unpin,
2442 {
2443 self.apply_soft_delete_filter();
2444 let mut query = String::new();
2445 let mut args = AnyArguments::default();
2446 let mut arg_counter = 1;
2447
2448 // Force limit 1 if not set
2449 if self.limit.is_none() {
2450 self.limit = Some(1);
2451 }
2452
2453 // Apply PK ordering fallback if no order is set
2454 if self.order_clauses.is_empty() {
2455 let table_id = self.get_table_identifier();
2456 let pk_columns: Vec<String> = <T as Model>::columns()
2457 .iter()
2458 .filter(|c| c.is_primary_key)
2459 .map(|c| format!("\"{}\".\"{}\"", table_id, c.name.strip_prefix("r#").unwrap_or(c.name).to_snake_case()))
2460 .collect();
2461
2462 if !pk_columns.is_empty() {
2463 self.order_clauses.push(pk_columns.iter().map(|col| format!("{} ASC", col)).collect::<Vec<_>>().join(", "));
2464 }
2465 }
2466
2467 self.write_select_sql::<R>(&mut query, &mut args, &mut arg_counter);
2468
2469 if self.debug_mode {
2470 log::debug!("SQL: {}", query);
2471 }
2472
2473 let row = self.tx.fetch_one(&query, args).await?;
2474 R::from_any_row(&row)
2475 }
2476
2477 /// Executes the query and returns a single scalar value.
2478 pub async fn scalar<O>(mut self) -> Result<O, sqlx::Error>
2479 where
2480 O: FromAnyRow + AnyImpl + Send + Unpin,
2481 {
2482 self.apply_soft_delete_filter();
2483 let mut query = String::new();
2484 let mut args = AnyArguments::default();
2485 let mut arg_counter = 1;
2486
2487 // Force limit 1 if not set
2488 if self.limit.is_none() {
2489 self.limit = Some(1);
2490 }
2491
2492 self.write_select_sql::<O>(&mut query, &mut args, &mut arg_counter);
2493
2494 if self.debug_mode {
2495 log::debug!("SQL: {}", query);
2496 }
2497
2498 let row = self.tx.fetch_one(&query, args).await?;
2499 O::from_any_row(&row)
2500 }
2501
2502 /// Updates a single column in the database.
2503 ///
2504 /// # Arguments
2505 ///
2506 /// * `col` - The column name to update
2507 /// * `value` - The new value
2508 ///
2509 /// # Returns
2510 ///
2511 /// * `Ok(u64)` - The number of rows affected
2512 pub fn update<'b, V>(&'b mut self, col: &str, value: V) -> BoxFuture<'b, Result<u64, sqlx::Error>>
2513 where
2514 V: ToString + Send + Sync,
2515 {
2516 let mut map = std::collections::HashMap::new();
2517 map.insert(col.to_string(), value.to_string());
2518 self.execute_update(map)
2519 }
2520
2521 /// Updates all columns based on the model instance.
2522 ///
2523 /// This method updates all active columns of the table with values from the provided model.
2524 ///
2525 /// # Arguments
2526 ///
2527 /// * `model` - The model instance containing new values
2528 ///
2529 /// # Returns
2530 ///
2531 /// * `Ok(u64)` - The number of rows affected
2532 pub fn updates<'b>(&'b mut self, model: &T) -> BoxFuture<'b, Result<u64, sqlx::Error>> {
2533 self.execute_update(Model::to_map(model))
2534 }
2535
2536 /// Updates columns based on a partial model (struct implementing AnyImpl).
2537 ///
2538 /// This allows updating a subset of columns using a custom struct.
2539 /// The struct must implement `AnyImpl` (usually via `#[derive(FromAnyRow)]`).
2540 ///
2541 /// # Arguments
2542 ///
2543 /// * `partial` - The partial model containing new values
2544 ///
2545 /// # Returns
2546 ///
2547 /// * `Ok(u64)` - The number of rows affected
2548 pub fn update_partial<'b, P: AnyImpl>(&'b mut self, partial: &P) -> BoxFuture<'b, Result<u64, sqlx::Error>> {
2549 self.execute_update(AnyImpl::to_map(partial))
2550 }
2551
2552 /// Updates a column using a raw SQL expression.
2553 ///
2554 /// This allows for complex updates like incrementing values or using database functions.
2555 /// You can use a `?` placeholder in the expression and provide a value to bind.
2556 ///
2557 /// # Arguments
2558 ///
2559 /// * `col` - The column name to update
2560 /// * `expr` - The raw SQL expression (e.g., "age + 1" or "age + ?")
2561 /// * `value` - The value to bind for the placeholder
2562 ///
2563 /// # Example
2564 ///
2565 /// ```rust,ignore
2566 /// // Increment age by 1
2567 /// db.model::<User>()
2568 /// .filter("id", "=", 1)
2569 /// .update_raw("age", "age + 1", 0)
2570 /// .await?;
2571 ///
2572 /// // Increment age by a variable
2573 /// db.model::<User>()
2574 /// .filter("id", "=", 1)
2575 /// .update_raw("age", "age + ?", 5)
2576 /// .await?;
2577 /// ```
2578 pub fn update_raw<'b, V>(
2579 &'b mut self,
2580 col: &str,
2581 expr: &str,
2582 value: V,
2583 ) -> BoxFuture<'b, Result<u64, sqlx::Error>>
2584 where
2585 V: 'static + for<'q> Encode<'q, Any> + Type<Any> + Send + Sync + Clone,
2586 {
2587 self.apply_soft_delete_filter();
2588
2589 let col_name_clean = col.strip_prefix("r#").unwrap_or(col).to_snake_case();
2590 let expr_owned = expr.to_string();
2591 let value_owned = value.clone();
2592
2593 Box::pin(async move {
2594 let table_name = self.table_name.to_snake_case();
2595 let mut query = format!("UPDATE \"{}\" ", table_name);
2596 if let Some(alias) = &self.alias {
2597 query.push_str(&format!("{} ", alias));
2598 }
2599 query.push_str("SET ");
2600
2601 let mut arg_counter = 1;
2602 let mut args = AnyArguments::default();
2603
2604 let mut processed_expr = expr_owned.clone();
2605 if let Some(pos) = processed_expr.find('?') {
2606 let placeholder = match self.driver {
2607 Drivers::Postgres => {
2608 let p = format!("${}", arg_counter);
2609 arg_counter += 1;
2610 p
2611 }
2612 _ => "?".to_string(),
2613 };
2614 processed_expr.replace_range(pos..pos + 1, &placeholder);
2615 let _ = args.add(value_owned);
2616 } else {
2617 // If no placeholder, we still add the value to match where_raw behavior,
2618 // but increment the counter if it's Postgres to avoid offset issues
2619 // Actually, if there's no ?, Postgres doesn't care about the extra arg if we don't use it.
2620 // But the next arg_counter must be correct.
2621 // If we don't increment arg_counter here, the NEXT placeholder will use $1.
2622 // If we ADDED a value to args, $1 will point to THIS value.
2623 // So if we don't use ?, we probably SHOULD NOT add to args.
2624 }
2625
2626 query.push_str(&format!("\"{}\" = {}", col_name_clean, processed_expr));
2627
2628 query.push_str(" WHERE 1=1");
2629
2630 for clause in &self.where_clauses {
2631 clause(&mut query, &mut args, &self.driver, &mut arg_counter);
2632 }
2633
2634 if self.debug_mode {
2635 log::debug!("SQL: {}", query);
2636 }
2637
2638 let result = self.tx.execute(&query, args).await?;
2639 Ok(result.rows_affected())
2640 })
2641 }
2642
2643 /// Internal helper to apply soft delete filter to where clauses if necessary.
2644 fn apply_soft_delete_filter(&mut self) {
2645 if !self.with_deleted {
2646 if let Some(soft_delete_col) = self.columns_info.iter().find(|c| c.soft_delete).map(|c| c.name) {
2647 let col_owned = soft_delete_col.to_string();
2648 let clause: FilterFn = Box::new(move |query, _args, _driver, _arg_counter| {
2649 query.push_str(" AND ");
2650 query.push_str(&format!("\"{}\"", col_owned));
2651 query.push_str(" IS NULL");
2652 });
2653 self.where_clauses.push(clause);
2654 }
2655 }
2656 }
2657
2658 /// Internal helper to execute an UPDATE query from a map of values.
2659 fn execute_update<'b>(
2660 &'b mut self,
2661 data_map: std::collections::HashMap<String, String>,
2662 ) -> BoxFuture<'b, Result<u64, sqlx::Error>> {
2663 self.apply_soft_delete_filter();
2664
2665 Box::pin(async move {
2666 let table_name = self.table_name.to_snake_case();
2667 let mut query = format!("UPDATE \"{}\" ", table_name);
2668 if let Some(alias) = &self.alias {
2669 query.push_str(&format!("{} ", alias));
2670 }
2671 query.push_str("SET ");
2672
2673 let mut bindings: Vec<(String, &str)> = Vec::new();
2674 let mut set_clauses = Vec::new();
2675
2676 // Maintain argument counter for PostgreSQL ($1, $2, ...)
2677 let mut arg_counter = 1;
2678
2679 // Build SET clause
2680 for (col_name, value) in data_map {
2681 // Strip the "r#" prefix if present
2682 let col_name_clean = col_name.strip_prefix("r#").unwrap_or(&col_name).to_snake_case();
2683
2684 // Find the SQL type for this column from the Model metadata
2685 let sql_type = self
2686 .columns_info
2687 .iter()
2688 .find(|c| c.name == col_name || c.name == col_name_clean)
2689 .map(|c| c.sql_type)
2690 .unwrap_or("TEXT");
2691
2692 // Generate placeholder
2693 let placeholder = match self.driver {
2694 Drivers::Postgres => {
2695 let idx = arg_counter;
2696 arg_counter += 1;
2697
2698 if temporal::is_temporal_type(sql_type) {
2699 format!("${}{}", idx, temporal::get_postgres_type_cast(sql_type))
2700 } else {
2701 match sql_type {
2702 "UUID" => format!("${}::UUID", idx),
2703 "JSONB" | "jsonb" => format!("${}::JSONB", idx),
2704 _ => format!("${}", idx),
2705 }
2706 }
2707 }
2708 _ => "?".to_string(),
2709 };
2710
2711 set_clauses.push(format!("\"{}\" = {}", col_name_clean, placeholder));
2712 bindings.push((value, sql_type));
2713 }
2714
2715 // If no fields to update, return 0
2716 if set_clauses.is_empty() {
2717 return Ok(0);
2718 }
2719
2720 query.push_str(&set_clauses.join(", "));
2721
2722 // Build WHERE clause
2723 query.push_str(" WHERE 1=1");
2724
2725 let mut args = AnyArguments::default();
2726
2727 // Bind SET values
2728 for (val_str, sql_type) in bindings {
2729 if args.bind_value(&val_str, sql_type, &self.driver).is_err() {
2730 let _ = args.add(val_str);
2731 }
2732 }
2733
2734 // Apply WHERE clauses (appending to args and query)
2735 for clause in &self.where_clauses {
2736 clause(&mut query, &mut args, &self.driver, &mut arg_counter);
2737 }
2738
2739 // Print SQL query to logs if debug mode is active
2740 if self.debug_mode {
2741 log::debug!("SQL: {}", query);
2742 }
2743
2744 // Execute the UPDATE query
2745 let result = self.tx.execute(&query, args).await?;
2746
2747 Ok(result.rows_affected())
2748 })
2749 }
2750
2751 /// Executes a DELETE query based on the current filters.
2752 ///
2753 /// If the model has a `#[orm(soft_delete)]` column, this method performs
2754 /// an UPDATE setting the soft delete column to the current timestamp instead
2755 /// of physically deleting the record.
2756 ///
2757 /// For permanent deletion, use `hard_delete()`.
2758 ///
2759 /// # Returns
2760 ///
2761 /// * `Ok(u64)` - The number of rows deleted (or soft-deleted)
2762 /// * `Err(sqlx::Error)` - Database error
2763 pub async fn delete(self) -> Result<u64, sqlx::Error> {
2764 // Check for soft delete column
2765 let soft_delete_col = self.columns_info.iter().find(|c| c.soft_delete).map(|c| c.name);
2766
2767 if let Some(col) = soft_delete_col {
2768 // Soft Delete: Update the column to current timestamp
2769 let table_name = self.table_name.to_snake_case();
2770 let mut query = format!("UPDATE \"{}\" ", table_name);
2771 if let Some(alias) = &self.alias {
2772 query.push_str(&format!("{} ", alias));
2773 }
2774 query.push_str(&format!("SET \"{}\" = ", col));
2775
2776 match self.driver {
2777 Drivers::Postgres => query.push_str("NOW()"),
2778 Drivers::SQLite => query.push_str("strftime('%Y-%m-%dT%H:%M:%SZ', 'now')"),
2779 Drivers::MySQL => query.push_str("NOW()"),
2780 }
2781
2782 query.push_str(" WHERE 1=1");
2783
2784 let mut args = AnyArguments::default();
2785 let mut arg_counter = 1;
2786
2787 // Apply filters
2788 for clause in &self.where_clauses {
2789 clause(&mut query, &mut args, &self.driver, &mut arg_counter);
2790 }
2791
2792 // Print SQL query to logs if debug mode is active
2793 if self.debug_mode {
2794 log::debug!("SQL: {}", query);
2795 }
2796
2797 let result = self.tx.execute(&query, args).await?;
2798 Ok(result.rows_affected())
2799 } else {
2800 // Standard Delete (no soft delete column)
2801 let mut query = String::from("DELETE FROM \"");
2802 query.push_str(&self.table_name.to_snake_case());
2803 query.push_str("\" WHERE 1=1");
2804
2805 let mut args = AnyArguments::default();
2806 let mut arg_counter = 1;
2807
2808 for clause in &self.where_clauses {
2809 clause(&mut query, &mut args, &self.driver, &mut arg_counter);
2810 }
2811
2812 // Print SQL query to logs if debug mode is active
2813 if self.debug_mode {
2814 log::debug!("SQL: {}", query);
2815 }
2816
2817 let result = self.tx.execute(&query, args).await?;
2818 Ok(result.rows_affected())
2819 }
2820 }
2821
2822 /// Permanently removes records from the database.
2823 ///
2824 /// This method performs a physical DELETE, bypassing any soft delete logic.
2825 /// Use this when you need to permanently remove records.
2826 ///
2827 /// # Returns
2828 ///
2829 /// * `Ok(u64)` - The number of rows deleted
2830 /// * `Err(sqlx::Error)` - Database error
2831 ///
2832 /// # Example
2833 ///
2834 /// ```rust,ignore
2835 /// // Permanently delete soft-deleted records older than 30 days
2836 /// db.model::<User>()
2837 /// .with_deleted()
2838 /// .filter("deleted_at", "<", thirty_days_ago)
2839 /// .hard_delete()
2840 /// .await?;
2841 /// ```
2842 pub async fn hard_delete(self) -> Result<u64, sqlx::Error> {
2843 let mut query = String::from("DELETE FROM \"");
2844 query.push_str(&self.table_name.to_snake_case());
2845 query.push_str("\" WHERE 1=1");
2846
2847 let mut args = AnyArguments::default();
2848 let mut arg_counter = 1;
2849
2850 for clause in &self.where_clauses {
2851 clause(&mut query, &mut args, &self.driver, &mut arg_counter);
2852 }
2853
2854 // Print SQL query to logs if debug mode is active
2855 if self.debug_mode {
2856 log::debug!("SQL: {}", query);
2857 }
2858
2859 let result = self.tx.execute(&query, args).await?;
2860 Ok(result.rows_affected())
2861 }
2862}