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, 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<Filter> for Condition {
59 fn from(filter: Filter) -> Self {
60 Condition {
61 condition_one_of: Some(ConditionOneOf::Filter(filter)),
62 }
63 }
64}
65
66impl From<NestedCondition> for Condition {
67 fn from(nested_condition: NestedCondition) -> Self {
68 debug_assert!(
69 !&nested_condition
70 .filter
71 .as_ref()
72 .is_some_and(|f| f.check_has_id()),
73 "Filters containing a `has_id` condition are not supported for nested filtering."
74 );
75
76 Condition {
77 condition_one_of: Some(ConditionOneOf::Nested(nested_condition)),
78 }
79 }
80}
81
82impl qdrant::Filter {
83 fn check_has_id(&self) -> bool {
86 self.should
87 .iter()
88 .chain(self.must.iter())
89 .chain(self.must_not.iter())
90 .any(|cond| match &cond.condition_one_of {
91 Some(ConditionOneOf::HasId(_)) => true,
92 Some(ConditionOneOf::Nested(nested)) => nested
93 .filter
94 .as_ref()
95 .is_some_and(|filter| filter.check_has_id()),
96 Some(ConditionOneOf::Filter(filter)) => filter.check_has_id(),
97 _ => false,
98 })
99 }
100
101 pub fn must(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
103 Self {
104 must: conds.into_iter().collect(),
105 ..Default::default()
106 }
107 }
108
109 pub fn should(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
111 Self {
112 should: conds.into_iter().collect(),
113 ..Default::default()
114 }
115 }
116
117 pub fn min_should(min_count: u64, conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
119 Self {
120 min_should: Some(MinShould {
121 min_count,
122 conditions: conds.into_iter().collect(),
123 }),
124 ..Default::default()
125 }
126 }
127
128 pub fn must_not(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
130 Self {
131 must_not: conds.into_iter().collect(),
132 ..Default::default()
133 }
134 }
135
136 pub fn any(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
140 Self::should(conds)
141 }
142
143 pub fn all(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
147 Self::must(conds)
148 }
149
150 pub fn none(conds: impl IntoIterator<Item = qdrant::Condition>) -> Self {
154 Self::must_not(conds)
155 }
156}
157
158impl qdrant::Condition {
159 pub fn is_empty(key: impl Into<String>) -> Self {
166 Self::from(qdrant::IsEmptyCondition { key: key.into() })
167 }
168
169 pub fn is_null(key: impl Into<String>) -> Self {
176 Self::from(qdrant::IsNullCondition { key: key.into() })
177 }
178
179 pub fn has_id(ids: impl IntoIterator<Item = impl Into<PointId>>) -> Self {
186 Self::from(qdrant::HasIdCondition {
187 has_id: ids.into_iter().map(Into::into).collect(),
188 })
189 }
190
191 pub fn has_vector(vector_name: impl Into<String>) -> Self {
198 Self::from(qdrant::HasVectorCondition {
199 has_vector: vector_name.into(),
200 })
201 }
202
203 pub fn matches(field: impl Into<String>, r#match: impl Into<MatchValue>) -> Self {
211 Self {
212 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
213 key: field.into(),
214 r#match: Some(qdrant::Match {
215 match_value: Some(r#match.into()),
216 }),
217 ..Default::default()
218 })),
219 }
220 }
221
222 pub fn matches_text(field: impl Into<String>, query: impl Into<String>) -> Self {
229 Self {
230 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
231 key: field.into(),
232 r#match: Some(qdrant::Match {
233 match_value: Some(MatchValue::Text(query.into())),
234 }),
235 ..Default::default()
236 })),
237 }
238 }
239
240 pub fn matches_phrase(field: impl Into<String>, query: impl Into<String>) -> Self {
247 Self {
248 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
249 key: field.into(),
250 r#match: Some(qdrant::Match {
251 match_value: Some(MatchValue::Phrase(query.into())),
252 }),
253 ..Default::default()
254 })),
255 }
256 }
257
258 pub fn matches_text_any(field: impl Into<String>, query: impl Into<String>) -> Self {
265 Self {
266 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
267 key: field.into(),
268 r#match: Some(qdrant::Match {
269 match_value: Some(MatchValue::TextAny(query.into())),
270 }),
271 ..Default::default()
272 })),
273 }
274 }
275
276 pub fn range(field: impl Into<String>, range: Range) -> Self {
288 Self {
289 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
290 key: field.into(),
291 range: Some(range),
292 ..Default::default()
293 })),
294 }
295 }
296
297 pub fn datetime_range(field: impl Into<String>, range: DatetimeRange) -> Self {
309 Self {
310 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
311 key: field.into(),
312 datetime_range: Some(range),
313 ..Default::default()
314 })),
315 }
316 }
317
318 pub fn geo_radius(field: impl Into<String>, geo_radius: GeoRadius) -> Self {
329 Self {
330 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
331 key: field.into(),
332 geo_radius: Some(geo_radius),
333 ..Default::default()
334 })),
335 }
336 }
337
338 pub fn geo_bounding_box(field: impl Into<String>, geo_bounding_box: GeoBoundingBox) -> Self {
349 Self {
350 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
351 key: field.into(),
352 geo_bounding_box: Some(geo_bounding_box),
353 ..Default::default()
354 })),
355 }
356 }
357
358 pub fn geo_polygon(field: impl Into<String>, geo_polygon: GeoPolygon) -> Self {
370 Self {
371 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
372 key: field.into(),
373 geo_polygon: Some(geo_polygon),
374 ..Default::default()
375 })),
376 }
377 }
378
379 pub fn values_count(field: impl Into<String>, values_count: ValuesCount) -> Self {
390 Self {
391 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
392 key: field.into(),
393 values_count: Some(values_count),
394 ..Default::default()
395 })),
396 }
397 }
398
399 pub fn nested(field: impl Into<String>, filter: Filter) -> Self {
421 Self::from(NestedCondition {
422 key: field.into(),
423 filter: Some(filter),
424 })
425 }
426}
427
428impl From<bool> for MatchValue {
429 fn from(value: bool) -> Self {
430 Self::Boolean(value)
431 }
432}
433
434impl From<i64> for MatchValue {
435 fn from(value: i64) -> Self {
436 Self::Integer(value)
437 }
438}
439
440impl From<String> for MatchValue {
441 fn from(value: String) -> Self {
442 if value.contains(char::is_whitespace) {
443 Self::Text(value)
444 } else {
445 Self::Keyword(value)
446 }
447 }
448}
449
450impl From<Vec<i64>> for MatchValue {
451 fn from(integers: Vec<i64>) -> Self {
452 Self::Integers(qdrant::RepeatedIntegers { integers })
453 }
454}
455
456impl From<Vec<String>> for MatchValue {
457 fn from(strings: Vec<String>) -> Self {
458 Self::Keywords(qdrant::RepeatedStrings { strings })
459 }
460}
461
462impl<const N: usize> From<[&str; N]> for MatchValue {
463 fn from(strings: [&str; N]) -> Self {
464 Self::Keywords(qdrant::RepeatedStrings {
465 strings: strings.iter().map(|&s| String::from(s)).collect(),
466 })
467 }
468}
469
470impl std::ops::Not for MatchValue {
471 type Output = Self;
472
473 fn not(self) -> Self::Output {
474 match self {
475 Self::Keyword(s) => Self::ExceptKeywords(qdrant::RepeatedStrings { strings: vec![s] }),
476 Self::Integer(i) => {
477 Self::ExceptIntegers(qdrant::RepeatedIntegers { integers: vec![i] })
478 }
479 Self::Boolean(b) => Self::Boolean(!b),
480 Self::Keywords(ks) => Self::ExceptKeywords(ks),
481 Self::Integers(is) => Self::ExceptIntegers(is),
482 Self::ExceptKeywords(ks) => Self::Keywords(ks),
483 Self::ExceptIntegers(is) => Self::Integers(is),
484 Self::Text(_) => {
485 panic!("cannot negate a MatchValue::Text, use within must_not clause instead")
486 }
487 Self::Phrase(_) => {
488 panic!("cannot negate a MatchValue::Phrase, use within must_not clause instead")
489 }
490 Self::TextAny(_) => {
491 panic!("cannot negate a MatchValue::TextAny, use within must_not clause instead")
492 }
493 }
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use crate::qdrant::{Condition, Filter, NestedCondition};
500
501 #[test]
502 fn test_nested_has_id() {
503 assert!(!Filter::any([]).check_has_id());
504 assert!(Filter::any([Condition::has_id([0])]).check_has_id());
505
506 assert!(Filter::any([Filter::any([Condition::has_id([0])]).into()]).check_has_id());
508
509 assert!(
511 Filter::any([Filter::any([Filter::any([Condition::has_id([0])]).into()]).into()])
512 .check_has_id()
513 );
514
515 assert!(Filter::any([Condition {
517 condition_one_of: Some(crate::qdrant::condition::ConditionOneOf::Nested(
518 NestedCondition {
519 key: "test".to_string(),
520 filter: Some(Filter::any([Condition::has_id([0])]))
521 }
522 ))
523 }])
524 .check_has_id());
525 }
526
527 #[test]
528 #[should_panic]
529 fn test_nested_condition_validation() {
530 let _ = Filter::any([Condition::nested(
531 "test",
532 Filter::any([Condition::has_id([0])]),
533 )]);
534 }
535}