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