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