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