summa_core/query/
boost.rs1use std::sync::Arc;
4
5use crate::segment::SegmentReader;
6use crate::{DocId, Score};
7
8use super::{CountFuture, Query, Scorer, ScorerFuture};
9
10#[derive(Clone)]
12pub struct BoostQuery {
13 pub inner: Arc<dyn Query>,
14 pub boost: f32,
15}
16
17impl std::fmt::Debug for BoostQuery {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 f.debug_struct("BoostQuery")
20 .field("boost", &self.boost)
21 .finish()
22 }
23}
24
25impl std::fmt::Display for BoostQuery {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{}^{}", self.inner, self.boost)
28 }
29}
30
31fn boosted_inner_options(boost: f32, options: super::ScorerOptions) -> super::ScorerOptions {
37 if boost == 1.0 {
38 options
39 } else if boost <= 0.0 {
40 options.for_required_clause()
41 } else {
42 options.without_threshold()
43 }
44}
45
46impl BoostQuery {
47 pub fn new(query: impl Query + 'static, boost: f32) -> Self {
48 Self {
49 inner: Arc::new(query),
50 boost,
51 }
52 }
53}
54
55impl Query for BoostQuery {
56 fn physical_text_field(&self, reader: &SegmentReader, complete: bool) -> Option<crate::Field> {
57 self.inner
58 .physical_text_field(reader, complete || self.boost <= 0.0)
59 }
60 fn candidate_query(&self) -> crate::Result<crate::query::CandidateQuery> {
61 self.inner.candidate_query()?.boosted(self.boost)
62 }
63
64 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
65 self.scorer_with_options(reader, limit, super::ScorerOptions::with_positions())
66 }
67
68 fn scorer_with_options<'a>(
69 &self,
70 reader: &'a SegmentReader,
71 limit: usize,
72 options: super::ScorerOptions,
73 ) -> ScorerFuture<'a> {
74 let inner = self.inner.clone();
75 let boost = self.boost;
76 Box::pin(async move {
77 if !boost.is_finite() {
78 return Err(crate::Error::Query(
79 "boost must be a finite number".to_string(),
80 ));
81 }
82 let inner_options = boosted_inner_options(boost, options);
83 let inner_scorer = inner
84 .scorer_with_options(reader, limit, inner_options)
85 .await?;
86 Ok(Box::new(BoostScorer {
87 inner: inner_scorer,
88 boost,
89 }) as Box<dyn Scorer + 'a>)
90 })
91 }
92
93 #[cfg(feature = "sync")]
94 fn scorer_sync<'a>(
95 &self,
96 reader: &'a SegmentReader,
97 limit: usize,
98 ) -> crate::Result<Box<dyn Scorer + 'a>> {
99 self.scorer_sync_with_options(reader, limit, super::ScorerOptions::with_positions())
100 }
101
102 #[cfg(feature = "sync")]
103 fn scorer_sync_with_options<'a>(
104 &self,
105 reader: &'a SegmentReader,
106 limit: usize,
107 options: super::ScorerOptions,
108 ) -> crate::Result<Box<dyn Scorer + 'a>> {
109 if !self.boost.is_finite() {
110 return Err(crate::Error::Query(
111 "boost must be a finite number".to_string(),
112 ));
113 }
114 let inner_options = boosted_inner_options(self.boost, options);
115 let inner_scorer = self
116 .inner
117 .scorer_sync_with_options(reader, limit, inner_options)?;
118 Ok(Box::new(BoostScorer {
119 inner: inner_scorer,
120 boost: self.boost,
121 }) as Box<dyn Scorer + 'a>)
122 }
123
124 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
125 let inner = self.inner.clone();
126 Box::pin(async move { inner.count_estimate(reader).await })
127 }
128
129 fn is_filter(&self) -> bool {
130 self.boost == 1.0 && self.inner.is_filter()
131 }
132
133 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
134 (self.boost == 1.0)
135 .then(|| self.inner.as_doc_predicate(reader))
136 .flatten()
137 }
138
139 fn text_terms(&self, out: &mut Vec<(crate::dsl::Field, Vec<u8>)>) {
140 self.inner.text_terms(out);
141 }
142
143 fn decompose(&self) -> super::QueryDecomposition {
144 match self.inner.decompose() {
145 super::QueryDecomposition::TextTerm(mut info) => {
148 info.weight *= self.boost;
149 super::QueryDecomposition::TextTerm(info)
150 }
151 other if self.boost == 1.0 => other,
152 _ => super::QueryDecomposition::Opaque,
153 }
154 }
155}
156
157struct BoostScorer<'a> {
158 inner: Box<dyn Scorer + 'a>,
159 boost: f32,
160}
161
162impl super::docset::DocSet for BoostScorer<'_> {
163 fn doc(&self) -> DocId {
164 self.inner.doc()
165 }
166
167 fn advance(&mut self) -> DocId {
168 self.inner.advance()
169 }
170
171 fn seek(&mut self, target: DocId) -> DocId {
172 self.inner.seek(target)
173 }
174
175 fn size_hint(&self) -> u32 {
176 self.inner.size_hint()
177 }
178}
179
180impl Scorer for BoostScorer<'_> {
181 fn score(&self) -> Score {
182 self.inner.score() * self.boost
183 }
184
185 fn matched_positions(&self) -> Option<super::MatchedPositions> {
186 let mut positions = self.inner.matched_positions()?;
187 for (_, scored_positions) in &mut positions {
188 for position in scored_positions {
189 position.score *= self.boost;
190 }
191 }
192 Some(positions)
193 }
194}