1use super::FieldSelector;
7use crate::backends::types::QueryValue;
8use crate::naming::to_snake_case;
9use crate::orm::query_fields::GroupByFields;
10use crate::orm::query_fields::aggregate::{AggregateExpr, ComparisonExpr};
11use crate::orm::query_fields::comparison::FieldComparison;
12use crate::orm::query_fields::compiler::QueryFieldCompiler;
13use reinhardt_query::prelude::{
14 Alias, BinOper, ColumnRef, Condition, Expr, ExprTrait, Func, JoinType as SeaJoinType,
15 MySqlQueryBuilder, Order, PostgresQueryBuilder, Query, QueryBuilder, QueryStatementBuilder,
16 SelectStatement, SimpleExpr, SqliteQueryBuilder, UpdateStatement,
17};
18use reinhardt_query::types::PgBinOper;
19use reinhardt_query::value::Value;
20use serde::{Deserialize, Serialize};
21use smallvec::SmallVec;
22use std::collections::HashMap;
23use std::time::Instant;
24use uuid::Uuid;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum FilterOperator {
30 Eq,
32 IExact,
34 Ne,
36 Gt,
38 Gte,
40 Lt,
42 Lte,
44 In,
46 NotIn,
48 Contains,
50 IContains,
52 StartsWith,
54 IStartsWith,
56 EndsWith,
58 IEndsWith,
60 Regex,
62 IRegex,
64 Range,
66 ArrayContains,
69 ArrayContainedBy,
71 ArrayOverlap,
73 FullTextMatch,
76 JsonbContains,
79 JsonbContainedBy,
81 JsonbKeyExists,
83 JsonbAnyKeyExists,
85 JsonbAllKeysExist,
87 JsonbPathExists,
89 IsNull,
92 IsNotNull,
94 RangeContains,
96 RangeContainedBy,
98 RangeOverlaps,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub enum FilterValue {
105 String(String),
107 Timestamp(chrono::DateTime<chrono::Utc>),
109 Date(chrono::NaiveDate),
111 Time(chrono::NaiveTime),
113 NaiveDateTime(chrono::NaiveDateTime),
115 Decimal(rust_decimal::Decimal),
117 Uuid(uuid::Uuid),
119 Integer(i64),
121 Int(i64),
123 Float(f64),
125 Boolean(bool),
127 Bool(bool),
129 Null,
131 Array(Vec<String>),
133 List(Vec<FilterValue>),
135 Range(Box<FilterValue>, Box<FilterValue>),
137 FieldRef(super::expressions::F),
139 Expression(super::annotation::Expression),
141 OuterRef(super::expressions::OuterRef),
143}
144
145impl FilterValue {
146 fn is_empty_membership_collection(&self) -> bool {
148 match self {
149 Self::List(values) => values.is_empty(),
150 Self::Array(values) => values.is_empty(),
151 Self::String(s) => parse_membership_string(s).is_empty(),
152 _ => false,
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
158enum FilterField {
159 Column,
160 Expression { sql: String, source: Option<String> },
161}
162
163#[derive(Debug, Clone)]
164pub struct Filter {
166 pub field: String,
168 field_source: FilterField,
169 pub operator: FilterOperator,
171 pub value: FilterValue,
173}
174
175impl Filter {
176 pub fn new(field: impl Into<String>, operator: FilterOperator, value: FilterValue) -> Self {
178 let field = field.into();
179 Self {
180 field,
181 field_source: FilterField::Column,
182 operator,
183 value,
184 }
185 }
186
187 pub fn map_expression_source<F>(&mut self, mapper: F)
189 where
190 F: FnOnce(&str) -> String,
191 {
192 if let FilterField::Expression { sql, .. } = &mut self.field_source {
193 let mapped = mapper(sql);
194 self.field = mapped.clone();
195 *sql = mapped;
196 }
197 }
198
199 pub fn source_field_name(&self) -> Option<&str> {
201 match &self.field_source {
202 FilterField::Column => Some(&self.field),
203 FilterField::Expression { source, .. } => source.as_deref(),
204 }
205 }
206
207 pub fn lhs_expr(&self) -> Expr {
209 filter_lhs_expr(self)
210 }
211
212 pub fn lhs_sql(&self) -> String {
214 filter_lhs_sql(self)
215 }
216
217 pub fn and(self, other: impl Into<FilterCondition>) -> FilterCondition {
219 FilterCondition::And(vec![FilterCondition::from(self), other.into()])
220 }
221
222 pub fn or(self, other: impl Into<FilterCondition>) -> FilterCondition {
224 FilterCondition::Or(vec![FilterCondition::from(self), other.into()])
225 }
226
227 #[allow(clippy::should_implement_trait)]
231 pub fn not(self) -> FilterCondition {
232 FilterCondition::not(self)
233 }
234
235 #[cfg(test)]
236 pub(crate) fn expression(
237 sql: impl Into<String>,
238 operator: FilterOperator,
239 value: FilterValue,
240 ) -> Self {
241 Self::expression_with_source(sql, None, operator, value)
242 }
243
244 pub(crate) fn expression_with_source(
245 sql: impl Into<String>,
246 source: Option<String>,
247 operator: FilterOperator,
248 value: FilterValue,
249 ) -> Self {
250 let sql = sql.into();
251 Self {
252 field: sql.clone(),
253 field_source: FilterField::Expression { sql, source },
254 operator,
255 value,
256 }
257 }
258
259 fn is_always_true(&self) -> bool {
264 matches!(self.operator, FilterOperator::NotIn)
265 && self.value.is_empty_membership_collection()
266 }
267
268 fn is_always_false(&self) -> bool {
273 matches!(self.operator, FilterOperator::In) && self.value.is_empty_membership_collection()
274 }
275}
276
277#[derive(Debug, Clone)]
279pub enum UpdateValue {
280 String(String),
282 Integer(i64),
284 Float(f64),
286 Boolean(bool),
288 Null,
290 Timestamp(chrono::DateTime<chrono::Utc>),
292 Uuid(Uuid),
294 FieldRef(super::expressions::F),
296 Expression(super::annotation::Expression),
298}
299
300impl From<String> for UpdateValue {
301 fn from(value: String) -> Self {
302 Self::String(value)
303 }
304}
305
306impl From<&str> for UpdateValue {
307 fn from(value: &str) -> Self {
308 Self::String(value.to_string())
309 }
310}
311
312impl From<i64> for UpdateValue {
313 fn from(value: i64) -> Self {
314 Self::Integer(value)
315 }
316}
317
318impl From<i32> for UpdateValue {
319 fn from(value: i32) -> Self {
320 Self::Integer(value as i64)
321 }
322}
323
324impl From<f64> for UpdateValue {
325 fn from(value: f64) -> Self {
326 Self::Float(value)
327 }
328}
329
330impl From<f32> for UpdateValue {
331 fn from(value: f32) -> Self {
332 Self::Float(value as f64)
333 }
334}
335
336impl From<bool> for UpdateValue {
337 fn from(value: bool) -> Self {
338 Self::Boolean(value)
339 }
340}
341
342impl From<chrono::DateTime<chrono::Utc>> for UpdateValue {
343 fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
344 Self::Timestamp(value)
345 }
346}
347
348impl From<Uuid> for UpdateValue {
349 fn from(value: Uuid) -> Self {
350 Self::Uuid(value)
351 }
352}
353
354impl<T> From<Option<T>> for UpdateValue
355where
356 T: Into<UpdateValue>,
357{
358 fn from(value: Option<T>) -> Self {
359 value.map_or(Self::Null, Into::into)
360 }
361}
362
363#[derive(Debug, Clone)]
365pub struct FieldAssignment {
366 field: String,
367 value: UpdateValue,
368}
369
370impl FieldAssignment {
371 pub fn new(field: impl Into<String>, value: impl Into<UpdateValue>) -> Self {
373 Self {
374 field: field.into(),
375 value: value.into(),
376 }
377 }
378
379 pub fn field(&self) -> &str {
381 &self.field
382 }
383
384 pub fn value(&self) -> &UpdateValue {
386 &self.value
387 }
388}
389
390impl<M, T, V> From<(super::expressions::FieldRef<M, T>, V)> for FieldAssignment
391where
392 V: Into<UpdateValue>,
393{
394 fn from((field, value): (super::expressions::FieldRef<M, T>, V)) -> Self {
395 Self::new(field.name(), value)
396 }
397}
398
399impl<V> From<(&str, V)> for FieldAssignment
400where
401 V: Into<UpdateValue>,
402{
403 fn from((field, value): (&str, V)) -> Self {
404 Self::new(field, value)
405 }
406}
407
408impl<V> From<(String, V)> for FieldAssignment
409where
410 V: Into<UpdateValue>,
411{
412 fn from((field, value): (String, V)) -> Self {
413 Self::new(field, value)
414 }
415}
416
417#[derive(Debug, Clone)]
457pub enum FilterCondition {
458 Single(Filter),
460 And(Vec<FilterCondition>),
462 Or(Vec<FilterCondition>),
464 Not(Box<FilterCondition>),
466}
467
468impl FilterCondition {
469 pub fn single(filter: Filter) -> Self {
471 Self::Single(filter)
472 }
473
474 pub fn and(conditions: Vec<FilterCondition>) -> Self {
476 Self::And(conditions)
477 }
478
479 pub fn or(conditions: Vec<FilterCondition>) -> Self {
481 Self::Or(conditions)
482 }
483
484 #[allow(clippy::should_implement_trait)]
501 pub fn not(condition: impl Into<FilterCondition>) -> Self {
502 Self::Not(Box::new(condition.into()))
503 }
504
505 pub fn all(conditions: Vec<FilterCondition>) -> Self {
507 Self::and(conditions)
508 }
509
510 pub fn any(conditions: Vec<FilterCondition>) -> Self {
512 Self::or(conditions)
513 }
514
515 pub fn negate(condition: impl Into<FilterCondition>) -> Self {
517 Self::not(condition)
518 }
519
520 pub fn or_filters(filters: Vec<Filter>) -> Self {
536 Self::Or(filters.into_iter().map(FilterCondition::Single).collect())
537 }
538
539 pub fn and_filters(filters: Vec<Filter>) -> Self {
541 Self::And(filters.into_iter().map(FilterCondition::Single).collect())
542 }
543
544 pub fn is_empty(&self) -> bool {
546 match self {
547 FilterCondition::Single(_) => false,
548 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
549 conditions.is_empty() || conditions.iter().all(|c| c.is_empty())
550 }
551 FilterCondition::Not(condition) => condition.is_empty(),
552 }
553 }
554
555 fn is_always_true(&self) -> bool {
561 match self {
562 FilterCondition::Single(filter) => filter.is_always_true(),
563 FilterCondition::And(conditions) => conditions.iter().all(Self::is_always_true),
564 FilterCondition::Or(conditions) => {
565 !conditions.is_empty() && conditions.iter().any(Self::is_always_true)
566 }
567 FilterCondition::Not(condition) => condition.is_always_false(),
568 }
569 }
570
571 fn is_always_false(&self) -> bool {
573 match self {
574 FilterCondition::Single(filter) => filter.is_always_false(),
575 FilterCondition::And(conditions) => {
576 !conditions.is_empty() && conditions.iter().any(Self::is_always_false)
577 }
578 FilterCondition::Or(conditions) => conditions.iter().all(Self::is_always_false),
579 FilterCondition::Not(condition) => condition.is_always_true(),
580 }
581 }
582}
583
584fn map_filter_condition_columns<F>(condition: &mut FilterCondition, mapper: &mut F)
585where
586 F: FnMut(&mut Filter),
587{
588 match condition {
589 FilterCondition::Single(filter) => mapper(filter),
590 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
591 for condition in conditions {
592 map_filter_condition_columns(condition, mapper);
593 }
594 }
595 FilterCondition::Not(condition) => map_filter_condition_columns(condition, mapper),
596 }
597}
598
599impl From<Filter> for FilterCondition {
600 fn from(filter: Filter) -> Self {
601 Self::Single(filter)
602 }
603}
604
605pub(crate) fn parse_membership_string(s: &str) -> Vec<reinhardt_query::value::Value> {
610 let trimmed = s.trim();
611
612 if trimmed.starts_with('[')
614 && trimmed.ends_with(']')
615 && let Ok(arr) = serde_json::from_str::<Vec<serde_json::Value>>(trimmed)
616 {
617 return arr
618 .iter()
619 .map(|v| match v {
620 serde_json::Value::String(s) => s.clone().into(),
621 serde_json::Value::Number(n) => {
622 if let Some(i) = n.as_i64() {
623 i.into()
624 } else if let Some(f) = n.as_f64() {
625 f.into()
626 } else {
627 n.to_string().into()
628 }
629 }
630 serde_json::Value::Bool(b) => (*b).into(),
631 _ => v.to_string().into(),
632 })
633 .collect();
634 }
635
636 let trimmed = trimmed
638 .strip_prefix('(')
639 .and_then(|value| value.strip_suffix(')'))
640 .unwrap_or(trimmed);
641 trimmed
642 .split(',')
643 .map(|s| s.trim())
644 .filter(|s| !s.is_empty())
645 .map(|s| s.to_string().into())
646 .collect()
647}
648
649impl From<String> for FilterValue {
651 fn from(s: String) -> Self {
652 FilterValue::String(s)
653 }
654}
655
656impl From<&str> for FilterValue {
657 fn from(s: &str) -> Self {
658 FilterValue::String(s.to_string())
659 }
660}
661
662impl From<i64> for FilterValue {
663 fn from(i: i64) -> Self {
664 FilterValue::Integer(i)
665 }
666}
667
668impl From<i32> for FilterValue {
669 fn from(i: i32) -> Self {
670 FilterValue::Integer(i as i64)
671 }
672}
673
674macro_rules! filter_value_signed_integer {
675 ($($type:ty),+ $(,)?) => {
676 $(
677 impl From<$type> for FilterValue {
678 fn from(value: $type) -> Self {
679 Self::Integer(i64::from(value))
680 }
681 }
682 )+
683 };
684}
685
686macro_rules! filter_value_unsigned_integer {
687 ($($type:ty),+ $(,)?) => {
688 $(
689 impl From<$type> for FilterValue {
690 fn from(value: $type) -> Self {
691 Self::Integer(i64::from(value))
692 }
693 }
694 )+
695 };
696}
697
698filter_value_signed_integer!(i8, i16);
699filter_value_unsigned_integer!(u8, u16, u32);
700
701impl From<u64> for FilterValue {
702 fn from(value: u64) -> Self {
703 value
704 .try_into()
705 .map_or_else(|_| Self::String(value.to_string()), Self::Integer)
706 }
707}
708
709impl From<usize> for FilterValue {
710 fn from(value: usize) -> Self {
711 Self::from(value as u64)
712 }
713}
714
715impl From<isize> for FilterValue {
716 fn from(value: isize) -> Self {
717 Self::Integer(value as i64)
718 }
719}
720
721impl From<i128> for FilterValue {
722 fn from(value: i128) -> Self {
723 value
724 .try_into()
725 .map_or_else(|_| Self::String(value.to_string()), Self::Integer)
726 }
727}
728
729impl From<u128> for FilterValue {
730 fn from(value: u128) -> Self {
731 value
732 .try_into()
733 .map_or_else(|_| Self::String(value.to_string()), Self::Integer)
734 }
735}
736
737impl From<f64> for FilterValue {
738 fn from(f: f64) -> Self {
739 FilterValue::Float(f)
740 }
741}
742
743impl From<bool> for FilterValue {
744 fn from(b: bool) -> Self {
745 FilterValue::Boolean(b)
746 }
747}
748
749impl From<chrono::DateTime<chrono::Utc>> for FilterValue {
750 fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
751 Self::Timestamp(value)
752 }
753}
754
755impl From<uuid::Uuid> for FilterValue {
756 fn from(u: uuid::Uuid) -> Self {
757 Self::Uuid(u)
758 }
759}
760
761#[derive(Debug, Clone)]
762pub struct OrmQuery {
764 filters: Vec<Filter>,
765}
766
767impl OrmQuery {
768 pub fn new() -> Self {
770 Self {
771 filters: Vec::new(),
772 }
773 }
774
775 pub fn filter(mut self, filter: Filter) -> Self {
777 self.filters.push(filter);
778 self
779 }
780}
781
782impl Default for OrmQuery {
783 fn default() -> Self {
784 Self::new()
785 }
786}
787
788#[derive(Clone, Debug)]
790struct JoinClause {
791 join_type: super::sqlalchemy_query::JoinType,
793 target_table: String,
795 target_alias: Option<String>,
797 on_condition: String,
801}
802
803#[derive(Clone, Debug)]
805enum AggregateFunc {
806 Avg,
807 Count,
808 Sum,
809 Min,
810 Max,
811}
812
813#[derive(Clone, Debug)]
815pub enum ComparisonOp {
816 Eq,
818 Ne,
820 Gt,
822 Gte,
824 Lt,
826 Lte,
828}
829
830#[derive(Clone, Debug)]
832enum AggregateValue {
833 Int(i64),
834 Float(f64),
835}
836
837#[derive(Clone, Debug)]
839enum HavingCondition {
840 AggregateCompare {
843 func: AggregateFunc,
844 field: String,
845 operator: ComparisonOp,
846 value: AggregateValue,
847 },
848}
849
850#[derive(Clone, Debug)]
852enum SubqueryCondition {
853 In { field: String, subquery: String },
856 NotIn { field: String, subquery: String },
858 Exists {
861 subquery: String,
862 outer_fields: Vec<String>,
863 },
864 NotExists {
866 subquery: String,
867 outer_fields: Vec<String>,
868 },
869}
870
871fn rewrite_subquery_field_to_placeholder(sql: &mut String, old_field: &str, placeholder: &str) {
872 let old_identifier = format!("\"{}\"", old_field.replace('"', "\"\""));
873 *sql = sql.replace(&old_identifier, placeholder);
874
875 let old_qualified_identifier = quote_identifier(old_field);
876 *sql = sql.replace(&old_qualified_identifier, placeholder);
877}
878
879fn rewrite_subquery_fields(sql: &str, rewrites: &[(String, String)]) -> String {
880 let mut rewritten = sql.to_owned();
881 let placeholders = rewrites
882 .iter()
883 .enumerate()
884 .map(|(index, _)| format!("\u{1}reinhardt_subquery_field_{index}\u{1}"))
885 .collect::<Vec<_>>();
886
887 for ((old_field, _), placeholder) in rewrites.iter().zip(&placeholders) {
888 rewrite_subquery_field_to_placeholder(&mut rewritten, old_field, placeholder);
889 }
890 for ((_, new_field), placeholder) in rewrites.iter().zip(placeholders) {
891 rewritten = rewritten.replace(&placeholder, "e_identifier(new_field));
892 }
893
894 rewritten
895}
896
897fn collect_subquery_outer_fields(value: &FilterValue, fields: &mut Vec<String>) {
898 match value {
899 FilterValue::FieldRef(field) if field.field.contains('.') => {
900 fields.push(field.field.clone());
901 }
902 FilterValue::OuterRef(field) => fields.push(field.field.clone()),
903 FilterValue::List(values) => {
904 for value in values {
905 collect_subquery_outer_fields(value, fields);
906 }
907 }
908 FilterValue::Range(start, end) => {
909 collect_subquery_outer_fields(start, fields);
910 collect_subquery_outer_fields(end, fields);
911 }
912 _ => {}
913 }
914}
915
916fn collect_subquery_outer_condition(condition: &FilterCondition, fields: &mut Vec<String>) {
917 match condition {
918 FilterCondition::Single(filter) => collect_subquery_outer_fields(&filter.value, fields),
919 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
920 for condition in conditions {
921 collect_subquery_outer_condition(condition, fields);
922 }
923 }
924 FilterCondition::Not(condition) => collect_subquery_outer_condition(condition, fields),
925 }
926}
927
928const MAX_FILTER_CONDITION_DEPTH: usize = 64;
929
930#[derive(Clone)]
931pub struct QuerySet<T>
933where
934 T: super::Model,
935{
936 _phantom: std::marker::PhantomData<T>,
937 filters: SmallVec<[Filter; 10]>,
938 filter_conditions: SmallVec<[FilterCondition; 4]>,
939 select_related_fields: Vec<String>,
940 prefetch_related_fields: Vec<String>,
941 order_by_fields: Vec<String>,
942 distinct_enabled: bool,
943 selected_fields: Option<Vec<String>>,
944 deferred_fields: Vec<String>,
945 annotations: Vec<super::annotation::Annotation>,
946 manager: Option<std::sync::Arc<super::manager::Manager<T>>>,
947 limit: Option<usize>,
948 offset: Option<usize>,
949 ctes: super::cte::CTECollection,
950 lateral_joins: super::lateral_join::LateralJoins,
951 joins: Vec<JoinClause>,
952 group_by_fields: Vec<String>,
953 having_conditions: Vec<HavingCondition>,
954 subquery_conditions: Vec<SubqueryCondition>,
955 from_alias: Option<String>,
956 from_subquery_sql: Option<String>,
959}
960
961impl<T> QuerySet<T>
962where
963 T: super::Model,
964{
965 pub fn new() -> Self {
967 Self {
968 _phantom: std::marker::PhantomData,
969 filters: SmallVec::new(),
970 filter_conditions: SmallVec::new(),
971 select_related_fields: Vec::new(),
972 prefetch_related_fields: Vec::new(),
973 order_by_fields: Vec::new(),
974 distinct_enabled: false,
975 selected_fields: None,
976 deferred_fields: Vec::new(),
977 annotations: Vec::new(),
978 manager: None,
979 limit: None,
980 offset: None,
981 ctes: super::cte::CTECollection::new(),
982 lateral_joins: super::lateral_join::LateralJoins::new(),
983 joins: Vec::new(),
984 group_by_fields: Vec::new(),
985 having_conditions: Vec::new(),
986 subquery_conditions: Vec::new(),
987 from_alias: None,
988 from_subquery_sql: None,
989 }
990 }
991
992 pub fn with_manager(manager: std::sync::Arc<super::manager::Manager<T>>) -> Self {
994 Self {
995 _phantom: std::marker::PhantomData,
996 filters: SmallVec::new(),
997 filter_conditions: SmallVec::new(),
998 select_related_fields: Vec::new(),
999 prefetch_related_fields: Vec::new(),
1000 order_by_fields: Vec::new(),
1001 distinct_enabled: false,
1002 selected_fields: None,
1003 deferred_fields: Vec::new(),
1004 annotations: Vec::new(),
1005 manager: Some(manager),
1006 limit: None,
1007 offset: None,
1008 ctes: super::cte::CTECollection::new(),
1009 lateral_joins: super::lateral_join::LateralJoins::new(),
1010 joins: Vec::new(),
1011 group_by_fields: Vec::new(),
1012 having_conditions: Vec::new(),
1013 subquery_conditions: Vec::new(),
1014 from_alias: None,
1015 from_subquery_sql: None,
1016 }
1017 }
1018
1019 pub fn filter(mut self, filter: impl Into<FilterCondition>) -> Self {
1025 match filter.into() {
1026 FilterCondition::Single(filter) => self.filters.push(filter),
1027 condition => self.filter_conditions.push(condition),
1028 }
1029 self
1030 }
1031
1032 pub fn filters(&self) -> &[Filter] {
1037 &self.filters
1038 }
1039
1040 pub fn filter_conditions(&self) -> &[FilterCondition] {
1042 &self.filter_conditions
1043 }
1044
1045 pub fn map_filter_columns<F>(&mut self, mut mapper: F)
1050 where
1051 F: FnMut(&mut Filter),
1052 {
1053 for filter in &mut self.filters {
1054 mapper(filter);
1055 }
1056 for condition in &mut self.filter_conditions {
1057 map_filter_condition_columns(condition, &mut mapper);
1058 }
1059 }
1060
1061 pub fn map_order_by_fields<F>(&mut self, mut mapper: F)
1066 where
1067 F: FnMut(&mut String),
1068 {
1069 for field in &mut self.order_by_fields {
1070 mapper(field);
1071 }
1072 }
1073
1074 pub fn map_subquery_fields<F>(&mut self, mut mapper: F)
1079 where
1080 F: FnMut(&mut String),
1081 {
1082 for condition in &mut self.subquery_conditions {
1083 match condition {
1084 SubqueryCondition::In { field, .. } | SubqueryCondition::NotIn { field, .. } => {
1085 mapper(field)
1086 }
1087 SubqueryCondition::Exists {
1088 subquery,
1089 outer_fields,
1090 }
1091 | SubqueryCondition::NotExists {
1092 subquery,
1093 outer_fields,
1094 } => {
1095 let mut rewrites = Vec::new();
1096 for field in outer_fields {
1097 let old_field = field.clone();
1098 mapper(field);
1099 if old_field != *field {
1100 rewrites.push((old_field, field.clone()));
1101 }
1102 }
1103 if !rewrites.is_empty() {
1104 *subquery = rewrite_subquery_fields(subquery, &rewrites);
1105 }
1106 }
1107 }
1108 }
1109 }
1110
1111 pub fn subquery_fields(&self) -> impl Iterator<Item = &str> {
1113 self.subquery_conditions
1114 .iter()
1115 .flat_map(|condition| {
1116 let fields: &[String] = match condition {
1117 SubqueryCondition::In { field, .. }
1118 | SubqueryCondition::NotIn { field, .. } => std::slice::from_ref(field),
1119 SubqueryCondition::Exists { outer_fields, .. }
1120 | SubqueryCondition::NotExists { outer_fields, .. } => outer_fields,
1121 };
1122 fields.iter()
1123 })
1124 .map(String::as_str)
1125 }
1126
1127 pub fn annotations(&self) -> &[super::annotation::Annotation] {
1129 &self.annotations
1130 }
1131
1132 pub fn root_table_alias(&self) -> &str {
1134 self.from_alias.as_deref().unwrap_or(T::table_name())
1135 }
1136
1137 pub fn has_joins(&self) -> bool {
1139 !self.joins.is_empty()
1140 }
1141
1142 fn has_where_predicates(&self) -> bool {
1143 !(self.filters.is_empty()
1144 && self.filter_conditions.is_empty()
1145 && self.subquery_conditions.is_empty())
1146 }
1147
1148 fn has_restricting_where_predicates(&self) -> bool {
1155 if !self.subquery_conditions.is_empty() {
1156 return true;
1157 }
1158 self.filters.iter().any(|filter| !filter.is_always_true())
1159 || self
1160 .filter_conditions
1161 .iter()
1162 .any(|condition| !condition.is_always_true())
1163 }
1164
1165 fn outer_reference_fields(&self) -> Vec<String> {
1166 let mut fields = Vec::new();
1167 for filter in &self.filters {
1168 collect_subquery_outer_fields(&filter.value, &mut fields);
1169 }
1170 for condition in &self.filter_conditions {
1171 collect_subquery_outer_condition(condition, &mut fields);
1172 }
1173 fields.sort_unstable();
1174 fields.dedup();
1175 fields
1176 }
1177
1178 pub fn from_subquery<M, F>(builder: F, alias: &str) -> Self
1231 where
1232 M: super::Model + 'static,
1233 F: FnOnce(QuerySet<M>) -> QuerySet<M>,
1234 {
1235 let subquery_qs = QuerySet::<M>::new();
1237 let configured_subquery = builder(subquery_qs);
1239 let subquery_sql = configured_subquery.as_subquery();
1241
1242 Self {
1244 _phantom: std::marker::PhantomData,
1245 filters: SmallVec::new(),
1246 filter_conditions: SmallVec::new(),
1247 select_related_fields: Vec::new(),
1248 prefetch_related_fields: Vec::new(),
1249 order_by_fields: Vec::new(),
1250 distinct_enabled: false,
1251 selected_fields: None,
1252 deferred_fields: Vec::new(),
1253 annotations: Vec::new(),
1254 manager: None,
1255 limit: None,
1256 offset: None,
1257 ctes: super::cte::CTECollection::new(),
1258 lateral_joins: super::lateral_join::LateralJoins::new(),
1259 joins: Vec::new(),
1260 group_by_fields: Vec::new(),
1261 having_conditions: Vec::new(),
1262 subquery_conditions: Vec::new(),
1263 from_alias: Some(alias.to_string()),
1264 from_subquery_sql: Some(subquery_sql),
1265 }
1266 }
1267
1268 pub fn inner_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
1329 let condition = format!(
1330 "{}.{} = {}.{}",
1331 T::table_name(),
1332 left_field,
1333 R::table_name(),
1334 right_field
1335 );
1336
1337 self.joins.push(JoinClause {
1338 join_type: super::sqlalchemy_query::JoinType::Inner,
1339 target_table: R::table_name().to_string(),
1340 target_alias: None,
1341 on_condition: condition,
1342 });
1343
1344 self
1345 }
1346
1347 pub fn left_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
1400 let condition = format!(
1401 "{}.{} = {}.{}",
1402 T::table_name(),
1403 left_field,
1404 R::table_name(),
1405 right_field
1406 );
1407
1408 self.joins.push(JoinClause {
1409 join_type: super::sqlalchemy_query::JoinType::Left,
1410 target_table: R::table_name().to_string(),
1411 target_alias: None,
1412 on_condition: condition,
1413 });
1414
1415 self
1416 }
1417
1418 pub fn right_join<R: super::Model>(mut self, left_field: &str, right_field: &str) -> Self {
1471 let condition = format!(
1472 "{}.{} = {}.{}",
1473 T::table_name(),
1474 left_field,
1475 R::table_name(),
1476 right_field
1477 );
1478
1479 self.joins.push(JoinClause {
1480 join_type: super::sqlalchemy_query::JoinType::Right,
1481 target_table: R::table_name().to_string(),
1482 target_alias: None,
1483 on_condition: condition,
1484 });
1485
1486 self
1487 }
1488
1489 pub fn cross_join<R: super::Model>(mut self) -> Self {
1542 self.joins.push(JoinClause {
1543 join_type: super::sqlalchemy_query::JoinType::Inner, target_table: R::table_name().to_string(),
1545 target_alias: None,
1546 on_condition: String::new(), });
1548
1549 self
1550 }
1551
1552 pub fn from_as(mut self, alias: &str) -> Self {
1605 self.from_alias = Some(alias.to_string());
1606 self
1607 }
1608
1609 pub fn inner_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1669 self.joins.push(JoinClause {
1670 join_type: super::sqlalchemy_query::JoinType::Inner,
1671 target_table: R::table_name().to_string(),
1672 target_alias: None,
1673 on_condition: condition.to_string(),
1674 });
1675
1676 self
1677 }
1678
1679 pub fn left_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1737 self.joins.push(JoinClause {
1738 join_type: super::sqlalchemy_query::JoinType::Left,
1739 target_table: R::table_name().to_string(),
1740 target_alias: None,
1741 on_condition: condition.to_string(),
1742 });
1743
1744 self
1745 }
1746
1747 pub fn right_join_on<R: super::Model>(mut self, condition: &str) -> Self {
1805 self.joins.push(JoinClause {
1806 join_type: super::sqlalchemy_query::JoinType::Right,
1807 target_table: R::table_name().to_string(),
1808 target_alias: None,
1809 on_condition: condition.to_string(),
1810 });
1811
1812 self
1813 }
1814
1815 pub fn inner_join_as<R: super::Model, F>(
1878 mut self,
1879 left_alias: &str,
1880 right_alias: &str,
1881 condition_fn: F,
1882 ) -> Self
1883 where
1884 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
1885 {
1886 if self.from_alias.is_none() {
1888 self.from_alias = Some(left_alias.to_string());
1889 }
1890
1891 let left_fields = T::new_fields().with_alias(left_alias);
1893 let right_fields = R::new_fields().with_alias(right_alias);
1894
1895 let comparison = condition_fn(left_fields, right_fields);
1897
1898 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
1900
1901 self.joins.push(JoinClause {
1903 join_type: super::sqlalchemy_query::JoinType::Inner,
1904 target_table: R::table_name().to_string(),
1905 target_alias: Some(right_alias.to_string()),
1906 on_condition: condition,
1907 });
1908
1909 self
1910 }
1911
1912 pub fn left_join_as<R: super::Model, F>(
1979 mut self,
1980 left_alias: &str,
1981 right_alias: &str,
1982 condition_fn: F,
1983 ) -> Self
1984 where
1985 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
1986 {
1987 if self.from_alias.is_none() {
1989 self.from_alias = Some(left_alias.to_string());
1990 }
1991
1992 let left_fields = T::new_fields().with_alias(left_alias);
1994 let right_fields = R::new_fields().with_alias(right_alias);
1995
1996 let comparison = condition_fn(left_fields, right_fields);
1998
1999 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
2001
2002 self.joins.push(JoinClause {
2004 join_type: super::sqlalchemy_query::JoinType::Left,
2005 target_table: R::table_name().to_string(),
2006 target_alias: Some(right_alias.to_string()),
2007 on_condition: condition,
2008 });
2009
2010 self
2011 }
2012
2013 pub fn right_join_as<R: super::Model, F>(
2080 mut self,
2081 left_alias: &str,
2082 right_alias: &str,
2083 condition_fn: F,
2084 ) -> Self
2085 where
2086 F: FnOnce(T::Fields, R::Fields) -> FieldComparison,
2087 {
2088 if self.from_alias.is_none() {
2090 self.from_alias = Some(left_alias.to_string());
2091 }
2092
2093 let left_fields = T::new_fields().with_alias(left_alias);
2095 let right_fields = R::new_fields().with_alias(right_alias);
2096
2097 let comparison = condition_fn(left_fields, right_fields);
2099
2100 let condition = QueryFieldCompiler::compile_field_comparison(&comparison);
2102
2103 self.joins.push(JoinClause {
2105 join_type: super::sqlalchemy_query::JoinType::Right,
2106 target_table: R::table_name().to_string(),
2107 target_alias: Some(right_alias.to_string()),
2108 on_condition: condition,
2109 });
2110
2111 self
2112 }
2113
2114 pub fn group_by<F>(mut self, selector_fn: F) -> Self
2211 where
2212 F: FnOnce(T::Fields) -> GroupByFields,
2213 {
2214 let fields = T::new_fields();
2215 let group_by_fields = selector_fn(fields);
2216 self.group_by_fields = group_by_fields.build();
2217 self
2218 }
2219
2220 pub fn having_avg<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
2286 where
2287 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
2288 NT: super::query_fields::NumericType,
2289 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
2290 {
2291 let fields = T::new_fields();
2292 let field = field_selector(&fields);
2293 let field_path = field.path().join(".");
2294
2295 let avg_expr = AggregateExpr::avg(&field_path);
2296 let comparison = expr_fn(avg_expr);
2297
2298 let operator = match comparison.op {
2300 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2301 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2302 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2303 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2304 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2305 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2306 };
2307
2308 let value = match comparison.value {
2309 super::query_fields::aggregate::ComparisonValue::Int(i) => {
2310 AggregateValue::Float(i as f64)
2311 }
2312 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2313 };
2314
2315 self.having_conditions
2316 .push(HavingCondition::AggregateCompare {
2317 func: AggregateFunc::Avg,
2318 field: comparison.aggregate.field().to_string(),
2319 operator,
2320 value,
2321 });
2322 self
2323 }
2324
2325 pub fn having_count<F>(mut self, expr_fn: F) -> Self
2385 where
2386 F: FnOnce(AggregateExpr) -> ComparisonExpr,
2387 {
2388 let count_expr = AggregateExpr::count("*");
2389 let comparison = expr_fn(count_expr);
2390
2391 let operator = match comparison.op {
2393 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2394 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2395 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2396 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2397 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2398 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2399 };
2400
2401 let value = match comparison.value {
2402 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
2403 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2404 };
2405
2406 self.having_conditions
2407 .push(HavingCondition::AggregateCompare {
2408 func: AggregateFunc::Count,
2409 field: comparison.aggregate.field().to_string(),
2410 operator,
2411 value,
2412 });
2413 self
2414 }
2415
2416 pub fn having_sum<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
2481 where
2482 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
2483 NT: super::query_fields::NumericType,
2484 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
2485 {
2486 let fields = T::new_fields();
2487 let field = field_selector(&fields);
2488 let field_path = field.path().join(".");
2489
2490 let sum_expr = AggregateExpr::sum(&field_path);
2491 let comparison = expr_fn(sum_expr);
2492
2493 let operator = match comparison.op {
2494 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2495 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2496 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2497 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2498 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2499 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2500 };
2501
2502 let value = match comparison.value {
2503 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
2504 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2505 };
2506
2507 self.having_conditions
2508 .push(HavingCondition::AggregateCompare {
2509 func: AggregateFunc::Sum,
2510 field: comparison.aggregate.field().to_string(),
2511 operator,
2512 value,
2513 });
2514 self
2515 }
2516
2517 pub fn having_min<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
2583 where
2584 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
2585 NT: super::query_fields::NumericType,
2586 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
2587 {
2588 let fields = T::new_fields();
2589 let field = field_selector(&fields);
2590 let field_path = field.path().join(".");
2591
2592 let min_expr = AggregateExpr::min(&field_path);
2593 let comparison = expr_fn(min_expr);
2594
2595 let operator = match comparison.op {
2596 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2597 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2598 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2599 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2600 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2601 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2602 };
2603
2604 let value = match comparison.value {
2605 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
2606 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2607 };
2608
2609 self.having_conditions
2610 .push(HavingCondition::AggregateCompare {
2611 func: AggregateFunc::Min,
2612 field: comparison.aggregate.field().to_string(),
2613 operator,
2614 value,
2615 });
2616 self
2617 }
2618
2619 pub fn having_max<FS, FE, NT>(mut self, field_selector: FS, expr_fn: FE) -> Self
2685 where
2686 FS: FnOnce(&T::Fields) -> &super::query_fields::Field<T, NT>,
2687 NT: super::query_fields::NumericType,
2688 FE: FnOnce(AggregateExpr) -> ComparisonExpr,
2689 {
2690 let fields = T::new_fields();
2691 let field = field_selector(&fields);
2692 let field_path = field.path().join(".");
2693
2694 let max_expr = AggregateExpr::max(&field_path);
2695 let comparison = expr_fn(max_expr);
2696
2697 let operator = match comparison.op {
2698 super::query_fields::comparison::ComparisonOperator::Eq => ComparisonOp::Eq,
2699 super::query_fields::comparison::ComparisonOperator::Ne => ComparisonOp::Ne,
2700 super::query_fields::comparison::ComparisonOperator::Gt => ComparisonOp::Gt,
2701 super::query_fields::comparison::ComparisonOperator::Gte => ComparisonOp::Gte,
2702 super::query_fields::comparison::ComparisonOperator::Lt => ComparisonOp::Lt,
2703 super::query_fields::comparison::ComparisonOperator::Lte => ComparisonOp::Lte,
2704 };
2705
2706 let value = match comparison.value {
2707 super::query_fields::aggregate::ComparisonValue::Int(i) => AggregateValue::Int(i),
2708 super::query_fields::aggregate::ComparisonValue::Float(f) => AggregateValue::Float(f),
2709 };
2710
2711 self.having_conditions
2712 .push(HavingCondition::AggregateCompare {
2713 func: AggregateFunc::Max,
2714 field: comparison.aggregate.field().to_string(),
2715 operator,
2716 value,
2717 });
2718 self
2719 }
2720
2721 pub fn filter_in_subquery<R: super::Model, F>(mut self, field: &str, subquery_fn: F) -> Self
2781 where
2782 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2783 {
2784 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2785 let subquery_sql = subquery_qs.as_subquery();
2786
2787 self.subquery_conditions.push(SubqueryCondition::In {
2788 field: field.to_string(),
2789 subquery: subquery_sql,
2790 });
2791
2792 self
2793 }
2794
2795 pub fn filter_not_in_subquery<R: super::Model, F>(mut self, field: &str, subquery_fn: F) -> Self
2850 where
2851 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2852 {
2853 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2854 let subquery_sql = subquery_qs.as_subquery();
2855
2856 self.subquery_conditions.push(SubqueryCondition::NotIn {
2857 field: field.to_string(),
2858 subquery: subquery_sql,
2859 });
2860
2861 self
2862 }
2863
2864 pub fn filter_exists<R: super::Model, F>(mut self, subquery_fn: F) -> Self
2920 where
2921 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2922 {
2923 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2924 let outer_fields = subquery_qs.outer_reference_fields();
2925 let subquery_sql = subquery_qs.as_subquery();
2926
2927 self.subquery_conditions.push(SubqueryCondition::Exists {
2928 subquery: subquery_sql,
2929 outer_fields,
2930 });
2931
2932 self
2933 }
2934
2935 pub fn filter_not_exists<R: super::Model, F>(mut self, subquery_fn: F) -> Self
2991 where
2992 F: FnOnce(QuerySet<R>) -> QuerySet<R>,
2993 {
2994 let subquery_qs = subquery_fn(QuerySet::<R>::new());
2995 let outer_fields = subquery_qs.outer_reference_fields();
2996 let subquery_sql = subquery_qs.as_subquery();
2997
2998 self.subquery_conditions.push(SubqueryCondition::NotExists {
2999 subquery: subquery_sql,
3000 outer_fields,
3001 });
3002
3003 self
3004 }
3005
3006 pub fn with_cte(mut self, cte: super::cte::CTE) -> Self {
3059 self.ctes.add(cte);
3060 self
3061 }
3062
3063 pub fn with_lateral_join(mut self, join: super::lateral_join::LateralJoin) -> Self {
3130 self.lateral_joins.add(join);
3131 self
3132 }
3133
3134 fn build_where_condition(&self) -> reinhardt_core::exception::Result<Option<Condition>> {
3136 if !self.has_where_predicates() {
3137 return Ok(None);
3138 }
3139
3140 let mut cond = Condition::all();
3141 let mut added = false;
3142
3143 for filter in &self.filters {
3144 let col = Self::filter_lhs_expr(filter);
3145
3146 let expr = match (&filter.operator, &filter.value) {
3147 (FilterOperator::Eq, FilterValue::FieldRef(f)) => {
3149 col.eq(Expr::col(Alias::new(&f.field)))
3150 }
3151 (FilterOperator::Ne, FilterValue::FieldRef(f)) => {
3152 col.ne(Expr::col(Alias::new(&f.field)))
3153 }
3154 (FilterOperator::Gt, FilterValue::FieldRef(f)) => {
3155 col.gt(Expr::col(Alias::new(&f.field)))
3156 }
3157 (FilterOperator::Gte, FilterValue::FieldRef(f)) => {
3158 col.gte(Expr::col(Alias::new(&f.field)))
3159 }
3160 (FilterOperator::Lt, FilterValue::FieldRef(f)) => {
3161 col.lt(Expr::col(Alias::new(&f.field)))
3162 }
3163 (FilterOperator::Lte, FilterValue::FieldRef(f)) => {
3164 col.lte(Expr::col(Alias::new(&f.field)))
3165 }
3166 (FilterOperator::Eq, FilterValue::OuterRef(outer)) => {
3168 col.eq(Expr::col(parse_column_reference(&outer.field)))
3171 }
3172 (FilterOperator::Ne, FilterValue::OuterRef(outer)) => {
3173 col.ne(Expr::col(parse_column_reference(&outer.field)))
3174 }
3175 (FilterOperator::Gt, FilterValue::OuterRef(outer)) => {
3176 col.gt(Expr::col(parse_column_reference(&outer.field)))
3177 }
3178 (FilterOperator::Gte, FilterValue::OuterRef(outer)) => {
3179 col.gte(Expr::col(parse_column_reference(&outer.field)))
3180 }
3181 (FilterOperator::Lt, FilterValue::OuterRef(outer)) => {
3182 col.lt(Expr::col(parse_column_reference(&outer.field)))
3183 }
3184 (FilterOperator::Lte, FilterValue::OuterRef(outer)) => {
3185 col.lte(Expr::col(parse_column_reference(&outer.field)))
3186 }
3187 (FilterOperator::Eq, FilterValue::Expression(expr)) => {
3189 col.eq(Self::expression_to_query_expr(expr))
3190 }
3191 (FilterOperator::Ne, FilterValue::Expression(expr)) => {
3192 col.ne(Self::expression_to_query_expr(expr))
3193 }
3194 (FilterOperator::Gt, FilterValue::Expression(expr)) => {
3195 col.gt(Self::expression_to_query_expr(expr))
3196 }
3197 (FilterOperator::Gte, FilterValue::Expression(expr)) => {
3198 col.gte(Self::expression_to_query_expr(expr))
3199 }
3200 (FilterOperator::Lt, FilterValue::Expression(expr)) => {
3201 col.lt(Self::expression_to_query_expr(expr))
3202 }
3203 (FilterOperator::Lte, FilterValue::Expression(expr)) => {
3204 col.lte(Self::expression_to_query_expr(expr))
3205 }
3206 (FilterOperator::Eq, FilterValue::Null) => col.is_null(),
3208 (FilterOperator::Ne, FilterValue::Null) => col.is_not_null(),
3209 (FilterOperator::IExact, FilterValue::String(s)) => {
3210 Self::like_expr(filter, s, LikePattern::Exact, true)
3211 }
3212 (FilterOperator::IExact, v) => col.eq(Self::filter_value_to_sea_value(v)),
3213 (FilterOperator::Eq, v) => col.eq(Self::filter_value_to_sea_value(v)),
3215 (FilterOperator::Ne, v) => col.ne(Self::filter_value_to_sea_value(v)),
3216 (FilterOperator::Gt, v) => col.gt(Self::filter_value_to_sea_value(v)),
3217 (FilterOperator::Gte, v) => col.gte(Self::filter_value_to_sea_value(v)),
3218 (FilterOperator::Lt, v) => col.lt(Self::filter_value_to_sea_value(v)),
3219 (FilterOperator::Lte, v) => col.lte(Self::filter_value_to_sea_value(v)),
3220 (FilterOperator::In, FilterValue::String(s)) => {
3221 let values = parse_membership_string(s);
3222 col.is_in(values)
3223 }
3224 (FilterOperator::In, FilterValue::Array(arr)) => {
3225 col.is_in(arr.iter().map(|s| s.as_str()).collect::<Vec<_>>())
3226 }
3227 (FilterOperator::In, FilterValue::List(values)) => col.is_in(
3228 values
3229 .iter()
3230 .map(Self::filter_value_to_sea_value)
3231 .collect::<Vec<_>>(),
3232 ),
3233 (FilterOperator::NotIn, FilterValue::String(s)) => {
3234 let values = parse_membership_string(s);
3235 col.is_not_in(values)
3236 }
3237 (FilterOperator::NotIn, FilterValue::Array(arr)) => {
3238 col.is_not_in(arr.iter().map(|s| s.as_str()).collect::<Vec<_>>())
3239 }
3240 (FilterOperator::NotIn, FilterValue::List(values)) => col.is_not_in(
3241 values
3242 .iter()
3243 .map(Self::filter_value_to_sea_value)
3244 .collect::<Vec<_>>(),
3245 ),
3246 (FilterOperator::Contains, FilterValue::String(s)) => {
3247 Self::like_expr(filter, s, LikePattern::Contains, false)
3248 }
3249 (FilterOperator::IContains, FilterValue::String(s)) => {
3250 Self::like_expr(filter, s, LikePattern::Contains, true)
3251 }
3252 (FilterOperator::Contains, FilterValue::Array(arr)) => {
3253 let value = arr.first().map(String::as_str).unwrap_or("");
3254 Self::like_expr(filter, value, LikePattern::Contains, false)
3255 }
3256 (FilterOperator::StartsWith, FilterValue::String(s)) => {
3257 Self::like_expr(filter, s, LikePattern::StartsWith, false)
3258 }
3259 (FilterOperator::IStartsWith, FilterValue::String(s)) => {
3260 Self::like_expr(filter, s, LikePattern::StartsWith, true)
3261 }
3262 (FilterOperator::StartsWith, FilterValue::Array(arr)) => {
3263 let value = arr.first().map(String::as_str).unwrap_or("");
3264 Self::like_expr(filter, value, LikePattern::StartsWith, false)
3265 }
3266 (FilterOperator::EndsWith, FilterValue::String(s)) => {
3267 Self::like_expr(filter, s, LikePattern::EndsWith, false)
3268 }
3269 (FilterOperator::IEndsWith, FilterValue::String(s)) => {
3270 Self::like_expr(filter, s, LikePattern::EndsWith, true)
3271 }
3272 (FilterOperator::EndsWith, FilterValue::Array(arr)) => {
3273 let value = arr.first().map(String::as_str).unwrap_or("");
3274 Self::like_expr(filter, value, LikePattern::EndsWith, false)
3275 }
3276 (FilterOperator::Regex, FilterValue::String(pattern)) => Expr::cust_with_values(
3277 format!("{} ~ ?", Self::filter_lhs_sql(filter)),
3278 [pattern.clone()],
3279 )
3280 .into_simple_expr(),
3281 (FilterOperator::IRegex, FilterValue::String(pattern)) => Expr::cust_with_values(
3282 format!("{} ~* ?", Self::filter_lhs_sql(filter)),
3283 [pattern.clone()],
3284 )
3285 .into_simple_expr(),
3286 (FilterOperator::Range, FilterValue::Range(start, end)) => Expr::cust_with_values(
3287 format!("{} BETWEEN ? AND ?", Self::filter_lhs_sql(filter)),
3288 [
3289 Self::filter_value_to_sea_value(start),
3290 Self::filter_value_to_sea_value(end),
3291 ],
3292 )
3293 .into_simple_expr(),
3294 (FilterOperator::Contains, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3296 col.like(format!("%{}%", i))
3297 }
3298 (FilterOperator::IContains, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3299 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}%", i)))
3300 }
3301 (FilterOperator::Contains, FilterValue::Float(f)) => col.like(format!("%{}%", f)),
3302 (FilterOperator::IContains, FilterValue::Float(f)) => {
3303 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}%", f)))
3304 }
3305 (FilterOperator::Contains, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3306 col.like(format!("%{}%", b))
3307 }
3308 (FilterOperator::IContains, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3309 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}%", b)))
3310 }
3311 (FilterOperator::Contains, FilterValue::Null) => col.like("%"),
3312 (FilterOperator::IContains, FilterValue::Null) => {
3313 col.binary(BinOper::ILike, SimpleExpr::from("%"))
3314 }
3315 (FilterOperator::StartsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3316 col.like(format!("{}%", i))
3317 }
3318 (FilterOperator::IStartsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3319 col.binary(BinOper::ILike, SimpleExpr::from(format!("{}%", i)))
3320 }
3321 (FilterOperator::StartsWith, FilterValue::Float(f)) => col.like(format!("{}%", f)),
3322 (FilterOperator::IStartsWith, FilterValue::Float(f)) => {
3323 col.binary(BinOper::ILike, SimpleExpr::from(format!("{}%", f)))
3324 }
3325 (FilterOperator::StartsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3326 col.like(format!("{}%", b))
3327 }
3328 (FilterOperator::IStartsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3329 col.binary(BinOper::ILike, SimpleExpr::from(format!("{}%", b)))
3330 }
3331 (FilterOperator::StartsWith, FilterValue::Null) => col.like("%"),
3332 (FilterOperator::IStartsWith, FilterValue::Null) => {
3333 col.binary(BinOper::ILike, SimpleExpr::from("%"))
3334 }
3335 (FilterOperator::EndsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3336 col.like(format!("%{}", i))
3337 }
3338 (FilterOperator::IEndsWith, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3339 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}", i)))
3340 }
3341 (FilterOperator::EndsWith, FilterValue::Float(f)) => col.like(format!("%{}", f)),
3342 (FilterOperator::IEndsWith, FilterValue::Float(f)) => {
3343 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}", f)))
3344 }
3345 (FilterOperator::EndsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3346 col.like(format!("%{}", b))
3347 }
3348 (FilterOperator::IEndsWith, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3349 col.binary(BinOper::ILike, SimpleExpr::from(format!("%{}", b)))
3350 }
3351 (FilterOperator::EndsWith, FilterValue::Null) => col.like("%"),
3352 (FilterOperator::IEndsWith, FilterValue::Null) => {
3353 col.binary(BinOper::ILike, SimpleExpr::from("%"))
3354 }
3355 (FilterOperator::In, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3357 col.is_in(vec![*i])
3358 }
3359 (FilterOperator::In, FilterValue::Float(f)) => col.is_in(vec![*f]),
3360 (FilterOperator::In, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3361 col.is_in(vec![*b])
3362 }
3363 (FilterOperator::In, FilterValue::Null) => {
3364 col.is_in(vec![reinhardt_query::value::Value::Int(None)])
3365 }
3366 (FilterOperator::NotIn, FilterValue::Integer(i) | FilterValue::Int(i)) => {
3367 col.is_not_in(vec![*i])
3368 }
3369 (FilterOperator::NotIn, FilterValue::Float(f)) => col.is_not_in(vec![*f]),
3370 (FilterOperator::NotIn, FilterValue::Boolean(b) | FilterValue::Bool(b)) => {
3371 col.is_not_in(vec![*b])
3372 }
3373 (FilterOperator::NotIn, FilterValue::Null) => {
3374 col.is_not_in(vec![reinhardt_query::value::Value::Int(None)])
3375 }
3376 (FilterOperator::IsNull, _) => col.is_null(),
3378 (FilterOperator::IsNotNull, _) => col.is_not_null(),
3379 (FilterOperator::ArrayContains, FilterValue::Array(arr)) => {
3381 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
3383 Expr::cust_with_values(
3384 format!(
3385 "{} @> ARRAY[{}]",
3386 Self::filter_lhs_sql(filter),
3387 placeholders
3388 ),
3389 arr.iter().cloned(),
3390 )
3391 .into_simple_expr()
3392 }
3393 (FilterOperator::ArrayContainedBy, FilterValue::Array(arr)) => {
3394 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
3396 Expr::cust_with_values(
3397 format!(
3398 "{} <@ ARRAY[{}]",
3399 Self::filter_lhs_sql(filter),
3400 placeholders
3401 ),
3402 arr.iter().cloned(),
3403 )
3404 .into_simple_expr()
3405 }
3406 (FilterOperator::ArrayOverlap, FilterValue::Array(arr)) => {
3407 let placeholders = arr.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
3409 Expr::cust_with_values(
3410 format!(
3411 "{} && ARRAY[{}]",
3412 Self::filter_lhs_sql(filter),
3413 placeholders
3414 ),
3415 arr.iter().cloned(),
3416 )
3417 .into_simple_expr()
3418 }
3419 (FilterOperator::FullTextMatch, FilterValue::String(query)) => {
3421 Expr::cust_with_values(
3423 format!(
3424 "{} @@ plainto_tsquery('english', ?)",
3425 Self::filter_lhs_sql(filter)
3426 ),
3427 [query.clone()],
3428 )
3429 .into_simple_expr()
3430 }
3431 (FilterOperator::JsonbContains, FilterValue::String(json)) => {
3433 Expr::cust_with_values(
3435 format!("{} @> ?::jsonb", Self::filter_lhs_sql(filter)),
3436 [json.clone()],
3437 )
3438 .into_simple_expr()
3439 }
3440 (FilterOperator::JsonbContainedBy, FilterValue::String(json)) => {
3441 Expr::cust_with_values(
3443 format!("{} <@ ?::jsonb", Self::filter_lhs_sql(filter)),
3444 [json.clone()],
3445 )
3446 .into_simple_expr()
3447 }
3448 (FilterOperator::JsonbKeyExists, FilterValue::String(key)) => {
3449 Expr::cust(Self::filter_lhs_sql(filter))
3451 .into_simple_expr()
3452 .binary(
3453 BinOper::PgOperator(PgBinOper::JsonContainsKey),
3454 SimpleExpr::from(key.clone()),
3455 )
3456 }
3457 (FilterOperator::JsonbAnyKeyExists, FilterValue::Array(keys)) => {
3458 let placeholders = keys.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
3460 let array_expr = Expr::cust_with_values(
3461 format!("array[{}]", placeholders),
3462 keys.iter().cloned(),
3463 )
3464 .into_simple_expr();
3465 Expr::cust(Self::filter_lhs_sql(filter))
3466 .into_simple_expr()
3467 .binary(
3468 BinOper::PgOperator(PgBinOper::JsonContainsAnyKey),
3469 array_expr,
3470 )
3471 }
3472 (FilterOperator::JsonbAllKeysExist, FilterValue::Array(keys)) => {
3473 let placeholders = keys.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
3475 let array_expr = Expr::cust_with_values(
3476 format!("array[{}]", placeholders),
3477 keys.iter().cloned(),
3478 )
3479 .into_simple_expr();
3480 Expr::cust(Self::filter_lhs_sql(filter))
3481 .into_simple_expr()
3482 .binary(
3483 BinOper::PgOperator(PgBinOper::JsonContainsAllKeys),
3484 array_expr,
3485 )
3486 }
3487 (FilterOperator::JsonbPathExists, FilterValue::String(path)) => {
3488 Expr::cust_with_values(
3490 format!("{} @? ?", Self::filter_lhs_sql(filter)),
3491 [path.clone()],
3492 )
3493 .into_simple_expr()
3494 }
3495 (FilterOperator::RangeContains, v) => {
3497 Expr::cust_with_values(
3499 format!("{} @> ?", Self::filter_lhs_sql(filter)),
3500 [Self::filter_value_to_sea_value(v)],
3501 )
3502 .into_simple_expr()
3503 }
3504 (FilterOperator::RangeContainedBy, FilterValue::String(range)) => {
3505 Expr::cust_with_values(
3507 format!("{} <@ ?", Self::filter_lhs_sql(filter)),
3508 [range.clone()],
3509 )
3510 .into_simple_expr()
3511 }
3512 (FilterOperator::RangeOverlaps, FilterValue::String(range)) => {
3513 Expr::cust_with_values(
3515 format!("{} && ?", Self::filter_lhs_sql(filter)),
3516 [range.clone()],
3517 )
3518 .into_simple_expr()
3519 }
3520 _ => {
3522 col.eq(Self::filter_value_to_sea_value(&filter.value))
3524 }
3525 };
3526
3527 cond = cond.add(expr);
3528 added = true;
3529 }
3530
3531 for filter_condition in &self.filter_conditions {
3532 if let Some(expr) = Self::build_filter_condition(filter_condition, 0)? {
3533 cond = cond.add(expr);
3534 added = true;
3535 }
3536 }
3537
3538 for subq_cond in &self.subquery_conditions {
3540 let expr = match subq_cond {
3541 SubqueryCondition::In { field, subquery } => {
3542 Expr::cust(format!("{} IN {}", quote_identifier(field), subquery))
3544 .into_simple_expr()
3545 }
3546 SubqueryCondition::NotIn { field, subquery } => {
3547 Expr::cust(format!("{} NOT IN {}", quote_identifier(field), subquery))
3549 .into_simple_expr()
3550 }
3551 SubqueryCondition::Exists { subquery, .. } => {
3552 Expr::cust(format!("EXISTS {}", subquery)).into_simple_expr()
3554 }
3555 SubqueryCondition::NotExists { subquery, .. } => {
3556 Expr::cust(format!("NOT EXISTS {}", subquery)).into_simple_expr()
3558 }
3559 };
3560
3561 cond = cond.add(expr);
3562 added = true;
3563 }
3564
3565 Ok(added.then_some(cond))
3566 }
3567
3568 fn build_filter_condition(
3569 filter_condition: &FilterCondition,
3570 depth: usize,
3571 ) -> reinhardt_core::exception::Result<Option<Condition>> {
3572 if depth >= MAX_FILTER_CONDITION_DEPTH {
3573 return Err(reinhardt_core::exception::Error::Validation(format!(
3574 "Filter condition exceeded maximum depth of {} levels",
3575 MAX_FILTER_CONDITION_DEPTH
3576 )));
3577 }
3578
3579 match filter_condition {
3580 FilterCondition::Single(filter) => {
3581 let mut queryset = Self::new();
3582 queryset.filters.push(filter.clone());
3583 queryset.build_where_condition()
3584 }
3585 FilterCondition::And(conditions) => {
3586 let mut condition = Condition::all();
3587 let mut added = false;
3588 for item in conditions {
3589 if let Some(sub_condition) = Self::build_filter_condition(item, depth + 1)? {
3590 condition = condition.add(sub_condition);
3591 added = true;
3592 }
3593 }
3594 if !added {
3595 condition = condition.add(Expr::cust("TRUE").into_simple_expr());
3596 }
3597 Ok(Some(condition))
3598 }
3599 FilterCondition::Or(conditions) => {
3600 if conditions.is_empty() {
3601 return Ok(Some(Self::false_condition()));
3602 }
3603 let mut condition = Condition::any();
3604 let mut added = false;
3605 for item in conditions {
3606 if let Some(sub_condition) = Self::build_filter_condition(item, depth + 1)? {
3607 condition = condition.add(sub_condition);
3608 added = true;
3609 }
3610 }
3611 Ok(added.then_some(condition))
3612 }
3613 FilterCondition::Not(condition) => {
3614 Ok(Self::build_filter_condition(condition, depth + 1)?
3615 .map(|condition| condition.not()))
3616 }
3617 }
3618 }
3619
3620 fn false_condition() -> Condition {
3621 Condition::all().add(Expr::cust("FALSE").into_simple_expr())
3622 }
3623
3624 fn build_where_condition_or_false(&self) -> Option<Condition> {
3625 match self.build_where_condition() {
3626 Ok(condition) => condition,
3627 Err(_) => Some(Self::false_condition()),
3628 }
3629 }
3630
3631 fn expression_to_query_expr(expr: &super::annotation::Expression) -> Expr {
3638 use crate::orm::annotation::Expression;
3639
3640 match expr {
3641 Expression::Add(left, right) => {
3642 let left_sql = Self::annotation_value_to_sql(left);
3643 let right_sql = Self::annotation_value_to_sql(right);
3644 Expr::cust(format!("({} + {})", left_sql, right_sql))
3645 }
3646 Expression::Subtract(left, right) => {
3647 let left_sql = Self::annotation_value_to_sql(left);
3648 let right_sql = Self::annotation_value_to_sql(right);
3649 Expr::cust(format!("({} - {})", left_sql, right_sql))
3650 }
3651 Expression::Multiply(left, right) => {
3652 let left_sql = Self::annotation_value_to_sql(left);
3653 let right_sql = Self::annotation_value_to_sql(right);
3654 Expr::cust(format!("({} * {})", left_sql, right_sql))
3655 }
3656 Expression::Divide(left, right) => {
3657 let left_sql = Self::annotation_value_to_sql(left);
3658 let right_sql = Self::annotation_value_to_sql(right);
3659 Expr::cust(format!("({} / {})", left_sql, right_sql))
3660 }
3661 Expression::Case { whens, default } => {
3662 let mut case_sql = "CASE".to_string();
3663 for when in whens.iter() {
3664 case_sql.push_str(&format!(" {}", when.to_sql()));
3666 }
3667 if let Some(default_val) = default {
3668 case_sql.push_str(&format!(
3669 " ELSE {}",
3670 Self::annotation_value_to_sql(default_val)
3671 ));
3672 }
3673 case_sql.push_str(" END");
3674 Expr::cust(case_sql)
3675 }
3676 Expression::Coalesce(values) => {
3677 let value_sqls = values
3678 .iter()
3679 .map(|v| Self::annotation_value_to_sql(v))
3680 .collect::<Vec<_>>()
3681 .join(", ");
3682 Expr::cust(format!("COALESCE({})", value_sqls))
3683 }
3684 }
3685 }
3686
3687 fn annotation_value_to_sql(value: &super::annotation::AnnotationValue) -> String {
3692 value.to_sql()
3693 }
3694
3695 fn filter_lhs_expr(filter: &Filter) -> Expr {
3696 filter_lhs_expr(filter)
3697 }
3698
3699 fn filter_lhs_sql(filter: &Filter) -> String {
3700 filter_lhs_sql(filter)
3701 }
3702
3703 fn like_expr(
3704 filter: &Filter,
3705 value: &str,
3706 pattern: LikePattern,
3707 case_insensitive: bool,
3708 ) -> SimpleExpr {
3709 let operator = if case_insensitive { "ILIKE" } else { "LIKE" };
3710 Expr::cust_with_values(
3711 format!(
3712 "{} {} ? ESCAPE '\\'",
3713 Self::filter_lhs_sql(filter),
3714 operator
3715 ),
3716 [pattern.apply(value)],
3717 )
3718 .into_simple_expr()
3719 }
3720
3721 pub(crate) fn filter_value_to_sea_value(v: &FilterValue) -> reinhardt_query::value::Value {
3722 match v {
3723 FilterValue::String(s) => s.clone().into(),
3724 FilterValue::Timestamp(value) => (*value).into(),
3725 FilterValue::Date(value) => (*value).into(),
3726 FilterValue::Time(value) => (*value).into(),
3727 FilterValue::NaiveDateTime(value) => (*value).into(),
3728 FilterValue::Decimal(value) => (*value).into(),
3729 FilterValue::Uuid(value) => (*value).into(),
3730 FilterValue::Integer(i) | FilterValue::Int(i) => (*i).into(),
3731 FilterValue::Float(f) => (*f).into(),
3732 FilterValue::Boolean(b) | FilterValue::Bool(b) => (*b).into(),
3733 FilterValue::Null => reinhardt_query::value::Value::Int(None),
3734 FilterValue::Array(arr) => arr.join(",").into(),
3735 FilterValue::List(values) => values
3736 .iter()
3737 .map(Self::value_to_string)
3738 .collect::<Vec<_>>()
3739 .join(",")
3740 .into(),
3741 FilterValue::Range(start, end) => format!(
3742 "{},{}",
3743 Self::value_to_string(start),
3744 Self::value_to_string(end)
3745 )
3746 .into(),
3747 FilterValue::FieldRef(f) => f.field.clone().into(),
3750 FilterValue::Expression(expr) => expr.to_sql().into(),
3751 FilterValue::OuterRef(outer_ref) => outer_ref.field.clone().into(),
3752 }
3753 }
3754
3755 #[allow(dead_code)]
3758 fn value_to_string(v: &FilterValue) -> String {
3759 match v {
3760 FilterValue::String(s) => s.clone(),
3761 FilterValue::Timestamp(value) => value.to_rfc3339(),
3762 FilterValue::Date(value) => value.to_string(),
3763 FilterValue::Time(value) => value.to_string(),
3764 FilterValue::NaiveDateTime(value) => value.to_string(),
3765 FilterValue::Decimal(value) => value.to_string(),
3766 FilterValue::Uuid(value) => value.to_string(),
3767 FilterValue::Integer(i) | FilterValue::Int(i) => i.to_string(),
3768 FilterValue::Float(f) => f.to_string(),
3769 FilterValue::Boolean(b) | FilterValue::Bool(b) => b.to_string(),
3770 FilterValue::Null => String::new(),
3771 FilterValue::Array(arr) => arr.join(","),
3772 FilterValue::List(values) => values
3773 .iter()
3774 .map(Self::value_to_string)
3775 .collect::<Vec<_>>()
3776 .join(","),
3777 FilterValue::Range(start, end) => {
3778 format!(
3779 "{},{}",
3780 Self::value_to_string(start),
3781 Self::value_to_string(end)
3782 )
3783 }
3784 FilterValue::FieldRef(f) => f.field.clone(),
3785 FilterValue::Expression(expr) => expr.to_sql(),
3786 FilterValue::OuterRef(outer_ref) => outer_ref.field.clone(),
3787 }
3788 }
3789
3790 #[allow(dead_code)]
3793 fn value_to_array(v: &FilterValue) -> Vec<reinhardt_query::value::Value> {
3794 match v {
3795 FilterValue::String(s) => parse_membership_string(s),
3796 FilterValue::Timestamp(value) => vec![(*value).into()],
3797 FilterValue::Date(value) => vec![(*value).into()],
3798 FilterValue::Time(value) => vec![(*value).into()],
3799 FilterValue::NaiveDateTime(value) => vec![(*value).into()],
3800 FilterValue::Decimal(value) => vec![(*value).into()],
3801 FilterValue::Uuid(value) => vec![(*value).into()],
3802 FilterValue::Integer(i) | FilterValue::Int(i) => vec![(*i).into()],
3803 FilterValue::Float(f) => vec![(*f).into()],
3804 FilterValue::Boolean(b) | FilterValue::Bool(b) => vec![(*b).into()],
3805 FilterValue::Null => vec![reinhardt_query::value::Value::Int(None)],
3806 FilterValue::Array(arr) => arr.iter().map(|s| s.clone().into()).collect(),
3807 FilterValue::List(values) => {
3808 values.iter().map(Self::filter_value_to_sea_value).collect()
3809 }
3810 FilterValue::Range(start, end) => vec![
3811 Self::filter_value_to_sea_value(start),
3812 Self::filter_value_to_sea_value(end),
3813 ],
3814 FilterValue::FieldRef(f) => vec![f.field.clone().into()],
3815 FilterValue::Expression(expr) => vec![expr.to_sql().into()],
3816 FilterValue::OuterRef(outer) => vec![outer.field.clone().into()],
3817 }
3818 }
3819
3820 #[allow(dead_code)]
3832 fn build_where_clause(&self) -> (String, Vec<String>) {
3833 if !self.has_where_predicates() {
3834 return (String::new(), Vec::new());
3835 }
3836
3837 let mut stmt = Query::select();
3839 stmt.from(Alias::new("dummy"));
3840
3841 if let Some(cond) = self.build_where_condition_or_false() {
3842 stmt.cond_where(cond);
3843 }
3844
3845 use reinhardt_query::prelude::PostgresQueryBuilder;
3847 let sql = stmt.to_string(PostgresQueryBuilder);
3848
3849 let where_clause = if let Some(idx) = sql.find(" WHERE ") {
3851 sql[idx..].to_string()
3852 } else {
3853 String::new()
3854 };
3855
3856 (where_clause, Vec::new())
3857 }
3858
3859 pub fn select_related(mut self, fields: &[&str]) -> Self {
3910 self.select_related_fields = fields.iter().map(|s| s.to_string()).collect();
3911 self
3912 }
3913
3914 pub fn select_related_query(&self) -> SelectStatement {
3952 let table_name = T::table_name();
3953 let mut stmt = Query::select();
3954
3955 if let Some(ref alias) = self.from_alias {
3957 stmt.from_as(Alias::new(table_name), Alias::new(alias));
3958 } else {
3959 stmt.from(Alias::new(table_name));
3960 }
3961
3962 if self.distinct_enabled {
3964 stmt.distinct();
3965 }
3966
3967 stmt.column(ColumnRef::table_asterisk(Alias::new(table_name)));
3969
3970 for related_field in &self.select_related_fields {
3972 let fk_field = Alias::new(format!("{}_id", related_field));
3975 let related_table = Alias::new(format!("{}s", related_field));
3976 let related_alias = Alias::new(related_field);
3977
3978 stmt.left_join(
3980 related_table,
3981 Expr::col((Alias::new(table_name), fk_field))
3982 .equals((related_alias.clone(), Alias::new("id"))),
3983 );
3984
3985 stmt.column(ColumnRef::table_asterisk(related_alias));
3987 }
3988
3989 for join in &self.joins {
3991 if join.on_condition.is_empty() {
3992 if let Some(ref alias) = join.target_alias {
3994 stmt.cross_join((Alias::new(&join.target_table), Alias::new(alias)));
3995 } else {
3996 stmt.cross_join(Alias::new(&join.target_table));
3997 }
3998 } else {
3999 let sea_join_type = match join.join_type {
4001 super::sqlalchemy_query::JoinType::Inner => SeaJoinType::InnerJoin,
4002 super::sqlalchemy_query::JoinType::Left => SeaJoinType::LeftJoin,
4003 super::sqlalchemy_query::JoinType::Right => SeaJoinType::RightJoin,
4004 super::sqlalchemy_query::JoinType::Full => SeaJoinType::FullOuterJoin,
4005 };
4006
4007 if let Some(ref alias) = join.target_alias {
4009 stmt.join(
4010 sea_join_type,
4011 (Alias::new(&join.target_table), Alias::new(alias)),
4012 Expr::cust(join.on_condition.clone()),
4013 );
4014 } else {
4015 stmt.join(
4016 sea_join_type,
4017 Alias::new(&join.target_table),
4018 Expr::cust(join.on_condition.clone()),
4019 );
4020 }
4021 }
4022 }
4023
4024 if let Some(cond) = self.build_where_condition_or_false() {
4026 stmt.cond_where(cond);
4027 }
4028
4029 for group_field in &self.group_by_fields {
4031 let col_ref = parse_column_reference(group_field);
4032 stmt.group_by_col(col_ref);
4033 }
4034
4035 for having_cond in &self.having_conditions {
4037 match having_cond {
4038 HavingCondition::AggregateCompare {
4039 func,
4040 field,
4041 operator,
4042 value,
4043 } => {
4044 let agg_expr = match func {
4046 AggregateFunc::Avg => {
4047 Func::avg(Expr::col(Alias::new(field)).into_simple_expr())
4048 }
4049 AggregateFunc::Count => {
4050 if field == "*" {
4051 Func::count(Expr::asterisk().into_simple_expr())
4052 } else {
4053 Func::count(Expr::col(Alias::new(field)).into_simple_expr())
4054 }
4055 }
4056 AggregateFunc::Sum => {
4057 Func::sum(Expr::col(Alias::new(field)).into_simple_expr())
4058 }
4059 AggregateFunc::Min => {
4060 Func::min(Expr::col(Alias::new(field)).into_simple_expr())
4061 }
4062 AggregateFunc::Max => {
4063 Func::max(Expr::col(Alias::new(field)).into_simple_expr())
4064 }
4065 };
4066
4067 let having_expr = match operator {
4069 ComparisonOp::Eq => match value {
4070 AggregateValue::Int(v) => agg_expr.eq(*v),
4071 AggregateValue::Float(v) => agg_expr.eq(*v),
4072 },
4073 ComparisonOp::Ne => match value {
4074 AggregateValue::Int(v) => agg_expr.ne(*v),
4075 AggregateValue::Float(v) => agg_expr.ne(*v),
4076 },
4077 ComparisonOp::Gt => match value {
4078 AggregateValue::Int(v) => agg_expr.gt(*v),
4079 AggregateValue::Float(v) => agg_expr.gt(*v),
4080 },
4081 ComparisonOp::Gte => match value {
4082 AggregateValue::Int(v) => agg_expr.gte(*v),
4083 AggregateValue::Float(v) => agg_expr.gte(*v),
4084 },
4085 ComparisonOp::Lt => match value {
4086 AggregateValue::Int(v) => agg_expr.lt(*v),
4087 AggregateValue::Float(v) => agg_expr.lt(*v),
4088 },
4089 ComparisonOp::Lte => match value {
4090 AggregateValue::Int(v) => agg_expr.lte(*v),
4091 AggregateValue::Float(v) => agg_expr.lte(*v),
4092 },
4093 };
4094
4095 stmt.and_having(having_expr);
4096 }
4097 }
4098 }
4099
4100 for order_field in &self.order_by_fields {
4102 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
4103 (stripped, true)
4104 } else {
4105 (order_field.as_str(), false)
4106 };
4107
4108 let col_ref = parse_column_reference(field);
4109 let expr = Expr::col(col_ref);
4110 if is_desc {
4111 stmt.order_by_expr(expr, Order::Desc);
4112 } else {
4113 stmt.order_by_expr(expr, Order::Asc);
4114 }
4115 }
4116
4117 if let Some(limit) = self.limit {
4119 stmt.limit(limit as u64);
4120 }
4121 if let Some(offset) = self.offset {
4122 stmt.offset(offset as u64);
4123 }
4124
4125 stmt.to_owned()
4126 }
4127
4128 pub fn prefetch_related(mut self, fields: &[&str]) -> Self {
4182 self.prefetch_related_fields = fields.iter().map(|s| s.to_string()).collect();
4183 self
4184 }
4185
4186 pub fn prefetch_related_queries(&self, pk_values: &[i64]) -> Vec<(String, SelectStatement)> {
4229 if pk_values.is_empty() {
4230 return Vec::new();
4231 }
4232
4233 let mut queries = Vec::new();
4234
4235 for related_field in &self.prefetch_related_fields {
4236 let is_m2m = self.is_many_to_many_relation(related_field);
4239
4240 let stmt = if is_m2m {
4241 self.prefetch_many_to_many_query(related_field, pk_values)
4242 } else {
4243 self.prefetch_one_to_many_query(related_field, pk_values)
4244 };
4245
4246 queries.push((related_field.clone(), stmt));
4247 }
4248
4249 queries
4250 }
4251
4252 fn is_many_to_many_relation(&self, related_field: &str) -> bool {
4257 let relations = T::relationship_metadata();
4259
4260 relations
4262 .iter()
4263 .find(|rel| rel.name == related_field)
4264 .map(|rel| rel.relationship_type == super::relationship::RelationshipType::ManyToMany)
4265 .unwrap_or(false)
4266 }
4267
4268 fn prefetch_one_to_many_query(
4272 &self,
4273 related_field: &str,
4274 pk_values: &[i64],
4275 ) -> SelectStatement {
4276 let table_name = T::table_name();
4277 let related_table = Alias::new(format!("{}s", related_field));
4278 let fk_field = Alias::new(format!("{}_id", table_name.trim_end_matches('s')));
4279
4280 let mut stmt = Query::select();
4281 stmt.from(related_table).column(ColumnRef::Asterisk);
4282
4283 let values: Vec<reinhardt_query::value::Value> =
4285 pk_values.iter().map(|&id| id.into()).collect();
4286 stmt.and_where(Expr::col(fk_field).is_in(values));
4287
4288 stmt.to_owned()
4289 }
4290
4291 fn prefetch_many_to_many_query(
4297 &self,
4298 related_field: &str,
4299 pk_values: &[i64],
4300 ) -> SelectStatement {
4301 let table_name = T::table_name();
4302 let junction_table = Alias::new(format!(
4310 "{}_{}",
4311 table_name.to_lowercase(),
4312 to_snake_case(related_field)
4313 ));
4314
4315 let rel_info = T::relationship_metadata().into_iter().find(|r| {
4317 r.name == related_field
4318 && r.relationship_type == super::relationship::RelationshipType::ManyToMany
4319 });
4320
4321 let related_table = if let Some(ref info) = rel_info {
4323 Alias::new(to_snake_case(&info.related_model).to_lowercase())
4324 } else {
4325 Alias::new(format!("{}s", related_field))
4327 };
4328
4329 let table_name_lower = table_name.to_lowercase();
4331 let (junction_main_fk, junction_related_fk) = if let Some(ref info) = rel_info {
4332 let source_fk = if let Some(ref sf) = info.source_field {
4333 sf.clone()
4334 } else {
4335 let related_lower = to_snake_case(&info.related_model).to_lowercase();
4337 if table_name_lower == related_lower {
4338 format!("from_{}_id", table_name_lower)
4339 } else {
4340 format!("{}_id", table_name_lower)
4341 }
4342 };
4343
4344 let target_fk = if let Some(ref tf) = info.target_field {
4345 tf.clone()
4346 } else {
4347 let related_lower = to_snake_case(&info.related_model).to_lowercase();
4348 if table_name_lower == related_lower {
4349 format!("to_{}_id", table_name_lower)
4350 } else {
4351 format!("{}_id", to_snake_case(related_field))
4352 }
4353 };
4354
4355 (Alias::new(source_fk), Alias::new(target_fk))
4356 } else {
4357 let source_fk = format!("{}_id", table_name_lower);
4359 let target_fk = format!("{}_id", to_snake_case(related_field));
4360 (Alias::new(source_fk), Alias::new(target_fk))
4361 };
4362
4363 let mut stmt = Query::select();
4364 stmt.from(related_table.clone())
4365 .column(ColumnRef::table_asterisk(related_table.clone()))
4366 .column((junction_table.clone(), junction_main_fk.clone()))
4367 .inner_join(
4368 junction_table.clone(),
4369 Expr::col((related_table.clone(), Alias::new("id")))
4370 .equals((junction_table.clone(), junction_related_fk)),
4371 );
4372
4373 let values: Vec<reinhardt_query::value::Value> =
4375 pk_values.iter().map(|&id| id.into()).collect();
4376 stmt.and_where(Expr::col((junction_table, junction_main_fk)).is_in(values));
4377
4378 stmt.to_owned()
4379 }
4380
4381 fn build_select_statement(&self) -> reinhardt_core::exception::Result<SelectStatement> {
4382 let has_structural_clauses = !self.select_related_fields.is_empty()
4383 || !self.joins.is_empty()
4384 || !self.group_by_fields.is_empty()
4385 || !self.having_conditions.is_empty();
4386 let mut stmt = if has_structural_clauses {
4387 self.select_related_query()
4388 } else {
4389 let mut stmt = Query::select();
4390 if let Some(ref alias) = self.from_alias {
4391 stmt.from_as(Alias::new(T::table_name()), Alias::new(alias));
4392 } else {
4393 stmt.from(Alias::new(T::table_name()));
4394 }
4395
4396 if self.distinct_enabled {
4397 stmt.distinct();
4398 }
4399
4400 if let Some(ref fields) = self.selected_fields {
4401 for field in fields {
4402 if field.contains('(') && field.contains(')') {
4403 stmt.expr(Expr::cust(field.clone()));
4404 } else {
4405 stmt.column(parse_column_reference(field));
4406 }
4407 }
4408 } else if !self.deferred_fields.is_empty() {
4409 for field in T::field_metadata() {
4410 if !self.deferred_fields.contains(&field.name) {
4411 stmt.column(parse_column_reference(&field.name));
4412 }
4413 }
4414 } else {
4415 stmt.column(ColumnRef::Asterisk);
4416 }
4417
4418 if let Some(cond) = self.build_where_condition()? {
4419 stmt.cond_where(cond);
4420 }
4421
4422 for order_field in &self.order_by_fields {
4423 let (field, order) = if let Some(field) = order_field.strip_prefix('-') {
4424 (field, Order::Desc)
4425 } else {
4426 (order_field.as_str(), Order::Asc)
4427 };
4428 stmt.order_by_expr(Expr::col(parse_column_reference(field)), order);
4429 }
4430
4431 if let Some(limit) = self.limit {
4432 stmt.limit(limit as u64);
4433 }
4434 if let Some(offset) = self.offset {
4435 stmt.offset(offset as u64);
4436 }
4437
4438 stmt.to_owned()
4439 };
4440
4441 if let Some(subquery_sql) = &self.from_subquery_sql {
4442 let inner_sql = subquery_sql
4443 .strip_prefix('(')
4444 .and_then(|sql| sql.strip_suffix(')'))
4445 .unwrap_or(subquery_sql);
4446 stmt.clear_from().from_subquery(
4447 SelectStatement::raw(inner_sql),
4448 Alias::new(self.from_alias.as_deref().unwrap_or(T::table_name())),
4449 );
4450 }
4451
4452 for annotation in &self.annotations {
4453 stmt.expr_as(
4454 Expr::cust(annotation.value.to_sql_expr()),
4455 Alias::new(&annotation.alias),
4456 );
4457 }
4458
4459 for cte in self.ctes.iter() {
4460 let query = SelectStatement::raw(cte.query.clone());
4461 if cte.recursive {
4462 stmt.with_recursive_cte(Alias::new(&cte.name), query);
4463 } else {
4464 stmt.with_cte(Alias::new(&cte.name), query);
4465 }
4466 }
4467
4468 for join in self.lateral_joins.iter() {
4469 let join_type = match join.join_type {
4470 super::lateral_join::LateralJoinType::Inner => SeaJoinType::InnerJoin,
4471 super::lateral_join::LateralJoinType::Left => SeaJoinType::LeftJoin,
4472 super::lateral_join::LateralJoinType::Right => SeaJoinType::RightJoin,
4473 super::lateral_join::LateralJoinType::Full => SeaJoinType::FullOuterJoin,
4474 };
4475 let condition = join.on_condition.as_deref().unwrap_or("true");
4476 stmt.join(
4477 join_type,
4478 reinhardt_query::types::TableRef::lateral_subquery(
4479 SelectStatement::raw(join.subquery.clone()),
4480 Alias::new(&join.alias),
4481 ),
4482 Expr::cust(condition),
4483 );
4484 }
4485
4486 Ok(stmt)
4487 }
4488
4489 pub(crate) fn build_full_model_select_statement(
4490 &self,
4491 ) -> reinhardt_core::exception::Result<SelectStatement> {
4492 if self.selected_fields.is_some()
4493 || !self.deferred_fields.is_empty()
4494 || !self.select_related_fields.is_empty()
4495 {
4496 return Err(reinhardt_core::exception::Error::Database(
4497 "Session::list requires a model-shaped QuerySet".to_owned(),
4498 ));
4499 }
4500
4501 self.build_select_statement()
4502 }
4503
4504 pub fn for_model_session(mut self) -> Self {
4511 self.selected_fields = None;
4512 self.deferred_fields.clear();
4513 self.select_related_fields.clear();
4514 self.prefetch_related_fields.clear();
4515 self
4516 }
4517
4518 pub async fn all(&self) -> reinhardt_core::exception::Result<Vec<T>>
4570 where
4571 T: serde::de::DeserializeOwned,
4572 {
4573 let conn = super::manager::get_connection().await?;
4574
4575 let stmt = if self.select_related_fields.is_empty() {
4576 self.build_select_statement()?
4577 } else {
4578 self.select_related_query()
4580 };
4581
4582 let sql = stmt.to_string(PostgresQueryBuilder);
4584
4585 let started_at = Instant::now();
4587 let query_result = conn.query(&sql, vec![]).await;
4588 let duration = started_at.elapsed();
4589
4590 let rows = match query_result {
4591 Ok(rows) => {
4592 super::instrumentation::instrumentation()
4593 .orm_query_end_with_params(&sql, &[], duration)
4594 .await;
4595 rows
4596 }
4597 Err(error) => {
4598 super::instrumentation::instrumentation()
4599 .query_error(&sql, &format!("{error:?}"), duration)
4600 .await;
4601 return Err(error.into());
4602 }
4603 };
4604 rows.into_iter()
4605 .map(|row| {
4606 serde_json::from_value(serde_json::to_value(&row.data).map_err(|e| {
4607 reinhardt_core::exception::Error::Database(format!(
4608 "Serialization error: {}",
4609 e
4610 ))
4611 })?)
4612 .map_err(|e| {
4613 reinhardt_core::exception::Error::Database(format!(
4614 "Deserialization error: {}",
4615 e
4616 ))
4617 })
4618 })
4619 .collect()
4620 }
4621
4622 pub async fn first(&self) -> reinhardt_core::exception::Result<Option<T>>
4667 where
4668 T: serde::de::DeserializeOwned,
4669 {
4670 let mut results = self.all().await?;
4671 Ok(results.drain(..).next())
4672 }
4673
4674 pub async fn get(&self) -> reinhardt_core::exception::Result<T>
4721 where
4722 T: serde::de::DeserializeOwned,
4723 {
4724 let results = self.all().await?;
4725 match results.len() {
4726 0 => Err(reinhardt_core::exception::Error::Database(
4727 "No record found matching the query".to_string(),
4728 )),
4729 1 => Ok(results.into_iter().next().unwrap()),
4730 n => Err(reinhardt_core::exception::Error::Database(format!(
4731 "Multiple records found ({}), expected exactly one",
4732 n
4733 ))),
4734 }
4735 }
4736
4737 pub async fn all_with_db(
4770 &self,
4771 conn: &super::connection::DatabaseConnection,
4772 ) -> reinhardt_core::exception::Result<Vec<T>>
4773 where
4774 T: serde::de::DeserializeOwned,
4775 {
4776 let stmt = if self.select_related_fields.is_empty() {
4777 self.build_select_statement()?
4778 } else {
4779 self.select_related_query()
4780 };
4781
4782 let (sql, params) = build_select_statement(&stmt, conn.backend())?;
4783
4784 let started_at = Instant::now();
4785 let query_result = conn.query(&sql, params.clone()).await;
4786 let duration = started_at.elapsed();
4787
4788 let rows = match query_result {
4789 Ok(rows) => {
4790 let instrumentation_params = params
4791 .iter()
4792 .map(|param| format!("{param:?}"))
4793 .collect::<Vec<_>>();
4794 super::instrumentation::instrumentation()
4795 .orm_query_end_with_params(&sql, &instrumentation_params, duration)
4796 .await;
4797 rows
4798 }
4799 Err(error) => {
4800 super::instrumentation::instrumentation()
4801 .query_error(&sql, &format!("{error:?}"), duration)
4802 .await;
4803 return Err(error.into());
4804 }
4805 };
4806 rows.into_iter()
4807 .map(|row| {
4808 serde_json::from_value(serde_json::to_value(&row.data).map_err(|e| {
4809 reinhardt_core::exception::Error::Database(format!(
4810 "Serialization error: {}",
4811 e
4812 ))
4813 })?)
4814 .map_err(|e| {
4815 reinhardt_core::exception::Error::Database(format!(
4816 "Deserialization error: {}",
4817 e
4818 ))
4819 })
4820 })
4821 .collect()
4822 }
4823
4824 pub async fn get_with_db(
4858 &self,
4859 conn: &super::connection::DatabaseConnection,
4860 ) -> reinhardt_core::exception::Result<T>
4861 where
4862 T: serde::de::DeserializeOwned,
4863 {
4864 let results = self.all_with_db(conn).await?;
4865 match results.len() {
4866 0 => Err(reinhardt_core::exception::Error::NotFound(
4867 "No record found matching the query".to_string(),
4868 )),
4869 1 => Ok(results.into_iter().next().unwrap()),
4870 n => Err(reinhardt_core::exception::Error::Database(format!(
4871 "Multiple records found ({}), expected exactly one",
4872 n
4873 ))),
4874 }
4875 }
4876
4877 pub async fn first_with_db(
4910 &self,
4911 conn: &super::connection::DatabaseConnection,
4912 ) -> reinhardt_core::exception::Result<Option<T>>
4913 where
4914 T: serde::de::DeserializeOwned,
4915 {
4916 let mut results = self.all_with_db(conn).await?;
4917 Ok(results.drain(..).next())
4918 }
4919
4920 pub async fn count(&self) -> reinhardt_core::exception::Result<usize> {
4962 use reinhardt_query::prelude::{Func, PostgresQueryBuilder, QueryBuilder};
4963
4964 let conn = super::manager::get_connection().await?;
4965
4966 let mut stmt = Query::select();
4968 stmt.from(Alias::new(T::table_name()))
4969 .expr(Func::count(Expr::asterisk().into_simple_expr()));
4970
4971 if let Some(cond) = self.build_where_condition()? {
4973 stmt.cond_where(cond);
4974 }
4975
4976 let (sql, values) = PostgresQueryBuilder.build_select(&stmt);
4978
4979 let params = super::execution::convert_values(values);
4981
4982 let rows = conn.query(&sql, params).await?;
4984 if let Some(row) = rows.first() {
4985 if let Some(count_value) = row.data.get("count")
4987 && let Some(count) = count_value.as_i64()
4988 {
4989 return Ok(count as usize);
4990 }
4991 }
4992
4993 Ok(0)
4994 }
4995
4996 pub async fn exists(&self) -> reinhardt_core::exception::Result<bool> {
5040 let count = self.count().await?;
5041 Ok(count > 0)
5042 }
5043
5044 pub async fn create(&self, object: T) -> reinhardt_core::exception::Result<T>
5078 where
5079 T: super::Model + Clone,
5080 {
5081 match &self.manager {
5084 Some(manager) => manager.create(&object).await,
5085 None => {
5086 let manager = super::manager::Manager::<T>::new();
5088 manager.create(&object).await
5089 }
5090 }
5091 }
5092
5093 pub fn update_query(
5095 &self,
5096 updates: &HashMap<String, UpdateValue>,
5097 ) -> reinhardt_query::prelude::UpdateStatement {
5098 let mut stmt = Query::update();
5099 stmt.table(Alias::new(T::table_name()));
5100
5101 for (field, value) in updates {
5103 stmt.value_expr(Alias::new(field), Self::update_value_to_query_expr(value));
5104 }
5105
5106 if let Some(cond) = self.build_where_condition_or_false() {
5108 stmt.cond_where(cond);
5109 }
5110
5111 stmt.to_owned()
5112 }
5113
5114 pub fn update_fields_query<I, A>(
5121 &self,
5122 values: I,
5123 ) -> reinhardt_core::exception::Result<UpdateStatement>
5124 where
5125 I: IntoIterator<Item = A>,
5126 A: Into<FieldAssignment>,
5127 {
5128 let assignments = Self::collect_field_assignments(values);
5129 self.update_fields_query_from_assignments(&assignments)
5130 }
5131
5132 pub fn update_fields_sql<I, A>(
5138 &self,
5139 values: I,
5140 ) -> reinhardt_core::exception::Result<(String, Vec<String>)>
5141 where
5142 I: IntoIterator<Item = A>,
5143 A: Into<FieldAssignment>,
5144 {
5145 let stmt = self.update_fields_query(values)?;
5146 let (sql, values) = PostgresQueryBuilder.build_update(&stmt);
5147 let params = values
5148 .iter()
5149 .map(|value| Self::sea_value_to_string(value))
5150 .collect();
5151 Ok((sql, params))
5152 }
5153
5154 pub async fn update_fields<I, A>(self, values: I) -> reinhardt_core::exception::Result<u64>
5159 where
5160 I: IntoIterator<Item = A>,
5161 A: Into<FieldAssignment>,
5162 {
5163 let conn = super::manager::get_connection().await?;
5164 self.update_fields_with_conn(&conn, values).await
5165 }
5166
5167 pub async fn update_fields_with_conn<I, A>(
5169 &self,
5170 conn: &super::connection::DatabaseConnection,
5171 values: I,
5172 ) -> reinhardt_core::exception::Result<u64>
5173 where
5174 I: IntoIterator<Item = A>,
5175 A: Into<FieldAssignment>,
5176 {
5177 let stmt = self.update_fields_query(values)?;
5178 let (sql, values) = Self::build_update_for_backend(&stmt, conn.backend());
5179 let params = super::execution::convert_values(values);
5180
5181 conn.execute(&sql, params)
5182 .await
5183 .map_err(|error| reinhardt_core::exception::Error::Database(error.to_string()))
5184 }
5185
5186 fn collect_field_assignments<I, A>(values: I) -> Vec<FieldAssignment>
5187 where
5188 I: IntoIterator<Item = A>,
5189 A: Into<FieldAssignment>,
5190 {
5191 values.into_iter().map(Into::into).collect()
5192 }
5193
5194 fn update_fields_query_from_assignments(
5195 &self,
5196 assignments: &[FieldAssignment],
5197 ) -> reinhardt_core::exception::Result<UpdateStatement> {
5198 Self::validate_update_fields(assignments)?;
5199
5200 if !self.has_restricting_where_predicates() {
5201 let message = if self.has_where_predicates() {
5202 "QuerySet::update_fields requires at least one non-empty filter predicate"
5203 } else {
5204 "QuerySet::update_fields requires at least one filter predicate"
5205 };
5206 return Err(reinhardt_core::exception::Error::Validation(
5207 message.to_string(),
5208 ));
5209 }
5210
5211 let condition = self.build_where_condition()?.ok_or_else(|| {
5212 reinhardt_core::exception::Error::Validation(
5213 "QuerySet::update_fields requires at least one non-empty filter predicate"
5214 .to_string(),
5215 )
5216 })?;
5217
5218 let mut stmt = Query::update();
5219 stmt.table(Alias::new(T::table_name()));
5220
5221 for assignment in assignments {
5222 stmt.value_expr(
5223 Alias::new(assignment.field()),
5224 Self::update_value_to_query_expr(assignment.value()),
5225 );
5226 }
5227
5228 stmt.cond_where(condition);
5229
5230 Ok(stmt.to_owned())
5231 }
5232
5233 fn validate_update_fields(
5234 assignments: &[FieldAssignment],
5235 ) -> reinhardt_core::exception::Result<()> {
5236 if assignments.is_empty() {
5237 return Err(reinhardt_core::exception::Error::Validation(
5238 "QuerySet::update_fields requires at least one field assignment".to_string(),
5239 ));
5240 }
5241
5242 if assignments
5243 .iter()
5244 .any(|assignment| assignment.field().trim().is_empty())
5245 {
5246 return Err(reinhardt_core::exception::Error::Validation(
5247 "QuerySet::update_fields field names must not be empty".to_string(),
5248 ));
5249 }
5250
5251 Ok(())
5252 }
5253
5254 fn build_update_for_backend(
5255 stmt: &UpdateStatement,
5256 backend: super::connection::DatabaseBackend,
5257 ) -> (String, reinhardt_query::prelude::Values) {
5258 match backend {
5259 super::connection::DatabaseBackend::Postgres => PostgresQueryBuilder.build_update(stmt),
5260 super::connection::DatabaseBackend::MySql => MySqlQueryBuilder.build_update(stmt),
5261 super::connection::DatabaseBackend::Sqlite => SqliteQueryBuilder.build_update(stmt),
5262 }
5263 }
5264
5265 fn update_value_to_query_expr(value: &UpdateValue) -> Expr {
5266 match value {
5267 UpdateValue::String(s) => Expr::val(s.clone()),
5268 UpdateValue::Integer(i) => Expr::val(*i),
5269 UpdateValue::Float(f) => Expr::val(*f),
5270 UpdateValue::Boolean(b) => Expr::val(*b),
5271 UpdateValue::Null => Expr::cust("NULL"),
5272 UpdateValue::Timestamp(dt) => Expr::val(
5273 reinhardt_query::value::Value::ChronoDateTimeUtc(Some(Box::new(*dt))),
5274 ),
5275 UpdateValue::Uuid(uuid) => {
5276 Expr::val(reinhardt_query::value::Value::Uuid(Some(Box::new(*uuid))))
5277 }
5278 UpdateValue::FieldRef(f) => Expr::col(Alias::new(&f.field)),
5279 UpdateValue::Expression(expr) => Self::expression_to_query_expr(expr),
5280 }
5281 }
5282
5283 pub fn update_sql(&self, updates: &HashMap<String, UpdateValue>) -> (String, Vec<String>) {
5322 let stmt = self.update_query(updates);
5323 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryBuilder};
5324 let (sql, values) = PostgresQueryBuilder.build_update(&stmt);
5325 let params: Vec<String> = values
5326 .iter()
5327 .map(|v| Self::sea_value_to_string(v))
5328 .collect();
5329 (sql, params)
5330 }
5331
5332 fn sea_value_to_string(value: &reinhardt_query::value::Value) -> String {
5334 use reinhardt_query::value::Value;
5335 match value {
5336 Value::Bool(Some(b)) => b.to_string(),
5337 Value::TinyInt(Some(i)) => i.to_string(),
5338 Value::SmallInt(Some(i)) => i.to_string(),
5339 Value::Int(Some(i)) => i.to_string(),
5340 Value::BigInt(Some(i)) => i.to_string(),
5341 Value::TinyUnsigned(Some(i)) => i.to_string(),
5342 Value::SmallUnsigned(Some(i)) => i.to_string(),
5343 Value::Unsigned(Some(i)) => i.to_string(),
5344 Value::BigUnsigned(Some(i)) => i.to_string(),
5345 Value::Float(Some(f)) => f.to_string(),
5346 Value::Double(Some(f)) => f.to_string(),
5347 Value::String(Some(s)) => s.to_string(),
5348 Value::Bytes(Some(b)) => String::from_utf8_lossy(b).to_string(),
5349 Value::ChronoDateTimeUtc(Some(dt)) => dt.to_rfc3339(),
5350 Value::Uuid(Some(uuid)) => uuid.to_string(),
5351 _ => String::new(),
5352 }
5353 }
5354
5355 pub fn delete_query(&self) -> reinhardt_query::prelude::DeleteStatement {
5397 if !self.has_restricting_where_predicates() {
5398 panic!(
5399 "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
5400 );
5401 }
5402
5403 let Some(cond) = self.build_where_condition_or_false() else {
5404 panic!(
5405 "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
5406 );
5407 };
5408
5409 let mut stmt = Query::delete();
5410 stmt.from_table(Alias::new(T::table_name()));
5411 stmt.cond_where(cond);
5412
5413 stmt.to_owned()
5414 }
5415
5416 pub fn delete_sql(&self) -> (String, Vec<String>) {
5418 let stmt = self.delete_query();
5419 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryBuilder};
5420 let (sql, values) = PostgresQueryBuilder.build_delete(&stmt);
5421 let params: Vec<String> = values
5422 .iter()
5423 .map(|v| Self::sea_value_to_string(v))
5424 .collect();
5425 (sql, params)
5426 }
5427
5428 pub async fn get_composite(
5477 &self,
5478 pk_values: &HashMap<String, super::composite_pk::PkValue>,
5479 ) -> reinhardt_core::exception::Result<T>
5480 where
5481 T: super::Model + Clone,
5482 {
5483 use reinhardt_query::prelude::{
5484 Alias, BinOper, ColumnRef, Expr, PostgresQueryBuilder, Value,
5485 };
5486
5487 let composite_pk = T::composite_primary_key().ok_or_else(|| {
5489 reinhardt_core::exception::Error::Database(
5490 "Model does not have a composite primary key".to_string(),
5491 )
5492 })?;
5493
5494 composite_pk.validate(pk_values).map_err(|e| {
5496 reinhardt_core::exception::Error::Database(format!(
5497 "Composite PK validation failed: {}",
5498 e
5499 ))
5500 })?;
5501
5502 let table_name = T::table_name();
5504 let mut query = Query::select();
5505
5506 let table_alias = Alias::new(table_name);
5508 query.from(table_alias).column(ColumnRef::Asterisk);
5509
5510 for field_name in composite_pk.fields() {
5512 let pk_value: &super::composite_pk::PkValue = pk_values.get(field_name).unwrap();
5513 let col_alias = Alias::new(field_name);
5514
5515 match pk_value {
5516 &super::composite_pk::PkValue::Int(v) => {
5517 let condition = Expr::col(col_alias)
5518 .binary(BinOper::Equal, Expr::value(Value::BigInt(Some(v))));
5519 query.and_where(condition);
5520 }
5521 &super::composite_pk::PkValue::Uint(v) => {
5522 let condition = Expr::col(col_alias)
5523 .binary(BinOper::Equal, Expr::value(Value::BigInt(Some(v as i64))));
5524 query.and_where(condition);
5525 }
5526 super::composite_pk::PkValue::String(v) => {
5527 let condition = Expr::col(col_alias).binary(
5528 BinOper::Equal,
5529 Expr::value(Value::String(Some(Box::new(v.clone())))),
5530 );
5531 query.and_where(condition);
5532 }
5533 &super::composite_pk::PkValue::Bool(v) => {
5534 let condition = Expr::col(col_alias)
5535 .binary(BinOper::Equal, Expr::value(Value::Bool(Some(v))));
5536 query.and_where(condition);
5537 }
5538 }
5539 }
5540
5541 let sql = query.to_string(PostgresQueryBuilder);
5543
5544 let conn = super::manager::get_connection().await?;
5546
5547 let rows = conn.query(&sql, vec![]).await?;
5549
5550 if rows.is_empty() {
5552 return Err(reinhardt_core::exception::Error::Database(
5553 "No record found matching the composite primary key".to_string(),
5554 ));
5555 }
5556
5557 if rows.len() > 1 {
5558 return Err(reinhardt_core::exception::Error::Database(format!(
5559 "Multiple records found ({}) for composite primary key, expected exactly one",
5560 rows.len()
5561 )));
5562 }
5563
5564 let row = &rows[0];
5566 let value = serde_json::to_value(&row.data).map_err(|e| {
5567 reinhardt_core::exception::Error::Database(format!("Serialization error: {}", e))
5568 })?;
5569
5570 serde_json::from_value(value).map_err(|e| {
5571 reinhardt_core::exception::Error::Database(format!("Deserialization error: {}", e))
5572 })
5573 }
5574
5575 pub fn annotate(mut self, annotation: super::annotation::Annotation) -> Self {
5615 self.annotations.push(annotation);
5616 self
5617 }
5618
5619 pub fn annotate_subquery<M, F>(mut self, name: &str, builder: F) -> Self
5691 where
5692 M: super::Model + 'static,
5693 F: FnOnce(QuerySet<M>) -> QuerySet<M>,
5694 {
5695 let subquery_qs = QuerySet::<M>::new();
5697 let configured_subquery = builder(subquery_qs);
5699 let subquery_sql = configured_subquery.as_subquery();
5701
5702 let annotation = super::annotation::Annotation {
5704 alias: name.to_string(),
5705 value: super::annotation::AnnotationValue::Subquery(subquery_sql),
5706 };
5707 self.annotations.push(annotation);
5708 self
5709 }
5710
5711 pub fn aggregate(mut self, aggregate: super::aggregation::Aggregate) -> Self {
5773 let alias = aggregate
5775 .alias
5776 .clone()
5777 .unwrap_or_else(|| aggregate.func.to_string().to_lowercase());
5778 let annotation = super::annotation::Annotation {
5779 alias,
5780 value: super::annotation::AnnotationValue::Aggregate(aggregate),
5781 };
5782 self.annotations.push(annotation);
5783 self
5784 }
5785
5786 pub fn to_sql(&self) -> String {
5788 let mut stmt = if self.select_related_fields.is_empty() {
5789 let mut stmt = Query::select();
5791
5792 if let Some(ref alias) = self.from_alias {
5794 stmt.from_as(Alias::new(T::table_name()), Alias::new(alias));
5795 } else {
5796 stmt.from(Alias::new(T::table_name()));
5797 }
5798
5799 if self.distinct_enabled {
5801 stmt.distinct();
5802 }
5803
5804 if let Some(ref fields) = self.selected_fields {
5806 for field in fields {
5807 if field.contains('(') && field.contains(')') {
5809 stmt.expr(Expr::cust(field.clone()));
5811 } else {
5812 let col_ref = parse_column_reference(field);
5814 stmt.column(col_ref);
5815 }
5816 }
5817 } else if !self.deferred_fields.is_empty() {
5818 let all_fields = T::field_metadata();
5819 for field in all_fields {
5820 if !self.deferred_fields.contains(&field.name) {
5821 let col_ref = parse_column_reference(&field.name);
5822 stmt.column(col_ref);
5823 }
5824 }
5825 } else {
5826 stmt.column(ColumnRef::Asterisk);
5827 }
5828
5829 for join in &self.joins {
5831 if join.on_condition.is_empty() {
5832 if let Some(ref alias) = join.target_alias {
5834 stmt.cross_join((Alias::new(&join.target_table), Alias::new(alias)));
5837 } else {
5838 stmt.cross_join(Alias::new(&join.target_table));
5839 }
5840 } else {
5841 let sea_join_type = match join.join_type {
5843 super::sqlalchemy_query::JoinType::Inner => SeaJoinType::InnerJoin,
5844 super::sqlalchemy_query::JoinType::Left => SeaJoinType::LeftJoin,
5845 super::sqlalchemy_query::JoinType::Right => SeaJoinType::RightJoin,
5846 super::sqlalchemy_query::JoinType::Full => SeaJoinType::FullOuterJoin,
5847 };
5848
5849 if let Some(ref alias) = join.target_alias {
5851 stmt.join(
5853 sea_join_type,
5854 (Alias::new(&join.target_table), Alias::new(alias)),
5855 Expr::cust(join.on_condition.clone()),
5856 );
5857 } else {
5858 stmt.join(
5860 sea_join_type,
5861 Alias::new(&join.target_table),
5862 Expr::cust(join.on_condition.clone()),
5863 );
5864 }
5865 }
5866 }
5867
5868 if let Some(cond) = self.build_where_condition_or_false() {
5870 stmt.cond_where(cond);
5871 }
5872
5873 for group_field in &self.group_by_fields {
5875 stmt.group_by_col(Alias::new(group_field));
5876 }
5877
5878 for having_cond in &self.having_conditions {
5880 match having_cond {
5881 HavingCondition::AggregateCompare {
5882 func,
5883 field,
5884 operator,
5885 value,
5886 } => {
5887 let agg_expr = match func {
5889 AggregateFunc::Avg => {
5890 Func::avg(Expr::col(Alias::new(field)).into_simple_expr())
5891 }
5892 AggregateFunc::Count => {
5893 if field == "*" {
5894 Func::count(Expr::asterisk().into_simple_expr())
5895 } else {
5896 Func::count(Expr::col(Alias::new(field)).into_simple_expr())
5897 }
5898 }
5899 AggregateFunc::Sum => {
5900 Func::sum(Expr::col(Alias::new(field)).into_simple_expr())
5901 }
5902 AggregateFunc::Min => {
5903 Func::min(Expr::col(Alias::new(field)).into_simple_expr())
5904 }
5905 AggregateFunc::Max => {
5906 Func::max(Expr::col(Alias::new(field)).into_simple_expr())
5907 }
5908 };
5909
5910 let having_expr = match operator {
5912 ComparisonOp::Eq => match value {
5913 AggregateValue::Int(v) => agg_expr.eq(*v),
5914 AggregateValue::Float(v) => agg_expr.eq(*v),
5915 },
5916 ComparisonOp::Ne => match value {
5917 AggregateValue::Int(v) => agg_expr.ne(*v),
5918 AggregateValue::Float(v) => agg_expr.ne(*v),
5919 },
5920 ComparisonOp::Gt => match value {
5921 AggregateValue::Int(v) => agg_expr.gt(*v),
5922 AggregateValue::Float(v) => agg_expr.gt(*v),
5923 },
5924 ComparisonOp::Gte => match value {
5925 AggregateValue::Int(v) => agg_expr.gte(*v),
5926 AggregateValue::Float(v) => agg_expr.gte(*v),
5927 },
5928 ComparisonOp::Lt => match value {
5929 AggregateValue::Int(v) => agg_expr.lt(*v),
5930 AggregateValue::Float(v) => agg_expr.lt(*v),
5931 },
5932 ComparisonOp::Lte => match value {
5933 AggregateValue::Int(v) => agg_expr.lte(*v),
5934 AggregateValue::Float(v) => agg_expr.lte(*v),
5935 },
5936 };
5937
5938 stmt.and_having(having_expr);
5939 }
5940 }
5941 }
5942
5943 for order_field in &self.order_by_fields {
5945 let (field, is_desc) = if let Some(stripped) = order_field.strip_prefix('-') {
5946 (stripped, true)
5947 } else {
5948 (order_field.as_str(), false)
5949 };
5950
5951 let col_ref = parse_column_reference(field);
5952 let expr = Expr::col(col_ref);
5953 if is_desc {
5954 stmt.order_by_expr(expr, Order::Desc);
5955 } else {
5956 stmt.order_by_expr(expr, Order::Asc);
5957 }
5958 }
5959
5960 if let Some(limit) = self.limit {
5962 stmt.limit(limit as u64);
5963 }
5964 if let Some(offset) = self.offset {
5965 stmt.offset(offset as u64);
5966 }
5967
5968 stmt.to_owned()
5969 } else {
5970 self.select_related_query()
5972 };
5973
5974 let annotation_exprs: Vec<_> = self
5978 .annotations
5979 .iter()
5980 .map(|a| (a.value.to_sql_expr(), a.alias.clone()))
5981 .collect();
5982
5983 for (value_sql, alias) in annotation_exprs {
5984 stmt.expr_as(Expr::cust(value_sql), Alias::new(alias));
5985 }
5986
5987 use reinhardt_query::prelude::PostgresQueryBuilder;
5988 let mut select_sql = stmt.to_string(PostgresQueryBuilder);
5989
5990 if !self.lateral_joins.is_empty() {
5992 let lateral_sql = self.lateral_joins.to_sql().join(" ");
5993
5994 let insert_pos = select_sql
5997 .find(" WHERE ")
5998 .or_else(|| select_sql.find(" ORDER BY "))
5999 .or_else(|| select_sql.find(" LIMIT "))
6000 .unwrap_or(select_sql.len());
6001
6002 select_sql.insert_str(insert_pos, &format!(" {}", lateral_sql));
6003 }
6004
6005 if let Some(ref subquery_sql) = self.from_subquery_sql
6007 && let Some(ref alias) = self.from_alias
6008 {
6009 let from_pattern_with_alias = format!("FROM \"{}\" AS \"{}\"", T::table_name(), alias);
6011 let from_pattern_simple = format!("FROM \"{}\"", T::table_name());
6012
6013 let from_replacement = format!("FROM {} AS \"{}\"", subquery_sql, alias);
6014
6015 if select_sql.contains(&from_pattern_with_alias) {
6017 select_sql = select_sql.replace(&from_pattern_with_alias, &from_replacement);
6018 } else if select_sql.contains(&from_pattern_simple) {
6019 select_sql = select_sql.replace(&from_pattern_simple, &from_replacement);
6020 }
6021 }
6022
6023 if let Some(cte_sql) = self.ctes.to_sql() {
6025 format!("{} {}", cte_sql, select_sql)
6026 } else {
6027 select_sql
6028 }
6029 }
6030
6031 pub fn values(mut self, fields: &[&str]) -> Self {
6076 self.selected_fields = Some(fields.iter().map(|s| s.to_string()).collect());
6077 self
6078 }
6079
6080 pub fn values_list(self, fields: &[&str]) -> Self {
6117 self.values(fields)
6118 }
6119
6120 pub fn order_by(mut self, fields: &[&str]) -> Self {
6155 self.order_by_fields = fields.iter().map(|s| s.to_string()).collect();
6156 self
6157 }
6158
6159 pub fn distinct(mut self) -> Self {
6161 self.distinct_enabled = true;
6162 self
6163 }
6164
6165 pub fn without_distinct(mut self) -> Self {
6167 self.distinct_enabled = false;
6168 self
6169 }
6170
6171 pub fn limit(mut self, limit: usize) -> Self {
6206 self.limit = Some(limit);
6207 self
6208 }
6209
6210 pub fn offset(mut self, offset: usize) -> Self {
6246 self.offset = Some(offset);
6247 self
6248 }
6249
6250 pub fn without_slicing(mut self) -> Self {
6252 self.limit = None;
6253 self.offset = None;
6254 self
6255 }
6256
6257 pub fn paginate(self, page: usize, page_size: usize) -> Self {
6293 let offset = page.saturating_sub(1) * page_size;
6294 self.offset(offset).limit(page_size)
6295 }
6296
6297 pub fn as_subquery(self) -> String {
6354 format!("({})", self.to_sql())
6355 }
6356
6357 pub fn defer(mut self, fields: &[&str]) -> Self {
6394 self.deferred_fields = fields.iter().map(|s| s.to_string()).collect();
6395 self
6396 }
6397
6398 pub fn only(self, fields: &[&str]) -> Self {
6436 self.values(fields)
6437 }
6438
6439 pub fn full_text_search(self, field: &str, query: &str) -> Self {
6483 self.filter(Filter::new(
6484 field,
6485 FilterOperator::FullTextMatch,
6486 FilterValue::String(query.to_string()),
6487 ))
6488 }
6489
6490 pub fn filter_array_overlap(self, field: &str, values: &[&str]) -> Self {
6531 self.filter(Filter::new(
6532 field,
6533 FilterOperator::ArrayOverlap,
6534 FilterValue::Array(values.iter().map(|s| s.to_string()).collect()),
6535 ))
6536 }
6537
6538 pub fn filter_array_contains(self, field: &str, values: &[&str]) -> Self {
6579 self.filter(Filter::new(
6580 field,
6581 FilterOperator::ArrayContains,
6582 FilterValue::Array(values.iter().map(|s| s.to_string()).collect()),
6583 ))
6584 }
6585
6586 pub fn filter_jsonb_contains(self, field: &str, json: &str) -> Self {
6627 self.filter(Filter::new(
6628 field,
6629 FilterOperator::JsonbContains,
6630 FilterValue::String(json.to_string()),
6631 ))
6632 }
6633
6634 pub fn filter_jsonb_key_exists(self, field: &str, key: &str) -> Self {
6675 self.filter(Filter::new(
6676 field,
6677 FilterOperator::JsonbKeyExists,
6678 FilterValue::String(key.to_string()),
6679 ))
6680 }
6681
6682 pub fn filter_range_contains(self, field: &str, value: &str) -> Self {
6723 self.filter(Filter::new(
6724 field,
6725 FilterOperator::RangeContains,
6726 FilterValue::String(value.to_string()),
6727 ))
6728 }
6729}
6730
6731impl<T> Default for QuerySet<T>
6732where
6733 T: super::Model,
6734{
6735 fn default() -> Self {
6736 Self::new()
6737 }
6738}
6739
6740impl FilterValue {
6742 pub fn string(value: impl ToString) -> Self {
6749 Self::String(value.to_string())
6750 }
6751}
6752
6753pub(crate) fn quote_identifier(field: &str) -> String {
6761 if field.contains('\0') {
6762 panic!("SQL identifier must not contain null bytes");
6763 }
6764
6765 fn quote_single(name: &str) -> String {
6766 format!("\"{}\"", name.replace('"', "\"\""))
6767 }
6768
6769 if field.contains('.') {
6770 field
6771 .split('.')
6772 .map(quote_single)
6773 .collect::<Vec<_>>()
6774 .join(".")
6775 } else {
6776 quote_single(field)
6777 }
6778}
6779
6780fn filter_lhs_expr(filter: &Filter) -> Expr {
6781 match &filter.field_source {
6782 FilterField::Column => Expr::col(parse_column_reference(&filter.field)),
6783 FilterField::Expression { sql, .. } if filter.field == *sql => Expr::cust(sql.clone()),
6784 FilterField::Expression { .. } => Expr::col(parse_column_reference(&filter.field)),
6785 }
6786}
6787
6788fn filter_lhs_sql(filter: &Filter) -> String {
6789 match &filter.field_source {
6790 FilterField::Column => quote_identifier(&filter.field),
6791 FilterField::Expression { sql, .. } if filter.field == *sql => sql.clone(),
6792 FilterField::Expression { .. } => quote_identifier(&filter.field),
6793 }
6794}
6795
6796pub(crate) fn parse_column_reference(field: &str) -> reinhardt_query::prelude::ColumnRef {
6811 use reinhardt_query::prelude::ColumnRef;
6812
6813 if field.contains('(') && field.contains(')') {
6816 ColumnRef::column(Alias::new(field))
6818 } else if field.contains('.') {
6819 let parts: Vec<&str> = field.split('.').collect();
6821 match parts.as_slice() {
6822 [table, column] => {
6823 ColumnRef::table_column(Alias::new(*table), Alias::new(*column))
6825 }
6826 [schema, table, column] => {
6827 ColumnRef::schema_table_column(
6829 Alias::new(*schema),
6830 Alias::new(*table),
6831 Alias::new(*column),
6832 )
6833 }
6834 _ => {
6835 ColumnRef::column(Alias::new(field))
6837 }
6838 }
6839 } else {
6840 ColumnRef::column(Alias::new(field))
6842 }
6843}
6844
6845#[derive(Debug, Clone, Copy)]
6846enum LikePattern {
6847 Exact,
6848 Contains,
6849 StartsWith,
6850 EndsWith,
6851}
6852
6853impl LikePattern {
6854 fn apply(self, value: &str) -> String {
6855 let escaped = escape_like_pattern(value);
6856 match self {
6857 Self::Exact => escaped,
6858 Self::Contains => format!("%{}%", escaped),
6859 Self::StartsWith => format!("{}%", escaped),
6860 Self::EndsWith => format!("%{}", escaped),
6861 }
6862 }
6863}
6864
6865fn escape_like_pattern(value: &str) -> String {
6866 let mut escaped = String::with_capacity(value.len());
6867 for ch in value.chars() {
6868 if matches!(ch, '\\' | '%' | '_') {
6869 escaped.push('\\');
6870 }
6871 escaped.push(ch);
6872 }
6873 escaped
6874}
6875
6876fn build_select_statement(
6877 statement: &SelectStatement,
6878 backend: super::connection::DatabaseBackend,
6879) -> reinhardt_core::exception::Result<(String, Vec<QueryValue>)> {
6880 let (sql, values) = match backend {
6881 super::connection::DatabaseBackend::Postgres => statement.build(PostgresQueryBuilder),
6882 super::connection::DatabaseBackend::MySql => statement.build(MySqlQueryBuilder),
6883 super::connection::DatabaseBackend::Sqlite => statement.build(SqliteQueryBuilder),
6884 };
6885
6886 let params = values
6887 .into_iter()
6888 .map(query_value_from_sea_value)
6889 .collect::<reinhardt_core::exception::Result<Vec<_>>>()?;
6890 Ok((sql, params))
6891}
6892
6893#[cfg(test)]
6894fn render_select_statement(
6895 statement: &SelectStatement,
6896 backend: super::connection::DatabaseBackend,
6897) -> String {
6898 let (sql, values) = build_select_statement(statement, backend)
6899 .expect("test statements must contain bind values supported by QueryValue");
6900 inline_query_params(&sql, &values)
6901}
6902
6903fn query_value_from_sea_value(value: Value) -> reinhardt_core::exception::Result<QueryValue> {
6904 let value = match value {
6905 Value::Bool(Some(v)) => QueryValue::Bool(v),
6906 Value::TinyInt(Some(v)) => QueryValue::Int(i64::from(v)),
6907 Value::SmallInt(Some(v)) => QueryValue::Int(i64::from(v)),
6908 Value::Int(Some(v)) => QueryValue::Int(i64::from(v)),
6909 Value::BigInt(Some(v)) => QueryValue::Int(v),
6910 Value::TinyUnsigned(Some(v)) => QueryValue::Int(i64::from(v)),
6911 Value::SmallUnsigned(Some(v)) => QueryValue::Int(i64::from(v)),
6912 Value::Unsigned(Some(v)) => QueryValue::Int(i64::from(v)),
6913 Value::BigUnsigned(Some(v)) => QueryValue::Int(i64::try_from(v).map_err(|_| {
6914 reinhardt_core::exception::Error::Database(format!(
6915 "Unsigned query parameter {v} exceeds the supported i64 range"
6916 ))
6917 })?),
6918 Value::Float(Some(v)) => QueryValue::Float(f64::from(v)),
6919 Value::Double(Some(v)) => QueryValue::Float(v),
6920 Value::Char(Some(v)) => QueryValue::String(v.to_string()),
6921 Value::String(Some(v)) => QueryValue::String(*v),
6922 Value::Bytes(Some(v)) => QueryValue::Bytes(*v),
6923 Value::ChronoDateTimeUtc(Some(v)) => QueryValue::Timestamp(*v),
6924 Value::Uuid(Some(v)) => QueryValue::Uuid(*v),
6925 _ => QueryValue::Null,
6926 };
6927 Ok(value)
6928}
6929
6930#[cfg(test)]
6931fn inline_query_params(sql: &str, params: &[QueryValue]) -> String {
6932 let mut rendered = sql.to_string();
6933 for value in params {
6934 rendered = rendered.replacen('?', &query_value_to_sql_literal(value), 1);
6935 }
6936 rendered
6937}
6938
6939#[cfg(test)]
6940fn query_value_to_sql_literal(value: &QueryValue) -> String {
6941 match value {
6942 QueryValue::Null => "NULL".to_string(),
6943 QueryValue::Bool(v) => v.to_string(),
6944 QueryValue::Int(v) => v.to_string(),
6945 QueryValue::Float(v) => v.to_string(),
6946 QueryValue::String(v) => format!("'{}'", v.replace('\\', "\\\\").replace('\'', "''")),
6947 QueryValue::Bytes(v) => format!(
6948 "X'{}'",
6949 v.iter().map(|b| format!("{b:02X}")).collect::<String>()
6950 ),
6951 QueryValue::Timestamp(v) => format!("'{}'", v.to_rfc3339()),
6952 QueryValue::Uuid(v) => format!("'{}'", v),
6953 QueryValue::Now => "NOW()".to_string(),
6954 }
6955}
6956
6957#[cfg(test)]
6958mod tests {
6959 use super::{
6960 AggregateFunc, AggregateValue, ComparisonOp, FilterCondition, HavingCondition,
6961 MAX_FILTER_CONDITION_DEPTH, build_select_statement, render_select_statement,
6962 };
6963 use crate::orm::connection::DatabaseBackend;
6964 use crate::orm::query::{FieldAssignment, UpdateValue};
6965 use crate::orm::{FilterOperator, FilterValue, Manager, Model, QuerySet, query::Filter};
6966 use reinhardt_query::prelude::{ExprTrait, PostgresQueryBuilder, QueryStatementBuilder};
6967 use rstest::rstest;
6968 use serde::{Deserialize, Serialize};
6969 use std::collections::HashMap;
6970
6971 #[test]
6972 fn render_select_statement_uses_mysql_identifier_quoting() {
6973 let mut statement = reinhardt_query::prelude::Query::select();
6975 statement
6976 .column(reinhardt_query::prelude::Alias::new("id"))
6977 .from(reinhardt_query::prelude::Alias::new("articles"));
6978
6979 let sql = render_select_statement(&statement, DatabaseBackend::MySql);
6981
6982 assert_eq!(sql, "SELECT `id` FROM `articles`");
6984 }
6985
6986 #[test]
6987 fn build_select_statement_keeps_mysql_filter_values_bound() {
6988 let payload = "\\' OR 1=1 -- ";
6990 let mut statement = reinhardt_query::prelude::Query::select();
6991 statement
6992 .column(reinhardt_query::prelude::Alias::new("id"))
6993 .from(reinhardt_query::prelude::Alias::new("users"))
6994 .and_where(
6995 reinhardt_query::prelude::Expr::col(reinhardt_query::prelude::Alias::new("name"))
6996 .eq(payload),
6997 );
6998
6999 let (sql, params) = build_select_statement(&statement, DatabaseBackend::MySql)
7001 .expect("string filter should fit in QueryValue");
7002
7003 assert_eq!(sql, "SELECT `id` FROM `users` WHERE `name` = ?");
7005 assert_eq!(
7006 params,
7007 vec![crate::backends::types::QueryValue::String(
7008 payload.to_string()
7009 )]
7010 );
7011 }
7012
7013 #[test]
7014 fn build_select_statement_rejects_oversized_unsigned_parameters() {
7015 let mut statement = reinhardt_query::prelude::Query::select();
7017 statement
7018 .column(reinhardt_query::prelude::Alias::new("id"))
7019 .from(reinhardt_query::prelude::Alias::new("users"))
7020 .limit((i64::MAX as u64) + 1);
7021
7022 let error = build_select_statement(&statement, DatabaseBackend::MySql)
7024 .expect_err("oversized unsigned parameters must not be clamped");
7025
7026 assert_eq!(
7028 error.to_string(),
7029 "Database error: Unsigned query parameter 9223372036854775808 exceeds the supported i64 range"
7030 );
7031 }
7032
7033 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7034 struct TestUser {
7035 id: Option<i64>,
7036 username: String,
7037 email: String,
7038 }
7039
7040 impl TestUser {
7041 #[allow(dead_code)]
7043 fn new(username: String, email: String) -> Self {
7044 Self {
7045 id: None,
7046 username,
7047 email,
7048 }
7049 }
7050
7051 const fn field_id() -> crate::orm::expressions::FieldRef<TestUser, i64> {
7052 crate::orm::expressions::FieldRef::new("id")
7053 }
7054
7055 const fn field_username() -> crate::orm::expressions::FieldRef<TestUser, String> {
7056 crate::orm::expressions::FieldRef::new("username")
7057 }
7058
7059 const fn field_email() -> crate::orm::expressions::FieldRef<TestUser, String> {
7060 crate::orm::expressions::FieldRef::new("email")
7061 }
7062
7063 const fn field_created_at() -> crate::orm::expressions::FieldRef<TestUser, String> {
7064 crate::orm::expressions::FieldRef::new("created_at")
7065 }
7066
7067 const fn field_tags() -> crate::orm::expressions::FieldRef<TestUser, Vec<String>> {
7068 crate::orm::expressions::FieldRef::new("tags")
7069 }
7070
7071 const fn field_metadata() -> crate::orm::expressions::FieldRef<TestUser, String> {
7072 crate::orm::expressions::FieldRef::new("metadata")
7073 }
7074
7075 const fn field_active_period() -> crate::orm::expressions::FieldRef<TestUser, String> {
7076 crate::orm::expressions::FieldRef::new("active_period")
7077 }
7078 }
7079
7080 #[derive(Debug, Clone)]
7081 struct TestUserFields;
7082
7083 impl crate::orm::model::FieldSelector for TestUserFields {
7084 fn with_alias(self, _alias: &str) -> Self {
7085 self
7086 }
7087 }
7088
7089 impl Model for TestUser {
7090 type PrimaryKey = i64;
7091 type Fields = TestUserFields;
7092 type Objects = Manager<Self>;
7093
7094 fn table_name() -> &'static str {
7095 "test_users"
7096 }
7097
7098 fn primary_key(&self) -> Option<Self::PrimaryKey> {
7099 self.id
7100 }
7101
7102 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
7103 self.id = Some(value);
7104 }
7105
7106 fn primary_key_field() -> &'static str {
7107 "id"
7108 }
7109
7110 fn new_fields() -> Self::Fields {
7111 TestUserFields
7112 }
7113 }
7114
7115 #[test]
7116 fn queryset_maps_in_and_not_in_subquery_fields() {
7117 let mut queryset = QuerySet::<TestUser>::new()
7118 .filter_in_subquery("username", |subquery: QuerySet<TestUser>| subquery)
7119 .filter_not_in_subquery("email", |subquery: QuerySet<TestUser>| subquery);
7120
7121 assert_eq!(
7122 queryset.subquery_fields().collect::<Vec<_>>(),
7123 vec!["username", "email"]
7124 );
7125
7126 queryset.map_subquery_fields(|field| field.push_str("_column"));
7127
7128 assert_eq!(
7129 queryset.subquery_fields().collect::<Vec<_>>(),
7130 vec!["username_column", "email_column"]
7131 );
7132 }
7133
7134 #[test]
7135 fn queryset_tracks_correlated_exists_fields() {
7136 let mut queryset = QuerySet::<TestUser>::new()
7137 .filter_exists(|subquery: QuerySet<TestUser>| {
7138 subquery.filter(Filter::new(
7139 "tenant_id",
7140 FilterOperator::Eq,
7141 FilterValue::FieldRef(crate::orm::expressions::F::new("items.tenant_slug")),
7142 ))
7143 })
7144 .filter_not_exists(|subquery: QuerySet<TestUser>| {
7145 subquery.filter(Filter::new(
7146 "organization_id",
7147 FilterOperator::Eq,
7148 FilterValue::OuterRef(crate::orm::expressions::OuterRef::new(
7149 "items.organization_id",
7150 )),
7151 ))
7152 });
7153
7154 assert_eq!(
7155 queryset.subquery_fields().collect::<Vec<_>>(),
7156 vec!["items.tenant_slug", "items.organization_id"]
7157 );
7158
7159 queryset.map_subquery_fields(|field| {
7160 *field = match field.as_str() {
7161 "items.tenant_slug" => "items.tenant_id".to_owned(),
7162 "items.organization_id" => "items.organization_key".to_owned(),
7163 field => format!("{field}_column"),
7164 };
7165 });
7166
7167 assert_eq!(
7168 queryset.subquery_fields().collect::<Vec<_>>(),
7169 vec!["items.tenant_id", "items.organization_key"]
7170 );
7171
7172 let sql = queryset.to_sql();
7173 assert_eq!(
7174 sql,
7175 r#"SELECT * FROM "test_users" WHERE (EXISTS (SELECT * FROM "test_users" WHERE "tenant_id" = "items"."tenant_id") AND NOT EXISTS (SELECT * FROM "test_users" WHERE "organization_id" = "items"."organization_key"))"#
7176 );
7177 }
7178
7179 #[test]
7180 fn map_subquery_fields_does_not_cascade_replacements() {
7181 let mut queryset =
7182 QuerySet::<TestUser>::new().filter_exists(|subquery: QuerySet<TestUser>| {
7183 subquery
7184 .filter(Filter::new(
7185 "tenant_id",
7186 FilterOperator::Eq,
7187 FilterValue::FieldRef(crate::orm::expressions::F::new("items.a")),
7188 ))
7189 .filter(Filter::new(
7190 "organization_id",
7191 FilterOperator::Eq,
7192 FilterValue::FieldRef(crate::orm::expressions::F::new("items.b")),
7193 ))
7194 });
7195
7196 queryset.map_subquery_fields(|field| {
7197 *field = match field.as_str() {
7198 "items.a" => "items.b".to_owned(),
7199 "items.b" => "items.c".to_owned(),
7200 field => field.to_owned(),
7201 };
7202 });
7203
7204 assert_eq!(
7205 queryset.to_sql(),
7206 r#"SELECT * FROM "test_users" WHERE EXISTS (SELECT * FROM "test_users" WHERE ("tenant_id" = "items"."b" AND "organization_id" = "items"."c"))"#
7207 );
7208 }
7209
7210 #[test]
7211 fn full_model_select_keeps_filter_order_limit_and_bound_value() {
7212 let queryset = QuerySet::<TestUser>::new()
7213 .filter(Filter::new(
7214 "username",
7215 FilterOperator::Eq,
7216 FilterValue::String("alice".to_owned()),
7217 ))
7218 .order_by(&["-id"])
7219 .limit(1);
7220 let statement = queryset.build_full_model_select_statement().unwrap();
7221 let (sql, values) = statement.build(PostgresQueryBuilder);
7222 assert_eq!(
7223 sql,
7224 r#"SELECT * FROM "test_users" WHERE "username" = $1 ORDER BY "id" DESC LIMIT $2"#
7225 );
7226 assert_eq!(
7227 values.0,
7228 vec![
7229 reinhardt_query::value::Value::String(Some(Box::new("alice".to_owned()))),
7230 reinhardt_query::value::Value::BigUnsigned(Some(1)),
7231 ]
7232 );
7233 }
7234
7235 #[test]
7236 fn full_model_select_rejects_non_model_shapes() {
7237 fn assert_rejected(queryset: QuerySet<TestUser>) {
7238 assert_eq!(
7239 queryset
7240 .build_full_model_select_statement()
7241 .unwrap_err()
7242 .to_string(),
7243 "Database error: Session::list requires a model-shaped QuerySet"
7244 );
7245 }
7246
7247 assert_rejected(QuerySet::<TestUser>::new().values(&["id"]));
7248 assert_rejected(QuerySet::<TestUser>::new().defer(&["email"]));
7249 let annotated =
7250 QuerySet::<TestUser>::new().annotate(crate::orm::annotation::Annotation::new(
7251 "answer",
7252 crate::orm::annotation::AnnotationValue::Value(crate::orm::annotation::Value::Int(
7253 42,
7254 )),
7255 ));
7256 assert!(
7257 annotated
7258 .build_full_model_select_statement()
7259 .expect("annotations must remain available to model sessions")
7260 .to_string(PostgresQueryBuilder)
7261 .contains("42 AS \"answer\"")
7262 );
7263 assert_rejected(QuerySet::<TestUser>::new().select_related(&["profile"]));
7264 assert_eq!(
7265 QuerySet::<TestUser>::new()
7266 .prefetch_related(&["groups"])
7267 .build_full_model_select_statement()
7268 .expect("prefetch must not change the root model projection")
7269 .to_string(PostgresQueryBuilder),
7270 r#"SELECT * FROM "test_users""#
7271 );
7272 fn assert_supported(queryset: QuerySet<TestUser>, fragment: &str) {
7273 let sql = queryset
7274 .build_full_model_select_statement()
7275 .expect("structural clauses must remain executable")
7276 .to_string(PostgresQueryBuilder);
7277 assert!(
7278 sql.contains(fragment),
7279 "SQL `{sql}` must contain `{fragment}`"
7280 );
7281 }
7282
7283 assert_supported(
7284 QuerySet::<TestUser>::new().with_cte(crate::orm::cte::CTE::new("active", "SELECT 1")),
7285 "WITH \"active\" AS (SELECT 1)",
7286 );
7287 assert_supported(
7288 QuerySet::<TestUser>::new().with_lateral_join(
7289 crate::orm::lateral_join::LateralJoin::new("latest", "SELECT 1"),
7290 ),
7291 "LEFT JOIN LATERAL (SELECT 1) AS \"latest\" ON true",
7292 );
7293 assert_supported(
7294 QuerySet::<TestUser>::new().inner_join::<TestUser>("id", "id"),
7295 "INNER JOIN \"test_users\" ON",
7296 );
7297
7298 let mut grouped = QuerySet::<TestUser>::new();
7299 grouped.group_by_fields.push("id".to_owned());
7300 assert_supported(grouped, "GROUP BY \"id\"");
7301
7302 let mut having = QuerySet::<TestUser>::new();
7303 having
7304 .having_conditions
7305 .push(HavingCondition::AggregateCompare {
7306 func: AggregateFunc::Count,
7307 field: "*".to_owned(),
7308 operator: ComparisonOp::Gt,
7309 value: AggregateValue::Int(0),
7310 });
7311 assert_supported(having, "HAVING COUNT(*) >");
7312
7313 assert_supported(
7314 QuerySet::<TestUser>::new().from_as("users_alias"),
7315 "FROM \"test_users\" AS \"users_alias\"",
7316 );
7317 assert_supported(
7318 QuerySet::<TestUser>::from_subquery(
7319 |queryset: QuerySet<TestUser>| queryset,
7320 "users_subquery",
7321 ),
7322 "FROM (SELECT * FROM \"test_users\") AS \"users_subquery\"",
7323 );
7324 }
7325
7326 #[test]
7327 fn for_model_session_discards_manager_result_modifiers() {
7328 let queryset = QuerySet::<TestUser>::new()
7329 .values(&["id"])
7330 .defer(&["email"])
7331 .annotate(crate::orm::annotation::Annotation::new(
7332 "answer",
7333 crate::orm::annotation::AnnotationValue::Value(crate::orm::annotation::Value::Int(
7334 42,
7335 )),
7336 ))
7337 .select_related(&["profile"])
7338 .prefetch_related(&["groups"]);
7339
7340 let statement = queryset
7341 .for_model_session()
7342 .build_full_model_select_statement()
7343 .expect("manager result modifiers must not block model decoding");
7344
7345 assert_eq!(
7346 statement.to_string(PostgresQueryBuilder),
7347 r#"SELECT *, 42 AS "answer" FROM "test_users""#
7348 );
7349 }
7350
7351 #[test]
7352 fn test_field_assignment_from_generated_field_ref_tuple() {
7353 let timestamp = chrono::DateTime::parse_from_rfc3339("2026-06-19T00:00:00Z")
7354 .expect("valid timestamp")
7355 .with_timezone(&chrono::Utc);
7356
7357 let assignment: FieldAssignment = (TestUser::field_created_at(), timestamp).into();
7358
7359 assert_eq!(assignment.field(), "created_at");
7360 assert!(matches!(assignment.value(), UpdateValue::Timestamp(_)));
7361 }
7362
7363 #[test]
7364 fn test_typed_timestamp_filter_binds_as_timestamp() {
7365 let timestamp = chrono::DateTime::parse_from_rfc3339("2026-06-19T00:00:00Z")
7367 .expect("valid timestamp")
7368 .with_timezone(&chrono::Utc);
7369 let value: FilterValue = timestamp.into();
7370
7371 let bound = QuerySet::<TestUser>::filter_value_to_sea_value(&value);
7373
7374 assert!(matches!(value, FilterValue::Timestamp(_)));
7376 assert!(matches!(
7377 bound,
7378 reinhardt_query::value::Value::ChronoDateTimeUtc(Some(_))
7379 ));
7380 }
7381
7382 #[test]
7383 fn test_typed_uuid_filter_binds_as_uuid() {
7384 let uuid =
7386 uuid::Uuid::parse_str("123e4567-e89b-12d3-a456-426614174000").expect("valid UUID");
7387 let value: FilterValue = uuid.into();
7388
7389 let bound = QuerySet::<TestUser>::filter_value_to_sea_value(&value);
7391
7392 assert!(matches!(value, FilterValue::Uuid(_)));
7394 assert!(matches!(
7395 bound,
7396 reinhardt_query::value::Value::Uuid(Some(_))
7397 ));
7398 }
7399
7400 #[test]
7401 fn test_field_assignment_from_field_ref_assign_helper() {
7402 let assignment = TestUser::field_username().assign("alice");
7403
7404 assert_eq!(assignment.field(), "username");
7405 assert!(matches!(
7406 assignment.value(),
7407 UpdateValue::String(value) if value == "alice"
7408 ));
7409 }
7410
7411 #[test]
7412 fn test_update_fields_sql_preserves_queryset_predicates() {
7413 let timestamp = chrono::DateTime::parse_from_rfc3339("2026-06-19T00:00:00Z")
7414 .expect("valid timestamp")
7415 .with_timezone(&chrono::Utc);
7416 let queryset = QuerySet::<TestUser>::new()
7417 .filter(TestUser::field_id().eq(7))
7418 .filter(TestUser::field_email().is_null());
7419
7420 let (sql, params) = queryset
7421 .update_fields_sql([(TestUser::field_created_at(), timestamp)])
7422 .expect("update fields sql");
7423
7424 assert_eq!(
7425 sql,
7426 "UPDATE \"test_users\" SET \"created_at\" = $1 WHERE (\"id\" = $2 AND \"email\" IS NULL)"
7427 );
7428 assert_eq!(params.len(), 2);
7429 assert_eq!(params[0], "2026-06-19T00:00:00+00:00");
7430 assert_eq!(params[1], "7");
7431 }
7432
7433 #[test]
7434 fn test_update_fields_sql_rejects_empty_assignments() {
7435 let queryset = QuerySet::<TestUser>::new().filter(TestUser::field_id().eq(7));
7436
7437 let error = queryset
7438 .update_fields_sql(std::iter::empty::<FieldAssignment>())
7439 .expect_err("empty assignments should fail");
7440
7441 assert!(matches!(
7442 error,
7443 reinhardt_core::exception::Error::Validation(message)
7444 if message.contains("field assignment")
7445 ));
7446 }
7447
7448 #[test]
7449 fn test_update_fields_sql_rejects_missing_predicate() {
7450 let queryset = QuerySet::<TestUser>::new();
7451
7452 let error = queryset
7453 .update_fields_sql([("username", "alice")])
7454 .expect_err("missing predicate should fail");
7455
7456 assert!(matches!(
7457 error,
7458 reinhardt_core::exception::Error::Validation(message)
7459 if message.contains("filter predicate")
7460 ));
7461 }
7462
7463 #[test]
7464 fn test_update_fields_sql_rejects_empty_and_predicate() {
7465 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::and(Vec::new()));
7466
7467 let error = queryset
7468 .update_fields_sql([("username", "alice")])
7469 .expect_err("empty AND predicate should fail");
7470
7471 assert!(matches!(
7472 error,
7473 reinhardt_core::exception::Error::Validation(message)
7474 if message == "QuerySet::update_fields requires at least one non-empty filter predicate"
7475 ));
7476 }
7477
7478 #[test]
7479 fn test_update_fields_sql_rejects_empty_not_in_predicate() {
7480 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7481 "id".to_string(),
7482 FilterOperator::NotIn,
7483 FilterValue::List(vec![]),
7484 ));
7485
7486 let error = queryset
7487 .update_fields_sql([("username", "alice")])
7488 .expect_err("empty NOT IN predicate should fail");
7489
7490 assert!(matches!(
7491 error,
7492 reinhardt_core::exception::Error::Validation(message)
7493 if message == "QuerySet::update_fields requires at least one non-empty filter predicate"
7494 ));
7495 }
7496
7497 #[tokio::test]
7498 async fn test_queryset_create_with_manager() {
7499 let manager = std::sync::Arc::new(TestUser::objects());
7501 let queryset = QuerySet::with_manager(manager);
7502
7503 let user = TestUser {
7504 id: None,
7505 username: "testuser".to_string(),
7506 email: "test@example.com".to_string(),
7507 };
7508
7509 let result = queryset.create(user).await;
7512
7513 assert!(result.is_err() || result.is_ok());
7516 }
7517
7518 #[tokio::test]
7519 async fn test_queryset_create_without_manager() {
7520 let queryset = QuerySet::<TestUser>::new();
7522
7523 let user = TestUser {
7524 id: None,
7525 username: "fallback_user".to_string(),
7526 email: "fallback@example.com".to_string(),
7527 };
7528
7529 let result = queryset.create(user).await;
7531
7532 assert!(result.is_err() || result.is_ok());
7534 }
7535
7536 #[test]
7537 fn test_queryset_with_manager() {
7538 let manager = std::sync::Arc::new(TestUser::objects());
7539 let queryset = QuerySet::with_manager(manager.clone());
7540
7541 assert!(queryset.manager.is_some());
7543 }
7544
7545 #[test]
7546 fn test_queryset_filter_preserves_manager() {
7547 let manager = std::sync::Arc::new(TestUser::objects());
7548 let queryset = QuerySet::with_manager(manager);
7549
7550 let filter = Filter::new(
7551 "username".to_string(),
7552 FilterOperator::Eq,
7553 FilterValue::String("alice".to_string()),
7554 );
7555
7556 let filtered = queryset.filter(filter);
7557
7558 assert!(filtered.manager.is_some());
7560 }
7561
7562 #[test]
7563 fn test_queryset_select_related_preserves_manager() {
7564 let manager = std::sync::Arc::new(TestUser::objects());
7565 let queryset = QuerySet::with_manager(manager);
7566
7567 let selected = queryset.select_related(&["profile", "posts"]);
7568
7569 assert!(selected.manager.is_some());
7571 assert_eq!(selected.select_related_fields, vec!["profile", "posts"]);
7572 }
7573
7574 #[test]
7575 fn test_queryset_prefetch_related_preserves_manager() {
7576 let manager = std::sync::Arc::new(TestUser::objects());
7577 let queryset = QuerySet::with_manager(manager);
7578
7579 let prefetched = queryset.prefetch_related(&["comments", "likes"]);
7580
7581 assert!(prefetched.manager.is_some());
7583 assert_eq!(
7584 prefetched.prefetch_related_fields,
7585 vec!["comments", "likes"]
7586 );
7587 }
7588
7589 #[tokio::test]
7590 async fn test_get_composite_validation_error() {
7591 use std::collections::HashMap;
7592
7593 let queryset = QuerySet::<TestUser>::new();
7594 let pk_values = HashMap::new(); let result = queryset.get_composite(&pk_values).await;
7597
7598 assert!(result.is_err());
7600 let err = result.unwrap_err();
7601 assert!(err.to_string().contains("composite primary key"));
7602 }
7603
7604 #[test]
7607 fn test_update_sql_single_field_single_filter() {
7608 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7609 "id".to_string(),
7610 FilterOperator::Eq,
7611 FilterValue::Integer(1),
7612 ));
7613
7614 let mut updates = HashMap::new();
7615 updates.insert(
7616 "username".to_string(),
7617 UpdateValue::String("alice".to_string()),
7618 );
7619 let (sql, params) = queryset.update_sql(&updates);
7620
7621 assert_eq!(
7622 sql,
7623 "UPDATE \"test_users\" SET \"username\" = $1 WHERE \"id\" = $2"
7624 );
7625 assert_eq!(params, vec!["alice", "1"]);
7626 }
7627
7628 #[test]
7629 fn test_update_sql_multiple_fields_multiple_filters() {
7630 let queryset = QuerySet::<TestUser>::new()
7631 .filter(Filter::new(
7632 "id".to_string(),
7633 FilterOperator::Gt,
7634 FilterValue::Integer(10),
7635 ))
7636 .filter(Filter::new(
7637 "email".to_string(),
7638 FilterOperator::Contains,
7639 FilterValue::String("example.com".to_string()),
7640 ));
7641
7642 let mut updates = HashMap::new();
7643 updates.insert(
7644 "username".to_string(),
7645 UpdateValue::String("bob".to_string()),
7646 );
7647 updates.insert(
7648 "email".to_string(),
7649 UpdateValue::String("bob@test.com".to_string()),
7650 );
7651 let (sql, params) = queryset.update_sql(&updates);
7652
7653 let valid_sql_1 = "UPDATE \"test_users\" SET \"username\" = $1, \"email\" = $2 WHERE (\"id\" > $3 AND \"email\" LIKE $4 ESCAPE '\\')";
7655 let valid_sql_2 = "UPDATE \"test_users\" SET \"email\" = $1, \"username\" = $2 WHERE (\"id\" > $3 AND \"email\" LIKE $4 ESCAPE '\\')";
7656 assert!(
7657 sql == valid_sql_1 || sql == valid_sql_2,
7658 "Generated SQL '{}' does not match either expected pattern",
7659 sql
7660 );
7661
7662 assert!(
7664 params.contains(&"bob".to_string()) || params.contains(&"bob@test.com".to_string())
7665 );
7666 assert_eq!(params[2], "10");
7667 assert_eq!(params[3], "%example.com%");
7668 }
7669
7670 #[test]
7671 fn test_delete_sql_single_filter() {
7672 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7673 "id".to_string(),
7674 FilterOperator::Eq,
7675 FilterValue::Integer(1),
7676 ));
7677
7678 let (sql, params) = queryset.delete_sql();
7679
7680 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"id\" = $1");
7681 assert_eq!(params, vec!["1"]);
7682 }
7683
7684 #[test]
7685 fn test_delete_sql_multiple_filters() {
7686 let queryset = QuerySet::<TestUser>::new()
7687 .filter(Filter::new(
7688 "username".to_string(),
7689 FilterOperator::Eq,
7690 FilterValue::String("alice".to_string()),
7691 ))
7692 .filter(Filter::new(
7693 "email".to_string(),
7694 FilterOperator::StartsWith,
7695 FilterValue::String("alice@".to_string()),
7696 ));
7697
7698 let (sql, params) = queryset.delete_sql();
7699
7700 assert_eq!(
7701 sql,
7702 "DELETE FROM \"test_users\" WHERE (\"username\" = $1 AND \"email\" LIKE $2 ESCAPE '\\')"
7703 );
7704 assert_eq!(params, vec!["alice", "alice@%"]);
7705 }
7706
7707 #[test]
7708 #[should_panic(
7709 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7710 )]
7711 fn test_delete_sql_without_filters_panics() {
7712 let queryset = QuerySet::<TestUser>::new();
7713 let _ = queryset.delete_sql();
7714 }
7715
7716 #[test]
7717 #[should_panic(
7718 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7719 )]
7720 fn test_delete_sql_with_empty_composite_filter_panics() {
7721 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::and(Vec::new()));
7722 let _ = queryset.delete_sql();
7723 }
7724
7725 #[test]
7726 #[should_panic(
7727 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7728 )]
7729 fn test_delete_sql_with_nested_empty_and_filter_panics() {
7730 let queryset = QuerySet::<TestUser>::new()
7731 .filter(FilterCondition::and(vec![FilterCondition::and(Vec::new())]));
7732 let _ = queryset.delete_sql();
7733 }
7734
7735 #[test]
7736 fn test_delete_sql_with_empty_or_filter_matches_no_rows() {
7737 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::or(Vec::new()));
7738
7739 let (sql, params) = queryset.delete_sql();
7740
7741 assert_eq!(sql, r#"DELETE FROM "test_users" WHERE FALSE"#);
7742 assert!(params.is_empty());
7743 }
7744
7745 #[test]
7746 fn test_delete_sql_keeps_real_filter_beside_empty_and() {
7747 let queryset = QuerySet::<TestUser>::new()
7748 .filter(FilterCondition::and(Vec::new()))
7749 .filter(Filter::new(
7750 "id".to_string(),
7751 FilterOperator::Eq,
7752 FilterValue::Integer(1),
7753 ));
7754
7755 let (sql, params) = queryset.delete_sql();
7756
7757 assert_eq!(
7758 sql,
7759 r#"DELETE FROM "test_users" WHERE ("id" = $1 AND TRUE)"#
7760 );
7761 assert_eq!(params, vec!["1"]);
7762 }
7763
7764 #[test]
7765 #[should_panic(
7766 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7767 )]
7768 fn test_delete_sql_with_empty_not_in_list_panics() {
7769 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7770 "id".to_string(),
7771 FilterOperator::NotIn,
7772 FilterValue::List(vec![]),
7773 ));
7774 let _ = queryset.delete_sql();
7775 }
7776
7777 #[test]
7778 #[should_panic(
7779 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7780 )]
7781 fn test_delete_sql_with_empty_not_in_array_panics() {
7782 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7783 "id".to_string(),
7784 FilterOperator::NotIn,
7785 FilterValue::Array(vec![]),
7786 ));
7787 let _ = queryset.delete_sql();
7788 }
7789
7790 #[test]
7791 #[should_panic(
7792 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7793 )]
7794 fn test_delete_sql_with_empty_not_in_single_condition_panics() {
7795 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::and(vec![
7796 FilterCondition::Single(Filter::new(
7797 "id".to_string(),
7798 FilterOperator::NotIn,
7799 FilterValue::List(vec![]),
7800 )),
7801 ]));
7802 let _ = queryset.delete_sql();
7803 }
7804
7805 #[test]
7806 #[should_panic(
7807 expected = "DELETE without WHERE clause is not allowed. Use .filter() to specify which rows to delete."
7808 )]
7809 fn test_delete_sql_with_not_empty_in_panics() {
7810 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::not(Filter::new(
7811 "id".to_string(),
7812 FilterOperator::In,
7813 FilterValue::List(vec![]),
7814 )));
7815 let _ = queryset.delete_sql();
7816 }
7817
7818 #[test]
7819 fn test_delete_sql_with_empty_in_list_matches_no_rows() {
7820 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7821 "id".to_string(),
7822 FilterOperator::In,
7823 FilterValue::List(vec![]),
7824 ));
7825
7826 let (sql, params) = queryset.delete_sql();
7827
7828 assert_eq!(sql, r#"DELETE FROM "test_users" WHERE FALSE"#);
7829 assert!(params.is_empty());
7830 }
7831
7832 #[test]
7833 fn test_delete_sql_keeps_real_filter_beside_empty_not_in() {
7834 let queryset = QuerySet::<TestUser>::new()
7835 .filter(Filter::new(
7836 "id".to_string(),
7837 FilterOperator::Eq,
7838 FilterValue::Integer(1),
7839 ))
7840 .filter(Filter::new(
7841 "id".to_string(),
7842 FilterOperator::NotIn,
7843 FilterValue::List(vec![]),
7844 ));
7845
7846 let (sql, params) = queryset.delete_sql();
7847
7848 assert_eq!(
7849 sql,
7850 r#"DELETE FROM "test_users" WHERE ("id" = $1 AND TRUE)"#
7851 );
7852 assert_eq!(params, vec!["1"]);
7853 }
7854
7855 #[test]
7856 fn test_empty_and_filter_condition_is_always_true() {
7857 assert!(FilterCondition::and(Vec::new()).is_always_true());
7858 assert!(!FilterCondition::and(Vec::new()).is_always_false());
7859 assert!(!FilterCondition::or(Vec::new()).is_always_true());
7860 assert!(FilterCondition::or(Vec::new()).is_always_false());
7861 assert!(!FilterCondition::not(FilterCondition::and(Vec::new())).is_always_true());
7862 assert!(FilterCondition::not(FilterCondition::and(Vec::new())).is_always_false());
7863
7864 let empty_not_in = Filter::new(
7865 "id".to_string(),
7866 FilterOperator::NotIn,
7867 FilterValue::List(vec![]),
7868 );
7869 assert!(empty_not_in.is_always_true());
7870 assert!(!empty_not_in.is_always_false());
7871 assert!(FilterCondition::Single(empty_not_in.clone()).is_always_true());
7872 assert!(!FilterCondition::Single(empty_not_in.clone()).is_always_false());
7873 assert!(
7874 FilterCondition::and(vec![FilterCondition::Single(empty_not_in.clone())])
7875 .is_always_true()
7876 );
7877
7878 let empty_in = Filter::new(
7879 "id".to_string(),
7880 FilterOperator::In,
7881 FilterValue::List(vec![]),
7882 );
7883 assert!(!empty_in.is_always_true());
7884 assert!(empty_in.is_always_false());
7885 assert!(FilterCondition::not(empty_in.clone()).is_always_true());
7886 assert!(!FilterCondition::not(empty_in).is_always_false());
7887 assert!(!FilterCondition::not(empty_not_in).is_always_true());
7888 }
7889
7890 #[test]
7891 fn test_filter_operators() {
7892 let queryset = QuerySet::<TestUser>::new()
7893 .filter(Filter::new(
7894 "id".to_string(),
7895 FilterOperator::Gte,
7896 FilterValue::Integer(5),
7897 ))
7898 .filter(Filter::new(
7899 "username".to_string(),
7900 FilterOperator::Ne,
7901 FilterValue::String("admin".to_string()),
7902 ));
7903
7904 let (sql, params) = queryset.delete_sql();
7905
7906 assert_eq!(
7907 sql,
7908 "DELETE FROM \"test_users\" WHERE (\"id\" >= $1 AND \"username\" <> $2)"
7909 );
7910 assert_eq!(params, vec!["5", "admin"]);
7911 }
7912
7913 #[test]
7914 fn test_null_value_filter() {
7915 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7916 "email".to_string(),
7917 FilterOperator::Eq,
7918 FilterValue::Null,
7919 ));
7920
7921 let (sql, params) = queryset.delete_sql();
7922
7923 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"email\" IS NULL");
7924 assert_eq!(params, Vec::<String>::new());
7925 }
7926
7927 #[test]
7928 fn test_not_null_value_filter() {
7929 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
7930 "email".to_string(),
7931 FilterOperator::Ne,
7932 FilterValue::Null,
7933 ));
7934
7935 let (sql, params) = queryset.delete_sql();
7936
7937 assert_eq!(
7938 sql,
7939 "DELETE FROM \"test_users\" WHERE \"email\" IS NOT NULL"
7940 );
7941 assert_eq!(params, Vec::<String>::new());
7942 }
7943
7944 #[test]
7947 fn test_select_related_query_generation() {
7948 let queryset = QuerySet::<TestUser>::new().select_related(&["profile", "department"]);
7950
7951 let stmt = queryset.select_related_query();
7952
7953 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryStatementBuilder};
7955 let sql = stmt.build(PostgresQueryBuilder).0;
7956
7957 assert!(sql.contains("SELECT"));
7958 assert!(sql.contains("test_users"));
7959 assert!(sql.contains("LEFT JOIN"));
7960 }
7961
7962 #[test]
7963 fn test_prefetch_related_queries_generation() {
7964 let queryset = QuerySet::<TestUser>::new().prefetch_related(&["posts", "comments"]);
7966 let pk_values = vec![1, 2, 3];
7967
7968 let queries = queryset.prefetch_related_queries(&pk_values);
7969
7970 assert_eq!(queries.len(), 2);
7972
7973 assert_eq!(queries[0].0, "posts");
7975 assert_eq!(queries[1].0, "comments");
7976 }
7977
7978 #[test]
7979 fn test_prefetch_related_queries_empty_pk_values() {
7980 let queryset = QuerySet::<TestUser>::new().prefetch_related(&["posts", "comments"]);
7981 let pk_values = vec![];
7982
7983 let queries = queryset.prefetch_related_queries(&pk_values);
7984
7985 assert_eq!(queries.len(), 0);
7987 }
7988
7989 #[test]
7990 fn test_select_related_and_prefetch_together() {
7991 let queryset = QuerySet::<TestUser>::new()
7993 .select_related(&["profile"])
7994 .prefetch_related(&["posts", "comments"]);
7995
7996 let select_stmt = queryset.select_related_query();
7998 use reinhardt_query::prelude::{PostgresQueryBuilder, QueryStatementBuilder};
7999 let select_sql = select_stmt.build(PostgresQueryBuilder).0;
8000 assert!(select_sql.contains("LEFT JOIN"));
8001
8002 let pk_values = vec![1, 2, 3];
8004 let prefetch_queries = queryset.prefetch_related_queries(&pk_values);
8005 assert_eq!(prefetch_queries.len(), 2);
8006 }
8007
8008 #[test]
8011 fn test_smallvec_stack_allocation_within_capacity() {
8012 let mut queryset = QuerySet::<TestUser>::new();
8014
8015 for i in 0..10 {
8016 queryset = queryset.filter(Filter::new(
8017 format!("field{}", i),
8018 FilterOperator::Eq,
8019 FilterValue::Integer(i as i64),
8020 ));
8021 }
8022
8023 assert_eq!(queryset.filters.len(), 10);
8025
8026 let (sql, params) = queryset.delete_sql();
8028 assert!(sql.contains("WHERE"));
8029 assert_eq!(params.len(), 10);
8030 }
8031
8032 #[test]
8033 fn test_smallvec_heap_fallback_over_capacity() {
8034 let mut queryset = QuerySet::<TestUser>::new();
8036
8037 for i in 0..15 {
8038 queryset = queryset.filter(Filter::new(
8039 format!("field{}", i),
8040 FilterOperator::Eq,
8041 FilterValue::Integer(i as i64),
8042 ));
8043 }
8044
8045 assert_eq!(queryset.filters.len(), 15);
8047
8048 let (sql, params) = queryset.delete_sql();
8050 assert!(sql.contains("WHERE"));
8051 assert_eq!(params.len(), 15);
8052 }
8053
8054 #[test]
8055 fn test_smallvec_typical_use_case_1_5_filters() {
8056 let queryset = QuerySet::<TestUser>::new()
8058 .filter(Filter::new(
8059 "username".to_string(),
8060 FilterOperator::StartsWith,
8061 FilterValue::String("admin".to_string()),
8062 ))
8063 .filter(Filter::new(
8064 "email".to_string(),
8065 FilterOperator::Contains,
8066 FilterValue::String("example.com".to_string()),
8067 ))
8068 .filter(Filter::new(
8069 "id".to_string(),
8070 FilterOperator::Gt,
8071 FilterValue::Integer(100),
8072 ));
8073
8074 assert_eq!(queryset.filters.len(), 3);
8076
8077 let (sql, params) = queryset.delete_sql();
8079 assert!(sql.contains("WHERE"));
8080 assert!(sql.contains("\"username\" LIKE"));
8081 assert!(sql.contains("\"email\" LIKE"));
8082 assert!(sql.contains("\"id\" >"));
8083 assert_eq!(params.len(), 3);
8084 }
8085
8086 #[test]
8087 fn test_smallvec_empty_initialization() {
8088 let queryset = QuerySet::<TestUser>::new();
8090
8091 assert_eq!(queryset.filters.len(), 0);
8092 assert!(queryset.filters.is_empty());
8093
8094 let (where_clause, params) = queryset.build_where_clause();
8096 assert!(where_clause.is_empty());
8097 assert!(params.is_empty());
8098 }
8099
8100 #[test]
8101 fn test_smallvec_single_filter() {
8102 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8104 "id".to_string(),
8105 FilterOperator::Eq,
8106 FilterValue::Integer(1),
8107 ));
8108
8109 assert_eq!(queryset.filters.len(), 1);
8110
8111 let (sql, params) = queryset.delete_sql();
8112 assert_eq!(sql, "DELETE FROM \"test_users\" WHERE \"id\" = $1");
8113 assert_eq!(params, vec!["1"]);
8114 }
8115
8116 #[rstest]
8117 #[case("username", r#""username""#)]
8118 #[case("user_id", r#""user_id""#)]
8119 #[case(r#"a"b"#, r#""a""b""#)]
8120 #[case("field; DROP TABLE users", r#""field; DROP TABLE users""#)]
8121 #[case("", r#""""#)]
8122 #[case("authors.id", r#""authors"."id""#)]
8123 #[case("schema.table.column", r#""schema"."table"."column""#)]
8124 fn test_quote_identifier(#[case] input: &str, #[case] expected: &str) {
8125 let result = super::quote_identifier(input);
8130
8131 assert_eq!(result, expected);
8133 }
8134
8135 #[rstest]
8136 fn test_outerref_filter_uses_safe_quoting() {
8137 use crate::orm::expressions::OuterRef;
8139 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8140 "author_id".to_string(),
8141 FilterOperator::Eq,
8142 FilterValue::OuterRef(OuterRef::new("id")),
8143 ));
8144
8145 let sql = queryset.to_sql();
8147
8148 assert_eq!(
8150 sql,
8151 r#"SELECT * FROM "test_users" WHERE "author_id" = "id""#
8152 );
8153 }
8154
8155 #[rstest]
8156 fn test_array_contains_filter_quotes_field() {
8157 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8159 "tags".to_string(),
8160 FilterOperator::ArrayContains,
8161 FilterValue::Array(vec!["rust".to_string(), "web".to_string()]),
8162 ));
8163
8164 let sql = queryset.to_sql();
8166
8167 assert_eq!(
8169 sql,
8170 r#"SELECT * FROM "test_users" WHERE "tags" @> ARRAY['rust', 'web']"#
8171 );
8172 }
8173
8174 #[rstest]
8175 fn test_outerref_dot_separated_renders_qualified_column() {
8176 use crate::orm::expressions::OuterRef;
8178 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8179 "author_id".to_string(),
8180 FilterOperator::Eq,
8181 FilterValue::OuterRef(OuterRef::new("authors.id")),
8182 ));
8183
8184 let sql = queryset.to_sql();
8186
8187 assert_eq!(
8189 sql,
8190 r#"SELECT * FROM "test_users" WHERE "author_id" = "authors"."id""#
8191 );
8192 }
8193
8194 #[rstest]
8195 fn test_injection_attempt_in_field_name_is_quoted() {
8196 let malicious_field = r#"id" OR 1=1 --"#.to_string();
8199
8200 let quoted = super::quote_identifier(&malicious_field);
8202
8203 assert_eq!(quoted, r#""id"" OR 1=1 --""#);
8206 assert!(quoted.starts_with('"'));
8208 assert!(quoted.ends_with('"'));
8209 }
8210
8211 #[rstest]
8212 #[should_panic(expected = "SQL identifier must not contain null bytes")]
8213 fn test_quote_identifier_rejects_null_bytes() {
8214 let field_with_null = "field\0name";
8216
8217 super::quote_identifier(field_with_null);
8219
8220 }
8222
8223 #[rstest]
8224 #[case(FilterOperator::Ne, "<>")]
8225 #[case(FilterOperator::Gt, ">")]
8226 #[case(FilterOperator::Gte, ">=")]
8227 #[case(FilterOperator::Lt, "<")]
8228 #[case(FilterOperator::Lte, "<=")]
8229 fn test_outerref_comparison_operators(#[case] op: FilterOperator, #[case] sql_op: &str) {
8230 use crate::orm::expressions::OuterRef;
8232 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8233 "author_id".to_string(),
8234 op,
8235 FilterValue::OuterRef(OuterRef::new("id")),
8236 ));
8237
8238 let sql = queryset.to_sql();
8240
8241 let expected = format!(
8243 r#"SELECT * FROM "test_users" WHERE "author_id" {} "id""#,
8244 sql_op
8245 );
8246 assert_eq!(sql, expected);
8247 }
8248
8249 #[rstest]
8250 fn test_array_contained_by_filter_quotes_field() {
8251 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8253 "tags".to_string(),
8254 FilterOperator::ArrayContainedBy,
8255 FilterValue::Array(vec!["rust".to_string()]),
8256 ));
8257
8258 let sql = queryset.to_sql();
8260
8261 assert_eq!(
8263 sql,
8264 r#"SELECT * FROM "test_users" WHERE "tags" <@ ARRAY['rust']"#
8265 );
8266 }
8267
8268 #[rstest]
8269 fn test_array_overlap_filter_quotes_field() {
8270 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8272 "tags".to_string(),
8273 FilterOperator::ArrayOverlap,
8274 FilterValue::Array(vec!["rust".to_string()]),
8275 ));
8276
8277 let sql = queryset.to_sql();
8279
8280 assert_eq!(
8282 sql,
8283 r#"SELECT * FROM "test_users" WHERE "tags" && ARRAY['rust']"#
8284 );
8285 }
8286
8287 #[rstest]
8288 fn test_full_text_match_filter_quotes_field() {
8289 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8291 "content".to_string(),
8292 FilterOperator::FullTextMatch,
8293 FilterValue::String("search term".to_string()),
8294 ));
8295
8296 let sql = queryset.to_sql();
8298
8299 assert_eq!(
8301 sql,
8302 r#"SELECT * FROM "test_users" WHERE "content" @@ plainto_tsquery('english', 'search term')"#
8303 );
8304 }
8305
8306 #[rstest]
8307 fn test_jsonb_contains_filter_quotes_field() {
8308 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8310 "metadata".to_string(),
8311 FilterOperator::JsonbContains,
8312 FilterValue::String(r#"{"key": "value"}"#.to_string()),
8313 ));
8314
8315 let sql = queryset.to_sql();
8317
8318 assert_eq!(
8320 sql,
8321 r#"SELECT * FROM "test_users" WHERE "metadata" @> '{"key": "value"}'::jsonb"#
8322 );
8323 }
8324
8325 #[rstest]
8326 fn test_jsonb_contained_by_filter_quotes_field() {
8327 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8329 "metadata".to_string(),
8330 FilterOperator::JsonbContainedBy,
8331 FilterValue::String(r#"{"key": "value"}"#.to_string()),
8332 ));
8333
8334 let sql = queryset.to_sql();
8336
8337 assert_eq!(
8339 sql,
8340 r#"SELECT * FROM "test_users" WHERE "metadata" <@ '{"key": "value"}'::jsonb"#
8341 );
8342 }
8343
8344 #[rstest]
8345 fn test_jsonb_key_exists_filter_quotes_field() {
8346 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8348 "metadata".to_string(),
8349 FilterOperator::JsonbKeyExists,
8350 FilterValue::String("key".to_string()),
8351 ));
8352
8353 let sql = queryset.to_sql();
8355
8356 assert_eq!(
8358 sql,
8359 r#"SELECT * FROM "test_users" WHERE "metadata" ? 'key'"#
8360 );
8361 }
8362
8363 #[rstest]
8364 fn test_jsonb_any_key_exists_filter_quotes_field() {
8365 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8367 "metadata".to_string(),
8368 FilterOperator::JsonbAnyKeyExists,
8369 FilterValue::Array(vec!["key1".to_string(), "key2".to_string()]),
8370 ));
8371
8372 let sql = queryset.to_sql();
8374
8375 assert_eq!(
8377 sql,
8378 r#"SELECT * FROM "test_users" WHERE "metadata" ?| array['key1', 'key2']"#
8379 );
8380 }
8381
8382 #[rstest]
8383 fn test_jsonb_all_keys_exist_filter_quotes_field() {
8384 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8386 "metadata".to_string(),
8387 FilterOperator::JsonbAllKeysExist,
8388 FilterValue::Array(vec!["key1".to_string(), "key2".to_string()]),
8389 ));
8390
8391 let sql = queryset.to_sql();
8393
8394 assert_eq!(
8396 sql,
8397 r#"SELECT * FROM "test_users" WHERE "metadata" ?& array['key1', 'key2']"#
8398 );
8399 }
8400
8401 #[rstest]
8402 fn test_jsonb_path_exists_filter_quotes_field() {
8403 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8405 "metadata".to_string(),
8406 FilterOperator::JsonbPathExists,
8407 FilterValue::String("$.key".to_string()),
8408 ));
8409
8410 let sql = queryset.to_sql();
8412
8413 assert_eq!(
8415 sql,
8416 r#"SELECT * FROM "test_users" WHERE "metadata" @'$.key' "#
8417 );
8418 }
8419
8420 #[rstest]
8421 #[case(
8422 Filter::new("username", FilterOperator::IExact, FilterValue::String("Alice".to_string())),
8423 r#"SELECT * FROM "test_users" WHERE "username" ILIKE 'Alice' ESCAPE '\'"#
8424 )]
8425 #[case(
8426 Filter::new("email", FilterOperator::IContains, FilterValue::String("example.com".to_string())),
8427 r#"SELECT * FROM "test_users" WHERE "email" ILIKE '%example.com%' ESCAPE '\'"#
8428 )]
8429 #[case(
8430 Filter::new("username", FilterOperator::IStartsWith, FilterValue::String("ali".to_string())),
8431 r#"SELECT * FROM "test_users" WHERE "username" ILIKE 'ali%' ESCAPE '\'"#
8432 )]
8433 #[case(
8434 Filter::new("username", FilterOperator::IEndsWith, FilterValue::String("ice".to_string())),
8435 r#"SELECT * FROM "test_users" WHERE "username" ILIKE '%ice' ESCAPE '\'"#
8436 )]
8437 #[case(
8438 Filter::new("username", FilterOperator::Regex, FilterValue::String("^a".to_string())),
8439 r#"SELECT * FROM "test_users" WHERE "username" ~ '^a'"#
8440 )]
8441 #[case(
8442 Filter::new("username", FilterOperator::IRegex, FilterValue::String("^a".to_string())),
8443 r#"SELECT * FROM "test_users" WHERE "username" ~* '^a'"#
8444 )]
8445 fn test_django_style_string_lookup_filters(#[case] filter: Filter, #[case] expected: &str) {
8446 let queryset = QuerySet::<TestUser>::new().filter(filter);
8448
8449 let sql = queryset.to_sql();
8451
8452 assert_eq!(sql, expected);
8454 }
8455
8456 #[rstest]
8457 fn test_filter_or_chain_generates_expected_sql() {
8458 let condition = TestUser::field_username()
8460 .exact("alice")
8461 .or(TestUser::field_email().icontains("example.com"));
8462 let queryset = QuerySet::<TestUser>::new().filter(condition);
8463
8464 let sql = queryset.to_sql();
8466
8467 assert_eq!(
8469 sql,
8470 r#"SELECT * FROM "test_users" WHERE ("username" = 'alice' OR "email" ILIKE '%example.com%' ESCAPE '\')"#
8471 );
8472 }
8473
8474 #[rstest]
8475 fn test_filter_and_chain_generates_expected_sql() {
8476 let condition = TestUser::field_username()
8478 .exact("alice")
8479 .and(TestUser::field_id().gte(10));
8480 let queryset = QuerySet::<TestUser>::new().filter(condition);
8481
8482 let sql = queryset.to_sql();
8484
8485 assert_eq!(
8487 sql,
8488 r#"SELECT * FROM "test_users" WHERE ("username" = 'alice' AND "id" >= 10)"#
8489 );
8490 }
8491
8492 #[rstest]
8493 fn test_filter_not_chain_generates_expected_sql() {
8494 let condition = TestUser::field_username().exact("alice").not();
8496 let queryset = QuerySet::<TestUser>::new().filter(condition);
8497
8498 let sql = queryset.to_sql();
8500
8501 assert_eq!(
8503 sql,
8504 r#"SELECT * FROM "test_users" WHERE NOT "username" = 'alice'"#
8505 );
8506 }
8507
8508 #[rstest]
8509 fn test_composite_only_filter_is_recognized_by_delete_sql() {
8510 let queryset = QuerySet::<TestUser>::new().filter(
8512 TestUser::field_username()
8513 .exact("alice")
8514 .or(TestUser::field_email().icontains("example.com")),
8515 );
8516
8517 let (sql, params) = queryset.delete_sql();
8519
8520 assert_eq!(
8522 sql,
8523 r#"DELETE FROM "test_users" WHERE ("username" = $1 OR "email" ILIKE $2 ESCAPE '\')"#
8524 );
8525 assert_eq!(params, vec!["alice", "%example.com%"]);
8526 }
8527
8528 #[rstest]
8529 fn test_over_deep_filter_condition_returns_error_and_to_sql_stays_safe() {
8530 let mut condition = FilterCondition::Single(TestUser::field_username().exact("alice"));
8532 for _ in 0..=MAX_FILTER_CONDITION_DEPTH {
8533 condition = FilterCondition::not(condition);
8534 }
8535 let queryset = QuerySet::<TestUser>::new().filter(condition);
8536
8537 let result = queryset.build_where_condition();
8539 let sql = queryset.to_sql();
8540
8541 assert!(matches!(
8543 result,
8544 Err(reinhardt_core::exception::Error::Validation(_))
8545 ));
8546 assert_eq!(sql, r#"SELECT * FROM "test_users" WHERE FALSE"#);
8547 }
8548
8549 #[rstest]
8550 fn test_empty_or_filter_condition_is_false() {
8551 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::Or(Vec::new()));
8552
8553 assert_eq!(
8554 queryset.to_sql(),
8555 r#"SELECT * FROM "test_users" WHERE FALSE"#
8556 );
8557 }
8558
8559 #[rstest]
8560 fn test_empty_not_in_filter_condition_is_always_true() {
8561 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8562 "id",
8563 FilterOperator::NotIn,
8564 FilterValue::List(vec![]),
8565 ));
8566
8567 assert_eq!(
8568 queryset.to_sql(),
8569 r#"SELECT * FROM "test_users" WHERE TRUE"#
8570 );
8571 }
8572
8573 #[rstest]
8574 fn test_not_empty_and_filter_condition_is_false() {
8575 let queryset = QuerySet::<TestUser>::new().filter(FilterCondition::Not(Box::new(
8576 FilterCondition::And(Vec::new()),
8577 )));
8578
8579 assert_eq!(
8580 queryset.to_sql(),
8581 r#"SELECT * FROM "test_users" WHERE NOT TRUE"#
8582 );
8583 }
8584
8585 #[rstest]
8586 #[case(
8587 Filter::new("email", FilterOperator::IContains, FilterValue::String("100%_match\\".to_string())),
8588 r#"SELECT * FROM "test_users" WHERE "email" ILIKE '%100\%\_match\\%' ESCAPE '\'"#
8589 )]
8590 #[case(
8591 Filter::new("username", FilterOperator::IExact, FilterValue::String("alice_admin".to_string())),
8592 r#"SELECT * FROM "test_users" WHERE "username" ILIKE 'alice\_admin' ESCAPE '\'"#
8593 )]
8594 fn test_django_style_case_insensitive_like_filters_escape_metacharacters(
8595 #[case] filter: Filter,
8596 #[case] expected: &str,
8597 ) {
8598 let queryset = QuerySet::<TestUser>::new().filter(filter);
8600
8601 let sql = queryset.to_sql();
8603
8604 assert_eq!(sql, expected);
8606 }
8607
8608 #[rstest]
8609 #[case(
8610 Filter::new("email", FilterOperator::Contains, FilterValue::String("100%_match\\".to_string())),
8611 r#"SELECT * FROM "test_users" WHERE "email" LIKE '%100\%\_match\\%' ESCAPE '\'"#
8612 )]
8613 #[case(
8614 Filter::new("username", FilterOperator::StartsWith, FilterValue::String("alice_admin".to_string())),
8615 r#"SELECT * FROM "test_users" WHERE "username" LIKE 'alice\_admin%' ESCAPE '\'"#
8616 )]
8617 #[case(
8618 Filter::new("username", FilterOperator::EndsWith, FilterValue::String("100%".to_string())),
8619 r#"SELECT * FROM "test_users" WHERE "username" LIKE '%100\%' ESCAPE '\'"#
8620 )]
8621 fn test_django_style_case_sensitive_like_filters_escape_metacharacters(
8622 #[case] filter: Filter,
8623 #[case] expected: &str,
8624 ) {
8625 let queryset = QuerySet::<TestUser>::new().filter(filter);
8627
8628 let sql = queryset.to_sql();
8630
8631 assert_eq!(sql, expected);
8633 }
8634
8635 #[rstest]
8636 fn test_django_style_is_in_filter_accepts_typed_values() {
8637 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8639 "id",
8640 FilterOperator::In,
8641 FilterValue::List(vec![FilterValue::Integer(1), FilterValue::Integer(2)]),
8642 ));
8643
8644 let sql = queryset.to_sql();
8646
8647 assert_eq!(sql, r#"SELECT * FROM "test_users" WHERE "id" IN (1, 2)"#);
8649 }
8650
8651 #[rstest]
8652 fn test_django_style_between_range_filter() {
8653 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8655 "id",
8656 FilterOperator::Range,
8657 FilterValue::Range(
8658 Box::new(FilterValue::Integer(10)),
8659 Box::new(FilterValue::Integer(20)),
8660 ),
8661 ));
8662
8663 let sql = queryset.to_sql();
8665
8666 assert_eq!(
8668 sql,
8669 r#"SELECT * FROM "test_users" WHERE "id" BETWEEN 10 AND 20"#
8670 );
8671 }
8672
8673 #[rstest]
8674 fn test_django_style_date_part_filter_expression() {
8675 let queryset = QuerySet::<TestUser>::new().filter(Filter::expression(
8677 "EXTRACT(YEAR FROM \"created_at\")",
8678 FilterOperator::Eq,
8679 FilterValue::Integer(2026),
8680 ));
8681
8682 let sql = queryset.to_sql();
8684
8685 assert_eq!(
8687 sql,
8688 r#"SELECT * FROM "test_users" WHERE EXTRACT(YEAR FROM "created_at") = 2026"#
8689 );
8690 }
8691
8692 #[rstest]
8693 fn test_public_filter_new_treats_expression_like_field_as_quoted_column() {
8694 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8696 "__reinhardt_filter_expr:EXTRACT(YEAR FROM \"created_at\")",
8697 FilterOperator::Eq,
8698 FilterValue::Integer(2026),
8699 ));
8700
8701 let sql = queryset.to_sql();
8703
8704 assert_eq!(
8706 sql,
8707 r#"SELECT * FROM "test_users" WHERE "__reinhardt_filter_expr:EXTRACT(YEAR FROM ""created_at"")" = 2026"#
8708 );
8709 }
8710
8711 #[rstest]
8712 fn test_public_column_filter_uses_mutated_field_consistently() {
8713 let mut filter = Filter::new(
8715 "username",
8716 FilterOperator::Eq,
8717 FilterValue::String("alice".into()),
8718 );
8719 filter.field = "email".to_string();
8720 let queryset = QuerySet::<TestUser>::new().filter(filter);
8721
8722 let sql = queryset.to_sql();
8724
8725 assert_eq!(sql, r#"SELECT * FROM "test_users" WHERE "email" = 'alice'"#);
8727 }
8728
8729 #[rstest]
8730 fn test_mutated_transformed_filter_field_falls_back_to_quoted_column() {
8731 let mut filter = TestUser::field_created_at().year().eq(2026);
8733 filter.field = "EXTRACT(MONTH FROM \"created_at\")".to_string();
8734 let queryset = QuerySet::<TestUser>::new().filter(filter);
8735
8736 let sql = queryset.to_sql();
8738
8739 assert_eq!(
8741 sql,
8742 r#"SELECT * FROM "test_users" WHERE "EXTRACT(MONTH FROM ""created_at"")" = 2026"#
8743 );
8744 }
8745
8746 #[rstest]
8747 fn test_field_accessor_lookup_helpers_generate_expected_sql() {
8748 let queryset = QuerySet::<TestUser>::new()
8750 .filter(TestUser::field_username().exact("alice"))
8751 .filter(TestUser::field_email().icontains("example.com"))
8752 .filter(TestUser::field_id().is_in([1_i64, 2, 3]))
8753 .filter(TestUser::field_created_at().year().gte(2026));
8754
8755 let sql = queryset.to_sql();
8757
8758 assert_eq!(
8760 sql,
8761 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)"#
8762 );
8763 }
8764
8765 #[rstest]
8766 fn test_field_accessor_null_not_in_and_range_helpers_generate_expected_sql() {
8767 let queryset = QuerySet::<TestUser>::new()
8769 .filter(TestUser::field_email().is_not_null())
8770 .filter(TestUser::field_id().not_in([10_i64, 20]))
8771 .filter(TestUser::field_id().range(100_i64, 200));
8772
8773 let sql = queryset.to_sql();
8775
8776 assert_eq!(
8778 sql,
8779 r#"SELECT * FROM "test_users" WHERE ("email" IS NOT NULL AND "id" NOT IN (10, 20) AND "id" BETWEEN 100 AND 200)"#
8780 );
8781 }
8782
8783 #[rstest]
8784 fn test_field_accessor_string_lookup_variants_generate_expected_sql() {
8785 let queryset = QuerySet::<TestUser>::new()
8787 .filter(TestUser::field_username().contains("lic"))
8788 .filter(TestUser::field_username().starts_with("a"))
8789 .filter(TestUser::field_username().ends_with("e"))
8790 .filter(TestUser::field_username().istarts_with("AL"))
8791 .filter(TestUser::field_username().iends_with("CE"))
8792 .filter(TestUser::field_username().regex("^a.*e$"))
8793 .filter(TestUser::field_username().iregex("^A.*E$"));
8794
8795 let sql = queryset.to_sql();
8797
8798 assert_eq!(
8800 sql,
8801 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$')"#
8802 );
8803 }
8804
8805 #[rstest]
8806 #[case(TestUser::field_created_at().date().eq("2026-06-10"), r#"SELECT * FROM "test_users" WHERE DATE("created_at") = '2026-06-10'"#)]
8807 #[case(TestUser::field_created_at().time().eq("05:00:00"), r#"SELECT * FROM "test_users" WHERE TIME("created_at") = '05:00:00'"#)]
8808 #[case(TestUser::field_created_at().month().eq(6), r#"SELECT * FROM "test_users" WHERE EXTRACT(MONTH FROM "created_at") = 6"#)]
8809 #[case(TestUser::field_created_at().day().eq(10), r#"SELECT * FROM "test_users" WHERE EXTRACT(DAY FROM "created_at") = 10"#)]
8810 #[case(TestUser::field_created_at().week().eq(24), r#"SELECT * FROM "test_users" WHERE EXTRACT(WEEK FROM "created_at") = 24"#)]
8811 #[case(TestUser::field_created_at().week_day().eq(4), r#"SELECT * FROM "test_users" WHERE (EXTRACT(DOW FROM "created_at") + 1) = 4"#)]
8812 #[case(TestUser::field_created_at().iso_week_day().eq(3), r#"SELECT * FROM "test_users" WHERE EXTRACT(ISODOW FROM "created_at") = 3"#)]
8813 #[case(TestUser::field_created_at().quarter().eq(2), r#"SELECT * FROM "test_users" WHERE EXTRACT(QUARTER FROM "created_at") = 2"#)]
8814 #[case(TestUser::field_created_at().hour().gte(5), r#"SELECT * FROM "test_users" WHERE EXTRACT(HOUR FROM "created_at") >= 5"#)]
8815 #[case(TestUser::field_created_at().minute().lt(30), r#"SELECT * FROM "test_users" WHERE EXTRACT(MINUTE FROM "created_at") < 30"#)]
8816 #[case(TestUser::field_created_at().second().lte(59), r#"SELECT * FROM "test_users" WHERE EXTRACT(SECOND FROM "created_at") <= 59"#)]
8817 fn test_field_accessor_date_time_transforms_generate_expected_sql(
8818 #[case] filter: Filter,
8819 #[case] expected: &str,
8820 ) {
8821 let queryset = QuerySet::<TestUser>::new().filter(filter);
8823
8824 let sql = queryset.to_sql();
8826
8827 assert_eq!(sql, expected);
8829 }
8830
8831 #[rstest]
8832 fn test_field_accessor_postgres_array_jsonb_and_range_helpers_generate_expected_sql() {
8833 let queryset = QuerySet::<TestUser>::new()
8835 .filter(TestUser::field_tags().array_contains(["rust", "async"]))
8836 .filter(TestUser::field_tags().array_overlap(["web", "orm"]))
8837 .filter(TestUser::field_metadata().jsonb_contains(r#"{"active": true}"#))
8838 .filter(TestUser::field_metadata().jsonb_has_any_keys(["tier", "plan"]))
8839 .filter(TestUser::field_active_period().range_overlaps("[2026-01-01,2027-01-01)"));
8840
8841 let sql = queryset.to_sql();
8843
8844 assert_eq!(
8846 sql,
8847 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)')"#
8848 );
8849 }
8850
8851 #[rstest]
8852 fn test_complex_django_style_lookup_query_combines_order_distinct_and_limit() {
8853 let queryset = QuerySet::<TestUser>::new()
8855 .filter(TestUser::field_email().icontains("example.com"))
8856 .filter(TestUser::field_username().is_not_null())
8857 .filter(TestUser::field_created_at().year().range(2024, 2026))
8858 .distinct()
8859 .order_by(&["-created_at", "username"])
8860 .limit(25)
8861 .offset(50);
8862
8863 let sql = queryset.to_sql();
8865
8866 assert_eq!(
8868 sql,
8869 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"#
8870 );
8871 }
8872
8873 #[rstest]
8874 fn test_range_contains_filter_quotes_field() {
8875 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8877 "age_range".to_string(),
8878 FilterOperator::RangeContains,
8879 FilterValue::String("25".to_string()),
8880 ));
8881
8882 let sql = queryset.to_sql();
8884
8885 assert_eq!(
8887 sql,
8888 r#"SELECT * FROM "test_users" WHERE "age_range" @> '25'"#
8889 );
8890 }
8891
8892 #[rstest]
8893 fn test_range_contained_by_filter_quotes_field() {
8894 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8896 "age_range".to_string(),
8897 FilterOperator::RangeContainedBy,
8898 FilterValue::String("[20, 30]".to_string()),
8899 ));
8900
8901 let sql = queryset.to_sql();
8903
8904 assert_eq!(
8906 sql,
8907 r#"SELECT * FROM "test_users" WHERE "age_range" <@ '[20, 30]'"#
8908 );
8909 }
8910
8911 #[rstest]
8912 fn test_range_overlaps_filter_quotes_field() {
8913 let queryset = QuerySet::<TestUser>::new().filter(Filter::new(
8915 "age_range".to_string(),
8916 FilterOperator::RangeOverlaps,
8917 FilterValue::String("[20, 30]".to_string()),
8918 ));
8919
8920 let sql = queryset.to_sql();
8922
8923 assert_eq!(
8925 sql,
8926 r#"SELECT * FROM "test_users" WHERE "age_range" && '[20, 30]'"#
8927 );
8928 }
8929}