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 slice(total: u32, index: u32) -> Self {
317 debug_assert!(total >= 1, "`total` must be at least 1");
318 debug_assert!(index < total, "`index` must be less than `total`");
319
320 Self::from(SliceCondition { total, index })
321 }
322
323 pub fn range(field: impl Into<String>, range: Range) -> Self {
335 Self {
336 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
337 key: field.into(),
338 range: Some(range),
339 ..Default::default()
340 })),
341 }
342 }
343
344 pub fn datetime_range(field: impl Into<String>, range: DatetimeRange) -> Self {
356 Self {
357 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
358 key: field.into(),
359 datetime_range: Some(range),
360 ..Default::default()
361 })),
362 }
363 }
364
365 pub fn geo_radius(field: impl Into<String>, geo_radius: GeoRadius) -> Self {
376 Self {
377 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
378 key: field.into(),
379 geo_radius: Some(geo_radius),
380 ..Default::default()
381 })),
382 }
383 }
384
385 pub fn geo_bounding_box(field: impl Into<String>, geo_bounding_box: GeoBoundingBox) -> Self {
396 Self {
397 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
398 key: field.into(),
399 geo_bounding_box: Some(geo_bounding_box),
400 ..Default::default()
401 })),
402 }
403 }
404
405 pub fn geo_polygon(field: impl Into<String>, geo_polygon: GeoPolygon) -> Self {
417 Self {
418 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
419 key: field.into(),
420 geo_polygon: Some(geo_polygon),
421 ..Default::default()
422 })),
423 }
424 }
425
426 pub fn values_count(field: impl Into<String>, values_count: ValuesCount) -> Self {
437 Self {
438 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
439 key: field.into(),
440 values_count: Some(values_count),
441 ..Default::default()
442 })),
443 }
444 }
445
446 pub fn nested(field: impl Into<String>, filter: Filter) -> Self {
468 Self::from(NestedCondition {
469 key: field.into(),
470 filter: Some(filter),
471 })
472 }
473}
474
475impl From<bool> for MatchValue {
476 fn from(value: bool) -> Self {
477 Self::Boolean(value)
478 }
479}
480
481impl From<i64> for MatchValue {
482 fn from(value: i64) -> Self {
483 Self::Integer(value)
484 }
485}
486
487impl From<String> for MatchValue {
488 fn from(value: String) -> Self {
489 if value.contains(char::is_whitespace) {
490 Self::Text(value)
491 } else {
492 Self::Keyword(value)
493 }
494 }
495}
496
497impl From<Vec<i64>> for MatchValue {
498 fn from(integers: Vec<i64>) -> Self {
499 Self::Integers(qdrant::RepeatedIntegers { integers })
500 }
501}
502
503impl From<Vec<String>> for MatchValue {
504 fn from(strings: Vec<String>) -> Self {
505 Self::Keywords(qdrant::RepeatedStrings { strings })
506 }
507}
508
509impl<const N: usize> From<[&str; N]> for MatchValue {
510 fn from(strings: [&str; N]) -> Self {
511 Self::Keywords(qdrant::RepeatedStrings {
512 strings: strings.iter().map(|&s| String::from(s)).collect(),
513 })
514 }
515}
516
517impl std::ops::Not for MatchValue {
518 type Output = Self;
519
520 fn not(self) -> Self::Output {
521 match self {
522 Self::Keyword(s) => Self::ExceptKeywords(qdrant::RepeatedStrings { strings: vec![s] }),
523 Self::Integer(i) => {
524 Self::ExceptIntegers(qdrant::RepeatedIntegers { integers: vec![i] })
525 }
526 Self::Boolean(b) => Self::Boolean(!b),
527 Self::Keywords(ks) => Self::ExceptKeywords(ks),
528 Self::Integers(is) => Self::ExceptIntegers(is),
529 Self::ExceptKeywords(ks) => Self::Keywords(ks),
530 Self::ExceptIntegers(is) => Self::Integers(is),
531 Self::Text(_) => {
532 panic!("cannot negate a MatchValue::Text, use within must_not clause instead")
533 }
534 Self::Phrase(_) => {
535 panic!("cannot negate a MatchValue::Phrase, use within must_not clause instead")
536 }
537 Self::TextAny(_) => {
538 panic!("cannot negate a MatchValue::TextAny, use within must_not clause instead")
539 }
540 Self::Prefix(_) => {
541 panic!("cannot negate a MatchValue::Prefix, use within must_not clause instead")
542 }
543 }
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use crate::qdrant::{Condition, Filter, NestedCondition};
550
551 #[test]
552 fn test_nested_has_id() {
553 assert!(!Filter::any([]).check_has_id());
554 assert!(Filter::any([Condition::has_id([0])]).check_has_id());
555
556 assert!(Filter::any([Filter::any([Condition::has_id([0])]).into()]).check_has_id());
558
559 assert!(
561 Filter::any([Filter::any([Filter::any([Condition::has_id([0])]).into()]).into()])
562 .check_has_id()
563 );
564
565 assert!(Filter::any([Condition {
567 condition_one_of: Some(crate::qdrant::condition::ConditionOneOf::Nested(
568 NestedCondition {
569 key: "test".to_string(),
570 filter: Some(Filter::any([Condition::has_id([0])]))
571 }
572 ))
573 }])
574 .check_has_id());
575 }
576
577 #[test]
578 #[should_panic]
579 fn test_nested_condition_validation() {
580 let _ = Filter::any([Condition::nested(
581 "test",
582 Filter::any([Condition::has_id([0])]),
583 )]);
584 }
585}