elasticsearch_dsl/search/rescoring/rescore_.rs
1use crate::util::ShouldSkip;
2use crate::{Query, ScoreMode};
3
4/// Rescoring can help to improve precision by reordering just the top (eg 100 - 500)
5/// documents returned by the [query](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#request-body-search-query)
6/// and [post_filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#post-filter)
7/// phases, using a secondary (usually more costly) algorithm, instead of applying the costly algorithm to all documents in the index.
8///
9/// A `rescore` request is executed on each shard before it returns its results to be sorted by the node handling the overall search request.
10///
11/// Currently the rescore API has only one implementation: the query rescorer, which uses a query to tweak the scoring.
12/// In the future, alternative rescorers may be made available, for example, a pair-wise rescorer.
13///
14/// To create a `rescore` query with simple `term` query:
15/// ```
16/// # use elasticsearch_dsl::rescoring::*;
17/// # use elasticsearch_dsl::queries::*;
18/// # use elasticsearch_dsl::queries::params::*;
19/// # let rescore =
20/// Rescore::new(Query::term("title", "test"));
21/// ```
22/// To create a `rescore` query with simple `term` query and optional fields:
23/// ```
24/// # use elasticsearch_dsl::rescoring::*;
25/// # use elasticsearch_dsl::queries::*;
26/// # use elasticsearch_dsl::queries::params::*;
27/// # let query =
28/// Rescore::new(Query::term("title", "test"))
29/// .rescore_query_weight(0.2)
30/// .window_size(100);
31/// ```
32/// <https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#rescore>
33#[derive(Debug, Clone, PartialEq, Serialize)]
34pub struct Rescore {
35 query: RescoreQuery,
36
37 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
38 window_size: Option<u64>,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize)]
42struct RescoreQuery {
43 rescore_query: Option<Query>,
44
45 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
46 rescore_query_weight: Option<f32>,
47
48 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
49 query_weight: Option<f32>,
50
51 #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
52 score_mode: Option<ScoreMode>,
53}
54
55impl Rescore {
56 /// Creates a new instance of [Rescore]
57 ///
58 /// - `query` - Second query which will be execute on top-k results returned by original query.
59 pub fn new<T>(query: T) -> Self
60 where
61 T: Into<Option<Query>>,
62 {
63 Self {
64 query: RescoreQuery {
65 rescore_query: query.into(),
66 rescore_query_weight: None,
67 query_weight: None,
68 score_mode: None,
69 },
70 window_size: None,
71 }
72 }
73
74 /// The number of docs which will be examined on each shard can be controlled by the `window_size` parameter, which defaults to 10.
75 pub fn window_size(mut self, window_size: u64) -> Self {
76 self.window_size = Some(window_size);
77 self
78 }
79
80 /// The relative importance of the rescore query can be controlled with the `rescore_query_weight` respectively. Both default to 1.
81 pub fn rescore_query_weight<T>(mut self, rescore_query_weight: T) -> Self
82 where
83 T: num_traits::AsPrimitive<f32>,
84 {
85 self.query.rescore_query_weight = Some(rescore_query_weight.as_());
86 self
87 }
88
89 /// The relative importance of the original query can be controlled with the `query_weight` respectively. Both default to 1.
90 pub fn query_weight<T>(mut self, query_weight: T) -> Self
91 where
92 T: num_traits::AsPrimitive<f32>,
93 {
94 self.query.query_weight = Some(query_weight.as_());
95 self
96 }
97
98 /// The way the scores are combined can be controlled with the
99 pub fn score_mode(mut self, score_mode: ScoreMode) -> Self {
100 self.query.score_mode = Some(score_mode);
101 self
102 }
103}
104
105impl ShouldSkip for Rescore {
106 fn should_skip(&self) -> bool {
107 self.query
108 .rescore_query
109 .as_ref()
110 .is_none_or(ShouldSkip::should_skip)
111 }
112}
113
114impl IntoIterator for Rescore {
115 type Item = Self;
116
117 type IntoIter = std::option::IntoIter<Self::Item>;
118
119 fn into_iter(self) -> Self::IntoIter {
120 Some(self).into_iter()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::util::assert_serialize_rescore;
128
129 #[test]
130 fn should_skip() {
131 assert!(Rescore::new(Query::range("field")).should_skip());
132 assert!(!Rescore::new(Query::range("field").gte(1)).should_skip());
133 }
134
135 #[test]
136 fn serialization() {
137 assert_serialize_rescore(
138 Rescore::new(Query::term("title", "test")),
139 json!({
140 "query": {
141 "rescore_query": {
142 "term": {
143 "title": {
144 "value": "test"
145 }
146 }
147 }
148 }
149 }),
150 );
151
152 assert_serialize_rescore(
153 Rescore::new(Query::term("title", "test"))
154 .rescore_query_weight(0.2)
155 .query_weight(0.5)
156 .window_size(100)
157 .score_mode(ScoreMode::Max),
158 json!({
159 "window_size": 100,
160 "query": {
161 "query_weight": 0.5,
162 "rescore_query_weight": 0.2,
163 "score_mode": "max",
164 "rescore_query": {
165 "term": {
166 "title": {
167 "value": "test"
168 }
169 }
170 }
171 }
172 }),
173 );
174 }
175}