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 range(field: impl Into<String>, range: Range) -> Self {
270 Self {
271 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
272 key: field.into(),
273 range: Some(range),
274 ..Default::default()
275 })),
276 }
277 }
278
279 pub fn datetime_range(field: impl Into<String>, range: DatetimeRange) -> Self {
291 Self {
292 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
293 key: field.into(),
294 datetime_range: Some(range),
295 ..Default::default()
296 })),
297 }
298 }
299
300 pub fn geo_radius(field: impl Into<String>, geo_radius: GeoRadius) -> Self {
311 Self {
312 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
313 key: field.into(),
314 geo_radius: Some(geo_radius),
315 ..Default::default()
316 })),
317 }
318 }
319
320 pub fn geo_bounding_box(field: impl Into<String>, geo_bounding_box: GeoBoundingBox) -> Self {
331 Self {
332 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
333 key: field.into(),
334 geo_bounding_box: Some(geo_bounding_box),
335 ..Default::default()
336 })),
337 }
338 }
339
340 pub fn geo_polygon(field: impl Into<String>, geo_polygon: GeoPolygon) -> Self {
352 Self {
353 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
354 key: field.into(),
355 geo_polygon: Some(geo_polygon),
356 ..Default::default()
357 })),
358 }
359 }
360
361 pub fn values_count(field: impl Into<String>, values_count: ValuesCount) -> Self {
372 Self {
373 condition_one_of: Some(ConditionOneOf::Field(qdrant::FieldCondition {
374 key: field.into(),
375 values_count: Some(values_count),
376 ..Default::default()
377 })),
378 }
379 }
380
381 pub fn nested(field: impl Into<String>, filter: Filter) -> Self {
403 Self::from(NestedCondition {
404 key: field.into(),
405 filter: Some(filter),
406 })
407 }
408}
409
410impl From<bool> for MatchValue {
411 fn from(value: bool) -> Self {
412 Self::Boolean(value)
413 }
414}
415
416impl From<i64> for MatchValue {
417 fn from(value: i64) -> Self {
418 Self::Integer(value)
419 }
420}
421
422impl From<String> for MatchValue {
423 fn from(value: String) -> Self {
424 if value.contains(char::is_whitespace) {
425 Self::Text(value)
426 } else {
427 Self::Keyword(value)
428 }
429 }
430}
431
432impl From<Vec<i64>> for MatchValue {
433 fn from(integers: Vec<i64>) -> Self {
434 Self::Integers(qdrant::RepeatedIntegers { integers })
435 }
436}
437
438impl From<Vec<String>> for MatchValue {
439 fn from(strings: Vec<String>) -> Self {
440 Self::Keywords(qdrant::RepeatedStrings { strings })
441 }
442}
443
444impl<const N: usize> From<[&str; N]> for MatchValue {
445 fn from(strings: [&str; N]) -> Self {
446 Self::Keywords(qdrant::RepeatedStrings {
447 strings: strings.iter().map(|&s| String::from(s)).collect(),
448 })
449 }
450}
451
452impl std::ops::Not for MatchValue {
453 type Output = Self;
454
455 fn not(self) -> Self::Output {
456 match self {
457 Self::Keyword(s) => Self::ExceptKeywords(qdrant::RepeatedStrings { strings: vec![s] }),
458 Self::Integer(i) => {
459 Self::ExceptIntegers(qdrant::RepeatedIntegers { integers: vec![i] })
460 }
461 Self::Boolean(b) => Self::Boolean(!b),
462 Self::Keywords(ks) => Self::ExceptKeywords(ks),
463 Self::Integers(is) => Self::ExceptIntegers(is),
464 Self::ExceptKeywords(ks) => Self::Keywords(ks),
465 Self::ExceptIntegers(is) => Self::Integers(is),
466 Self::Text(_) => {
467 panic!("cannot negate a MatchValue::Text, use within must_not clause instead")
468 }
469 Self::Phrase(_) => {
470 panic!("cannot negate a MatchValue::Phrase, use within must_not clause instead")
471 }
472 }
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use crate::qdrant::{Condition, Filter, NestedCondition};
479
480 #[test]
481 fn test_nested_has_id() {
482 assert!(!Filter::any([]).check_has_id());
483 assert!(Filter::any([Condition::has_id([0])]).check_has_id());
484
485 assert!(Filter::any([Filter::any([Condition::has_id([0])]).into()]).check_has_id());
487
488 assert!(
490 Filter::any([Filter::any([Filter::any([Condition::has_id([0])]).into()]).into()])
491 .check_has_id()
492 );
493
494 assert!(Filter::any([Condition {
496 condition_one_of: Some(crate::qdrant::condition::ConditionOneOf::Nested(
497 NestedCondition {
498 key: "test".to_string(),
499 filter: Some(Filter::any([Condition::has_id([0])]))
500 }
501 ))
502 }])
503 .check_has_id());
504 }
505
506 #[test]
507 #[should_panic]
508 fn test_nested_condition_validation() {
509 let _ = Filter::any([Condition::nested(
510 "test",
511 Filter::any([Condition::has_id([0])]),
512 )]);
513 }
514}