1use serde::{Deserialize, Serialize};
28use std::marker::PhantomData;
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ArrayAgg<T> {
47 field: String,
48 distinct: bool,
49 ordering: Option<Vec<String>>,
50 _phantom: PhantomData<T>,
51}
52
53impl<T> ArrayAgg<T> {
54 pub fn new(field: String) -> Self {
65 Self {
66 field,
67 distinct: false,
68 ordering: None,
69 _phantom: PhantomData,
70 }
71 }
72
73 pub fn distinct(mut self) -> Self {
84 self.distinct = true;
85 self
86 }
87
88 pub fn order_by(mut self, fields: Vec<String>) -> Self {
100 self.ordering = Some(fields);
101 self
102 }
103
104 pub fn to_sql(&self) -> String {
106 let mut sql = String::from("ARRAY_AGG(");
107
108 if self.distinct {
109 sql.push_str("DISTINCT ");
110 }
111
112 sql.push_str(&self.field);
113
114 if let Some(ref ordering) = self.ordering {
115 sql.push_str(" ORDER BY ");
116 sql.push_str(&ordering.join(", "));
117 }
118
119 sql.push(')');
120 sql
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct JsonbBuildObject {
140 pairs: Vec<(String, String)>,
141}
142
143impl JsonbBuildObject {
144 pub fn new() -> Self {
155 Self { pairs: Vec::new() }
156 }
157
158 pub fn add(mut self, key: &str, value_field: &str) -> Self {
173 self.pairs.push((key.to_string(), value_field.to_string()));
174 self
175 }
176
177 pub fn to_sql(&self) -> String {
179 let mut sql = String::from("jsonb_build_object(");
180
181 let parts: Vec<String> = self
182 .pairs
183 .iter()
184 .flat_map(|(k, v)| vec![format!("'{}'", k), v.clone()])
185 .collect();
186
187 sql.push_str(&parts.join(", "));
188 sql.push(')');
189 sql
190 }
191}
192
193impl Default for JsonbBuildObject {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct FullTextSearch {
214 vector_field: String,
215 query: String,
216 config: String,
217}
218
219impl FullTextSearch {
220 pub fn new(field: String, query: String) -> Self {
231 Self {
232 vector_field: field,
233 query,
234 config: "english".to_string(),
235 }
236 }
237
238 pub fn with_config(mut self, config: String) -> Self {
250 self.config = config;
251 self
252 }
253
254 pub fn config(&self) -> &str {
256 &self.config
257 }
258
259 pub fn to_sql(&self) -> String {
272 format!(
273 "to_tsvector('{}', {}) @@ to_tsquery('{}', '{}')",
274 self.config, self.vector_field, self.config, self.query
275 )
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct StringAgg {
296 field: String,
297 separator: String,
298 distinct: bool,
299 ordering: Option<Vec<String>>,
300}
301
302impl StringAgg {
303 pub fn new(field: String, separator: String) -> Self {
314 Self {
315 field,
316 separator,
317 distinct: false,
318 ordering: None,
319 }
320 }
321
322 pub fn distinct(mut self) -> Self {
333 self.distinct = true;
334 self
335 }
336
337 pub fn order_by(mut self, fields: Vec<String>) -> Self {
349 self.ordering = Some(fields);
350 self
351 }
352
353 pub fn to_sql(&self) -> String {
355 let mut sql = String::from("STRING_AGG(");
356
357 if self.distinct {
358 sql.push_str("DISTINCT ");
359 }
360
361 sql.push_str(&self.field);
362 sql.push_str(", '");
363 sql.push_str(&self.separator);
364 sql.push('\'');
365
366 if let Some(ref ordering) = self.ordering {
367 sql.push_str(" ORDER BY ");
368 sql.push_str(&ordering.join(", "));
369 }
370
371 sql.push(')');
372 sql
373 }
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct JsonbAgg {
393 expression: String,
394 distinct: bool,
395 ordering: Option<Vec<String>>,
396}
397
398impl JsonbAgg {
399 pub fn new(expression: String) -> Self {
410 Self {
411 expression,
412 distinct: false,
413 ordering: None,
414 }
415 }
416
417 pub fn distinct(mut self) -> Self {
428 self.distinct = true;
429 self
430 }
431
432 pub fn order_by(mut self, fields: Vec<String>) -> Self {
444 self.ordering = Some(fields);
445 self
446 }
447
448 pub fn to_sql(&self) -> String {
450 let mut sql = String::from("JSONB_AGG(");
451
452 if self.distinct {
453 sql.push_str("DISTINCT ");
454 }
455
456 sql.push_str(&self.expression);
457
458 if let Some(ref ordering) = self.ordering {
459 sql.push_str(" ORDER BY ");
460 sql.push_str(&ordering.join(", "));
461 }
462
463 sql.push(')');
464 sql
465 }
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct TsRank {
483 vector_field: String,
484 query: String,
485 config: String,
486 normalization: Option<i32>,
487}
488
489impl TsRank {
490 pub fn new(vector_field: String, query: String) -> Self {
502 Self {
503 vector_field,
504 query,
505 config: "english".to_string(),
506 normalization: None,
507 }
508 }
509
510 pub fn with_config(mut self, config: String) -> Self {
523 self.config = config;
524 self
525 }
526
527 pub fn with_normalization(mut self, norm: i32) -> Self {
551 self.normalization = Some(norm);
552 self
553 }
554
555 pub fn config(&self) -> &str {
557 &self.config
558 }
559
560 pub fn to_sql(&self) -> String {
572 let tsquery = format!("to_tsquery('{}', '{}')", self.config, self.query);
573
574 match self.normalization {
575 Some(norm) => format!("ts_rank({}, {}, {})", self.vector_field, tsquery, norm),
576 None => format!("ts_rank({}, {})", self.vector_field, tsquery),
577 }
578 }
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct ArrayOverlap {
595 field: String,
596 values: Vec<String>,
597}
598
599impl ArrayOverlap {
600 pub fn new(field: String, values: Vec<String>) -> Self {
614 Self { field, values }
615 }
616
617 pub fn to_sql(&self) -> String {
619 let array_literal = format!(
620 "ARRAY[{}]",
621 self.values
622 .iter()
623 .map(|v| format!("'{}'", v))
624 .collect::<Vec<_>>()
625 .join(", ")
626 );
627 format!("{} && {}", self.field, array_literal)
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn test_array_agg_basic() {
637 let agg = ArrayAgg::<i32>::new("score".to_string());
638 assert_eq!(agg.to_sql(), "ARRAY_AGG(score)");
639 }
640
641 #[test]
642 fn test_array_agg_distinct() {
643 let agg = ArrayAgg::<String>::new("category".to_string()).distinct();
644 assert_eq!(agg.to_sql(), "ARRAY_AGG(DISTINCT category)");
645 }
646
647 #[test]
648 fn test_array_agg_with_ordering() {
649 let agg =
650 ArrayAgg::<i32>::new("id".to_string()).order_by(vec!["created_at DESC".to_string()]);
651 assert_eq!(agg.to_sql(), "ARRAY_AGG(id ORDER BY created_at DESC)");
652 }
653
654 #[test]
655 fn test_array_agg_distinct_with_ordering() {
656 let agg = ArrayAgg::<String>::new("name".to_string())
657 .distinct()
658 .order_by(vec!["name ASC".to_string(), "id DESC".to_string()]);
659 assert_eq!(
660 agg.to_sql(),
661 "ARRAY_AGG(DISTINCT name ORDER BY name ASC, id DESC)"
662 );
663 }
664
665 #[test]
666 fn test_jsonb_build_object_empty() {
667 let builder = JsonbBuildObject::new();
668 assert_eq!(builder.to_sql(), "jsonb_build_object()");
669 }
670
671 #[test]
672 fn test_jsonb_build_object_single_pair() {
673 let builder = JsonbBuildObject::new().add("id", "user_id");
674 assert_eq!(builder.to_sql(), "jsonb_build_object('id', user_id)");
675 }
676
677 #[test]
678 fn test_jsonb_build_object_multiple_pairs() {
679 let builder = JsonbBuildObject::new()
680 .add("id", "user_id")
681 .add("name", "user_name")
682 .add("email", "user_email");
683 assert_eq!(
684 builder.to_sql(),
685 "jsonb_build_object('id', user_id, 'name', user_name, 'email', user_email)"
686 );
687 }
688
689 #[test]
690 fn test_full_text_search_basic() {
691 let search = FullTextSearch::new("content".to_string(), "rust".to_string());
692 assert_eq!(
693 search.to_sql(),
694 "to_tsvector('english', content) @@ to_tsquery('english', 'rust')"
695 );
696 }
697
698 #[test]
699 fn test_full_text_search_custom_config() {
700 let search = FullTextSearch::new("title".to_string(), "database".to_string())
701 .with_config("french".to_string());
702 assert_eq!(
703 search.to_sql(),
704 "to_tsvector('french', title) @@ to_tsquery('french', 'database')"
705 );
706 }
707
708 #[test]
709 fn test_full_text_search_complex_query() {
710 let search = FullTextSearch::new("body".to_string(), "rust & programming".to_string());
711 let sql = search.to_sql();
712 assert!(sql.contains("to_tsvector('english', body)"));
713 assert!(sql.contains("to_tsquery('english', 'rust & programming')"));
714 }
715
716 #[test]
717 fn test_array_overlap_basic() {
718 let overlap = ArrayOverlap::new(
719 "tags".to_string(),
720 vec!["rust".to_string(), "web".to_string()],
721 );
722 assert_eq!(overlap.to_sql(), "tags && ARRAY['rust', 'web']");
723 }
724
725 #[test]
726 fn test_array_overlap_single_value() {
727 let overlap = ArrayOverlap::new("categories".to_string(), vec!["tech".to_string()]);
728 assert_eq!(overlap.to_sql(), "categories && ARRAY['tech']");
729 }
730
731 #[test]
732 fn test_array_overlap_multiple_values() {
733 let overlap = ArrayOverlap::new(
734 "labels".to_string(),
735 vec![
736 "important".to_string(),
737 "urgent".to_string(),
738 "reviewed".to_string(),
739 ],
740 );
741 assert_eq!(
742 overlap.to_sql(),
743 "labels && ARRAY['important', 'urgent', 'reviewed']"
744 );
745 }
746
747 #[test]
748 fn test_array_agg_type_safety() {
749 let int_agg = ArrayAgg::<i32>::new("scores".to_string());
750 let string_agg = ArrayAgg::<String>::new("names".to_string());
751
752 assert_eq!(int_agg.to_sql(), "ARRAY_AGG(scores)");
753 assert_eq!(string_agg.to_sql(), "ARRAY_AGG(names)");
754 }
755
756 #[test]
757 fn test_jsonb_build_object_default() {
758 let builder = JsonbBuildObject::default();
759 assert_eq!(builder.to_sql(), "jsonb_build_object()");
760 }
761
762 #[test]
763 fn test_full_text_search_config_getter() {
764 let search = FullTextSearch::new("text".to_string(), "query".to_string());
765 assert_eq!(search.config(), "english");
766
767 let search_fr = search.with_config("french".to_string());
768 assert_eq!(search_fr.config(), "french");
769 }
770
771 #[test]
773 fn test_string_agg_basic() {
774 let agg = StringAgg::new("name".to_string(), ", ".to_string());
775 assert_eq!(agg.to_sql(), "STRING_AGG(name, ', ')");
776 }
777
778 #[test]
779 fn test_string_agg_distinct() {
780 let agg = StringAgg::new("category".to_string(), "; ".to_string()).distinct();
781 assert_eq!(agg.to_sql(), "STRING_AGG(DISTINCT category, '; ')");
782 }
783
784 #[test]
785 fn test_string_agg_with_ordering() {
786 let agg = StringAgg::new("name".to_string(), ", ".to_string())
787 .order_by(vec!["name ASC".to_string()]);
788 assert_eq!(agg.to_sql(), "STRING_AGG(name, ', ' ORDER BY name ASC)");
789 }
790
791 #[test]
792 fn test_string_agg_distinct_with_ordering() {
793 let agg = StringAgg::new("name".to_string(), ",".to_string())
794 .distinct()
795 .order_by(vec!["created_at DESC".to_string()]);
796 assert_eq!(
797 agg.to_sql(),
798 "STRING_AGG(DISTINCT name, ',' ORDER BY created_at DESC)"
799 );
800 }
801
802 #[test]
804 fn test_jsonb_agg_basic() {
805 let agg = JsonbAgg::new("user_data".to_string());
806 assert_eq!(agg.to_sql(), "JSONB_AGG(user_data)");
807 }
808
809 #[test]
810 fn test_jsonb_agg_distinct() {
811 let agg = JsonbAgg::new("category".to_string()).distinct();
812 assert_eq!(agg.to_sql(), "JSONB_AGG(DISTINCT category)");
813 }
814
815 #[test]
816 fn test_jsonb_agg_with_ordering() {
817 let agg = JsonbAgg::new("items".to_string()).order_by(vec!["created_at DESC".to_string()]);
818 assert_eq!(agg.to_sql(), "JSONB_AGG(items ORDER BY created_at DESC)");
819 }
820
821 #[test]
822 fn test_jsonb_agg_distinct_with_ordering() {
823 let agg = JsonbAgg::new("data".to_string())
824 .distinct()
825 .order_by(vec!["id ASC".to_string(), "name DESC".to_string()]);
826 assert_eq!(
827 agg.to_sql(),
828 "JSONB_AGG(DISTINCT data ORDER BY id ASC, name DESC)"
829 );
830 }
831
832 #[test]
834 fn test_ts_rank_basic() {
835 let rank = TsRank::new("search_vector".to_string(), "rust".to_string());
836 assert_eq!(
837 rank.to_sql(),
838 "ts_rank(search_vector, to_tsquery('english', 'rust'))"
839 );
840 }
841
842 #[test]
843 fn test_ts_rank_with_config() {
844 let rank = TsRank::new("content".to_string(), "bonjour".to_string())
845 .with_config("french".to_string());
846 assert_eq!(
847 rank.to_sql(),
848 "ts_rank(content, to_tsquery('french', 'bonjour'))"
849 );
850 }
851
852 #[test]
853 fn test_ts_rank_with_normalization() {
854 let rank = TsRank::new("content".to_string(), "rust".to_string()).with_normalization(2);
855 assert_eq!(
856 rank.to_sql(),
857 "ts_rank(content, to_tsquery('english', 'rust'), 2)"
858 );
859 }
860
861 #[test]
862 fn test_ts_rank_with_config_and_normalization() {
863 let rank = TsRank::new("text_vector".to_string(), "database".to_string())
864 .with_config("simple".to_string())
865 .with_normalization(4);
866 assert_eq!(
867 rank.to_sql(),
868 "ts_rank(text_vector, to_tsquery('simple', 'database'), 4)"
869 );
870 }
871
872 #[test]
873 fn test_ts_rank_config_getter() {
874 let rank = TsRank::new("content".to_string(), "query".to_string());
875 assert_eq!(rank.config(), "english");
876
877 let rank_fr = rank.with_config("french".to_string());
878 assert_eq!(rank_fr.config(), "french");
879 }
880}