1use crate::sql::{
2 ArrayRangeOperator, FilterCondition, FilterOperator, IntoSqlParam, IsValue, ParamStore,
3 PatternOperator, TextSearchType, validate_column_name,
4};
5pub trait Filterable: Sized {
9 fn filters_mut(&mut self) -> &mut Vec<FilterCondition>;
11 fn params_mut(&mut self) -> &mut ParamStore;
13
14 fn eq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
16 if let Err(e) = validate_column_name(column) {
17 tracing::error!("Invalid column name in eq filter: {e}");
18 return self;
19 }
20 let idx = self.params_mut().push_value(value);
21 self.filters_mut().push(FilterCondition::Comparison {
22 column: column.to_string(),
23 operator: FilterOperator::Eq,
24 param_index: idx,
25 });
26 self
27 }
28
29 fn neq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
31 if let Err(e) = validate_column_name(column) {
32 tracing::error!("Invalid column name in neq filter: {e}");
33 return self;
34 }
35 let idx = self.params_mut().push_value(value);
36 self.filters_mut().push(FilterCondition::Comparison {
37 column: column.to_string(),
38 operator: FilterOperator::Neq,
39 param_index: idx,
40 });
41 self
42 }
43
44 fn gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
46 if let Err(e) = validate_column_name(column) {
47 tracing::error!("Invalid column name in gt filter: {e}");
48 return self;
49 }
50 let idx = self.params_mut().push_value(value);
51 self.filters_mut().push(FilterCondition::Comparison {
52 column: column.to_string(),
53 operator: FilterOperator::Gt,
54 param_index: idx,
55 });
56 self
57 }
58
59 fn gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
61 if let Err(e) = validate_column_name(column) {
62 tracing::error!("Invalid column name in gte filter: {e}");
63 return self;
64 }
65 let idx = self.params_mut().push_value(value);
66 self.filters_mut().push(FilterCondition::Comparison {
67 column: column.to_string(),
68 operator: FilterOperator::Gte,
69 param_index: idx,
70 });
71 self
72 }
73
74 fn lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
76 if let Err(e) = validate_column_name(column) {
77 tracing::error!("Invalid column name in lt filter: {e}");
78 return self;
79 }
80 let idx = self.params_mut().push_value(value);
81 self.filters_mut().push(FilterCondition::Comparison {
82 column: column.to_string(),
83 operator: FilterOperator::Lt,
84 param_index: idx,
85 });
86 self
87 }
88
89 fn lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
91 if let Err(e) = validate_column_name(column) {
92 tracing::error!("Invalid column name in lte filter: {e}");
93 return self;
94 }
95 let idx = self.params_mut().push_value(value);
96 self.filters_mut().push(FilterCondition::Comparison {
97 column: column.to_string(),
98 operator: FilterOperator::Lte,
99 param_index: idx,
100 });
101 self
102 }
103
104 fn like(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
106 if let Err(e) = validate_column_name(column) {
107 tracing::error!("Invalid column name in like filter: {e}");
108 return self;
109 }
110 let idx = self.params_mut().push_value(pattern);
111 self.filters_mut().push(FilterCondition::Pattern {
112 column: column.to_string(),
113 operator: PatternOperator::Like,
114 param_index: idx,
115 });
116 self
117 }
118
119 fn ilike(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
121 if let Err(e) = validate_column_name(column) {
122 tracing::error!("Invalid column name in ilike filter: {e}");
123 return self;
124 }
125 let idx = self.params_mut().push_value(pattern);
126 self.filters_mut().push(FilterCondition::Pattern {
127 column: column.to_string(),
128 operator: PatternOperator::ILike,
129 param_index: idx,
130 });
131 self
132 }
133
134 fn is(mut self, column: &str, value: IsValue) -> Self {
136 if let Err(e) = validate_column_name(column) {
137 tracing::error!("Invalid column name in is filter: {e}");
138 return self;
139 }
140 self.filters_mut().push(FilterCondition::Is {
141 column: column.to_string(),
142 value,
143 });
144 self
145 }
146
147 fn in_<V: IntoSqlParam>(mut self, column: &str, values: Vec<V>) -> Self {
149 if let Err(e) = validate_column_name(column) {
150 tracing::error!("Invalid column name in in_ filter: {e}");
151 return self;
152 }
153 let indices: Vec<usize> = values
154 .into_iter()
155 .map(|v| self.params_mut().push_value(v))
156 .collect();
157 self.filters_mut().push(FilterCondition::In {
158 column: column.to_string(),
159 param_indices: indices,
160 });
161 self
162 }
163
164 fn contains(mut self, column: &str, value: impl IntoSqlParam) -> Self {
166 if let Err(e) = validate_column_name(column) {
167 tracing::error!("Invalid column name in contains filter: {e}");
168 return self;
169 }
170 let idx = self.params_mut().push_value(value);
171 self.filters_mut().push(FilterCondition::ArrayRange {
172 column: column.to_string(),
173 operator: ArrayRangeOperator::Contains,
174 param_index: idx,
175 });
176 self
177 }
178
179 fn contained_by(mut self, column: &str, value: impl IntoSqlParam) -> Self {
181 if let Err(e) = validate_column_name(column) {
182 tracing::error!("Invalid column name in contained_by filter: {e}");
183 return self;
184 }
185 let idx = self.params_mut().push_value(value);
186 self.filters_mut().push(FilterCondition::ArrayRange {
187 column: column.to_string(),
188 operator: ArrayRangeOperator::ContainedBy,
189 param_index: idx,
190 });
191 self
192 }
193
194 fn overlaps(mut self, column: &str, value: impl IntoSqlParam) -> Self {
196 if let Err(e) = validate_column_name(column) {
197 tracing::error!("Invalid column name in overlaps filter: {e}");
198 return self;
199 }
200 let idx = self.params_mut().push_value(value);
201 self.filters_mut().push(FilterCondition::ArrayRange {
202 column: column.to_string(),
203 operator: ArrayRangeOperator::Overlaps,
204 param_index: idx,
205 });
206 self
207 }
208
209 fn range_gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
211 if let Err(e) = validate_column_name(column) {
212 tracing::error!("Invalid column name in range_gt filter: {e}");
213 return self;
214 }
215 let idx = self.params_mut().push_value(value);
216 self.filters_mut().push(FilterCondition::ArrayRange {
217 column: column.to_string(),
218 operator: ArrayRangeOperator::RangeGt,
219 param_index: idx,
220 });
221 self
222 }
223
224 fn range_gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
226 if let Err(e) = validate_column_name(column) {
227 tracing::error!("Invalid column name in range_gte filter: {e}");
228 return self;
229 }
230 let idx = self.params_mut().push_value(value);
231 self.filters_mut().push(FilterCondition::ArrayRange {
232 column: column.to_string(),
233 operator: ArrayRangeOperator::RangeGte,
234 param_index: idx,
235 });
236 self
237 }
238
239 fn range_lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
241 if let Err(e) = validate_column_name(column) {
242 tracing::error!("Invalid column name in range_lt filter: {e}");
243 return self;
244 }
245 let idx = self.params_mut().push_value(value);
246 self.filters_mut().push(FilterCondition::ArrayRange {
247 column: column.to_string(),
248 operator: ArrayRangeOperator::RangeLt,
249 param_index: idx,
250 });
251 self
252 }
253
254 fn range_lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
256 if let Err(e) = validate_column_name(column) {
257 tracing::error!("Invalid column name in range_lte filter: {e}");
258 return self;
259 }
260 let idx = self.params_mut().push_value(value);
261 self.filters_mut().push(FilterCondition::ArrayRange {
262 column: column.to_string(),
263 operator: ArrayRangeOperator::RangeLte,
264 param_index: idx,
265 });
266 self
267 }
268
269 fn range_adjacent(mut self, column: &str, value: impl IntoSqlParam) -> Self {
271 if let Err(e) = validate_column_name(column) {
272 tracing::error!("Invalid column name in range_adjacent filter: {e}");
273 return self;
274 }
275 let idx = self.params_mut().push_value(value);
276 self.filters_mut().push(FilterCondition::ArrayRange {
277 column: column.to_string(),
278 operator: ArrayRangeOperator::RangeAdjacent,
279 param_index: idx,
280 });
281 self
282 }
283
284 fn text_search(
286 mut self,
287 column: &str,
288 query: impl IntoSqlParam,
289 search_type: TextSearchType,
290 config: Option<&str>,
291 ) -> Self {
292 if let Err(e) = validate_column_name(column) {
293 tracing::error!("Invalid column name in text_search filter: {e}");
294 return self;
295 }
296 let idx = self.params_mut().push_value(query);
297 self.filters_mut().push(FilterCondition::TextSearch {
298 column: column.to_string(),
299 query_param_index: idx,
300 config: config.map(|s| s.to_string()),
301 search_type,
302 });
303 self
304 }
305
306 fn not(mut self, f: impl FnOnce(FilterCollector) -> FilterCollector) -> Self {
308 let collector = f(FilterCollector::new(self.params_mut()));
309 if let Some(condition) = collector.into_single_condition() {
310 self.filters_mut().push(FilterCondition::Not(Box::new(condition)));
311 }
312 self
313 }
314
315 fn or_filter(mut self, f: impl FnOnce(FilterCollector) -> FilterCollector) -> Self {
317 let collector = f(FilterCollector::new(self.params_mut()));
318 let conditions = collector.into_conditions();
319 if !conditions.is_empty() {
320 self.filters_mut().push(FilterCondition::Or(conditions));
321 }
322 self
323 }
324
325 fn match_filter(mut self, pairs: Vec<(&str, impl IntoSqlParam + Clone)>) -> Self {
327 let conditions: Vec<(String, usize)> = pairs
328 .into_iter()
329 .filter_map(|(col, val)| {
330 if let Err(e) = validate_column_name(col) {
331 tracing::error!("Invalid column name in match_filter: {e}");
332 return None;
333 }
334 let idx = self.params_mut().push_value(val);
335 Some((col.to_string(), idx))
336 })
337 .collect();
338 if !conditions.is_empty() {
339 self.filters_mut().push(FilterCondition::Match { conditions });
340 }
341 self
342 }
343
344 fn filter(mut self, raw_sql: &str) -> Self {
346 self.filters_mut()
347 .push(FilterCondition::Raw(raw_sql.to_string()));
348 self
349 }
350}
351
352pub struct FilterCollector<'a> {
354 filters: Vec<FilterCondition>,
355 params: &'a mut ParamStore,
356}
357
358impl<'a> FilterCollector<'a> {
359 pub fn new(params: &'a mut ParamStore) -> Self {
360 Self {
361 filters: Vec::new(),
362 params,
363 }
364 }
365
366 pub fn eq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
367 if validate_column_name(column).is_ok() {
368 let idx = self.params.push_value(value);
369 self.filters.push(FilterCondition::Comparison {
370 column: column.to_string(),
371 operator: FilterOperator::Eq,
372 param_index: idx,
373 });
374 }
375 self
376 }
377
378 pub fn neq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
379 if validate_column_name(column).is_ok() {
380 let idx = self.params.push_value(value);
381 self.filters.push(FilterCondition::Comparison {
382 column: column.to_string(),
383 operator: FilterOperator::Neq,
384 param_index: idx,
385 });
386 }
387 self
388 }
389
390 pub fn gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
391 if validate_column_name(column).is_ok() {
392 let idx = self.params.push_value(value);
393 self.filters.push(FilterCondition::Comparison {
394 column: column.to_string(),
395 operator: FilterOperator::Gt,
396 param_index: idx,
397 });
398 }
399 self
400 }
401
402 pub fn gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
403 if validate_column_name(column).is_ok() {
404 let idx = self.params.push_value(value);
405 self.filters.push(FilterCondition::Comparison {
406 column: column.to_string(),
407 operator: FilterOperator::Gte,
408 param_index: idx,
409 });
410 }
411 self
412 }
413
414 pub fn lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
415 if validate_column_name(column).is_ok() {
416 let idx = self.params.push_value(value);
417 self.filters.push(FilterCondition::Comparison {
418 column: column.to_string(),
419 operator: FilterOperator::Lt,
420 param_index: idx,
421 });
422 }
423 self
424 }
425
426 pub fn lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
427 if validate_column_name(column).is_ok() {
428 let idx = self.params.push_value(value);
429 self.filters.push(FilterCondition::Comparison {
430 column: column.to_string(),
431 operator: FilterOperator::Lte,
432 param_index: idx,
433 });
434 }
435 self
436 }
437
438 pub fn like(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
439 if validate_column_name(column).is_ok() {
440 let idx = self.params.push_value(pattern);
441 self.filters.push(FilterCondition::Pattern {
442 column: column.to_string(),
443 operator: PatternOperator::Like,
444 param_index: idx,
445 });
446 }
447 self
448 }
449
450 pub fn ilike(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
451 if validate_column_name(column).is_ok() {
452 let idx = self.params.push_value(pattern);
453 self.filters.push(FilterCondition::Pattern {
454 column: column.to_string(),
455 operator: PatternOperator::ILike,
456 param_index: idx,
457 });
458 }
459 self
460 }
461
462 pub fn is(mut self, column: &str, value: IsValue) -> Self {
463 if validate_column_name(column).is_ok() {
464 self.filters.push(FilterCondition::Is {
465 column: column.to_string(),
466 value,
467 });
468 }
469 self
470 }
471
472 pub fn into_conditions(self) -> Vec<FilterCondition> {
473 self.filters
474 }
475
476 pub fn into_single_condition(self) -> Option<FilterCondition> {
477 let mut filters = self.filters;
478 if filters.len() == 1 {
479 Some(filters.remove(0))
480 } else if filters.is_empty() {
481 None
482 } else {
483 Some(FilterCondition::And(filters))
484 }
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use crate::backend::QueryBackend;
492 use crate::select::SelectBuilder;
493 use crate::sql::*;
494 use std::marker::PhantomData;
495 use std::sync::Arc;
496
497 fn make_select() -> SelectBuilder<supabase_client_core::Row> {
498 SelectBuilder {
499 backend: QueryBackend::Rest {
500 http: reqwest::Client::new(),
501 base_url: Arc::from("http://localhost"),
502 api_key: Arc::from("key"),
503 schema: "public".to_string(),
504 },
505 parts: SqlParts::new(SqlOperation::Select, "public", "test"),
506 params: ParamStore::new(),
507 _marker: PhantomData,
508 }
509 }
510
511 #[test]
512 fn test_eq_adds_comparison_filter() {
513 let builder = make_select().eq("name", "Alice");
514 assert_eq!(builder.parts.filters.len(), 1);
515 match &builder.parts.filters[0] {
516 FilterCondition::Comparison { column, operator, param_index } => {
517 assert_eq!(column, "name");
518 assert_eq!(*operator, FilterOperator::Eq);
519 assert_eq!(*param_index, 1);
520 }
521 _ => panic!("expected Comparison filter"),
522 }
523 }
524
525 #[test]
526 fn test_neq_adds_comparison_filter() {
527 let builder = make_select().neq("status", "inactive");
528 assert_eq!(builder.parts.filters.len(), 1);
529 match &builder.parts.filters[0] {
530 FilterCondition::Comparison { column, operator, .. } => {
531 assert_eq!(column, "status");
532 assert_eq!(*operator, FilterOperator::Neq);
533 }
534 _ => panic!("expected Comparison filter"),
535 }
536 }
537
538 #[test]
539 fn test_gt_adds_comparison_filter() {
540 let builder = make_select().gt("age", 18i32);
541 match &builder.parts.filters[0] {
542 FilterCondition::Comparison { column, operator, .. } => {
543 assert_eq!(column, "age");
544 assert_eq!(*operator, FilterOperator::Gt);
545 }
546 _ => panic!("expected Comparison filter"),
547 }
548 }
549
550 #[test]
551 fn test_gte_adds_comparison_filter() {
552 let builder = make_select().gte("score", 90i32);
553 match &builder.parts.filters[0] {
554 FilterCondition::Comparison { column, operator, .. } => {
555 assert_eq!(column, "score");
556 assert_eq!(*operator, FilterOperator::Gte);
557 }
558 _ => panic!("expected Comparison filter"),
559 }
560 }
561
562 #[test]
563 fn test_lt_adds_comparison_filter() {
564 let builder = make_select().lt("price", 100i32);
565 match &builder.parts.filters[0] {
566 FilterCondition::Comparison { column, operator, .. } => {
567 assert_eq!(column, "price");
568 assert_eq!(*operator, FilterOperator::Lt);
569 }
570 _ => panic!("expected Comparison filter"),
571 }
572 }
573
574 #[test]
575 fn test_lte_adds_comparison_filter() {
576 let builder = make_select().lte("count", 50i32);
577 match &builder.parts.filters[0] {
578 FilterCondition::Comparison { column, operator, .. } => {
579 assert_eq!(column, "count");
580 assert_eq!(*operator, FilterOperator::Lte);
581 }
582 _ => panic!("expected Comparison filter"),
583 }
584 }
585
586 #[test]
587 fn test_like_adds_pattern_filter() {
588 let builder = make_select().like("name", "%test%");
589 assert_eq!(builder.parts.filters.len(), 1);
590 match &builder.parts.filters[0] {
591 FilterCondition::Pattern { column, operator, .. } => {
592 assert_eq!(column, "name");
593 assert_eq!(*operator, PatternOperator::Like);
594 }
595 _ => panic!("expected Pattern filter"),
596 }
597 }
598
599 #[test]
600 fn test_ilike_adds_pattern_filter() {
601 let builder = make_select().ilike("name", "%TEST%");
602 match &builder.parts.filters[0] {
603 FilterCondition::Pattern { column, operator, .. } => {
604 assert_eq!(column, "name");
605 assert_eq!(*operator, PatternOperator::ILike);
606 }
607 _ => panic!("expected Pattern filter"),
608 }
609 }
610
611 #[test]
612 fn test_is_null() {
613 let builder = make_select().is("deleted_at", IsValue::Null);
614 match &builder.parts.filters[0] {
615 FilterCondition::Is { column, value } => {
616 assert_eq!(column, "deleted_at");
617 assert_eq!(*value, IsValue::Null);
618 }
619 _ => panic!("expected Is filter"),
620 }
621 }
622
623 #[test]
624 fn test_is_not_null() {
625 let builder = make_select().is("name", IsValue::NotNull);
626 match &builder.parts.filters[0] {
627 FilterCondition::Is { value, .. } => assert_eq!(*value, IsValue::NotNull),
628 _ => panic!("expected Is filter"),
629 }
630 }
631
632 #[test]
633 fn test_is_true() {
634 let builder = make_select().is("active", IsValue::True);
635 match &builder.parts.filters[0] {
636 FilterCondition::Is { value, .. } => assert_eq!(*value, IsValue::True),
637 _ => panic!("expected Is filter"),
638 }
639 }
640
641 #[test]
642 fn test_is_false() {
643 let builder = make_select().is("active", IsValue::False);
644 match &builder.parts.filters[0] {
645 FilterCondition::Is { value, .. } => assert_eq!(*value, IsValue::False),
646 _ => panic!("expected Is filter"),
647 }
648 }
649
650 #[test]
651 fn test_in_with_values() {
652 let builder = make_select().in_("id", vec![1i32, 2, 3]);
653 assert_eq!(builder.parts.filters.len(), 1);
654 match &builder.parts.filters[0] {
655 FilterCondition::In { column, param_indices } => {
656 assert_eq!(column, "id");
657 assert_eq!(param_indices.len(), 3);
658 assert_eq!(param_indices[0], 1);
659 assert_eq!(param_indices[1], 2);
660 assert_eq!(param_indices[2], 3);
661 }
662 _ => panic!("expected In filter"),
663 }
664 }
665
666 #[test]
667 fn test_contains_adds_array_range_filter() {
668 let builder = make_select().contains("tags", "rust");
669 match &builder.parts.filters[0] {
670 FilterCondition::ArrayRange { column, operator, .. } => {
671 assert_eq!(column, "tags");
672 assert_eq!(*operator, ArrayRangeOperator::Contains);
673 }
674 _ => panic!("expected ArrayRange filter"),
675 }
676 }
677
678 #[test]
679 fn test_contained_by_adds_array_range_filter() {
680 let builder = make_select().contained_by("tags", "all_tags");
681 match &builder.parts.filters[0] {
682 FilterCondition::ArrayRange { column, operator, .. } => {
683 assert_eq!(column, "tags");
684 assert_eq!(*operator, ArrayRangeOperator::ContainedBy);
685 }
686 _ => panic!("expected ArrayRange filter"),
687 }
688 }
689
690 #[test]
691 fn test_overlaps_adds_array_range_filter() {
692 let builder = make_select().overlaps("tags", "some_tags");
693 match &builder.parts.filters[0] {
694 FilterCondition::ArrayRange { column, operator, .. } => {
695 assert_eq!(column, "tags");
696 assert_eq!(*operator, ArrayRangeOperator::Overlaps);
697 }
698 _ => panic!("expected ArrayRange filter"),
699 }
700 }
701
702 #[test]
703 fn test_range_gt() {
704 let builder = make_select().range_gt("period", "[2024-01-01,2024-12-31]");
705 match &builder.parts.filters[0] {
706 FilterCondition::ArrayRange { operator, .. } => {
707 assert_eq!(*operator, ArrayRangeOperator::RangeGt);
708 }
709 _ => panic!("expected ArrayRange filter"),
710 }
711 }
712
713 #[test]
714 fn test_range_gte() {
715 let builder = make_select().range_gte("period", "[2024-01-01,2024-12-31]");
716 match &builder.parts.filters[0] {
717 FilterCondition::ArrayRange { operator, .. } => {
718 assert_eq!(*operator, ArrayRangeOperator::RangeGte);
719 }
720 _ => panic!("expected ArrayRange filter"),
721 }
722 }
723
724 #[test]
725 fn test_range_lt() {
726 let builder = make_select().range_lt("period", "[2024-01-01,2024-12-31]");
727 match &builder.parts.filters[0] {
728 FilterCondition::ArrayRange { operator, .. } => {
729 assert_eq!(*operator, ArrayRangeOperator::RangeLt);
730 }
731 _ => panic!("expected ArrayRange filter"),
732 }
733 }
734
735 #[test]
736 fn test_range_lte() {
737 let builder = make_select().range_lte("period", "[2024-01-01,2024-12-31]");
738 match &builder.parts.filters[0] {
739 FilterCondition::ArrayRange { operator, .. } => {
740 assert_eq!(*operator, ArrayRangeOperator::RangeLte);
741 }
742 _ => panic!("expected ArrayRange filter"),
743 }
744 }
745
746 #[test]
747 fn test_range_adjacent() {
748 let builder = make_select().range_adjacent("period", "[2024-01-01,2024-12-31]");
749 match &builder.parts.filters[0] {
750 FilterCondition::ArrayRange { operator, .. } => {
751 assert_eq!(*operator, ArrayRangeOperator::RangeAdjacent);
752 }
753 _ => panic!("expected ArrayRange filter"),
754 }
755 }
756
757 #[test]
758 fn test_text_search_without_config() {
759 let builder = make_select().text_search("body", "hello world", TextSearchType::Plain, None);
760 match &builder.parts.filters[0] {
761 FilterCondition::TextSearch { column, config, search_type, .. } => {
762 assert_eq!(column, "body");
763 assert!(config.is_none());
764 assert_eq!(*search_type, TextSearchType::Plain);
765 }
766 _ => panic!("expected TextSearch filter"),
767 }
768 }
769
770 #[test]
771 fn test_text_search_with_config() {
772 let builder = make_select().text_search(
773 "body",
774 "hello world",
775 TextSearchType::Websearch,
776 Some("english"),
777 );
778 match &builder.parts.filters[0] {
779 FilterCondition::TextSearch { config, search_type, .. } => {
780 assert_eq!(config.as_deref(), Some("english"));
781 assert_eq!(*search_type, TextSearchType::Websearch);
782 }
783 _ => panic!("expected TextSearch filter"),
784 }
785 }
786
787 #[test]
788 fn test_not_wraps_in_not() {
789 let builder = make_select().not(|f| f.eq("active", true));
790 assert_eq!(builder.parts.filters.len(), 1);
791 match &builder.parts.filters[0] {
792 FilterCondition::Not(inner) => {
793 assert!(matches!(inner.as_ref(), FilterCondition::Comparison { .. }));
794 }
795 _ => panic!("expected Not filter"),
796 }
797 }
798
799 #[test]
800 fn test_or_filter_wraps_in_or() {
801 let builder = make_select().or_filter(|f| f.eq("a", 1i32).eq("b", 2i32));
802 assert_eq!(builder.parts.filters.len(), 1);
803 match &builder.parts.filters[0] {
804 FilterCondition::Or(conditions) => {
805 assert_eq!(conditions.len(), 2);
806 }
807 _ => panic!("expected Or filter"),
808 }
809 }
810
811 #[test]
812 fn test_match_filter_creates_match_conditions() {
813 let builder = make_select().match_filter(vec![("name", "Alice"), ("age", "30")]);
814 assert_eq!(builder.parts.filters.len(), 1);
815 match &builder.parts.filters[0] {
816 FilterCondition::Match { conditions } => {
817 assert_eq!(conditions.len(), 2);
818 assert_eq!(conditions[0].0, "name");
819 assert_eq!(conditions[1].0, "age");
820 }
821 _ => panic!("expected Match filter"),
822 }
823 }
824
825 #[test]
826 fn test_filter_raw_sql() {
827 let builder = make_select().filter("age > 18 AND status = 'active'");
828 match &builder.parts.filters[0] {
829 FilterCondition::Raw(sql) => {
830 assert_eq!(sql, "age > 18 AND status = 'active'");
831 }
832 _ => panic!("expected Raw filter"),
833 }
834 }
835
836 #[test]
837 fn test_invalid_column_name_silently_ignored() {
838 let builder = make_select().eq("bad;col", "value");
839 assert!(builder.parts.filters.is_empty());
840 }
841
842 #[test]
843 fn test_invalid_column_name_in_neq_silently_ignored() {
844 let builder = make_select().neq("bad\"col", "value");
845 assert!(builder.parts.filters.is_empty());
846 }
847
848 #[test]
849 fn test_invalid_column_name_in_like_silently_ignored() {
850 let builder = make_select().like("bad--col", "%test%");
851 assert!(builder.parts.filters.is_empty());
852 }
853
854 #[test]
855 fn test_invalid_column_name_in_is_silently_ignored() {
856 let builder = make_select().is("", IsValue::Null);
857 assert!(builder.parts.filters.is_empty());
858 }
859
860 #[test]
863 fn test_filter_collector_into_conditions() {
864 let mut params = ParamStore::new();
865 let collector = FilterCollector::new(&mut params)
866 .eq("a", 1i32)
867 .eq("b", 2i32);
868 let conditions = collector.into_conditions();
869 assert_eq!(conditions.len(), 2);
870 }
871
872 #[test]
873 fn test_filter_collector_into_single_condition_single() {
874 let mut params = ParamStore::new();
875 let collector = FilterCollector::new(&mut params).eq("a", 1i32);
876 let condition = collector.into_single_condition();
877 assert!(condition.is_some());
878 assert!(matches!(condition.unwrap(), FilterCondition::Comparison { .. }));
879 }
880
881 #[test]
882 fn test_filter_collector_into_single_condition_empty() {
883 let mut params = ParamStore::new();
884 let collector = FilterCollector::new(&mut params);
885 let condition = collector.into_single_condition();
886 assert!(condition.is_none());
887 }
888
889 #[test]
890 fn test_filter_collector_into_single_condition_multiple() {
891 let mut params = ParamStore::new();
892 let collector = FilterCollector::new(&mut params)
893 .eq("a", 1i32)
894 .neq("b", 2i32);
895 let condition = collector.into_single_condition();
896 assert!(condition.is_some());
897 assert!(matches!(condition.unwrap(), FilterCondition::And(_)));
898 }
899
900 #[test]
901 fn test_filter_collector_all_methods() {
902 let mut params = ParamStore::new();
903 let collector = FilterCollector::new(&mut params)
904 .gt("a", 1i32)
905 .gte("b", 2i32)
906 .lt("c", 3i32)
907 .lte("d", 4i32)
908 .like("e", "%test%")
909 .ilike("f", "%TEST%")
910 .is("g", IsValue::Null);
911 let conditions = collector.into_conditions();
912 assert_eq!(conditions.len(), 7);
913 }
914
915 #[test]
916 fn test_filter_collector_invalid_column_skipped() {
917 let mut params = ParamStore::new();
918 let collector = FilterCollector::new(&mut params)
919 .eq("good_col", 1i32)
920 .eq("bad;col", 2i32);
921 let conditions = collector.into_conditions();
922 assert_eq!(conditions.len(), 1);
923 }
924}