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