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