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