1use serde::{Deserialize, Serialize};
8
9use super::VectorStoreError;
10use crate::markers::{Missing, Provided};
11
12#[derive(Clone, Serialize, Deserialize, Debug)]
17pub struct VectorSearchRequest<F = Filter<serde_json::Value>> {
18 query: String,
20 samples: u64,
22 threshold: Option<f64>,
24 additional_params: Option<serde_json::Value>,
26 filter: Option<F>,
28}
29
30impl<Filter> VectorSearchRequest<Filter> {
31 pub fn builder() -> VectorSearchRequestBuilder<Filter> {
33 VectorSearchRequestBuilder::<Filter>::default()
34 }
35
36 pub fn query(&self) -> &str {
38 &self.query
39 }
40
41 pub fn samples(&self) -> u64 {
43 self.samples
44 }
45
46 pub fn threshold(&self) -> Option<f64> {
48 self.threshold
49 }
50
51 pub fn filter(&self) -> &Option<Filter> {
53 &self.filter
54 }
55
56 pub fn map_filter<T, F>(self, f: F) -> VectorSearchRequest<T>
61 where
62 F: Fn(Filter) -> T,
63 {
64 VectorSearchRequest {
65 query: self.query,
66 samples: self.samples,
67 threshold: self.threshold,
68 additional_params: self.additional_params,
69 filter: self.filter.map(f),
70 }
71 }
72
73 pub fn try_map_filter<T, F>(self, f: F) -> Result<VectorSearchRequest<T>, FilterError>
77 where
78 F: Fn(Filter) -> Result<T, FilterError>,
79 {
80 let filter = self.filter.map(f).transpose()?;
81
82 Ok(VectorSearchRequest {
83 query: self.query,
84 samples: self.samples,
85 threshold: self.threshold,
86 additional_params: self.additional_params,
87 filter,
88 })
89 }
90}
91
92#[derive(Debug, Clone, thiserror::Error)]
94pub enum FilterError {
95 #[error("Expected: {expected}, got: {got}")]
96 Expected { expected: String, got: String },
97
98 #[error("Cannot compile '{0}' to the backend's filter type")]
99 TypeError(String),
100
101 #[error("Missing field '{0}'")]
102 MissingField(String),
103
104 #[error("'{0}' must {1}")]
105 Must(String, String),
106
107 #[error("Filter serialization failed: {0}")]
109 Serialization(String),
110}
111
112pub trait SearchFilter {
118 type Value;
119
120 fn eq(key: impl AsRef<str>, value: Self::Value) -> Self;
121 fn gt(key: impl AsRef<str>, value: Self::Value) -> Self;
122 fn lt(key: impl AsRef<str>, value: Self::Value) -> Self;
123 fn and(self, rhs: Self) -> Self;
124 fn or(self, rhs: Self) -> Self;
125}
126
127#[derive(Clone, Debug, Serialize, Deserialize)]
137pub struct SqlCondition<P> {
138 condition: String,
139 params: Vec<P>,
140}
141
142impl<P> Default for SqlCondition<P> {
145 fn default() -> Self {
146 Self {
147 condition: String::new(),
148 params: Vec::new(),
149 }
150 }
151}
152
153impl<P> SqlCondition<P> {
154 pub fn binary(key: impl AsRef<str>, op: &str, placeholder: &str, value: P) -> Self {
157 Self {
158 condition: format!("{} {op} {placeholder}", key.as_ref()),
159 params: vec![value],
160 }
161 }
162
163 pub fn list(key: impl AsRef<str>, op: &str, placeholder: &str, values: Vec<P>) -> Self {
166 let placeholders = vec![placeholder; values.len()].join(", ");
167
168 Self {
169 condition: format!("{} {op} ({placeholders})", key.as_ref()),
170 params: values,
171 }
172 }
173
174 pub fn raw(condition: impl Into<String>) -> Self {
176 Self {
177 condition: condition.into(),
178 params: Vec::new(),
179 }
180 }
181
182 pub fn and(self, rhs: Self) -> Self {
184 self.combine("AND", rhs)
185 }
186
187 pub fn or(self, rhs: Self) -> Self {
189 self.combine("OR", rhs)
190 }
191
192 #[allow(clippy::should_implement_trait)]
194 pub fn not(self) -> Self {
195 Self {
196 condition: format!("NOT ({})", self.condition),
197 ..self
198 }
199 }
200
201 fn combine(self, joiner: &str, rhs: Self) -> Self {
202 Self {
203 condition: format!("({}) {joiner} ({})", self.condition, rhs.condition),
204 params: self.params.into_iter().chain(rhs.params).collect(),
205 }
206 }
207
208 pub fn condition(&self) -> &str {
210 &self.condition
211 }
212
213 pub fn params(&self) -> &[P] {
215 &self.params
216 }
217
218 pub fn into_parts(self) -> (String, Vec<P>) {
220 (self.condition, self.params)
221 }
222}
223
224pub trait DynamicSearchFilter: SearchFilter + Sized {
232 fn from_dynamic_filter(filter: Filter<serde_json::Value>) -> Result<Self, FilterError>;
234
235 fn normalize_dynamic_document(document: serde_json::Value) -> serde_json::Value {
241 document
242 }
243}
244
245impl<F> DynamicSearchFilter for F
246where
247 F: SearchFilter<Value = serde_json::Value>,
248{
249 fn from_dynamic_filter(filter: Filter<serde_json::Value>) -> Result<Self, FilterError> {
250 Ok(filter.interpret())
251 }
252
253 fn normalize_dynamic_document(document: serde_json::Value) -> serde_json::Value {
254 prune_document(document).unwrap_or_default()
255 }
256}
257
258fn prune_document(document: serde_json::Value) -> Option<serde_json::Value> {
259 match document {
260 serde_json::Value::Object(mut map) => {
261 let new_map = map
262 .iter_mut()
263 .filter_map(|(key, value)| {
264 prune_document(value.take()).map(|value| (key.clone(), value))
265 })
266 .collect::<serde_json::Map<_, _>>();
267
268 Some(serde_json::Value::Object(new_map))
269 }
270 serde_json::Value::Array(vec) if vec.len() > 400 => None,
271 serde_json::Value::Array(vec) => Some(serde_json::Value::Array(
272 vec.into_iter().filter_map(prune_document).collect(),
273 )),
274 value => Some(value),
275 }
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
283#[serde(rename_all = "lowercase")]
284pub enum Filter<V>
285where
286 V: std::fmt::Debug + Clone,
287{
288 Eq(String, V),
289 Gt(String, V),
290 Lt(String, V),
291 And(Box<Self>, Box<Self>),
292 Or(Box<Self>, Box<Self>),
293}
294
295impl<V> SearchFilter for Filter<V>
296where
297 V: std::fmt::Debug + Clone + Serialize + for<'de> Deserialize<'de>,
298{
299 type Value = V;
300
301 fn eq(key: impl AsRef<str>, value: Self::Value) -> Self {
303 Self::Eq(key.as_ref().to_owned(), value)
304 }
305
306 fn gt(key: impl AsRef<str>, value: Self::Value) -> Self {
308 Self::Gt(key.as_ref().to_owned(), value)
309 }
310
311 fn lt(key: impl AsRef<str>, value: Self::Value) -> Self {
313 Self::Lt(key.as_ref().to_owned(), value)
314 }
315
316 fn and(self, rhs: Self) -> Self {
318 Self::And(self.into(), rhs.into())
319 }
320
321 fn or(self, rhs: Self) -> Self {
323 Self::Or(self.into(), rhs.into())
324 }
325}
326
327impl<V> Filter<V>
328where
329 V: std::fmt::Debug + Clone,
330{
331 pub fn interpret<F>(self) -> F
333 where
334 F: SearchFilter<Value = V>,
335 {
336 self.interpret_with(|v| v)
337 }
338
339 pub fn interpret_with<F, W>(self, conv: impl Fn(V) -> W + Copy) -> F
342 where
343 F: SearchFilter<Value = W>,
344 {
345 match self.try_interpret(|v| Ok::<W, std::convert::Infallible>(conv(v))) {
346 Ok(filter) => filter,
347 Err(never) => match never {},
348 }
349 }
350
351 pub fn try_interpret<F, W, E>(self, conv: impl Fn(V) -> Result<W, E> + Copy) -> Result<F, E>
354 where
355 F: SearchFilter<Value = W>,
356 {
357 Ok(match self {
358 Self::Eq(key, val) => F::eq(key, conv(val)?),
359 Self::Gt(key, val) => F::gt(key, conv(val)?),
360 Self::Lt(key, val) => F::lt(key, conv(val)?),
361 Self::And(lhs, rhs) => F::and(lhs.try_interpret(conv)?, rhs.try_interpret(conv)?),
362 Self::Or(lhs, rhs) => F::or(lhs.try_interpret(conv)?, rhs.try_interpret(conv)?),
363 })
364 }
365}
366
367impl Filter<serde_json::Value> {
368 pub fn satisfies(&self, value: &serde_json::Value) -> bool {
375 use Filter::*;
376 use serde_json::{Value, Value::*};
377 use std::cmp::Ordering;
378
379 fn compare_pair(l: &Value, r: &Value) -> Option<Ordering> {
380 match (l, r) {
381 (Number(l), Number(r)) => {
385 if let (Some(l), Some(r)) = (l.as_i64(), r.as_i64()) {
386 Some(l.cmp(&r))
387 } else if let (Some(l), Some(r)) = (l.as_u64(), r.as_u64()) {
388 Some(l.cmp(&r))
389 } else {
390 l.as_f64()
391 .zip(r.as_f64())
392 .and_then(|(l, r)| l.partial_cmp(&r))
393 }
394 }
395 (String(l), String(r)) => Some(l.cmp(r)),
396 (Null, Null) => Some(Ordering::Equal),
397 (Bool(l), Bool(r)) => Some(l.cmp(r)),
398 _ => None,
399 }
400 }
401
402 match self {
403 Eq(k, v) => value
407 .get(k)
408 .is_some_and(|field| compare_pair(field, v) == Some(Ordering::Equal) || field == v),
409 Gt(k, v) => value
410 .get(k)
411 .and_then(|field| compare_pair(field, v))
412 .is_some_and(|ord| ord == Ordering::Greater),
413 Lt(k, v) => value
414 .get(k)
415 .and_then(|field| compare_pair(field, v))
416 .is_some_and(|ord| ord == Ordering::Less),
417 And(l, r) => l.satisfies(value) && r.satisfies(value),
418 Or(l, r) => l.satisfies(value) || r.satisfies(value),
419 }
420 }
421}
422
423#[derive(Clone, Serialize, Deserialize, Debug)]
425pub struct VectorSearchRequestBuilder<F = Filter<serde_json::Value>, Q = Missing, S = Missing> {
426 query: Q,
427 samples: S,
428 threshold: Option<f64>,
429 additional_params: Option<serde_json::Value>,
430 filter: Option<F>,
431}
432
433impl<F> Default for VectorSearchRequestBuilder<F, Missing, Missing> {
434 fn default() -> Self {
435 Self {
436 query: Missing,
437 samples: Missing,
438 threshold: None,
439 additional_params: None,
440 filter: None,
441 }
442 }
443}
444
445impl<F, Q, S> VectorSearchRequestBuilder<F, Q, S>
446where
447 F: SearchFilter,
448{
449 pub fn query<T>(self, query: T) -> VectorSearchRequestBuilder<F, Provided<String>, S>
451 where
452 T: Into<String>,
453 {
454 VectorSearchRequestBuilder {
455 query: Provided(query.into()),
456 samples: self.samples,
457 threshold: self.threshold,
458 additional_params: self.additional_params,
459 filter: self.filter,
460 }
461 }
462
463 pub fn samples(self, samples: u64) -> VectorSearchRequestBuilder<F, Q, Provided<u64>> {
465 VectorSearchRequestBuilder {
466 query: self.query,
467 samples: Provided(samples),
468 threshold: self.threshold,
469 additional_params: self.additional_params,
470 filter: self.filter,
471 }
472 }
473
474 pub fn threshold(mut self, threshold: f64) -> Self {
476 self.threshold = Some(threshold);
477 self
478 }
479
480 pub fn additional_params(
482 mut self,
483 params: serde_json::Value,
484 ) -> Result<Self, VectorStoreError> {
485 self.additional_params = Some(params);
486 Ok(self)
487 }
488
489 pub fn filter(mut self, filter: F) -> Self {
491 self.filter = Some(filter);
492 self
493 }
494}
495
496impl<F> VectorSearchRequestBuilder<F, Provided<String>, Provided<u64>> {
498 pub fn build(self) -> VectorSearchRequest<F> {
500 VectorSearchRequest {
501 query: self.query.0,
502 samples: self.samples.0,
503 threshold: self.threshold,
504 additional_params: self.additional_params,
505 filter: self.filter,
506 }
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::{Filter, SearchFilter};
513 use serde_json::json;
514
515 type F = Filter<serde_json::Value>;
516
517 #[test]
518 fn eq_matches_field_within_multi_field_document() {
519 let doc = json!({ "category": "fruit", "text": "banana" });
520 assert!(F::eq("category", json!("fruit")).satisfies(&doc));
521 assert!(!F::eq("category", json!("veg")).satisfies(&doc));
522 assert!(!F::eq("missing", json!("fruit")).satisfies(&doc));
524 }
525
526 #[test]
527 fn gt_and_lt_compare_the_named_field() {
528 let doc = json!({ "price": 10, "text": "banana" });
529 assert!(F::gt("price", json!(5)).satisfies(&doc));
530 assert!(!F::gt("price", json!(10)).satisfies(&doc));
531 assert!(F::lt("price", json!(20)).satisfies(&doc));
532 assert!(!F::lt("price", json!(10)).satisfies(&doc));
533 assert!(!F::gt("missing", json!(1)).satisfies(&doc));
535 assert!(!F::gt("text", json!(1)).satisfies(&doc));
536 }
537
538 #[test]
539 fn eq_matches_integer_and_float_representations() {
540 assert!(F::eq("score", json!(5)).satisfies(&json!({ "score": 5.0 })));
543 assert!(F::eq("score", json!(5.0)).satisfies(&json!({ "score": 5 })));
544 assert!(!F::eq("score", json!(6)).satisfies(&json!({ "score": 5.0 })));
545 assert!(F::eq("tag", json!("a")).satisfies(&json!({ "tag": "a" })));
547 assert!(F::eq("tags", json!(["a", "b"])).satisfies(&json!({ "tags": ["a", "b"] })));
548 assert!(!F::eq("tags", json!(["a"])).satisfies(&json!({ "tags": ["a", "b"] })));
549 }
550
551 #[test]
552 fn ordering_compares_large_integers_exactly() {
553 let doc = json!({ "id": 9007199254740993_u64 }); assert!(F::gt("id", json!(9007199254740992_u64)).satisfies(&doc)); assert!(!F::gt("id", json!(9007199254740993_u64)).satisfies(&doc));
557 assert!(F::lt("id", json!(9007199254740994_u64)).satisfies(&doc));
558 }
559
560 #[test]
561 fn and_or_combine_leaf_filters() {
562 let doc = json!({ "category": "fruit", "price": 10 });
563 let both = F::eq("category", json!("fruit")).and(F::gt("price", json!(5)));
564 assert!(both.satisfies(&doc));
565
566 let missing_branch = F::eq("category", json!("fruit")).and(F::gt("price", json!(50)));
567 assert!(!missing_branch.satisfies(&doc));
568
569 let either = F::eq("category", json!("veg")).or(F::lt("price", json!(50)));
570 assert!(either.satisfies(&doc));
571 }
572
573 #[test]
574 fn try_interpret_converts_nested_leaf_values() {
575 let f: Filter<i64> =
576 Filter::Eq("a".into(), 1).and(Filter::Gt("b".into(), 2).or(Filter::Lt("c".into(), 3)));
577 let out: Filter<String> = f
578 .try_interpret(|v| Ok::<_, std::convert::Infallible>(v.to_string()))
579 .unwrap();
580 match out {
581 Filter::And(lhs, rhs) => {
582 assert!(matches!(*lhs, Filter::Eq(ref k, ref v) if k == "a" && v == "1"));
583 match *rhs {
584 Filter::Or(l, r) => {
585 assert!(matches!(*l, Filter::Gt(ref k, ref v) if k == "b" && v == "2"));
586 assert!(matches!(*r, Filter::Lt(ref k, ref v) if k == "c" && v == "3"));
587 }
588 other => panic!("expected Or, got {other:?}"),
589 }
590 }
591 other => panic!("expected And, got {other:?}"),
592 }
593 }
594
595 #[test]
596 fn try_interpret_propagates_conversion_errors() {
597 let f: Filter<i64> = Filter::Eq("a".into(), 1).and(Filter::Gt("b".into(), -2));
598 let out: Result<Filter<u64>, String> =
599 f.try_interpret(|v| u64::try_from(v).map_err(|e| e.to_string()));
600 assert!(out.is_err());
601 }
602}