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