reinhardt_db/orm/query.rs
1//! Unified query interface facade
2//!
3//! This module provides a unified entry point for querying functionality.
4//! By default, it exports the expression-based query API (SQLAlchemy-style).
5
6use super::FieldSelector;
7use crate::naming::to_snake_case;
8use crate::orm::query_fields::GroupByFields;
9use crate::orm::query_fields::aggregate::{AggregateExpr, ComparisonExpr};
10use crate::orm::query_fields::comparison::FieldComparison;
11use crate::orm::query_fields::compiler::QueryFieldCompiler;
12use reinhardt_query::prelude::{
13 Alias, BinOper, ColumnRef, Condition, Expr, ExprTrait, Func, JoinType as SeaJoinType, Order,
14 PostgresQueryBuilder, Query, QueryStatementBuilder, SelectStatement, SimpleExpr,
15};
16use reinhardt_query::types::PgBinOper;
17use serde::{Deserialize, Serialize};
18use smallvec::SmallVec;
19use std::collections::HashMap;
20use uuid::Uuid;
21
22// Django QuerySet API types
23#[derive(Debug, Clone, Serialize, Deserialize)]
24/// Defines possible filter operator values.
25pub enum FilterOperator {
26 /// Eq variant.
27 Eq,
28 /// Ne variant.
29 Ne,
30 /// Gt variant.
31 Gt,
32 /// Gte variant.
33 Gte,
34 /// Lt variant.
35 Lt,
36 /// Lte variant.
37 Lte,
38 /// In variant.
39 In,
40 /// NotIn variant.
41 NotIn,
42 /// Contains variant.
43 Contains,
44 /// StartsWith variant.
45 StartsWith,
46 /// EndsWith variant.
47 EndsWith,
48 // PostgreSQL array operators
49 /// Array contains all elements (@>)
50 ArrayContains,
51 /// Array is contained by (<@)
52 ArrayContainedBy,
53 /// Arrays overlap (&&) - at least one common element
54 ArrayOverlap,
55 // PostgreSQL full-text search
56 /// Full-text search match (@@)
57 FullTextMatch,
58 // PostgreSQL JSONB operators
59 /// JSONB contains (@>)
60 JsonbContains,
61 /// JSONB is contained by (<@)
62 JsonbContainedBy,
63 /// JSONB key exists (?)
64 JsonbKeyExists,
65 /// JSONB any key exists (?|)
66 JsonbAnyKeyExists,
67 /// JSONB all keys exist (?&)
68 JsonbAllKeysExist,
69 /// JSONB path exists (@?)
70 JsonbPathExists,
71 // Other operators
72 /// Is null check
73 IsNull,
74 /// Is not null check
75 IsNotNull,
76 /// Range contains value (@>)
77 RangeContains,
78 /// Value is within range (<@)
79 RangeContainedBy,
80 /// Range overlaps (&&)
81 RangeOverlaps,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85/// Defines possible filter value values.
86pub enum FilterValue {
87 /// String variant.
88 String(String),
89 /// Integer variant.
90 Integer(i64),
91 /// Alias for Integer (for compatibility with test code)
92 Int(i64),
93 /// Float variant.
94 Float(f64),
95 /// Boolean variant.
96 Boolean(bool),
97 /// Alias for Boolean (for compatibility with test code)
98 Bool(bool),
99 /// Null variant.
100 Null,
101 /// Array variant.
102 Array(Vec<String>),
103 /// Field reference for field-to-field comparisons (e.g., WHERE discount_price < total_price)
104 FieldRef(super::expressions::F),
105 /// Arithmetic expression (e.g., WHERE total != unit_price * quantity)
106 Expression(super::annotation::Expression),
107 /// Outer query reference for correlated subqueries (e.g., WHERE books.author_id = OuterRef("authors.id"))
108 OuterRef(super::expressions::OuterRef),
109}
110
111#[derive(Debug, Clone)]
112/// Represents a filter.
113pub struct Filter {
114 /// The field.
115 pub field: String,
116 /// The operator.
117 pub operator: FilterOperator,
118 /// The value.
119 pub value: FilterValue,
120}
121
122impl Filter {
123 /// Creates a new instance.
124 pub fn new(field: impl Into<String>, operator: FilterOperator, value: FilterValue) -> Self {
125 Self {
126 field: field.into(),
127 operator,
128 value,
129 }
130 }
131}
132
133/// Values that can be used in UPDATE statements
134#[derive(Debug, Clone)]
135pub enum UpdateValue {
136 /// String variant.
137 String(String),
138 /// Integer variant.
139 Integer(i64),
140 /// Float variant.
141 Float(f64),
142 /// Boolean variant.
143 Boolean(bool),
144 /// Null variant.
145 Null,
146 /// Field reference for field-to-field updates (e.g., SET discount_price = total_price)
147 FieldRef(super::expressions::F),
148 /// Arithmetic expression (e.g., SET total = unit_price * quantity)
149 Expression(super::annotation::Expression),
150}
151
152/// Composite filter condition supporting AND/OR logic
153///
154/// This enum allows building complex filter expressions with nested AND/OR conditions.
155/// It's particularly useful for search functionality that needs to match across
156/// multiple fields using OR logic.
157///
158/// # Examples
159///
160/// ```
161/// use reinhardt_db::orm::{Filter, FilterCondition, FilterOperator, FilterValue};
162///
163/// // Simple single filter
164/// let single = FilterCondition::Single(Filter::new(
165/// "name".to_string(),
166/// FilterOperator::Eq,
167/// FilterValue::String("Alice".to_string()),
168/// ));
169///
170/// // OR condition across multiple fields (useful for search)
171/// let search = FilterCondition::Or(vec![
172/// FilterCondition::Single(Filter::new(
173/// "name".to_string(),
174/// FilterOperator::Contains,
175/// FilterValue::String("alice".to_string()),
176/// )),
177/// FilterCondition::Single(Filter::new(
178/// "email".to_string(),
179/// FilterOperator::Contains,
180/// FilterValue::String("alice".to_string()),
181/// )),
182/// ]);
183///
184/// // Complex nested condition: (status = 'active') AND (name LIKE '%alice%' OR email LIKE '%alice%')
185/// let complex = FilterCondition::And(vec![
186/// FilterCondition::Single(Filter::new(
187/// "status".to_string(),
188/// FilterOperator::Eq,
189/// FilterValue::String("active".to_string()),
190/// )),
191/// search,
192/// ]);
193/// ```
194#[derive(Debug, Clone)]
195pub enum FilterCondition {
196 /// A single filter expression
197 Single(Filter),
198 /// All conditions must match (AND logic)
199 And(Vec<FilterCondition>),
200 /// Any condition must match (OR logic)
201 Or(Vec<FilterCondition>),
202 /// Negates the inner condition (NOT logic)
203 Not(Box<FilterCondition>),
204}
205
206impl FilterCondition {
207 /// Create a single filter condition
208 pub fn single(filter: Filter) -> Self {
209 Self::Single(filter)
210 }
211
212 /// Create an AND condition from multiple conditions
213 pub fn and(conditions: Vec<FilterCondition>) -> Self {
214 Self::And(conditions)
215 }
216
217 /// Create an OR condition from multiple conditions
218 pub fn or(conditions: Vec<FilterCondition>) -> Self {
219 Self::Or(conditions)
220 }
221
222 /// Create a NOT condition that negates the given condition
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// use reinhardt_db::orm::{Filter, FilterCondition, FilterOperator, FilterValue};
228 ///
229 /// let condition = FilterCondition::not(
230 /// FilterCondition::Single(Filter::new(
231 /// "is_active".to_string(),
232 /// FilterOperator::Eq,
233 /// FilterValue::Boolean(true),
234 /// ))
235 /// );
236 /// ```
237 // This method is intentionally named `not` for API consistency with Django's Q object.
238 // It does not implement std::ops::Not because it constructs a FilterCondition variant,
239 // not a boolean negation.
240 #[allow(clippy::should_implement_trait)]
241 pub fn not(condition: FilterCondition) -> Self {
242 Self::Not(Box::new(condition))
243 }
244
245 /// Create an OR condition from multiple filters (convenience method for search)
246 ///
247 /// This is particularly useful for implementing search across multiple fields.
248 ///
249 /// # Examples
250 ///
251 /// ```
252 /// use reinhardt_db::orm::{Filter, FilterCondition, FilterOperator, FilterValue};
253 ///
254 /// let search_filters = vec![
255 /// Filter::new("name".to_string(), FilterOperator::Contains, FilterValue::String("test".to_string())),
256 /// Filter::new("email".to_string(), FilterOperator::Contains, FilterValue::String("test".to_string())),
257 /// ];
258 /// let or_condition = FilterCondition::or_filters(search_filters);
259 /// ```
260 pub fn or_filters(filters: Vec<Filter>) -> Self {
261 Self::Or(filters.into_iter().map(FilterCondition::Single).collect())
262 }
263
264 /// Create an AND condition from multiple filters
265 pub fn and_filters(filters: Vec<Filter>) -> Self {
266 Self::And(filters.into_iter().map(FilterCondition::Single).collect())
267 }
268
269 /// Check if this condition is empty (no actual filters)
270 pub fn is_empty(&self) -> bool {
271 match self {
272 FilterCondition::Single(_) => false,
273 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
274 conditions.is_empty() || conditions.iter().all(|c| c.is_empty())
275 }
276 FilterCondition::Not(condition) => condition.is_empty(),
277 }
278 }
279}
280
281// From implementations for FilterValue
282impl From<String> for FilterValue {
283 fn from(s: String) -> Self {
284 FilterValue::String(s)
285 }
286}
287
288impl From<&str> for FilterValue {
289 fn from(s: &str) -> Self {
290 FilterValue::String(s.to_string())
291 }
292}
293
294impl From<i64> for FilterValue {
295 fn from(i: i64) -> Self {
296 FilterValue::Integer(i)
297 }
298}
299
300impl From<i32> for FilterValue {
301 fn from(i: i32) -> Self {
302 FilterValue::Integer(i as i64)
303 }
304}
305
306impl From<f64> for FilterValue {
307 fn from(f: f64) -> Self {
308 FilterValue::Float(f)
309 }
310}
311
312impl From<bool> for FilterValue {
313 fn from(b: bool) -> Self {
314 FilterValue::Boolean(b)
315 }
316}
317
318impl From<uuid::Uuid> for FilterValue {
319 fn from(u: uuid::Uuid) -> Self {
320 FilterValue::String(u.to_string())
321 }
322}
323
324#[derive(Debug, Clone)]
325/// Represents a orm query.
326pub struct OrmQuery {
327 filters: Vec<Filter>,
328}
329
330impl OrmQuery {
331 /// Creates a new instance.
332 pub fn new() -> Self {
333 Self {
334 filters: Vec::new(),
335 }
336 }
337
338 /// Performs the filter operation.
339 pub fn filter(mut self, filter: Filter) -> Self {
340 self.filters.push(filter);
341 self
342 }
343}
344
345impl Default for OrmQuery {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351/// JOIN clause specification for QuerySet
352#[derive(Clone, Debug)]
353struct JoinClause {
354 /// The type of JOIN (INNER, LEFT, RIGHT, CROSS)
355 join_type: super::sqlalchemy_query::JoinType,
356 /// The name of the table to join
357 target_table: String,
358 /// Optional alias for the target table (for self-joins)
359 target_alias: Option<String>,
360 /// The ON condition as a SQL expression string
361 /// Format: "left_table.left_field = right_table.right_field"
362 /// Can include table aliases for self-joins (e.g., "u1.id < u2.id")
363 on_condition: String,
364}
365
366/// Aggregate function types for HAVING clauses
367#[derive(Clone, Debug)]
368enum AggregateFunc {
369 Avg,
370 Count,
371 Sum,
372 Min,
373 Max,
374}
375
376/// Comparison operators for HAVING clauses
377#[derive(Clone, Debug)]
378pub enum ComparisonOp {
379 /// Eq variant.
380 Eq,
381 /// Ne variant.
382 Ne,
383 /// Gt variant.
384 Gt,
385 /// Gte variant.
386 Gte,
387 /// Lt variant.
388 Lt,
389 /// Lte variant.
390 Lte,
391}
392
393/// Value types for aggregate comparisons in HAVING clauses
394#[derive(Clone, Debug)]
395enum AggregateValue {
396 Int(i64),
397 Float(f64),
398}
399
400/// HAVING clause condition specification
401#[derive(Clone, Debug)]
402enum HavingCondition {
403 /// Compare an aggregate function result with a value
404 /// Example: HAVING AVG(price) > 1500.0
405 AggregateCompare {
406 func: AggregateFunc,
407 field: String,
408 operator: ComparisonOp,
409 value: AggregateValue,
410 },
411}
412
413/// Subquery condition specification for WHERE clause
414#[derive(Clone, Debug)]
415enum SubqueryCondition {
416 /// WHERE field IN (subquery)
417 /// Example: WHERE author_id IN (SELECT id FROM authors WHERE name = 'John')
418 In { field: String, subquery: String },
419 /// WHERE field NOT IN (subquery)
420 NotIn { field: String, subquery: String },
421 /// WHERE EXISTS (subquery)
422 /// Example: WHERE EXISTS (SELECT 1 FROM books WHERE author_id = authors.id)
423 Exists { subquery: String },
424 /// WHERE NOT EXISTS (subquery)
425 NotExists { subquery: String },
426}
427
428#[derive(Clone)]
429/// Represents a query set.
430pub struct QuerySet<T>
431where
432 T: super::Model,
433{
434 _phantom: std::marker::PhantomData<T>,
435 filters: SmallVec<[Filter; 10]>,
436 select_related_fields: Vec<String>,
437 prefetch_related_fields: Vec<String>,
438 order_by_fields: Vec<String>,
439 distinct_enabled: bool,
440 selected_fields: Option<Vec<String>>,
441 deferred_fields: Vec<String>,
442 annotations: Vec<super::annotation::Annotation>,
443 manager: Option<std::sync::Arc<super::manager::Manager<T>>>,
444 limit: Option<usize>,
445 offset: Option<usize>,
446 ctes: super::cte::CTECollection,
447 lateral_joins: super::lateral_join::LateralJoins,
448 joins: Vec<JoinClause>,
449 group_by_fields: Vec<String>,
450 having_conditions: Vec<HavingCondition>,
451 subquery_conditions: Vec<SubqueryCondition>,
452 from_alias: Option<String>,
453 /// Subquery SQL for FROM clause (derived table)
454 /// When set, the FROM clause will use this subquery instead of the model's table
455 from_subquery_sql: Option<String>,
456}
457
458impl<T> QuerySet<T>
459where
460 T: super::Model,
461{
462 /// Creates a new instance.
463 pub fn new() -> Self {
464 Self {
465 _phantom: std::marker::PhantomData,
466 filters: SmallVec::new(),
467 select_related_fields: Vec::new(),
468 prefetch_related_fields: Vec::new(),
469 order_by_fields: Vec::new(),
470 distinct_enabled: false,
471 selected_fields: None,
472 deferred_fields: Vec::new(),
473 annotations: Vec::new(),
474 manager: None,
475 limit: None,
476 offset: None,
477 ctes: super::cte::CTECollection::new(),
478 lateral_joins: super::lateral_join::LateralJoins::new(),
479 joins: Vec::new(),
480 group_by_fields: Vec::new(),
481 having_conditions: Vec::new(),
482 subquery_conditions: Vec::new(),
483 from_alias: None,
484 from_subquery_sql: None,
485 }
486 }
487
488 /// Sets the manager and returns self for chaining.
489 pub fn with_manager(manager: std::sync::Arc<super::manager::Manager<T>>) -> Self {
490 Self {
491 _phantom: std::marker::PhantomData,
492 filters: SmallVec::new(),
493 select_related_fields: Vec::new(),
494 prefetch_related_fields: Vec::new(),
495 order_by_fields: Vec::new(),
496 distinct_enabled: false,
497 selected_fields: None,
498 deferred_fields: Vec::new(),
499 annotations: Vec::new(),
500 manager: Some(manager),
501 limit: None,
502 offset: None,
503 ctes: super::cte::CTECollection::new(),
504 lateral_joins: super::lateral_join::LateralJoins::new(),
505 joins: Vec::new(),
506 group_by_fields: Vec::new(),
507 having_conditions: Vec::new(),
508 subquery_conditions: Vec::new(),
509 from_alias: None,
510 from_subquery_sql: None,
511 }
512 }
513
514 /// Performs the filter operation.
515 pub fn filter(mut self, filter: Filter) -> Self {
516 self.filters.push(filter);
517 self
518 }
519
520 /// Returns the filters that have been applied to this `QuerySet`.
521 ///
522 /// Useful for inspection in tests and for custom managers that need to
523 /// observe or assert on the active filter chain (Issue #3980).
524 pub fn filters(&self) -> &[Filter] {
525 &self.filters
526 }
527
528 /// Create a QuerySet from a subquery (FROM clause subquery / derived table)
529 ///
530 /// This method creates a new QuerySet that uses a subquery as its data source
531 /// instead of a regular table. The subquery becomes a derived table in the FROM clause.
532 ///
533 /// # Type Parameters
534 ///
535 /// * `M` - The model type for the subquery
536 /// * `F` - A closure that builds the subquery
537 ///
538 /// # Parameters
539 ///
540 /// * `builder` - A closure that receives a fresh `QuerySet<M>` and returns a configured QuerySet
541 /// * `alias` - The alias for the derived table (required for FROM subqueries)
542 ///
543 /// # Examples
544 ///
545 /// ```
546 /// # use reinhardt_db::orm::{Model, QuerySet};
547 /// # use reinhardt_db::orm::annotation::{Annotation, AnnotationValue};
548 /// # use reinhardt_db::orm::aggregation::Aggregate;
549 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
550 /// # use reinhardt_db::orm::GroupByFields;
551 /// # use serde::{Serialize, Deserialize};
552 /// # #[derive(Clone, Serialize, Deserialize)]
553 /// # struct Book { id: Option<i64>, author_id: Option<i64> }
554 /// # #[derive(Clone)]
555 /// # struct BookFields;
556 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
557 /// # fn with_alias(self, _alias: &str) -> Self { self }
558 /// # }
559 /// # impl Model for Book {
560 /// # type PrimaryKey = i64;
561 /// # type Fields = BookFields;
562 /// # fn table_name() -> &'static str { "books" }
563 /// # fn new_fields() -> Self::Fields { BookFields }
564 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
565 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
566 /// # }
567 /// // Query from a derived table showing author book counts
568 /// let results = QuerySet::<Book>::from_subquery(
569 /// |subq: QuerySet<Book>| {
570 /// subq.values(&["author_id"])
571 /// .annotate(Annotation::new("book_count", AnnotationValue::Aggregate(Aggregate::count_all())))
572 /// },
573 /// "book_stats"
574 /// )
575 /// .filter(Filter::new("book_count", FilterOperator::Gt, FilterValue::Int(1)))
576 /// .to_sql();
577 /// // Generates: SELECT * FROM (SELECT author_id, COUNT(*) AS book_count FROM books GROUP BY author_id) AS book_stats WHERE book_count > 1
578 /// ```
579 pub fn from_subquery<M, F>(builder: F, alias: &str) -> Self
580 where
581 M: super::Model + 'static,
582 F: FnOnce(QuerySet<M>) -> QuerySet<M>,
583 {
584 // Create a fresh QuerySet for the subquery model
585 let subquery_qs = QuerySet::<M>::new();
586 // Apply the builder to configure the subquery
587 let configured_subquery = builder(subquery_qs);
588 // Generate SQL for the subquery (wrapped in parentheses)
589 let subquery_sql = configured_subquery.as_subquery();
590
591 // Create a new QuerySet with the subquery as FROM source
592 Self {
593 _phantom: std::marker::PhantomData,
594 filters: SmallVec::new(),
595 select_related_fields: Vec::new(),
596 prefetch_related_fields: Vec::new(),
597 order_by_fields: Vec::new(),
598 distinct_enabled: false,
599 selected_fields: None,
600 deferred_fields: Vec::new(),
601 annotations: Vec::new(),
602 manager: None,
603 limit: None,
604 offset: None,
605 ctes: super::cte::CTECollection::new(),
606 lateral_joins: super::lateral_join::LateralJoins::new(),
607 joins: Vec::new(),
608 group_by_fields: Vec::new(),
609 having_conditions: Vec::new(),
610 subquery_conditions: Vec::new(),
611 from_alias: Some(alias.to_string()),
612 from_subquery_sql: Some(subquery_sql),
613 }
614 }
615
616 /// Add an INNER JOIN to the query
617 ///
618 /// Performs an INNER JOIN between the current model (T) and another model (R).
619 /// Only rows with matching values in both tables are included in the result.
620 ///
621 /// # Type Parameters
622 ///
623 /// * `R` - The model type to join with (must implement `Model` trait)
624 ///
625 /// # Parameters
626 ///
627 /// * `left_field` - The field name from the left table (current model T)
628 /// * `right_field` - The field name from the right table (model R)
629 ///
630 /// # Examples
631 ///
632 /// ```no_run
633 /// # use reinhardt_db::orm::Model;
634 /// # use serde::{Serialize, Deserialize};
635 /// # #[derive(Clone, Serialize, Deserialize)]
636 /// # struct User { id: Option<i64> }
637 /// # #[derive(Clone)]
638 /// # struct UserFields;
639 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
640 /// # fn with_alias(self, _alias: &str) -> Self { self }
641 /// # }
642 /// # impl Model for User {
643 /// # type PrimaryKey = i64;
644 /// # type Fields = UserFields;
645 /// # fn table_name() -> &'static str { "users" }
646 /// # fn new_fields() -> Self::Fields { UserFields }
647 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
648 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
649 /// # }
650 /// # #[derive(Clone, Serialize, Deserialize)]
651 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
652 /// # #[derive(Clone)]
653 /// # struct PostFields;
654 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
655 /// # fn with_alias(self, _alias: &str) -> Self { self }
656 /// # }
657 /// # impl Model for Post {
658 /// # type PrimaryKey = i64;
659 /// # type Fields = PostFields;
660 /// # fn table_name() -> &'static str { "posts" }
661 /// # fn new_fields() -> Self::Fields { PostFields }
662 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
663 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
664 /// # }
665 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
666 /// // Join User and Post on user.id = post.user_id
667 /// let sql = User::objects()
668 /// .all()
669 /// .inner_join::<Post>("id", "user_id")
670 /// .to_sql();
671 /// # Ok(())
672 /// # }
673 /// ```
674 pub fn inner_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
675 let condition = format!(
676 "{}.{} = {}.{}",
677 T::table_name(),
678 left_field,
679 R::table_name(),
680 right_field
681 );
682
683 self.joins.push(JoinClause {
684 join_type: super::sqlalchemy_query::JoinType::Inner,
685 target_table: R::table_name().to_string(),
686 target_alias: None,
687 on_condition: condition,
688 });
689
690 self
691 }
692
693 /// Add a LEFT OUTER JOIN to the query
694 ///
695 /// Performs a LEFT OUTER JOIN between the current model (T) and another model (R).
696 /// All rows from the left table are included, with matching rows from the right table
697 /// or NULL values if no match is found.
698 ///
699 /// # Examples
700 ///
701 /// ```no_run
702 /// # use reinhardt_db::orm::Model;
703 /// # use serde::{Serialize, Deserialize};
704 /// # #[derive(Clone, Serialize, Deserialize)]
705 /// # struct User { id: Option<i64> }
706 /// # #[derive(Clone)]
707 /// # struct UserFields;
708 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
709 /// # fn with_alias(self, _alias: &str) -> Self { self }
710 /// # }
711 /// # impl Model for User {
712 /// # type PrimaryKey = i64;
713 /// # type Fields = UserFields;
714 /// # fn table_name() -> &'static str { "users" }
715 /// # fn new_fields() -> Self::Fields { UserFields }
716 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
717 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
718 /// # }
719 /// # #[derive(Clone, Serialize, Deserialize)]
720 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
721 /// # #[derive(Clone)]
722 /// # struct PostFields;
723 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
724 /// # fn with_alias(self, _alias: &str) -> Self { self }
725 /// # }
726 /// # impl Model for Post {
727 /// # type PrimaryKey = i64;
728 /// # type Fields = PostFields;
729 /// # fn table_name() -> &'static str { "posts" }
730 /// # fn new_fields() -> Self::Fields { PostFields }
731 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
732 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
733 /// # }
734 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
735 /// // Left join User and Post
736 /// let sql = User::objects()
737 /// .all()
738 /// .left_join::<Post>("id", "user_id")
739 /// .to_sql();
740 /// # Ok(())
741 /// # }
742 /// ```
743 pub fn left_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
744 let condition = format!(
745 "{}.{} = {}.{}",
746 T::table_name(),
747 left_field,
748 R::table_name(),
749 right_field
750 );
751
752 self.joins.push(JoinClause {
753 join_type: super::sqlalchemy_query::JoinType::Left,
754 target_table: R::table_name().to_string(),
755 target_alias: None,
756 on_condition: condition,
757 });
758
759 self
760 }
761
762 /// Add a RIGHT OUTER JOIN to the query
763 ///
764 /// Performs a RIGHT OUTER JOIN between the current model (T) and another model (R).
765 /// All rows from the right table are included, with matching rows from the left table
766 /// or NULL values if no match is found.
767 ///
768 /// # Examples
769 ///
770 /// ```no_run
771 /// # use reinhardt_db::orm::Model;
772 /// # use serde::{Serialize, Deserialize};
773 /// # #[derive(Clone, Serialize, Deserialize)]
774 /// # struct User { id: Option<i64> }
775 /// # #[derive(Clone)]
776 /// # struct UserFields;
777 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
778 /// # fn with_alias(self, _alias: &str) -> Self { self }
779 /// # }
780 /// # impl Model for User {
781 /// # type PrimaryKey = i64;
782 /// # type Fields = UserFields;
783 /// # fn table_name() -> &'static str { "users" }
784 /// # fn new_fields() -> Self::Fields { UserFields }
785 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
786 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
787 /// # }
788 /// # #[derive(Clone, Serialize, Deserialize)]
789 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
790 /// # #[derive(Clone)]
791 /// # struct PostFields;
792 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
793 /// # fn with_alias(self, _alias: &str) -> Self { self }
794 /// # }
795 /// # impl Model for Post {
796 /// # type PrimaryKey = i64;
797 /// # type Fields = PostFields;
798 /// # fn table_name() -> &'static str { "posts" }
799 /// # fn new_fields() -> Self::Fields { PostFields }
800 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
801 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
802 /// # }
803 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
804 /// // Right join User and Post
805 /// let sql = User::objects()
806 /// .all()
807 /// .right_join::<Post>("id", "user_id")
808 /// .to_sql();
809 /// # Ok(())
810 /// # }
811 /// ```
812 pub fn right_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
813 let condition = format!(
814 "{}.{} = {}.{}",
815 T::table_name(),
816 left_field,
817 R::table_name(),
818 right_field
819 );
820
821 self.joins.push(JoinClause {
822 join_type: super::sqlalchemy_query::JoinType::Right,
823 target_table: R::table_name().to_string(),
824 target_alias: None,
825 on_condition: condition,
826 });
827
828 self
829 }
830
831 /// Add a CROSS JOIN to the query
832 ///
833 /// Performs a CROSS JOIN between the current model (T) and another model (R).
834 /// Produces the Cartesian product of both tables (all possible combinations).
835 /// No ON condition is needed for CROSS JOIN.
836 ///
837 /// # Examples
838 ///
839 /// ```no_run
840 /// # use reinhardt_db::orm::Model;
841 /// # use serde::{Serialize, Deserialize};
842 /// # #[derive(Clone, Serialize, Deserialize)]
843 /// # struct User { id: Option<i64> }
844 /// # #[derive(Clone)]
845 /// # struct UserFields;
846 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
847 /// # fn with_alias(self, _alias: &str) -> Self { self }
848 /// # }
849 /// # impl Model for User {
850 /// # type PrimaryKey = i64;
851 /// # type Fields = UserFields;
852 /// # fn table_name() -> &'static str { "users" }
853 /// # fn new_fields() -> Self::Fields { UserFields }
854 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
855 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
856 /// # }
857 /// # #[derive(Clone, Serialize, Deserialize)]
858 /// # struct Category { id: Option<i64> }
859 /// # #[derive(Clone)]
860 /// # struct CategoryFields;
861 /// # impl reinhardt_db::orm::model::FieldSelector for CategoryFields {
862 /// # fn with_alias(self, _alias: &str) -> Self { self }
863 /// # }
864 /// # impl Model for Category {
865 /// # type PrimaryKey = i64;
866 /// # type Fields = CategoryFields;
867 /// # fn table_name() -> &'static str { "categories" }
868 /// # fn new_fields() -> Self::Fields { CategoryFields }
869 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
870 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
871 /// # }
872 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
873 /// // Cross join User and Category
874 /// let sql = User::objects()
875 /// .all()
876 /// .cross_join::<Category>()
877 /// .to_sql();
878 /// # Ok(())
879 /// # }
880 /// ```
881 pub fn cross_join<R: super::Model>(mut self) -> Self {
882 self.joins.push(JoinClause {
883 join_type: super::sqlalchemy_query::JoinType::Inner, // CROSS JOIN uses Inner with empty condition
884 target_table: R::table_name().to_string(),
885 target_alias: None,
886 on_condition: String::new(), // Empty condition for CROSS JOIN
887 });
888
889 self
890 }
891
892 /// Set an alias for the base table (FROM clause)
893 ///
894 /// This is useful for self-joins where you need to reference the same table multiple times.
895 ///
896 /// # Parameters
897 ///
898 /// * `alias` - The alias name for the base table
899 ///
900 /// # Examples
901 ///
902 /// ```
903 /// # use reinhardt_db::orm::Model;
904 /// # use reinhardt_db::orm::query_fields::Field;
905 /// # use reinhardt_db::orm::FieldSelector;
906 /// # use serde::{Serialize, Deserialize};
907 /// # #[derive(Clone, Serialize, Deserialize)]
908 /// # struct User { id: Option<i64> }
909 /// #
910 /// # #[derive(Clone)]
911 /// # struct UserFields {
912 /// # pub id: Field<User, i64>,
913 /// # }
914 /// # impl UserFields {
915 /// # pub fn new() -> Self {
916 /// # Self { id: Field::new(vec!["id"]) }
917 /// # }
918 /// # }
919 /// # impl FieldSelector for UserFields {
920 /// # fn with_alias(mut self, alias: &str) -> Self {
921 /// # self.id = self.id.with_alias(alias);
922 /// # self
923 /// # }
924 /// # }
925 /// # impl Model for User {
926 /// # type PrimaryKey = i64;
927 /// # type Fields = UserFields;
928 /// # fn table_name() -> &'static str { "users" }
929 /// # fn new_fields() -> Self::Fields { UserFields::new() }
930 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
931 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
932 /// # }
933 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
934 /// // Self-join: find user pairs
935 /// let sql = User::objects()
936 /// .all()
937 /// .from_as("u1")
938 /// .inner_join_as::<User, _>("u1", "u2", |left, right| left.id.field_lt(right.id))
939 /// .to_sql();
940 /// # Ok(())
941 /// # }
942 /// ```
943 pub fn from_as(mut self, alias: &str) -> Self {
944 self.from_alias = Some(alias.to_string());
945 self
946 }
947
948 /// Add an INNER JOIN with custom condition
949 ///
950 /// Performs an INNER JOIN with a custom ON condition expression.
951 /// Use this when you need complex join conditions beyond simple equality.
952 ///
953 /// # Type Parameters
954 ///
955 /// * `R` - The model type to join with (must implement `Model` trait)
956 ///
957 /// # Parameters
958 ///
959 /// * `condition` - Custom SQL condition for the JOIN (e.g., "users.id = posts.user_id AND posts.status = 'published'")
960 ///
961 /// # Examples
962 ///
963 /// ```no_run
964 /// # use reinhardt_db::orm::Model;
965 /// # use serde::{Serialize, Deserialize};
966 /// # #[derive(Clone, Serialize, Deserialize)]
967 /// # struct User { id: Option<i64> }
968 /// # #[derive(Clone)]
969 /// # struct UserFields;
970 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
971 /// # fn with_alias(self, _alias: &str) -> Self { self }
972 /// # }
973 /// # impl Model for User {
974 /// # type PrimaryKey = i64;
975 /// # type Fields = UserFields;
976 /// # fn table_name() -> &'static str { "users" }
977 /// # fn new_fields() -> Self::Fields { UserFields }
978 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
979 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
980 /// # }
981 /// # #[derive(Clone, Serialize, Deserialize)]
982 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
983 /// # #[derive(Clone)]
984 /// # struct PostFields;
985 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
986 /// # fn with_alias(self, _alias: &str) -> Self { self }
987 /// # }
988 /// # impl Model for Post {
989 /// # type PrimaryKey = i64;
990 /// # type Fields = PostFields;
991 /// # fn table_name() -> &'static str { "posts" }
992 /// # fn new_fields() -> Self::Fields { PostFields }
993 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
994 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
995 /// # }
996 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
997 /// // Join with complex condition
998 /// let sql = User::objects()
999 /// .all()
1000 /// .inner_join_on::<Post>("users.id = posts.user_id AND posts.title LIKE 'First%'")
1001 /// .to_sql();
1002 /// # Ok(())
1003 /// # }
1004 /// ```
1005 pub fn inner_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1006 self.joins.push(JoinClause {
1007 join_type: super::sqlalchemy_query::JoinType::Inner,
1008 target_table: R::table_name().to_string(),
1009 target_alias: None,
1010 on_condition: condition.to_string(),
1011 });
1012
1013 self
1014 }
1015
1016 /// Add a LEFT OUTER JOIN with custom condition
1017 ///
1018 /// Similar to `inner_join_on()` but performs a LEFT OUTER JOIN.
1019 ///
1020 /// # Type Parameters
1021 ///
1022 /// * `R` - The model type to join with (must implement `Model` trait)
1023 ///
1024 /// # Parameters
1025 ///
1026 /// * `condition` - Custom SQL condition for the JOIN
1027 ///
1028 /// # Examples
1029 ///
1030 /// ```no_run
1031 /// # use reinhardt_db::orm::Model;
1032 /// # use serde::{Serialize, Deserialize};
1033 /// # #[derive(Clone, Serialize, Deserialize)]
1034 /// # struct User { id: Option<i64> }
1035 /// # #[derive(Clone)]
1036 /// # struct UserFields;
1037 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
1038 /// # fn with_alias(self, _alias: &str) -> Self { self }
1039 /// # }
1040 /// # impl Model for User {
1041 /// # type PrimaryKey = i64;
1042 /// # type Fields = UserFields;
1043 /// # fn table_name() -> &'static str { "users" }
1044 /// # fn new_fields() -> Self::Fields { UserFields }
1045 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1046 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1047 /// # }
1048 /// # #[derive(Clone, Serialize, Deserialize)]
1049 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
1050 /// # #[derive(Clone)]
1051 /// # struct PostFields;
1052 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
1053 /// # fn with_alias(self, _alias: &str) -> Self { self }
1054 /// # }
1055 /// # impl Model for Post {
1056 /// # type PrimaryKey = i64;
1057 /// # type Fields = PostFields;
1058 /// # fn table_name() -> &'static str { "posts" }
1059 /// # fn new_fields() -> Self::Fields { PostFields }
1060 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1061 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1062 /// # }
1063 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1064 /// let sql = User::objects()
1065 /// .all()
1066 /// .left_join_on::<Post>("users.id = posts.user_id AND posts.published = true")
1067 /// .to_sql();
1068 /// # Ok(())
1069 /// # }
1070 /// ```
1071 pub fn left_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1072 self.joins.push(JoinClause {
1073 join_type: super::sqlalchemy_query::JoinType::Left,
1074 target_table: R::table_name().to_string(),
1075 target_alias: None,
1076 on_condition: condition.to_string(),
1077 });
1078
1079 self
1080 }
1081
1082 /// Add a RIGHT OUTER JOIN with custom condition
1083 ///
1084 /// Similar to `inner_join_on()` but performs a RIGHT OUTER JOIN.
1085 ///
1086 /// # Type Parameters
1087 ///
1088 /// * `R` - The model type to join with (must implement `Model` trait)
1089 ///
1090 /// # Parameters
1091 ///
1092 /// * `condition` - Custom SQL condition for the JOIN
1093 ///
1094 /// # Examples
1095 ///
1096 /// ```no_run
1097 /// # use reinhardt_db::orm::Model;
1098 /// # use serde::{Serialize, Deserialize};
1099 /// # #[derive(Clone, Serialize, Deserialize)]
1100 /// # struct User { id: Option<i64> }
1101 /// # #[derive(Clone)]
1102 /// # struct UserFields;
1103 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
1104 /// # fn with_alias(self, _alias: &str) -> Self { self }
1105 /// # }
1106 /// # impl Model for User {
1107 /// # type PrimaryKey = i64;
1108 /// # type Fields = UserFields;
1109 /// # fn table_name() -> &'static str { "users" }
1110 /// # fn new_fields() -> Self::Fields { UserFields }
1111 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1112 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1113 /// # }
1114 /// # #[derive(Clone, Serialize, Deserialize)]
1115 /// # struct Post { id: Option<i64>, user_id: Option<i64> }
1116 /// # #[derive(Clone)]
1117 /// # struct PostFields;
1118 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
1119 /// # fn with_alias(self, _alias: &str) -> Self { self }
1120 /// # }
1121 /// # impl Model for Post {
1122 /// # type PrimaryKey = i64;
1123 /// # type Fields = PostFields;
1124 /// # fn table_name() -> &'static str { "posts" }
1125 /// # fn new_fields() -> Self::Fields { PostFields }
1126 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1127 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1128 /// # }
1129 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1130 /// let sql = User::objects()
1131 /// .all()
1132 /// .right_join_on::<Post>("users.id = posts.user_id AND users.active = true")
1133 /// .to_sql();
1134 /// # Ok(())
1135 /// # }
1136 /// ```
1137 pub fn right_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1138 self.joins.push(JoinClause {
1139 join_type: super::sqlalchemy_query::JoinType::Right,
1140 target_table: R::table_name().to_string(),
1141 target_alias: None,
1142 on_condition: condition.to_string(),
1143 });
1144
1145 self
1146 }
1147
1148 /// Add an INNER JOIN with table alias
1149 ///
1150 /// Performs an INNER JOIN with an alias for the target table.
1151 /// Useful for self-joins or when you need to reference the same table multiple times.
1152 ///
1153 /// # Type Parameters
1154 ///
1155 /// * `R` - The model type to join with (must implement `Model` trait)
1156 /// * `F` - Closure that builds the JOIN ON condition
1157 ///
1158 /// # Parameters
1159 ///
1160 /// * `alias` - Alias name for the target table
1161 /// * `condition_fn` - Closure that receives a `JoinOnBuilder` and returns it with the condition set
1162 ///
1163 /// # Examples
1164 ///
1165 /// ```
1166 /// # use reinhardt_db::orm::Model;
1167 /// # use reinhardt_db::orm::query_fields::Field;
1168 /// # use reinhardt_db::orm::FieldSelector;
1169 /// # use serde::{Serialize, Deserialize};
1170 /// # #[derive(Clone, Serialize, Deserialize)]
1171 /// # struct User { id: Option<i64> }
1172 /// #
1173 /// # #[derive(Clone)]
1174 /// # struct UserFields {
1175 /// # pub id: Field<User, i64>,
1176 /// # }
1177 /// # impl UserFields {
1178 /// # pub fn new() -> Self {
1179 /// # Self { id: Field::new(vec!["id"]) }
1180 /// # }
1181 /// # }
1182 /// # impl FieldSelector for UserFields {
1183 /// # fn with_alias(mut self, alias: &str) -> Self {
1184 /// # self.id = self.id.with_alias(alias);
1185 /// # self
1186 /// # }
1187 /// # }
1188 /// # impl Model for User {
1189 /// # type PrimaryKey = i64;
1190 /// # type Fields = UserFields;
1191 /// # fn table_name() -> &'static str { "users" }
1192 /// # fn new_fields() -> Self::Fields { UserFields::new() }
1193 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1194 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1195 /// # }
1196 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1197 /// // Self-join: find user pairs where user1.id < user2.id
1198 /// let sql = User::objects()
1199 /// .all()
1200 /// .inner_join_as::<User, _>("u1", "u2", |u1, u2| u1.id.field_lt(u2.id))
1201 /// .to_sql();
1202 /// # Ok(())
1203 /// # }
1204 /// ```
1205 /// # Breaking Change
1206 ///
1207 /// The signature of this method has been changed from string-based JOIN conditions
1208 /// to type-safe field comparisons.
1209 pub fn inner_join_as<R: super::Model, F>(
1210 mut self,
1211 left_alias: &str,
1212 right_alias: &str,
1213 condition_fn: F,
1214 ) -> Self
1215 where
1216 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
1217 {
1218 // Set base table alias
1219 if self.from_alias.is_none() {
1220 self.from_alias = Some(left_alias.to_string());
1221 }
1222
1223 // Create field selectors and set aliases
1224 let left_fields = T::new_fields().with_alias(left_alias);
1225 let right_fields = R::new_fields().with_alias(right_alias);
1226
1227 // Get comparison expression from closure
1228 let comparison = condition_fn(left_fields, right_fields);
1229
1230 // Convert to SQL
1231 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
1232
1233 // Add to JoinClause
1234 self.joins.push(JoinClause {
1235 join_type: super::sqlalchemy_query::JoinType::Inner,
1236 target_table: R::table_name().to_string(),
1237 target_alias: Some(right_alias.to_string()),
1238 on_condition: condition,
1239 });
1240
1241 self
1242 }
1243
1244 /// Add a LEFT OUTER JOIN with table alias
1245 ///
1246 /// Similar to `inner_join_as()` but performs a LEFT OUTER JOIN.
1247 ///
1248 /// # Type Parameters
1249 ///
1250 /// * `R` - The model type to join with (must implement `Model` trait)
1251 /// * `F` - Closure that builds the JOIN ON condition
1252 ///
1253 /// # Parameters
1254 ///
1255 /// * `alias` - Alias name for the target table
1256 /// * `condition_fn` - Closure that receives a `JoinOnBuilder` and returns it with the condition set
1257 ///
1258 /// # Examples
1259 ///
1260 /// ```
1261 /// # use reinhardt_db::orm::Model;
1262 /// # use reinhardt_db::orm::query_fields::Field;
1263 /// # use reinhardt_db::orm::FieldSelector;
1264 /// # use serde::{Serialize, Deserialize};
1265 /// # #[derive(Clone, Serialize, Deserialize)]
1266 /// # struct User { id: Option<i64> }
1267 /// #
1268 /// # #[derive(Clone)]
1269 /// # struct UserFields {
1270 /// # pub id: Field<User, i64>,
1271 /// # pub manager_id: Field<User, i64>,
1272 /// # }
1273 /// # impl UserFields {
1274 /// # pub fn new() -> Self {
1275 /// # Self {
1276 /// # id: Field::new(vec!["id"]),
1277 /// # manager_id: Field::new(vec!["manager_id"]),
1278 /// # }
1279 /// # }
1280 /// # }
1281 /// # impl FieldSelector for UserFields {
1282 /// # fn with_alias(mut self, alias: &str) -> Self {
1283 /// # self.id = self.id.with_alias(alias);
1284 /// # self.manager_id = self.manager_id.with_alias(alias);
1285 /// # self
1286 /// # }
1287 /// # }
1288 /// # impl Model for User {
1289 /// # type PrimaryKey = i64;
1290 /// # type Fields = UserFields;
1291 /// # fn table_name() -> &'static str { "users" }
1292 /// # fn new_fields() -> Self::Fields { UserFields::new() }
1293 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1294 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1295 /// # }
1296 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1297 /// // Self-join with LEFT JOIN: find employees and their managers
1298 /// let sql = User::objects()
1299 /// .all()
1300 /// .left_join_as::<User, _>("u1", "u2", |u1, u2| u2.id.field_eq(u1.manager_id))
1301 /// .to_sql();
1302 /// # Ok(())
1303 /// # }
1304 /// ```
1305 /// # Breaking Change
1306 ///
1307 /// This method signature has been changed from string-based JOIN conditions
1308 /// to type-safe field comparisons.
1309 pub fn left_join_as<R: super::Model, F>(
1310 mut self,
1311 left_alias: &str,
1312 right_alias: &str,
1313 condition_fn: F,
1314 ) -> Self
1315 where
1316 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
1317 {
1318 // Set base table alias
1319 if self.from_alias.is_none() {
1320 self.from_alias = Some(left_alias.to_string());
1321 }
1322
1323 // Create field selectors with aliases
1324 let left_fields = T::new_fields().with_alias(left_alias);
1325 let right_fields = R::new_fields().with_alias(right_alias);
1326
1327 // Get comparison from closure
1328 let comparison = condition_fn(left_fields, right_fields);
1329
1330 // Convert to SQL
1331 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
1332
1333 // Add to JoinClause
1334 self.joins.push(JoinClause {
1335 join_type: super::sqlalchemy_query::JoinType::Left,
1336 target_table: R::table_name().to_string(),
1337 target_alias: Some(right_alias.to_string()),
1338 on_condition: condition,
1339 });
1340
1341 self
1342 }
1343
1344 /// Add a RIGHT OUTER JOIN with table alias
1345 ///
1346 /// Similar to `inner_join_as()` but performs a RIGHT OUTER JOIN.
1347 ///
1348 /// # Type Parameters
1349 ///
1350 /// * `R` - The model type to join with (must implement `Model` trait)
1351 /// * `F` - Closure that builds the JOIN ON condition
1352 ///
1353 /// # Parameters
1354 ///
1355 /// * `alias` - Alias name for the target table
1356 /// * `condition_fn` - Closure that receives a `JoinOnBuilder` and returns it with the condition set
1357 ///
1358 /// # Examples
1359 ///
1360 /// ```
1361 /// # use reinhardt_db::orm::Model;
1362 /// # use reinhardt_db::orm::query_fields::Field;
1363 /// # use reinhardt_db::orm::FieldSelector;
1364 /// # use serde::{Serialize, Deserialize};
1365 /// # #[derive(Clone, Serialize, Deserialize)]
1366 /// # struct User { id: Option<i64> }
1367 /// #
1368 /// # #[derive(Clone)]
1369 /// # struct UserFields {
1370 /// # pub id: Field<User, i64>,
1371 /// # pub department_id: Field<User, i64>,
1372 /// # }
1373 /// # impl UserFields {
1374 /// # pub fn new() -> Self {
1375 /// # Self {
1376 /// # id: Field::new(vec!["id"]),
1377 /// # department_id: Field::new(vec!["department_id"]),
1378 /// # }
1379 /// # }
1380 /// # }
1381 /// # impl FieldSelector for UserFields {
1382 /// # fn with_alias(mut self, alias: &str) -> Self {
1383 /// # self.id = self.id.with_alias(alias);
1384 /// # self.department_id = self.department_id.with_alias(alias);
1385 /// # self
1386 /// # }
1387 /// # }
1388 /// # impl Model for User {
1389 /// # type PrimaryKey = i64;
1390 /// # type Fields = UserFields;
1391 /// # fn table_name() -> &'static str { "users" }
1392 /// # fn new_fields() -> Self::Fields { UserFields::new() }
1393 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1394 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1395 /// # }
1396 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1397 /// // RIGHT JOIN: find all departments even if no users belong to them
1398 /// let sql = User::objects()
1399 /// .all()
1400 /// .right_join_as::<User, _>("u1", "u2", |u1, u2| u2.id.field_eq(u1.department_id))
1401 /// .to_sql();
1402 /// # Ok(())
1403 /// # }
1404 /// ```
1405 /// # Breaking Change
1406 ///
1407 /// This method signature has been changed from string-based JOIN conditions
1408 /// to type-safe field comparisons.
1409 pub fn right_join_as<R: super::Model, F>(
1410 mut self,
1411 left_alias: &str,
1412 right_alias: &str,
1413 condition_fn: F,
1414 ) -> Self
1415 where
1416 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
1417 {
1418 // Set base table alias
1419 if self.from_alias.is_none() {
1420 self.from_alias = Some(left_alias.to_string());
1421 }
1422
1423 // Create field selectors with aliases
1424 let left_fields = T::new_fields().with_alias(left_alias);
1425 let right_fields = R::new_fields().with_alias(right_alias);
1426
1427 // Get comparison from closure
1428 let comparison = condition_fn(left_fields, right_fields);
1429
1430 // Convert to SQL
1431 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
1432
1433 // Add to JoinClause
1434 self.joins.push(JoinClause {
1435 join_type: super::sqlalchemy_query::JoinType::Right,
1436 target_table: R::table_name().to_string(),
1437 target_alias: Some(right_alias.to_string()),
1438 on_condition: condition,
1439 });
1440
1441 self
1442 }
1443
1444 /// Add GROUP BY clause to the query
1445 ///
1446 /// Groups rows that have the same values in specified columns into summary rows.
1447 /// Typically used with aggregate functions (COUNT, MAX, MIN, SUM, AVG).
1448 ///
1449 /// # Type Parameters
1450 ///
1451 /// * `F` - Closure that builds the GROUP BY field list
1452 ///
1453 /// # Parameters
1454 ///
1455 /// * `builder_fn` - Closure that receives a `GroupByBuilder` and returns it with fields set
1456 ///
1457 /// # Examples
1458 ///
1459 /// ```
1460 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1461 /// # use serde::{Serialize, Deserialize};
1462 /// # #[derive(Clone, Serialize, Deserialize)]
1463 /// # struct Book { id: Option<i64> }
1464 /// #
1465 /// # #[derive(Clone)]
1466 /// # struct BookFields {
1467 /// # pub author_id: Field<Book, i64>,
1468 /// # }
1469 /// # impl BookFields {
1470 /// # pub fn new() -> Self {
1471 /// # Self { author_id: Field::new(vec!["author_id"]) }
1472 /// # }
1473 /// # }
1474 /// # impl FieldSelector for BookFields {
1475 /// # fn with_alias(mut self, alias: &str) -> Self {
1476 /// # self.author_id = self.author_id.with_alias(alias);
1477 /// # self
1478 /// # }
1479 /// # }
1480 /// # impl Model for Book {
1481 /// # type PrimaryKey = i64;
1482 /// # type Fields = BookFields;
1483 /// # fn table_name() -> &'static str { "books" }
1484 /// # fn new_fields() -> Self::Fields { BookFields::new() }
1485 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1486 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1487 /// # }
1488 /// # #[derive(Clone, Serialize, Deserialize)]
1489 /// # struct Sale { id: Option<i64> }
1490 /// #
1491 /// # #[derive(Clone)]
1492 /// # struct SaleFields {
1493 /// # pub region: Field<Sale, String>,
1494 /// # pub product_category: Field<Sale, String>,
1495 /// # }
1496 /// # impl SaleFields {
1497 /// # pub fn new() -> Self {
1498 /// # Self {
1499 /// # region: Field::new(vec!["region"]),
1500 /// # product_category: Field::new(vec!["product_category"]),
1501 /// # }
1502 /// # }
1503 /// # }
1504 /// # impl FieldSelector for SaleFields {
1505 /// # fn with_alias(mut self, alias: &str) -> Self {
1506 /// # self.region = self.region.with_alias(alias);
1507 /// # self.product_category = self.product_category.with_alias(alias);
1508 /// # self
1509 /// # }
1510 /// # }
1511 /// # impl Model for Sale {
1512 /// # type PrimaryKey = i64;
1513 /// # type Fields = SaleFields;
1514 /// # fn table_name() -> &'static str { "sales" }
1515 /// # fn new_fields() -> Self::Fields { SaleFields::new() }
1516 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1517 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1518 /// # }
1519 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1520 /// // Group by single field
1521 /// let sql1 = Book::objects()
1522 /// .all()
1523 /// .group_by(|fields| GroupByFields::new().add(&fields.author_id))
1524 /// .to_sql();
1525 ///
1526 /// // Group by multiple fields (chain .add())
1527 /// let sql2 = Sale::objects()
1528 /// .all()
1529 /// .group_by(|fields| GroupByFields::new().add(&fields.region).add(&fields.product_category))
1530 /// .to_sql();
1531 /// # Ok(())
1532 /// # }
1533 /// ```
1534 /// # Breaking Change
1535 ///
1536 /// This method signature has been changed from string-based field selection
1537 /// to type-safe field selectors.
1538 pub fn group_by<F>(mut self, selector_fn: F) -> Self
1539 where
1540 F: FnOnce(T::Fields) -> GroupByFields,
1541 {
1542 let fields = T::new_fields();
1543 let group_by_fields = selector_fn(fields);
1544 self.group_by_fields = group_by_fields.build();
1545 self
1546 }
1547
1548 /// Add HAVING clause for AVG aggregate
1549 ///
1550 /// Filters grouped rows based on the average value of a field.
1551 ///
1552 /// # Type Parameters
1553 ///
1554 /// * `F` - Closure that selects the field
1555 ///
1556 /// # Parameters
1557 ///
1558 /// * `field_fn` - Closure that receives a `HavingFieldSelector` and returns it with the field set
1559 /// * `operator` - Comparison operator (Eq, Ne, Gt, Gte, Lt, Lte)
1560 /// * `value` - Value to compare against
1561 ///
1562 /// # Examples
1563 ///
1564 /// ```
1565 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1566 /// # use serde::{Serialize, Deserialize};
1567 /// # #[derive(Clone, Serialize, Deserialize)]
1568 /// # struct Author { id: Option<i64> }
1569 /// #
1570 /// # #[derive(Clone)]
1571 /// # struct AuthorFields {
1572 /// # pub author_id: Field<Author, i64>,
1573 /// # pub price: Field<Author, f64>,
1574 /// # }
1575 /// # impl AuthorFields {
1576 /// # pub fn new() -> Self {
1577 /// # Self {
1578 /// # author_id: Field::new(vec!["author_id"]),
1579 /// # price: Field::new(vec!["price"]),
1580 /// # }
1581 /// # }
1582 /// # }
1583 /// # impl FieldSelector for AuthorFields {
1584 /// # fn with_alias(mut self, alias: &str) -> Self {
1585 /// # self.author_id = self.author_id.with_alias(alias);
1586 /// # self.price = self.price.with_alias(alias);
1587 /// # self
1588 /// # }
1589 /// # }
1590 /// # impl Model for Author {
1591 /// # type PrimaryKey = i64;
1592 /// # type Fields = AuthorFields;
1593 /// # fn table_name() -> &'static str { "authors" }
1594 /// # fn new_fields() -> Self::Fields { AuthorFields::new() }
1595 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1596 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1597 /// # }
1598 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1599 /// // Find authors with average book price > 1500
1600 /// let sql = Author::objects()
1601 /// .all()
1602 /// .group_by(|fields| GroupByFields::new().add(&fields.author_id))
1603 /// .having_avg(|fields| &fields.price, |avg| avg.gt(1500.0))
1604 /// .to_sql();
1605 /// # Ok(())
1606 /// # }
1607 /// ```
1608 /// # Breaking Change
1609 ///
1610 /// This method signature has been changed to use type-safe field selectors
1611 /// and aggregate expressions.
1612 pub fn having_avg<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
1613 where
1614 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
1615 NT: super::query_fields::NumericType,
1616 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
1617 {
1618 let fields = T::new_fields();
1619 let field = field_selector(&fields);
1620 let field_path = field.path().join(".");
1621
1622 let avg_expr = AggregateExpr::avg(&field_path);
1623 let comparison = expr_fn(avg_expr);
1624
1625 // Extract components for HavingCondition
1626 let operator = match comparison.op {
1627 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
1628 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
1629 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
1630 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
1631 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
1632 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
1633 };
1634
1635 let value = match comparison.value {
1636 super::query_fields::aggregate::ComparisonValue::Int(i) => {
1637 AggregateValue::Float(i as f64)
1638 }
1639 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
1640 };
1641
1642 self.having_conditions
1643 .push(HavingCondition::AggregateCompare {
1644 func: AggregateFunc::Avg,
1645 field: comparison.aggregate.field().to_string(),
1646 operator,
1647 value,
1648 });
1649 self
1650 }
1651
1652 /// Add HAVING clause for COUNT aggregate
1653 ///
1654 /// Filters grouped rows based on the count of rows in each group.
1655 ///
1656 /// # Type Parameters
1657 ///
1658 /// * `F` - Closure that selects the field
1659 ///
1660 /// # Parameters
1661 ///
1662 /// * `field_fn` - Closure that receives a `HavingFieldSelector` and returns it with the field set
1663 /// * `operator` - Comparison operator (Eq, Ne, Gt, Gte, Lt, Lte)
1664 /// * `value` - Value to compare against
1665 ///
1666 /// # Examples
1667 ///
1668 /// ```
1669 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1670 /// # use serde::{Serialize, Deserialize};
1671 /// # #[derive(Clone, Serialize, Deserialize)]
1672 /// # struct Author { id: Option<i64> }
1673 /// #
1674 /// # #[derive(Clone)]
1675 /// # struct AuthorFields {
1676 /// # pub author_id: Field<Author, i64>,
1677 /// # }
1678 /// # impl AuthorFields {
1679 /// # pub fn new() -> Self {
1680 /// # Self { author_id: Field::new(vec!["author_id"]) }
1681 /// # }
1682 /// # }
1683 /// # impl FieldSelector for AuthorFields {
1684 /// # fn with_alias(mut self, alias: &str) -> Self {
1685 /// # self.author_id = self.author_id.with_alias(alias);
1686 /// # self
1687 /// # }
1688 /// # }
1689 /// # impl Model for Author {
1690 /// # type PrimaryKey = i64;
1691 /// # type Fields = AuthorFields;
1692 /// # fn table_name() -> &'static str { "authors" }
1693 /// # fn new_fields() -> Self::Fields { AuthorFields::new() }
1694 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1695 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1696 /// # }
1697 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1698 /// // Find authors with more than 5 books
1699 /// let sql = Author::objects()
1700 /// .all()
1701 /// .group_by(|fields| GroupByFields::new().add(&fields.author_id))
1702 /// .having_count(|count| count.gt(5))
1703 /// .to_sql();
1704 /// # Ok(())
1705 /// # }
1706 /// ```
1707 /// # Breaking Change
1708 ///
1709 /// This method signature has been changed to use type-safe aggregate expressions.
1710 pub fn having_count<F>(mut self, expr_fn: F) -> Self
1711 where
1712 F: FnOnce(AggregateExpr) -> ComparisonExpr,
1713 {
1714 let count_expr = AggregateExpr::count("*");
1715 let comparison = expr_fn(count_expr);
1716
1717 // Extract components for HavingCondition
1718 let operator = match comparison.op {
1719 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
1720 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
1721 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
1722 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
1723 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
1724 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
1725 };
1726
1727 let value = match comparison.value {
1728 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
1729 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
1730 };
1731
1732 self.having_conditions
1733 .push(HavingCondition::AggregateCompare {
1734 func: AggregateFunc::Count,
1735 field: comparison.aggregate.field().to_string(),
1736 operator,
1737 value,
1738 });
1739 self
1740 }
1741
1742 /// Add HAVING clause for SUM aggregate
1743 ///
1744 /// Filters grouped rows based on the sum of values in a field.
1745 ///
1746 /// # Type Parameters
1747 ///
1748 /// * `F` - Closure that selects the field
1749 ///
1750 /// # Parameters
1751 ///
1752 /// * `field_fn` - Closure that receives a `HavingFieldSelector` and returns it with the field set
1753 /// * `operator` - Comparison operator (Eq, Ne, Gt, Gte, Lt, Lte)
1754 /// * `value` - Value to compare against
1755 ///
1756 /// # Examples
1757 ///
1758 /// ```
1759 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1760 /// # use serde::{Serialize, Deserialize};
1761 /// # #[derive(Clone, Serialize, Deserialize)]
1762 /// # struct Product { id: Option<i64> }
1763 /// #
1764 /// # #[derive(Clone)]
1765 /// # struct ProductFields {
1766 /// # pub category: Field<Product, String>,
1767 /// # pub sales_amount: Field<Product, f64>,
1768 /// # }
1769 /// # impl ProductFields {
1770 /// # pub fn new() -> Self {
1771 /// # Self {
1772 /// # category: Field::new(vec!["category"]),
1773 /// # sales_amount: Field::new(vec!["sales_amount"]),
1774 /// # }
1775 /// # }
1776 /// # }
1777 /// # impl FieldSelector for ProductFields {
1778 /// # fn with_alias(mut self, alias: &str) -> Self {
1779 /// # self.category = self.category.with_alias(alias);
1780 /// # self.sales_amount = self.sales_amount.with_alias(alias);
1781 /// # self
1782 /// # }
1783 /// # }
1784 /// # impl Model for Product {
1785 /// # type PrimaryKey = i64;
1786 /// # type Fields = ProductFields;
1787 /// # fn table_name() -> &'static str { "products" }
1788 /// # fn new_fields() -> Self::Fields { ProductFields::new() }
1789 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1790 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1791 /// # }
1792 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1793 /// // Find categories with total sales > 10000
1794 /// let sql = Product::objects()
1795 /// .all()
1796 /// .group_by(|fields| GroupByFields::new().add(&fields.category))
1797 /// .having_sum(|fields| &fields.sales_amount, |sum| sum.gt(10000.0))
1798 /// .to_sql();
1799 /// # Ok(())
1800 /// # }
1801 /// ```
1802 /// # Breaking Change
1803 ///
1804 /// This method signature has been changed to use type-safe field selectors.
1805 pub fn having_sum<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
1806 where
1807 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
1808 NT: super::query_fields::NumericType,
1809 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
1810 {
1811 let fields = T::new_fields();
1812 let field = field_selector(&fields);
1813 let field_path = field.path().join(".");
1814
1815 let sum_expr = AggregateExpr::sum(&field_path);
1816 let comparison = expr_fn(sum_expr);
1817
1818 let operator = match comparison.op {
1819 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
1820 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
1821 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
1822 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
1823 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
1824 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
1825 };
1826
1827 let value = match comparison.value {
1828 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
1829 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
1830 };
1831
1832 self.having_conditions
1833 .push(HavingCondition::AggregateCompare {
1834 func: AggregateFunc::Sum,
1835 field: comparison.aggregate.field().to_string(),
1836 operator,
1837 value,
1838 });
1839 self
1840 }
1841
1842 /// Add HAVING clause for MIN aggregate
1843 ///
1844 /// Filters grouped rows based on the minimum value in a field.
1845 ///
1846 /// # Breaking Change
1847 ///
1848 /// This method signature has been changed to use type-safe field selectors.
1849 ///
1850 /// # Type Parameters
1851 ///
1852 /// * `FS` - Field selector closure that returns a reference to a numeric field
1853 /// * `FE` - Expression closure that builds the comparison expression
1854 ///
1855 /// # Parameters
1856 ///
1857 /// * `field_selector` - Closure that selects the field from the model
1858 /// * `expr_fn` - Closure that builds the comparison expression using method chaining
1859 ///
1860 /// # Examples
1861 ///
1862 /// ```
1863 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1864 /// # use serde::{Serialize, Deserialize};
1865 /// # #[derive(Clone, Serialize, Deserialize)]
1866 /// # struct Author { id: Option<i64> }
1867 /// #
1868 /// # #[derive(Clone)]
1869 /// # struct AuthorFields {
1870 /// # pub author_id: Field<Author, i64>,
1871 /// # pub price: Field<Author, f64>,
1872 /// # }
1873 /// # impl AuthorFields {
1874 /// # pub fn new() -> Self {
1875 /// # Self {
1876 /// # author_id: Field::new(vec!["author_id"]),
1877 /// # price: Field::new(vec!["price"]),
1878 /// # }
1879 /// # }
1880 /// # }
1881 /// # impl FieldSelector for AuthorFields {
1882 /// # fn with_alias(mut self, alias: &str) -> Self {
1883 /// # self.author_id = self.author_id.with_alias(alias);
1884 /// # self.price = self.price.with_alias(alias);
1885 /// # self
1886 /// # }
1887 /// # }
1888 /// # impl Model for Author {
1889 /// # type PrimaryKey = i64;
1890 /// # type Fields = AuthorFields;
1891 /// # fn table_name() -> &'static str { "authors" }
1892 /// # fn new_fields() -> Self::Fields { AuthorFields::new() }
1893 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1894 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1895 /// # }
1896 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1897 /// // Find authors where minimum book price > 1000
1898 /// let sql = Author::objects()
1899 /// .all()
1900 /// .group_by(|fields| GroupByFields::new().add(&fields.author_id))
1901 /// .having_min(|fields| &fields.price, |min| min.gt(1000.0))
1902 /// .to_sql();
1903 /// # Ok(())
1904 /// # }
1905 /// ```
1906 pub fn having_min<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
1907 where
1908 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
1909 NT: super::query_fields::NumericType,
1910 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
1911 {
1912 let fields = T::new_fields();
1913 let field = field_selector(&fields);
1914 let field_path = field.path().join(".");
1915
1916 let min_expr = AggregateExpr::min(&field_path);
1917 let comparison = expr_fn(min_expr);
1918
1919 let operator = match comparison.op {
1920 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
1921 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
1922 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
1923 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
1924 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
1925 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
1926 };
1927
1928 let value = match comparison.value {
1929 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
1930 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
1931 };
1932
1933 self.having_conditions
1934 .push(HavingCondition::AggregateCompare {
1935 func: AggregateFunc::Min,
1936 field: comparison.aggregate.field().to_string(),
1937 operator,
1938 value,
1939 });
1940 self
1941 }
1942
1943 /// Add HAVING clause for MAX aggregate
1944 ///
1945 /// Filters grouped rows based on the maximum value in a field.
1946 ///
1947 /// # Breaking Change
1948 ///
1949 /// This method signature has been changed to use type-safe field selectors.
1950 ///
1951 /// # Type Parameters
1952 ///
1953 /// * `FS` - Field selector closure that returns a reference to a numeric field
1954 /// * `FE` - Expression closure that builds the comparison expression
1955 ///
1956 /// # Parameters
1957 ///
1958 /// * `field_selector` - Closure that selects the field from the model
1959 /// * `expr_fn` - Closure that builds the comparison expression using method chaining
1960 ///
1961 /// # Examples
1962 ///
1963 /// ```
1964 /// # use reinhardt_db::orm::{Model, query_fields::{Field, GroupByFields}, FieldSelector};
1965 /// # use serde::{Serialize, Deserialize};
1966 /// # #[derive(Clone, Serialize, Deserialize)]
1967 /// # struct Author { id: Option<i64> }
1968 /// #
1969 /// # #[derive(Clone)]
1970 /// # struct AuthorFields {
1971 /// # pub author_id: Field<Author, i64>,
1972 /// # pub price: Field<Author, f64>,
1973 /// # }
1974 /// # impl AuthorFields {
1975 /// # pub fn new() -> Self {
1976 /// # Self {
1977 /// # author_id: Field::new(vec!["author_id"]),
1978 /// # price: Field::new(vec!["price"]),
1979 /// # }
1980 /// # }
1981 /// # }
1982 /// # impl FieldSelector for AuthorFields {
1983 /// # fn with_alias(mut self, alias: &str) -> Self {
1984 /// # self.author_id = self.author_id.with_alias(alias);
1985 /// # self.price = self.price.with_alias(alias);
1986 /// # self
1987 /// # }
1988 /// # }
1989 /// # impl Model for Author {
1990 /// # type PrimaryKey = i64;
1991 /// # type Fields = AuthorFields;
1992 /// # fn table_name() -> &'static str { "authors" }
1993 /// # fn new_fields() -> Self::Fields { AuthorFields::new() }
1994 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
1995 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
1996 /// # }
1997 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1998 /// // Find authors where maximum book price < 5000
1999 /// let sql = Author::objects()
2000 /// .all()
2001 /// .group_by(|fields| GroupByFields::new().add(&fields.author_id))
2002 /// .having_max(|fields| &fields.price, |max| max.lt(5000.0))
2003 /// .to_sql();
2004 /// # Ok(())
2005 /// # }
2006 /// ```
2007 pub fn having_max<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
2008 where
2009 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
2010 NT: super::query_fields::NumericType,
2011 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
2012 {
2013 let fields = T::new_fields();
2014 let field = field_selector(&fields);
2015 let field_path = field.path().join(".");
2016
2017 let max_expr = AggregateExpr::max(&field_path);
2018 let comparison = expr_fn(max_expr);
2019
2020 let operator = match comparison.op {
2021 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2022 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2023 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2024 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2025 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2026 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2027 };
2028
2029 let value = match comparison.value {
2030 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
2031 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2032 };
2033
2034 self.having_conditions
2035 .push(HavingCondition::AggregateCompare {
2036 func: AggregateFunc::Max,
2037 field: comparison.aggregate.field().to_string(),
2038 operator,
2039 value,
2040 });
2041 self
2042 }
2043
2044 /// Add WHERE IN (subquery) condition
2045 ///
2046 /// Filters rows where the specified field's value is in the result set of a subquery.
2047 ///
2048 /// # Type Parameters
2049 ///
2050 /// * `R` - The model type used in the subquery (must implement `Model` trait)
2051 /// * `F` - Function that builds the subquery QuerySet
2052 ///
2053 /// # Examples
2054 ///
2055 /// ```no_run
2056 /// # use reinhardt_db::orm::Model;
2057 /// # use reinhardt_db::orm::{QuerySet, Filter, FilterOperator, FilterValue};
2058 /// # use serde::{Serialize, Deserialize};
2059 /// # #[derive(Clone, Serialize, Deserialize)]
2060 /// # struct Author { id: Option<i64> }
2061 /// # #[derive(Clone)]
2062 /// # struct AuthorFields;
2063 /// # impl reinhardt_db::orm::model::FieldSelector for AuthorFields {
2064 /// # fn with_alias(self, _alias: &str) -> Self { self }
2065 /// # }
2066 /// # impl Model for Author {
2067 /// # type PrimaryKey = i64;
2068 /// # type Fields = AuthorFields;
2069 /// # fn table_name() -> &'static str { "authors" }
2070 /// # fn new_fields() -> Self::Fields { AuthorFields }
2071 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2072 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2073 /// # }
2074 /// # #[derive(Clone, Serialize, Deserialize)]
2075 /// # struct Book { id: Option<i64> }
2076 /// # #[derive(Clone)]
2077 /// # struct BookFields;
2078 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
2079 /// # fn with_alias(self, _alias: &str) -> Self { self }
2080 /// # }
2081 /// # impl Model for Book {
2082 /// # type PrimaryKey = i64;
2083 /// # type Fields = BookFields;
2084 /// # fn table_name() -> &'static str { "books" }
2085 /// # fn new_fields() -> Self::Fields { BookFields }
2086 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2087 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2088 /// # }
2089 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2090 /// // Find authors who have books priced over 1500
2091 /// let authors = Author::objects()
2092 /// .filter_in_subquery("id", |subq: QuerySet<Book>| {
2093 /// subq.filter(Filter::new("price", FilterOperator::Gt, FilterValue::Int(1500)))
2094 /// .values(&["author_id"])
2095 /// })
2096 /// .all()
2097 /// .await?;
2098 /// # Ok(())
2099 /// # }
2100 /// ```
2101 pub fn filter_in_subquery<R: super::Model, F>(mut self, field: &str, subquery_fn: F) -> Self
2102 where
2103 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2104 {
2105 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2106 let subquery_sql = subquery_qs.as_subquery();
2107
2108 self.subquery_conditions.push(SubqueryCondition::In {
2109 field: field.to_string(),
2110 subquery: subquery_sql,
2111 });
2112
2113 self
2114 }
2115
2116 /// Add WHERE NOT IN (subquery) condition
2117 ///
2118 /// Filters rows where the specified field's value is NOT in the result set of a subquery.
2119 ///
2120 /// # Examples
2121 ///
2122 /// ```no_run
2123 /// # use reinhardt_db::orm::Model;
2124 /// # use reinhardt_db::orm::{QuerySet, Filter, FilterOperator, FilterValue};
2125 /// # use serde::{Serialize, Deserialize};
2126 /// # #[derive(Clone, Serialize, Deserialize)]
2127 /// # struct Author { id: Option<i64> }
2128 /// # #[derive(Clone)]
2129 /// # struct AuthorFields;
2130 /// # impl reinhardt_db::orm::model::FieldSelector for AuthorFields {
2131 /// # fn with_alias(self, _alias: &str) -> Self { self }
2132 /// # }
2133 /// # impl Model for Author {
2134 /// # type PrimaryKey = i64;
2135 /// # type Fields = AuthorFields;
2136 /// # fn table_name() -> &'static str { "authors" }
2137 /// # fn new_fields() -> Self::Fields { AuthorFields }
2138 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2139 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2140 /// # }
2141 /// # #[derive(Clone, Serialize, Deserialize)]
2142 /// # struct Book { id: Option<i64> }
2143 /// # #[derive(Clone)]
2144 /// # struct BookFields;
2145 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
2146 /// # fn with_alias(self, _alias: &str) -> Self { self }
2147 /// # }
2148 /// # impl Model for Book {
2149 /// # type PrimaryKey = i64;
2150 /// # type Fields = BookFields;
2151 /// # fn table_name() -> &'static str { "books" }
2152 /// # fn new_fields() -> Self::Fields { BookFields }
2153 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2154 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2155 /// # }
2156 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2157 /// // Find authors who have NO books priced over 1500
2158 /// let authors = Author::objects()
2159 /// .filter_not_in_subquery("id", |subq: QuerySet<Book>| {
2160 /// subq.filter(Filter::new("price", FilterOperator::Gt, FilterValue::Int(1500)))
2161 /// .values(&["author_id"])
2162 /// })
2163 /// .all()
2164 /// .await?;
2165 /// # Ok(())
2166 /// # }
2167 /// ```
2168 pub fn filter_not_in_subquery<R: super::Model, F>(mut self, field: &str, subquery_fn: F) -> Self
2169 where
2170 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2171 {
2172 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2173 let subquery_sql = subquery_qs.as_subquery();
2174
2175 self.subquery_conditions.push(SubqueryCondition::NotIn {
2176 field: field.to_string(),
2177 subquery: subquery_sql,
2178 });
2179
2180 self
2181 }
2182
2183 /// Add WHERE EXISTS (subquery) condition
2184 ///
2185 /// Filters rows where the subquery returns at least one row.
2186 /// Typically used with correlated subqueries.
2187 ///
2188 /// # Examples
2189 ///
2190 /// ```no_run
2191 /// # use reinhardt_db::orm::Model;
2192 /// # use reinhardt_db::orm::{QuerySet, Filter, FilterOperator, FilterValue};
2193 /// # use serde::{Serialize, Deserialize};
2194 /// # #[derive(Clone, Serialize, Deserialize)]
2195 /// # struct Author { id: Option<i64> }
2196 /// # #[derive(Clone)]
2197 /// # struct AuthorFields;
2198 /// # impl reinhardt_db::orm::model::FieldSelector for AuthorFields {
2199 /// # fn with_alias(self, _alias: &str) -> Self { self }
2200 /// # }
2201 /// # impl Model for Author {
2202 /// # type PrimaryKey = i64;
2203 /// # type Fields = AuthorFields;
2204 /// # fn table_name() -> &'static str { "authors" }
2205 /// # fn new_fields() -> Self::Fields { AuthorFields }
2206 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2207 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2208 /// # }
2209 /// # #[derive(Clone, Serialize, Deserialize)]
2210 /// # struct Book { id: Option<i64> }
2211 /// # #[derive(Clone)]
2212 /// # struct BookFields;
2213 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
2214 /// # fn with_alias(self, _alias: &str) -> Self { self }
2215 /// # }
2216 /// # impl Model for Book {
2217 /// # type PrimaryKey = i64;
2218 /// # type Fields = BookFields;
2219 /// # fn table_name() -> &'static str { "books" }
2220 /// # fn new_fields() -> Self::Fields { BookFields }
2221 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2222 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2223 /// # }
2224 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2225 /// use reinhardt_db::orm::F;
2226 /// // Find authors who have at least one book
2227 /// let authors = Author::objects()
2228 /// .filter_exists(|subq: QuerySet<Book>| {
2229 /// subq.filter(Filter::new("author_id", FilterOperator::Eq, FilterValue::FieldRef(F::new("authors.id"))))
2230 /// })
2231 /// .all()
2232 /// .await?;
2233 /// # Ok(())
2234 /// # }
2235 /// ```
2236 pub fn filter_exists<R: super::Model, F>(mut self, subquery_fn: F) -> Self
2237 where
2238 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2239 {
2240 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2241 let subquery_sql = subquery_qs.as_subquery();
2242
2243 self.subquery_conditions.push(SubqueryCondition::Exists {
2244 subquery: subquery_sql,
2245 });
2246
2247 self
2248 }
2249
2250 /// Add WHERE NOT EXISTS (subquery) condition
2251 ///
2252 /// Filters rows where the subquery returns no rows.
2253 /// Typically used with correlated subqueries.
2254 ///
2255 /// # Examples
2256 ///
2257 /// ```no_run
2258 /// # use reinhardt_db::orm::Model;
2259 /// # use reinhardt_db::orm::{QuerySet, Filter, FilterOperator, FilterValue};
2260 /// # use serde::{Serialize, Deserialize};
2261 /// # #[derive(Clone, Serialize, Deserialize)]
2262 /// # struct Author { id: Option<i64> }
2263 /// # #[derive(Clone)]
2264 /// # struct AuthorFields;
2265 /// # impl reinhardt_db::orm::model::FieldSelector for AuthorFields {
2266 /// # fn with_alias(self, _alias: &str) -> Self { self }
2267 /// # }
2268 /// # impl Model for Author {
2269 /// # type PrimaryKey = i64;
2270 /// # type Fields = AuthorFields;
2271 /// # fn table_name() -> &'static str { "authors" }
2272 /// # fn new_fields() -> Self::Fields { AuthorFields }
2273 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2274 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2275 /// # }
2276 /// # #[derive(Clone, Serialize, Deserialize)]
2277 /// # struct Book { id: Option<i64> }
2278 /// # #[derive(Clone)]
2279 /// # struct BookFields;
2280 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
2281 /// # fn with_alias(self, _alias: &str) -> Self { self }
2282 /// # }
2283 /// # impl Model for Book {
2284 /// # type PrimaryKey = i64;
2285 /// # type Fields = BookFields;
2286 /// # fn table_name() -> &'static str { "books" }
2287 /// # fn new_fields() -> Self::Fields { BookFields }
2288 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2289 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2290 /// # }
2291 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2292 /// use reinhardt_db::orm::F;
2293 /// // Find authors who have NO books
2294 /// let authors = Author::objects()
2295 /// .filter_not_exists(|subq: QuerySet<Book>| {
2296 /// subq.filter(Filter::new("author_id", FilterOperator::Eq, FilterValue::FieldRef(F::new("authors.id"))))
2297 /// })
2298 /// .all()
2299 /// .await?;
2300 /// # Ok(())
2301 /// # }
2302 /// ```
2303 pub fn filter_not_exists<R: super::Model, F>(mut self, subquery_fn: F) -> Self
2304 where
2305 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2306 {
2307 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2308 let subquery_sql = subquery_qs.as_subquery();
2309
2310 self.subquery_conditions.push(SubqueryCondition::NotExists {
2311 subquery: subquery_sql,
2312 });
2313
2314 self
2315 }
2316
2317 /// Add a Common Table Expression (WITH clause) to the query
2318 ///
2319 /// CTEs allow you to define named subqueries that can be referenced
2320 /// in the main query. This is useful for complex queries that need
2321 /// to reference the same subquery multiple times or for recursive queries.
2322 ///
2323 /// # Examples
2324 ///
2325 /// ```no_run
2326 /// # use reinhardt_db::orm::Model;
2327 /// # use reinhardt_db::orm::cte::CTE;
2328 /// # use serde::{Serialize, Deserialize};
2329 /// # #[derive(Clone, Serialize, Deserialize)]
2330 /// # struct Employee { id: Option<i64> }
2331 /// # #[derive(Clone)]
2332 /// # struct EmployeeFields;
2333 /// # impl reinhardt_db::orm::model::FieldSelector for EmployeeFields {
2334 /// # fn with_alias(self, _alias: &str) -> Self { self }
2335 /// # }
2336 /// # impl Model for Employee {
2337 /// # type PrimaryKey = i64;
2338 /// # type Fields = EmployeeFields;
2339 /// # fn table_name() -> &'static str { "employees" }
2340 /// # fn new_fields() -> Self::Fields { EmployeeFields }
2341 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2342 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2343 /// # }
2344 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2345 /// // Simple CTE
2346 /// let high_earners = CTE::new("high_earners", "SELECT * FROM employees WHERE salary > 100000");
2347 /// let results = Employee::objects()
2348 /// .with_cte(high_earners)
2349 /// .all()
2350 /// .await?;
2351 ///
2352 /// // Recursive CTE for hierarchical data
2353 /// let hierarchy = CTE::new(
2354 /// "org_hierarchy",
2355 /// "SELECT id, name, manager_id, 1 as level FROM employees WHERE manager_id IS NULL \
2356 /// UNION ALL \
2357 /// SELECT e.id, e.name, e.manager_id, h.level + 1 \
2358 /// FROM employees e JOIN org_hierarchy h ON e.manager_id = h.id"
2359 /// ).recursive();
2360 ///
2361 /// let org = Employee::objects()
2362 /// .with_cte(hierarchy)
2363 /// .all()
2364 /// .await?;
2365 /// # Ok(())
2366 /// # }
2367 /// ```
2368 pub fn with_cte(mut self, cte: super::cte::CTE) -> Self {
2369 self.ctes.add(cte);
2370 self
2371 }
2372
2373 /// Add a LATERAL JOIN to the query
2374 ///
2375 /// LATERAL JOINs allow correlated subqueries in the FROM clause,
2376 /// where the subquery can reference columns from preceding tables.
2377 /// This is useful for "top-N per group" queries and similar patterns.
2378 ///
2379 /// **Note**: LATERAL JOIN is supported in PostgreSQL 9.3+, MySQL 8.0.14+,
2380 /// but NOT in SQLite.
2381 ///
2382 /// # Examples
2383 ///
2384 /// ```no_run
2385 /// # use reinhardt_db::orm::Model;
2386 /// # use serde::{Serialize, Deserialize};
2387 /// # #[derive(Clone, Serialize, Deserialize)]
2388 /// # struct Customer { id: Option<i64> }
2389 /// # #[derive(Clone)]
2390 /// # struct CustomerFields;
2391 /// # impl reinhardt_db::orm::model::FieldSelector for CustomerFields {
2392 /// # fn with_alias(self, _alias: &str) -> Self { self }
2393 /// # }
2394 /// # impl Model for Customer {
2395 /// # type PrimaryKey = i64;
2396 /// # type Fields = CustomerFields;
2397 /// # fn table_name() -> &'static str { "customers" }
2398 /// # fn new_fields() -> Self::Fields { CustomerFields }
2399 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
2400 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
2401 /// # }
2402 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2403 /// use reinhardt_db::orm::lateral_join::{LateralJoin, LateralJoinPatterns};
2404 ///
2405 /// // Get top 3 orders per customer
2406 /// let top_orders = LateralJoinPatterns::top_n_per_group(
2407 /// "recent_orders",
2408 /// "orders",
2409 /// "customer_id",
2410 /// "customers",
2411 /// "created_at DESC",
2412 /// 3,
2413 /// );
2414 ///
2415 /// let results = Customer::objects()
2416 /// .all()
2417 /// .with_lateral_join(top_orders)
2418 /// .all()
2419 /// .await?;
2420 ///
2421 /// // Get latest order per customer
2422 /// let latest = LateralJoinPatterns::latest_per_parent(
2423 /// "latest_order",
2424 /// "orders",
2425 /// "customer_id",
2426 /// "customers",
2427 /// "created_at",
2428 /// );
2429 ///
2430 /// let customers_with_orders = Customer::objects()
2431 /// .all()
2432 /// .with_lateral_join(latest)
2433 /// .all()
2434 /// .await?;
2435 /// # Ok(())
2436 /// # }
2437 /// ```
2438 pub fn with_lateral_join(mut self, join: super::lateral_join::LateralJoin) -> Self {
2439 self.lateral_joins.add(join);
2440 self
2441 }
2442
2443 /// Build WHERE condition using reinhardt-query from accumulated filters
2444 fn build_where_condition(&self) -> Option<Condition> {
2445 // Early return only if both filters and subquery_conditions are empty
2446 if self.filters.is_empty() && self.subquery_conditions.is_empty() {
2447 return None;
2448 }
2449
2450 let mut cond = Condition::all();
2451
2452 for filter in &self.filters {
2453 let col = Expr::col(parse_column_reference(&filter.field));
2454
2455 let expr = match (&filter.operator, &filter.value) {
2456 // Field-to-field comparisons (must come before generic patterns)
2457 (FilterOperator::Eq, FilterValue::FieldRef(f)) => {
2458 col.eq(Expr::col(Alias::new(&f.field)))
2459 }
2460 (FilterOperator::Ne, FilterValue::FieldRef(f)) => {
2461 col.ne(Expr::col(Alias::new(&f.field)))
2462 }
2463 (FilterOperator::Gt, FilterValue::FieldRef(f)) => {
2464 col.gt(Expr::col(Alias::new(&f.field)))
2465 }
2466 (FilterOperator::Gte, FilterValue::FieldRef(f)) => {
2467 col.gte(Expr::col(Alias::new(&f.field)))
2468 }
2469 (FilterOperator::Lt, FilterValue::FieldRef(f)) => {
2470 col.lt(Expr::col(Alias::new(&f.field)))
2471 }
2472 (FilterOperator::Lte, FilterValue::FieldRef(f)) => {
2473 col.lte(Expr::col(Alias::new(&f.field)))
2474 }
2475 // OuterRef comparisons for correlated subqueries
2476 (FilterOperator::Eq, FilterValue::OuterRef(outer)) => {
2477 // For correlated subqueries, reference outer query field
2478 // e.g., WHERE books.author_id = authors.id (where authors is from outer query)
2479 col.eq(Expr::col(parse_column_reference(&outer.field)))
2480 }
2481 (FilterOperator::Ne, FilterValue::OuterRef(outer)) => {
2482 col.ne(Expr::col(parse_column_reference(&outer.field)))
2483 }
2484 (FilterOperator::Gt, FilterValue::OuterRef(outer)) => {
2485 col.gt(Expr::col(parse_column_reference(&outer.field)))
2486 }
2487 (FilterOperator::Gte, FilterValue::OuterRef(outer)) => {
2488 col.gte(Expr::col(parse_column_reference(&outer.field)))
2489 }
2490 (FilterOperator::Lt, FilterValue::OuterRef(outer)) => {
2491 col.lt(Expr::col(parse_column_reference(&outer.field)))
2492 }
2493 (FilterOperator::Lte, FilterValue::OuterRef(outer)) => {
2494 col.lte(Expr::col(parse_column_reference(&outer.field)))
2495 }
2496 // Expression comparisons (F("a") * F("b") etc.)
2497 (FilterOperator::Eq, FilterValue::Expression(expr)) => {
2498 col.eq(Self::expression_to_query_expr(expr))
2499 }
2500 (FilterOperator::Ne, FilterValue::Expression(expr)) => {
2501 col.ne(Self::expression_to_query_expr(expr))
2502 }
2503 (FilterOperator::Gt, FilterValue::Expression(expr)) => {
2504 col.gt(Self::expression_to_query_expr(expr))
2505 }
2506 (FilterOperator::Gte, FilterValue::Expression(expr)) => {
2507 col.gte(Self::expression_to_query_expr(expr))
2508 }
2509 (FilterOperator::Lt, FilterValue::Expression(expr)) => {
2510 col.lt(Self::expression_to_query_expr(expr))
2511 }
2512 (FilterOperator::Lte, FilterValue::Expression(expr)) => {
2513 col.lte(Self::expression_to_query_expr(expr))
2514 }
2515 // NULL checks
2516 (FilterOperator::Eq, FilterValue::Null) => col.is_null(),
2517 (FilterOperator::Ne, FilterValue::Null) => col.is_not_null(),
2518 // Generic value comparisons (catch-all for other FilterValue types)
2519 (FilterOperator::Eq, v) => col.eq(Self::filter_value_to_sea_value(v)),
2520 (FilterOperator::Ne, v) => col.ne(Self::filter_value_to_sea_value(v)),
2521 (FilterOperator::Gt, v) => col.gt(Self::filter_value_to_sea_value(v)),
2522 (FilterOperator::Gte, v) => col.gte(Self::filter_value_to_sea_value(v)),
2523 (FilterOperator::Lt, v) => col.lt(Self::filter_value_to_sea_value(v)),
2524 (FilterOperator::Lte, v) => col.lte(Self::filter_value_to_sea_value(v)),
2525 (FilterOperator::In, FilterValue::String(s)) => {
2526 let values = Self::parse_array_string(s);
2527 col.is_in(values)
2528 }
2529 (FilterOperator::In, FilterValue::Array(arr)) => {
2530 col.is_in(arr.iter().map(|s| s.as_str()).collect::<Vec<_>>())
2531 }
2532 (FilterOperator::NotIn, FilterValue::String(s)) => {
2533 let values = Self::parse_array_string(s);
2534 col.is_not_in(values)
2535 }
2536 (FilterOperator::NotIn, FilterValue::Array(arr)) => {
2537 col.is_not_in(arr.iter().map(|s| s.as_str()).collect::<Vec<_>>())
2538 }
2539 (FilterOperator::Contains, FilterValue::String(s)) => col.like(format!("%{}%", s)),
2540 (FilterOperator::Contains, FilterValue::Array(arr)) => {
2541 col.like(format!("%{}%", arr.first().unwrap_or(&String::new())))
2542 }
2543 (FilterOperator::StartsWith, FilterValue::String(s)) => col.like(format!("{}%", s)),
2544 (FilterOperator::StartsWith, FilterValue::Array(arr)) => {
2545 col.like(format!("{}%", arr.first().unwrap_or(&String::new())))
2546 }
2547 (FilterOperator::EndsWith, FilterValue::String(s)) => col.like(format!("%{}", s)),
2548 (FilterOperator::EndsWith, FilterValue::Array(arr)) => {
2549 col.like(format!("%{}", arr.first().unwrap_or(&String::new())))
2550 }
2551 // Handle Integer, Float, Boolean for text operators
2552 (FilterOperator::Contains, FilterValue::Integer(i) | FilterValue::Int(i)) => {
2553 col.like(format!("%{}%", i))
2554 }
2555 (FilterOperator::Contains, FilterValue::Float(f)) => col.like(format!("%{}%", f)),
2556 (FilterOperator::Contains, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
2557 col.like(format!("%{}%", b))
2558 }
2559 (FilterOperator::Contains, FilterValue::Null) => col.like("%"),
2560 (FilterOperator::StartsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
2561 col.like(format!("{}%", i))
2562 }
2563 (FilterOperator::StartsWith, FilterValue::Float(f)) => col.like(format!("{}%", f)),
2564 (FilterOperator::StartsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
2565 col.like(format!("{}%", b))
2566 }
2567 (FilterOperator::StartsWith, FilterValue::Null) => col.like("%"),
2568 (FilterOperator::EndsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
2569 col.like(format!("%{}", i))
2570 }
2571 (FilterOperator::EndsWith, FilterValue::Float(f)) => col.like(format!("%{}", f)),
2572 (FilterOperator::EndsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
2573 col.like(format!("%{}", b))
2574 }
2575 (FilterOperator::EndsWith, FilterValue::Null) => col.like("%"),
2576 // Handle In/NotIn for non-String types
2577 (FilterOperator::In, FilterValue::Integer(i) | FilterValue::Int(i)) => {
2578 col.is_in(vec![*i])
2579 }
2580 (FilterOperator::In, FilterValue::Float(f)) => col.is_in(vec![*f]),
2581 (FilterOperator::In, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
2582 col.is_in(vec![*b])
2583 }
2584 (FilterOperator::In, FilterValue::Null) => {
2585 col.is_in(vec![reinhardt_query::value::Value::Int(None)])
2586 }
2587 (FilterOperator::NotIn, FilterValue::Integer(i) | FilterValue::Int(i)) => {
2588 col.is_not_in(vec![*i])
2589 }
2590 (FilterOperator::NotIn, FilterValue::Float(f)) => col.is_not_in(vec![*f]),
2591 (FilterOperator::NotIn, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
2592 col.is_not_in(vec![*b])
2593 }
2594 (FilterOperator::NotIn, FilterValue::Null) => {
2595 col.is_not_in(vec![reinhardt_query::value::Value::Int(None)])
2596 }
2597 // IsNull/IsNotNull operators
2598 (FilterOperator::IsNull, _) => col.is_null(),
2599 (FilterOperator::IsNotNull, _) => col.is_not_null(),
2600 // PostgreSQL Array operators (using custom SQL)
2601 (FilterOperator::ArrayContains, FilterValue::Array(arr)) => {
2602 // field @> ARRAY[?, ?] - parameterized
2603 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
2604 Expr::cust_with_values(
2605 format!(
2606 "{} @> ARRAY[{}]",
2607 quote_identifier(&filter.field),
2608 placeholders
2609 ),
2610 arr.iter().cloned(),
2611 )
2612 .into_simple_expr()
2613 }
2614 (FilterOperator::ArrayContainedBy, FilterValue::Array(arr)) => {
2615 // field <@ ARRAY[?, ?] - parameterized
2616 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
2617 Expr::cust_with_values(
2618 format!(
2619 "{} <@ ARRAY[{}]",
2620 quote_identifier(&filter.field),
2621 placeholders
2622 ),
2623 arr.iter().cloned(),
2624 )
2625 .into_simple_expr()
2626 }
2627 (FilterOperator::ArrayOverlap, FilterValue::Array(arr)) => {
2628 // field && ARRAY[?, ?] - parameterized
2629 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
2630 Expr::cust_with_values(
2631 format!(
2632 "{} && ARRAY[{}]",
2633 quote_identifier(&filter.field),
2634 placeholders
2635 ),
2636 arr.iter().cloned(),
2637 )
2638 .into_simple_expr()
2639 }
2640 // PostgreSQL Full-text search
2641 (FilterOperator::FullTextMatch, FilterValue::String(query)) => {
2642 // field @@ plainto_tsquery('english', ?) - parameterized
2643 Expr::cust_with_values(
2644 format!(
2645 "{} @@ plainto_tsquery('english', ?)",
2646 quote_identifier(&filter.field)
2647 ),
2648 [query.clone()],
2649 )
2650 .into_simple_expr()
2651 }
2652 // PostgreSQL JSONB operators
2653 (FilterOperator::JsonbContains, FilterValue::String(json)) => {
2654 // field @> ?::jsonb - parameterized
2655 Expr::cust_with_values(
2656 format!("{} @> ?::jsonb", quote_identifier(&filter.field)),
2657 [json.clone()],
2658 )
2659 .into_simple_expr()
2660 }
2661 (FilterOperator::JsonbContainedBy, FilterValue::String(json)) => {
2662 // field <@ ?::jsonb - parameterized
2663 Expr::cust_with_values(
2664 format!("{} <@ ?::jsonb", quote_identifier(&filter.field)),
2665 [json.clone()],
2666 )
2667 .into_simple_expr()
2668 }
2669 (FilterOperator::JsonbKeyExists, FilterValue::String(key)) => {
2670 // field ? 'key' - using PgBinOper for safe parameterization
2671 Expr::cust(quote_identifier(&filter.field))
2672 .into_simple_expr()
2673 .binary(
2674 BinOper::PgOperator(PgBinOper::JsonContainsKey),
2675 SimpleExpr::from(key.clone()),
2676 )
2677 }
2678 (FilterOperator::JsonbAnyKeyExists, FilterValue::Array(keys)) => {
2679 // field ?| array[?, ?] - using PgBinOper for safe parameterization
2680 let placeholders = keys.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
2681 let array_expr = Expr::cust_with_values(
2682 format!("array[{}]", placeholders),
2683 keys.iter().cloned(),
2684 )
2685 .into_simple_expr();
2686 Expr::cust(quote_identifier(&filter.field))
2687 .into_simple_expr()
2688 .binary(
2689 BinOper::PgOperator(PgBinOper::JsonContainsAnyKey),
2690 array_expr,
2691 )
2692 }
2693 (FilterOperator::JsonbAllKeysExist, FilterValue::Array(keys)) => {
2694 // field ?& array[?, ?] - using PgBinOper for safe parameterization
2695 let placeholders = keys.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
2696 let array_expr = Expr::cust_with_values(
2697 format!("array[{}]", placeholders),
2698 keys.iter().cloned(),
2699 )
2700 .into_simple_expr();
2701 Expr::cust(quote_identifier(&filter.field))
2702 .into_simple_expr()
2703 .binary(
2704 BinOper::PgOperator(PgBinOper::JsonContainsAllKeys),
2705 array_expr,
2706 )
2707 }
2708 (FilterOperator::JsonbPathExists, FilterValue::String(path)) => {
2709 // field @? ? - parameterized
2710 Expr::cust_with_values(
2711 format!("{} @? ?", quote_identifier(&filter.field)),
2712 [path.clone()],
2713 )
2714 .into_simple_expr()
2715 }
2716 // PostgreSQL Range operators
2717 (FilterOperator::RangeContains, v) => {
2718 // field @> ? - parameterized
2719 let val = Self::filter_value_to_sql_string(v);
2720 Expr::cust_with_values(
2721 format!("{} @> ?", quote_identifier(&filter.field)),
2722 [val],
2723 )
2724 .into_simple_expr()
2725 }
2726 (FilterOperator::RangeContainedBy, FilterValue::String(range)) => {
2727 // field <@ ? - parameterized
2728 Expr::cust_with_values(
2729 format!("{} <@ ?", quote_identifier(&filter.field)),
2730 [range.clone()],
2731 )
2732 .into_simple_expr()
2733 }
2734 (FilterOperator::RangeOverlaps, FilterValue::String(range)) => {
2735 // field && ? - parameterized
2736 Expr::cust_with_values(
2737 format!("{} && ?", quote_identifier(&filter.field)),
2738 [range.clone()],
2739 )
2740 .into_simple_expr()
2741 }
2742 // Fallback for unsupported combinations
2743 _ => {
2744 // Default to equality for unhandled cases
2745 col.eq(Self::filter_value_to_sea_value(&filter.value))
2746 }
2747 };
2748
2749 cond = cond.add(expr);
2750 }
2751
2752 // Add subquery conditions
2753 for subq_cond in &self.subquery_conditions {
2754 let expr = match subq_cond {
2755 SubqueryCondition::In { field, subquery } => {
2756 // field IN (subquery)
2757 Expr::cust(format!("{} IN {}", quote_identifier(field), subquery))
2758 .into_simple_expr()
2759 }
2760 SubqueryCondition::NotIn { field, subquery } => {
2761 // field NOT IN (subquery)
2762 Expr::cust(format!("{} NOT IN {}", quote_identifier(field), subquery))
2763 .into_simple_expr()
2764 }
2765 SubqueryCondition::Exists { subquery } => {
2766 // EXISTS (subquery)
2767 Expr::cust(format!("EXISTS {}", subquery)).into_simple_expr()
2768 }
2769 SubqueryCondition::NotExists { subquery } => {
2770 // NOT EXISTS (subquery)
2771 Expr::cust(format!("NOT EXISTS {}", subquery)).into_simple_expr()
2772 }
2773 };
2774
2775 cond = cond.add(expr);
2776 }
2777
2778 Some(cond)
2779 }
2780
2781 /// Convert FilterValue to reinhardt_query::value::Value
2782 /// Convert Expression to reinhardt-query Expr for use in WHERE clauses
2783 ///
2784 /// Uses Expr::cust() for arithmetic operations as reinhardt-query doesn't provide
2785 /// multiply/divide/etc. methods. SQL injection risk is low since F() only
2786 /// accepts field names.
2787 fn expression_to_query_expr(expr: &super::annotation::Expression) -> Expr {
2788 use crate::orm::annotation::Expression;
2789
2790 match expr {
2791 Expression::Add(left, right) => {
2792 let left_sql = Self::annotation_value_to_sql(left);
2793 let right_sql = Self::annotation_value_to_sql(right);
2794 Expr::cust(format!("({} + {})", left_sql, right_sql))
2795 }
2796 Expression::Subtract(left, right) => {
2797 let left_sql = Self::annotation_value_to_sql(left);
2798 let right_sql = Self::annotation_value_to_sql(right);
2799 Expr::cust(format!("({} - {})", left_sql, right_sql))
2800 }
2801 Expression::Multiply(left, right) => {
2802 let left_sql = Self::annotation_value_to_sql(left);
2803 let right_sql = Self::annotation_value_to_sql(right);
2804 Expr::cust(format!("({} * {})", left_sql, right_sql))
2805 }
2806 Expression::Divide(left, right) => {
2807 let left_sql = Self::annotation_value_to_sql(left);
2808 let right_sql = Self::annotation_value_to_sql(right);
2809 Expr::cust(format!("({} / {})", left_sql, right_sql))
2810 }
2811 Expression::Case { whens, default } => {
2812 let mut case_sql = "CASE".to_string();
2813 for when in whens.iter() {
2814 // Use When::to_sql() which generates "WHEN condition THEN value"
2815 case_sql.push_str(&format!(" {}", when.to_sql()));
2816 }
2817 if let Some(default_val) = default {
2818 case_sql.push_str(&format!(
2819 " ELSE {}",
2820 Self::annotation_value_to_sql(default_val)
2821 ));
2822 }
2823 case_sql.push_str(" END");
2824 Expr::cust(case_sql)
2825 }
2826 Expression::Coalesce(values) => {
2827 let value_sqls = values
2828 .iter()
2829 .map(|v| Self::annotation_value_to_sql(v))
2830 .collect::<Vec<_>>()
2831 .join(", ");
2832 Expr::cust(format!("COALESCE({})", value_sqls))
2833 }
2834 }
2835 }
2836
2837 /// Convert AnnotationValue to SQL string for custom expressions
2838 ///
2839 /// Delegates to the `AnnotationValue::to_sql()` method which provides
2840 /// complete SQL generation for all annotation value types.
2841 fn annotation_value_to_sql(value: &super::annotation::AnnotationValue) -> String {
2842 value.to_sql()
2843 }
2844
2845 fn filter_value_to_sea_value(v: &FilterValue) -> reinhardt_query::value::Value {
2846 match v {
2847 FilterValue::String(s) => {
2848 // Try to parse as UUID first for proper PostgreSQL uuid column handling
2849 if let Ok(uuid) = Uuid::parse_str(s) {
2850 reinhardt_query::value::Value::Uuid(Some(Box::new(uuid)))
2851 } else {
2852 s.clone().into()
2853 }
2854 }
2855 FilterValue::Integer(i) | FilterValue::Int(i) => (*i).into(),
2856 FilterValue::Float(f) => (*f).into(),
2857 FilterValue::Boolean(b) | FilterValue::Bool(b) => (*b).into(),
2858 FilterValue::Null => reinhardt_query::value::Value::Int(None),
2859 FilterValue::Array(arr) => arr.join(",").into(),
2860 // FieldRef, Expression, and OuterRef are typically handled separately
2861 // in build_where_condition(), but provide proper conversion as fallback
2862 FilterValue::FieldRef(f) => f.field.clone().into(),
2863 FilterValue::Expression(expr) => expr.to_sql().into(),
2864 FilterValue::OuterRef(outer_ref) => outer_ref.field.clone().into(),
2865 }
2866 }
2867
2868 /// Convert FilterValue to SQL-safe string representation
2869 /// Used for custom SQL expressions (PostgreSQL operators)
2870 fn filter_value_to_sql_string(v: &FilterValue) -> String {
2871 match v {
2872 FilterValue::String(s) => format!("'{}'", s.replace('\'', "''")),
2873 FilterValue::Integer(i) | FilterValue::Int(i) => i.to_string(),
2874 FilterValue::Float(f) => f.to_string(),
2875 FilterValue::Boolean(b) | FilterValue::Bool(b) => {
2876 if *b { "TRUE" } else { "FALSE" }.to_string()
2877 }
2878 FilterValue::Null => "NULL".to_string(),
2879 FilterValue::Array(arr) => {
2880 // Format as PostgreSQL array literal
2881 let elements = arr
2882 .iter()
2883 .map(|s| format!("'{}'", s.replace('\'', "''")))
2884 .collect::<Vec<_>>();
2885 format!("ARRAY[{}]", elements.join(", "))
2886 }
2887 FilterValue::FieldRef(f) => f.field.clone(),
2888 FilterValue::Expression(expr) => expr.to_sql(),
2889 FilterValue::OuterRef(outer_ref) => outer_ref.field.clone(),
2890 }
2891 }
2892
2893 /// Convert FilterValue to String representation
2894 // Allow dead_code: internal conversion helper for filter value stringification in queries
2895 #[allow(dead_code)]
2896 fn value_to_string(v: &FilterValue) -> String {
2897 match v {
2898 FilterValue::String(s) => s.clone(),
2899 FilterValue::Integer(i) | FilterValue::Int(i) => i.to_string(),
2900 FilterValue::Float(f) => f.to_string(),
2901 FilterValue::Boolean(b) | FilterValue::Bool(b) => b.to_string(),
2902 FilterValue::Null => String::new(),
2903 FilterValue::Array(arr) => arr.join(","),
2904 FilterValue::FieldRef(f) => f.field.clone(),
2905 FilterValue::Expression(expr) => expr.to_sql(),
2906 FilterValue::OuterRef(outer_ref) => outer_ref.field.clone(),
2907 }
2908 }
2909
2910 /// Parse array string into `Vec<reinhardt_query::value::Value>`
2911 /// Supports comma-separated values or JSON array format
2912 fn parse_array_string(s: &str) -> Vec<reinhardt_query::value::Value> {
2913 let trimmed = s.trim();
2914
2915 // Try parsing as JSON array first
2916 if trimmed.starts_with('[')
2917 && trimmed.ends_with(']')
2918 && let Ok(arr) = serde_json::from_str::<Vec<serde_json::Value>>(trimmed)
2919 {
2920 return arr
2921 .iter()
2922 .map(|v| match v {
2923 serde_json::Value::String(s) => s.clone().into(),
2924 serde_json::Value::Number(n) => {
2925 if let Some(i) = n.as_i64() {
2926 i.into()
2927 } else if let Some(f) = n.as_f64() {
2928 f.into()
2929 } else {
2930 n.to_string().into()
2931 }
2932 }
2933 serde_json::Value::Bool(b) => (*b).into(),
2934 _ => v.to_string().into(),
2935 })
2936 .collect();
2937 }
2938
2939 // Fallback to comma-separated parsing
2940 trimmed
2941 .split(',')
2942 .map(|s| s.trim())
2943 .filter(|s| !s.is_empty())
2944 .map(|s| s.to_string().into())
2945 .collect()
2946 }
2947
2948 /// Convert FilterValue to array of reinhardt_query::value::Value
2949 // Allow dead_code: internal conversion for IN clause array parameter binding
2950 #[allow(dead_code)]
2951 fn value_to_array(v: &FilterValue) -> Vec<reinhardt_query::value::Value> {
2952 match v {
2953 FilterValue::String(s) => Self::parse_array_string(s),
2954 FilterValue::Integer(i) | FilterValue::Int(i) => vec![(*i).into()],
2955 FilterValue::Float(f) => vec![(*f).into()],
2956 FilterValue::Boolean(b) | FilterValue::Bool(b) => vec![(*b).into()],
2957 FilterValue::Null => vec![reinhardt_query::value::Value::Int(None)],
2958 FilterValue::Array(arr) => arr.iter().map(|s| s.clone().into()).collect(),
2959 FilterValue::FieldRef(f) => vec![f.field.clone().into()],
2960 FilterValue::Expression(expr) => vec![expr.to_sql().into()],
2961 FilterValue::OuterRef(outer) => vec![outer.field.clone().into()],
2962 }
2963 }
2964
2965 /// Build WHERE clause from accumulated filters
2966 ///
2967 /// # Deprecation Note
2968 ///
2969 /// This method is maintained for backward compatibility with existing code that
2970 /// expects a string-based WHERE clause. New code should use `build_where_condition()`
2971 /// which returns a `Condition` object that can be directly added to reinhardt-query statements.
2972 ///
2973 /// This method generates a complete SELECT statement internally and extracts only
2974 /// the WHERE portion, which is less efficient than using `build_where_condition()`.
2975 // Allow dead_code: backward-compatible string-based WHERE clause builder for legacy code paths
2976 #[allow(dead_code)]
2977 fn build_where_clause(&self) -> (String, Vec<String>) {
2978 if self.filters.is_empty() {
2979 return (String::new(), Vec::new());
2980 }
2981
2982 // Build reinhardt-query condition
2983 let mut stmt = Query::select();
2984 stmt.from(Alias::new("dummy"));
2985
2986 if let Some(cond) = self.build_where_condition() {
2987 stmt.cond_where(cond);
2988 }
2989
2990 // Convert to SQL string with inline values
2991 use reinhardt_query::prelude::PostgresQueryBuilder;
2992 let sql = stmt.to_string(PostgresQueryBuilder);
2993
2994 // Extract WHERE clause portion by finding the WHERE keyword
2995 let where_clause = if let Some(idx) = sql.find(" WHERE ") {
2996 sql[idx..].to_string()
2997 } else {
2998 String::new()
2999 };
3000
3001 (where_clause, Vec::new())
3002 }
3003
3004 /// Eagerly load related objects using JOIN queries
3005 ///
3006 /// This method performs SQL JOINs to fetch related objects in a single query,
3007 /// reducing the number of database round-trips and preventing N+1 query problems.
3008 ///
3009 /// # Performance
3010 ///
3011 /// Best for one-to-one and many-to-one relationships where JOIN won't create
3012 /// significant data duplication. For one-to-many and many-to-many relationships,
3013 /// consider using `prefetch_related()` instead.
3014 ///
3015 /// # Examples
3016 ///
3017 /// ```no_run
3018 /// # use reinhardt_db::orm::Model;
3019 /// # use serde::{Serialize, Deserialize};
3020 /// # #[derive(Clone, Serialize, Deserialize)]
3021 /// # struct Post { id: Option<i64>, author: Author, category: Category }
3022 /// # #[derive(Clone, Serialize, Deserialize)]
3023 /// # struct Author { name: String }
3024 /// # #[derive(Clone, Serialize, Deserialize)]
3025 /// # struct Category { name: String }
3026 /// # #[derive(Clone)]
3027 /// # struct PostFields;
3028 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
3029 /// # fn with_alias(self, _alias: &str) -> Self { self }
3030 /// # }
3031 /// # impl Model for Post {
3032 /// # type PrimaryKey = i64;
3033 /// # type Fields = PostFields;
3034 /// # fn table_name() -> &'static str { "posts" }
3035 /// # fn new_fields() -> Self::Fields { PostFields }
3036 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3037 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3038 /// # }
3039 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3040 /// // Single query with JOINs instead of N+1 queries
3041 /// let posts = Post::objects()
3042 /// .select_related(&["author", "category"])
3043 /// .all()
3044 /// .await?;
3045 ///
3046 /// // Each post has author and category pre-loaded
3047 /// for post in posts {
3048 /// println!("Author: {}", post.author.name); // No additional query
3049 /// }
3050 /// # Ok(())
3051 /// # }
3052 /// ```
3053 pub fn select_related(mut self, fields: &[&str]) -> Self {
3054 self.select_related_fields = fields.iter().map(|s| s.to_string()).collect();
3055 self
3056 }
3057
3058 /// Generate SELECT query with JOIN clauses for select_related fields
3059 ///
3060 /// Returns reinhardt-query SelectStatement with LEFT JOIN for each related field to enable eager loading.
3061 ///
3062 /// # Examples
3063 ///
3064 /// ```no_run
3065 /// # use reinhardt_db::orm::Model;
3066 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
3067 /// # use serde::{Serialize, Deserialize};
3068 /// # #[derive(Clone, Serialize, Deserialize)]
3069 /// # struct Post { id: Option<i64> }
3070 /// # #[derive(Clone)]
3071 /// # struct PostFields;
3072 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
3073 /// # fn with_alias(self, _alias: &str) -> Self { self }
3074 /// # }
3075 /// # impl Model for Post {
3076 /// # type PrimaryKey = i64;
3077 /// # type Fields = PostFields;
3078 /// # fn table_name() -> &'static str { "posts" }
3079 /// # fn new_fields() -> Self::Fields { PostFields }
3080 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3081 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3082 /// # }
3083 /// let queryset = Post::objects()
3084 /// .select_related(&["author", "category"])
3085 /// .filter(Filter::new("published", FilterOperator::Eq, FilterValue::Boolean(true)));
3086 ///
3087 /// let stmt = queryset.select_related_query();
3088 /// // Generates:
3089 /// // SELECT posts.*, author.*, category.* FROM posts
3090 /// // LEFT JOIN users AS author ON posts.author_id = author.id
3091 /// // LEFT JOIN categories AS category ON posts.category_id = category.id
3092 /// // WHERE posts.published = $1
3093 /// ```
3094 pub fn select_related_query(&self) -> SelectStatement {
3095 let table_name = T::table_name();
3096 let mut stmt = Query::select();
3097
3098 // Apply FROM clause with optional alias
3099 if let Some(ref alias) = self.from_alias {
3100 stmt.from_as(Alias::new(table_name), Alias::new(alias));
3101 } else {
3102 stmt.from(Alias::new(table_name));
3103 }
3104
3105 // Apply DISTINCT if enabled
3106 if self.distinct_enabled {
3107 stmt.distinct();
3108 }
3109
3110 // Add main table columns
3111 stmt.column(ColumnRef::table_asterisk(Alias::new(table_name)));
3112
3113 // Add LEFT JOIN for each related field
3114 for related_field in &self.select_related_fields {
3115 // Convention: related_field is the field name in the model
3116 // We assume FK field is "{related_field}_id" and join to "{related_field}s" table
3117 let fk_field = Alias::new(format!("{}_id", related_field));
3118 let related_table = Alias::new(format!("{}s", related_field));
3119 let related_alias = Alias::new(related_field);
3120
3121 // LEFT JOIN related_table AS related_field ON table.fk_field = related_field.id
3122 stmt.left_join(
3123 related_table,
3124 Expr::col((Alias::new(table_name), fk_field))
3125 .equals((related_alias.clone(), Alias::new("id"))),
3126 );
3127
3128 // Add related table columns to SELECT
3129 stmt.column(ColumnRef::table_asterisk(related_alias));
3130 }
3131
3132 // Apply manual JOINs
3133 for join in &self.joins {
3134 if join.on_condition.is_empty() {
3135 // CROSS JOIN (no ON condition)
3136 if let Some(ref alias) = join.target_alias {
3137 stmt.cross_join((Alias::new(&join.target_table), Alias::new(alias)));
3138 } else {
3139 stmt.cross_join(Alias::new(&join.target_table));
3140 }
3141 } else {
3142 // Convert reinhardt JoinType to reinhardt-query JoinType
3143 let sea_join_type = match join.join_type {
3144 super::sqlalchemy_query::JoinType::Inner => SeaJoinType::InnerJoin,
3145 super::sqlalchemy_query::JoinType::Left => SeaJoinType::LeftJoin,
3146 super::sqlalchemy_query::JoinType::Right => SeaJoinType::RightJoin,
3147 super::sqlalchemy_query::JoinType::Full => SeaJoinType::FullOuterJoin,
3148 };
3149
3150 // Build the join with optional alias
3151 if let Some(ref alias) = join.target_alias {
3152 stmt.join(
3153 sea_join_type,
3154 (Alias::new(&join.target_table), Alias::new(alias)),
3155 Expr::cust(join.on_condition.clone()),
3156 );
3157 } else {
3158 stmt.join(
3159 sea_join_type,
3160 Alias::new(&join.target_table),
3161 Expr::cust(join.on_condition.clone()),
3162 );
3163 }
3164 }
3165 }
3166
3167 // Apply WHERE conditions
3168 if let Some(cond) = self.build_where_condition() {
3169 stmt.cond_where(cond);
3170 }
3171
3172 // Apply GROUP BY
3173 for group_field in &self.group_by_fields {
3174 let col_ref = parse_column_reference(group_field);
3175 stmt.group_by_col(col_ref);
3176 }
3177
3178 // Apply HAVING
3179 for having_cond in &self.having_conditions {
3180 match having_cond {
3181 HavingCondition::AggregateCompare {
3182 func,
3183 field,
3184 operator,
3185 value,
3186 } => {
3187 // Build aggregate function expression
3188 let agg_expr = match func {
3189 AggregateFunc::Avg => {
3190 Func::avg(Expr::col(Alias::new(field)).into_simple_expr())
3191 }
3192 AggregateFunc::Count => {
3193 if field == "*" {
3194 Func::count(Expr::asterisk().into_simple_expr())
3195 } else {
3196 Func::count(Expr::col(Alias::new(field)).into_simple_expr())
3197 }
3198 }
3199 AggregateFunc::Sum => {
3200 Func::sum(Expr::col(Alias::new(field)).into_simple_expr())
3201 }
3202 AggregateFunc::Min => {
3203 Func::min(Expr::col(Alias::new(field)).into_simple_expr())
3204 }
3205 AggregateFunc::Max => {
3206 Func::max(Expr::col(Alias::new(field)).into_simple_expr())
3207 }
3208 };
3209
3210 // Build comparison expression
3211 let having_expr = match operator {
3212 ComparisonOp::Eq => match value {
3213 AggregateValue::Int(v) => agg_expr.eq(*v),
3214 AggregateValue::Float(v) => agg_expr.eq(*v),
3215 },
3216 ComparisonOp::Ne => match value {
3217 AggregateValue::Int(v) => agg_expr.ne(*v),
3218 AggregateValue::Float(v) => agg_expr.ne(*v),
3219 },
3220 ComparisonOp::Gt => match value {
3221 AggregateValue::Int(v) => agg_expr.gt(*v),
3222 AggregateValue::Float(v) => agg_expr.gt(*v),
3223 },
3224 ComparisonOp::Gte => match value {
3225 AggregateValue::Int(v) => agg_expr.gte(*v),
3226 AggregateValue::Float(v) => agg_expr.gte(*v),
3227 },
3228 ComparisonOp::Lt => match value {
3229 AggregateValue::Int(v) => agg_expr.lt(*v),
3230 AggregateValue::Float(v) => agg_expr.lt(*v),
3231 },
3232 ComparisonOp::Lte => match value {
3233 AggregateValue::Int(v) => agg_expr.lte(*v),
3234 AggregateValue::Float(v) => agg_expr.lte(*v),
3235 },
3236 };
3237
3238 stmt.and_having(having_expr);
3239 }
3240 }
3241 }
3242
3243 // Apply ORDER BY
3244 for order_field in &self.order_by_fields {
3245 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
3246 (stripped, true)
3247 } else {
3248 (order_field.as_str(), false)
3249 };
3250
3251 let col_ref = parse_column_reference(field);
3252 let expr = Expr::col(col_ref);
3253 if is_desc {
3254 stmt.order_by_expr(expr, Order::Desc);
3255 } else {
3256 stmt.order_by_expr(expr, Order::Asc);
3257 }
3258 }
3259
3260 // Apply LIMIT/OFFSET
3261 if let Some(limit) = self.limit {
3262 stmt.limit(limit as u64);
3263 }
3264 if let Some(offset) = self.offset {
3265 stmt.offset(offset as u64);
3266 }
3267
3268 stmt.to_owned()
3269 }
3270
3271 /// Eagerly load related objects using separate queries
3272 ///
3273 /// This method performs separate SQL queries for related objects and joins them
3274 /// in memory, which is more efficient than JOINs for one-to-many and many-to-many
3275 /// relationships that would create significant data duplication.
3276 ///
3277 /// # Performance
3278 ///
3279 /// Best for one-to-many and many-to-many relationships where JOINs would create
3280 /// data duplication (e.g., a post with 100 comments would duplicate post data 100 times).
3281 /// Uses 1 + N queries where N is the number of prefetch_related fields.
3282 ///
3283 /// # Examples
3284 ///
3285 /// ```no_run
3286 /// # use reinhardt_db::orm::Model;
3287 /// # use serde::{Serialize, Deserialize};
3288 /// # #[derive(Clone, Serialize, Deserialize)]
3289 /// # struct Post { id: Option<i64>, comments: Vec<Comment>, tags: Vec<Tag> }
3290 /// # #[derive(Clone, Serialize, Deserialize)]
3291 /// # struct Comment { text: String }
3292 /// # #[derive(Clone, Serialize, Deserialize)]
3293 /// # struct Tag { name: String }
3294 /// # #[derive(Clone)]
3295 /// # struct PostFields;
3296 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
3297 /// # fn with_alias(self, _alias: &str) -> Self { self }
3298 /// # }
3299 /// # impl Model for Post {
3300 /// # type PrimaryKey = i64;
3301 /// # type Fields = PostFields;
3302 /// # fn table_name() -> &'static str { "posts" }
3303 /// # fn new_fields() -> Self::Fields { PostFields }
3304 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3305 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3306 /// # }
3307 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3308 /// // 2 queries total instead of N+1 queries
3309 /// let posts = Post::objects()
3310 /// .prefetch_related(&["comments", "tags"])
3311 /// .all()
3312 /// .await?;
3313 ///
3314 /// // Each post has comments and tags pre-loaded
3315 /// for post in posts {
3316 /// for comment in &post.comments {
3317 /// println!("Comment: {}", comment.text); // No additional query
3318 /// }
3319 /// }
3320 /// # Ok(())
3321 /// # }
3322 /// ```
3323 pub fn prefetch_related(mut self, fields: &[&str]) -> Self {
3324 self.prefetch_related_fields = fields.iter().map(|s| s.to_string()).collect();
3325 self
3326 }
3327
3328 /// Generate SELECT queries for prefetch_related fields
3329 ///
3330 /// Returns a vector of (field_name, SelectStatement) tuples, one for each prefetch field.
3331 /// Each query fetches related objects using IN clause with collected primary keys.
3332 ///
3333 /// # Examples
3334 ///
3335 /// ```no_run
3336 /// # use reinhardt_db::orm::Model;
3337 /// # use serde::{Serialize, Deserialize};
3338 /// # #[derive(Clone, Serialize, Deserialize)]
3339 /// # struct Post { id: Option<i64> }
3340 /// # #[derive(Clone)]
3341 /// # struct PostFields;
3342 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
3343 /// # fn with_alias(self, _alias: &str) -> Self { self }
3344 /// # }
3345 /// # impl Model for Post {
3346 /// # type PrimaryKey = i64;
3347 /// # type Fields = PostFields;
3348 /// # fn table_name() -> &'static str { "posts" }
3349 /// # fn new_fields() -> Self::Fields { PostFields }
3350 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3351 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3352 /// # }
3353 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3354 /// let queryset = Post::objects()
3355 /// .prefetch_related(&["comments", "tags"]);
3356 ///
3357 /// let main_results = queryset.all().await?; // Main query
3358 /// let pk_values = vec![1, 2, 3]; // Collected from main results
3359 ///
3360 /// let prefetch_queries = queryset.prefetch_related_queries(&pk_values);
3361 /// // Returns SelectStatements for:
3362 /// // 1. comments: SELECT * FROM comments WHERE post_id IN ($1, $2, $3)
3363 /// // 2. tags: SELECT tags.* FROM tags
3364 /// // INNER JOIN post_tags ON tags.id = post_tags.tag_id
3365 /// // WHERE post_tags.post_id IN ($1, $2, $3)
3366 /// # Ok(())
3367 /// # }
3368 /// ```
3369 pub fn prefetch_related_queries(&self, pk_values: &[i64]) -> Vec<(String, SelectStatement)> {
3370 if pk_values.is_empty() {
3371 return Vec::new();
3372 }
3373
3374 let mut queries = Vec::new();
3375
3376 for related_field in &self.prefetch_related_fields {
3377 // Determine if this is a many-to-many relation or one-to-many
3378 // by querying the model's relationship metadata
3379 let is_m2m = self.is_many_to_many_relation(related_field);
3380
3381 let stmt = if is_m2m {
3382 self.prefetch_many_to_many_query(related_field, pk_values)
3383 } else {
3384 self.prefetch_one_to_many_query(related_field, pk_values)
3385 };
3386
3387 queries.push((related_field.clone(), stmt));
3388 }
3389
3390 queries
3391 }
3392
3393 /// Check if a related field is a many-to-many relation
3394 ///
3395 /// Determines relationship type by querying the model's metadata.
3396 /// Returns true if the relationship is defined as ManyToMany in the model metadata.
3397 fn is_many_to_many_relation(&self, related_field: &str) -> bool {
3398 // Get relationship metadata from the model
3399 let relations = T::relationship_metadata();
3400
3401 // Find the relationship with the matching name
3402 relations
3403 .iter()
3404 .find(|rel| rel.name == related_field)
3405 .map(|rel| rel.relationship_type == super::relationship::RelationshipType::ManyToMany)
3406 .unwrap_or(false)
3407 }
3408
3409 /// Generate query for one-to-many prefetch
3410 ///
3411 /// Generates: SELECT * FROM related_table WHERE fk_field IN (pk_values)
3412 fn prefetch_one_to_many_query(
3413 &self,
3414 related_field: &str,
3415 pk_values: &[i64],
3416 ) -> SelectStatement {
3417 let table_name = T::table_name();
3418 let related_table = Alias::new(format!("{}s", related_field));
3419 let fk_field = Alias::new(format!("{}_id", table_name.trim_end_matches('s')));
3420
3421 let mut stmt = Query::select();
3422 stmt.from(related_table).column(ColumnRef::Asterisk);
3423
3424 // Add IN clause with pk_values
3425 let values: Vec<reinhardt_query::value::Value> =
3426 pk_values.iter().map(|&id| id.into()).collect();
3427 stmt.and_where(Expr::col(fk_field).is_in(values));
3428
3429 stmt.to_owned()
3430 }
3431
3432 /// Generate query for many-to-many prefetch
3433 ///
3434 /// Generates: SELECT related.*, junction.main_id FROM related
3435 /// INNER JOIN junction ON related.id = junction.related_id
3436 /// WHERE junction.main_id IN (pk_values)
3437 fn prefetch_many_to_many_query(
3438 &self,
3439 related_field: &str,
3440 pk_values: &[i64],
3441 ) -> SelectStatement {
3442 let table_name = T::table_name();
3443 // Apply the canonical M2M naming rule used by
3444 // `ManyToManyAccessor::default_through_table` and the autodetector
3445 // (`crates/reinhardt-db/src/migrations/autodetector.rs`):
3446 // `{source_table.to_lowercase()}_{to_snake_case(field_name)}`.
3447 // Without this, prefetch joins target a junction table whose
3448 // casing/snake-case diverges from what `makemigrations` produced
3449 // for the same M2M field (#4659).
3450 let junction_table = Alias::new(format!(
3451 "{}_{}",
3452 table_name.to_lowercase(),
3453 to_snake_case(related_field)
3454 ));
3455
3456 // Look up relationship metadata to derive FK names correctly
3457 let rel_info = T::relationship_metadata().into_iter().find(|r| {
3458 r.name == related_field
3459 && r.relationship_type == super::relationship::RelationshipType::ManyToMany
3460 });
3461
3462 // Derive related table name from metadata
3463 let related_table = if let Some(ref info) = rel_info {
3464 Alias::new(to_snake_case(&info.related_model).to_lowercase())
3465 } else {
3466 // Fallback to pluralization heuristic
3467 Alias::new(format!("{}s", related_field))
3468 };
3469
3470 // Derive junction FK names from metadata or use default_link_fields logic
3471 let table_name_lower = table_name.to_lowercase();
3472 let (junction_main_fk, junction_related_fk) = if let Some(ref info) = rel_info {
3473 let source_fk = if let Some(ref sf) = info.source_field {
3474 sf.clone()
3475 } else {
3476 // Mirror ManyToManyAccessor::default_link_fields logic
3477 let related_lower = to_snake_case(&info.related_model).to_lowercase();
3478 if table_name_lower == related_lower {
3479 format!("from_{}_id", table_name_lower)
3480 } else {
3481 format!("{}_id", table_name_lower)
3482 }
3483 };
3484
3485 let target_fk = if let Some(ref tf) = info.target_field {
3486 tf.clone()
3487 } else {
3488 let related_lower = to_snake_case(&info.related_model).to_lowercase();
3489 if table_name_lower == related_lower {
3490 format!("to_{}_id", table_name_lower)
3491 } else {
3492 format!("{}_id", to_snake_case(related_field))
3493 }
3494 };
3495
3496 (Alias::new(source_fk), Alias::new(target_fk))
3497 } else {
3498 // Fallback to heuristics
3499 let source_fk = format!("{}_id", table_name_lower);
3500 let target_fk = format!("{}_id", to_snake_case(related_field));
3501 (Alias::new(source_fk), Alias::new(target_fk))
3502 };
3503
3504 let mut stmt = Query::select();
3505 stmt.from(related_table.clone())
3506 .column(ColumnRef::table_asterisk(related_table.clone()))
3507 .column((junction_table.clone(), junction_main_fk.clone()))
3508 .inner_join(
3509 junction_table.clone(),
3510 Expr::col((related_table.clone(), Alias::new("id")))
3511 .equals((junction_table.clone(), junction_related_fk)),
3512 );
3513
3514 // Add IN clause with pk_values
3515 let values: Vec<reinhardt_query::value::Value> =
3516 pk_values.iter().map(|&id| id.into()).collect();
3517 stmt.and_where(Expr::col((junction_table, junction_main_fk)).is_in(values));
3518
3519 stmt.to_owned()
3520 }
3521
3522 /// Execute the queryset and return all matching records
3523 ///
3524 /// Fetches all records from the database that match the accumulated filters.
3525 /// If `select_related` fields are specified, performs JOIN queries for eager loading.
3526 ///
3527 /// # Examples
3528 ///
3529 /// ```no_run
3530 /// # use reinhardt_db::orm::Model;
3531 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
3532 /// # use serde::{Serialize, Deserialize};
3533 /// # #[derive(Clone, Serialize, Deserialize)]
3534 /// # struct User { id: Option<i64> }
3535 /// # #[derive(Clone)]
3536 /// # struct UserFields;
3537 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3538 /// # fn with_alias(self, _alias: &str) -> Self { self }
3539 /// # }
3540 /// # impl Model for User {
3541 /// # type PrimaryKey = i64;
3542 /// # type Fields = UserFields;
3543 /// # fn table_name() -> &'static str { "users" }
3544 /// # fn new_fields() -> Self::Fields { UserFields }
3545 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3546 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3547 /// # }
3548 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3549 /// // Fetch all users (Manager.all() returns QuerySet, then call .all().await)
3550 /// let users = User::objects().all().all().await?;
3551 ///
3552 /// // Fetch filtered users with eager loading
3553 /// let active_users = User::objects()
3554 /// .filter(
3555 /// "is_active",
3556 /// FilterOperator::Eq,
3557 /// FilterValue::Boolean(true),
3558 /// )
3559 /// .select_related(&["profile"])
3560 /// .all()
3561 /// .await?;
3562 /// # Ok(())
3563 /// # }
3564 /// ```
3565 ///
3566 /// # Errors
3567 ///
3568 /// Returns an error if:
3569 /// - Database connection fails
3570 /// - SQL execution fails
3571 /// - Deserialization of results fails
3572 pub async fn all(&self) -> reinhardt_core::exception::Result<Vec<T>>
3573 where
3574 T: serde::de::DeserializeOwned,
3575 {
3576 let conn = super::manager::get_connection().await?;
3577
3578 let stmt = if self.select_related_fields.is_empty() {
3579 // Simple SELECT without JOINs
3580 let mut stmt = Query::select();
3581 stmt.from(Alias::new(T::table_name()));
3582
3583 // Column selection considering selected_fields and deferred_fields
3584 if let Some(ref fields) = self.selected_fields {
3585 for field in fields {
3586 // Detect raw SQL expressions (like COUNT(*), AVG(price), etc.)
3587 if field.contains('(') && field.contains(')') {
3588 // Use expr() for raw SQL expressions - clone to satisfy lifetime
3589 stmt.expr(Expr::cust(field.clone()));
3590 } else {
3591 // Regular column reference
3592 let col_ref = parse_column_reference(field);
3593 stmt.column(col_ref);
3594 }
3595 }
3596 } else if !self.deferred_fields.is_empty() {
3597 let all_fields = T::field_metadata();
3598 for field in all_fields {
3599 if !self.deferred_fields.contains(&field.name) {
3600 let col_ref = parse_column_reference(&field.name);
3601 stmt.column(col_ref);
3602 }
3603 }
3604 } else {
3605 stmt.column(ColumnRef::Asterisk);
3606 }
3607
3608 if let Some(cond) = self.build_where_condition() {
3609 stmt.cond_where(cond);
3610 }
3611
3612 // Apply ORDER BY clause
3613 for order_field in &self.order_by_fields {
3614 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
3615 (stripped, true)
3616 } else {
3617 (order_field.as_str(), false)
3618 };
3619
3620 let col_ref = parse_column_reference(field);
3621 let expr = Expr::col(col_ref);
3622 if is_desc {
3623 stmt.order_by_expr(expr, Order::Desc);
3624 } else {
3625 stmt.order_by_expr(expr, Order::Asc);
3626 }
3627 }
3628
3629 // Apply LIMIT/OFFSET
3630 if let Some(limit) = self.limit {
3631 stmt.limit(limit as u64);
3632 }
3633 if let Some(offset) = self.offset {
3634 stmt.offset(offset as u64);
3635 }
3636
3637 stmt.to_owned()
3638 } else {
3639 // SELECT with JOINs for select_related
3640 self.select_related_query()
3641 };
3642
3643 // Convert statement to SQL with inline values (no placeholders)
3644 let sql = stmt.to_string(PostgresQueryBuilder);
3645
3646 // Execute query and deserialize results
3647 let rows = conn.query(&sql, vec![]).await?;
3648 rows.into_iter()
3649 .map(|row| {
3650 serde_json::from_value(serde_json::to_value(&row.data).map_err(|e| {
3651 reinhardt_core::exception::Error::Database(format!(
3652 "Serialization error: {}",
3653 e
3654 ))
3655 })?)
3656 .map_err(|e| {
3657 reinhardt_core::exception::Error::Database(format!(
3658 "Deserialization error: {}",
3659 e
3660 ))
3661 })
3662 })
3663 .collect()
3664 }
3665
3666 /// Execute the queryset and return the first matching record
3667 ///
3668 /// Returns `None` if no records match the query.
3669 ///
3670 /// # Examples
3671 ///
3672 /// ```no_run
3673 /// # use reinhardt_db::orm::Model;
3674 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
3675 /// # use serde::{Serialize, Deserialize};
3676 /// # #[derive(Clone, Serialize, Deserialize)]
3677 /// # struct User { id: Option<i64>, username: String }
3678 /// # #[derive(Clone)]
3679 /// # struct UserFields;
3680 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3681 /// # fn with_alias(self, _alias: &str) -> Self { self }
3682 /// # }
3683 /// # impl Model for User {
3684 /// # type PrimaryKey = i64;
3685 /// # type Fields = UserFields;
3686 /// # fn table_name() -> &'static str { "users" }
3687 /// # fn new_fields() -> Self::Fields { UserFields }
3688 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3689 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3690 /// # }
3691 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3692 /// // Fetch first active user
3693 /// let user = User::objects()
3694 /// .filter(
3695 /// "is_active",
3696 /// FilterOperator::Eq,
3697 /// FilterValue::Boolean(true),
3698 /// )
3699 /// .first()
3700 /// .await?;
3701 ///
3702 /// match user {
3703 /// Some(u) => println!("Found user: {}", u.username),
3704 /// None => println!("No active users found"),
3705 /// }
3706 /// # Ok(())
3707 /// # }
3708 /// ```
3709 pub async fn first(&self) -> reinhardt_core::exception::Result<Option<T>>
3710 where
3711 T: serde::de::DeserializeOwned,
3712 {
3713 let mut results = self.all().await?;
3714 Ok(results.drain(..).next())
3715 }
3716
3717 /// Execute the queryset and return a single matching record
3718 ///
3719 /// Returns an error if zero or multiple records are found.
3720 ///
3721 /// # Examples
3722 ///
3723 /// ```no_run
3724 /// # use reinhardt_db::orm::Model;
3725 /// # use reinhardt_db::orm::{FilterOperator, FilterValue};
3726 /// # use serde::{Serialize, Deserialize};
3727 /// # #[derive(Clone, Serialize, Deserialize)]
3728 /// # struct User { id: Option<i64>, email: String }
3729 /// # #[derive(Clone)]
3730 /// # struct UserFields;
3731 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3732 /// # fn with_alias(self, _alias: &str) -> Self { self }
3733 /// # }
3734 /// # impl Model for User {
3735 /// # type PrimaryKey = i64;
3736 /// # type Fields = UserFields;
3737 /// # fn table_name() -> &'static str { "users" }
3738 /// # fn new_fields() -> Self::Fields { UserFields }
3739 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3740 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3741 /// # }
3742 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3743 /// // Fetch user with specific email (must be unique)
3744 /// let user = User::objects()
3745 /// .filter(
3746 /// "email",
3747 /// FilterOperator::Eq,
3748 /// FilterValue::String("alice@example.com".to_string()),
3749 /// )
3750 /// .get()
3751 /// .await?;
3752 /// # Ok(())
3753 /// # }
3754 /// ```
3755 ///
3756 /// # Errors
3757 ///
3758 /// Returns an error if:
3759 /// - No records match the query
3760 /// - Multiple records match the query
3761 /// - Database connection fails
3762 pub async fn get(&self) -> reinhardt_core::exception::Result<T>
3763 where
3764 T: serde::de::DeserializeOwned,
3765 {
3766 let results = self.all().await?;
3767 match results.len() {
3768 0 => Err(reinhardt_core::exception::Error::Database(
3769 "No record found matching the query".to_string(),
3770 )),
3771 1 => Ok(results.into_iter().next().unwrap()),
3772 n => Err(reinhardt_core::exception::Error::Database(format!(
3773 "Multiple records found ({}), expected exactly one",
3774 n
3775 ))),
3776 }
3777 }
3778
3779 /// Execute the queryset with an explicit database connection and return all records
3780 ///
3781 /// # Examples
3782 ///
3783 /// ```no_run
3784 /// # use reinhardt_db::orm::Model;
3785 /// # use serde::{Serialize, Deserialize};
3786 /// # #[derive(Clone, Serialize, Deserialize)]
3787 /// # struct User { id: Option<i64> }
3788 /// # #[derive(Clone)]
3789 /// # struct UserFields;
3790 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3791 /// # fn with_alias(self, _alias: &str) -> Self { self }
3792 /// # }
3793 /// # impl Model for User {
3794 /// # type PrimaryKey = i64;
3795 /// # type Fields = UserFields;
3796 /// # fn table_name() -> &'static str { "users" }
3797 /// # fn new_fields() -> Self::Fields { UserFields }
3798 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3799 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3800 /// # }
3801 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3802 /// # let db = reinhardt_db::orm::manager::get_connection().await?;
3803 /// let users = User::objects()
3804 /// .all()
3805 /// .all_with_db(&db)
3806 /// .await?;
3807 /// # Ok(())
3808 /// # }
3809 /// ```
3810 pub async fn all_with_db(
3811 &self,
3812 conn: &super::connection::DatabaseConnection,
3813 ) -> reinhardt_core::exception::Result<Vec<T>>
3814 where
3815 T: serde::de::DeserializeOwned,
3816 {
3817 let stmt = if self.select_related_fields.is_empty() {
3818 let mut stmt = Query::select();
3819 stmt.from(Alias::new(T::table_name()));
3820
3821 // Column selection considering selected_fields and deferred_fields
3822 if let Some(ref fields) = self.selected_fields {
3823 for field in fields {
3824 // Detect raw SQL expressions (like COUNT(*), AVG(price), etc.)
3825 if field.contains('(') && field.contains(')') {
3826 // Use expr() for raw SQL expressions - clone to satisfy lifetime
3827 stmt.expr(Expr::cust(field.clone()));
3828 } else {
3829 // Regular column reference
3830 let col_ref = parse_column_reference(field);
3831 stmt.column(col_ref);
3832 }
3833 }
3834 } else if !self.deferred_fields.is_empty() {
3835 let all_fields = T::field_metadata();
3836 for field in all_fields {
3837 if !self.deferred_fields.contains(&field.name) {
3838 let col_ref = parse_column_reference(&field.name);
3839 stmt.column(col_ref);
3840 }
3841 }
3842 } else {
3843 stmt.column(ColumnRef::Asterisk);
3844 }
3845
3846 if let Some(cond) = self.build_where_condition() {
3847 stmt.cond_where(cond);
3848 }
3849
3850 // Apply ORDER BY clause
3851 for order_field in &self.order_by_fields {
3852 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
3853 (stripped, true)
3854 } else {
3855 (order_field.as_str(), false)
3856 };
3857
3858 let col_ref = parse_column_reference(field);
3859 let expr = Expr::col(col_ref);
3860 if is_desc {
3861 stmt.order_by_expr(expr, Order::Desc);
3862 } else {
3863 stmt.order_by_expr(expr, Order::Asc);
3864 }
3865 }
3866
3867 // Apply LIMIT/OFFSET
3868 if let Some(limit) = self.limit {
3869 stmt.limit(limit as u64);
3870 }
3871 if let Some(offset) = self.offset {
3872 stmt.offset(offset as u64);
3873 }
3874
3875 stmt.to_owned()
3876 } else {
3877 self.select_related_query()
3878 };
3879
3880 let sql = stmt.to_string(PostgresQueryBuilder);
3881
3882 let rows = conn.query(&sql, vec![]).await?;
3883 rows.into_iter()
3884 .map(|row| {
3885 serde_json::from_value(serde_json::to_value(&row.data).map_err(|e| {
3886 reinhardt_core::exception::Error::Database(format!(
3887 "Serialization error: {}",
3888 e
3889 ))
3890 })?)
3891 .map_err(|e| {
3892 reinhardt_core::exception::Error::Database(format!(
3893 "Deserialization error: {}",
3894 e
3895 ))
3896 })
3897 })
3898 .collect()
3899 }
3900
3901 /// Execute the queryset with an explicit database connection and return a single record
3902 ///
3903 /// # Examples
3904 ///
3905 /// ```no_run
3906 /// # use reinhardt_db::orm::Model;
3907 /// # use serde::{Serialize, Deserialize};
3908 /// # #[derive(Clone, Serialize, Deserialize)]
3909 /// # struct User { id: Option<i64> }
3910 /// # #[derive(Clone)]
3911 /// # struct UserFields;
3912 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3913 /// # fn with_alias(self, _alias: &str) -> Self { self }
3914 /// # }
3915 /// # impl Model for User {
3916 /// # type PrimaryKey = i64;
3917 /// # type Fields = UserFields;
3918 /// # fn table_name() -> &'static str { "users" }
3919 /// # fn new_fields() -> Self::Fields { UserFields }
3920 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3921 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3922 /// # }
3923 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3924 /// # let user_id = 1;
3925 /// let db = reinhardt_db::orm::manager::get_connection().await?;
3926 /// let user = User::objects()
3927 /// .filter("id", reinhardt_db::orm::FilterOperator::Eq, reinhardt_db::orm::FilterValue::Integer(user_id))
3928 /// .get_with_db(&db)
3929 /// .await?;
3930 /// # Ok(())
3931 /// # }
3932 /// ```
3933 pub async fn get_with_db(
3934 &self,
3935 conn: &super::connection::DatabaseConnection,
3936 ) -> reinhardt_core::exception::Result<T>
3937 where
3938 T: serde::de::DeserializeOwned,
3939 {
3940 let results = self.all_with_db(conn).await?;
3941 match results.len() {
3942 0 => Err(reinhardt_core::exception::Error::NotFound(
3943 "No record found matching the query".to_string(),
3944 )),
3945 1 => Ok(results.into_iter().next().unwrap()),
3946 n => Err(reinhardt_core::exception::Error::Database(format!(
3947 "Multiple records found ({}), expected exactly one",
3948 n
3949 ))),
3950 }
3951 }
3952
3953 /// Execute the queryset with an explicit database connection and return the first record
3954 ///
3955 /// # Examples
3956 ///
3957 /// ```no_run
3958 /// # use reinhardt_db::orm::Model;
3959 /// # use serde::{Serialize, Deserialize};
3960 /// # #[derive(Clone, Serialize, Deserialize)]
3961 /// # struct User { id: Option<i64> }
3962 /// # #[derive(Clone)]
3963 /// # struct UserFields;
3964 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
3965 /// # fn with_alias(self, _alias: &str) -> Self { self }
3966 /// # }
3967 /// # impl Model for User {
3968 /// # type PrimaryKey = i64;
3969 /// # type Fields = UserFields;
3970 /// # fn table_name() -> &'static str { "users" }
3971 /// # fn new_fields() -> Self::Fields { UserFields }
3972 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
3973 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
3974 /// # }
3975 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3976 /// let db = reinhardt_db::orm::manager::get_connection().await?;
3977 /// let user = User::objects()
3978 /// .filter("status", reinhardt_db::orm::FilterOperator::Eq, reinhardt_db::orm::FilterValue::String("active".to_string()))
3979 /// .first_with_db(&db)
3980 /// .await?;
3981 /// # Ok(())
3982 /// # }
3983 /// ```
3984 pub async fn first_with_db(
3985 &self,
3986 conn: &super::connection::DatabaseConnection,
3987 ) -> reinhardt_core::exception::Result<Option<T>>
3988 where
3989 T: serde::de::DeserializeOwned,
3990 {
3991 let mut results = self.all_with_db(conn).await?;
3992 Ok(results.drain(..).next())
3993 }
3994
3995 /// Execute the queryset and return the count of matching records
3996 ///
3997 /// More efficient than calling `all().await?.len()` as it only executes COUNT query.
3998 ///
3999 /// # Examples
4000 ///
4001 /// ```no_run
4002 /// # use reinhardt_db::orm::Model;
4003 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4004 /// # use serde::{Serialize, Deserialize};
4005 /// # #[derive(Clone, Serialize, Deserialize)]
4006 /// # struct User { id: Option<i64> }
4007 /// # #[derive(Clone)]
4008 /// # struct UserFields;
4009 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4010 /// # fn with_alias(self, _alias: &str) -> Self { self }
4011 /// # }
4012 /// # impl Model for User {
4013 /// # type PrimaryKey = i64;
4014 /// # type Fields = UserFields;
4015 /// # fn table_name() -> &'static str { "users" }
4016 /// # fn new_fields() -> Self::Fields { UserFields }
4017 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4018 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4019 /// # }
4020 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4021 /// // Count active users
4022 /// let count = User::objects()
4023 /// .filter(
4024 /// "is_active",
4025 /// FilterOperator::Eq,
4026 /// FilterValue::Boolean(true),
4027 /// )
4028 /// .count()
4029 /// .await?;
4030 ///
4031 /// println!("Active users: {}", count);
4032 /// # Ok(())
4033 /// # }
4034 /// ```
4035 pub async fn count(&self) -> reinhardt_core::exception::Result<usize> {
4036 use reinhardt_query::prelude::{Func, PostgresQueryBuilder, QueryBuilder};
4037
4038 let conn = super::manager::get_connection().await?;
4039
4040 // Build COUNT query using reinhardt-query
4041 let mut stmt = Query::select();
4042 stmt.from(Alias::new(T::table_name()))
4043 .expr(Func::count(Expr::asterisk().into_simple_expr()));
4044
4045 // Add WHERE conditions
4046 if let Some(cond) = self.build_where_condition() {
4047 stmt.cond_where(cond);
4048 }
4049
4050 // Convert to SQL and extract parameter values
4051 let (sql, values) = PostgresQueryBuilder.build_select(&stmt);
4052
4053 // Convert reinhardt_query::value::Values to QueryValue
4054 let params = super::execution::convert_values(values);
4055
4056 // Execute query with parameters
4057 let rows = conn.query(&sql, params).await?;
4058 if let Some(row) = rows.first() {
4059 // Extract count from first row
4060 if let Some(count_value) = row.data.get("count")
4061 && let Some(count) = count_value.as_i64()
4062 {
4063 return Ok(count as usize);
4064 }
4065 }
4066
4067 Ok(0)
4068 }
4069
4070 /// Check if any records match the queryset
4071 ///
4072 /// More efficient than calling `count().await? > 0` as it can short-circuit.
4073 ///
4074 /// # Examples
4075 ///
4076 /// ```no_run
4077 /// # use reinhardt_db::orm::Model;
4078 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4079 /// # use serde::{Serialize, Deserialize};
4080 /// # #[derive(Clone, Serialize, Deserialize)]
4081 /// # struct User { id: Option<i64> }
4082 /// # #[derive(Clone)]
4083 /// # struct UserFields;
4084 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4085 /// # fn with_alias(self, _alias: &str) -> Self { self }
4086 /// # }
4087 /// # impl Model for User {
4088 /// # type PrimaryKey = i64;
4089 /// # type Fields = UserFields;
4090 /// # fn table_name() -> &'static str { "users" }
4091 /// # fn new_fields() -> Self::Fields { UserFields }
4092 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4093 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4094 /// # }
4095 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4096 /// // Check if any admin users exist
4097 /// let has_admin = User::objects()
4098 /// .filter(
4099 /// "role",
4100 /// FilterOperator::Eq,
4101 /// FilterValue::String("admin".to_string()),
4102 /// )
4103 /// .exists()
4104 /// .await?;
4105 ///
4106 /// if has_admin {
4107 /// println!("Admin users exist");
4108 /// }
4109 /// # Ok(())
4110 /// # }
4111 /// ```
4112 pub async fn exists(&self) -> reinhardt_core::exception::Result<bool> {
4113 let count = self.count().await?;
4114 Ok(count > 0)
4115 }
4116
4117 /// Create a new object in the database
4118 ///
4119 /// # Examples
4120 ///
4121 /// ```no_run
4122 /// # use reinhardt_db::orm::Model;
4123 /// # use serde::{Serialize, Deserialize};
4124 /// # #[derive(Clone, Serialize, Deserialize)]
4125 /// # struct User { id: Option<i64>, username: String, email: String }
4126 /// # #[derive(Clone)]
4127 /// # struct UserFields;
4128 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4129 /// # fn with_alias(self, _alias: &str) -> Self { self }
4130 /// # }
4131 /// # impl Model for User {
4132 /// # type PrimaryKey = i64;
4133 /// # type Fields = UserFields;
4134 /// # fn table_name() -> &'static str { "users" }
4135 /// # fn new_fields() -> Self::Fields { UserFields }
4136 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4137 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4138 /// # }
4139 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4140 /// let user = User {
4141 /// id: None,
4142 /// username: "alice".to_string(),
4143 /// email: "alice@example.com".to_string(),
4144 /// };
4145 /// let created = User::objects().create(&user).await?;
4146 /// # Ok(())
4147 /// # }
4148 /// ```
4149 pub async fn create(&self, object: T) -> reinhardt_core::exception::Result<T>
4150 where
4151 T: super::Model + Clone,
4152 {
4153 // Delegate to Manager::create() which handles all the SQL generation,
4154 // database connection, primary key retrieval, and error handling
4155 match &self.manager {
4156 Some(manager) => manager.create(&object).await,
4157 None => {
4158 // Fallback: create a new manager instance if none exists
4159 let manager = super::manager::Manager::<T>::new();
4160 manager.create(&object).await
4161 }
4162 }
4163 }
4164
4165 /// Generate UPDATE statement using reinhardt-query
4166 pub fn update_query(
4167 &self,
4168 updates: &HashMap<String, UpdateValue>,
4169 ) -> reinhardt_query::prelude::UpdateStatement {
4170 let mut stmt = Query::update();
4171 stmt.table(Alias::new(T::table_name()));
4172
4173 // Add SET clauses
4174 for (field, value) in updates {
4175 let val_expr = match value {
4176 UpdateValue::String(s) => Expr::val(s.clone()),
4177 UpdateValue::Integer(i) => Expr::val(*i),
4178 UpdateValue::Float(f) => Expr::val(*f),
4179 UpdateValue::Boolean(b) => Expr::val(*b),
4180 UpdateValue::Null => Expr::val(reinhardt_query::value::Value::Int(None)),
4181 UpdateValue::FieldRef(f) => Expr::col(Alias::new(&f.field)),
4182 UpdateValue::Expression(expr) => Self::expression_to_query_expr(expr),
4183 };
4184 stmt.value_expr(Alias::new(field), val_expr);
4185 }
4186
4187 // Add WHERE conditions
4188 if let Some(cond) = self.build_where_condition() {
4189 stmt.cond_where(cond);
4190 }
4191
4192 stmt.to_owned()
4193 }
4194
4195 /// Generate UPDATE SQL with WHERE clause and parameter binding
4196 ///
4197 /// Returns SQL with placeholders ($1, $2, etc.) and the values to bind.
4198 ///
4199 /// # Examples
4200 ///
4201 /// ```no_run
4202 /// # use reinhardt_db::orm::Model;
4203 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4204 /// # use reinhardt_db::orm::query::UpdateValue;
4205 /// # use serde::{Serialize, Deserialize};
4206 /// # #[derive(Clone, Serialize, Deserialize)]
4207 /// # struct User { id: Option<i64> }
4208 /// # #[derive(Clone)]
4209 /// # struct UserFields;
4210 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4211 /// # fn with_alias(self, _alias: &str) -> Self { self }
4212 /// # }
4213 /// # impl Model for User {
4214 /// # type PrimaryKey = i64;
4215 /// # type Fields = UserFields;
4216 /// # fn table_name() -> &'static str { "users" }
4217 /// # fn new_fields() -> Self::Fields { UserFields }
4218 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4219 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4220 /// # }
4221 /// use std::collections::HashMap;
4222 /// let queryset = User::objects()
4223 /// .filter("id", FilterOperator::Eq, FilterValue::Integer(1));
4224 ///
4225 /// let mut updates = HashMap::new();
4226 /// updates.insert("name".to_string(), UpdateValue::String("Alice".to_string()));
4227 /// updates.insert("email".to_string(), UpdateValue::String("alice@example.com".to_string()));
4228 /// let (sql, params) = queryset.update_sql(&updates);
4229 /// // sql: "UPDATE users SET name = $1, email = $2 WHERE id = $3"
4230 /// // params: ["Alice", "alice@example.com", "1"]
4231 /// ```
4232 pub fn update_sql(&self, updates: &HashMap<String, UpdateValue>) -> (String, Vec<String>) {
4233 let stmt = self.update_query(updates);
4234 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryBuilder};
4235 let (sql, values) = PostgresQueryBuilder.build_update(&stmt);
4236 let params: Vec<String> = values
4237 .iter()
4238 .map(|v| Self::sea_value_to_string(v))
4239 .collect();
4240 (sql, params)
4241 }
4242
4243 /// Convert reinhardt-query Value to String without SQL quoting
4244 fn sea_value_to_string(value: &reinhardt_query::value::Value) -> String {
4245 use reinhardt_query::value::Value;
4246 match value {
4247 Value::Bool(Some(b)) => b.to_string(),
4248 Value::TinyInt(Some(i)) => i.to_string(),
4249 Value::SmallInt(Some(i)) => i.to_string(),
4250 Value::Int(Some(i)) => i.to_string(),
4251 Value::BigInt(Some(i)) => i.to_string(),
4252 Value::TinyUnsigned(Some(i)) => i.to_string(),
4253 Value::SmallUnsigned(Some(i)) => i.to_string(),
4254 Value::Unsigned(Some(i)) => i.to_string(),
4255 Value::BigUnsigned(Some(i)) => i.to_string(),
4256 Value::Float(Some(f)) => f.to_string(),
4257 Value::Double(Some(f)) => f.to_string(),
4258 Value::String(Some(s)) => s.to_string(),
4259 Value::Bytes(Some(b)) => String::from_utf8_lossy(b).to_string(),
4260 _ => String::new(),
4261 }
4262 }
4263
4264 /// Generate DELETE SQL with WHERE clause and parameter binding
4265 ///
4266 /// Returns SQL with placeholders ($1, $2, etc.) and the values to bind.
4267 ///
4268 /// # Safety
4269 ///
4270 /// This method will panic if no filters are set to prevent accidental deletion of all rows.
4271 /// Always use `.filter()` before calling this method.
4272 ///
4273 /// # Examples
4274 ///
4275 /// ```no_run
4276 /// # use reinhardt_db::orm::Model;
4277 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4278 /// # use serde::{Serialize, Deserialize};
4279 /// # #[derive(Clone, Serialize, Deserialize)]
4280 /// # struct User { id: Option<i64> }
4281 /// # #[derive(Clone)]
4282 /// # struct UserFields;
4283 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4284 /// # fn with_alias(self, _alias: &str) -> Self { self }
4285 /// # }
4286 /// # impl Model for User {
4287 /// # type PrimaryKey = i64;
4288 /// # type Fields = UserFields;
4289 /// # fn table_name() -> &'static str { "users" }
4290 /// # fn new_fields() -> Self::Fields { UserFields }
4291 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4292 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4293 /// # }
4294 /// let queryset = User::objects()
4295 /// .filter("id", FilterOperator::Eq, FilterValue::Integer(1));
4296 ///
4297 /// let (sql, params) = queryset.delete_sql();
4298 /// // sql: "DELETE FROM users WHERE id = $1"
4299 /// // params: ["1"]
4300 /// ```
4301 /// Generate DELETE statement using reinhardt-query
4302 pub fn delete_query(&self) -> reinhardt_query::prelude::DeleteStatement {
4303 if self.filters.is_empty() {
4304 panic!(
4305 "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
4306 );
4307 }
4308
4309 let mut stmt = Query::delete();
4310 stmt.from_table(Alias::new(T::table_name()));
4311
4312 // Add WHERE conditions
4313 if let Some(cond) = self.build_where_condition() {
4314 stmt.cond_where(cond);
4315 }
4316
4317 stmt.to_owned()
4318 }
4319
4320 /// Deletes sql.
4321 pub fn delete_sql(&self) -> (String, Vec<String>) {
4322 let stmt = self.delete_query();
4323 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryBuilder};
4324 let (sql, values) = PostgresQueryBuilder.build_delete(&stmt);
4325 let params: Vec<String> = values
4326 .iter()
4327 .map(|v| Self::sea_value_to_string(v))
4328 .collect();
4329 (sql, params)
4330 }
4331
4332 /// Retrieve a single object by composite primary key
4333 ///
4334 /// This method queries the database using all fields that compose the composite primary key.
4335 /// It validates that all required primary key fields are provided and returns the matching record.
4336 ///
4337 /// # Examples
4338 ///
4339 /// ```no_run
4340 /// # use reinhardt_db::orm::Model;
4341 /// # use reinhardt_db::orm::composite_pk::{CompositePrimaryKey, PkValue};
4342 /// # use serde::{Serialize, Deserialize};
4343 /// # use std::collections::HashMap;
4344 /// # #[derive(Clone, Serialize, Deserialize)]
4345 /// # struct PostTag { post_id: i64, tag_id: i64 }
4346 /// # #[derive(Clone)]
4347 /// # struct PostTagFields;
4348 /// # impl reinhardt_db::orm::model::FieldSelector for PostTagFields {
4349 /// # fn with_alias(self, _alias: &str) -> Self { self }
4350 /// # }
4351 /// # impl Model for PostTag {
4352 /// # type PrimaryKey = i64;
4353 /// # type Fields = PostTagFields;
4354 /// # fn table_name() -> &'static str { "post_tags" }
4355 /// # fn new_fields() -> Self::Fields { PostTagFields }
4356 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { None }
4357 /// # fn set_primary_key(&mut self, _value: Self::PrimaryKey) {}
4358 /// # fn composite_primary_key() -> Option<CompositePrimaryKey> {
4359 /// # CompositePrimaryKey::new(vec!["post_id".to_string(), "tag_id".to_string()]).ok()
4360 /// # }
4361 /// # }
4362 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4363 /// let mut pk_values = HashMap::new();
4364 /// pk_values.insert("post_id".to_string(), PkValue::Int(1));
4365 /// pk_values.insert("tag_id".to_string(), PkValue::Int(5));
4366 ///
4367 /// let post_tag = PostTag::objects().get_composite(&pk_values).await?;
4368 /// # Ok(())
4369 /// # }
4370 /// ```
4371 ///
4372 /// # Errors
4373 ///
4374 /// Returns an error if:
4375 /// - The model doesn't have a composite primary key
4376 /// - Required primary key fields are missing from the provided values
4377 /// - No matching record is found in the database
4378 /// - Multiple records match (should not happen with a valid composite PK)
4379 pub async fn get_composite(
4380 &self,
4381 pk_values: &HashMap<String, super::composite_pk::PkValue>,
4382 ) -> reinhardt_core::exception::Result<T>
4383 where
4384 T: super::Model + Clone,
4385 {
4386 use reinhardt_query::prelude::{
4387 Alias, BinOper, ColumnRef, Expr, PostgresQueryBuilder, Value,
4388 };
4389
4390 // Get composite primary key definition from the model
4391 let composite_pk = T::composite_primary_key().ok_or_else(|| {
4392 reinhardt_core::exception::Error::Database(
4393 "Model does not have a composite primary key".to_string(),
4394 )
4395 })?;
4396
4397 // Validate that all required PK fields are provided
4398 composite_pk.validate(pk_values).map_err(|e| {
4399 reinhardt_core::exception::Error::Database(format!(
4400 "Composite PK validation failed: {}",
4401 e
4402 ))
4403 })?;
4404
4405 // Build SELECT query using reinhardt-query
4406 let table_name = T::table_name();
4407 let mut query = Query::select();
4408
4409 // Use Alias::new for table name
4410 let table_alias = Alias::new(table_name);
4411 query.from(table_alias).column(ColumnRef::Asterisk);
4412
4413 // Add WHERE conditions for each composite PK field
4414 for field_name in composite_pk.fields() {
4415 let pk_value: &super::composite_pk::PkValue = pk_values.get(field_name).unwrap();
4416 let col_alias = Alias::new(field_name);
4417
4418 match pk_value {
4419 &super::composite_pk::PkValue::Int(v) => {
4420 let condition = Expr::col(col_alias)
4421 .binary(BinOper::Equal, Expr::value(Value::BigInt(Some(v))));
4422 query.and_where(condition);
4423 }
4424 &super::composite_pk::PkValue::Uint(v) => {
4425 let condition = Expr::col(col_alias)
4426 .binary(BinOper::Equal, Expr::value(Value::BigInt(Some(v as i64))));
4427 query.and_where(condition);
4428 }
4429 super::composite_pk::PkValue::String(v) => {
4430 let condition = Expr::col(col_alias).binary(
4431 BinOper::Equal,
4432 Expr::value(Value::String(Some(Box::new(v.clone())))),
4433 );
4434 query.and_where(condition);
4435 }
4436 &super::composite_pk::PkValue::Bool(v) => {
4437 let condition = Expr::col(col_alias)
4438 .binary(BinOper::Equal, Expr::value(Value::Bool(Some(v))));
4439 query.and_where(condition);
4440 }
4441 }
4442 }
4443
4444 // Build SQL with inline values (no placeholders)
4445 let sql = query.to_string(PostgresQueryBuilder);
4446
4447 // Execute query using database connection
4448 let conn = super::manager::get_connection().await?;
4449
4450 // Execute the SELECT query
4451 let rows = conn.query(&sql, vec![]).await?;
4452
4453 // Composite PK queries should return exactly one row
4454 if rows.is_empty() {
4455 return Err(reinhardt_core::exception::Error::Database(
4456 "No record found matching the composite primary key".to_string(),
4457 ));
4458 }
4459
4460 if rows.len() > 1 {
4461 return Err(reinhardt_core::exception::Error::Database(format!(
4462 "Multiple records found ({}) for composite primary key, expected exactly one",
4463 rows.len()
4464 )));
4465 }
4466
4467 // Deserialize the single row into the model
4468 let row = &rows[0];
4469 let value = serde_json::to_value(&row.data).map_err(|e| {
4470 reinhardt_core::exception::Error::Database(format!("Serialization error: {}", e))
4471 })?;
4472
4473 serde_json::from_value(value).map_err(|e| {
4474 reinhardt_core::exception::Error::Database(format!("Deserialization error: {}", e))
4475 })
4476 }
4477
4478 /// Add an annotation to the QuerySet
4479 ///
4480 /// Annotations allow you to add calculated fields to query results using expressions,
4481 /// aggregations, or subqueries. The annotation will be added to the SELECT clause.
4482 ///
4483 /// # Examples
4484 ///
4485 /// ```no_run
4486 /// # use reinhardt_db::orm::Model;
4487 /// # use serde::{Serialize, Deserialize};
4488 /// # #[derive(Clone, Serialize, Deserialize)]
4489 /// # struct User { id: Option<i64> }
4490 /// # #[derive(Clone)]
4491 /// # struct UserFields;
4492 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4493 /// # fn with_alias(self, _alias: &str) -> Self { self }
4494 /// # }
4495 /// # impl Model for User {
4496 /// # type PrimaryKey = i64;
4497 /// # type Fields = UserFields;
4498 /// # fn table_name() -> &'static str { "users" }
4499 /// # fn new_fields() -> Self::Fields { UserFields }
4500 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4501 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4502 /// # }
4503 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4504 /// use reinhardt_db::orm::annotation::{Annotation, AnnotationValue};
4505 /// use reinhardt_db::orm::aggregation::Aggregate;
4506 ///
4507 /// // Add aggregate annotation
4508 /// let users = User::objects()
4509 /// .annotate(Annotation::new("total_orders",
4510 /// AnnotationValue::Aggregate(Aggregate::count(Some("orders")))))
4511 /// .all()
4512 /// .await?;
4513 /// # Ok(())
4514 /// # }
4515 /// ```
4516 pub fn annotate(mut self, annotation: super::annotation::Annotation) -> Self {
4517 self.annotations.push(annotation);
4518 self
4519 }
4520
4521 /// Add a subquery annotation to the QuerySet (SELECT clause subquery)
4522 ///
4523 /// This method adds a scalar subquery to the SELECT clause, allowing you to
4524 /// include computed values from related tables without explicit JOINs.
4525 ///
4526 /// # Type Parameters
4527 ///
4528 /// * `M` - The model type for the subquery
4529 /// * `F` - A closure that builds the subquery
4530 ///
4531 /// # Parameters
4532 ///
4533 /// * `name` - The alias for the subquery result column
4534 /// * `builder` - A closure that receives a fresh `QuerySet<M>` and returns a configured QuerySet
4535 ///
4536 /// # Examples
4537 ///
4538 /// ```no_run
4539 /// # use reinhardt_db::orm::Model;
4540 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4541 /// # use reinhardt_db::orm::OuterRef;
4542 /// # use serde::{Serialize, Deserialize};
4543 /// # #[derive(Clone, Serialize, Deserialize)]
4544 /// # struct Author { id: Option<i64> }
4545 /// # #[derive(Clone)]
4546 /// # struct AuthorFields;
4547 /// # impl reinhardt_db::orm::model::FieldSelector for AuthorFields {
4548 /// # fn with_alias(self, _alias: &str) -> Self { self }
4549 /// # }
4550 /// # impl Model for Author {
4551 /// # type PrimaryKey = i64;
4552 /// # type Fields = AuthorFields;
4553 /// # fn table_name() -> &'static str { "authors" }
4554 /// # fn new_fields() -> Self::Fields { AuthorFields }
4555 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4556 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4557 /// # }
4558 /// # #[derive(Clone, Serialize, Deserialize)]
4559 /// # struct Book { id: Option<i64> }
4560 /// # #[derive(Clone)]
4561 /// # struct BookFields;
4562 /// # impl reinhardt_db::orm::model::FieldSelector for BookFields {
4563 /// # fn with_alias(self, _alias: &str) -> Self { self }
4564 /// # }
4565 /// # impl Model for Book {
4566 /// # type PrimaryKey = i64;
4567 /// # type Fields = BookFields;
4568 /// # fn table_name() -> &'static str { "books" }
4569 /// # fn new_fields() -> Self::Fields { BookFields }
4570 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4571 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4572 /// # }
4573 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4574 /// // Add book count for each author
4575 /// let authors = Author::objects()
4576 /// .annotate_subquery::<Book, _>("book_count", |subq| {
4577 /// subq.filter(Filter::new(
4578 /// "author_id",
4579 /// FilterOperator::Eq,
4580 /// FilterValue::OuterRef(OuterRef::new("authors.id"))
4581 /// ))
4582 /// .values(&["COUNT(*)"])
4583 /// })
4584 /// .all()
4585 /// .await?;
4586 /// // Generates: SELECT *, (SELECT COUNT(*) FROM books WHERE author_id = authors.id) AS book_count FROM authors
4587 /// # Ok(())
4588 /// # }
4589 /// ```
4590 pub fn annotate_subquery<M, F>(mut self, name: &str, builder: F) -> Self
4591 where
4592 M: super::Model + 'static,
4593 F: FnOnce(QuerySet<M>) -> QuerySet<M>,
4594 {
4595 // Create a fresh QuerySet for the subquery model
4596 let subquery_qs = QuerySet::<M>::new();
4597 // Apply the builder to configure the subquery
4598 let configured_subquery = builder(subquery_qs);
4599 // Generate SQL for the subquery (wrapped in parentheses)
4600 let subquery_sql = configured_subquery.as_subquery();
4601
4602 // Add as annotation using AnnotationValue::Subquery
4603 let annotation = super::annotation::Annotation {
4604 alias: name.to_string(),
4605 value: super::annotation::AnnotationValue::Subquery(subquery_sql),
4606 };
4607 self.annotations.push(annotation);
4608 self
4609 }
4610
4611 /// Perform an aggregation on the QuerySet
4612 ///
4613 /// Aggregations allow you to calculate summary statistics (COUNT, SUM, AVG, MAX, MIN)
4614 /// for the queryset. The aggregation result will be added to the SELECT clause.
4615 ///
4616 /// # Examples
4617 ///
4618 /// ```no_run
4619 /// # use reinhardt_db::orm::Model;
4620 /// # use serde::{Serialize, Deserialize};
4621 /// # #[derive(Serialize, Deserialize, Clone)]
4622 /// # struct User { id: Option<i64> }
4623 /// # #[derive(Clone)]
4624 /// # struct UserFields;
4625 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4626 /// # fn with_alias(self, _alias: &str) -> Self { self }
4627 /// # }
4628 /// # impl Model for User {
4629 /// # type PrimaryKey = i64;
4630 /// # type Fields = UserFields;
4631 /// # fn table_name() -> &'static str { "users" }
4632 /// # fn new_fields() -> Self::Fields { UserFields }
4633 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4634 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4635 /// # }
4636 /// # #[derive(Serialize, Deserialize, Clone)]
4637 /// # struct Order { id: Option<i64> }
4638 /// # #[derive(Clone)]
4639 /// # struct OrderFields;
4640 /// # impl reinhardt_db::orm::model::FieldSelector for OrderFields {
4641 /// # fn with_alias(self, _alias: &str) -> Self { self }
4642 /// # }
4643 /// # impl Model for Order {
4644 /// # type PrimaryKey = i64;
4645 /// # type Fields = OrderFields;
4646 /// # fn table_name() -> &'static str { "orders" }
4647 /// # fn new_fields() -> Self::Fields { OrderFields }
4648 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4649 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4650 /// # }
4651 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4652 /// use reinhardt_db::orm::aggregation::Aggregate;
4653 ///
4654 /// // Count all users
4655 /// let result = User::objects()
4656 /// .all()
4657 /// .aggregate(Aggregate::count_all().with_alias("total_users"))
4658 /// .all()
4659 /// .await?;
4660 ///
4661 /// // Sum order amounts
4662 /// let result = Order::objects()
4663 /// .all()
4664 /// .aggregate(Aggregate::sum("amount").with_alias("total_amount"))
4665 /// .all()
4666 /// .await?;
4667 /// # Ok(())
4668 /// # }
4669 /// ```
4670 pub fn aggregate(mut self, aggregate: super::aggregation::Aggregate) -> Self {
4671 // Convert Aggregate to Annotation and add to annotations list
4672 let alias = aggregate
4673 .alias
4674 .clone()
4675 .unwrap_or_else(|| aggregate.func.to_string().to_lowercase());
4676 let annotation = super::annotation::Annotation {
4677 alias,
4678 value: super::annotation::AnnotationValue::Aggregate(aggregate),
4679 };
4680 self.annotations.push(annotation);
4681 self
4682 }
4683
4684 /// Converts to sql.
4685 pub fn to_sql(&self) -> String {
4686 let mut stmt = if self.select_related_fields.is_empty() {
4687 // Simple SELECT without JOINs
4688 let mut stmt = Query::select();
4689
4690 // Apply FROM clause with optional alias
4691 if let Some(ref alias) = self.from_alias {
4692 stmt.from_as(Alias::new(T::table_name()), Alias::new(alias));
4693 } else {
4694 stmt.from(Alias::new(T::table_name()));
4695 }
4696
4697 // Apply DISTINCT if enabled
4698 if self.distinct_enabled {
4699 stmt.distinct();
4700 }
4701
4702 // Column selection considering selected_fields and deferred_fields
4703 if let Some(ref fields) = self.selected_fields {
4704 for field in fields {
4705 // Detect raw SQL expressions (like COUNT(*), AVG(price), etc.)
4706 if field.contains('(') && field.contains(')') {
4707 // Use expr() for raw SQL expressions - clone to satisfy lifetime
4708 stmt.expr(Expr::cust(field.clone()));
4709 } else {
4710 // Regular column reference
4711 let col_ref = parse_column_reference(field);
4712 stmt.column(col_ref);
4713 }
4714 }
4715 } else if !self.deferred_fields.is_empty() {
4716 let all_fields = T::field_metadata();
4717 for field in all_fields {
4718 if !self.deferred_fields.contains(&field.name) {
4719 let col_ref = parse_column_reference(&field.name);
4720 stmt.column(col_ref);
4721 }
4722 }
4723 } else {
4724 stmt.column(ColumnRef::Asterisk);
4725 }
4726
4727 // Apply JOINs
4728 for join in &self.joins {
4729 if join.on_condition.is_empty() {
4730 // CROSS JOIN (no ON condition)
4731 if let Some(ref alias) = join.target_alias {
4732 // CROSS JOIN with alias - reinhardt-query doesn't support this directly
4733 // Use regular join syntax instead
4734 stmt.cross_join((Alias::new(&join.target_table), Alias::new(alias)));
4735 } else {
4736 stmt.cross_join(Alias::new(&join.target_table));
4737 }
4738 } else {
4739 // Convert reinhardt JoinType to reinhardt-query JoinType
4740 let sea_join_type = match join.join_type {
4741 super::sqlalchemy_query::JoinType::Inner => SeaJoinType::InnerJoin,
4742 super::sqlalchemy_query::JoinType::Left => SeaJoinType::LeftJoin,
4743 super::sqlalchemy_query::JoinType::Right => SeaJoinType::RightJoin,
4744 super::sqlalchemy_query::JoinType::Full => SeaJoinType::FullOuterJoin,
4745 };
4746
4747 // Build the join with optional alias
4748 if let Some(ref alias) = join.target_alias {
4749 // JOIN with alias: (table, alias)
4750 stmt.join(
4751 sea_join_type,
4752 (Alias::new(&join.target_table), Alias::new(alias)),
4753 Expr::cust(join.on_condition.clone()),
4754 );
4755 } else {
4756 // JOIN without alias
4757 stmt.join(
4758 sea_join_type,
4759 Alias::new(&join.target_table),
4760 Expr::cust(join.on_condition.clone()),
4761 );
4762 }
4763 }
4764 }
4765
4766 // Apply WHERE conditions
4767 if let Some(cond) = self.build_where_condition() {
4768 stmt.cond_where(cond);
4769 }
4770
4771 // Apply GROUP BY
4772 for group_field in &self.group_by_fields {
4773 stmt.group_by_col(Alias::new(group_field));
4774 }
4775
4776 // Apply HAVING
4777 for having_cond in &self.having_conditions {
4778 match having_cond {
4779 HavingCondition::AggregateCompare {
4780 func,
4781 field,
4782 operator,
4783 value,
4784 } => {
4785 // Build aggregate function expression
4786 let agg_expr = match func {
4787 AggregateFunc::Avg => {
4788 Func::avg(Expr::col(Alias::new(field)).into_simple_expr())
4789 }
4790 AggregateFunc::Count => {
4791 if field == "*" {
4792 Func::count(Expr::asterisk().into_simple_expr())
4793 } else {
4794 Func::count(Expr::col(Alias::new(field)).into_simple_expr())
4795 }
4796 }
4797 AggregateFunc::Sum => {
4798 Func::sum(Expr::col(Alias::new(field)).into_simple_expr())
4799 }
4800 AggregateFunc::Min => {
4801 Func::min(Expr::col(Alias::new(field)).into_simple_expr())
4802 }
4803 AggregateFunc::Max => {
4804 Func::max(Expr::col(Alias::new(field)).into_simple_expr())
4805 }
4806 };
4807
4808 // Build comparison expression
4809 let having_expr = match operator {
4810 ComparisonOp::Eq => match value {
4811 AggregateValue::Int(v) => agg_expr.eq(*v),
4812 AggregateValue::Float(v) => agg_expr.eq(*v),
4813 },
4814 ComparisonOp::Ne => match value {
4815 AggregateValue::Int(v) => agg_expr.ne(*v),
4816 AggregateValue::Float(v) => agg_expr.ne(*v),
4817 },
4818 ComparisonOp::Gt => match value {
4819 AggregateValue::Int(v) => agg_expr.gt(*v),
4820 AggregateValue::Float(v) => agg_expr.gt(*v),
4821 },
4822 ComparisonOp::Gte => match value {
4823 AggregateValue::Int(v) => agg_expr.gte(*v),
4824 AggregateValue::Float(v) => agg_expr.gte(*v),
4825 },
4826 ComparisonOp::Lt => match value {
4827 AggregateValue::Int(v) => agg_expr.lt(*v),
4828 AggregateValue::Float(v) => agg_expr.lt(*v),
4829 },
4830 ComparisonOp::Lte => match value {
4831 AggregateValue::Int(v) => agg_expr.lte(*v),
4832 AggregateValue::Float(v) => agg_expr.lte(*v),
4833 },
4834 };
4835
4836 stmt.and_having(having_expr);
4837 }
4838 }
4839 }
4840
4841 // Apply ORDER BY
4842 for order_field in &self.order_by_fields {
4843 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
4844 (stripped, true)
4845 } else {
4846 (order_field.as_str(), false)
4847 };
4848
4849 let col_ref = parse_column_reference(field);
4850 let expr = Expr::col(col_ref);
4851 if is_desc {
4852 stmt.order_by_expr(expr, Order::Desc);
4853 } else {
4854 stmt.order_by_expr(expr, Order::Asc);
4855 }
4856 }
4857
4858 // Apply LIMIT/OFFSET
4859 if let Some(limit) = self.limit {
4860 stmt.limit(limit as u64);
4861 }
4862 if let Some(offset) = self.offset {
4863 stmt.offset(offset as u64);
4864 }
4865
4866 stmt.to_owned()
4867 } else {
4868 // SELECT with JOINs for select_related
4869 self.select_related_query()
4870 };
4871
4872 // Add annotations to SELECT clause if any using reinhardt-query API
4873 // Collect annotation SQL strings first to handle lifetime issues
4874 // Note: Use to_sql_expr() to get expression without alias (reinhardt-query adds alias via expr_as)
4875 let annotation_exprs: Vec<_> = self
4876 .annotations
4877 .iter()
4878 .map(|a| (a.value.to_sql_expr(), a.alias.clone()))
4879 .collect();
4880
4881 for (value_sql, alias) in annotation_exprs {
4882 stmt.expr_as(Expr::cust(value_sql), Alias::new(alias));
4883 }
4884
4885 use reinhardt_query::prelude::PostgresQueryBuilder;
4886 let mut select_sql = stmt.to_string(PostgresQueryBuilder);
4887
4888 // Insert LATERAL JOIN clauses after FROM clause
4889 if !self.lateral_joins.is_empty() {
4890 let lateral_sql = self.lateral_joins.to_sql().join(" ");
4891
4892 // Find insertion point: after FROM clause, before WHERE/ORDER BY/LIMIT
4893 // Look for WHERE, ORDER BY, or end of string
4894 let insert_pos = select_sql
4895 .find(" WHERE ")
4896 .or_else(|| select_sql.find(" ORDER BY "))
4897 .or_else(|| select_sql.find(" LIMIT "))
4898 .unwrap_or(select_sql.len());
4899
4900 select_sql.insert_str(insert_pos, &format!(" {}", lateral_sql));
4901 }
4902
4903 // Replace FROM table with FROM subquery if from_subquery_sql is set
4904 if let Some(ref subquery_sql) = self.from_subquery_sql
4905 && let Some(ref alias) = self.from_alias
4906 {
4907 // Pattern: FROM "table_name" AS "alias" or FROM "table_name"
4908 let from_pattern_with_alias = format!("FROM \"{}\" AS \"{}\"", T::table_name(), alias);
4909 let from_pattern_simple = format!("FROM \"{}\"", T::table_name());
4910
4911 let from_replacement = format!("FROM {} AS \"{}\"", subquery_sql, alias);
4912
4913 // Try to replace with alias pattern first, then simple pattern
4914 if select_sql.contains(&from_pattern_with_alias) {
4915 select_sql = select_sql.replace(&from_pattern_with_alias, &from_replacement);
4916 } else if select_sql.contains(&from_pattern_simple) {
4917 select_sql = select_sql.replace(&from_pattern_simple, &from_replacement);
4918 }
4919 }
4920
4921 // Prepend CTE clause if any CTEs are defined
4922 if let Some(cte_sql) = self.ctes.to_sql() {
4923 format!("{} {}", cte_sql, select_sql)
4924 } else {
4925 select_sql
4926 }
4927 }
4928
4929 /// Select specific values from the QuerySet
4930 ///
4931 /// Returns only the specified fields instead of all columns.
4932 /// Useful for optimizing queries when you don't need all model fields.
4933 ///
4934 /// # Examples
4935 ///
4936 /// ```no_run
4937 /// # use reinhardt_db::orm::Model;
4938 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
4939 /// # use serde::{Serialize, Deserialize};
4940 /// # #[derive(Clone, Serialize, Deserialize)]
4941 /// # struct User { id: Option<i64> }
4942 /// # #[derive(Clone)]
4943 /// # struct UserFields;
4944 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4945 /// # fn with_alias(self, _alias: &str) -> Self { self }
4946 /// # }
4947 /// # impl Model for User {
4948 /// # type PrimaryKey = i64;
4949 /// # type Fields = UserFields;
4950 /// # fn table_name() -> &'static str { "users" }
4951 /// # fn new_fields() -> Self::Fields { UserFields }
4952 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
4953 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
4954 /// # }
4955 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
4956 /// // Select only specific fields
4957 /// let users = User::objects()
4958 /// .values(&["id", "username", "email"])
4959 /// .all()
4960 /// .await?;
4961 /// // Generates: SELECT id, username, email FROM users
4962 ///
4963 /// // Combine with filters
4964 /// let active_user_names = User::objects()
4965 /// .filter("is_active", FilterOperator::Eq, FilterValue::Boolean(true))
4966 /// .values(&["username"])
4967 /// .all()
4968 /// .await?;
4969 /// # Ok(())
4970 /// # }
4971 /// ```
4972 pub fn values(mut self, fields: &[&str]) -> Self {
4973 self.selected_fields = Some(fields.iter().map(|s| s.to_string()).collect());
4974 self
4975 }
4976
4977 /// Select specific values as a list
4978 ///
4979 /// Alias for `values()` - returns tuple-like results with specified fields.
4980 /// In Django, this returns tuples instead of dictionaries, but in Rust
4981 /// the behavior is the same as `values()` due to type safety.
4982 ///
4983 /// # Examples
4984 ///
4985 /// ```no_run
4986 /// # use reinhardt_db::orm::Model;
4987 /// # use serde::{Serialize, Deserialize};
4988 /// # #[derive(Clone, Serialize, Deserialize)]
4989 /// # struct User { id: Option<i64> }
4990 /// # #[derive(Clone)]
4991 /// # struct UserFields;
4992 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
4993 /// # fn with_alias(self, _alias: &str) -> Self { self }
4994 /// # }
4995 /// # impl Model for User {
4996 /// # type PrimaryKey = i64;
4997 /// # type Fields = UserFields;
4998 /// # fn table_name() -> &'static str { "users" }
4999 /// # fn new_fields() -> Self::Fields { UserFields }
5000 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5001 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5002 /// # }
5003 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5004 /// // Same as values()
5005 /// let user_data = User::objects()
5006 /// .values_list(&["id", "username"])
5007 /// .all()
5008 /// .await?;
5009 /// # Ok(())
5010 /// # }
5011 /// ```
5012 pub fn values_list(self, fields: &[&str]) -> Self {
5013 self.values(fields)
5014 }
5015
5016 /// Order the QuerySet by specified fields
5017 ///
5018 /// # Examples
5019 ///
5020 /// ```no_run
5021 /// # use reinhardt_db::orm::Model;
5022 /// # use serde::{Serialize, Deserialize};
5023 /// # #[derive(Clone, Serialize, Deserialize)]
5024 /// # struct User { id: Option<i64> }
5025 /// # #[derive(Clone)]
5026 /// # struct UserFields;
5027 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5028 /// # fn with_alias(self, _alias: &str) -> Self { self }
5029 /// # }
5030 /// # impl Model for User {
5031 /// # type PrimaryKey = i64;
5032 /// # type Fields = UserFields;
5033 /// # fn table_name() -> &'static str { "users" }
5034 /// # fn new_fields() -> Self::Fields { UserFields }
5035 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5036 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5037 /// # }
5038 /// # fn example() {
5039 /// // Ascending order
5040 /// User::objects().order_by(&["name"]);
5041 ///
5042 /// // Descending order (prefix with '-')
5043 /// User::objects().order_by(&["-created_at"]);
5044 ///
5045 /// // Multiple fields
5046 /// User::objects().order_by(&["department", "-salary"]);
5047 /// # }
5048 /// ```
5049 pub fn order_by(mut self, fields: &[&str]) -> Self {
5050 self.order_by_fields = fields.iter().map(|s| s.to_string()).collect();
5051 self
5052 }
5053
5054 /// Return only distinct results
5055 pub fn distinct(mut self) -> Self {
5056 self.distinct_enabled = true;
5057 self
5058 }
5059
5060 /// Set LIMIT clause
5061 ///
5062 /// Limits the number of records returned by the query.
5063 /// Corresponds to Django's QuerySet slicing `[:limit]`.
5064 ///
5065 /// # Examples
5066 ///
5067 /// ```no_run
5068 /// # use reinhardt_db::orm::Model;
5069 /// # use serde::{Serialize, Deserialize};
5070 /// # #[derive(Clone, Serialize, Deserialize)]
5071 /// # struct User { id: Option<i64> }
5072 /// # #[derive(Clone)]
5073 /// # struct UserFields;
5074 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5075 /// # fn with_alias(self, _alias: &str) -> Self { self }
5076 /// # }
5077 /// # impl Model for User {
5078 /// # type PrimaryKey = i64;
5079 /// # type Fields = UserFields;
5080 /// # fn table_name() -> &'static str { "users" }
5081 /// # fn new_fields() -> Self::Fields { UserFields }
5082 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5083 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5084 /// # }
5085 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5086 /// let users = User::objects()
5087 /// .limit(10)
5088 /// .all()
5089 /// .await?;
5090 /// # Ok(())
5091 /// # }
5092 /// ```
5093 pub fn limit(mut self, limit: usize) -> Self {
5094 self.limit = Some(limit);
5095 self
5096 }
5097
5098 /// Set OFFSET clause
5099 ///
5100 /// Skips the specified number of records before returning results.
5101 /// Corresponds to Django's QuerySet slicing `[offset:]`.
5102 ///
5103 /// # Examples
5104 ///
5105 /// ```no_run
5106 /// # use reinhardt_db::orm::Model;
5107 /// # use serde::{Serialize, Deserialize};
5108 /// # #[derive(Clone, Serialize, Deserialize)]
5109 /// # struct User { id: Option<i64> }
5110 /// # #[derive(Clone)]
5111 /// # struct UserFields;
5112 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5113 /// # fn with_alias(self, _alias: &str) -> Self { self }
5114 /// # }
5115 /// # impl Model for User {
5116 /// # type PrimaryKey = i64;
5117 /// # type Fields = UserFields;
5118 /// # fn table_name() -> &'static str { "users" }
5119 /// # fn new_fields() -> Self::Fields { UserFields }
5120 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5121 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5122 /// # }
5123 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5124 /// let users = User::objects()
5125 /// .offset(20)
5126 /// .limit(10)
5127 /// .all()
5128 /// .await?;
5129 /// # Ok(())
5130 /// # }
5131 /// ```
5132 pub fn offset(mut self, offset: usize) -> Self {
5133 self.offset = Some(offset);
5134 self
5135 }
5136
5137 /// Paginate results using page number and page size
5138 ///
5139 /// Convenience method that calculates offset automatically.
5140 /// Corresponds to Django REST framework's PageNumberPagination.
5141 ///
5142 /// # Examples
5143 ///
5144 /// ```no_run
5145 /// # use reinhardt_db::orm::Model;
5146 /// # use serde::{Serialize, Deserialize};
5147 /// # #[derive(Clone, Serialize, Deserialize)]
5148 /// # struct User { id: Option<i64> }
5149 /// # #[derive(Clone)]
5150 /// # struct UserFields;
5151 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5152 /// # fn with_alias(self, _alias: &str) -> Self { self }
5153 /// # }
5154 /// # impl Model for User {
5155 /// # type PrimaryKey = i64;
5156 /// # type Fields = UserFields;
5157 /// # fn table_name() -> &'static str { "users" }
5158 /// # fn new_fields() -> Self::Fields { UserFields }
5159 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5160 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5161 /// # }
5162 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5163 /// // Page 3, 10 items per page (offset=20, limit=10)
5164 /// let users = User::objects()
5165 /// .paginate(3, 10)
5166 /// .all()
5167 /// .await?;
5168 /// # Ok(())
5169 /// # }
5170 /// ```
5171 pub fn paginate(self, page: usize, page_size: usize) -> Self {
5172 let offset = page.saturating_sub(1) * page_size;
5173 self.offset(offset).limit(page_size)
5174 }
5175
5176 /// Convert QuerySet to a subquery
5177 ///
5178 /// Returns the QuerySet as a SQL subquery wrapped in parentheses,
5179 /// suitable for use in IN clauses, EXISTS clauses, or as a derived table.
5180 ///
5181 /// # Examples
5182 ///
5183 /// ```no_run
5184 /// # use reinhardt_db::orm::Model;
5185 /// # use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
5186 /// # use serde::{Serialize, Deserialize};
5187 /// # #[derive(Clone, Serialize, Deserialize)]
5188 /// # struct User { id: Option<i64> }
5189 /// # #[derive(Clone)]
5190 /// # struct UserFields;
5191 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5192 /// # fn with_alias(self, _alias: &str) -> Self { self }
5193 /// # }
5194 /// # impl Model for User {
5195 /// # type PrimaryKey = i64;
5196 /// # type Fields = UserFields;
5197 /// # fn table_name() -> &'static str { "users" }
5198 /// # fn new_fields() -> Self::Fields { UserFields }
5199 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5200 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5201 /// # }
5202 /// # #[derive(Clone, Serialize, Deserialize)]
5203 /// # struct Post { id: Option<i64> }
5204 /// # #[derive(Clone)]
5205 /// # struct PostFields;
5206 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
5207 /// # fn with_alias(self, _alias: &str) -> Self { self }
5208 /// # }
5209 /// # impl Model for Post {
5210 /// # type PrimaryKey = i64;
5211 /// # type Fields = PostFields;
5212 /// # fn table_name() -> &'static str { "posts" }
5213 /// # fn new_fields() -> Self::Fields { PostFields }
5214 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5215 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5216 /// # }
5217 /// // Use in IN clause
5218 /// let active_user_ids = User::objects()
5219 /// .filter("is_active", FilterOperator::Eq, FilterValue::Bool(true))
5220 /// .values(&["id"])
5221 /// .as_subquery();
5222 /// // Generates: (SELECT id FROM users WHERE is_active = $1)
5223 ///
5224 /// // Use as derived table
5225 /// let subquery = Post::objects()
5226 /// .filter("published", FilterOperator::Eq, FilterValue::Bool(true))
5227 /// .as_subquery();
5228 /// // Generates: (SELECT * FROM posts WHERE published = $1)
5229 /// ```
5230 pub fn as_subquery(self) -> String {
5231 format!("({})", self.to_sql())
5232 }
5233
5234 /// Defer loading of specific fields
5235 ///
5236 /// Marks specific fields for deferred loading (lazy loading).
5237 /// The specified fields will be excluded from the initial query.
5238 ///
5239 /// # Examples
5240 ///
5241 /// ```no_run
5242 /// # use reinhardt_db::orm::Model;
5243 /// # use serde::{Serialize, Deserialize};
5244 /// # #[derive(Clone, Serialize, Deserialize)]
5245 /// # struct User { id: Option<i64>, username: String, email: String }
5246 /// # #[derive(Clone)]
5247 /// # struct UserFields;
5248 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5249 /// # fn with_alias(self, _alias: &str) -> Self { self }
5250 /// # }
5251 /// # impl Model for User {
5252 /// # type PrimaryKey = i64;
5253 /// # type Fields = UserFields;
5254 /// # fn table_name() -> &'static str { "users" }
5255 /// # fn new_fields() -> Self::Fields { UserFields }
5256 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5257 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5258 /// # }
5259 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5260 /// // Defer large text fields
5261 /// let users = User::objects()
5262 /// .defer(&["bio", "profile_picture"])
5263 /// .all()
5264 /// .await?;
5265 /// // Generates: SELECT id, username, email FROM users (excluding bio, profile_picture)
5266 /// # Ok(())
5267 /// # }
5268 /// ```
5269 pub fn defer(mut self, fields: &[&str]) -> Self {
5270 self.deferred_fields = fields.iter().map(|s| s.to_string()).collect();
5271 self
5272 }
5273
5274 /// Load only specific fields
5275 ///
5276 /// Alias for `values()` - specifies which fields to load immediately.
5277 /// In Django, this is used for deferred loading optimization, but in Rust
5278 /// it behaves the same as `values()`.
5279 ///
5280 /// # Examples
5281 ///
5282 /// ```no_run
5283 /// # use reinhardt_db::orm::Model;
5284 /// # use serde::{Serialize, Deserialize};
5285 /// # #[derive(Clone, Serialize, Deserialize)]
5286 /// # struct User { id: Option<i64>, username: String }
5287 /// # #[derive(Clone)]
5288 /// # struct UserFields;
5289 /// # impl reinhardt_db::orm::model::FieldSelector for UserFields {
5290 /// # fn with_alias(self, _alias: &str) -> Self { self }
5291 /// # }
5292 /// # impl Model for User {
5293 /// # type PrimaryKey = i64;
5294 /// # type Fields = UserFields;
5295 /// # fn table_name() -> &'static str { "users" }
5296 /// # fn new_fields() -> Self::Fields { UserFields }
5297 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5298 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5299 /// # }
5300 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5301 /// // Load only specific fields
5302 /// let users = User::objects()
5303 /// .only(&["id", "username"])
5304 /// .all()
5305 /// .await?;
5306 /// // Generates: SELECT id, username FROM users
5307 /// # Ok(())
5308 /// # }
5309 /// ```
5310 pub fn only(self, fields: &[&str]) -> Self {
5311 self.values(fields)
5312 }
5313
5314 // ==================== PostgreSQL-specific convenience methods ====================
5315
5316 /// Filter by PostgreSQL full-text search
5317 ///
5318 /// This method adds a filter for full-text search using PostgreSQL's `@@` operator.
5319 /// The query is converted using `plainto_tsquery` for simple word matching.
5320 ///
5321 /// # Arguments
5322 ///
5323 /// * `field` - The tsvector field to search
5324 /// * `query` - The search query string
5325 ///
5326 /// # Examples
5327 ///
5328 /// ```no_run
5329 /// # use reinhardt_db::orm::Model;
5330 /// # use serde::{Serialize, Deserialize};
5331 /// # #[derive(Clone, Serialize, Deserialize)]
5332 /// # struct Article { id: Option<i64>, title: String }
5333 /// # #[derive(Clone)]
5334 /// # struct ArticleFields;
5335 /// # impl reinhardt_db::orm::model::FieldSelector for ArticleFields {
5336 /// # fn with_alias(self, _alias: &str) -> Self { self }
5337 /// # }
5338 /// # impl Model for Article {
5339 /// # type PrimaryKey = i64;
5340 /// # type Fields = ArticleFields;
5341 /// # fn table_name() -> &'static str { "articles" }
5342 /// # fn new_fields() -> Self::Fields { ArticleFields }
5343 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5344 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5345 /// # }
5346 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5347 /// // Search articles for "rust programming"
5348 /// let articles = Article::objects()
5349 /// .full_text_search("search_vector", "rust programming")
5350 /// .all()
5351 /// .await?;
5352 /// // Generates: WHERE search_vector @@ plainto_tsquery('english', 'rust programming')
5353 /// # Ok(())
5354 /// # }
5355 /// ```
5356 pub fn full_text_search(self, field: &str, query: &str) -> Self {
5357 self.filter(Filter::new(
5358 field,
5359 FilterOperator::FullTextMatch,
5360 FilterValue::String(query.to_string()),
5361 ))
5362 }
5363
5364 /// Filter by PostgreSQL array overlap
5365 ///
5366 /// Returns rows where the array field has at least one element in common with the given values.
5367 ///
5368 /// # Arguments
5369 ///
5370 /// * `field` - The array field name
5371 /// * `values` - Values to check for overlap
5372 ///
5373 /// # Examples
5374 ///
5375 /// ```no_run
5376 /// # use reinhardt_db::orm::Model;
5377 /// # use serde::{Serialize, Deserialize};
5378 /// # #[derive(Clone, Serialize, Deserialize)]
5379 /// # struct Post { id: Option<i64>, title: String }
5380 /// # #[derive(Clone)]
5381 /// # struct PostFields;
5382 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
5383 /// # fn with_alias(self, _alias: &str) -> Self { self }
5384 /// # }
5385 /// # impl Model for Post {
5386 /// # type PrimaryKey = i64;
5387 /// # type Fields = PostFields;
5388 /// # fn table_name() -> &'static str { "posts" }
5389 /// # fn new_fields() -> Self::Fields { PostFields }
5390 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5391 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5392 /// # }
5393 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5394 /// // Find posts with any of these tags
5395 /// let posts = Post::objects()
5396 /// .filter_array_overlap("tags", &["rust", "programming"])
5397 /// .all()
5398 /// .await?;
5399 /// // Generates: WHERE tags && ARRAY['rust', 'programming']
5400 /// # Ok(())
5401 /// # }
5402 /// ```
5403 pub fn filter_array_overlap(self, field: &str, values: &[&str]) -> Self {
5404 self.filter(Filter::new(
5405 field,
5406 FilterOperator::ArrayOverlap,
5407 FilterValue::Array(values.iter().map(|s| s.to_string()).collect()),
5408 ))
5409 }
5410
5411 /// Filter by PostgreSQL array containment
5412 ///
5413 /// Returns rows where the array field contains all the given values.
5414 ///
5415 /// # Arguments
5416 ///
5417 /// * `field` - The array field name
5418 /// * `values` - Values that must all be present in the array
5419 ///
5420 /// # Examples
5421 ///
5422 /// ```no_run
5423 /// # use reinhardt_db::orm::Model;
5424 /// # use serde::{Serialize, Deserialize};
5425 /// # #[derive(Clone, Serialize, Deserialize)]
5426 /// # struct Post { id: Option<i64>, title: String }
5427 /// # #[derive(Clone)]
5428 /// # struct PostFields;
5429 /// # impl reinhardt_db::orm::model::FieldSelector for PostFields {
5430 /// # fn with_alias(self, _alias: &str) -> Self { self }
5431 /// # }
5432 /// # impl Model for Post {
5433 /// # type PrimaryKey = i64;
5434 /// # type Fields = PostFields;
5435 /// # fn table_name() -> &'static str { "posts" }
5436 /// # fn new_fields() -> Self::Fields { PostFields }
5437 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5438 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5439 /// # }
5440 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5441 /// // Find posts that have both "rust" and "async" tags
5442 /// let posts = Post::objects()
5443 /// .filter_array_contains("tags", &["rust", "async"])
5444 /// .all()
5445 /// .await?;
5446 /// // Generates: WHERE tags @> ARRAY['rust', 'async']
5447 /// # Ok(())
5448 /// # }
5449 /// ```
5450 pub fn filter_array_contains(self, field: &str, values: &[&str]) -> Self {
5451 self.filter(Filter::new(
5452 field,
5453 FilterOperator::ArrayContains,
5454 FilterValue::Array(values.iter().map(|s| s.to_string()).collect()),
5455 ))
5456 }
5457
5458 /// Filter by PostgreSQL JSONB containment
5459 ///
5460 /// Returns rows where the JSONB field contains the given JSON object.
5461 ///
5462 /// # Arguments
5463 ///
5464 /// * `field` - The JSONB field name
5465 /// * `json` - JSON string to check for containment
5466 ///
5467 /// # Examples
5468 ///
5469 /// ```no_run
5470 /// # use reinhardt_db::orm::Model;
5471 /// # use serde::{Serialize, Deserialize};
5472 /// # #[derive(Clone, Serialize, Deserialize)]
5473 /// # struct Product { id: Option<i64>, name: String }
5474 /// # #[derive(Clone)]
5475 /// # struct ProductFields;
5476 /// # impl reinhardt_db::orm::model::FieldSelector for ProductFields {
5477 /// # fn with_alias(self, _alias: &str) -> Self { self }
5478 /// # }
5479 /// # impl Model for Product {
5480 /// # type PrimaryKey = i64;
5481 /// # type Fields = ProductFields;
5482 /// # fn table_name() -> &'static str { "products" }
5483 /// # fn new_fields() -> Self::Fields { ProductFields }
5484 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5485 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5486 /// # }
5487 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5488 /// // Find products with specific metadata
5489 /// let products = Product::objects()
5490 /// .filter_jsonb_contains("metadata", r#"{"active": true}"#)
5491 /// .all()
5492 /// .await?;
5493 /// // Generates: WHERE metadata @> '{"active": true}'::jsonb
5494 /// # Ok(())
5495 /// # }
5496 /// ```
5497 pub fn filter_jsonb_contains(self, field: &str, json: &str) -> Self {
5498 self.filter(Filter::new(
5499 field,
5500 FilterOperator::JsonbContains,
5501 FilterValue::String(json.to_string()),
5502 ))
5503 }
5504
5505 /// Filter by PostgreSQL JSONB key existence
5506 ///
5507 /// Returns rows where the JSONB field contains the given key.
5508 ///
5509 /// # Arguments
5510 ///
5511 /// * `field` - The JSONB field name
5512 /// * `key` - Key to check for existence
5513 ///
5514 /// # Examples
5515 ///
5516 /// ```no_run
5517 /// # use reinhardt_db::orm::Model;
5518 /// # use serde::{Serialize, Deserialize};
5519 /// # #[derive(Clone, Serialize, Deserialize)]
5520 /// # struct Product { id: Option<i64>, name: String }
5521 /// # #[derive(Clone)]
5522 /// # struct ProductFields;
5523 /// # impl reinhardt_db::orm::model::FieldSelector for ProductFields {
5524 /// # fn with_alias(self, _alias: &str) -> Self { self }
5525 /// # }
5526 /// # impl Model for Product {
5527 /// # type PrimaryKey = i64;
5528 /// # type Fields = ProductFields;
5529 /// # fn table_name() -> &'static str { "products" }
5530 /// # fn new_fields() -> Self::Fields { ProductFields }
5531 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5532 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5533 /// # }
5534 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5535 /// // Find products with "sale_price" in metadata
5536 /// let products = Product::objects()
5537 /// .filter_jsonb_key_exists("metadata", "sale_price")
5538 /// .all()
5539 /// .await?;
5540 /// // Generates: WHERE metadata ? 'sale_price'
5541 /// # Ok(())
5542 /// # }
5543 /// ```
5544 pub fn filter_jsonb_key_exists(self, field: &str, key: &str) -> Self {
5545 self.filter(Filter::new(
5546 field,
5547 FilterOperator::JsonbKeyExists,
5548 FilterValue::String(key.to_string()),
5549 ))
5550 }
5551
5552 /// Filter by PostgreSQL range containment
5553 ///
5554 /// Returns rows where the range field contains the given value.
5555 ///
5556 /// # Arguments
5557 ///
5558 /// * `field` - The range field name
5559 /// * `value` - Value to check for containment in the range
5560 ///
5561 /// # Examples
5562 ///
5563 /// ```no_run
5564 /// # use reinhardt_db::orm::Model;
5565 /// # use serde::{Serialize, Deserialize};
5566 /// # #[derive(Clone, Serialize, Deserialize)]
5567 /// # struct Event { id: Option<i64>, name: String }
5568 /// # #[derive(Clone)]
5569 /// # struct EventFields;
5570 /// # impl reinhardt_db::orm::model::FieldSelector for EventFields {
5571 /// # fn with_alias(self, _alias: &str) -> Self { self }
5572 /// # }
5573 /// # impl Model for Event {
5574 /// # type PrimaryKey = i64;
5575 /// # type Fields = EventFields;
5576 /// # fn table_name() -> &'static str { "events" }
5577 /// # fn new_fields() -> Self::Fields { EventFields }
5578 /// # fn primary_key(&self) -> Option<Self::PrimaryKey> { self.id }
5579 /// # fn set_primary_key(&mut self, value: Self::PrimaryKey) { self.id = Some(value); }
5580 /// # }
5581 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
5582 /// // Find events that include a specific date
5583 /// let events = Event::objects()
5584 /// .filter_range_contains("date_range", "2024-06-15")
5585 /// .all()
5586 /// .await?;
5587 /// // Generates: WHERE date_range @> '2024-06-15'
5588 /// # Ok(())
5589 /// # }
5590 /// ```
5591 pub fn filter_range_contains(self, field: &str, value: &str) -> Self {
5592 self.filter(Filter::new(
5593 field,
5594 FilterOperator::RangeContains,
5595 FilterValue::String(value.to_string()),
5596 ))
5597 }
5598}
5599
5600impl<T> Default for QuerySet<T>
5601where
5602 T: super::Model,
5603{
5604 fn default() -> Self {
5605 Self::new()
5606 }
5607}
5608
5609// Convenience conversions for FilterValue
5610impl FilterValue {
5611 /// Create a String variant from any value that can be converted to String
5612 ///
5613 /// Accepts any type that implements `ToString`, including:
5614 /// - String, &str
5615 /// - Uuid (via Display)
5616 /// - Numeric types (i64, u64, etc. via Display)
5617 pub fn string(value: impl ToString) -> Self {
5618 Self::String(value.to_string())
5619 }
5620}
5621
5622// ============================================================================
5623// Helper Functions
5624// ============================================================================
5625
5626/// Quote a SQL identifier to prevent injection via field names.
5627/// Uses PostgreSQL double-quote escaping (also valid for SQLite).
5628/// Handles dot-separated qualified names (e.g., "table.column" becomes "table"."column").
5629pub(crate) fn quote_identifier(field: &str) -> String {
5630 if field.contains('\0') {
5631 panic!("SQL identifier must not contain null bytes");
5632 }
5633
5634 fn quote_single(name: &str) -> String {
5635 format!("\"{}\"", name.replace('"', "\"\""))
5636 }
5637
5638 if field.contains('.') {
5639 field
5640 .split('.')
5641 .map(quote_single)
5642 .collect::<Vec<_>>()
5643 .join(".")
5644 } else {
5645 quote_single(field)
5646 }
5647}
5648
5649/// Parse field reference into reinhardt-query column expression
5650///
5651/// Handles both qualified (`table.column`) and unqualified (`column`) references.
5652///
5653/// # Examples
5654///
5655/// - `"id"` → `ColumnRef::Column("id")`
5656/// - `"users.id"` → `ColumnRef::Column("users.id")` (qualified name as-is)
5657///
5658/// Note: For reinhardt-query v1.0.0-rc.29+, we use the full qualified name as a column identifier.
5659/// This works for most databases that support qualified column references.
5660///
5661/// This function also detects raw SQL expressions (containing parentheses, like `COUNT(*)`,
5662/// `AVG(price)`) and returns them wrapped in `Expr::cust()` instead of as column references.
5663fn parse_column_reference(field: &str) -> reinhardt_query::prelude::ColumnRef {
5664 use reinhardt_query::prelude::ColumnRef;
5665
5666 // Detect raw SQL expressions by checking for parentheses
5667 // Examples: COUNT(*), AVG(price), SUM(amount), MAX(value)
5668 if field.contains('(') && field.contains(')') {
5669 // Use column reference with raw expression name
5670 ColumnRef::column(Alias::new(field))
5671 } else if field.contains('.') {
5672 // Qualified column reference (table.column format)
5673 let parts: Vec<&str> = field.split('.').collect();
5674 match parts.as_slice() {
5675 [table, column] => {
5676 // Produces: "table"."column" instead of "table.column"
5677 ColumnRef::table_column(Alias::new(*table), Alias::new(*column))
5678 }
5679 [schema, table, column] => {
5680 // Produces: "schema"."table"."column"
5681 ColumnRef::schema_table_column(
5682 Alias::new(*schema),
5683 Alias::new(*table),
5684 Alias::new(*column),
5685 )
5686 }
5687 _ => {
5688 // Fallback for unexpected formats (4+ parts)
5689 ColumnRef::column(Alias::new(field))
5690 }
5691 }
5692 } else {
5693 // Simple column reference
5694 ColumnRef::column(Alias::new(field))
5695 }
5696}
5697
5698#[cfg(test)]
5699mod tests {
5700 use crate::orm::query::UpdateValue;
5701 use crate::orm::{FilterOperator, FilterValue, Model, QuerySet, query::Filter};
5702 use rstest::rstest;
5703 use serde::{Deserialize, Serialize};
5704 use std::collections::HashMap;
5705
5706 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5707 struct TestUser {
5708 id: Option<i64>,
5709 username: String,
5710 email: String,
5711 }
5712
5713 impl TestUser {
5714 // Allow dead_code: test helper constructor for query tests
5715 #[allow(dead_code)]
5716 fn new(username: String, email: String) -> Self {
5717 Self {
5718 id: None,
5719 username,
5720 email,
5721 }
5722 }
5723 }
5724
5725 #[derive(Debug, Clone)]
5726 struct TestUserFields;
5727
5728 impl crate::orm::model::FieldSelector for TestUserFields {
5729 fn with_alias(self, _alias: &str) -> Self {
5730 self
5731 }
5732 }
5733
5734 impl Model for TestUser {
5735 type PrimaryKey = i64;
5736 type Fields = TestUserFields;
5737
5738 fn table_name() -> &'static str {
5739 "test_users"
5740 }
5741
5742 fn primary_key(&self) -> Option<Self::PrimaryKey> {
5743 self.id
5744 }
5745
5746 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
5747 self.id = Some(value);
5748 }
5749
5750 fn primary_key_field() -> &'static str {
5751 "id"
5752 }
5753
5754 fn new_fields() -> Self::Fields {
5755 TestUserFields
5756 }
5757 }
5758
5759 #[tokio::test]
5760 async fn test_queryset_create_with_manager() {
5761 // Test QuerySet::create() with explicit manager
5762 let manager = std::sync::Arc::new(TestUser::objects());
5763 let queryset = QuerySet::with_manager(manager);
5764
5765 let user = TestUser {
5766 id: None,
5767 username: "testuser".to_string(),
5768 email: "test@example.com".to_string(),
5769 };
5770
5771 // Note: This will fail without a real database connection
5772 // In actual integration tests, we would set up a test database
5773 let result = queryset.create(user).await;
5774
5775 // In unit tests, we expect this to fail due to no database
5776 // In integration tests with TestContainers, this would succeed
5777 assert!(result.is_err() || result.is_ok());
5778 }
5779
5780 #[tokio::test]
5781 async fn test_queryset_create_without_manager() {
5782 // Test QuerySet::create() fallback without manager
5783 let queryset = QuerySet::<TestUser>::new();
5784
5785 let user = TestUser {
5786 id: None,
5787 username: "fallback_user".to_string(),
5788 email: "fallback@example.com".to_string(),
5789 };
5790
5791 // Note: This will fail without a real database connection
5792 let result = queryset.create(user).await;
5793
5794 // In unit tests, we expect this to fail due to no database
5795 assert!(result.is_err() || result.is_ok());
5796 }
5797
5798 #[test]
5799 fn test_queryset_with_manager() {
5800 let manager = std::sync::Arc::new(TestUser::objects());
5801 let queryset = QuerySet::with_manager(manager.clone());
5802
5803 // Verify manager is set
5804 assert!(queryset.manager.is_some());
5805 }
5806
5807 #[test]
5808 fn test_queryset_filter_preserves_manager() {
5809 let manager = std::sync::Arc::new(TestUser::objects());
5810 let queryset = QuerySet::with_manager(manager);
5811
5812 let filter = Filter::new(
5813 "username".to_string(),
5814 FilterOperator::Eq,
5815 FilterValue::String("alice".to_string()),
5816 );
5817
5818 let filtered = queryset.filter(filter);
5819
5820 // Verify manager is preserved after filter
5821 assert!(filtered.manager.is_some());
5822 }
5823
5824 #[test]
5825 fn test_queryset_select_related_preserves_manager() {
5826 let manager = std::sync::Arc::new(TestUser::objects());
5827 let queryset = QuerySet::with_manager(manager);
5828
5829 let selected = queryset.select_related(&["profile", "posts"]);
5830
5831 // Verify manager is preserved after select_related
5832 assert!(selected.manager.is_some());
5833 assert_eq!(selected.select_related_fields, vec!["profile", "posts"]);
5834 }
5835
5836 #[test]
5837 fn test_queryset_prefetch_related_preserves_manager() {
5838 let manager = std::sync::Arc::new(TestUser::objects());
5839 let queryset = QuerySet::with_manager(manager);
5840
5841 let prefetched = queryset.prefetch_related(&["comments", "likes"]);
5842
5843 // Verify manager is preserved after prefetch_related
5844 assert!(prefetched.manager.is_some());
5845 assert_eq!(
5846 prefetched.prefetch_related_fields,
5847 vec!["comments", "likes"]
5848 );
5849 }
5850
5851 #[tokio::test]
5852 async fn test_get_composite_validation_error() {
5853 use std::collections::HashMap;
5854
5855 let queryset = QuerySet::<TestUser>::new();
5856 let pk_values = HashMap::new(); // Empty HashMap - should fail validation
5857
5858 let result = queryset.get_composite(&pk_values).await;
5859
5860 // Expect error because TestUser doesn't have a composite primary key
5861 assert!(result.is_err());
5862 let err = result.unwrap_err();
5863 assert!(err.to_string().contains("composite primary key"));
5864 }
5865
5866 // SQL Generation Tests
5867
5868 #[test]
5869 fn test_update_sql_single_field_single_filter() {
5870 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
5871 "id".to_string(),
5872 FilterOperator::Eq,
5873 FilterValue::Integer(1),
5874 ));
5875
5876 let mut updates = HashMap::new();
5877 updates.insert(
5878 "username".to_string(),
5879 UpdateValue::String("alice".to_string()),
5880 );
5881 let (sql, params) = queryset.update_sql(&updates);
5882
5883 assert_eq!(
5884 sql,
5885 "UPDATE \"test_users\" SET \"username\" = $1 WHERE \"id\" = $2"
5886 );
5887 assert_eq!(params, vec!["alice", "1"]);
5888 }
5889
5890 #[test]
5891 fn test_update_sql_multiple_fields_multiple_filters() {
5892 let queryset = QuerySet::<TestUser>::new()
5893 .filter(Filter::new(
5894 "id".to_string(),
5895 FilterOperator::Gt,
5896 FilterValue::Integer(10),
5897 ))
5898 .filter(Filter::new(
5899 "email".to_string(),
5900 FilterOperator::Contains,
5901 FilterValue::String("example.com".to_string()),
5902 ));
5903
5904 let mut updates = HashMap::new();
5905 updates.insert(
5906 "username".to_string(),
5907 UpdateValue::String("bob".to_string()),
5908 );
5909 updates.insert(
5910 "email".to_string(),
5911 UpdateValue::String("bob@test.com".to_string()),
5912 );
5913 let (sql, params) = queryset.update_sql(&updates);
5914
5915 // HashMap iteration order is not guaranteed, so we check both possible orderings
5916 let valid_sql_1 = "UPDATE \"test_users\" SET \"username\" = $1, \"email\" = $2 WHERE (\"id\" > $3 AND \"email\" LIKE $4)";
5917 let valid_sql_2 = "UPDATE \"test_users\" SET \"email\" = $1, \"username\" = $2 WHERE (\"id\" > $3 AND \"email\" LIKE $4)";
5918 assert!(
5919 sql == valid_sql_1 || sql == valid_sql_2,
5920 "Generated SQL '{}' does not match either expected pattern",
5921 sql
5922 );
5923
5924 // Check that all expected values are present (order may vary for SET clause)
5925 assert!(
5926 params.contains(&"bob".to_string()) || params.contains(&"bob@test.com".to_string())
5927 );
5928 assert_eq!(params[2], "10");
5929 assert_eq!(params[3], "%example.com%");
5930 }
5931
5932 #[test]
5933 fn test_delete_sql_single_filter() {
5934 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
5935 "id".to_string(),
5936 FilterOperator::Eq,
5937 FilterValue::Integer(1),
5938 ));
5939
5940 let (sql, params) = queryset.delete_sql();
5941
5942 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"id\" = $1");
5943 assert_eq!(params, vec!["1"]);
5944 }
5945
5946 #[test]
5947 fn test_delete_sql_multiple_filters() {
5948 let queryset = QuerySet::<TestUser>::new()
5949 .filter(Filter::new(
5950 "username".to_string(),
5951 FilterOperator::Eq,
5952 FilterValue::String("alice".to_string()),
5953 ))
5954 .filter(Filter::new(
5955 "email".to_string(),
5956 FilterOperator::StartsWith,
5957 FilterValue::String("alice@".to_string()),
5958 ));
5959
5960 let (sql, params) = queryset.delete_sql();
5961
5962 assert_eq!(
5963 sql,
5964 "DELETE FROM \"test_users\" WHERE (\"username\" = $1 AND \"email\" LIKE $2)"
5965 );
5966 assert_eq!(params, vec!["alice", "alice@%"]);
5967 }
5968
5969 #[test]
5970 #[should_panic(
5971 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
5972 )]
5973 fn test_delete_sql_without_filters_panics() {
5974 let queryset = QuerySet::<TestUser>::new();
5975 let _ = queryset.delete_sql();
5976 }
5977
5978 #[test]
5979 fn test_filter_operators() {
5980 let queryset = QuerySet::<TestUser>::new()
5981 .filter(Filter::new(
5982 "id".to_string(),
5983 FilterOperator::Gte,
5984 FilterValue::Integer(5),
5985 ))
5986 .filter(Filter::new(
5987 "username".to_string(),
5988 FilterOperator::Ne,
5989 FilterValue::String("admin".to_string()),
5990 ));
5991
5992 let (sql, params) = queryset.delete_sql();
5993
5994 assert_eq!(
5995 sql,
5996 "DELETE FROM \"test_users\" WHERE (\"id\" >= $1 AND \"username\" <> $2)"
5997 );
5998 assert_eq!(params, vec!["5", "admin"]);
5999 }
6000
6001 #[test]
6002 fn test_null_value_filter() {
6003 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6004 "email".to_string(),
6005 FilterOperator::Eq,
6006 FilterValue::Null,
6007 ));
6008
6009 let (sql, params) = queryset.delete_sql();
6010
6011 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"email\" IS NULL");
6012 assert_eq!(params, Vec::<String>::new());
6013 }
6014
6015 #[test]
6016 fn test_not_null_value_filter() {
6017 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6018 "email".to_string(),
6019 FilterOperator::Ne,
6020 FilterValue::Null,
6021 ));
6022
6023 let (sql, params) = queryset.delete_sql();
6024
6025 assert_eq!(
6026 sql,
6027 "DELETE FROM \"test_users\" WHERE \"email\" IS NOT NULL"
6028 );
6029 assert_eq!(params, Vec::<String>::new());
6030 }
6031
6032 // Query Optimization Tests
6033
6034 #[test]
6035 fn test_select_related_query_generation() {
6036 // Test that select_related_query() generates SelectStatement correctly
6037 let queryset = QuerySet::<TestUser>::new().select_related(&["profile", "department"]);
6038
6039 let stmt = queryset.select_related_query();
6040
6041 // Convert to SQL to verify structure
6042 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryStatementBuilder};
6043 let sql = stmt.build(PostgresQueryBuilder).0;
6044
6045 assert!(sql.contains("SELECT"));
6046 assert!(sql.contains("test_users"));
6047 assert!(sql.contains("LEFT JOIN"));
6048 }
6049
6050 #[test]
6051 fn test_prefetch_related_queries_generation() {
6052 // Test that prefetch_related_queries() generates correct queries
6053 let queryset = QuerySet::<TestUser>::new().prefetch_related(&["posts", "comments"]);
6054 let pk_values = vec![1, 2, 3];
6055
6056 let queries = queryset.prefetch_related_queries(&pk_values);
6057
6058 // Should generate 2 queries (one for each prefetch field)
6059 assert_eq!(queries.len(), 2);
6060
6061 // Each query should be a (field_name, SelectStatement) tuple
6062 assert_eq!(queries[0].0, "posts");
6063 assert_eq!(queries[1].0, "comments");
6064 }
6065
6066 #[test]
6067 fn test_prefetch_related_queries_empty_pk_values() {
6068 let queryset = QuerySet::<TestUser>::new().prefetch_related(&["posts", "comments"]);
6069 let pk_values = vec![];
6070
6071 let queries = queryset.prefetch_related_queries(&pk_values);
6072
6073 // Should return empty vector when no PK values provided
6074 assert_eq!(queries.len(), 0);
6075 }
6076
6077 #[test]
6078 fn test_select_related_and_prefetch_together() {
6079 // Test that both can be used together
6080 let queryset = QuerySet::<TestUser>::new()
6081 .select_related(&["profile"])
6082 .prefetch_related(&["posts", "comments"]);
6083
6084 // Check select_related generates query
6085 let select_stmt = queryset.select_related_query();
6086 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryStatementBuilder};
6087 let select_sql = select_stmt.build(PostgresQueryBuilder).0;
6088 assert!(select_sql.contains("LEFT JOIN"));
6089
6090 // Check prefetch_related generates queries
6091 let pk_values = vec![1, 2, 3];
6092 let prefetch_queries = queryset.prefetch_related_queries(&pk_values);
6093 assert_eq!(prefetch_queries.len(), 2);
6094 }
6095
6096 // SmallVec Optimization Tests
6097
6098 #[test]
6099 fn test_smallvec_stack_allocation_within_capacity() {
6100 // Test with exactly 10 filters (at capacity)
6101 let mut queryset = QuerySet::<TestUser>::new();
6102
6103 for i in 0..10 {
6104 queryset = queryset.filter(Filter::new(
6105 format!("field{}", i),
6106 FilterOperator::Eq,
6107 FilterValue::Integer(i as i64),
6108 ));
6109 }
6110
6111 // Verify all filters are stored
6112 assert_eq!(queryset.filters.len(), 10);
6113
6114 // Generate SQL to ensure filters work correctly
6115 let (sql, params) = queryset.delete_sql();
6116 assert!(sql.contains("WHERE"));
6117 assert_eq!(params.len(), 10);
6118 }
6119
6120 #[test]
6121 fn test_smallvec_heap_fallback_over_capacity() {
6122 // Test with 15 filters (5 over capacity, should trigger heap allocation)
6123 let mut queryset = QuerySet::<TestUser>::new();
6124
6125 for i in 0..15 {
6126 queryset = queryset.filter(Filter::new(
6127 format!("field{}", i),
6128 FilterOperator::Eq,
6129 FilterValue::Integer(i as i64),
6130 ));
6131 }
6132
6133 // Verify all filters are stored (SmallVec automatically spills to heap)
6134 assert_eq!(queryset.filters.len(), 15);
6135
6136 // Generate SQL to ensure filters work correctly even after heap fallback
6137 let (sql, params) = queryset.delete_sql();
6138 assert!(sql.contains("WHERE"));
6139 assert_eq!(params.len(), 15);
6140 }
6141
6142 #[test]
6143 fn test_smallvec_typical_use_case_1_5_filters() {
6144 // Test typical use case: 1-5 filters (well within stack capacity)
6145 let queryset = QuerySet::<TestUser>::new()
6146 .filter(Filter::new(
6147 "username".to_string(),
6148 FilterOperator::StartsWith,
6149 FilterValue::String("admin".to_string()),
6150 ))
6151 .filter(Filter::new(
6152 "email".to_string(),
6153 FilterOperator::Contains,
6154 FilterValue::String("example.com".to_string()),
6155 ))
6156 .filter(Filter::new(
6157 "id".to_string(),
6158 FilterOperator::Gt,
6159 FilterValue::Integer(100),
6160 ));
6161
6162 // Verify filters stored correctly
6163 assert_eq!(queryset.filters.len(), 3);
6164
6165 // Generate SQL
6166 let (sql, params) = queryset.delete_sql();
6167 assert!(sql.contains("WHERE"));
6168 assert!(sql.contains("\"username\" LIKE"));
6169 assert!(sql.contains("\"email\" LIKE"));
6170 assert!(sql.contains("\"id\" >"));
6171 assert_eq!(params.len(), 3);
6172 }
6173
6174 #[test]
6175 fn test_smallvec_empty_initialization() {
6176 // Test that empty SmallVec is initialized correctly
6177 let queryset = QuerySet::<TestUser>::new();
6178
6179 assert_eq!(queryset.filters.len(), 0);
6180 assert!(queryset.filters.is_empty());
6181
6182 // Generate SQL with no filters should not include WHERE clause
6183 let (where_clause, params) = queryset.build_where_clause();
6184 assert!(where_clause.is_empty());
6185 assert!(params.is_empty());
6186 }
6187
6188 #[test]
6189 fn test_smallvec_single_filter() {
6190 // Test single filter (minimal usage)
6191 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6192 "id".to_string(),
6193 FilterOperator::Eq,
6194 FilterValue::Integer(1),
6195 ));
6196
6197 assert_eq!(queryset.filters.len(), 1);
6198
6199 let (sql, params) = queryset.delete_sql();
6200 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"id\" = $1");
6201 assert_eq!(params, vec!["1"]);
6202 }
6203
6204 #[rstest]
6205 #[case("username", r#""username""#)]
6206 #[case("user_id", r#""user_id""#)]
6207 #[case(r#"a"b"#, r#""a""b""#)]
6208 #[case("field; DROP TABLE users", r#""field; DROP TABLE users""#)]
6209 #[case("", r#""""#)]
6210 #[case("authors.id", r#""authors"."id""#)]
6211 #[case("schema.table.column", r#""schema"."table"."column""#)]
6212 fn test_quote_identifier(#[case] input: &str, #[case] expected: &str) {
6213 // Arrange
6214 // input and expected provided by rstest cases
6215
6216 // Act
6217 let result = super::quote_identifier(input);
6218
6219 // Assert
6220 assert_eq!(result, expected);
6221 }
6222
6223 #[rstest]
6224 fn test_outerref_filter_uses_safe_quoting() {
6225 // Arrange
6226 use crate::orm::expressions::OuterRef;
6227 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6228 "author_id".to_string(),
6229 FilterOperator::Eq,
6230 FilterValue::OuterRef(OuterRef::new("id")),
6231 ));
6232
6233 // Act
6234 let sql = queryset.to_sql();
6235
6236 // Assert
6237 assert_eq!(
6238 sql,
6239 r#"SELECT * FROM "test_users" WHERE "author_id" = "id""#
6240 );
6241 }
6242
6243 #[rstest]
6244 fn test_array_contains_filter_quotes_field() {
6245 // Arrange
6246 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6247 "tags".to_string(),
6248 FilterOperator::ArrayContains,
6249 FilterValue::Array(vec!["rust".to_string(), "web".to_string()]),
6250 ));
6251
6252 // Act
6253 let sql = queryset.to_sql();
6254
6255 // Assert
6256 assert_eq!(
6257 sql,
6258 r#"SELECT * FROM "test_users" WHERE "tags" @> ARRAY['rust', 'web']"#
6259 );
6260 }
6261
6262 #[rstest]
6263 fn test_outerref_dot_separated_renders_qualified_column() {
6264 // Arrange
6265 use crate::orm::expressions::OuterRef;
6266 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6267 "author_id".to_string(),
6268 FilterOperator::Eq,
6269 FilterValue::OuterRef(OuterRef::new("authors.id")),
6270 ));
6271
6272 // Act
6273 let sql = queryset.to_sql();
6274
6275 // Assert
6276 assert_eq!(
6277 sql,
6278 r#"SELECT * FROM "test_users" WHERE "author_id" = "authors"."id""#
6279 );
6280 }
6281
6282 #[rstest]
6283 fn test_injection_attempt_in_field_name_is_quoted() {
6284 // Arrange
6285 // Attempt SQL injection via field name with double quote
6286 let malicious_field = r#"id" OR 1=1 --"#.to_string();
6287
6288 // Act
6289 let quoted = super::quote_identifier(&malicious_field);
6290
6291 // Assert
6292 // The double quote inside is escaped, preventing injection
6293 assert_eq!(quoted, r#""id"" OR 1=1 --""#);
6294 // Verify the quote is not broken out of
6295 assert!(quoted.starts_with('"'));
6296 assert!(quoted.ends_with('"'));
6297 }
6298
6299 #[rstest]
6300 #[should_panic(expected = "SQL identifier must not contain null bytes")]
6301 fn test_quote_identifier_rejects_null_bytes() {
6302 // Arrange
6303 let field_with_null = "field\0name";
6304
6305 // Act
6306 super::quote_identifier(field_with_null);
6307
6308 // Assert - should panic before reaching here
6309 }
6310
6311 #[rstest]
6312 #[case(FilterOperator::Ne, "<>")]
6313 #[case(FilterOperator::Gt, ">")]
6314 #[case(FilterOperator::Gte, ">=")]
6315 #[case(FilterOperator::Lt, "<")]
6316 #[case(FilterOperator::Lte, "<=")]
6317 fn test_outerref_comparison_operators(#[case] op: FilterOperator, #[case] sql_op: &str) {
6318 // Arrange
6319 use crate::orm::expressions::OuterRef;
6320 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6321 "author_id".to_string(),
6322 op,
6323 FilterValue::OuterRef(OuterRef::new("id")),
6324 ));
6325
6326 // Act
6327 let sql = queryset.to_sql();
6328
6329 // Assert
6330 let expected = format!(
6331 r#"SELECT * FROM "test_users" WHERE "author_id" {} "id""#,
6332 sql_op
6333 );
6334 assert_eq!(sql, expected);
6335 }
6336
6337 #[rstest]
6338 fn test_array_contained_by_filter_quotes_field() {
6339 // Arrange
6340 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6341 "tags".to_string(),
6342 FilterOperator::ArrayContainedBy,
6343 FilterValue::Array(vec!["rust".to_string()]),
6344 ));
6345
6346 // Act
6347 let sql = queryset.to_sql();
6348
6349 // Assert
6350 assert_eq!(
6351 sql,
6352 r#"SELECT * FROM "test_users" WHERE "tags" <@ ARRAY['rust']"#
6353 );
6354 }
6355
6356 #[rstest]
6357 fn test_array_overlap_filter_quotes_field() {
6358 // Arrange
6359 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6360 "tags".to_string(),
6361 FilterOperator::ArrayOverlap,
6362 FilterValue::Array(vec!["rust".to_string()]),
6363 ));
6364
6365 // Act
6366 let sql = queryset.to_sql();
6367
6368 // Assert
6369 assert_eq!(
6370 sql,
6371 r#"SELECT * FROM "test_users" WHERE "tags" && ARRAY['rust']"#
6372 );
6373 }
6374
6375 #[rstest]
6376 fn test_full_text_match_filter_quotes_field() {
6377 // Arrange
6378 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6379 "content".to_string(),
6380 FilterOperator::FullTextMatch,
6381 FilterValue::String("search term".to_string()),
6382 ));
6383
6384 // Act
6385 let sql = queryset.to_sql();
6386
6387 // Assert
6388 assert_eq!(
6389 sql,
6390 r#"SELECT * FROM "test_users" WHERE "content" @@ plainto_tsquery('english', 'search term')"#
6391 );
6392 }
6393
6394 #[rstest]
6395 fn test_jsonb_contains_filter_quotes_field() {
6396 // Arrange
6397 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6398 "metadata".to_string(),
6399 FilterOperator::JsonbContains,
6400 FilterValue::String(r#"{"key": "value"}"#.to_string()),
6401 ));
6402
6403 // Act
6404 let sql = queryset.to_sql();
6405
6406 // Assert
6407 assert_eq!(
6408 sql,
6409 r#"SELECT * FROM "test_users" WHERE "metadata" @> '{"key": "value"}'::jsonb"#
6410 );
6411 }
6412
6413 #[rstest]
6414 fn test_jsonb_contained_by_filter_quotes_field() {
6415 // Arrange
6416 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6417 "metadata".to_string(),
6418 FilterOperator::JsonbContainedBy,
6419 FilterValue::String(r#"{"key": "value"}"#.to_string()),
6420 ));
6421
6422 // Act
6423 let sql = queryset.to_sql();
6424
6425 // Assert
6426 assert_eq!(
6427 sql,
6428 r#"SELECT * FROM "test_users" WHERE "metadata" <@ '{"key": "value"}'::jsonb"#
6429 );
6430 }
6431
6432 #[rstest]
6433 fn test_jsonb_key_exists_filter_quotes_field() {
6434 // Arrange
6435 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6436 "metadata".to_string(),
6437 FilterOperator::JsonbKeyExists,
6438 FilterValue::String("key".to_string()),
6439 ));
6440
6441 // Act
6442 let sql = queryset.to_sql();
6443
6444 // Assert
6445 assert_eq!(
6446 sql,
6447 r#"SELECT * FROM "test_users" WHERE "metadata" ? 'key'"#
6448 );
6449 }
6450
6451 #[rstest]
6452 fn test_jsonb_any_key_exists_filter_quotes_field() {
6453 // Arrange
6454 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6455 "metadata".to_string(),
6456 FilterOperator::JsonbAnyKeyExists,
6457 FilterValue::Array(vec!["key1".to_string(), "key2".to_string()]),
6458 ));
6459
6460 // Act
6461 let sql = queryset.to_sql();
6462
6463 // Assert
6464 assert_eq!(
6465 sql,
6466 r#"SELECT * FROM "test_users" WHERE "metadata" ?| array['key1', 'key2']"#
6467 );
6468 }
6469
6470 #[rstest]
6471 fn test_jsonb_all_keys_exist_filter_quotes_field() {
6472 // Arrange
6473 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6474 "metadata".to_string(),
6475 FilterOperator::JsonbAllKeysExist,
6476 FilterValue::Array(vec!["key1".to_string(), "key2".to_string()]),
6477 ));
6478
6479 // Act
6480 let sql = queryset.to_sql();
6481
6482 // Assert
6483 assert_eq!(
6484 sql,
6485 r#"SELECT * FROM "test_users" WHERE "metadata" ?& array['key1', 'key2']"#
6486 );
6487 }
6488
6489 #[rstest]
6490 fn test_jsonb_path_exists_filter_quotes_field() {
6491 // Arrange
6492 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6493 "metadata".to_string(),
6494 FilterOperator::JsonbPathExists,
6495 FilterValue::String("$.key".to_string()),
6496 ));
6497
6498 // Act
6499 let sql = queryset.to_sql();
6500
6501 // Assert
6502 assert_eq!(
6503 sql,
6504 r#"SELECT * FROM "test_users" WHERE "metadata" @'$.key' "#
6505 );
6506 }
6507
6508 #[rstest]
6509 fn test_range_contains_filter_quotes_field() {
6510 // Arrange
6511 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6512 "age_range".to_string(),
6513 FilterOperator::RangeContains,
6514 FilterValue::String("25".to_string()),
6515 ));
6516
6517 // Act
6518 let sql = queryset.to_sql();
6519
6520 // Assert
6521 assert_eq!(
6522 sql,
6523 r#"SELECT * FROM "test_users" WHERE "age_range" @> '''25'''"#
6524 );
6525 }
6526
6527 #[rstest]
6528 fn test_range_contained_by_filter_quotes_field() {
6529 // Arrange
6530 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6531 "age_range".to_string(),
6532 FilterOperator::RangeContainedBy,
6533 FilterValue::String("[20, 30]".to_string()),
6534 ));
6535
6536 // Act
6537 let sql = queryset.to_sql();
6538
6539 // Assert
6540 assert_eq!(
6541 sql,
6542 r#"SELECT * FROM "test_users" WHERE "age_range" <@ '[20, 30]'"#
6543 );
6544 }
6545
6546 #[rstest]
6547 fn test_range_overlaps_filter_quotes_field() {
6548 // Arrange
6549 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
6550 "age_range".to_string(),
6551 FilterOperator::RangeOverlaps,
6552 FilterValue::String("[20, 30]".to_string()),
6553 ));
6554
6555 // Act
6556 let sql = queryset.to_sql();
6557
6558 // Assert
6559 assert_eq!(
6560 sql,
6561 r#"SELECT * FROM "test_users" WHERE "age_range" && '[20, 30]'"#
6562 );
6563 }
6564}