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::Model;
369 use crate::orm::expressions::Q;
370 use crate::orm::query::{Filter, FilterOperator, FilterValue, QuerySet};
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
395 fn table_name() -> &'static str {
396 TEST_MODEL_TABLE.as_str()
397 }
398
399 fn new_fields() -> Self::Fields {
400 TestModelFields
401 }
402
403 fn primary_key(&self) -> Option<Self::PrimaryKey> {
404 self.id
405 }
406
407 fn set_primary_key(&mut self, key: Self::PrimaryKey) {
408 self.id = Some(key);
409 }
410 }
411
412 #[test]
413 fn test_aggregate_alias() {
415 use crate::orm::aggregation::Aggregate;
418 use crate::orm::expressions::F;
419 use crate::orm::query::QuerySet;
420
421 let qs = QuerySet::<TestModel>::new()
422 .annotate(Annotation::field(
423 "other_age",
424 AnnotationValue::Field(F::new("age")),
425 ))
426 .aggregate(Aggregate::sum("other_age").with_alias("otherage_sum"));
427
428 let sql = qs.to_sql();
429
430 assert!(
431 sql.contains("SUM") || sql.contains("age"),
432 "SQL should contain 'SUM' or 'age'. Got: {}",
433 sql
434 );
435 }
436
437 #[test]
438 fn test_aggregate_alias_1() {
440 use crate::orm::aggregation::Aggregate;
442 use crate::orm::expressions::F;
443 use crate::orm::query::QuerySet;
444
445 let qs = QuerySet::<TestModel>::new()
446 .annotate(Annotation::field(
447 "value_alias",
448 AnnotationValue::Field(F::new("value")),
449 ))
450 .aggregate(Aggregate::count(Some("value_alias")).with_alias("count_alias"));
451
452 let sql = qs.to_sql();
453
454 assert!(
455 sql.contains("COUNT") || sql.contains("value"),
456 "SQL should contain 'COUNT' or 'value'. Got: {}",
457 sql
458 );
459 }
460
461 #[test]
462 fn test_aggregate_over_annotation() {
464 use crate::orm::aggregation::Aggregate;
467 use crate::orm::expressions::F;
468 use crate::orm::query::QuerySet;
469
470 let qs = QuerySet::<TestModel>::new()
471 .annotate(Annotation::field(
472 "other_age",
473 AnnotationValue::Field(F::new("age")),
474 ))
475 .aggregate(Aggregate::sum("other_age").with_alias("otherage_sum"));
476
477 let sql = qs.to_sql();
478
479 assert!(
481 sql.contains("age") || sql.contains("other_age"),
482 "SQL should contain 'age' or 'other_age'. Got: {}",
483 sql
484 );
485 assert!(
487 sql.contains("SUM("),
488 "SQL should contain SUM clause. Got: {}",
489 sql
490 );
491 }
492
493 #[test]
494 fn test_aggregate_over_annotation_1() {
496 use crate::orm::aggregation::Aggregate;
498 use crate::orm::expressions::F;
499 use crate::orm::query::QuerySet;
500
501 let qs = QuerySet::<TestModel>::new()
502 .annotate(Annotation::field(
503 "doubled",
504 AnnotationValue::Field(F::new("value")),
505 ))
506 .aggregate(Aggregate::avg("doubled").with_alias("avg_doubled"));
507
508 let sql = qs.to_sql();
509
510 assert!(
511 sql.contains("AVG") || sql.contains("value") || sql.contains("doubled"),
512 "SQL should contain 'AVG', 'value', or 'doubled'. Got: {}",
513 sql
514 );
515 }
516
517 #[test]
518 fn test_aggregate_over_full_expression_annotation() {
520 use crate::orm::aggregation::Aggregate;
522 use crate::orm::expressions::F;
523 use crate::orm::query::QuerySet;
524
525 let qs = QuerySet::<TestModel>::new()
526 .annotate(Annotation::field(
527 "computed",
528 AnnotationValue::Field(F::new("field1")),
529 ))
530 .aggregate(Aggregate::max("computed").with_alias("max_computed"));
531
532 let sql = qs.to_sql();
533
534 assert!(
535 sql.contains("MAX") || sql.contains("field1") || sql.contains("computed"),
536 "SQL should contain 'MAX', 'field1', or 'computed'. Got: {}",
537 sql
538 );
539 }
540
541 #[test]
542 fn test_aggregate_over_full_expression_annotation_1() {
544 use crate::orm::aggregation::Aggregate;
546 use crate::orm::expressions::F;
547 use crate::orm::query::QuerySet;
548
549 let qs = QuerySet::<TestModel>::new()
550 .annotate(Annotation::field(
551 "calc",
552 AnnotationValue::Field(F::new("price")),
553 ))
554 .aggregate(Aggregate::min("calc").with_alias("min_calc"));
555
556 let sql = qs.to_sql();
557
558 assert!(
559 sql.contains("MIN") || sql.contains("price") || sql.contains("calc"),
560 "SQL should contain 'MIN', 'price', or 'calc'. Got: {}",
561 sql
562 );
563 }
564
565 #[test]
566 fn test_alias_after_values() {
568 use crate::orm::expressions::F;
570 use crate::orm::query::QuerySet;
571
572 let qs = QuerySet::<TestModel>::new()
573 .values(&["name", "age"])
574 .annotate(Annotation::field(
575 "age_alias",
576 AnnotationValue::Field(F::new("age")),
577 ));
578
579 let sql = qs.to_sql();
580
581 assert!(
582 sql.contains("name") && sql.contains("age"),
583 "SQL should contain 'name' and 'age'. Got: {}",
584 sql
585 );
586 }
587
588 #[test]
589 fn test_alias_after_values_1() {
591 use crate::orm::expressions::F;
593 use crate::orm::query::QuerySet;
594
595 let qs = QuerySet::<TestModel>::new()
596 .values_list(&["id", "name"])
597 .annotate(Annotation::field(
598 "name_alias",
599 AnnotationValue::Field(F::new("name")),
600 ));
601
602 let sql = qs.to_sql();
603
604 assert!(
605 sql.contains("id") && sql.contains("name"),
606 "SQL should contain 'id' and 'name'. Got: {}",
607 sql
608 );
609 }
610
611 #[test]
612 fn test_alias_annotate_with_aggregation() {
614 use crate::orm::aggregation::Aggregate;
617 use crate::orm::expressions::F;
618 use crate::orm::query::QuerySet;
619
620 let qs = QuerySet::<TestModel>::new()
621 .aggregate(Aggregate::count(Some("rating")).with_alias("rating_count_alias"))
622 .annotate(Annotation::field(
623 "rating_count",
624 AnnotationValue::Field(F::new("rating_count_alias")),
625 ));
626
627 let sql = qs.to_sql();
628
629 assert!(
630 sql.contains("COUNT") || sql.contains("rating"),
631 "SQL should contain 'COUNT' or 'rating'. Got: {}",
632 sql
633 );
634 }
635
636 #[test]
637 fn test_alias_annotate_with_aggregation_1() {
639 use crate::orm::aggregation::Aggregate;
641 use crate::orm::expressions::F;
642 use crate::orm::query::QuerySet;
643
644 let qs = QuerySet::<TestModel>::new()
645 .aggregate(Aggregate::sum("price").with_alias("total_alias"))
646 .annotate(Annotation::field(
647 "total",
648 AnnotationValue::Field(F::new("total_alias")),
649 ));
650
651 let sql = qs.to_sql();
652
653 assert!(
654 sql.contains("SUM") || sql.contains("price") || sql.contains("total"),
655 "SQL should contain 'SUM', 'price', or 'total'. Got: {}",
656 sql
657 );
658 }
659
660 #[test]
661 fn test_alias_annotation_expression() {
663 use crate::orm::expressions::F;
665 use crate::orm::query::QuerySet;
666
667 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
668 "expr_alias",
669 AnnotationValue::Field(F::new("field1")),
670 ));
671
672 let sql = qs.to_sql();
673
674 assert!(
675 sql.contains("field1") || sql.contains("expr_alias"),
676 "SQL should contain 'field1' or 'expr_alias'. Got: {}",
677 sql
678 );
679 }
680
681 #[test]
682 fn test_alias_annotation_expression_1() {
684 use crate::orm::expressions::F;
686 use crate::orm::query::QuerySet;
687
688 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
689 "complex",
690 AnnotationValue::Field(F::new("value")),
691 ));
692
693 let sql = qs.to_sql();
694
695 assert!(
696 sql.contains("value") || sql.contains("complex"),
697 "SQL should contain 'value' or 'complex'. Got: {}",
698 sql
699 );
700 }
701
702 #[test]
703 fn test_alias_default_alias_expression() {
705 use crate::orm::expressions::F;
707 use crate::orm::query::QuerySet;
708
709 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
710 "default_alias",
711 AnnotationValue::Field(F::new("name")),
712 ));
713
714 let sql = qs.to_sql();
715
716 assert!(
717 sql.contains("name") || sql.contains("default_alias"),
718 "SQL should contain 'name' or 'default_alias'. Got: {}",
719 sql
720 );
721 }
722
723 #[test]
724 fn test_alias_default_alias_expression_1() {
726 use crate::orm::expressions::F;
728 use crate::orm::query::QuerySet;
729
730 let qs = QuerySet::<TestModel>::new()
731 .annotate(Annotation::field(
732 "alias1",
733 AnnotationValue::Field(F::new("field1")),
734 ))
735 .annotate(Annotation::field(
736 "alias2",
737 AnnotationValue::Field(F::new("field2")),
738 ));
739
740 let sql = qs.to_sql();
741
742 assert!(
743 sql.contains("field1") || sql.contains("field2"),
744 "SQL should contain 'field1' or 'field2'. Got: {}",
745 sql
746 );
747 }
748
749 #[test]
750 fn test_alias_filtered_relation_sql_injection() {
752 let q = Q::new("status", "=", "active");
753 let sql = q.to_sql();
754 assert!(
755 sql.contains("status"),
756 "SQL should contain 'status'. Got: {}",
757 sql
758 );
759 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
760 }
761
762 #[test]
763 fn test_alias_filtered_relation_sql_injection_1() {
765 let q = Q::new("status", "=", "active");
766 let sql = q.to_sql();
767 assert!(
768 sql.contains("status"),
769 "SQL should contain 'status'. Got: {}",
770 sql
771 );
772 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
773 }
774
775 #[test]
776 fn test_alias_filtered_relation_sql_injection_2() {
778 let q = Q::new("status", "=", "active");
779 let sql = q.to_sql();
780 assert!(
781 sql.contains("status"),
782 "SQL should contain 'status'. Got: {}",
783 sql
784 );
785 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
786 }
787
788 #[test]
789 fn test_alias_filtered_relation_sql_injection_3() {
791 let q = Q::new("status", "=", "active");
792 let sql = q.to_sql();
793 assert!(
794 sql.contains("status"),
795 "SQL should contain 'status'. Got: {}",
796 sql
797 );
798 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
799 }
800
801 #[test]
802 fn test_annotate_exists() {
804 use crate::orm::query::QuerySet;
806
807 let subquery = QuerySet::<TestModel>::new()
808 .filter(Filter::new(
809 "status".to_string(),
810 FilterOperator::Eq,
811 FilterValue::String("active".to_string()),
812 ))
813 .as_subquery();
814
815 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
816 "has_active",
817 AnnotationValue::Subquery(subquery),
818 ));
819
820 let sql = qs.to_sql();
821
822 assert!(
823 sql.contains("SELECT") && (sql.contains("active") || sql.contains("status")),
824 "SQL should contain 'SELECT' and ('active' or 'status'). Got: {}",
825 sql
826 );
827 }
828
829 #[test]
830 fn test_annotate_exists_1() {
832 use crate::orm::query::QuerySet;
834
835 let subquery = QuerySet::<TestModel>::new()
836 .filter(Filter::new(
837 "id".to_string(),
838 FilterOperator::Gt,
839 FilterValue::Int(0),
840 ))
841 .as_subquery();
842
843 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
844 "exists_check",
845 AnnotationValue::Subquery(subquery),
846 ));
847
848 let sql = qs.to_sql();
849
850 assert!(
851 sql.starts_with("SELECT") || sql.contains(" SELECT "),
852 "SQL should contain SELECT clause. Got: {}",
853 sql
854 );
855 }
856
857 #[test]
858 fn test_annotate_with_aggregation() {
860 use crate::orm::aggregation::Aggregate;
862 use crate::orm::expressions::F;
863 use crate::orm::query::QuerySet;
864
865 let qs = QuerySet::<TestModel>::new()
866 .annotate(Annotation::field(
867 "value_doubled",
868 AnnotationValue::Field(F::new("value")),
869 ))
870 .aggregate(Aggregate::sum("value_doubled").with_alias("total"));
871
872 let sql = qs.to_sql();
873
874 assert!(
875 sql.contains("SUM") || sql.contains("value"),
876 "SQL should contain 'SUM' or 'value'. Got: {}",
877 sql
878 );
879 }
880
881 #[test]
882 fn test_annotate_with_aggregation_1() {
884 use crate::orm::aggregation::Aggregate;
886 use crate::orm::query::QuerySet;
887
888 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
889 "item_count",
890 AnnotationValue::Aggregate(Aggregate::count(Some("items"))),
891 ));
892
893 let sql = qs.to_sql();
894
895 assert!(
896 sql.contains("COUNT") || sql.contains("items"),
897 "SQL should contain 'COUNT' or 'items'. Got: {}",
898 sql
899 );
900 }
901
902 #[test]
903 fn test_annotation_aggregate_with_m2o() {
905 use crate::orm::aggregation::Aggregate;
907 use crate::orm::query::QuerySet;
908
909 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
910 "related_count",
911 AnnotationValue::Aggregate(Aggregate::count(Some("related_id"))),
912 ));
913
914 let sql = qs.to_sql();
915
916 assert!(
917 sql.contains("COUNT("),
918 "SQL should contain COUNT clause. Got: {}",
919 sql
920 );
921 }
922
923 #[test]
924 fn test_annotation_aggregate_with_m2o_1() {
926 use crate::orm::aggregation::Aggregate;
928 use crate::orm::query::QuerySet;
929
930 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
931 "sum_related",
932 AnnotationValue::Aggregate(Aggregate::sum("related_value")),
933 ));
934
935 let sql = qs.to_sql();
936
937 assert!(
938 sql.contains("SUM("),
939 "SQL should contain SUM clause. Got: {}",
940 sql
941 );
942 }
943
944 #[test]
945 fn test_annotation_and_alias_filter_in_subquery() {
947 let q = Q::new("status", "=", "active");
948 let sql = q.to_sql();
949 assert!(
950 sql.contains("status"),
951 "SQL should contain 'status'. Got: {}",
952 sql
953 );
954 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
955 }
956
957 #[test]
958 fn test_annotation_and_alias_filter_in_subquery_1() {
960 let q = Q::new("status", "=", "active");
961 let sql = q.to_sql();
962 assert!(
963 sql.contains("status"),
964 "SQL should contain 'status'. Got: {}",
965 sql
966 );
967 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
968 }
969
970 #[test]
971 fn test_annotation_and_alias_filter_related_in_subquery() {
973 let q = Q::new("status", "=", "active");
974 let sql = q.to_sql();
975 assert!(
976 sql.contains("status"),
977 "SQL should contain 'status'. Got: {}",
978 sql
979 );
980 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
981 }
982
983 #[test]
984 fn test_annotation_and_alias_filter_related_in_subquery_1() {
986 let q = Q::new("status", "=", "active");
987 let sql = q.to_sql();
988 assert!(
989 sql.contains("status"),
990 "SQL should contain 'status'. Got: {}",
991 sql
992 );
993 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
994 }
995
996 #[test]
997 fn test_annotation_exists_aggregate_values_chaining() {
999 use crate::orm::aggregation::Aggregate;
1003 use crate::orm::query::QuerySet;
1004
1005 let qs = QuerySet::<TestModel>::new()
1006 .values(&["publisher"])
1007 .annotate(Annotation::field(
1008 "max_date",
1009 AnnotationValue::Aggregate(Aggregate::max("pubdate")),
1010 ))
1011 .values_list(&["max_date"]);
1012
1013 let sql = qs.to_sql();
1014
1015 assert!(
1016 sql.contains("MAX") || sql.contains("pubdate"),
1017 "SQL should contain 'MAX' or 'pubdate'. Got: {}",
1018 sql
1019 );
1020 }
1021
1022 #[test]
1023 fn test_annotation_exists_aggregate_values_chaining_1() {
1025 use crate::orm::aggregation::Aggregate;
1027 use crate::orm::query::QuerySet;
1028
1029 let subquery = QuerySet::<TestModel>::new()
1030 .filter(Filter::new(
1031 "id".to_string(),
1032 FilterOperator::Gt,
1033 FilterValue::Int(0),
1034 ))
1035 .as_subquery();
1036
1037 let qs = QuerySet::<TestModel>::new()
1038 .annotate(Annotation::field(
1039 "has_items",
1040 AnnotationValue::Subquery(subquery),
1041 ))
1042 .annotate(Annotation::field(
1043 "count",
1044 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1045 ))
1046 .values(&["count"]);
1047
1048 let sql = qs.to_sql();
1049
1050 assert!(
1051 sql.contains("COUNT") || sql.contains("SELECT"),
1052 "SQL should contain 'COUNT' or 'SELECT'. Got: {}",
1053 sql
1054 );
1055 }
1056
1057 #[test]
1058 fn test_annotation_filter_with_subquery() {
1060 let q = Q::new("status", "=", "active");
1061 let sql = q.to_sql();
1062 assert!(
1063 sql.contains("status"),
1064 "SQL should contain 'status'. Got: {}",
1065 sql
1066 );
1067 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1068 }
1069
1070 #[test]
1071 fn test_annotation_filter_with_subquery_1() {
1073 let q = Q::new("status", "=", "active");
1074 let sql = q.to_sql();
1075 assert!(
1076 sql.contains("status"),
1077 "SQL should contain 'status'. Got: {}",
1078 sql
1079 );
1080 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1081 }
1082
1083 #[test]
1084 fn test_annotation_in_f_grouped_by_annotation() {
1086 use crate::orm::aggregation::Aggregate;
1088 use crate::orm::expressions::F;
1089 use crate::orm::query::QuerySet;
1090
1091 let qs = QuerySet::<TestModel>::new()
1092 .annotate(Annotation::field(
1093 "category",
1094 AnnotationValue::Field(F::new("type")),
1095 ))
1096 .values(&["category"])
1097 .annotate(Annotation::field(
1098 "total",
1099 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1100 ));
1101
1102 let sql = qs.to_sql();
1103
1104 assert!(
1105 sql.contains("COUNT") || sql.contains("type"),
1106 "SQL should contain 'COUNT' or 'type'. Got: {}",
1107 sql
1108 );
1109 }
1110
1111 #[test]
1112 fn test_annotation_in_f_grouped_by_annotation_1() {
1114 use crate::orm::aggregation::Aggregate;
1116 use crate::orm::expressions::F;
1117 use crate::orm::query::QuerySet;
1118
1119 let qs = QuerySet::<TestModel>::new()
1120 .annotate(Annotation::field(
1121 "group_field",
1122 AnnotationValue::Field(F::new("status")),
1123 ))
1124 .values(&["group_field"])
1125 .annotate(Annotation::field(
1126 "count",
1127 AnnotationValue::Aggregate(Aggregate::count(Some("*"))),
1128 ));
1129
1130 let sql = qs.to_sql();
1131
1132 assert!(
1133 sql.contains("COUNT") || sql.contains("status"),
1134 "SQL should contain 'COUNT' or 'status'. Got: {}",
1135 sql
1136 );
1137 }
1138
1139 #[test]
1140 fn test_annotation_subquery_and_aggregate_values_chaining() {
1142 use crate::orm::aggregation::Aggregate;
1146 use crate::orm::query::QuerySet;
1147
1148 let qs = QuerySet::<TestModel>::new()
1149 .values(&["year"])
1150 .annotate(Annotation::field(
1151 "total",
1152 AnnotationValue::Aggregate(Aggregate::sum("pages")),
1153 ));
1154
1155 let sql = qs.to_sql();
1156
1157 assert!(
1158 sql.contains("SUM") || sql.contains("pages"),
1159 "SQL should contain 'SUM' or 'pages'. Got: {}",
1160 sql
1161 );
1162 }
1163
1164 #[test]
1165 fn test_annotation_subquery_and_aggregate_values_chaining_1() {
1167 use crate::orm::aggregation::Aggregate;
1169 use crate::orm::query::QuerySet;
1170
1171 let subquery = QuerySet::<TestModel>::new()
1172 .filter(Filter::new(
1173 "rating".to_string(),
1174 FilterOperator::Gt,
1175 FilterValue::Int(3),
1176 ))
1177 .as_subquery();
1178
1179 let qs = QuerySet::<TestModel>::new()
1180 .annotate(Annotation::field(
1181 "top_rating",
1182 AnnotationValue::Subquery(subquery),
1183 ))
1184 .annotate(Annotation::field(
1185 "total",
1186 AnnotationValue::Aggregate(Aggregate::sum("value")),
1187 ))
1188 .values(&["total", "top_rating"]);
1189
1190 let sql = qs.to_sql();
1191
1192 assert!(
1193 sql.contains("SUM") || sql.contains("SELECT"),
1194 "SQL should contain 'SUM' or 'SELECT'. Got: {}",
1195 sql
1196 );
1197 }
1198
1199 #[test]
1200 fn test_arguments_must_be_expressions() {
1202 use crate::orm::expressions::F;
1204 use crate::orm::query::QuerySet;
1205
1206 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1207 "expr",
1208 AnnotationValue::Field(F::new("field1")),
1209 ));
1210
1211 let sql = qs.to_sql();
1212
1213 assert!(
1214 sql.contains("field1") || sql.contains("expr"),
1215 "SQL should contain 'field1' or 'expr'. Got: {}",
1216 sql
1217 );
1218 }
1219
1220 #[test]
1221 fn test_arguments_must_be_expressions_1() {
1223 use crate::orm::expressions::F;
1225 use crate::orm::query::QuerySet;
1226
1227 let qs = QuerySet::<TestModel>::new()
1228 .annotate(Annotation::field(
1229 "expr1",
1230 AnnotationValue::Field(F::new("field1")),
1231 ))
1232 .annotate(Annotation::field(
1233 "expr2",
1234 AnnotationValue::Field(F::new("field2")),
1235 ));
1236
1237 let sql = qs.to_sql();
1238
1239 assert!(
1240 sql.contains("field1") || sql.contains("field2"),
1241 "SQL should contain 'field1' or 'field2'. Got: {}",
1242 sql
1243 );
1244 }
1245
1246 #[test]
1247 fn test_boolean_value_annotation() {
1249 use crate::orm::expressions::F;
1251 use crate::orm::query::QuerySet;
1252
1253 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1254 "is_active",
1255 AnnotationValue::Field(F::new("active")),
1256 ));
1257
1258 let sql = qs.to_sql();
1259
1260 assert!(
1261 sql.contains("active") || sql.contains("is_active"),
1262 "SQL should contain 'active' or 'is_active'. Got: {}",
1263 sql
1264 );
1265 }
1266
1267 #[test]
1268 fn test_boolean_value_annotation_1() {
1270 use crate::orm::expressions::F;
1272 use crate::orm::query::QuerySet;
1273
1274 let qs = QuerySet::<TestModel>::new()
1275 .annotate(Annotation::field(
1276 "is_enabled",
1277 AnnotationValue::Field(F::new("enabled")),
1278 ))
1279 .filter(Filter::new(
1280 "is_enabled".to_string(),
1281 FilterOperator::Eq,
1282 FilterValue::Bool(true),
1283 ));
1284
1285 let sql = qs.to_sql();
1286
1287 assert!(
1288 sql.contains("enabled") || sql.contains("WHERE"),
1289 "SQL should contain 'enabled' or 'WHERE'. Got: {}",
1290 sql
1291 );
1292 }
1293
1294 #[test]
1295 fn test_chaining_annotation_filter_with_m2m() {
1297 let q = Q::new("status", "=", "active");
1298 let sql = q.to_sql();
1299 assert!(
1300 sql.contains("status"),
1301 "SQL should contain 'status'. Got: {}",
1302 sql
1303 );
1304 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1305 }
1306
1307 #[test]
1308 fn test_chaining_annotation_filter_with_m2m_1() {
1310 let q = Q::new("status", "=", "active");
1311 let sql = q.to_sql();
1312 assert!(
1313 sql.contains("status"),
1314 "SQL should contain 'status'. Got: {}",
1315 sql
1316 );
1317 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1318 }
1319
1320 #[test]
1321 fn test_column_field_ordering() {
1323 use crate::orm::expressions::F;
1325 use crate::orm::query::QuerySet;
1326
1327 let qs = QuerySet::<TestModel>::new()
1328 .annotate(Annotation::field(
1329 "annotated",
1330 AnnotationValue::Field(F::new("field1")),
1331 ))
1332 .values(&["id", "name", "annotated"]);
1333
1334 let sql = qs.to_sql();
1335
1336 assert!(
1337 sql.contains("id")
1338 && sql.contains("name")
1339 && (sql.contains("field1") || sql.contains("annotated")),
1340 "SQL should contain 'id', 'name', and ('field1' or 'annotated'). Got: {}",
1341 sql
1342 );
1343 }
1344
1345 #[test]
1346 fn test_column_field_ordering_1() {
1348 use crate::orm::expressions::F;
1350 use crate::orm::query::QuerySet;
1351
1352 let qs = QuerySet::<TestModel>::new()
1353 .annotate(Annotation::field(
1354 "extra",
1355 AnnotationValue::Field(F::new("value")),
1356 ))
1357 .values(&["extra", "id"]);
1358
1359 let sql = qs.to_sql();
1360
1361 assert!(
1362 sql.contains("id") && (sql.contains("value") || sql.contains("extra")),
1363 "SQL should contain 'id' and ('value' or 'extra'). Got: {}",
1364 sql
1365 );
1366 }
1367
1368 #[test]
1369 fn test_column_field_ordering_with_deferred() {
1371 use crate::orm::expressions::F;
1373 use crate::orm::query::QuerySet;
1374
1375 let qs = QuerySet::<TestModel>::new()
1376 .defer(&["description"])
1377 .annotate(Annotation::field(
1378 "computed",
1379 AnnotationValue::Field(F::new("value")),
1380 ));
1381
1382 let sql = qs.to_sql();
1383
1384 assert!(
1385 sql.contains("value") || sql.contains("computed"),
1386 "SQL should contain 'value' or 'computed'. Got: {}",
1387 sql
1388 );
1389 }
1390
1391 #[test]
1392 fn test_column_field_ordering_with_deferred_1() {
1394 use crate::orm::expressions::F;
1396 use crate::orm::query::QuerySet;
1397
1398 let qs = QuerySet::<TestModel>::new()
1399 .only(&["id", "name"])
1400 .annotate(Annotation::field(
1401 "calc",
1402 AnnotationValue::Field(F::new("field1")),
1403 ));
1404
1405 let sql = qs.to_sql();
1406
1407 assert!(
1408 sql.contains("id") && sql.contains("name"),
1409 "SQL should contain 'id' and 'name'. Got: {}",
1410 sql
1411 );
1412 }
1413
1414 #[test]
1415 fn test_combined_expression_annotation_with_aggregation() {
1417 use crate::orm::aggregation::Aggregate;
1422 use crate::orm::expressions::F;
1423 use crate::orm::query::QuerySet;
1424
1425 let qs = QuerySet::<TestModel>::new()
1426 .annotate(Annotation::field(
1427 "combined",
1428 AnnotationValue::Field(F::new("value")),
1429 ))
1430 .annotate(Annotation::field(
1431 "rating_count",
1432 AnnotationValue::Aggregate(Aggregate::count(Some("rating"))),
1433 ));
1434
1435 let sql = qs.to_sql();
1436
1437 assert!(
1438 sql.contains("COUNT") || sql.contains("value"),
1439 "SQL should contain 'COUNT' or 'value'. Got: {}",
1440 sql
1441 );
1442 }
1443
1444 #[test]
1445 fn test_combined_expression_annotation_with_aggregation_1() {
1447 use crate::orm::aggregation::Aggregate;
1449 use crate::orm::expressions::F;
1450 use crate::orm::query::QuerySet;
1451
1452 let qs = QuerySet::<TestModel>::new()
1453 .annotate(Annotation::field(
1454 "expr",
1455 AnnotationValue::Field(F::new("field1")),
1456 ))
1457 .annotate(Annotation::field(
1458 "total",
1459 AnnotationValue::Aggregate(Aggregate::sum("field2")),
1460 ));
1461
1462 let sql = qs.to_sql();
1463
1464 assert!(
1465 sql.contains("SUM") || sql.contains("field"),
1466 "SQL should contain 'SUM' or 'field'. Got: {}",
1467 sql
1468 );
1469 }
1470
1471 #[test]
1472 fn test_combined_f_expression_annotation_with_aggregation() {
1474 use crate::orm::aggregation::Aggregate;
1479 use crate::orm::expressions::F;
1480 use crate::orm::query::QuerySet;
1481
1482 let qs = QuerySet::<TestModel>::new()
1483 .annotate(Annotation::field(
1484 "combined",
1485 AnnotationValue::Field(F::new("price")),
1486 ))
1487 .annotate(Annotation::field(
1488 "rating_count",
1489 AnnotationValue::Aggregate(Aggregate::count(Some("rating"))),
1490 ));
1491
1492 let sql = qs.to_sql();
1493
1494 assert!(
1495 sql.contains("COUNT") || sql.contains("price") || sql.contains("rating"),
1496 "SQL should contain 'COUNT', 'price', or 'rating'. Got: {}",
1497 sql
1498 );
1499 }
1500
1501 #[test]
1502 fn test_combined_f_expression_annotation_with_aggregation_1() {
1504 use crate::orm::aggregation::Aggregate;
1506 use crate::orm::expressions::F;
1507 use crate::orm::query::QuerySet;
1508
1509 let qs = QuerySet::<TestModel>::new()
1510 .annotate(Annotation::field(
1511 "calc",
1512 AnnotationValue::Field(F::new("value1")),
1513 ))
1514 .annotate(Annotation::field(
1515 "max_value",
1516 AnnotationValue::Aggregate(Aggregate::max("value2")),
1517 ));
1518
1519 let sql = qs.to_sql();
1520
1521 assert!(
1522 sql.contains("MAX") || sql.contains("value"),
1523 "SQL should contain 'MAX' or 'value'. Got: {}",
1524 sql
1525 );
1526 }
1527
1528 #[test]
1529 fn test_distinct_on_alias() {
1531 use crate::orm::expressions::F;
1534 use crate::orm::query::QuerySet;
1535
1536 let qs = QuerySet::<TestModel>::new()
1537 .annotate(Annotation::field(
1538 "rating_alias",
1539 AnnotationValue::Field(F::new("rating")),
1540 ))
1541 .distinct();
1542
1543 let sql = qs.to_sql();
1544
1545 assert!(
1546 sql.contains("DISTINCT") || sql.contains("rating"),
1547 "SQL should contain 'DISTINCT' or 'rating'. Got: {}",
1548 sql
1549 );
1550 }
1551
1552 #[test]
1553 fn test_distinct_on_alias_1() {
1555 use crate::orm::expressions::F;
1557 use crate::orm::query::QuerySet;
1558
1559 let qs = QuerySet::<TestModel>::new()
1560 .annotate(Annotation::field(
1561 "name_alias",
1562 AnnotationValue::Field(F::new("name")),
1563 ))
1564 .distinct();
1565
1566 let sql = qs.to_sql();
1567
1568 assert!(
1569 sql.starts_with("DISTINCT") || sql.contains(" DISTINCT "),
1570 "SQL should contain DISTINCT clause. Got: {}",
1571 sql
1572 );
1573 }
1574
1575 #[test]
1576 fn test_distinct_on_with_annotation() {
1578 use crate::orm::expressions::F;
1581 use crate::orm::query::QuerySet;
1582
1583 let qs = QuerySet::<TestModel>::new()
1584 .annotate(Annotation::field(
1585 "name_lower",
1586 AnnotationValue::Field(F::new("last_name")),
1587 ))
1588 .distinct();
1589
1590 let sql = qs.to_sql();
1591
1592 assert!(
1593 sql.contains("DISTINCT") && (sql.contains("last_name") || sql.contains("name_lower"))
1594 );
1595 }
1596
1597 #[test]
1598 fn test_distinct_on_with_annotation_1() {
1600 use crate::orm::expressions::F;
1602 use crate::orm::query::QuerySet;
1603
1604 let qs = QuerySet::<TestModel>::new()
1605 .annotate(Annotation::field(
1606 "field1_lower",
1607 AnnotationValue::Field(F::new("field1")),
1608 ))
1609 .annotate(Annotation::field(
1610 "field2_lower",
1611 AnnotationValue::Field(F::new("field2")),
1612 ))
1613 .distinct();
1614
1615 let sql = qs.to_sql();
1616
1617 assert!(
1618 sql.starts_with("DISTINCT") || sql.contains(" DISTINCT "),
1619 "SQL should contain DISTINCT clause. Got: {}",
1620 sql
1621 );
1622 }
1623
1624 #[test]
1625 fn test_empty_expression_annotation() {
1627 use crate::orm::expressions::F;
1629 use crate::orm::query::QuerySet;
1630
1631 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1632 "simple",
1633 AnnotationValue::Field(F::new("id")),
1634 ));
1635
1636 let sql = qs.to_sql();
1637
1638 assert!(
1639 sql.contains("id") || sql.contains("simple"),
1640 "SQL should contain 'id' or 'simple'. Got: {}",
1641 sql
1642 );
1643 }
1644
1645 #[test]
1646 fn test_empty_expression_annotation_1() {
1648 use crate::orm::expressions::F;
1650 use crate::orm::query::QuerySet;
1651
1652 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1653 "minimal",
1654 AnnotationValue::Field(F::new("name")),
1655 ));
1656
1657 let sql = qs.to_sql();
1658
1659 assert!(
1660 sql.contains("name") || sql.contains("minimal"),
1661 "SQL should contain 'name' or 'minimal'. Got: {}",
1662 sql
1663 );
1664 }
1665
1666 #[test]
1667 fn test_filter_agg_with_double_f() {
1669 let q = Q::new("status", "=", "active");
1670 let sql = q.to_sql();
1671 assert!(
1672 sql.contains("status"),
1673 "SQL should contain 'status'. Got: {}",
1674 sql
1675 );
1676 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1677 }
1678
1679 #[test]
1680 fn test_filter_agg_with_double_f_1() {
1682 let q = Q::new("status", "=", "active");
1683 let sql = q.to_sql();
1684 assert!(
1685 sql.contains("status"),
1686 "SQL should contain 'status'. Got: {}",
1687 sql
1688 );
1689 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1690 }
1691
1692 #[test]
1693 fn test_filter_alias_agg_with_double_f() {
1695 let q = Q::new("status", "=", "active");
1696 let sql = q.to_sql();
1697 assert!(
1698 sql.contains("status"),
1699 "SQL should contain 'status'. Got: {}",
1700 sql
1701 );
1702 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1703 }
1704
1705 #[test]
1706 fn test_filter_alias_agg_with_double_f_1() {
1708 let q = Q::new("status", "=", "active");
1709 let sql = q.to_sql();
1710 assert!(
1711 sql.contains("status"),
1712 "SQL should contain 'status'. Got: {}",
1713 sql
1714 );
1715 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1716 }
1717
1718 #[test]
1719 fn test_filter_alias_with_double_f() {
1721 let q = Q::new("status", "=", "active");
1722 let sql = q.to_sql();
1723 assert!(
1724 sql.contains("status"),
1725 "SQL should contain 'status'. Got: {}",
1726 sql
1727 );
1728 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1729 }
1730
1731 #[test]
1732 fn test_filter_alias_with_double_f_1() {
1734 let q = Q::new("status", "=", "active");
1735 let sql = q.to_sql();
1736 assert!(
1737 sql.contains("status"),
1738 "SQL should contain 'status'. Got: {}",
1739 sql
1740 );
1741 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1742 }
1743
1744 #[test]
1745 fn test_filter_alias_with_f() {
1747 let q = Q::new("status", "=", "active");
1748 let sql = q.to_sql();
1749 assert!(
1750 sql.contains("status"),
1751 "SQL should contain 'status'. Got: {}",
1752 sql
1753 );
1754 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1755 }
1756
1757 #[test]
1758 fn test_filter_alias_with_f_1() {
1760 let q = Q::new("status", "=", "active");
1761 let sql = q.to_sql();
1762 assert!(
1763 sql.contains("status"),
1764 "SQL should contain 'status'. Got: {}",
1765 sql
1766 );
1767 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1768 }
1769
1770 #[test]
1771 fn test_filter_annotation() {
1773 let q = Q::new("status", "=", "active");
1774 let sql = q.to_sql();
1775 assert!(
1776 sql.contains("status"),
1777 "SQL should contain 'status'. Got: {}",
1778 sql
1779 );
1780 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1781 }
1782
1783 #[test]
1784 fn test_filter_annotation_1() {
1786 let q = Q::new("status", "=", "active");
1787 let sql = q.to_sql();
1788 assert!(
1789 sql.contains("status"),
1790 "SQL should contain 'status'. Got: {}",
1791 sql
1792 );
1793 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1794 }
1795
1796 #[test]
1797 fn test_filter_annotation_with_double_f() {
1799 let q = Q::new("status", "=", "active");
1800 let sql = q.to_sql();
1801 assert!(
1802 sql.contains("status"),
1803 "SQL should contain 'status'. Got: {}",
1804 sql
1805 );
1806 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1807 }
1808
1809 #[test]
1810 fn test_filter_annotation_with_double_f_1() {
1812 let q = Q::new("status", "=", "active");
1813 let sql = q.to_sql();
1814 assert!(
1815 sql.contains("status"),
1816 "SQL should contain 'status'. Got: {}",
1817 sql
1818 );
1819 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1820 }
1821
1822 #[test]
1823 fn test_filter_annotation_with_f() {
1825 let q = Q::new("status", "=", "active");
1826 let sql = q.to_sql();
1827 assert!(
1828 sql.contains("status"),
1829 "SQL should contain 'status'. Got: {}",
1830 sql
1831 );
1832 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1833 }
1834
1835 #[test]
1836 fn test_filter_annotation_with_f_1() {
1838 let q = Q::new("status", "=", "active");
1839 let sql = q.to_sql();
1840 assert!(
1841 sql.contains("status"),
1842 "SQL should contain 'status'. Got: {}",
1843 sql
1844 );
1845 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1846 }
1847
1848 #[test]
1849 fn test_filter_decimal_annotation() {
1851 let q = Q::new("status", "=", "active");
1852 let sql = q.to_sql();
1853 assert!(
1854 sql.contains("status"),
1855 "SQL should contain 'status'. Got: {}",
1856 sql
1857 );
1858 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1859 }
1860
1861 #[test]
1862 fn test_filter_decimal_annotation_1() {
1864 let q = Q::new("status", "=", "active");
1865 let sql = q.to_sql();
1866 assert!(
1867 sql.contains("status"),
1868 "SQL should contain 'status'. Got: {}",
1869 sql
1870 );
1871 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1872 }
1873
1874 #[test]
1875 fn test_filter_wrong_annotation() {
1877 let q = Q::new("status", "=", "active");
1878 let sql = q.to_sql();
1879 assert!(
1880 sql.contains("status"),
1881 "SQL should contain 'status'. Got: {}",
1882 sql
1883 );
1884 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1885 }
1886
1887 #[test]
1888 fn test_filter_wrong_annotation_1() {
1890 let q = Q::new("status", "=", "active");
1891 let sql = q.to_sql();
1892 assert!(
1893 sql.contains("status"),
1894 "SQL should contain 'status'. Got: {}",
1895 sql
1896 );
1897 assert!(sql.contains("="), "SQL should contain '='. Got: {}", sql);
1898 }
1899
1900 #[test]
1901 fn test_full_expression_annotation() {
1903 use crate::orm::expressions::F;
1905 use crate::orm::query::QuerySet;
1906
1907 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
1908 "full_expr",
1909 AnnotationValue::Field(F::new("field1")),
1910 ));
1911
1912 let sql = qs.to_sql();
1913
1914 assert!(
1915 sql.contains("field1") || sql.contains("full_expr"),
1916 "SQL should contain 'field1' or 'full_expr'. Got: {}",
1917 sql
1918 );
1919 }
1920
1921 #[test]
1922 fn test_full_expression_annotation_1() {
1924 use crate::orm::expressions::F;
1926 use crate::orm::query::QuerySet;
1927
1928 let qs = QuerySet::<TestModel>::new()
1929 .annotate(Annotation::field(
1930 "complex_expr",
1931 AnnotationValue::Field(F::new("value1")),
1932 ))
1933 .filter(Filter::new(
1934 "complex_expr".to_string(),
1935 FilterOperator::Gt,
1936 FilterValue::Int(0),
1937 ));
1938
1939 let sql = qs.to_sql();
1940
1941 assert!(
1942 sql.contains("value1") || sql.contains("complex_expr"),
1943 "SQL should contain 'value1' or 'complex_expr'. Got: {}",
1944 sql
1945 );
1946 }
1947
1948 #[test]
1949 fn test_full_expression_annotation_with_aggregation() {
1951 use crate::orm::aggregation::Aggregate;
1953 use crate::orm::expressions::F;
1954 use crate::orm::query::QuerySet;
1955
1956 let qs = QuerySet::<TestModel>::new()
1957 .annotate(Annotation::field(
1958 "expr",
1959 AnnotationValue::Field(F::new("price")),
1960 ))
1961 .annotate(Annotation::field(
1962 "total",
1963 AnnotationValue::Aggregate(Aggregate::sum("quantity")),
1964 ));
1965
1966 let sql = qs.to_sql();
1967
1968 assert!(
1969 sql.contains("SUM") || sql.contains("price"),
1970 "SQL should contain 'SUM' or 'price'. Got: {}",
1971 sql
1972 );
1973 }
1974
1975 #[test]
1976 fn test_full_expression_annotation_with_aggregation_1() {
1978 use crate::orm::aggregation::Aggregate;
1980 use crate::orm::expressions::F;
1981 use crate::orm::query::QuerySet;
1982
1983 let qs = QuerySet::<TestModel>::new()
1984 .annotate(Annotation::field(
1985 "calc",
1986 AnnotationValue::Field(F::new("value")),
1987 ))
1988 .annotate(Annotation::field(
1989 "count",
1990 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
1991 ));
1992
1993 let sql = qs.to_sql();
1994
1995 assert!(
1996 sql.contains("COUNT") || sql.contains("value"),
1997 "SQL should contain 'COUNT' or 'value'. Got: {}",
1998 sql
1999 );
2000 }
2001
2002 #[test]
2003 fn test_full_expression_wrapped_annotation() {
2005 use crate::orm::expressions::F;
2007 use crate::orm::query::QuerySet;
2008
2009 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2010 "wrapped",
2011 AnnotationValue::Field(F::new("field1")),
2012 ));
2013
2014 let sql = qs.to_sql();
2015
2016 assert!(
2017 sql.contains("field1") || sql.contains("wrapped"),
2018 "SQL should contain 'field1' or 'wrapped'. Got: {}",
2019 sql
2020 );
2021 }
2022
2023 #[test]
2024 fn test_full_expression_wrapped_annotation_1() {
2026 use crate::orm::expressions::F;
2028 use crate::orm::query::QuerySet;
2029
2030 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2031 "wrapped_expr",
2032 AnnotationValue::Field(F::new("value")),
2033 ));
2034
2035 let sql = qs.to_sql();
2036
2037 assert!(
2038 sql.contains("value") || sql.contains("wrapped_expr"),
2039 "SQL should contain 'value' or 'wrapped_expr'. Got: {}",
2040 sql
2041 );
2042 }
2043
2044 #[test]
2045 fn test_grouping_by_q_expression_annotation() {
2047 use crate::orm::aggregation::Aggregate;
2049 use crate::orm::expressions::F;
2050 use crate::orm::query::QuerySet;
2051
2052 let qs = QuerySet::<TestModel>::new()
2053 .annotate(Annotation::field(
2054 "group_expr",
2055 AnnotationValue::Field(F::new("category")),
2056 ))
2057 .values(&["group_expr"])
2058 .annotate(Annotation::field(
2059 "count",
2060 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2061 ));
2062
2063 let sql = qs.to_sql();
2064
2065 assert!(
2066 sql.contains("COUNT") || sql.contains("category"),
2067 "SQL should contain 'COUNT' or 'category'. Got: {}",
2068 sql
2069 );
2070 }
2071
2072 #[test]
2073 fn test_grouping_by_q_expression_annotation_1() {
2075 use crate::orm::aggregation::Aggregate;
2077 use crate::orm::expressions::F;
2078 use crate::orm::query::QuerySet;
2079
2080 let qs = QuerySet::<TestModel>::new()
2081 .annotate(Annotation::field(
2082 "status_group",
2083 AnnotationValue::Field(F::new("status")),
2084 ))
2085 .values(&["status_group"])
2086 .annotate(Annotation::field(
2087 "total",
2088 AnnotationValue::Aggregate(Aggregate::sum("value")),
2089 ));
2090
2091 let sql = qs.to_sql();
2092
2093 assert!(
2094 sql.contains("SUM") || sql.contains("status"),
2095 "SQL should contain 'SUM' or 'status'. Got: {}",
2096 sql
2097 );
2098 }
2099
2100 #[test]
2101 fn test_joined_alias_annotation() {
2103 use crate::orm::expressions::F;
2105 use crate::orm::query::QuerySet;
2106
2107 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2108 "related_field",
2109 AnnotationValue::Field(F::new("related__name")),
2110 ));
2111
2112 let sql = qs.to_sql();
2113
2114 assert!(
2115 sql.contains("related") || sql.contains("name"),
2116 "SQL should contain 'related' or 'name'. Got: {}",
2117 sql
2118 );
2119 }
2120
2121 #[test]
2122 fn test_joined_alias_annotation_1() {
2124 use crate::orm::expressions::F;
2126 use crate::orm::query::QuerySet;
2127
2128 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2129 "parent_value",
2130 AnnotationValue::Field(F::new("parent__value")),
2131 ));
2132
2133 let sql = qs.to_sql();
2134
2135 assert!(
2136 sql.contains("parent") || sql.contains("value"),
2137 "SQL should contain 'parent' or 'value'. Got: {}",
2138 sql
2139 );
2140 }
2141
2142 #[test]
2143 fn test_joined_annotation() {
2145 use crate::orm::expressions::F;
2147 use crate::orm::query::QuerySet;
2148
2149 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2150 "fk_field",
2151 AnnotationValue::Field(F::new("foreign_key__field")),
2152 ));
2153
2154 let sql = qs.to_sql();
2155
2156 assert!(
2157 sql.contains("foreign_key") || sql.contains("field"),
2158 "SQL should contain 'foreign_key' or 'field'. Got: {}",
2159 sql
2160 );
2161 }
2162
2163 #[test]
2164 fn test_joined_annotation_1() {
2166 use crate::orm::expressions::F;
2168 use crate::orm::query::QuerySet;
2169
2170 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2171 "related_data",
2172 AnnotationValue::Field(F::new("relation__data")),
2173 ));
2174
2175 let sql = qs.to_sql();
2176
2177 assert!(
2178 sql.contains("relation") || sql.contains("data"),
2179 "SQL should contain 'relation' or 'data'. Got: {}",
2180 sql
2181 );
2182 }
2183
2184 #[test]
2185 fn test_joined_transformed_annotation() {
2187 use crate::orm::expressions::F;
2189 use crate::orm::query::QuerySet;
2190
2191 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2192 "transformed",
2193 AnnotationValue::Field(F::new("related__transformed_field")),
2194 ));
2195
2196 let sql = qs.to_sql();
2197
2198 assert!(
2199 sql.contains("related") || sql.contains("transformed"),
2200 "SQL should contain 'related' or 'transformed'. Got: {}",
2201 sql
2202 );
2203 }
2204
2205 #[test]
2206 fn test_joined_transformed_annotation_1() {
2208 use crate::orm::expressions::F;
2210 use crate::orm::query::QuerySet;
2211
2212 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2213 "converted",
2214 AnnotationValue::Field(F::new("parent__converted_value")),
2215 ));
2216
2217 let sql = qs.to_sql();
2218
2219 assert!(
2220 sql.contains("parent") || sql.contains("converted"),
2221 "SQL should contain 'parent' or 'converted'. Got: {}",
2222 sql
2223 );
2224 }
2225
2226 #[test]
2227 fn test_order_by_aggregate() {
2229 use crate::orm::aggregation::Aggregate;
2231 use crate::orm::query::QuerySet;
2232
2233 let qs = QuerySet::<TestModel>::new()
2234 .values(&["category"])
2235 .annotate(Annotation::field(
2236 "count",
2237 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2238 ))
2239 .order_by(&["count"]);
2240
2241 let sql = qs.to_sql();
2242
2243 assert!(
2244 sql.contains("ORDER BY") && sql.contains("COUNT"),
2245 "SQL should contain 'ORDER BY' and 'COUNT'. Got: {}",
2246 sql
2247 );
2248 }
2249
2250 #[test]
2251 fn test_order_by_aggregate_1() {
2253 use crate::orm::aggregation::Aggregate;
2255 use crate::orm::query::QuerySet;
2256
2257 let qs = QuerySet::<TestModel>::new()
2258 .values(&["type"])
2259 .annotate(Annotation::field(
2260 "total",
2261 AnnotationValue::Aggregate(Aggregate::sum("value")),
2262 ))
2263 .order_by(&["-total"]);
2264
2265 let sql = qs.to_sql();
2266
2267 assert!(
2268 sql.contains("ORDER BY") && sql.contains("SUM"),
2269 "SQL should contain 'ORDER BY' and 'SUM'. Got: {}",
2270 sql
2271 );
2272 }
2273
2274 #[test]
2275 fn test_order_by_alias() {
2277 use crate::orm::expressions::F;
2279 use crate::orm::query::QuerySet;
2280
2281 let qs = QuerySet::<TestModel>::new()
2282 .annotate(Annotation::field(
2283 "other_age",
2284 AnnotationValue::Field(F::new("age")),
2285 ))
2286 .order_by(&["other_age"]);
2287
2288 let sql = qs.to_sql();
2289
2290 assert!(
2291 sql.contains("ORDER BY") && (sql.contains("age") || sql.contains("other_age")),
2292 "SQL should contain 'ORDER BY' and ('age' or 'other_age'). Got: {}",
2293 sql
2294 );
2295 }
2296
2297 #[test]
2298 fn test_order_by_alias_1() {
2300 use crate::orm::expressions::F;
2302 use crate::orm::query::QuerySet;
2303
2304 let qs = QuerySet::<TestModel>::new()
2305 .annotate(Annotation::field(
2306 "name_alias",
2307 AnnotationValue::Field(F::new("name")),
2308 ))
2309 .order_by(&["name_alias"]);
2310
2311 let sql = qs.to_sql();
2312
2313 assert!(
2314 sql.contains("ORDER BY"),
2315 "SQL should contain ORDER BY clause. Got: {}",
2316 sql
2317 );
2318 }
2319
2320 #[test]
2321 fn test_order_by_alias_aggregate() {
2323 use crate::orm::aggregation::Aggregate;
2327 use crate::orm::query::QuerySet;
2328
2329 let qs = QuerySet::<TestModel>::new()
2330 .values(&["age"])
2331 .annotate(Annotation::field(
2332 "age_count",
2333 AnnotationValue::Aggregate(Aggregate::count(Some("age"))),
2334 ))
2335 .order_by(&["age_count", "age"]);
2336
2337 let sql = qs.to_sql();
2338
2339 assert!(
2340 sql.contains("ORDER BY") && (sql.contains("COUNT") || sql.contains("age")),
2341 "SQL should contain 'ORDER BY' and ('COUNT' or 'age'). Got: {}",
2342 sql
2343 );
2344 }
2345
2346 #[test]
2347 fn test_order_by_alias_aggregate_1() {
2349 use crate::orm::aggregation::Aggregate;
2351 use crate::orm::query::QuerySet;
2352
2353 let qs = QuerySet::<TestModel>::new()
2354 .annotate(Annotation::field(
2355 "total",
2356 AnnotationValue::Aggregate(Aggregate::sum("value")),
2357 ))
2358 .order_by(&["-total"]);
2359
2360 let sql = qs.to_sql();
2361
2362 assert!(
2363 sql.contains("ORDER BY") && sql.contains("SUM"),
2364 "SQL should contain 'ORDER BY' and 'SUM'. Got: {}",
2365 sql
2366 );
2367 }
2368
2369 #[test]
2370 fn test_order_by_annotation() {
2372 use crate::orm::expressions::F;
2374 use crate::orm::query::QuerySet;
2375
2376 let qs = QuerySet::<TestModel>::new()
2377 .annotate(Annotation::field(
2378 "other_age",
2379 AnnotationValue::Field(F::new("age")),
2380 ))
2381 .order_by(&["other_age"]);
2382
2383 let sql = qs.to_sql();
2384
2385 assert!(
2386 sql.contains("ORDER BY") && (sql.contains("age") || sql.contains("other_age")),
2387 "SQL should contain 'ORDER BY' and ('age' or 'other_age'). Got: {}",
2388 sql
2389 );
2390 }
2391
2392 #[test]
2393 fn test_order_by_annotation_1() {
2395 use crate::orm::expressions::F;
2397 use crate::orm::query::QuerySet;
2398
2399 let qs = QuerySet::<TestModel>::new()
2400 .annotate(Annotation::field(
2401 "field1_alias",
2402 AnnotationValue::Field(F::new("field1")),
2403 ))
2404 .annotate(Annotation::field(
2405 "field2_alias",
2406 AnnotationValue::Field(F::new("field2")),
2407 ))
2408 .order_by(&["field1_alias", "-field2_alias"]);
2409
2410 let sql = qs.to_sql();
2411
2412 assert!(
2413 sql.contains("ORDER BY"),
2414 "SQL should contain ORDER BY clause. Got: {}",
2415 sql
2416 );
2417 }
2418
2419 #[test]
2420 fn test_q_expression_annotation_with_aggregation() {
2422 use crate::orm::aggregation::Aggregate;
2424 use crate::orm::expressions::F;
2425
2426 let qs = QuerySet::<TestModel>::new()
2427 .annotate(Annotation::field(
2428 "computed",
2429 AnnotationValue::Field(F::new("value")),
2430 ))
2431 .annotate(Annotation::field(
2432 "count",
2433 AnnotationValue::Aggregate(Aggregate::count(Some("id"))),
2434 ));
2435
2436 let sql = qs.to_sql();
2437
2438 assert!(
2439 sql.contains("COUNT") || sql.contains("value"),
2440 "SQL should contain 'COUNT' or 'value'. Got: {}",
2441 sql
2442 );
2443 }
2444
2445 #[test]
2446 fn test_q_expression_annotation_with_aggregation_1() {
2448 use crate::orm::aggregation::Aggregate;
2450 use crate::orm::expressions::F;
2451
2452 let qs = QuerySet::<TestModel>::new()
2453 .annotate(Annotation::field(
2454 "filtered",
2455 AnnotationValue::Field(F::new("status")),
2456 ))
2457 .annotate(Annotation::field(
2458 "total",
2459 AnnotationValue::Aggregate(Aggregate::sum("amount")),
2460 ));
2461
2462 let sql = qs.to_sql();
2463
2464 assert!(
2465 sql.contains("SUM") || sql.contains("status"),
2466 "SQL should contain 'SUM' or 'status'. Got: {}",
2467 sql
2468 );
2469 }
2470
2471 #[test]
2472 fn test_raw_sql_with_inherited_field() {
2474 use crate::orm::expressions::F;
2476
2477 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2478 "raw_field",
2479 AnnotationValue::Field(F::new("inherited_field")),
2480 ));
2481
2482 let sql = qs.to_sql();
2483
2484 assert!(
2485 sql.contains("inherited_field") || sql.contains("raw_field"),
2486 "SQL should contain 'inherited_field' or 'raw_field'. Got: {}",
2487 sql
2488 );
2489 }
2490
2491 #[test]
2492 fn test_raw_sql_with_inherited_field_1() {
2494 use crate::orm::expressions::F;
2496
2497 let qs = QuerySet::<TestModel>::new().annotate(Annotation::field(
2498 "parent_field",
2499 AnnotationValue::Field(F::new("base_field")),
2500 ));
2501
2502 let sql = qs.to_sql();
2503
2504 assert!(
2505 sql.contains("base_field") || sql.contains("parent_field"),
2506 "SQL should contain 'base_field' or 'parent_field'. Got: {}",
2507 sql
2508 );
2509 }
2510}