Skip to main content

couchbase_core/searchx/
queries.rs

1/*
2 *
3 *  * Copyright (c) 2025 Couchbase, Inc.
4 *  *
5 *  * Licensed under the Apache License, Version 2.0 (the "License");
6 *  * you may not use this file except in compliance with the License.
7 *  * You may obtain a copy of the License at
8 *  *
9 *  *    http://www.apache.org/licenses/LICENSE-2.0
10 *  *
11 *  * Unless required by applicable law or agreed to in writing, software
12 *  * distributed under the License is distributed on an "AS IS" BASIS,
13 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  * See the License for the specific language governing permissions and
15 *  * limitations under the License.
16 *
17 */
18
19use crate::searchx::query_options::Location;
20use serde::Serialize;
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
23#[serde(rename_all = "lowercase")]
24#[non_exhaustive]
25pub enum MatchOperator {
26    Or,
27    And,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize)]
31#[non_exhaustive]
32pub struct MatchQuery {
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub analyzer: Option<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub boost: Option<f32>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub field: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub fuzziness: Option<u64>,
41    #[serde(rename = "match")]
42    pub match_input: String,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub operator: Option<MatchOperator>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub prefix_length: Option<u64>,
47}
48
49impl MatchQuery {
50    pub fn new(match_input: impl Into<String>) -> Self {
51        Self {
52            analyzer: None,
53            boost: None,
54            field: None,
55            fuzziness: None,
56            match_input: match_input.into(),
57            operator: None,
58            prefix_length: None,
59        }
60    }
61
62    pub fn analyzer(mut self, analyzer: impl Into<Option<String>>) -> Self {
63        self.analyzer = analyzer.into();
64        self
65    }
66
67    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
68        self.boost = boost.into();
69        self
70    }
71
72    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
73        self.field = field.into();
74        self
75    }
76
77    pub fn fuzziness(mut self, fuzziness: impl Into<Option<u64>>) -> Self {
78        self.fuzziness = fuzziness.into();
79        self
80    }
81
82    pub fn operator(mut self, operator: impl Into<Option<MatchOperator>>) -> Self {
83        self.operator = operator.into();
84        self
85    }
86
87    pub fn prefix_length(mut self, prefix_length: impl Into<Option<u64>>) -> Self {
88        self.prefix_length = prefix_length.into();
89        self
90    }
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize)]
94#[non_exhaustive]
95pub struct MatchPhraseQuery {
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub analyzer: Option<String>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub boost: Option<f32>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub field: Option<String>,
102    pub match_phrase: String,
103}
104
105impl MatchPhraseQuery {
106    pub fn new(match_phrase: impl Into<String>) -> Self {
107        Self {
108            analyzer: None,
109            boost: None,
110            field: None,
111            match_phrase: match_phrase.into(),
112        }
113    }
114
115    pub fn analyzer(mut self, analyzer: impl Into<Option<String>>) -> Self {
116        self.analyzer = analyzer.into();
117        self
118    }
119
120    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
121        self.boost = boost.into();
122        self
123    }
124
125    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
126        self.field = field.into();
127        self
128    }
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize)]
132#[non_exhaustive]
133pub struct RegexpQuery {
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub boost: Option<f32>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub field: Option<String>,
138    pub regexp: String,
139}
140
141impl RegexpQuery {
142    pub fn new(regexp: impl Into<String>) -> Self {
143        Self {
144            boost: None,
145            field: None,
146            regexp: regexp.into(),
147        }
148    }
149
150    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
151        self.boost = boost.into();
152        self
153    }
154
155    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
156        self.field = field.into();
157        self
158    }
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize)]
162#[non_exhaustive]
163pub struct QueryStringQuery {
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub boost: Option<f32>,
166    pub query: String,
167}
168
169impl QueryStringQuery {
170    pub fn new(query: impl Into<String>) -> Self {
171        Self {
172            boost: None,
173            query: query.into(),
174        }
175    }
176
177    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
178        self.boost = boost.into();
179        self
180    }
181}
182
183#[derive(Debug, Default, Clone, PartialEq, Serialize)]
184#[non_exhaustive]
185pub struct NumericRangeQuery {
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub boost: Option<f32>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub field: Option<String>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub inclusive_min: Option<bool>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub inclusive_max: Option<bool>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub min: Option<f32>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub max: Option<f32>,
198}
199
200impl NumericRangeQuery {
201    pub fn new() -> Self {
202        Default::default()
203    }
204
205    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
206        self.boost = boost.into();
207        self
208    }
209
210    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
211        self.field = field.into();
212        self
213    }
214
215    pub fn inclusive_min(mut self, inclusive_min: impl Into<Option<bool>>) -> Self {
216        self.inclusive_min = inclusive_min.into();
217        self
218    }
219
220    pub fn inclusive_max(mut self, inclusive_max: impl Into<Option<bool>>) -> Self {
221        self.inclusive_max = inclusive_max.into();
222        self
223    }
224
225    pub fn min(mut self, min: impl Into<Option<f32>>) -> Self {
226        self.min = min.into();
227        self
228    }
229
230    pub fn max(mut self, max: impl Into<Option<f32>>) -> Self {
231        self.max = max.into();
232        self
233    }
234}
235
236#[derive(Debug, Default, Clone, PartialEq, Serialize)]
237#[non_exhaustive]
238pub struct DateRangeQuery {
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub boost: Option<f32>,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub field: Option<String>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub datetime_parser: Option<String>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub end: Option<String>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub inclusive_start: Option<bool>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub inclusive_end: Option<bool>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub start: Option<String>,
253}
254
255impl DateRangeQuery {
256    pub fn new() -> Self {
257        Default::default()
258    }
259
260    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
261        self.boost = boost.into();
262        self
263    }
264
265    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
266        self.field = field.into();
267        self
268    }
269
270    pub fn datetime_parser(mut self, datetime_parser: impl Into<Option<String>>) -> Self {
271        self.datetime_parser = datetime_parser.into();
272        self
273    }
274
275    pub fn end(mut self, end: impl Into<Option<String>>) -> Self {
276        self.end = end.into();
277        self
278    }
279
280    pub fn inclusive_start(mut self, inclusive_start: impl Into<Option<bool>>) -> Self {
281        self.inclusive_start = inclusive_start.into();
282        self
283    }
284
285    pub fn inclusive_end(mut self, inclusive_end: impl Into<Option<bool>>) -> Self {
286        self.inclusive_end = inclusive_end.into();
287        self
288    }
289
290    pub fn start(mut self, start: impl Into<Option<String>>) -> Self {
291        self.start = start.into();
292        self
293    }
294}
295
296#[derive(Debug, Default, Clone, PartialEq, Serialize)]
297#[non_exhaustive]
298pub struct TermRangeQuery {
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub boost: Option<f32>,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub field: Option<String>,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub inclusive_min: Option<bool>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub inclusive_max: Option<bool>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub max: Option<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub min: Option<String>,
311}
312
313impl TermRangeQuery {
314    pub fn new() -> Self {
315        Default::default()
316    }
317
318    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
319        self.boost = boost.into();
320        self
321    }
322
323    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
324        self.field = field.into();
325        self
326    }
327
328    pub fn inclusive_min(mut self, inclusive_min: impl Into<Option<bool>>) -> Self {
329        self.inclusive_min = inclusive_min.into();
330        self
331    }
332
333    pub fn inclusive_max(mut self, inclusive_max: impl Into<Option<bool>>) -> Self {
334        self.inclusive_max = inclusive_max.into();
335        self
336    }
337
338    pub fn max(mut self, max: impl Into<Option<String>>) -> Self {
339        self.max = max.into();
340        self
341    }
342
343    pub fn min(mut self, min: impl Into<Option<String>>) -> Self {
344        self.min = min.into();
345        self
346    }
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize)]
350#[non_exhaustive]
351pub struct ConjunctionQuery {
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub boost: Option<f32>,
354    pub conjuncts: Vec<Query>,
355}
356
357impl ConjunctionQuery {
358    pub fn new(conjuncts: Vec<Query>) -> Self {
359        Self {
360            boost: None,
361            conjuncts,
362        }
363    }
364
365    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
366        self.boost = boost.into();
367        self
368    }
369}
370
371#[derive(Debug, Clone, PartialEq, Serialize)]
372#[non_exhaustive]
373pub struct DisjunctionQuery {
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub boost: Option<f32>,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub min: Option<u32>,
378    pub disjuncts: Vec<Query>,
379}
380
381impl DisjunctionQuery {
382    pub fn new(disjuncts: Vec<Query>) -> Self {
383        Self {
384            boost: None,
385            disjuncts,
386            min: None,
387        }
388    }
389
390    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
391        self.boost = boost.into();
392        self
393    }
394
395    pub fn min(mut self, min: impl Into<Option<u32>>) -> Self {
396        self.min = min.into();
397        self
398    }
399}
400
401#[derive(Debug, Default, Clone, PartialEq, Serialize)]
402#[non_exhaustive]
403pub struct BooleanQuery {
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub boost: Option<f32>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub must: Option<ConjunctionQuery>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub must_not: Option<DisjunctionQuery>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub should: Option<DisjunctionQuery>,
412}
413
414impl BooleanQuery {
415    pub fn new() -> Self {
416        Default::default()
417    }
418
419    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
420        self.boost = boost.into();
421        self
422    }
423
424    pub fn must(mut self, must: impl Into<Option<ConjunctionQuery>>) -> Self {
425        self.must = must.into();
426        self
427    }
428
429    pub fn must_not(mut self, must_not: impl Into<Option<DisjunctionQuery>>) -> Self {
430        self.must_not = must_not.into();
431        self
432    }
433
434    pub fn should(mut self, should: impl Into<Option<DisjunctionQuery>>) -> Self {
435        self.should = should.into();
436        self
437    }
438}
439
440#[derive(Debug, Clone, PartialEq, Serialize)]
441#[non_exhaustive]
442pub struct WildcardQuery {
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub boost: Option<f32>,
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub field: Option<String>,
447    pub wildcard: String,
448}
449
450impl WildcardQuery {
451    pub fn new(wildcard: impl Into<String>) -> Self {
452        Self {
453            boost: None,
454            field: None,
455            wildcard: wildcard.into(),
456        }
457    }
458
459    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
460        self.boost = boost.into();
461        self
462    }
463
464    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
465        self.field = field.into();
466        self
467    }
468}
469
470#[derive(Debug, Clone, PartialEq, Serialize)]
471#[non_exhaustive]
472pub struct DocIDQuery {
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub boost: Option<f32>,
475    pub ids: Vec<String>,
476}
477
478impl DocIDQuery {
479    pub fn new(ids: Vec<String>) -> Self {
480        Self { boost: None, ids }
481    }
482
483    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
484        self.boost = boost.into();
485        self
486    }
487}
488
489#[derive(Debug, Clone, PartialEq, Serialize)]
490#[non_exhaustive]
491pub struct BooleanFieldQuery {
492    #[serde(rename = "bool")]
493    pub bool_value: bool,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub boost: Option<f32>,
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub field: Option<String>,
498}
499
500impl BooleanFieldQuery {
501    pub fn new(bool_value: bool) -> Self {
502        Self {
503            bool_value,
504            boost: None,
505            field: None,
506        }
507    }
508
509    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
510        self.boost = boost.into();
511        self
512    }
513
514    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
515        self.field = field.into();
516        self
517    }
518}
519
520#[derive(Debug, Clone, PartialEq, Serialize)]
521#[non_exhaustive]
522pub struct TermQuery {
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub boost: Option<f32>,
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub field: Option<String>,
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub fuzziness: Option<u32>,
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub prefix_length: Option<u32>,
531    pub term: String,
532}
533
534impl TermQuery {
535    pub fn new(term: impl Into<String>) -> Self {
536        Self {
537            boost: None,
538            field: None,
539            fuzziness: None,
540            prefix_length: None,
541            term: term.into(),
542        }
543    }
544
545    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
546        self.boost = boost.into();
547        self
548    }
549
550    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
551        self.field = field.into();
552        self
553    }
554
555    pub fn fuzziness(mut self, fuzziness: impl Into<Option<u32>>) -> Self {
556        self.fuzziness = fuzziness.into();
557        self
558    }
559
560    pub fn prefix_length(mut self, prefix_length: impl Into<Option<u32>>) -> Self {
561        self.prefix_length = prefix_length.into();
562        self
563    }
564}
565
566#[derive(Debug, Clone, PartialEq, Serialize)]
567#[non_exhaustive]
568pub struct PhraseQuery {
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub boost: Option<f32>,
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub field: Option<String>,
573    pub terms: Vec<String>,
574}
575
576impl PhraseQuery {
577    pub fn new(terms: Vec<String>) -> Self {
578        Self {
579            boost: None,
580            field: None,
581            terms,
582        }
583    }
584
585    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
586        self.boost = boost.into();
587        self
588    }
589
590    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
591        self.field = field.into();
592        self
593    }
594}
595
596#[derive(Debug, Clone, PartialEq, Serialize)]
597#[non_exhaustive]
598pub struct PrefixQuery {
599    #[serde(skip_serializing_if = "Option::is_none")]
600    pub boost: Option<f32>,
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub field: Option<String>,
603    pub prefix: String,
604}
605
606impl PrefixQuery {
607    pub fn new(prefix: impl Into<String>) -> Self {
608        Self {
609            boost: None,
610            field: None,
611            prefix: prefix.into(),
612        }
613    }
614
615    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
616        self.boost = boost.into();
617        self
618    }
619
620    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
621        self.field = field.into();
622        self
623    }
624}
625
626#[derive(Debug, Default, Clone, PartialEq)]
627#[non_exhaustive]
628pub struct MatchAllQuery {}
629
630impl MatchAllQuery {
631    pub fn new() -> Self {
632        Default::default()
633    }
634}
635
636impl Serialize for MatchAllQuery {
637    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
638    where
639        S: serde::Serializer,
640    {
641        use serde::ser::SerializeMap;
642        let mut map = serializer.serialize_map(Some(1))?;
643        map.serialize_entry("match_all", &serde_json::Value::Null)?;
644        map.end()
645    }
646}
647
648#[derive(Debug, Default, Clone, PartialEq)]
649#[non_exhaustive]
650pub struct MatchNoneQuery {}
651
652impl MatchNoneQuery {
653    pub fn new() -> Self {
654        Default::default()
655    }
656}
657
658impl Serialize for MatchNoneQuery {
659    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
660    where
661        S: serde::Serializer,
662    {
663        use serde::ser::SerializeMap;
664        let mut map = serializer.serialize_map(Some(1))?;
665        map.serialize_entry("match_none", &serde_json::Value::Null)?;
666        map.end()
667    }
668}
669
670#[derive(Debug, Clone, PartialEq, Serialize)]
671#[non_exhaustive]
672pub struct GeoDistanceQuery {
673    #[serde(skip_serializing_if = "Option::is_none")]
674    pub boost: Option<f32>,
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub field: Option<String>,
677    pub location: Location,
678    pub distance: String,
679}
680
681impl GeoDistanceQuery {
682    pub fn new(distance: impl Into<String>, location: Location) -> Self {
683        Self {
684            distance: distance.into(),
685            boost: None,
686            field: None,
687            location,
688        }
689    }
690
691    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
692        self.boost = boost.into();
693        self
694    }
695
696    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
697        self.field = field.into();
698        self
699    }
700}
701
702#[derive(Debug, Clone, PartialEq, Serialize)]
703#[non_exhaustive]
704pub struct GeoBoundingBoxQuery {
705    #[serde(skip_serializing_if = "Option::is_none")]
706    pub boost: Option<f32>,
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub field: Option<String>,
709    pub top_left: Location,
710    pub bottom_right: Location,
711}
712
713impl GeoBoundingBoxQuery {
714    pub fn new(top_left: impl Into<Location>, bottom_right: impl Into<Location>) -> Self {
715        Self {
716            bottom_right: bottom_right.into(),
717            boost: None,
718            field: None,
719            top_left: top_left.into(),
720        }
721    }
722
723    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
724        self.boost = boost.into();
725        self
726    }
727
728    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
729        self.field = field.into();
730        self
731    }
732}
733
734#[derive(Debug, Clone, PartialEq, Serialize)]
735#[non_exhaustive]
736pub struct GeoPolygonQuery {
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub boost: Option<f32>,
739    #[serde(skip_serializing_if = "Option::is_none")]
740    pub field: Option<String>,
741    pub polygon_points: Vec<Location>,
742}
743
744impl GeoPolygonQuery {
745    pub fn new(polygon_points: Vec<Location>) -> Self {
746        Self {
747            boost: None,
748            field: None,
749            polygon_points,
750        }
751    }
752
753    pub fn boost(mut self, boost: impl Into<Option<f32>>) -> Self {
754        self.boost = boost.into();
755        self
756    }
757
758    pub fn field(mut self, field: impl Into<Option<String>>) -> Self {
759        self.field = field.into();
760        self
761    }
762}
763
764#[derive(Debug, Clone, PartialEq, Serialize)]
765#[serde(untagged)]
766#[non_exhaustive]
767pub enum Query {
768    Match(MatchQuery),
769    MatchPhrase(MatchPhraseQuery),
770    Regexp(RegexpQuery),
771    QueryString(QueryStringQuery),
772    NumericRange(NumericRangeQuery),
773    DateRange(DateRangeQuery),
774    TermRange(TermRangeQuery),
775    Conjunction(ConjunctionQuery),
776    Disjunction(DisjunctionQuery),
777    Boolean(BooleanQuery),
778    Wildcard(WildcardQuery),
779    DocID(DocIDQuery),
780    BooleanField(BooleanFieldQuery),
781    Term(TermQuery),
782    Phrase(PhraseQuery),
783    Prefix(PrefixQuery),
784    MatchAll(MatchAllQuery),
785    MatchNone(MatchNoneQuery),
786    GeoDistance(GeoDistanceQuery),
787    GeoBoundingBox(GeoBoundingBoxQuery),
788    GeoPolygon(GeoPolygonQuery),
789}