Skip to main content

opensearch_dsl/search/
request.rs

1//! Allows you to execute a search query and get back search hits that match the
2//! query.
3use crate::{search::*, util::*, Map, Set};
4
5/// Returns search hits that match the query defined in the request.
6///
7/// <https://www.elastic.co/guide/en/opensearch/reference/current/search-search.html>
8#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)]
9pub struct Search {
10    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
11    runtime_mappings: Map<String, RuntimeMapping>,
12
13    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
14    indices_boost: Vec<KeyValuePair<String, f32>>,
15
16    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
17    min_score: Option<f32>,
18
19    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
20    _source: Option<SourceFilter>,
21
22    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
23    stats: Vec<String>,
24
25    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
26    from: Option<u64>,
27
28    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
29    size: Option<u64>,
30
31    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
32    query: Option<Query>,
33
34    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
35    sort: SortCollection,
36
37    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
38    aggs: Aggregations,
39
40    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
41    track_total_hits: Option<TrackTotalHits>,
42
43    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
44    highlight: Option<Highlight>,
45
46    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
47    rescore: RescoreCollection,
48
49    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
50    suggest: Map<String, Suggester>,
51
52    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
53    stored_fields: StoredFields,
54
55    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
56    docvalue_fields: Set<String>,
57
58    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
59    script_fields: Map<String, ScriptField>,
60
61    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
62    post_filter: Option<Query>,
63
64    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
65    pit: Option<PointInTime>,
66
67    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
68    search_after: Terms,
69
70    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
71    timeout: Option<Time>,
72
73    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
74    knn: Vec<Knn>,
75
76    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
77    collapse: Option<Collapse>,
78
79    #[serde(flatten)]
80    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
81    extra: Map<String, serde_json::Value>,
82
83    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
84    track_scores: Option<bool>,
85}
86
87impl Search {
88    add_aggregate!();
89
90    /// Creates a default search instance
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Add runtime mapping to the search request
96    pub fn runtime_mapping<S>(mut self, name: S, mapping: RuntimeMapping) -> Self
97    where
98        S: ToString,
99    {
100        let _ = self.runtime_mappings.insert(name.to_string(), mapping);
101        self
102    }
103
104    /// Add script fields to the search request
105    pub fn script_fields<S, T>(mut self, name: S, script: T) -> Self
106    where
107        S: ToString,
108        T: Into<ScriptField>,
109    {
110        let _ = self.script_fields.insert(name.to_string(), script.into());
111        self
112    }
113
114    /// Allows to configure different boost level per index when searching
115    /// across more than one indices. This is very handy when hits coming from
116    /// one index matter more than hits coming from another index (think social
117    /// graph where each user has an index).
118    pub fn indices_boost<T, U>(mut self, field: T, boost: U) -> Self
119    where
120        T: ToString,
121        U: num_traits::AsPrimitive<f32>,
122    {
123        self.indices_boost
124            .push(KeyValuePair::new(field.to_string(), boost.as_()));
125        self
126    }
127
128    /// Exclude documents which have a `_score` less than the minimum specified
129    /// in `min_score`
130    ///
131    /// Note, most times, this does not make much sense, but is provided for
132    /// advanced use cases
133    pub fn min_score<F>(mut self, min_score: F) -> Self
134    where
135        F: Into<f32>,
136    {
137        self.min_score = Some(min_score.into());
138        self
139    }
140
141    /// Indicates which source fields are returned for matching documents
142    pub fn source<S>(mut self, source: S) -> Self
143    where
144        S: Into<SourceFilter>,
145    {
146        self._source = Some(source.into());
147        self
148    }
149
150    /// Specific `tag` of the request for logging and statistical purposes.
151    pub fn stats<S>(mut self, stats: S) -> Self
152    where
153        S: ToString,
154    {
155        self.stats.push(stats.to_string());
156        self
157    }
158
159    /// Starting document offset.
160    ///
161    /// Defaults to `0`.
162    pub fn from(mut self, from: u64) -> Self {
163        self.from = Some(from);
164        self
165    }
166
167    /// The number of hits to return.
168    ///
169    /// Defaults to `10`.
170    pub fn size(mut self, size: u64) -> Self {
171        self.size = Some(size);
172        self
173    }
174
175    /// Defines the search definition using the
176    /// [Query DSL](https://www.elastic.co/guide/en/opensearch/reference/current/query-dsl.html).
177    pub fn query<Q>(mut self, query: Q) -> Self
178    where
179        Q: Into<Query>,
180    {
181        self.query = Some(query.into());
182        self
183    }
184
185    /// When you use the `post_filter` parameter to filter search results, the
186    /// search hits are filtered after the aggregations are calculated. A post
187    /// filter has no impact on the aggregation results.
188    pub fn post_filter<Q>(mut self, post_filter: Q) -> Self
189    where
190        Q: Into<Query>,
191    {
192        self.post_filter = Some(post_filter.into());
193        self
194    }
195
196    /// A collection of sorting fields
197    pub fn sort<T>(mut self, sort: T) -> Self
198    where
199        T: IntoIterator,
200        T::Item: Into<Sort>,
201    {
202        self.sort.extend(sort);
203        self
204    }
205
206    /// Track total hits
207    pub fn track_total_hits<T>(mut self, track_total_hits: T) -> Self
208    where
209        T: Into<TrackTotalHits>,
210    {
211        self.track_total_hits = Some(track_total_hits.into());
212        self
213    }
214
215    /// If true, calculate and return document scores, even if the scores are not
216    /// used for sorting.
217    pub fn track_scores(mut self, enabled: bool) -> Self {
218        self.track_scores = Some(enabled);
219        self
220    }
221
222    /// Highlight
223    pub fn highlight<H>(mut self, highlight: H) -> Self
224    where
225        H: Into<Highlight>,
226    {
227        self.highlight = Some(highlight.into());
228        self
229    }
230
231    /// Rescore
232    pub fn rescore<T>(mut self, rescore: T) -> Self
233    where
234        T: IntoIterator,
235        T::Item: Into<Rescore>,
236    {
237        self.rescore.extend(rescore);
238        self
239    }
240
241    /// Suggest
242    pub fn suggest<T, U>(mut self, name: T, suggester: U) -> Self
243    where
244        T: ToString,
245        U: Into<Suggester>,
246    {
247        let _ = self.suggest.insert(name.to_string(), suggester.into());
248        self
249    }
250
251    /// A collection of stored fields
252    pub fn stored_fields<T>(mut self, stored_fields: T) -> Self
253    where
254        T: Into<StoredFields>,
255    {
256        self.stored_fields = stored_fields.into();
257        self
258    }
259
260    /// A collection of docvalue fields
261    pub fn docvalue_fields<T>(mut self, docvalue_fields: T) -> Self
262    where
263        T: IntoIterator,
264        T::Item: ToString,
265    {
266        self.docvalue_fields
267            .extend(docvalue_fields.into_iter().map(|x| x.to_string()));
268        self
269    }
270
271    /// Point in time
272    pub fn pit(mut self, pit: PointInTime) -> Self {
273        self.pit = Some(pit);
274        self
275    }
276
277    /// Search after a set of sort values.
278    pub fn search_after<T>(mut self, sort_values: T) -> Self
279    where
280        T: Into<Terms>,
281    {
282        self.search_after = sort_values.into();
283        self
284    }
285
286    /// parameter to specify a duration you’d like to wait on each shard to
287    /// complete.
288    ///
289    /// <https://www.elastic.co/guide/en/opensearch/reference/8.9/search-your-data.html#search-timeout>
290    pub fn timeout<T>(mut self, timeout: T) -> Self
291    where
292        T: Into<Time>,
293    {
294        self.timeout = Some(timeout.into());
295        self
296    }
297
298    /// Defines the kNN query to run.
299    ///
300    /// <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-api-knn>
301    pub fn knn(mut self, knn: Knn) -> Self {
302        self.knn.push(knn);
303        self
304    }
305
306    /// Parameter to specify collapsing results on some field
307    ///
308    /// <https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html>
309    pub fn collapse<C>(mut self, collapse: C) -> Self
310    where
311        C: Into<Collapse>,
312    {
313        self.collapse = Some(collapse.into());
314        self
315    }
316
317    /// Extra fields for something not yet supported.
318    ///
319    /// ```
320    /// # use elasticsearch_dsl::Search;
321    /// # use serde_json::json;
322    /// # let search =
323    /// Search::new()
324    ///     .size(10)
325    ///     .extra([
326    ///         (
327    ///             "knn".to_owned(),
328    ///             json!({ "field": "abc" }),
329    ///         ),
330    ///         (
331    ///             "terminate_after".to_owned(),
332    ///             json!(42)
333    ///         ),
334    ///     ].into());
335    /// ```
336    pub fn extra(mut self, extra: Map<String, serde_json::Value>) -> Self {
337        self.extra = extra;
338        self
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn serializes_to_empty_object_by_default() {
348        assert_serialize(Search::new(), json!({}));
349        assert_serialize(Search::default(), json!({}));
350    }
351
352    #[test]
353    fn serializes_extra_fields() {
354        assert_serialize(
355            Search::new().size(10).track_scores(false).extra(
356                [
357                    ("knn".to_owned(), json!({ "field": "abc" })),
358                    ("terminate_after".to_owned(), json!(42)),
359                ]
360                .into(),
361            ),
362            json!({
363                "size": 10,
364                "track_scores": false,
365                "knn": { "field": "abc" },
366                "terminate_after": 42,
367            }),
368        );
369    }
370}