1use super::postgres_features::{ArrayAgg, JsonbAgg, JsonbBuildObject, StringAgg, TsRank};
2use crate::orm::aggregation::Aggregate;
3use crate::orm::expressions::{F, Q};
4use crate::orm::query::quote_identifier;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum AnnotationValue {
10 Value(Value),
12 Field(F),
14 Aggregate(Aggregate),
16 Expression(Expression),
18 Subquery(String),
20 ArrayAgg(ArrayAgg<serde_json::Value>),
23 StringAgg(StringAgg),
25 JsonbAgg(JsonbAgg),
27 JsonbBuildObject(JsonbBuildObject),
29 TsRank(TsRank),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum Value {
36 String(String),
38 Int(i64),
40 Float(f64),
42 Bool(bool),
44 Null,
46}
47
48impl Value {
49 pub fn to_sql(&self) -> String {
52 match self {
53 Value::String(s) => format!("'{}'", s.replace('\'', "''")),
54 Value::Int(i) => i.to_string(),
55 Value::Float(f) => f.to_string(),
56 Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
57 Value::Null => "NULL".to_string(),
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum Expression {
65 Add(Box<AnnotationValue>, Box<AnnotationValue>),
67 Subtract(Box<AnnotationValue>, Box<AnnotationValue>),
69 Multiply(Box<AnnotationValue>, Box<AnnotationValue>),
71 Divide(Box<AnnotationValue>, Box<AnnotationValue>),
73 Case {
75 whens: Vec<When>,
77 default: Option<Box<AnnotationValue>>,
79 },
80 Coalesce(Vec<AnnotationValue>),
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct When {
87 pub condition: Q,
89 pub then: AnnotationValue,
91}
92
93impl When {
94 pub fn new(condition: Q, then: AnnotationValue) -> Self {
112 Self { condition, then }
113 }
114 pub fn to_sql(&self) -> String {
117 format!(
118 "WHEN {} THEN {}",
119 self.condition.to_sql(),
120 self.then.to_sql()
121 )
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct Annotation {
128 pub alias: String,
130 pub value: AnnotationValue,
132}
133
134impl Annotation {
135 pub fn new(alias: impl Into<String>, value: AnnotationValue) -> Self {
147 Self {
148 alias: alias.into(),
149 value,
150 }
151 }
152 pub fn to_sql(&self) -> String {
155 format!(
156 "{} AS {}",
157 self.value.to_sql(),
158 quote_identifier(&self.alias)
159 )
160 }
161
162 pub fn field(alias: impl Into<String>, value: AnnotationValue) -> Self {
166 Self::new(alias, value)
167 }
168}
169
170impl AnnotationValue {
171 pub fn to_sql(&self) -> String {
174 match self {
175 AnnotationValue::Value(v) => v.to_sql(),
176 AnnotationValue::Field(f) => f.to_sql(),
177 AnnotationValue::Aggregate(a) => a.to_sql(),
178 AnnotationValue::Expression(e) => e.to_sql(),
179 AnnotationValue::Subquery(sql) => sql.clone(),
180 AnnotationValue::ArrayAgg(a) => a.to_sql(),
182 AnnotationValue::StringAgg(s) => s.to_sql(),
183 AnnotationValue::JsonbAgg(j) => j.to_sql(),
184 AnnotationValue::JsonbBuildObject(j) => j.to_sql(),
185 AnnotationValue::TsRank(t) => t.to_sql(),
186 }
187 }
188
189 pub fn to_sql_expr(&self) -> String {
191 match self {
192 AnnotationValue::Value(v) => v.to_sql(),
193 AnnotationValue::Field(f) => f.to_sql(),
194 AnnotationValue::Aggregate(a) => a.to_sql_expr(), AnnotationValue::Expression(e) => e.to_sql(),
196 AnnotationValue::Subquery(sql) => sql.clone(),
197 AnnotationValue::ArrayAgg(a) => a.to_sql(),
199 AnnotationValue::StringAgg(s) => s.to_sql(),
200 AnnotationValue::JsonbAgg(j) => j.to_sql(),
201 AnnotationValue::JsonbBuildObject(j) => j.to_sql(),
202 AnnotationValue::TsRank(t) => t.to_sql(),
203 }
204 }
205}
206
207impl Expression {
208 pub fn to_sql(&self) -> String {
211 match self {
212 Expression::Add(left, right) => {
213 format!("({} + {})", left.to_sql(), right.to_sql())
214 }
215 Expression::Subtract(left, right) => {
216 format!("({} - {})", left.to_sql(), right.to_sql())
217 }
218 Expression::Multiply(left, right) => {
219 format!("({} * {})", left.to_sql(), right.to_sql())
220 }
221 Expression::Divide(left, right) => {
222 format!("({} / {})", left.to_sql(), right.to_sql())
223 }
224 Expression::Case { whens, default } => {
225 let mut sql = String::from("CASE");
226 for when in whens {
227 sql.push(' ');
228 sql.push_str(&when.to_sql());
229 }
230 if let Some(default_val) = default {
231 sql.push_str(&format!(" ELSE {}", default_val.to_sql()));
232 }
233 sql.push_str(" END");
234 sql
235 }
236 Expression::Coalesce(values) => {
237 let values_sql: Vec<String> = values.iter().map(|v| v.to_sql()).collect();
238 format!("COALESCE({})", values_sql.join(", "))
239 }
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_value_annotation() {
250 let ann = Annotation::new("is_active", AnnotationValue::Value(Value::Bool(true)));
251 assert_eq!(ann.to_sql(), "TRUE AS \"is_active\"");
252 }
253
254 #[test]
255 fn test_field_annotation() {
256 let ann = Annotation::new("another_price", AnnotationValue::Field(F::new("price")));
257 assert_eq!(ann.to_sql(), "\"price\" AS \"another_price\"");
258 }
259
260 #[test]
261 fn test_annotation_aggregate() {
262 let agg = Aggregate::count(Some("id"));
263 let ann = Annotation::new("num_items", AnnotationValue::Aggregate(agg));
264 let sql = ann.to_sql();
265 assert!(
266 sql.contains("COUNT(id)") && sql.contains("AS \"num_items\""),
267 "SQL should contain 'COUNT(id) AS \"num_items\"'. Got: {}",
268 sql
269 );
270 }
271
272 #[test]
273 fn test_add_expression() {
274 let expr = Expression::Add(
275 Box::new(AnnotationValue::Field(F::new("price"))),
276 Box::new(AnnotationValue::Value(Value::Int(10))),
277 );
278 let ann = Annotation::new("new_price", AnnotationValue::Expression(expr));
279 assert_eq!(ann.to_sql(), "(\"price\" + 10) AS \"new_price\"");
280 }
281
282 #[test]
283 fn test_case_expression() {
284 let expr = Expression::Case {
285 whens: vec![When::new(
286 Q::new("age", ">=", "18"),
287 AnnotationValue::Value(Value::String("adult".into())),
288 )],
289 default: Some(Box::new(AnnotationValue::Value(Value::String(
290 "minor".into(),
291 )))),
292 };
293 let ann = Annotation::new("age_group", AnnotationValue::Expression(expr));
294 let sql = ann.to_sql();
295 assert!(
296 sql.starts_with("CASE") || sql.contains(" CASE "),
297 "SQL should contain CASE clause. Got: {}",
298 sql
299 );
300 assert!(
301 sql.contains("WHEN age >= 18 THEN 'adult'"),
302 "SQL should contain 'WHEN age >= 18 THEN 'adult''. Got: {}",
303 sql
304 );
305 assert!(
306 sql.contains("ELSE 'minor'"),
307 "SQL should contain 'ELSE 'minor''. Got: {}",
308 sql
309 );
310 assert!(
311 sql.ends_with("AS \"age_group\"") || sql.contains(" AS \"age_group\""),
312 "SQL should end with 'AS \"age_group\"'. Got: {}",
313 sql
314 );
315 }
316
317 #[test]
318 fn test_coalesce_expression() {
319 let expr = Expression::Coalesce(vec![
320 AnnotationValue::Field(F::new("nickname")),
321 AnnotationValue::Field(F::new("username")),
322 AnnotationValue::Value(Value::String("Anonymous".into())),
323 ]);
324 let ann = Annotation::new("display_name", AnnotationValue::Expression(expr));
325 assert_eq!(
326 ann.to_sql(),
327 "COALESCE(\"nickname\", \"username\", 'Anonymous') AS \"display_name\""
328 );
329 }
330
331 #[test]
332 fn test_complex_arithmetic() {
333 let expr = Expression::Add(
335 Box::new(AnnotationValue::Expression(Expression::Multiply(
336 Box::new(AnnotationValue::Field(F::new("price"))),
337 Box::new(AnnotationValue::Field(F::new("quantity"))),
338 ))),
339 Box::new(AnnotationValue::Field(F::new("tax"))),
340 );
341 let ann = Annotation::new("total", AnnotationValue::Expression(expr));
342 assert_eq!(
343 ann.to_sql(),
344 "((\"price\" * \"quantity\") + \"tax\") AS \"total\""
345 );
346 }
347
348 #[test]
349 fn test_division_expression() {
350 let expr = Expression::Divide(
351 Box::new(AnnotationValue::Field(F::new("total_sales"))),
352 Box::new(AnnotationValue::Field(F::new("num_orders"))),
353 );
354 let ann = Annotation::new("avg_order_value", AnnotationValue::Expression(expr));
355 assert_eq!(
356 ann.to_sql(),
357 "(\"total_sales\" / \"num_orders\") AS \"avg_order_value\""
358 );
359 }
360}
361#[cfg(test)]
366mod annotation_extended_tests {
367 use super::*;
368 use crate::orm::expressions::Q;
369 use crate::orm::query::{Filter, FilterOperator, FilterValue, QuerySet};
370 use crate::orm::{Manager, Model};
371 use reinhardt_core::validators::TableName;
372 use serde::{Deserialize, Serialize};
373
374 #[derive(Debug, Clone, Serialize, Deserialize)]
375 struct TestModel {
376 id: Option<i64>,
377 name: String,
378 }
379
380 #[derive(Clone)]
381 struct TestModelFields;
382
383 impl crate::orm::model::FieldSelector for TestModelFields {
384 fn with_alias(self, _alias: &str) -> Self {
385 self
386 }
387 }
388
389 const TEST_MODEL_TABLE: TableName = TableName::new_const("test_model");
390
391 impl Model for TestModel {
392 type PrimaryKey = i64;
393 type Fields = TestModelFields;
394 type Objects = Manager<Self>;
395
396 fn table_name() -> &'static str {
397 TEST_MODEL_TABLE.as_str()
398 }
399
400 fn new_fields() -> Self::Fields {
401 TestModelFields
402 }
403
404 fn primary_key(&self) -> Option<Self::PrimaryKey> {
405 self.id
406 }
407
408 fn set_primary_key(&mut self, key: Self::PrimaryKey) {
409 self.id = Some(key);
410 }
411 }
412
413 #[test]
414 fn test_aggregate_alias() {
416 use crate::orm::aggregation::Aggregate;
419 use crate::orm::expressions::F;
420 use crate::orm::query::QuerySet;
421
422 let qs = QuerySet::<TestModel>::new()
423 .annotate(Annotation::field(
424 "other_age",
425 AnnotationValue::Field(F::new("age")),
426 ))
427 .aggregate(Aggregate::sum("other_age").with_alias("otherage_sum"));
428
429 let sql = qs.to_sql();
430
431 assert!(
432 sql.contains("SUM") || sql.contains("age"),
433 "SQL should contain 'SUM' or 'age'. Got: {}",
434 sql
435 );
436 }
437
438 #[test]
439 fn test_aggregate_alias_1() {
441 use crate::orm::aggregation::Aggregate;
443 use crate::orm::expressions::F;
444 use crate::orm::query::QuerySet;
445
446 let qs = QuerySet::<TestModel>::new()
447 .annotate(Annotation::field(
448 "value_alias",
449 AnnotationValue::Field(F::new("value")),
450 ))
451 .aggregate(Aggregate::count(Some("value_alias")).with_alias("count_alias"));
452
453 let sql = qs.to_sql();
454
455 assert!(
456 sql.contains("COUNT") || sql.contains("value"),
457 "SQL should contain 'COUNT' or 'value'. Got: {}",
458 sql
459 );
460 }
461
462 #[test]
463 fn test_aggregate_over_annotation() {
465 use crate::orm::aggregation::Aggregate;
468 use crate::orm::expressions::F;
469 use crate::orm::query::QuerySet;
470
471 let qs = QuerySet::<TestModel>::new()
472 .annotate(Annotation::field(
473 "other_age",
474 AnnotationValue::Field(F::new("age")),
475 ))
476 .aggregate(Aggregate::sum("other_age").with_alias("otherage_sum"));
477
478 let sql = qs.to_sql();
479
480 assert!(
482 sql.contains("age") || sql.contains("other_age"),
483 "SQL should contain 'age' or 'other_age'. Got: {}",
484 sql
485 );
486 assert!(
488 sql.contains("SUM("),
489 "SQL should contain SUM clause. Got: {}",
490 sql
491 );
492 }
493
494 #[test]
495 fn test_aggregate_over_annotation_1() {
497 use crate::orm::aggregation::Aggregate;
499 use crate::orm::expressions::F;
500 use crate::orm::query::QuerySet;
501
502 let qs = QuerySet::<TestModel>::new()
503 .annotate(Annotation::field(
504 "doubled",
505 AnnotationValue::Field(F::new("value")),
506 ))
507 .aggregate(Aggregate::avg("doubled").with_alias("avg_doubled"));
508
509 let sql = qs.to_sql();
510
511 assert!(
512 sql.contains("AVG") || sql.contains("value") || sql.contains("doubled"),
513 "SQL should contain 'AVG', 'value', or 'doubled'. Got: {}",
514 sql
515 );
516 }
517
518 #[test]
519 fn test_aggregate_over_full_expression_annotation() {
521 use crate::orm::aggregation::Aggregate;
523 use crate::orm::expressions::F;
524 use crate::orm::query::QuerySet;
525
526 let qs = QuerySet::<TestModel>::new()
527 .annotate(Annotation::field(
528 "computed",
529 AnnotationValue::Field(F::new("field1")),
530 ))
531 .aggregate(Aggregate::max("computed").with_alias("max_computed"));
532
533 let sql = qs.to_sql();
534
535 assert!(
536 sql.contains("MAX") || sql.contains("field1") || sql.contains("computed"),
537 "SQL should contain 'MAX', 'field1', or 'computed'. Got: {}",
538 sql
539 );
540 }
541
542 #[test]
543 fn test_aggregate_over_full_expression_annotation_1() {
545 use crate::orm::aggregation::Aggregate;
547 use crate::orm::expressions::F;
548 use crate::orm::query::QuerySet;
549
550 let qs = QuerySet::<TestModel>::new()
551 .annotate(Annotation::field(
552 "calc",
553 AnnotationValue::Field(F::new("price")),
554 ))
555 .aggregate(Aggregate::min("calc").with_alias("min_calc"));
556
557 let sql = qs.to_sql();
558
559 assert!(
560 sql.contains("MIN") || sql.contains("price") || sql.contains("calc"),
561 "SQL should contain 'MIN', 'price', or 'calc'. Got: {}",
562 sql
563 );
564 }
565
566 #[test]
567 fn test_alias_after_values() {
569 use crate::orm::expressions::F;
571 use crate::orm::query::QuerySet;
572
573 let qs = QuerySet::<TestModel>::new()
574 .values(&["name", "age"])
575 .annotate(Annotation::field(
576 "age_alias",
577 AnnotationValue::Field(F::new("age")),
578 ));
579
580 let sql = qs.to_sql();
581
582 assert!(
583 sql.contains("name") && sql.contains("age"),
584 "SQL should contain 'name' and 'age'. Got: {}",
585 sql
586 );
587 }
588
589 #[test]
590 fn test_alias_after_values_1() {
592 use crate::orm::expressions::F;
594 use crate::orm::query::QuerySet;
595
596 let qs = QuerySet::<TestModel>::new()
597 .values_list(&["id", "name"])
598 .annotate(Annotation::field(
599 "name_alias",
600 AnnotationValue::Field(F::new("name")),
601 ));
602
603 let sql = qs.to_sql();
604
605 assert!(
606 sql.contains("id") && sql.contains("name"),
607 "SQL should contain 'id' and 'name'. Got: {}",
608 sql
609 );
610 }
611
612 #[test]
613 fn test_alias_annotate_with_aggregation() {
615 use crate::orm::aggregation::Aggregate;
618 use crate::orm::expressions::F;
619 use crate::orm::query::QuerySet;
620
621 let qs = QuerySet::<TestModel>::new()
622 .aggregate(Aggregate::count(Some("rating")).with_alias("rating_count_alias"))
623 .annotate(Annotation::field(
624 "rating_count",
625 AnnotationValue::Field(F::new("rating_count_alias")),
626 ));
627
628 let sql = qs.to_sql();
629
630 assert!(
631 sql.contains("COUNT") || sql.contains("rating"),
632 "SQL should contain 'COUNT' or 'rating'. Got: {}",
633 sql
634 );
635 }
636
637 #[test]
638 fn test_alias_annotate_with_aggregation_1() {
640 use crate::orm::aggregation::Aggregate;
642 use crate::orm::expressions::F;
643 use crate::orm::query::QuerySet;
644
645 let qs = QuerySet::<TestModel>::new()
646 .aggregate(Aggregate::sum("price").with_alias("total_alias"))
647 .annotate(Annotation::field(
648 "total",
649 AnnotationValue::Field(F::new("total_alias")),
650 ));
651
652 let sql = qs.to_sql();
653
654 assert!(
655 sql.contains("SUM") || sql.contains("price") || sql.contains("total"),
656 "SQL should contain 'SUM', 'price', or 'total'. Got: {}",
657 sql
658 );
659 }
660
661 #[test]
662 fn test_alias_annotation_expression() {
664 use crate::orm::expressions::F;
666 use crate::orm::query::QuerySet;
667
668 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
669 "expr_alias",
670 AnnotationValue::Field(F::new("field1")),
671 ));
672
673 let sql = qs.to_sql();
674
675 assert!(
676 sql.contains("field1") || sql.contains("expr_alias"),
677 "SQL should contain 'field1' or 'expr_alias'. Got: {}",
678 sql
679 );
680 }
681
682 #[test]
683 fn test_alias_annotation_expression_1() {
685 use crate::orm::expressions::F;
687 use crate::orm::query::QuerySet;
688
689 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
690 "complex",
691 AnnotationValue::Field(F::new("value")),
692 ));
693
694 let sql = qs.to_sql();
695
696 assert!(
697 sql.contains("value") || sql.contains("complex"),
698 "SQL should contain 'value' or 'complex'. Got: {}",
699 sql
700 );
701 }
702
703 #[test]
704 fn test_alias_default_alias_expression() {
706 use crate::orm::expressions::F;
708 use crate::orm::query::QuerySet;
709
710 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
711 "default_alias",
712 AnnotationValue::Field(F::new("name")),
713 ));
714
715 let sql = qs.to_sql();
716
717 assert!(
718 sql.contains("name") || sql.contains("default_alias"),
719 "SQL should contain 'name' or 'default_alias'. Got: {}",
720 sql
721 );
722 }
723
724 #[test]
725 fn test_alias_default_alias_expression_1() {
727 use crate::orm::expressions::F;
729 use crate::orm::query::QuerySet;
730
731 let qs = QuerySet::<TestModel>::new()
732 .annotate(Annotation::field(
733 "alias1",
734 AnnotationValue::Field(F::new("field1")),
735 ))
736 .annotate(Annotation::field(
737 "alias2",
738 AnnotationValue::Field(F::new("field2")),
739 ));
740
741 let sql = qs.to_sql();
742
743 assert!(
744 sql.contains("field1") || sql.contains("field2"),
745 "SQL should contain 'field1' or 'field2'. Got: {}",
746 sql
747 );
748 }
749
750 #[test]
751 fn test_alias_filtered_relation_sql_injection() {
753 let q = Q::new("status", "=", "active");
754 let sql = q.to_sql();
755 assert!(
756 sql.contains("status"),
757 "SQL should contain 'status'. Got: {}",
758 sql
759 );
760 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
761 }
762
763 #[test]
764 fn test_alias_filtered_relation_sql_injection_1() {
766 let q = Q::new("status", "=", "active");
767 let sql = q.to_sql();
768 assert!(
769 sql.contains("status"),
770 "SQL should contain 'status'. Got: {}",
771 sql
772 );
773 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
774 }
775
776 #[test]
777 fn test_alias_filtered_relation_sql_injection_2() {
779 let q = Q::new("status", "=", "active");
780 let sql = q.to_sql();
781 assert!(
782 sql.contains("status"),
783 "SQL should contain 'status'. Got: {}",
784 sql
785 );
786 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
787 }
788
789 #[test]
790 fn test_alias_filtered_relation_sql_injection_3() {
792 let q = Q::new("status", "=", "active");
793 let sql = q.to_sql();
794 assert!(
795 sql.contains("status"),
796 "SQL should contain 'status'. Got: {}",
797 sql
798 );
799 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
800 }
801
802 #[test]
803 fn test_annotate_exists() {
805 use crate::orm::query::QuerySet;
807
808 let subquery = QuerySet::<TestModel>::new()
809 .filter(Filter::new(
810 "status".to_string(),
811 FilterOperator::Eq,
812 FilterValue::String("active".to_string()),
813 ))
814 .as_subquery();
815
816 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
817 "has_active",
818 AnnotationValue::Subquery(subquery),
819 ));
820
821 let sql = qs.to_sql();
822
823 assert!(
824 sql.contains("SELECT") && (sql.contains("active") || sql.contains("status")),
825 "SQL should contain 'SELECT' and ('active' or 'status'). Got: {}",
826 sql
827 );
828 }
829
830 #[test]
831 fn test_annotate_exists_1() {
833 use crate::orm::query::QuerySet;
835
836 let subquery = QuerySet::<TestModel>::new()
837 .filter(Filter::new(
838 "id".to_string(),
839 FilterOperator::Gt,
840 FilterValue::Int(0),
841 ))
842 .as_subquery();
843
844 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
845 "exists_check",
846 AnnotationValue::Subquery(subquery),
847 ));
848
849 let sql = qs.to_sql();
850
851 assert!(
852 sql.starts_with("SELECT") || sql.contains(" SELECT "),
853 "SQL should contain SELECT clause. Got: {}",
854 sql
855 );
856 }
857
858 #[test]
859 fn test_annotate_with_aggregation() {
861 use crate::orm::aggregation::Aggregate;
863 use crate::orm::expressions::F;
864 use crate::orm::query::QuerySet;
865
866 let qs = QuerySet::<TestModel>::new()
867 .annotate(Annotation::field(
868 "value_doubled",
869 AnnotationValue::Field(F::new("value")),
870 ))
871 .aggregate(Aggregate::sum("value_doubled").with_alias("total"));
872
873 let sql = qs.to_sql();
874
875 assert!(
876 sql.contains("SUM") || sql.contains("value"),
877 "SQL should contain 'SUM' or 'value'. Got: {}",
878 sql
879 );
880 }
881
882 #[test]
883 fn test_annotate_with_aggregation_1() {
885 use crate::orm::aggregation::Aggregate;
887 use crate::orm::query::QuerySet;
888
889 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
890 "item_count",
891 AnnotationValue::Aggregate(Aggregate::count(Some("items"))),
892 ));
893
894 let sql = qs.to_sql();
895
896 assert!(
897 sql.contains("COUNT") || sql.contains("items"),
898 "SQL should contain 'COUNT' or 'items'. Got: {}",
899 sql
900 );
901 }
902
903 #[test]
904 fn test_annotation_aggregate_with_m2o() {
906 use crate::orm::aggregation::Aggregate;
908 use crate::orm::query::QuerySet;
909
910 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
911 "related_count",
912 AnnotationValue::Aggregate(Aggregate::count(Some("related_id"))),
913 ));
914
915 let sql = qs.to_sql();
916
917 assert!(
918 sql.contains("COUNT("),
919 "SQL should contain COUNT clause. Got: {}",
920 sql
921 );
922 }
923
924 #[test]
925 fn test_annotation_aggregate_with_m2o_1() {
927 use crate::orm::aggregation::Aggregate;
929 use crate::orm::query::QuerySet;
930
931 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
932 "sum_related",
933 AnnotationValue::Aggregate(Aggregate::sum("related_value")),
934 ));
935
936 let sql = qs.to_sql();
937
938 assert!(
939 sql.contains("SUM("),
940 "SQL should contain SUM clause. Got: {}",
941 sql
942 );
943 }
944
945 #[test]
946 fn test_annotation_and_alias_filter_in_subquery() {
948 let q = Q::new("status", "=", "active");
949 let sql = q.to_sql();
950 assert!(
951 sql.contains("status"),
952 "SQL should contain 'status'. Got: {}",
953 sql
954 );
955 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
956 }
957
958 #[test]
959 fn test_annotation_and_alias_filter_in_subquery_1() {
961 let q = Q::new("status", "=", "active");
962 let sql = q.to_sql();
963 assert!(
964 sql.contains("status"),
965 "SQL should contain 'status'. Got: {}",
966 sql
967 );
968 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
969 }
970
971 #[test]
972 fn test_annotation_and_alias_filter_related_in_subquery() {
974 let q = Q::new("status", "=", "active");
975 let sql = q.to_sql();
976 assert!(
977 sql.contains("status"),
978 "SQL should contain 'status'. Got: {}",
979 sql
980 );
981 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
982 }
983
984 #[test]
985 fn test_annotation_and_alias_filter_related_in_subquery_1() {
987 let q = Q::new("status", "=", "active");
988 let sql = q.to_sql();
989 assert!(
990 sql.contains("status"),
991 "SQL should contain 'status'. Got: {}",
992 sql
993 );
994 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
995 }
996
997 #[test]
998 fn test_annotation_exists_aggregate_values_chaining() {
1000 use crate::orm::aggregation::Aggregate;
1004 use crate::orm::query::QuerySet;
1005
1006 let qs = QuerySet::<TestModel>::new()
1007 .values(&["publisher"])
1008 .annotate(Annotation::field(
1009 "max_date",
1010 AnnotationValue::Aggregate(Aggregate::max("pubdate")),
1011 ))
1012 .values_list(&["max_date"]);
1013
1014 let sql = qs.to_sql();
1015
1016 assert!(
1017 sql.contains("MAX") || sql.contains("pubdate"),
1018 "SQL should contain 'MAX' or 'pubdate'. Got: {}",
1019 sql
1020 );
1021 }
1022
1023 #[test]
1024 fn test_annotation_exists_aggregate_values_chaining_1() {
1026 use crate::orm::aggregation::Aggregate;
1028 use crate::orm::query::QuerySet;
1029
1030 let subquery = QuerySet::<TestModel>::new()
1031 .filter(Filter::new(
1032 "id".to_string(),
1033 FilterOperator::Gt,
1034 FilterValue::Int(0),
1035 ))
1036 .as_subquery();
1037
1038 let qs = QuerySet::<TestModel>::new()
1039 .annotate(Annotation::field(
1040 "has_items",
1041 AnnotationValue::Subquery(subquery),
1042 ))
1043 .annotate(Annotation::field(
1044 "count",
1045 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1046 ))
1047 .values(&["count"]);
1048
1049 let sql = qs.to_sql();
1050
1051 assert!(
1052 sql.contains("COUNT") || sql.contains("SELECT"),
1053 "SQL should contain 'COUNT' or 'SELECT'. Got: {}",
1054 sql
1055 );
1056 }
1057
1058 #[test]
1059 fn test_annotation_filter_with_subquery() {
1061 let q = Q::new("status", "=", "active");
1062 let sql = q.to_sql();
1063 assert!(
1064 sql.contains("status"),
1065 "SQL should contain 'status'. Got: {}",
1066 sql
1067 );
1068 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1069 }
1070
1071 #[test]
1072 fn test_annotation_filter_with_subquery_1() {
1074 let q = Q::new("status", "=", "active");
1075 let sql = q.to_sql();
1076 assert!(
1077 sql.contains("status"),
1078 "SQL should contain 'status'. Got: {}",
1079 sql
1080 );
1081 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1082 }
1083
1084 #[test]
1085 fn test_annotation_in_f_grouped_by_annotation() {
1087 use crate::orm::aggregation::Aggregate;
1089 use crate::orm::expressions::F;
1090 use crate::orm::query::QuerySet;
1091
1092 let qs = QuerySet::<TestModel>::new()
1093 .annotate(Annotation::field(
1094 "category",
1095 AnnotationValue::Field(F::new("type")),
1096 ))
1097 .values(&["category"])
1098 .annotate(Annotation::field(
1099 "total",
1100 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1101 ));
1102
1103 let sql = qs.to_sql();
1104
1105 assert!(
1106 sql.contains("COUNT") || sql.contains("type"),
1107 "SQL should contain 'COUNT' or 'type'. Got: {}",
1108 sql
1109 );
1110 }
1111
1112 #[test]
1113 fn test_annotation_in_f_grouped_by_annotation_1() {
1115 use crate::orm::aggregation::Aggregate;
1117 use crate::orm::expressions::F;
1118 use crate::orm::query::QuerySet;
1119
1120 let qs = QuerySet::<TestModel>::new()
1121 .annotate(Annotation::field(
1122 "group_field",
1123 AnnotationValue::Field(F::new("status")),
1124 ))
1125 .values(&["group_field"])
1126 .annotate(Annotation::field(
1127 "count",
1128 AnnotationValue::Aggregate(Aggregate::count(Some("*"))),
1129 ));
1130
1131 let sql = qs.to_sql();
1132
1133 assert!(
1134 sql.contains("COUNT") || sql.contains("status"),
1135 "SQL should contain 'COUNT' or 'status'. Got: {}",
1136 sql
1137 );
1138 }
1139
1140 #[test]
1141 fn test_annotation_subquery_and_aggregate_values_chaining() {
1143 use crate::orm::aggregation::Aggregate;
1147 use crate::orm::query::QuerySet;
1148
1149 let qs = QuerySet::<TestModel>::new()
1150 .values(&["year"])
1151 .annotate(Annotation::field(
1152 "total",
1153 AnnotationValue::Aggregate(Aggregate::sum("pages")),
1154 ));
1155
1156 let sql = qs.to_sql();
1157
1158 assert!(
1159 sql.contains("SUM") || sql.contains("pages"),
1160 "SQL should contain 'SUM' or 'pages'. Got: {}",
1161 sql
1162 );
1163 }
1164
1165 #[test]
1166 fn test_annotation_subquery_and_aggregate_values_chaining_1() {
1168 use crate::orm::aggregation::Aggregate;
1170 use crate::orm::query::QuerySet;
1171
1172 let subquery = QuerySet::<TestModel>::new()
1173 .filter(Filter::new(
1174 "rating".to_string(),
1175 FilterOperator::Gt,
1176 FilterValue::Int(3),
1177 ))
1178 .as_subquery();
1179
1180 let qs = QuerySet::<TestModel>::new()
1181 .annotate(Annotation::field(
1182 "top_rating",
1183 AnnotationValue::Subquery(subquery),
1184 ))
1185 .annotate(Annotation::field(
1186 "total",
1187 AnnotationValue::Aggregate(Aggregate::sum("value")),
1188 ))
1189 .values(&["total", "top_rating"]);
1190
1191 let sql = qs.to_sql();
1192
1193 assert!(
1194 sql.contains("SUM") || sql.contains("SELECT"),
1195 "SQL should contain 'SUM' or 'SELECT'. Got: {}",
1196 sql
1197 );
1198 }
1199
1200 #[test]
1201 fn test_arguments_must_be_expressions() {
1203 use crate::orm::expressions::F;
1205 use crate::orm::query::QuerySet;
1206
1207 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1208 "expr",
1209 AnnotationValue::Field(F::new("field1")),
1210 ));
1211
1212 let sql = qs.to_sql();
1213
1214 assert!(
1215 sql.contains("field1") || sql.contains("expr"),
1216 "SQL should contain 'field1' or 'expr'. Got: {}",
1217 sql
1218 );
1219 }
1220
1221 #[test]
1222 fn test_arguments_must_be_expressions_1() {
1224 use crate::orm::expressions::F;
1226 use crate::orm::query::QuerySet;
1227
1228 let qs = QuerySet::<TestModel>::new()
1229 .annotate(Annotation::field(
1230 "expr1",
1231 AnnotationValue::Field(F::new("field1")),
1232 ))
1233 .annotate(Annotation::field(
1234 "expr2",
1235 AnnotationValue::Field(F::new("field2")),
1236 ));
1237
1238 let sql = qs.to_sql();
1239
1240 assert!(
1241 sql.contains("field1") || sql.contains("field2"),
1242 "SQL should contain 'field1' or 'field2'. Got: {}",
1243 sql
1244 );
1245 }
1246
1247 #[test]
1248 fn test_boolean_value_annotation() {
1250 use crate::orm::expressions::F;
1252 use crate::orm::query::QuerySet;
1253
1254 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1255 "is_active",
1256 AnnotationValue::Field(F::new("active")),
1257 ));
1258
1259 let sql = qs.to_sql();
1260
1261 assert!(
1262 sql.contains("active") || sql.contains("is_active"),
1263 "SQL should contain 'active' or 'is_active'. Got: {}",
1264 sql
1265 );
1266 }
1267
1268 #[test]
1269 fn test_boolean_value_annotation_1() {
1271 use crate::orm::expressions::F;
1273 use crate::orm::query::QuerySet;
1274
1275 let qs = QuerySet::<TestModel>::new()
1276 .annotate(Annotation::field(
1277 "is_enabled",
1278 AnnotationValue::Field(F::new("enabled")),
1279 ))
1280 .filter(Filter::new(
1281 "is_enabled".to_string(),
1282 FilterOperator::Eq,
1283 FilterValue::Bool(true),
1284 ));
1285
1286 let sql = qs.to_sql();
1287
1288 assert!(
1289 sql.contains("enabled") || sql.contains("WHERE"),
1290 "SQL should contain 'enabled' or 'WHERE'. Got: {}",
1291 sql
1292 );
1293 }
1294
1295 #[test]
1296 fn test_chaining_annotation_filter_with_m2m() {
1298 let q = Q::new("status", "=", "active");
1299 let sql = q.to_sql();
1300 assert!(
1301 sql.contains("status"),
1302 "SQL should contain 'status'. Got: {}",
1303 sql
1304 );
1305 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1306 }
1307
1308 #[test]
1309 fn test_chaining_annotation_filter_with_m2m_1() {
1311 let q = Q::new("status", "=", "active");
1312 let sql = q.to_sql();
1313 assert!(
1314 sql.contains("status"),
1315 "SQL should contain 'status'. Got: {}",
1316 sql
1317 );
1318 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1319 }
1320
1321 #[test]
1322 fn test_column_field_ordering() {
1324 use crate::orm::expressions::F;
1326 use crate::orm::query::QuerySet;
1327
1328 let qs = QuerySet::<TestModel>::new()
1329 .annotate(Annotation::field(
1330 "annotated",
1331 AnnotationValue::Field(F::new("field1")),
1332 ))
1333 .values(&["id", "name", "annotated"]);
1334
1335 let sql = qs.to_sql();
1336
1337 assert!(
1338 sql.contains("id")
1339 && sql.contains("name")
1340 && (sql.contains("field1") || sql.contains("annotated")),
1341 "SQL should contain 'id', 'name', and ('field1' or 'annotated'). Got: {}",
1342 sql
1343 );
1344 }
1345
1346 #[test]
1347 fn test_column_field_ordering_1() {
1349 use crate::orm::expressions::F;
1351 use crate::orm::query::QuerySet;
1352
1353 let qs = QuerySet::<TestModel>::new()
1354 .annotate(Annotation::field(
1355 "extra",
1356 AnnotationValue::Field(F::new("value")),
1357 ))
1358 .values(&["extra", "id"]);
1359
1360 let sql = qs.to_sql();
1361
1362 assert!(
1363 sql.contains("id") && (sql.contains("value") || sql.contains("extra")),
1364 "SQL should contain 'id' and ('value' or 'extra'). Got: {}",
1365 sql
1366 );
1367 }
1368
1369 #[test]
1370 fn test_column_field_ordering_with_deferred() {
1372 use crate::orm::expressions::F;
1374 use crate::orm::query::QuerySet;
1375
1376 let qs = QuerySet::<TestModel>::new()
1377 .defer(&["description"])
1378 .annotate(Annotation::field(
1379 "computed",
1380 AnnotationValue::Field(F::new("value")),
1381 ));
1382
1383 let sql = qs.to_sql();
1384
1385 assert!(
1386 sql.contains("value") || sql.contains("computed"),
1387 "SQL should contain 'value' or 'computed'. Got: {}",
1388 sql
1389 );
1390 }
1391
1392 #[test]
1393 fn test_column_field_ordering_with_deferred_1() {
1395 use crate::orm::expressions::F;
1397 use crate::orm::query::QuerySet;
1398
1399 let qs = QuerySet::<TestModel>::new()
1400 .only(&["id", "name"])
1401 .annotate(Annotation::field(
1402 "calc",
1403 AnnotationValue::Field(F::new("field1")),
1404 ));
1405
1406 let sql = qs.to_sql();
1407
1408 assert!(
1409 sql.contains("id") && sql.contains("name"),
1410 "SQL should contain 'id' and 'name'. Got: {}",
1411 sql
1412 );
1413 }
1414
1415 #[test]
1416 fn test_combined_expression_annotation_with_aggregation() {
1418 use crate::orm::aggregation::Aggregate;
1423 use crate::orm::expressions::F;
1424 use crate::orm::query::QuerySet;
1425
1426 let qs = QuerySet::<TestModel>::new()
1427 .annotate(Annotation::field(
1428 "combined",
1429 AnnotationValue::Field(F::new("value")),
1430 ))
1431 .annotate(Annotation::field(
1432 "rating_count",
1433 AnnotationValue::Aggregate(Aggregate::count(Some("rating"))),
1434 ));
1435
1436 let sql = qs.to_sql();
1437
1438 assert!(
1439 sql.contains("COUNT") || sql.contains("value"),
1440 "SQL should contain 'COUNT' or 'value'. Got: {}",
1441 sql
1442 );
1443 }
1444
1445 #[test]
1446 fn test_combined_expression_annotation_with_aggregation_1() {
1448 use crate::orm::aggregation::Aggregate;
1450 use crate::orm::expressions::F;
1451 use crate::orm::query::QuerySet;
1452
1453 let qs = QuerySet::<TestModel>::new()
1454 .annotate(Annotation::field(
1455 "expr",
1456 AnnotationValue::Field(F::new("field1")),
1457 ))
1458 .annotate(Annotation::field(
1459 "total",
1460 AnnotationValue::Aggregate(Aggregate::sum("field2")),
1461 ));
1462
1463 let sql = qs.to_sql();
1464
1465 assert!(
1466 sql.contains("SUM") || sql.contains("field"),
1467 "SQL should contain 'SUM' or 'field'. Got: {}",
1468 sql
1469 );
1470 }
1471
1472 #[test]
1473 fn test_combined_f_expression_annotation_with_aggregation() {
1475 use crate::orm::aggregation::Aggregate;
1480 use crate::orm::expressions::F;
1481 use crate::orm::query::QuerySet;
1482
1483 let qs = QuerySet::<TestModel>::new()
1484 .annotate(Annotation::field(
1485 "combined",
1486 AnnotationValue::Field(F::new("price")),
1487 ))
1488 .annotate(Annotation::field(
1489 "rating_count",
1490 AnnotationValue::Aggregate(Aggregate::count(Some("rating"))),
1491 ));
1492
1493 let sql = qs.to_sql();
1494
1495 assert!(
1496 sql.contains("COUNT") || sql.contains("price") || sql.contains("rating"),
1497 "SQL should contain 'COUNT', 'price', or 'rating'. Got: {}",
1498 sql
1499 );
1500 }
1501
1502 #[test]
1503 fn test_combined_f_expression_annotation_with_aggregation_1() {
1505 use crate::orm::aggregation::Aggregate;
1507 use crate::orm::expressions::F;
1508 use crate::orm::query::QuerySet;
1509
1510 let qs = QuerySet::<TestModel>::new()
1511 .annotate(Annotation::field(
1512 "calc",
1513 AnnotationValue::Field(F::new("value1")),
1514 ))
1515 .annotate(Annotation::field(
1516 "max_value",
1517 AnnotationValue::Aggregate(Aggregate::max("value2")),
1518 ));
1519
1520 let sql = qs.to_sql();
1521
1522 assert!(
1523 sql.contains("MAX") || sql.contains("value"),
1524 "SQL should contain 'MAX' or 'value'. Got: {}",
1525 sql
1526 );
1527 }
1528
1529 #[test]
1530 fn test_distinct_on_alias() {
1532 use crate::orm::expressions::F;
1535 use crate::orm::query::QuerySet;
1536
1537 let qs = QuerySet::<TestModel>::new()
1538 .annotate(Annotation::field(
1539 "rating_alias",
1540 AnnotationValue::Field(F::new("rating")),
1541 ))
1542 .distinct();
1543
1544 let sql = qs.to_sql();
1545
1546 assert!(
1547 sql.contains("DISTINCT") || sql.contains("rating"),
1548 "SQL should contain 'DISTINCT' or 'rating'. Got: {}",
1549 sql
1550 );
1551 }
1552
1553 #[test]
1554 fn test_distinct_on_alias_1() {
1556 use crate::orm::expressions::F;
1558 use crate::orm::query::QuerySet;
1559
1560 let qs = QuerySet::<TestModel>::new()
1561 .annotate(Annotation::field(
1562 "name_alias",
1563 AnnotationValue::Field(F::new("name")),
1564 ))
1565 .distinct();
1566
1567 let sql = qs.to_sql();
1568
1569 assert!(
1570 sql.starts_with("DISTINCT") || sql.contains(" DISTINCT "),
1571 "SQL should contain DISTINCT clause. Got: {}",
1572 sql
1573 );
1574 }
1575
1576 #[test]
1577 fn test_distinct_on_with_annotation() {
1579 use crate::orm::expressions::F;
1582 use crate::orm::query::QuerySet;
1583
1584 let qs = QuerySet::<TestModel>::new()
1585 .annotate(Annotation::field(
1586 "name_lower",
1587 AnnotationValue::Field(F::new("last_name")),
1588 ))
1589 .distinct();
1590
1591 let sql = qs.to_sql();
1592
1593 assert!(
1594 sql.contains("DISTINCT") && (sql.contains("last_name") || sql.contains("name_lower"))
1595 );
1596 }
1597
1598 #[test]
1599 fn test_distinct_on_with_annotation_1() {
1601 use crate::orm::expressions::F;
1603 use crate::orm::query::QuerySet;
1604
1605 let qs = QuerySet::<TestModel>::new()
1606 .annotate(Annotation::field(
1607 "field1_lower",
1608 AnnotationValue::Field(F::new("field1")),
1609 ))
1610 .annotate(Annotation::field(
1611 "field2_lower",
1612 AnnotationValue::Field(F::new("field2")),
1613 ))
1614 .distinct();
1615
1616 let sql = qs.to_sql();
1617
1618 assert!(
1619 sql.starts_with("DISTINCT") || sql.contains(" DISTINCT "),
1620 "SQL should contain DISTINCT clause. Got: {}",
1621 sql
1622 );
1623 }
1624
1625 #[test]
1626 fn test_empty_expression_annotation() {
1628 use crate::orm::expressions::F;
1630 use crate::orm::query::QuerySet;
1631
1632 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1633 "simple",
1634 AnnotationValue::Field(F::new("id")),
1635 ));
1636
1637 let sql = qs.to_sql();
1638
1639 assert!(
1640 sql.contains("id") || sql.contains("simple"),
1641 "SQL should contain 'id' or 'simple'. Got: {}",
1642 sql
1643 );
1644 }
1645
1646 #[test]
1647 fn test_empty_expression_annotation_1() {
1649 use crate::orm::expressions::F;
1651 use crate::orm::query::QuerySet;
1652
1653 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1654 "minimal",
1655 AnnotationValue::Field(F::new("name")),
1656 ));
1657
1658 let sql = qs.to_sql();
1659
1660 assert!(
1661 sql.contains("name") || sql.contains("minimal"),
1662 "SQL should contain 'name' or 'minimal'. Got: {}",
1663 sql
1664 );
1665 }
1666
1667 #[test]
1668 fn test_filter_agg_with_double_f() {
1670 let q = Q::new("status", "=", "active");
1671 let sql = q.to_sql();
1672 assert!(
1673 sql.contains("status"),
1674 "SQL should contain 'status'. Got: {}",
1675 sql
1676 );
1677 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1678 }
1679
1680 #[test]
1681 fn test_filter_agg_with_double_f_1() {
1683 let q = Q::new("status", "=", "active");
1684 let sql = q.to_sql();
1685 assert!(
1686 sql.contains("status"),
1687 "SQL should contain 'status'. Got: {}",
1688 sql
1689 );
1690 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1691 }
1692
1693 #[test]
1694 fn test_filter_alias_agg_with_double_f() {
1696 let q = Q::new("status", "=", "active");
1697 let sql = q.to_sql();
1698 assert!(
1699 sql.contains("status"),
1700 "SQL should contain 'status'. Got: {}",
1701 sql
1702 );
1703 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1704 }
1705
1706 #[test]
1707 fn test_filter_alias_agg_with_double_f_1() {
1709 let q = Q::new("status", "=", "active");
1710 let sql = q.to_sql();
1711 assert!(
1712 sql.contains("status"),
1713 "SQL should contain 'status'. Got: {}",
1714 sql
1715 );
1716 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1717 }
1718
1719 #[test]
1720 fn test_filter_alias_with_double_f() {
1722 let q = Q::new("status", "=", "active");
1723 let sql = q.to_sql();
1724 assert!(
1725 sql.contains("status"),
1726 "SQL should contain 'status'. Got: {}",
1727 sql
1728 );
1729 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1730 }
1731
1732 #[test]
1733 fn test_filter_alias_with_double_f_1() {
1735 let q = Q::new("status", "=", "active");
1736 let sql = q.to_sql();
1737 assert!(
1738 sql.contains("status"),
1739 "SQL should contain 'status'. Got: {}",
1740 sql
1741 );
1742 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1743 }
1744
1745 #[test]
1746 fn test_filter_alias_with_f() {
1748 let q = Q::new("status", "=", "active");
1749 let sql = q.to_sql();
1750 assert!(
1751 sql.contains("status"),
1752 "SQL should contain 'status'. Got: {}",
1753 sql
1754 );
1755 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1756 }
1757
1758 #[test]
1759 fn test_filter_alias_with_f_1() {
1761 let q = Q::new("status", "=", "active");
1762 let sql = q.to_sql();
1763 assert!(
1764 sql.contains("status"),
1765 "SQL should contain 'status'. Got: {}",
1766 sql
1767 );
1768 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1769 }
1770
1771 #[test]
1772 fn test_filter_annotation() {
1774 let q = Q::new("status", "=", "active");
1775 let sql = q.to_sql();
1776 assert!(
1777 sql.contains("status"),
1778 "SQL should contain 'status'. Got: {}",
1779 sql
1780 );
1781 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1782 }
1783
1784 #[test]
1785 fn test_filter_annotation_1() {
1787 let q = Q::new("status", "=", "active");
1788 let sql = q.to_sql();
1789 assert!(
1790 sql.contains("status"),
1791 "SQL should contain 'status'. Got: {}",
1792 sql
1793 );
1794 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1795 }
1796
1797 #[test]
1798 fn test_filter_annotation_with_double_f() {
1800 let q = Q::new("status", "=", "active");
1801 let sql = q.to_sql();
1802 assert!(
1803 sql.contains("status"),
1804 "SQL should contain 'status'. Got: {}",
1805 sql
1806 );
1807 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1808 }
1809
1810 #[test]
1811 fn test_filter_annotation_with_double_f_1() {
1813 let q = Q::new("status", "=", "active");
1814 let sql = q.to_sql();
1815 assert!(
1816 sql.contains("status"),
1817 "SQL should contain 'status'. Got: {}",
1818 sql
1819 );
1820 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1821 }
1822
1823 #[test]
1824 fn test_filter_annotation_with_f() {
1826 let q = Q::new("status", "=", "active");
1827 let sql = q.to_sql();
1828 assert!(
1829 sql.contains("status"),
1830 "SQL should contain 'status'. Got: {}",
1831 sql
1832 );
1833 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1834 }
1835
1836 #[test]
1837 fn test_filter_annotation_with_f_1() {
1839 let q = Q::new("status", "=", "active");
1840 let sql = q.to_sql();
1841 assert!(
1842 sql.contains("status"),
1843 "SQL should contain 'status'. Got: {}",
1844 sql
1845 );
1846 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1847 }
1848
1849 #[test]
1850 fn test_filter_decimal_annotation() {
1852 let q = Q::new("status", "=", "active");
1853 let sql = q.to_sql();
1854 assert!(
1855 sql.contains("status"),
1856 "SQL should contain 'status'. Got: {}",
1857 sql
1858 );
1859 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1860 }
1861
1862 #[test]
1863 fn test_filter_decimal_annotation_1() {
1865 let q = Q::new("status", "=", "active");
1866 let sql = q.to_sql();
1867 assert!(
1868 sql.contains("status"),
1869 "SQL should contain 'status'. Got: {}",
1870 sql
1871 );
1872 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1873 }
1874
1875 #[test]
1876 fn test_filter_wrong_annotation() {
1878 let q = Q::new("status", "=", "active");
1879 let sql = q.to_sql();
1880 assert!(
1881 sql.contains("status"),
1882 "SQL should contain 'status'. Got: {}",
1883 sql
1884 );
1885 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1886 }
1887
1888 #[test]
1889 fn test_filter_wrong_annotation_1() {
1891 let q = Q::new("status", "=", "active");
1892 let sql = q.to_sql();
1893 assert!(
1894 sql.contains("status"),
1895 "SQL should contain 'status'. Got: {}",
1896 sql
1897 );
1898 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1899 }
1900
1901 #[test]
1902 fn test_full_expression_annotation() {
1904 use crate::orm::expressions::F;
1906 use crate::orm::query::QuerySet;
1907
1908 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1909 "full_expr",
1910 AnnotationValue::Field(F::new("field1")),
1911 ));
1912
1913 let sql = qs.to_sql();
1914
1915 assert!(
1916 sql.contains("field1") || sql.contains("full_expr"),
1917 "SQL should contain 'field1' or 'full_expr'. Got: {}",
1918 sql
1919 );
1920 }
1921
1922 #[test]
1923 fn test_full_expression_annotation_1() {
1925 use crate::orm::expressions::F;
1927 use crate::orm::query::QuerySet;
1928
1929 let qs = QuerySet::<TestModel>::new()
1930 .annotate(Annotation::field(
1931 "complex_expr",
1932 AnnotationValue::Field(F::new("value1")),
1933 ))
1934 .filter(Filter::new(
1935 "complex_expr".to_string(),
1936 FilterOperator::Gt,
1937 FilterValue::Int(0),
1938 ));
1939
1940 let sql = qs.to_sql();
1941
1942 assert!(
1943 sql.contains("value1") || sql.contains("complex_expr"),
1944 "SQL should contain 'value1' or 'complex_expr'. Got: {}",
1945 sql
1946 );
1947 }
1948
1949 #[test]
1950 fn test_full_expression_annotation_with_aggregation() {
1952 use crate::orm::aggregation::Aggregate;
1954 use crate::orm::expressions::F;
1955 use crate::orm::query::QuerySet;
1956
1957 let qs = QuerySet::<TestModel>::new()
1958 .annotate(Annotation::field(
1959 "expr",
1960 AnnotationValue::Field(F::new("price")),
1961 ))
1962 .annotate(Annotation::field(
1963 "total",
1964 AnnotationValue::Aggregate(Aggregate::sum("quantity")),
1965 ));
1966
1967 let sql = qs.to_sql();
1968
1969 assert!(
1970 sql.contains("SUM") || sql.contains("price"),
1971 "SQL should contain 'SUM' or 'price'. Got: {}",
1972 sql
1973 );
1974 }
1975
1976 #[test]
1977 fn test_full_expression_annotation_with_aggregation_1() {
1979 use crate::orm::aggregation::Aggregate;
1981 use crate::orm::expressions::F;
1982 use crate::orm::query::QuerySet;
1983
1984 let qs = QuerySet::<TestModel>::new()
1985 .annotate(Annotation::field(
1986 "calc",
1987 AnnotationValue::Field(F::new("value")),
1988 ))
1989 .annotate(Annotation::field(
1990 "count",
1991 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1992 ));
1993
1994 let sql = qs.to_sql();
1995
1996 assert!(
1997 sql.contains("COUNT") || sql.contains("value"),
1998 "SQL should contain 'COUNT' or 'value'. Got: {}",
1999 sql
2000 );
2001 }
2002
2003 #[test]
2004 fn test_full_expression_wrapped_annotation() {
2006 use crate::orm::expressions::F;
2008 use crate::orm::query::QuerySet;
2009
2010 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2011 "wrapped",
2012 AnnotationValue::Field(F::new("field1")),
2013 ));
2014
2015 let sql = qs.to_sql();
2016
2017 assert!(
2018 sql.contains("field1") || sql.contains("wrapped"),
2019 "SQL should contain 'field1' or 'wrapped'. Got: {}",
2020 sql
2021 );
2022 }
2023
2024 #[test]
2025 fn test_full_expression_wrapped_annotation_1() {
2027 use crate::orm::expressions::F;
2029 use crate::orm::query::QuerySet;
2030
2031 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2032 "wrapped_expr",
2033 AnnotationValue::Field(F::new("value")),
2034 ));
2035
2036 let sql = qs.to_sql();
2037
2038 assert!(
2039 sql.contains("value") || sql.contains("wrapped_expr"),
2040 "SQL should contain 'value' or 'wrapped_expr'. Got: {}",
2041 sql
2042 );
2043 }
2044
2045 #[test]
2046 fn test_grouping_by_q_expression_annotation() {
2048 use crate::orm::aggregation::Aggregate;
2050 use crate::orm::expressions::F;
2051 use crate::orm::query::QuerySet;
2052
2053 let qs = QuerySet::<TestModel>::new()
2054 .annotate(Annotation::field(
2055 "group_expr",
2056 AnnotationValue::Field(F::new("category")),
2057 ))
2058 .values(&["group_expr"])
2059 .annotate(Annotation::field(
2060 "count",
2061 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2062 ));
2063
2064 let sql = qs.to_sql();
2065
2066 assert!(
2067 sql.contains("COUNT") || sql.contains("category"),
2068 "SQL should contain 'COUNT' or 'category'. Got: {}",
2069 sql
2070 );
2071 }
2072
2073 #[test]
2074 fn test_grouping_by_q_expression_annotation_1() {
2076 use crate::orm::aggregation::Aggregate;
2078 use crate::orm::expressions::F;
2079 use crate::orm::query::QuerySet;
2080
2081 let qs = QuerySet::<TestModel>::new()
2082 .annotate(Annotation::field(
2083 "status_group",
2084 AnnotationValue::Field(F::new("status")),
2085 ))
2086 .values(&["status_group"])
2087 .annotate(Annotation::field(
2088 "total",
2089 AnnotationValue::Aggregate(Aggregate::sum("value")),
2090 ));
2091
2092 let sql = qs.to_sql();
2093
2094 assert!(
2095 sql.contains("SUM") || sql.contains("status"),
2096 "SQL should contain 'SUM' or 'status'. Got: {}",
2097 sql
2098 );
2099 }
2100
2101 #[test]
2102 fn test_joined_alias_annotation() {
2104 use crate::orm::expressions::F;
2106 use crate::orm::query::QuerySet;
2107
2108 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2109 "related_field",
2110 AnnotationValue::Field(F::new("related__name")),
2111 ));
2112
2113 let sql = qs.to_sql();
2114
2115 assert!(
2116 sql.contains("related") || sql.contains("name"),
2117 "SQL should contain 'related' or 'name'. Got: {}",
2118 sql
2119 );
2120 }
2121
2122 #[test]
2123 fn test_joined_alias_annotation_1() {
2125 use crate::orm::expressions::F;
2127 use crate::orm::query::QuerySet;
2128
2129 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2130 "parent_value",
2131 AnnotationValue::Field(F::new("parent__value")),
2132 ));
2133
2134 let sql = qs.to_sql();
2135
2136 assert!(
2137 sql.contains("parent") || sql.contains("value"),
2138 "SQL should contain 'parent' or 'value'. Got: {}",
2139 sql
2140 );
2141 }
2142
2143 #[test]
2144 fn test_joined_annotation() {
2146 use crate::orm::expressions::F;
2148 use crate::orm::query::QuerySet;
2149
2150 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2151 "fk_field",
2152 AnnotationValue::Field(F::new("foreign_key__field")),
2153 ));
2154
2155 let sql = qs.to_sql();
2156
2157 assert!(
2158 sql.contains("foreign_key") || sql.contains("field"),
2159 "SQL should contain 'foreign_key' or 'field'. Got: {}",
2160 sql
2161 );
2162 }
2163
2164 #[test]
2165 fn test_joined_annotation_1() {
2167 use crate::orm::expressions::F;
2169 use crate::orm::query::QuerySet;
2170
2171 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2172 "related_data",
2173 AnnotationValue::Field(F::new("relation__data")),
2174 ));
2175
2176 let sql = qs.to_sql();
2177
2178 assert!(
2179 sql.contains("relation") || sql.contains("data"),
2180 "SQL should contain 'relation' or 'data'. Got: {}",
2181 sql
2182 );
2183 }
2184
2185 #[test]
2186 fn test_joined_transformed_annotation() {
2188 use crate::orm::expressions::F;
2190 use crate::orm::query::QuerySet;
2191
2192 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2193 "transformed",
2194 AnnotationValue::Field(F::new("related__transformed_field")),
2195 ));
2196
2197 let sql = qs.to_sql();
2198
2199 assert!(
2200 sql.contains("related") || sql.contains("transformed"),
2201 "SQL should contain 'related' or 'transformed'. Got: {}",
2202 sql
2203 );
2204 }
2205
2206 #[test]
2207 fn test_joined_transformed_annotation_1() {
2209 use crate::orm::expressions::F;
2211 use crate::orm::query::QuerySet;
2212
2213 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2214 "converted",
2215 AnnotationValue::Field(F::new("parent__converted_value")),
2216 ));
2217
2218 let sql = qs.to_sql();
2219
2220 assert!(
2221 sql.contains("parent") || sql.contains("converted"),
2222 "SQL should contain 'parent' or 'converted'. Got: {}",
2223 sql
2224 );
2225 }
2226
2227 #[test]
2228 fn test_order_by_aggregate() {
2230 use crate::orm::aggregation::Aggregate;
2232 use crate::orm::query::QuerySet;
2233
2234 let qs = QuerySet::<TestModel>::new()
2235 .values(&["category"])
2236 .annotate(Annotation::field(
2237 "count",
2238 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2239 ))
2240 .order_by(&["count"]);
2241
2242 let sql = qs.to_sql();
2243
2244 assert!(
2245 sql.contains("ORDER BY") && sql.contains("COUNT"),
2246 "SQL should contain 'ORDER BY' and 'COUNT'. Got: {}",
2247 sql
2248 );
2249 }
2250
2251 #[test]
2252 fn test_order_by_aggregate_1() {
2254 use crate::orm::aggregation::Aggregate;
2256 use crate::orm::query::QuerySet;
2257
2258 let qs = QuerySet::<TestModel>::new()
2259 .values(&["type"])
2260 .annotate(Annotation::field(
2261 "total",
2262 AnnotationValue::Aggregate(Aggregate::sum("value")),
2263 ))
2264 .order_by(&["-total"]);
2265
2266 let sql = qs.to_sql();
2267
2268 assert!(
2269 sql.contains("ORDER BY") && sql.contains("SUM"),
2270 "SQL should contain 'ORDER BY' and 'SUM'. Got: {}",
2271 sql
2272 );
2273 }
2274
2275 #[test]
2276 fn test_order_by_alias() {
2278 use crate::orm::expressions::F;
2280 use crate::orm::query::QuerySet;
2281
2282 let qs = QuerySet::<TestModel>::new()
2283 .annotate(Annotation::field(
2284 "other_age",
2285 AnnotationValue::Field(F::new("age")),
2286 ))
2287 .order_by(&["other_age"]);
2288
2289 let sql = qs.to_sql();
2290
2291 assert!(
2292 sql.contains("ORDER BY") && (sql.contains("age") || sql.contains("other_age")),
2293 "SQL should contain 'ORDER BY' and ('age' or 'other_age'). Got: {}",
2294 sql
2295 );
2296 }
2297
2298 #[test]
2299 fn test_order_by_alias_1() {
2301 use crate::orm::expressions::F;
2303 use crate::orm::query::QuerySet;
2304
2305 let qs = QuerySet::<TestModel>::new()
2306 .annotate(Annotation::field(
2307 "name_alias",
2308 AnnotationValue::Field(F::new("name")),
2309 ))
2310 .order_by(&["name_alias"]);
2311
2312 let sql = qs.to_sql();
2313
2314 assert!(
2315 sql.contains("ORDER BY"),
2316 "SQL should contain ORDER BY clause. Got: {}",
2317 sql
2318 );
2319 }
2320
2321 #[test]
2322 fn test_order_by_alias_aggregate() {
2324 use crate::orm::aggregation::Aggregate;
2328 use crate::orm::query::QuerySet;
2329
2330 let qs = QuerySet::<TestModel>::new()
2331 .values(&["age"])
2332 .annotate(Annotation::field(
2333 "age_count",
2334 AnnotationValue::Aggregate(Aggregate::count(Some("age"))),
2335 ))
2336 .order_by(&["age_count", "age"]);
2337
2338 let sql = qs.to_sql();
2339
2340 assert!(
2341 sql.contains("ORDER BY") && (sql.contains("COUNT") || sql.contains("age")),
2342 "SQL should contain 'ORDER BY' and ('COUNT' or 'age'). Got: {}",
2343 sql
2344 );
2345 }
2346
2347 #[test]
2348 fn test_order_by_alias_aggregate_1() {
2350 use crate::orm::aggregation::Aggregate;
2352 use crate::orm::query::QuerySet;
2353
2354 let qs = QuerySet::<TestModel>::new()
2355 .annotate(Annotation::field(
2356 "total",
2357 AnnotationValue::Aggregate(Aggregate::sum("value")),
2358 ))
2359 .order_by(&["-total"]);
2360
2361 let sql = qs.to_sql();
2362
2363 assert!(
2364 sql.contains("ORDER BY") && sql.contains("SUM"),
2365 "SQL should contain 'ORDER BY' and 'SUM'. Got: {}",
2366 sql
2367 );
2368 }
2369
2370 #[test]
2371 fn test_order_by_annotation() {
2373 use crate::orm::expressions::F;
2375 use crate::orm::query::QuerySet;
2376
2377 let qs = QuerySet::<TestModel>::new()
2378 .annotate(Annotation::field(
2379 "other_age",
2380 AnnotationValue::Field(F::new("age")),
2381 ))
2382 .order_by(&["other_age"]);
2383
2384 let sql = qs.to_sql();
2385
2386 assert!(
2387 sql.contains("ORDER BY") && (sql.contains("age") || sql.contains("other_age")),
2388 "SQL should contain 'ORDER BY' and ('age' or 'other_age'). Got: {}",
2389 sql
2390 );
2391 }
2392
2393 #[test]
2394 fn test_order_by_annotation_1() {
2396 use crate::orm::expressions::F;
2398 use crate::orm::query::QuerySet;
2399
2400 let qs = QuerySet::<TestModel>::new()
2401 .annotate(Annotation::field(
2402 "field1_alias",
2403 AnnotationValue::Field(F::new("field1")),
2404 ))
2405 .annotate(Annotation::field(
2406 "field2_alias",
2407 AnnotationValue::Field(F::new("field2")),
2408 ))
2409 .order_by(&["field1_alias", "-field2_alias"]);
2410
2411 let sql = qs.to_sql();
2412
2413 assert!(
2414 sql.contains("ORDER BY"),
2415 "SQL should contain ORDER BY clause. Got: {}",
2416 sql
2417 );
2418 }
2419
2420 #[test]
2421 fn test_q_expression_annotation_with_aggregation() {
2423 use crate::orm::aggregation::Aggregate;
2425 use crate::orm::expressions::F;
2426
2427 let qs = QuerySet::<TestModel>::new()
2428 .annotate(Annotation::field(
2429 "computed",
2430 AnnotationValue::Field(F::new("value")),
2431 ))
2432 .annotate(Annotation::field(
2433 "count",
2434 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2435 ));
2436
2437 let sql = qs.to_sql();
2438
2439 assert!(
2440 sql.contains("COUNT") || sql.contains("value"),
2441 "SQL should contain 'COUNT' or 'value'. Got: {}",
2442 sql
2443 );
2444 }
2445
2446 #[test]
2447 fn test_q_expression_annotation_with_aggregation_1() {
2449 use crate::orm::aggregation::Aggregate;
2451 use crate::orm::expressions::F;
2452
2453 let qs = QuerySet::<TestModel>::new()
2454 .annotate(Annotation::field(
2455 "filtered",
2456 AnnotationValue::Field(F::new("status")),
2457 ))
2458 .annotate(Annotation::field(
2459 "total",
2460 AnnotationValue::Aggregate(Aggregate::sum("amount")),
2461 ));
2462
2463 let sql = qs.to_sql();
2464
2465 assert!(
2466 sql.contains("SUM") || sql.contains("status"),
2467 "SQL should contain 'SUM' or 'status'. Got: {}",
2468 sql
2469 );
2470 }
2471
2472 #[test]
2473 fn test_raw_sql_with_inherited_field() {
2475 use crate::orm::expressions::F;
2477
2478 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2479 "raw_field",
2480 AnnotationValue::Field(F::new("inherited_field")),
2481 ));
2482
2483 let sql = qs.to_sql();
2484
2485 assert!(
2486 sql.contains("inherited_field") || sql.contains("raw_field"),
2487 "SQL should contain 'inherited_field' or 'raw_field'. Got: {}",
2488 sql
2489 );
2490 }
2491
2492 #[test]
2493 fn test_raw_sql_with_inherited_field_1() {
2495 use crate::orm::expressions::F;
2497
2498 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2499 "parent_field",
2500 AnnotationValue::Field(F::new("base_field")),
2501 ));
2502
2503 let sql = qs.to_sql();
2504
2505 assert!(
2506 sql.contains("base_field") || sql.contains("parent_field"),
2507 "SQL should contain 'base_field' or 'parent_field'. Got: {}",
2508 sql
2509 );
2510 }
2511}