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