1use crate::qdrant::condition::ConditionOneOf;
2use crate::qdrant::points_selector::PointsSelectorOneOf;
3use crate::qdrant::r#match::MatchValue;
4use crate::qdrant::{
5 self, Condition, DatetimeRange, FieldCondition, Filter, GeoBoundingBox, GeoPolygon, GeoRadius,
6 HasIdCondition, HasVectorCondition, IsEmptyCondition, IsNullCondition, MinShould,
7 NestedCondition, PointId, PointsSelector, Range, SliceCondition, ValuesCount,
8};
9
10impl From<Filter> for PointsSelector {
11 fn from(filter: Filter) -> Self {
12 PointsSelector {
13 points_selector_one_of: Some(PointsSelectorOneOf::Filter(filter)),
14 }
15 }
16}
17
18impl From<FieldCondition> for Condition {
19 fn from(field_condition: FieldCondition) -> Self {
20 Condition {
21 condition_one_of: Some(ConditionOneOf::Field(field_condition)),
22 }
23 }
24}
25
26impl From<IsEmptyCondition> for Condition {
27 fn from(is_empty_condition: IsEmptyCondition) -> Self {
28 Condition {
29 condition_one_of: Some(ConditionOneOf::IsEmpty(is_empty_condition)),
30 }
31 }
32}
33
34impl From<IsNullCondition> for Condition {
35 fn from(is_null_condition: IsNullCondition) -> Self {
36 Condition {
37 condition_one_of: Some(ConditionOneOf::IsNull(is_null_condition)),
38 }
39 }
40}
41
42impl From<HasIdCondition> for Condition {
43 fn from(has_id_condition: HasIdCondition) -> Self {
44 Condition {
45 condition_one_of: Some(ConditionOneOf::HasId(has_id_condition)),
46 }
47 }
48}
49
50impl From<HasVectorCondition> for Condition {
51 fn from(has_vector_condition: HasVectorCondition) -> Self {
52 Condition {
53 condition_one_of: Some(ConditionOneOf::HasVector(has_vector_condition)),
54 }
55 }
56}
57
58impl From<SliceCondition> for Condition {
59 fn from(slice_condition: SliceCondition) -> Self {
60 Condition {
61 condition_one_of: Some(ConditionOneOf::Slice(slice_condition)),
62 }
63 }
64}
65
66impl From<Filter> for Condition {
67 fn from(filter: Filter) -> Self {
68 Condition {
69 condition_one_of: Some(ConditionOneOf::Filter(filter)),
70 }
71 }
72}
73
74impl From<NestedCondition> for Condition {
75 fn from(nested_condition: NestedCondition) -> Self {
76 debug_assert!(
77 !&nested_condition
78 .filter
79 .as_ref()
80 .is_some_and(|f| f.check_has_id()),
81 "Filters containing a `has_id` condition are not supported for nested filtering."
82 );
83
84 Condition {
85 condition_one_of: Some(ConditionOneOf::Nested(nested_condition)),
86 }
87 }
88}
89
90impl qdrant::Filter {
91 fn check_has_id(&self) -> bool {
94 self.should
95 .iter()
96 .chain(self.must.iter())
97 .chain(self.must_not.iter())
98 .any(|cond| match &cond.condition_one_of {
99 Some(ConditionOneOf::HasId(_)) => true,
100 Some(ConditionOneOf::Nested(nested)) => nested
101 .filter
102 .as_ref()
103 .is_some_and(|filter| filter.check_has_id()),
104 Some(ConditionOneOf::Filter(filter)) => filter.check_has_id(),
105 _ => false,
106 })
107 }
108
109 pub fn must(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
111 Self {
112 must: conds.into_iter().collect(),
113 ..Default::default()
114 }
115 }
116
117 pub fn should(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
119 Self {
120 should: conds.into_iter().collect(),
121 ..Default::default()
122 }
123 }
124
125 pub fn min_should(min_count: u64, conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
127 Self {
128 min_should: Some(MinShould {
129 min_count,
130 conditions: conds.into_iter().collect(),
131 }),
132 ..Default::default()
133 }
134 }
135
136 pub fn must_not(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
138 Self {
139 must_not: conds.into_iter().collect(),
140 ..Default::default()
141 }
142 }
143
144 pub fn any(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
148 Self::should(conds)
149 }
150
151 pub fn all(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
155 Self::must(conds)
156 }
157
158 pub fn none(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
162 Self::must_not(conds)
163 }
164}
165
166impl qdrant::Condition {
167 pub fn is_empty(key: impl Into<String>) -> Self {
174 Self::from(qdrant::IsEmptyCondition { key: key.into() })
175 }
176
177 pub fn is_null(key: impl Into<String>) -> Self {
184 Self::from(qdrant::IsNullCondition { key: key.into() })
185 }
186
187 pub fn has_id(ids: impl IntoIterator<Item = impl Into<PointId>>) -> Self {
194 Self::from(qdrant::HasIdCondition {
195 has_id: ids.into_iter().map(Into::into).collect(),
196 })
197 }
198
199 pub fn has_vector(vector_name: impl Into<String>) -> Self {
206 Self::from(qdrant::HasVectorCondition {
207 has_vector: vector_name.into(),
208 })
209 }
210
211 pub fn matches(field: impl Into<String>, r#match: impl Into<MatchValue>) -> Self {
219 Self {
220 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
221 key: field.into(),
222 r#match: Some(qdrant::Match {
223 match_value: Some(r#match.into()),
224 }),
225 ..Default::default()
226 })),
227 }
228 }
229
230 pub fn matches_text(field: impl Into<String>, query: impl Into<String>) -> Self {
237 Self {
238 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
239 key: field.into(),
240 r#match: Some(qdrant::Match {
241 match_value: Some(MatchValue::Text(query.into())),
242 }),
243 ..Default::default()
244 })),
245 }
246 }
247
248 pub fn matches_phrase(field: impl Into<String>, query: impl Into<String>) -> Self {
255 Self {
256 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
257 key: field.into(),
258 r#match: Some(qdrant::Match {
259 match_value: Some(MatchValue::Phrase(query.into())),
260 }),
261 ..Default::default()
262 })),
263 }
264 }
265
266 pub fn matches_text_any(field: impl Into<String>, query: impl Into<String>) -> Self {
273 Self {
274 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
275 key: field.into(),
276 r#match: Some(qdrant::Match {
277 match_value: Some(MatchValue::TextAny(query.into())),
278 }),
279 ..Default::default()
280 })),
281 }
282 }
283
284 pub fn matches_prefix(field: impl Into<String>, prefix: impl Into<String>) -> Self {
294 Self {
295 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
296 key: field.into(),
297 r#match: Some(qdrant::Match {
298 match_value: Some(MatchValue::Prefix(prefix.into())),
299 }),
300 ..Default::default()
301 })),
302 }
303 }
304
305 pub fn matches_substring(field: impl Into<String>, substring: impl Into<String>) -> Self {
312 Self {
313 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
314 key: field.into(),
315 r#match: Some(qdrant::Match {
316 match_value: Some(MatchValue::Substring(substring.into())),
317 }),
318 ..Default::default()
319 })),
320 }
321 }
322
323 pub fn slice(total: u32, index: u32) -> Self {
335 debug_assert!(total >= 1, "`total` must be at least 1");
336 debug_assert!(index < total, "`index` must be less than `total`");
337
338 Self::from(SliceCondition { total, index })
339 }
340
341 pub fn range(field: impl Into<String>, range: Range) -> Self {
353 Self {
354 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
355 key: field.into(),
356 range: Some(range),
357 ..Default::default()
358 })),
359 }
360 }
361
362 pub fn datetime_range(field: impl Into<String>, range: DatetimeRange) -> Self {
374 Self {
375 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
376 key: field.into(),
377 datetime_range: Some(range),
378 ..Default::default()
379 })),
380 }
381 }
382
383 pub fn geo_radius(field: impl Into<String>, geo_radius: GeoRadius) -> Self {
394 Self {
395 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
396 key: field.into(),
397 geo_radius: Some(geo_radius),
398 ..Default::default()
399 })),
400 }
401 }
402
403 pub fn geo_bounding_box(field: impl Into<String>, geo_bounding_box: GeoBoundingBox) -> Self {
414 Self {
415 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
416 key: field.into(),
417 geo_bounding_box: Some(geo_bounding_box),
418 ..Default::default()
419 })),
420 }
421 }
422
423 pub fn geo_polygon(field: impl Into<String>, geo_polygon: GeoPolygon) -> Self {
435 Self {
436 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
437 key: field.into(),
438 geo_polygon: Some(geo_polygon),
439 ..Default::default()
440 })),
441 }
442 }
443
444 pub fn values_count(field: impl Into<String>, values_count: ValuesCount) -> Self {
455 Self {
456 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
457 key: field.into(),
458 values_count: Some(values_count),
459 ..Default::default()
460 })),
461 }
462 }
463
464 pub fn nested(field: impl Into<String>, filter: Filter) -> Self {
486 Self::from(NestedCondition {
487 key: field.into(),
488 filter: Some(filter),
489 })
490 }
491}
492
493impl From<bool> for MatchValue {
494 fn from(value: bool) -> Self {
495 Self::Boolean(value)
496 }
497}
498
499impl From<i64> for MatchValue {
500 fn from(value: i64) -> Self {
501 Self::Integer(value)
502 }
503}
504
505impl From<String> for MatchValue {
506 fn from(value: String) -> Self {
507 if value.contains(char::is_whitespace) {
508 Self::Text(value)
509 } else {
510 Self::Keyword(value)
511 }
512 }
513}
514
515impl From<Vec<i64>> for MatchValue {
516 fn from(integers: Vec<i64>) -> Self {
517 Self::Integers(qdrant::RepeatedIntegers { integers })
518 }
519}
520
521impl From<Vec<String>> for MatchValue {
522 fn from(strings: Vec<String>) -> Self {
523 Self::Keywords(qdrant::RepeatedStrings { strings })
524 }
525}
526
527impl<const N: usize> From<[&str; N]> for MatchValue {
528 fn from(strings: [&str; N]) -> Self {
529 Self::Keywords(qdrant::RepeatedStrings {
530 strings: strings.iter().map(|&s| String::from(s)).collect(),
531 })
532 }
533}
534
535impl std::ops::Not for MatchValue {
536 type Output = Self;
537
538 fn not(self) -> Self::Output {
539 match self {
540 Self::Keyword(s) => Self::ExceptKeywords(qdrant::RepeatedStrings { strings: vec![s] }),
541 Self::Integer(i) => {
542 Self::ExceptIntegers(qdrant::RepeatedIntegers { integers: vec![i] })
543 }
544 Self::Boolean(b) => Self::Boolean(!b),
545 Self::Keywords(ks) => Self::ExceptKeywords(ks),
546 Self::Integers(is) => Self::ExceptIntegers(is),
547 Self::ExceptKeywords(ks) => Self::Keywords(ks),
548 Self::ExceptIntegers(is) => Self::Integers(is),
549 Self::Text(_) => {
550 panic!("cannot negate a MatchValue::Text, use within must_not clause instead")
551 }
552 Self::Phrase(_) => {
553 panic!("cannot negate a MatchValue::Phrase, use within must_not clause instead")
554 }
555 Self::TextAny(_) => {
556 panic!("cannot negate a MatchValue::TextAny, use within must_not clause instead")
557 }
558 Self::Prefix(_) => {
559 panic!("cannot negate a MatchValue::Prefix, use within must_not clause instead")
560 }
561 Self::Substring(_) => {
562 panic!("cannot negate a MatchValue::Substring, use within must_not clause instead")
563 }
564 }
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use crate::qdrant::{Condition, Filter, NestedCondition};
571
572 #[test]
573 fn test_nested_has_id() {
574 assert!(!Filter::any([]).check_has_id());
575 assert!(Filter::any([Condition::has_id([0])]).check_has_id());
576
577 assert!(Filter::any([Filter::any([Condition::has_id([0])]).into()]).check_has_id());
579
580 assert!(
582 Filter::any([Filter::any([Filter::any([Condition::has_id([0])]).into()]).into()])
583 .check_has_id()
584 );
585
586 assert!(Filter::any([Condition {
588 condition_one_of: Some(crate::qdrant::condition::ConditionOneOf::Nested(
589 NestedCondition {
590 key: "test".to_string(),
591 filter: Some(Filter::any([Condition::has_id([0])]))
592 }
593 ))
594 }])
595 .check_has_id());
596 }
597
598 #[test]
599 #[should_panic]
600 fn test_nested_condition_validation() {
601 let _ = Filter::any([Condition::nested(
602 "test",
603 Filter::any([Condition::has_id([0])]),
604 )]);
605 }
606}