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 pub fn map_fields(&mut self, mut map: impl FnMut(&mut String)) {
125 map(&mut self.field);
126 if let Some(ordering) = &mut self.ordering {
127 for field in ordering {
128 map(field);
129 }
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct JsonbBuildObject {
150 pairs: Vec<(String, String)>,
151}
152
153impl JsonbBuildObject {
154 pub fn new() -> Self {
165 Self { pairs: Vec::new() }
166 }
167
168 pub fn add(mut self, key: &str, value_field: &str) -> Self {
183 self.pairs.push((key.to_string(), value_field.to_string()));
184 self
185 }
186
187 pub fn to_sql(&self) -> String {
189 let mut sql = String::from("jsonb_build_object(");
190
191 let parts: Vec<String> = self
192 .pairs
193 .iter()
194 .flat_map(|(k, v)| vec![format!("'{}'", k), v.clone()])
195 .collect();
196
197 sql.push_str(&parts.join(", "));
198 sql.push(')');
199 sql
200 }
201
202 pub fn map_fields(&mut self, mut map: impl FnMut(&mut String)) {
204 for (_, field) in &mut self.pairs {
205 map(field);
206 }
207 }
208}
209
210impl Default for JsonbBuildObject {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct FullTextSearch {
231 vector_field: String,
232 query: String,
233 config: String,
234}
235
236impl FullTextSearch {
237 pub fn new(field: String, query: String) -> Self {
248 Self {
249 vector_field: field,
250 query,
251 config: "english".to_string(),
252 }
253 }
254
255 pub fn with_config(mut self, config: String) -> Self {
267 self.config = config;
268 self
269 }
270
271 pub fn config(&self) -> &str {
273 &self.config
274 }
275
276 pub fn to_sql(&self) -> String {
289 format!(
290 "to_tsvector('{}', {}) @@ to_tsquery('{}', '{}')",
291 self.config, self.vector_field, self.config, self.query
292 )
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct StringAgg {
313 field: String,
314 separator: String,
315 distinct: bool,
316 ordering: Option<Vec<String>>,
317}
318
319impl StringAgg {
320 pub fn new(field: String, separator: String) -> Self {
331 Self {
332 field,
333 separator,
334 distinct: false,
335 ordering: None,
336 }
337 }
338
339 pub fn distinct(mut self) -> Self {
350 self.distinct = true;
351 self
352 }
353
354 pub fn order_by(mut self, fields: Vec<String>) -> Self {
366 self.ordering = Some(fields);
367 self
368 }
369
370 pub fn to_sql(&self) -> String {
372 let mut sql = String::from("STRING_AGG(");
373
374 if self.distinct {
375 sql.push_str("DISTINCT ");
376 }
377
378 sql.push_str(&self.field);
379 sql.push_str(", '");
380 sql.push_str(&self.separator);
381 sql.push('\'');
382
383 if let Some(ref ordering) = self.ordering {
384 sql.push_str(" ORDER BY ");
385 sql.push_str(&ordering.join(", "));
386 }
387
388 sql.push(')');
389 sql
390 }
391
392 pub fn map_fields(&mut self, mut map: impl FnMut(&mut String)) {
394 map(&mut self.field);
395 if let Some(ordering) = &mut self.ordering {
396 for field in ordering {
397 map(field);
398 }
399 }
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct JsonbAgg {
420 expression: String,
421 distinct: bool,
422 ordering: Option<Vec<String>>,
423}
424
425impl JsonbAgg {
426 pub fn new(expression: String) -> Self {
437 Self {
438 expression,
439 distinct: false,
440 ordering: None,
441 }
442 }
443
444 pub fn distinct(mut self) -> Self {
455 self.distinct = true;
456 self
457 }
458
459 pub fn order_by(mut self, fields: Vec<String>) -> Self {
471 self.ordering = Some(fields);
472 self
473 }
474
475 pub fn to_sql(&self) -> String {
477 let mut sql = String::from("JSONB_AGG(");
478
479 if self.distinct {
480 sql.push_str("DISTINCT ");
481 }
482
483 sql.push_str(&self.expression);
484
485 if let Some(ref ordering) = self.ordering {
486 sql.push_str(" ORDER BY ");
487 sql.push_str(&ordering.join(", "));
488 }
489
490 sql.push(')');
491 sql
492 }
493
494 pub fn map_fields(&mut self, mut map: impl FnMut(&mut String)) {
496 map(&mut self.expression);
497 if let Some(ordering) = &mut self.ordering {
498 for field in ordering {
499 map(field);
500 }
501 }
502 }
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
519pub struct TsRank {
520 vector_field: String,
521 query: String,
522 config: String,
523 normalization: Option<i32>,
524}
525
526impl TsRank {
527 pub fn new(vector_field: String, query: String) -> Self {
539 Self {
540 vector_field,
541 query,
542 config: "english".to_string(),
543 normalization: None,
544 }
545 }
546
547 pub fn with_config(mut self, config: String) -> Self {
560 self.config = config;
561 self
562 }
563
564 pub fn with_normalization(mut self, norm: i32) -> Self {
588 self.normalization = Some(norm);
589 self
590 }
591
592 pub fn config(&self) -> &str {
594 &self.config
595 }
596
597 pub fn to_sql(&self) -> String {
609 let tsquery = format!("to_tsquery('{}', '{}')", self.config, self.query);
610
611 match self.normalization {
612 Some(norm) => format!("ts_rank({}, {}, {})", self.vector_field, tsquery, norm),
613 None => format!("ts_rank({}, {})", self.vector_field, tsquery),
614 }
615 }
616
617 pub fn map_fields(&mut self, mut map: impl FnMut(&mut String)) {
619 map(&mut self.vector_field);
620 }
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct ArrayOverlap {
637 field: String,
638 values: Vec<String>,
639}
640
641impl ArrayOverlap {
642 pub fn new(field: String, values: Vec<String>) -> Self {
656 Self { field, values }
657 }
658
659 pub fn to_sql(&self) -> String {
661 let array_literal = format!(
662 "ARRAY[{}]",
663 self.values
664 .iter()
665 .map(|v| format!("'{}'", v))
666 .collect::<Vec<_>>()
667 .join(", ")
668 );
669 format!("{} && {}", self.field, array_literal)
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676
677 #[test]
678 fn test_array_agg_basic() {
679 let agg = ArrayAgg::<i32>::new("score".to_string());
680 assert_eq!(agg.to_sql(), "ARRAY_AGG(score)");
681 }
682
683 #[test]
684 fn test_array_agg_distinct() {
685 let agg = ArrayAgg::<String>::new("category".to_string()).distinct();
686 assert_eq!(agg.to_sql(), "ARRAY_AGG(DISTINCT category)");
687 }
688
689 #[test]
690 fn test_array_agg_with_ordering() {
691 let agg =
692 ArrayAgg::<i32>::new("id".to_string()).order_by(vec!["created_at DESC".to_string()]);
693 assert_eq!(agg.to_sql(), "ARRAY_AGG(id ORDER BY created_at DESC)");
694 }
695
696 #[test]
697 fn test_array_agg_distinct_with_ordering() {
698 let agg = ArrayAgg::<String>::new("name".to_string())
699 .distinct()
700 .order_by(vec!["name ASC".to_string(), "id DESC".to_string()]);
701 assert_eq!(
702 agg.to_sql(),
703 "ARRAY_AGG(DISTINCT name ORDER BY name ASC, id DESC)"
704 );
705 }
706
707 #[test]
708 fn test_jsonb_build_object_empty() {
709 let builder = JsonbBuildObject::new();
710 assert_eq!(builder.to_sql(), "jsonb_build_object()");
711 }
712
713 #[test]
714 fn test_jsonb_build_object_single_pair() {
715 let builder = JsonbBuildObject::new().add("id", "user_id");
716 assert_eq!(builder.to_sql(), "jsonb_build_object('id', user_id)");
717 }
718
719 #[test]
720 fn test_jsonb_build_object_multiple_pairs() {
721 let builder = JsonbBuildObject::new()
722 .add("id", "user_id")
723 .add("name", "user_name")
724 .add("email", "user_email");
725 assert_eq!(
726 builder.to_sql(),
727 "jsonb_build_object('id', user_id, 'name', user_name, 'email', user_email)"
728 );
729 }
730
731 #[test]
732 fn test_full_text_search_basic() {
733 let search = FullTextSearch::new("content".to_string(), "rust".to_string());
734 assert_eq!(
735 search.to_sql(),
736 "to_tsvector('english', content) @@ to_tsquery('english', 'rust')"
737 );
738 }
739
740 #[test]
741 fn test_full_text_search_custom_config() {
742 let search = FullTextSearch::new("title".to_string(), "database".to_string())
743 .with_config("french".to_string());
744 assert_eq!(
745 search.to_sql(),
746 "to_tsvector('french', title) @@ to_tsquery('french', 'database')"
747 );
748 }
749
750 #[test]
751 fn test_full_text_search_complex_query() {
752 let search = FullTextSearch::new("body".to_string(), "rust & programming".to_string());
753 let sql = search.to_sql();
754 assert!(sql.contains("to_tsvector('english', body)"));
755 assert!(sql.contains("to_tsquery('english', 'rust & programming')"));
756 }
757
758 #[test]
759 fn test_array_overlap_basic() {
760 let overlap = ArrayOverlap::new(
761 "tags".to_string(),
762 vec!["rust".to_string(), "web".to_string()],
763 );
764 assert_eq!(overlap.to_sql(), "tags && ARRAY['rust', 'web']");
765 }
766
767 #[test]
768 fn test_array_overlap_single_value() {
769 let overlap = ArrayOverlap::new("categories".to_string(), vec!["tech".to_string()]);
770 assert_eq!(overlap.to_sql(), "categories && ARRAY['tech']");
771 }
772
773 #[test]
774 fn test_array_overlap_multiple_values() {
775 let overlap = ArrayOverlap::new(
776 "labels".to_string(),
777 vec![
778 "important".to_string(),
779 "urgent".to_string(),
780 "reviewed".to_string(),
781 ],
782 );
783 assert_eq!(
784 overlap.to_sql(),
785 "labels && ARRAY['important', 'urgent', 'reviewed']"
786 );
787 }
788
789 #[test]
790 fn test_array_agg_type_safety() {
791 let int_agg = ArrayAgg::<i32>::new("scores".to_string());
792 let string_agg = ArrayAgg::<String>::new("names".to_string());
793
794 assert_eq!(int_agg.to_sql(), "ARRAY_AGG(scores)");
795 assert_eq!(string_agg.to_sql(), "ARRAY_AGG(names)");
796 }
797
798 #[test]
799 fn test_jsonb_build_object_default() {
800 let builder = JsonbBuildObject::default();
801 assert_eq!(builder.to_sql(), "jsonb_build_object()");
802 }
803
804 #[test]
805 fn test_full_text_search_config_getter() {
806 let search = FullTextSearch::new("text".to_string(), "query".to_string());
807 assert_eq!(search.config(), "english");
808
809 let search_fr = search.with_config("french".to_string());
810 assert_eq!(search_fr.config(), "french");
811 }
812
813 #[test]
815 fn test_string_agg_basic() {
816 let agg = StringAgg::new("name".to_string(), ", ".to_string());
817 assert_eq!(agg.to_sql(), "STRING_AGG(name, ', ')");
818 }
819
820 #[test]
821 fn test_string_agg_distinct() {
822 let agg = StringAgg::new("category".to_string(), "; ".to_string()).distinct();
823 assert_eq!(agg.to_sql(), "STRING_AGG(DISTINCT category, '; ')");
824 }
825
826 #[test]
827 fn test_string_agg_with_ordering() {
828 let agg = StringAgg::new("name".to_string(), ", ".to_string())
829 .order_by(vec!["name ASC".to_string()]);
830 assert_eq!(agg.to_sql(), "STRING_AGG(name, ', ' ORDER BY name ASC)");
831 }
832
833 #[test]
834 fn test_string_agg_distinct_with_ordering() {
835 let agg = StringAgg::new("name".to_string(), ",".to_string())
836 .distinct()
837 .order_by(vec!["created_at DESC".to_string()]);
838 assert_eq!(
839 agg.to_sql(),
840 "STRING_AGG(DISTINCT name, ',' ORDER BY created_at DESC)"
841 );
842 }
843
844 #[test]
846 fn test_jsonb_agg_basic() {
847 let agg = JsonbAgg::new("user_data".to_string());
848 assert_eq!(agg.to_sql(), "JSONB_AGG(user_data)");
849 }
850
851 #[test]
852 fn test_jsonb_agg_distinct() {
853 let agg = JsonbAgg::new("category".to_string()).distinct();
854 assert_eq!(agg.to_sql(), "JSONB_AGG(DISTINCT category)");
855 }
856
857 #[test]
858 fn test_jsonb_agg_with_ordering() {
859 let agg = JsonbAgg::new("items".to_string()).order_by(vec!["created_at DESC".to_string()]);
860 assert_eq!(agg.to_sql(), "JSONB_AGG(items ORDER BY created_at DESC)");
861 }
862
863 #[test]
864 fn test_jsonb_agg_distinct_with_ordering() {
865 let agg = JsonbAgg::new("data".to_string())
866 .distinct()
867 .order_by(vec!["id ASC".to_string(), "name DESC".to_string()]);
868 assert_eq!(
869 agg.to_sql(),
870 "JSONB_AGG(DISTINCT data ORDER BY id ASC, name DESC)"
871 );
872 }
873
874 #[test]
876 fn test_ts_rank_basic() {
877 let rank = TsRank::new("search_vector".to_string(), "rust".to_string());
878 assert_eq!(
879 rank.to_sql(),
880 "ts_rank(search_vector, to_tsquery('english', 'rust'))"
881 );
882 }
883
884 #[test]
885 fn test_ts_rank_with_config() {
886 let rank = TsRank::new("content".to_string(), "bonjour".to_string())
887 .with_config("french".to_string());
888 assert_eq!(
889 rank.to_sql(),
890 "ts_rank(content, to_tsquery('french', 'bonjour'))"
891 );
892 }
893
894 #[test]
895 fn test_ts_rank_with_normalization() {
896 let rank = TsRank::new("content".to_string(), "rust".to_string()).with_normalization(2);
897 assert_eq!(
898 rank.to_sql(),
899 "ts_rank(content, to_tsquery('english', 'rust'), 2)"
900 );
901 }
902
903 #[test]
904 fn test_ts_rank_with_config_and_normalization() {
905 let rank = TsRank::new("text_vector".to_string(), "database".to_string())
906 .with_config("simple".to_string())
907 .with_normalization(4);
908 assert_eq!(
909 rank.to_sql(),
910 "ts_rank(text_vector, to_tsquery('simple', 'database'), 4)"
911 );
912 }
913
914 #[test]
915 fn test_ts_rank_config_getter() {
916 let rank = TsRank::new("content".to_string(), "query".to_string());
917 assert_eq!(rank.config(), "english");
918
919 let rank_fr = rank.with_config("french".to_string());
920 assert_eq!(rank_fr.config(), "french");
921 }
922}