1use std::sync::Arc;
4
5use crate::segment::SegmentReader;
6use crate::structures::TERMINATED;
7use crate::{DocId, Score};
8
9use super::{
10 CountFuture, GlobalStats, MaxScoreExecutor, Query, ScoredDoc, Scorer, ScorerFuture,
11 SparseTermQueryInfo,
12};
13
14#[derive(Default, Clone)]
19pub struct BooleanQuery {
20 pub must: Vec<Arc<dyn Query>>,
21 pub should: Vec<Arc<dyn Query>>,
22 pub must_not: Vec<Arc<dyn Query>>,
23 global_stats: Option<Arc<GlobalStats>>,
25}
26
27impl std::fmt::Debug for BooleanQuery {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("BooleanQuery")
30 .field("must_count", &self.must.len())
31 .field("should_count", &self.should.len())
32 .field("must_not_count", &self.must_not.len())
33 .field("has_global_stats", &self.global_stats.is_some())
34 .finish()
35 }
36}
37
38impl std::fmt::Display for BooleanQuery {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(f, "Boolean(")?;
41 let mut first = true;
42 for q in &self.must {
43 if !first {
44 write!(f, " ")?;
45 }
46 write!(f, "+{}", q)?;
47 first = false;
48 }
49 for q in &self.should {
50 if !first {
51 write!(f, " ")?;
52 }
53 write!(f, "{}", q)?;
54 first = false;
55 }
56 for q in &self.must_not {
57 if !first {
58 write!(f, " ")?;
59 }
60 write!(f, "-{}", q)?;
61 first = false;
62 }
63 write!(f, ")")
64 }
65}
66
67impl BooleanQuery {
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn must(mut self, query: impl Query + 'static) -> Self {
73 self.must.push(Arc::new(query));
74 self
75 }
76
77 pub fn should(mut self, query: impl Query + 'static) -> Self {
78 self.should.push(Arc::new(query));
79 self
80 }
81
82 pub fn must_not(mut self, query: impl Query + 'static) -> Self {
83 self.must_not.push(Arc::new(query));
84 self
85 }
86
87 pub fn with_global_stats(mut self, stats: Arc<GlobalStats>) -> Self {
89 self.global_stats = Some(stats);
90 self
91 }
92}
93
94fn compute_idf(
96 posting_list: &crate::structures::BlockPostingList,
97 field: crate::Field,
98 term: &[u8],
99 num_docs: f32,
100 global_stats: Option<&Arc<GlobalStats>>,
101) -> f32 {
102 if let Some(stats) = global_stats {
103 let global_idf = stats.text_idf(field, &String::from_utf8_lossy(term));
104 if global_idf > 0.0 {
105 return global_idf;
106 }
107 }
108 let doc_freq = posting_list.doc_count() as f32;
109 super::bm25_idf(doc_freq, num_docs)
110}
111
112fn prepare_text_maxscore(
115 should: &[Arc<dyn Query>],
116 reader: &SegmentReader,
117 global_stats: Option<&Arc<GlobalStats>>,
118) -> Option<(Vec<super::TermQueryInfo>, crate::Field, f32, f32)> {
119 let infos: Vec<_> = should
120 .iter()
121 .filter_map(|q| q.as_term_query_info())
122 .collect();
123 if infos.len() != should.len() {
124 return None;
125 }
126 let field = infos[0].field;
127 if !infos.iter().all(|t| t.field == field) {
128 return None;
129 }
130 let avg_field_len = global_stats
131 .map(|s| s.avg_field_len(field))
132 .unwrap_or_else(|| reader.avg_field_len(field));
133 let num_docs = reader.num_docs() as f32;
134 Some((infos, field, avg_field_len, num_docs))
135}
136
137fn finish_text_maxscore<'a>(
139 posting_lists: Vec<(crate::structures::BlockPostingList, f32)>,
140 avg_field_len: f32,
141 limit: usize,
142) -> crate::Result<Box<dyn Scorer + 'a>> {
143 if posting_lists.is_empty() {
144 return Ok(Box::new(EmptyScorer) as Box<dyn Scorer + 'a>);
145 }
146 let results = MaxScoreExecutor::text(posting_lists, avg_field_len, limit).execute_sync()?;
147 Ok(Box::new(TopKResultScorer::new(results)) as Box<dyn Scorer + 'a>)
148}
149
150async fn try_maxscore_scorer<'a>(
152 should: &[Arc<dyn Query>],
153 reader: &'a SegmentReader,
154 limit: usize,
155 global_stats: Option<&Arc<GlobalStats>>,
156) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
157 let (mut infos, _field, avg_field_len, num_docs) =
158 match prepare_text_maxscore(should, reader, global_stats) {
159 Some(v) => v,
160 None => return Ok(None),
161 };
162 let mut posting_lists = Vec::with_capacity(infos.len());
163 for info in infos.drain(..) {
164 if let Some(pl) = reader.get_postings(info.field, &info.term).await? {
165 let idf = compute_idf(&pl, info.field, &info.term, num_docs, global_stats);
166 posting_lists.push((pl, idf));
167 }
168 }
169 Ok(Some(finish_text_maxscore(
170 posting_lists,
171 avg_field_len,
172 limit,
173 )?))
174}
175
176#[cfg(feature = "sync")]
178fn try_maxscore_scorer_sync<'a>(
179 should: &[Arc<dyn Query>],
180 reader: &'a SegmentReader,
181 limit: usize,
182 global_stats: Option<&Arc<GlobalStats>>,
183) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
184 let (mut infos, _field, avg_field_len, num_docs) =
185 match prepare_text_maxscore(should, reader, global_stats) {
186 Some(v) => v,
187 None => return Ok(None),
188 };
189 let mut posting_lists = Vec::with_capacity(infos.len());
190 for info in infos.drain(..) {
191 if let Some(pl) = reader.get_postings_sync(info.field, &info.term)? {
192 let idf = compute_idf(&pl, info.field, &info.term, num_docs, global_stats);
193 posting_lists.push((pl, idf));
194 }
195 }
196 Ok(Some(finish_text_maxscore(
197 posting_lists,
198 avg_field_len,
199 limit,
200 )?))
201}
202
203struct PerFieldGrouping {
205 multi_term_groups: Vec<(crate::Field, f32, Vec<super::TermQueryInfo>)>,
207 fallback_indices: Vec<usize>,
209 per_field_limit: usize,
211 num_docs: f32,
212}
213
214fn prepare_per_field_grouping(
217 should: &[Arc<dyn Query>],
218 reader: &SegmentReader,
219 limit: usize,
220 global_stats: Option<&Arc<GlobalStats>>,
221) -> Option<PerFieldGrouping> {
222 let mut field_groups: rustc_hash::FxHashMap<crate::Field, Vec<(usize, super::TermQueryInfo)>> =
223 rustc_hash::FxHashMap::default();
224 let mut non_term_indices: Vec<usize> = Vec::new();
225
226 for (i, q) in should.iter().enumerate() {
227 if let Some(info) = q.as_term_query_info() {
228 field_groups.entry(info.field).or_default().push((i, info));
229 } else {
230 non_term_indices.push(i);
231 }
232 }
233
234 if !field_groups.values().any(|g| g.len() >= 2) {
235 return None;
236 }
237
238 let num_groups = field_groups.len() + non_term_indices.len();
239 let per_field_limit = limit * num_groups;
240 let num_docs = reader.num_docs() as f32;
241
242 let mut multi_term_groups = Vec::new();
243 let mut fallback_indices = non_term_indices;
244
245 for group in field_groups.into_values() {
246 if group.len() >= 2 {
247 let field = group[0].1.field;
248 let avg_field_len = global_stats
249 .map(|s| s.avg_field_len(field))
250 .unwrap_or_else(|| reader.avg_field_len(field));
251 let infos: Vec<_> = group.into_iter().map(|(_, info)| info).collect();
252 multi_term_groups.push((field, avg_field_len, infos));
253 } else {
254 fallback_indices.push(group[0].0);
255 }
256 }
257
258 Some(PerFieldGrouping {
259 multi_term_groups,
260 fallback_indices,
261 per_field_limit,
262 num_docs,
263 })
264}
265
266fn build_should_scorer<'a>(scorers: Vec<Box<dyn Scorer + 'a>>) -> Box<dyn Scorer + 'a> {
268 if scorers.is_empty() {
269 return Box::new(EmptyScorer);
270 }
271 if scorers.len() == 1 {
272 return scorers.into_iter().next().unwrap();
273 }
274 let mut scorer = BooleanScorer {
275 must: vec![],
276 should: scorers,
277 must_not: vec![],
278 current_doc: 0,
279 };
280 scorer.current_doc = scorer.find_next_match();
281 Box::new(scorer)
282}
283
284async fn try_per_field_maxscore<'a>(
290 should: &[Arc<dyn Query>],
291 reader: &'a SegmentReader,
292 limit: usize,
293 global_stats: Option<&Arc<GlobalStats>>,
294) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
295 let grouping = match prepare_per_field_grouping(should, reader, limit, global_stats) {
296 Some(g) => g,
297 None => return Ok(None),
298 };
299
300 let mut scorers: Vec<Box<dyn Scorer + 'a>> = Vec::new();
301
302 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
303 let mut posting_lists = Vec::with_capacity(infos.len());
304 for info in infos {
305 if let Some(pl) = reader.get_postings(info.field, &info.term).await? {
306 let idf = compute_idf(&pl, *field, &info.term, grouping.num_docs, global_stats);
307 posting_lists.push((pl, idf));
308 }
309 }
310 if !posting_lists.is_empty() {
311 scorers.push(finish_text_maxscore(
312 posting_lists,
313 *avg_field_len,
314 grouping.per_field_limit,
315 )?);
316 }
317 }
318
319 for &idx in &grouping.fallback_indices {
320 scorers.push(should[idx].scorer(reader, limit).await?);
321 }
322
323 Ok(Some(build_should_scorer(scorers)))
324}
325
326#[cfg(feature = "sync")]
328fn try_per_field_maxscore_sync<'a>(
329 should: &[Arc<dyn Query>],
330 reader: &'a SegmentReader,
331 limit: usize,
332 global_stats: Option<&Arc<GlobalStats>>,
333) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
334 let grouping = match prepare_per_field_grouping(should, reader, limit, global_stats) {
335 Some(g) => g,
336 None => return Ok(None),
337 };
338
339 let mut scorers: Vec<Box<dyn Scorer + 'a>> = Vec::new();
340
341 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
342 let mut posting_lists = Vec::with_capacity(infos.len());
343 for info in infos {
344 if let Some(pl) = reader.get_postings_sync(info.field, &info.term)? {
345 let idf = compute_idf(&pl, *field, &info.term, grouping.num_docs, global_stats);
346 posting_lists.push((pl, idf));
347 }
348 }
349 if !posting_lists.is_empty() {
350 scorers.push(finish_text_maxscore(
351 posting_lists,
352 *avg_field_len,
353 grouping.per_field_limit,
354 )?);
355 }
356 }
357
358 for &idx in &grouping.fallback_indices {
359 scorers.push(should[idx].scorer_sync(reader, limit)?);
360 }
361
362 Ok(Some(build_should_scorer(scorers)))
363}
364
365fn prepare_sparse_maxscore<'a>(
368 should: &[Arc<dyn Query>],
369 reader: &'a SegmentReader,
370 limit: usize,
371) -> Option<Result<MaxScoreExecutor<'a>, Box<dyn Scorer + 'a>>> {
372 let infos: Vec<SparseTermQueryInfo> = should
373 .iter()
374 .filter_map(|q| q.as_sparse_term_query_info())
375 .collect();
376 if infos.len() != should.len() {
377 return None;
378 }
379 let field = infos[0].field;
380 if !infos.iter().all(|t| t.field == field) {
381 return None;
382 }
383 let si = match reader.sparse_index(field) {
384 Some(si) => si,
385 None => return Some(Err(Box::new(EmptyScorer))),
386 };
387 let query_terms: Vec<(u32, f32)> = infos
388 .iter()
389 .filter(|info| si.has_dimension(info.dim_id))
390 .map(|info| (info.dim_id, info.weight))
391 .collect();
392 if query_terms.is_empty() {
393 return Some(Err(Box::new(EmptyScorer)));
394 }
395 let executor_limit = (limit as f32 * infos[0].over_fetch_factor).ceil() as usize;
396 Some(Ok(MaxScoreExecutor::sparse(
397 si,
398 query_terms,
399 executor_limit,
400 infos[0].heap_factor,
401 )))
402}
403
404fn combine_sparse_results<'a>(
406 raw: Vec<ScoredDoc>,
407 combiner: super::MultiValueCombiner,
408 limit: usize,
409) -> Box<dyn Scorer + 'a> {
410 let combined = crate::segment::combine_ordinal_results(
411 raw.into_iter().map(|r| (r.doc_id, r.ordinal, r.score)),
412 combiner,
413 limit,
414 );
415 let scored: Vec<ScoredDoc> = combined
416 .into_iter()
417 .map(|r| ScoredDoc {
418 doc_id: r.doc_id,
419 score: r.score,
420 ordinal: 0,
421 })
422 .collect();
423 Box::new(TopKResultScorer::new(scored))
424}
425
426async fn try_sparse_maxscore_scorer<'a>(
428 should: &[Arc<dyn Query>],
429 reader: &'a SegmentReader,
430 limit: usize,
431) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
432 let executor = match prepare_sparse_maxscore(should, reader, limit) {
433 None => return Ok(None),
434 Some(Err(empty)) => return Ok(Some(empty)),
435 Some(Ok(e)) => e,
436 };
437 let combiner = should[0].as_sparse_term_query_info().unwrap().combiner;
438 let raw = executor.execute().await?;
439 Ok(Some(combine_sparse_results(raw, combiner, limit)))
440}
441
442#[cfg(feature = "sync")]
444fn try_sparse_maxscore_scorer_sync<'a>(
445 should: &[Arc<dyn Query>],
446 reader: &'a SegmentReader,
447 limit: usize,
448) -> crate::Result<Option<Box<dyn Scorer + 'a>>> {
449 let executor = match prepare_sparse_maxscore(should, reader, limit) {
450 None => return Ok(None),
451 Some(Err(empty)) => return Ok(Some(empty)),
452 Some(Ok(e)) => e,
453 };
454 let combiner = should[0].as_sparse_term_query_info().unwrap().combiner;
455 let raw = executor.execute_sync()?;
456 Ok(Some(combine_sparse_results(raw, combiner, limit)))
457}
458
459impl Query for BooleanQuery {
460 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
461 let must = self.must.clone();
463 let should = self.should.clone();
464 let must_not = self.must_not.clone();
465 let global_stats = self.global_stats.clone();
466
467 Box::pin(async move {
468 if must_not.is_empty() {
470 if must.len() == 1 && should.is_empty() {
471 return must[0].scorer(reader, limit).await;
472 }
473 if should.len() == 1 && must.is_empty() {
474 return should[0].scorer(reader, limit).await;
475 }
476 }
477
478 if must.is_empty() && must_not.is_empty() && should.len() >= 2 {
481 if let Some(scorer) =
483 try_maxscore_scorer(&should, reader, limit, global_stats.as_ref()).await?
484 {
485 return Ok(scorer);
486 }
487 if let Some(scorer) = try_sparse_maxscore_scorer(&should, reader, limit).await? {
489 return Ok(scorer);
490 }
491 if let Some(scorer) =
493 try_per_field_maxscore(&should, reader, limit, global_stats.as_ref()).await?
494 {
495 return Ok(scorer);
496 }
497 }
498
499 let mut must_scorers = Vec::with_capacity(must.len());
501 for q in &must {
502 must_scorers.push(q.scorer(reader, limit).await?);
503 }
504
505 let mut should_scorers = Vec::with_capacity(should.len());
506 for q in &should {
507 should_scorers.push(q.scorer(reader, limit).await?);
508 }
509
510 let mut must_not_scorers = Vec::with_capacity(must_not.len());
511 for q in &must_not {
512 must_not_scorers.push(q.scorer(reader, limit).await?);
513 }
514
515 let mut scorer = BooleanScorer {
516 must: must_scorers,
517 should: should_scorers,
518 must_not: must_not_scorers,
519 current_doc: 0,
520 };
521 scorer.current_doc = scorer.find_next_match();
523 Ok(Box::new(scorer) as Box<dyn Scorer + 'a>)
524 })
525 }
526
527 #[cfg(feature = "sync")]
528 fn scorer_sync<'a>(
529 &self,
530 reader: &'a SegmentReader,
531 limit: usize,
532 ) -> crate::Result<Box<dyn Scorer + 'a>> {
533 if self.must_not.is_empty() {
535 if self.must.len() == 1 && self.should.is_empty() {
536 return self.must[0].scorer_sync(reader, limit);
537 }
538 if self.should.len() == 1 && self.must.is_empty() {
539 return self.should[0].scorer_sync(reader, limit);
540 }
541 }
542
543 if self.must.is_empty() && self.must_not.is_empty() && self.should.len() >= 2 {
545 if let Some(scorer) =
546 try_maxscore_scorer_sync(&self.should, reader, limit, self.global_stats.as_ref())?
547 {
548 return Ok(scorer);
549 }
550 if let Some(scorer) = try_sparse_maxscore_scorer_sync(&self.should, reader, limit)? {
551 return Ok(scorer);
552 }
553 if let Some(scorer) = try_per_field_maxscore_sync(
555 &self.should,
556 reader,
557 limit,
558 self.global_stats.as_ref(),
559 )? {
560 return Ok(scorer);
561 }
562 }
563
564 let mut must_scorers = Vec::with_capacity(self.must.len());
566 for q in &self.must {
567 must_scorers.push(q.scorer_sync(reader, limit)?);
568 }
569
570 let mut should_scorers = Vec::with_capacity(self.should.len());
571 for q in &self.should {
572 should_scorers.push(q.scorer_sync(reader, limit)?);
573 }
574
575 let mut must_not_scorers = Vec::with_capacity(self.must_not.len());
576 for q in &self.must_not {
577 must_not_scorers.push(q.scorer_sync(reader, limit)?);
578 }
579
580 let mut scorer = BooleanScorer {
581 must: must_scorers,
582 should: should_scorers,
583 must_not: must_not_scorers,
584 current_doc: 0,
585 };
586 scorer.current_doc = scorer.find_next_match();
587 Ok(Box::new(scorer) as Box<dyn Scorer + 'a>)
588 }
589
590 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
591 let must = self.must.clone();
592 let should = self.should.clone();
593
594 Box::pin(async move {
595 if !must.is_empty() {
596 let mut estimates = Vec::with_capacity(must.len());
597 for q in &must {
598 estimates.push(q.count_estimate(reader).await?);
599 }
600 estimates
601 .into_iter()
602 .min()
603 .ok_or_else(|| crate::Error::Corruption("Empty must clause".to_string()))
604 } else if !should.is_empty() {
605 let mut sum = 0u32;
606 for q in &should {
607 sum = sum.saturating_add(q.count_estimate(reader).await?);
608 }
609 Ok(sum)
610 } else {
611 Ok(0)
612 }
613 })
614 }
615}
616
617struct BooleanScorer<'a> {
618 must: Vec<Box<dyn Scorer + 'a>>,
619 should: Vec<Box<dyn Scorer + 'a>>,
620 must_not: Vec<Box<dyn Scorer + 'a>>,
621 current_doc: DocId,
622}
623
624impl BooleanScorer<'_> {
625 fn find_next_match(&mut self) -> DocId {
626 if self.must.is_empty() && self.should.is_empty() {
627 return TERMINATED;
628 }
629
630 loop {
631 let candidate = if !self.must.is_empty() {
632 let mut max_doc = self
633 .must
634 .iter()
635 .map(|s| s.doc())
636 .max()
637 .unwrap_or(TERMINATED);
638
639 if max_doc == TERMINATED {
640 return TERMINATED;
641 }
642
643 loop {
644 let mut all_match = true;
645 for scorer in &mut self.must {
646 let doc = scorer.seek(max_doc);
647 if doc == TERMINATED {
648 return TERMINATED;
649 }
650 if doc > max_doc {
651 max_doc = doc;
652 all_match = false;
653 break;
654 }
655 }
656 if all_match {
657 break;
658 }
659 }
660 max_doc
661 } else {
662 self.should
663 .iter()
664 .map(|s| s.doc())
665 .filter(|&d| d != TERMINATED)
666 .min()
667 .unwrap_or(TERMINATED)
668 };
669
670 if candidate == TERMINATED {
671 return TERMINATED;
672 }
673
674 let excluded = self.must_not.iter_mut().any(|scorer| {
675 let doc = scorer.seek(candidate);
676 doc == candidate
677 });
678
679 if !excluded {
680 for scorer in &mut self.should {
682 scorer.seek(candidate);
683 }
684 self.current_doc = candidate;
685 return candidate;
686 }
687
688 if !self.must.is_empty() {
690 for scorer in &mut self.must {
691 scorer.advance();
692 }
693 } else {
694 for scorer in &mut self.should {
696 if scorer.doc() <= candidate && scorer.doc() != TERMINATED {
697 scorer.seek(candidate + 1);
698 }
699 }
700 }
701 }
702 }
703}
704
705impl super::docset::DocSet for BooleanScorer<'_> {
706 fn doc(&self) -> DocId {
707 self.current_doc
708 }
709
710 fn advance(&mut self) -> DocId {
711 if !self.must.is_empty() {
712 for scorer in &mut self.must {
713 scorer.advance();
714 }
715 } else {
716 for scorer in &mut self.should {
717 if scorer.doc() == self.current_doc {
718 scorer.advance();
719 }
720 }
721 }
722
723 self.current_doc = self.find_next_match();
724 self.current_doc
725 }
726
727 fn seek(&mut self, target: DocId) -> DocId {
728 for scorer in &mut self.must {
729 scorer.seek(target);
730 }
731
732 for scorer in &mut self.should {
733 scorer.seek(target);
734 }
735
736 self.current_doc = self.find_next_match();
737 self.current_doc
738 }
739
740 fn size_hint(&self) -> u32 {
741 if !self.must.is_empty() {
742 self.must.iter().map(|s| s.size_hint()).min().unwrap_or(0)
743 } else {
744 self.should.iter().map(|s| s.size_hint()).sum()
745 }
746 }
747}
748
749impl Scorer for BooleanScorer<'_> {
750 fn score(&self) -> Score {
751 let mut total = 0.0;
752
753 for scorer in &self.must {
754 if scorer.doc() == self.current_doc {
755 total += scorer.score();
756 }
757 }
758
759 for scorer in &self.should {
760 if scorer.doc() == self.current_doc {
761 total += scorer.score();
762 }
763 }
764
765 total
766 }
767
768 fn matched_positions(&self) -> Option<super::MatchedPositions> {
769 let mut all_positions: super::MatchedPositions = Vec::new();
770
771 for scorer in &self.must {
772 if scorer.doc() == self.current_doc
773 && let Some(positions) = scorer.matched_positions()
774 {
775 all_positions.extend(positions);
776 }
777 }
778
779 for scorer in &self.should {
780 if scorer.doc() == self.current_doc
781 && let Some(positions) = scorer.matched_positions()
782 {
783 all_positions.extend(positions);
784 }
785 }
786
787 if all_positions.is_empty() {
788 None
789 } else {
790 Some(all_positions)
791 }
792 }
793}
794
795struct TopKResultScorer {
797 results: Vec<ScoredDoc>,
798 position: usize,
799}
800
801impl TopKResultScorer {
802 fn new(mut results: Vec<ScoredDoc>) -> Self {
803 results.sort_unstable_by_key(|r| r.doc_id);
805 Self {
806 results,
807 position: 0,
808 }
809 }
810}
811
812impl super::docset::DocSet for TopKResultScorer {
813 fn doc(&self) -> DocId {
814 if self.position < self.results.len() {
815 self.results[self.position].doc_id
816 } else {
817 TERMINATED
818 }
819 }
820
821 fn advance(&mut self) -> DocId {
822 self.position += 1;
823 self.doc()
824 }
825
826 fn seek(&mut self, target: DocId) -> DocId {
827 let remaining = &self.results[self.position..];
828 self.position += remaining.partition_point(|r| r.doc_id < target);
829 self.doc()
830 }
831
832 fn size_hint(&self) -> u32 {
833 (self.results.len() - self.position) as u32
834 }
835}
836
837impl Scorer for TopKResultScorer {
838 fn score(&self) -> Score {
839 if self.position < self.results.len() {
840 self.results[self.position].score
841 } else {
842 0.0
843 }
844 }
845}
846
847struct EmptyScorer;
849
850impl super::docset::DocSet for EmptyScorer {
851 fn doc(&self) -> DocId {
852 TERMINATED
853 }
854
855 fn advance(&mut self) -> DocId {
856 TERMINATED
857 }
858
859 fn seek(&mut self, _target: DocId) -> DocId {
860 TERMINATED
861 }
862
863 fn size_hint(&self) -> u32 {
864 0
865 }
866}
867
868impl Scorer for EmptyScorer {
869 fn score(&self) -> Score {
870 0.0
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877 use crate::dsl::Field;
878 use crate::query::TermQuery;
879
880 #[test]
881 fn test_maxscore_eligible_pure_or_same_field() {
882 let query = BooleanQuery::new()
884 .should(TermQuery::text(Field(0), "hello"))
885 .should(TermQuery::text(Field(0), "world"))
886 .should(TermQuery::text(Field(0), "foo"));
887
888 assert!(
890 query
891 .should
892 .iter()
893 .all(|q| q.as_term_query_info().is_some())
894 );
895
896 let infos: Vec<_> = query
898 .should
899 .iter()
900 .filter_map(|q| q.as_term_query_info())
901 .collect();
902 assert_eq!(infos.len(), 3);
903 assert!(infos.iter().all(|i| i.field == Field(0)));
904 }
905
906 #[test]
907 fn test_maxscore_not_eligible_different_fields() {
908 let query = BooleanQuery::new()
910 .should(TermQuery::text(Field(0), "hello"))
911 .should(TermQuery::text(Field(1), "world")); let infos: Vec<_> = query
914 .should
915 .iter()
916 .filter_map(|q| q.as_term_query_info())
917 .collect();
918 assert_eq!(infos.len(), 2);
919 assert!(infos[0].field != infos[1].field);
921 }
922
923 #[test]
924 fn test_maxscore_not_eligible_with_must() {
925 let query = BooleanQuery::new()
927 .must(TermQuery::text(Field(0), "required"))
928 .should(TermQuery::text(Field(0), "hello"))
929 .should(TermQuery::text(Field(0), "world"));
930
931 assert!(!query.must.is_empty());
933 }
934
935 #[test]
936 fn test_maxscore_not_eligible_with_must_not() {
937 let query = BooleanQuery::new()
939 .should(TermQuery::text(Field(0), "hello"))
940 .should(TermQuery::text(Field(0), "world"))
941 .must_not(TermQuery::text(Field(0), "excluded"));
942
943 assert!(!query.must_not.is_empty());
945 }
946
947 #[test]
948 fn test_maxscore_not_eligible_single_term() {
949 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
951
952 assert_eq!(query.should.len(), 1);
954 }
955
956 #[test]
957 fn test_term_query_info_extraction() {
958 let term_query = TermQuery::text(Field(42), "test");
959 let info = term_query.as_term_query_info();
960
961 assert!(info.is_some());
962 let info = info.unwrap();
963 assert_eq!(info.field, Field(42));
964 assert_eq!(info.term, b"test");
965 }
966
967 #[test]
968 fn test_boolean_query_no_term_info() {
969 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
971
972 assert!(query.as_term_query_info().is_none());
973 }
974}