1use std::sync::Arc;
4
5use crate::segment::SegmentReader;
6use crate::structures::TERMINATED;
7use crate::{DocId, Score};
8
9use super::planner::{
10 build_combined_bitset, build_sparse_bmp_results, build_sparse_bmp_results_filtered,
11 build_sparse_maxscore_executor, chain_predicates, combine_sparse_results, compute_idf,
12 extract_all_sparse_infos, finish_text_maxscore, prepare_per_field_grouping,
13 prepare_text_maxscore,
14};
15use super::{CountFuture, EmptyScorer, GlobalStats, Query, Scorer, ScorerFuture};
16
17#[derive(Default, Clone)]
22pub struct BooleanQuery {
23 pub must: Vec<Arc<dyn Query>>,
24 pub should: Vec<Arc<dyn Query>>,
25 pub must_not: Vec<Arc<dyn Query>>,
26 global_stats: Option<Arc<GlobalStats>>,
28}
29
30impl std::fmt::Debug for BooleanQuery {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.debug_struct("BooleanQuery")
33 .field("must_count", &self.must.len())
34 .field("should_count", &self.should.len())
35 .field("must_not_count", &self.must_not.len())
36 .field("has_global_stats", &self.global_stats.is_some())
37 .finish()
38 }
39}
40
41impl std::fmt::Display for BooleanQuery {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 write!(f, "Boolean(")?;
44 let mut first = true;
45 for q in &self.must {
46 if !first {
47 write!(f, " ")?;
48 }
49 write!(f, "+{}", q)?;
50 first = false;
51 }
52 for q in &self.should {
53 if !first {
54 write!(f, " ")?;
55 }
56 write!(f, "{}", q)?;
57 first = false;
58 }
59 for q in &self.must_not {
60 if !first {
61 write!(f, " ")?;
62 }
63 write!(f, "-{}", q)?;
64 first = false;
65 }
66 write!(f, ")")
67 }
68}
69
70impl BooleanQuery {
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn must(mut self, query: impl Query + 'static) -> Self {
76 self.must.push(Arc::new(query));
77 self
78 }
79
80 pub fn should(mut self, query: impl Query + 'static) -> Self {
81 self.should.push(Arc::new(query));
82 self
83 }
84
85 pub fn must_not(mut self, query: impl Query + 'static) -> Self {
86 self.must_not.push(Arc::new(query));
87 self
88 }
89
90 pub fn with_global_stats(mut self, stats: Arc<GlobalStats>) -> Self {
92 self.global_stats = Some(stats);
93 self
94 }
95}
96
97fn build_should_scorer<'a>(scorers: Vec<Box<dyn Scorer + 'a>>) -> Box<dyn Scorer + 'a> {
99 if scorers.is_empty() {
100 return Box::new(EmptyScorer);
101 }
102 if scorers.len() == 1 {
103 return scorers.into_iter().next().unwrap();
104 }
105 let mut scorer = BooleanScorer {
106 must: vec![],
107 should: scorers,
108 must_not: vec![],
109 current_doc: 0,
110 };
111 scorer.current_doc = scorer.find_next_match();
112 Box::new(scorer)
113}
114
115macro_rules! boolean_plan {
129 ($must:expr, $should:expr, $must_not:expr, $global_stats:expr,
130 $reader:expr, $limit:expr, $scorer_options:expr,
131 $scorer_fn:ident, $get_postings_fn:ident, $execute_fn:ident
132 $(, $aw:tt)*) => {{
133 let must: &[Arc<dyn Query>] = &$must;
134 let should_all: &[Arc<dyn Query>] = &$should;
135 let must_not: &[Arc<dyn Query>] = &$must_not;
136 let global_stats: Option<&Arc<GlobalStats>> = $global_stats;
137 let reader: &SegmentReader = $reader;
138 let limit: usize = $limit;
139 let scorer_options: super::ScorerOptions = $scorer_options;
140
141 let should_capped: Vec<Arc<dyn Query>>;
144 let should: &[Arc<dyn Query>] = if should_all.len() > super::MAX_QUERY_TERMS {
145 let is_predicate: Vec<bool> = should_all
146 .iter()
147 .map(|q| q.is_filter() || q.as_doc_predicate(reader).is_some())
148 .collect();
149 let cursor_count = is_predicate.iter().filter(|&&p| !p).count();
150
151 if cursor_count > super::MAX_QUERY_TERMS {
152 let mut kept = Vec::with_capacity(should_all.len());
153 let mut cursor_kept = 0usize;
154 for (q, &is_pred) in should_all.iter().zip(is_predicate.iter()) {
155 if is_pred {
156 kept.push(q.clone());
157 } else if cursor_kept < super::MAX_QUERY_TERMS {
158 kept.push(q.clone());
159 cursor_kept += 1;
160 }
161 }
162 log::debug!(
163 "BooleanQuery: capping cursor SHOULD from {} to {} ({} fast-field predicates exempt)",
164 cursor_count,
165 super::MAX_QUERY_TERMS,
166 kept.len() - cursor_kept,
167 );
168 should_capped = kept;
169 &should_capped
170 } else {
171 log::debug!(
172 "BooleanQuery: {} SHOULD clauses OK ({} need cursors, {} fast-field predicates)",
173 should_all.len(),
174 cursor_count,
175 should_all.len() - cursor_count,
176 );
177 should_all
178 }
179 } else {
180 should_all
181 };
182
183 if must_not.is_empty() {
185 if must.len() == 1 && should.is_empty() {
186 return must[0].$scorer_fn(reader, limit, scorer_options) $(. $aw)* ;
187 }
188 if should.len() == 1 && must.is_empty() {
189 return should[0].$scorer_fn(reader, limit, scorer_options) $(. $aw)* ;
190 }
191 }
192
193 if must.is_empty() && must_not.is_empty() && should.len() >= 2 {
195 if !scorer_options.collect_positions
197 && let Some((mut infos, text_field, avg_field_len, num_docs)) =
198 prepare_text_maxscore(should, reader, global_stats)
199 {
200 let mut posting_lists = Vec::with_capacity(infos.len());
201 for info in infos.drain(..) {
202 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
203 $(. $aw)* ?
204 {
205 let idf = compute_idf(&pl, info.field, &info.term, num_docs, global_stats);
206 posting_lists.push((pl, idf));
207 }
208 }
209 let shared_threshold = std::cell::Cell::new(scorer_options.initial_threshold);
215 return finish_text_maxscore(
216 posting_lists,
217 avg_field_len,
218 limit,
219 &shared_threshold,
220 reader.schema().index_label(),
221 reader.schema().get_field_name(text_field).unwrap_or("?"),
222 );
223 }
224
225 if let Some(infos) = extract_all_sparse_infos(should) {
228 if let Some((raw, info)) =
229 build_sparse_bmp_results(&infos, reader, limit, &scorer_options)?
230 {
231 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
232 }
233 if let Some((executor, info)) =
234 build_sparse_maxscore_executor(&infos, reader, limit, None)
235 {
236 let raw = executor.$execute_fn() $(. $aw)* ?;
237 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
238 }
239 }
240
241 if !scorer_options.collect_positions
243 && let Some(grouping) =
244 prepare_per_field_grouping(should, reader, limit, global_stats)
245 {
246 let mut scorers: Vec<Box<dyn Scorer + '_>> = Vec::new();
247 let shared_threshold = std::cell::Cell::new(0.0f32);
249 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
250 let mut posting_lists = Vec::with_capacity(infos.len());
251 for info in infos {
252 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
253 $(. $aw)* ?
254 {
255 let idf = compute_idf(
256 &pl, *field, &info.term, grouping.num_docs, global_stats,
257 );
258 posting_lists.push((pl, idf));
259 }
260 }
261 if !posting_lists.is_empty() {
262 scorers.push(finish_text_maxscore(
263 posting_lists,
264 *avg_field_len,
265 grouping.per_field_limit,
266 &shared_threshold,
267 reader.schema().index_label(),
268 reader.schema().get_field_name(*field).unwrap_or("?"),
269 )?);
270 }
271 }
272 for &idx in &grouping.fallback_indices {
273 scorers.push(should[idx].$scorer_fn(
274 reader,
275 limit,
276 scorer_options.without_threshold(),
277 ) $(. $aw)* ?);
278 }
279 return Ok(build_should_scorer(scorers));
280 }
281 }
282
283 if !scorer_options.collect_positions
285 && !should.is_empty()
286 && !must.is_empty()
287 {
288 let should_is_sparse = extract_all_sparse_infos(should).is_some();
295
296 let mut predicates: Vec<super::DocPredicate<'_>> = Vec::new();
302 let mut must_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
303 for q in must {
304 if let Some(pred) = q.as_doc_predicate(reader) {
305 log::debug!("BooleanQuery planner 3a: MUST clause → predicate ({})", q);
306 predicates.push(pred);
307 } else if should_is_sparse {
308 if let Some(bitset) = q.as_doc_bitset(reader) {
309 log::debug!("BooleanQuery planner 3a: MUST clause → bitset predicate ({})", q);
310 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
311 } else {
312 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
313 must_verifiers.push(q.$scorer_fn(
314 reader, limit, scorer_options.without_threshold()
315 ) $(. $aw)* ?);
316 }
317 } else {
318 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
319 must_verifiers.push(q.$scorer_fn(
320 reader, limit, scorer_options.without_threshold()
321 ) $(. $aw)* ?);
322 }
323 }
324 let mut must_not_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
326 for q in must_not {
327 if let Some(pred) = q.as_doc_predicate(reader) {
328 let negated: super::DocPredicate<'_> =
329 Box::new(move |doc_id| !pred(doc_id));
330 predicates.push(negated);
331 } else if should_is_sparse {
332 if let Some(bitset) = q.as_doc_bitset(reader) {
333 log::debug!("BooleanQuery planner 3a: MUST_NOT clause → bitset predicate ({})", q);
334 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
335 } else {
336 must_not_verifiers.push(q.$scorer_fn(
337 reader, limit, scorer_options.without_threshold()
338 ) $(. $aw)* ?);
339 }
340 } else {
341 must_not_verifiers.push(q.$scorer_fn(
342 reader, limit, scorer_options.without_threshold()
343 ) $(. $aw)* ?);
344 }
345 }
346
347 if must_verifiers.is_empty()
349 && must_not_verifiers.is_empty()
350 && !predicates.is_empty()
351 {
352 if let Some(infos) = extract_all_sparse_infos(should) {
353 let bitset_result = build_combined_bitset(must, must_not, reader);
356 if let Some(ref bitset) = bitset_result {
357 let bitset_pred = |doc_id: crate::DocId| bitset.contains(doc_id);
358 if let Some((raw, info)) =
359 build_sparse_bmp_results_filtered(
360 &infos, reader, limit, &bitset_pred, &scorer_options
361 )?
362 {
363 log::debug!(
364 "BooleanQuery planner: bitset-aware sparse BMP, {} dims, {} matching docs",
365 infos.len(),
366 bitset.count()
367 );
368 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
369 }
370 }
371
372 let combined = chain_predicates(predicates);
374 if let Some((raw, info)) =
375 build_sparse_bmp_results_filtered(
376 &infos, reader, limit, &*combined, &scorer_options
377 )?
378 {
379 log::debug!(
380 "BooleanQuery planner: predicate-aware sparse BMP, {} dims",
381 infos.len()
382 );
383 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
384 }
385 if let Some((executor, info)) =
387 build_sparse_maxscore_executor(&infos, reader, limit, Some(combined))
388 {
389 log::debug!(
390 "BooleanQuery planner: predicate-aware sparse MaxScore, {} dims",
391 infos.len()
392 );
393 let raw = executor.$execute_fn() $(. $aw)* ?;
394 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
395 }
396 predicates = Vec::new();
400 for q in must {
401 if let Some(pred) = q.as_doc_predicate(reader) {
402 predicates.push(pred);
403 } else if let Some(bitset) = q.as_doc_bitset(reader) {
404 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
405 }
406 }
407 for q in must_not {
408 if let Some(pred) = q.as_doc_predicate(reader) {
409 let negated: super::DocPredicate<'_> =
410 Box::new(move |doc_id| !pred(doc_id));
411 predicates.push(negated);
412 } else if let Some(bitset) = q.as_doc_bitset(reader) {
413 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
414 }
415 }
416 }
417 }
418
419 let has_filters = !predicates.is_empty()
422 || !must_verifiers.is_empty()
423 || !must_not_verifiers.is_empty();
424 let should_limit = if has_filters {
425 super::max_candidate_limit(limit)
426 } else {
427 limit
428 };
429 let mut should_options = scorer_options.without_threshold();
430 if should_is_sparse {
431 should_options.lsp_plan = scorer_options.lsp_plan.clone();
437 }
438 let should_scorer = if should.len() == 1 {
439 should[0].$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
440 } else {
441 let sub = BooleanQuery {
442 must: Vec::new(),
443 should: should.to_vec(),
444 must_not: Vec::new(),
445 global_stats: global_stats.cloned(),
446 };
447 sub.$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
448 };
449
450 let use_predicated =
451 must_verifiers.is_empty() || should_scorer.size_hint() >= limit as u32;
452
453 if use_predicated {
454 log::debug!(
455 "BooleanQuery planner: PredicatedScorer {} preds + {} must_v + {} must_not_v, \
456 SHOULD size_hint={}, over_fetch={}",
457 predicates.len(), must_verifiers.len(), must_not_verifiers.len(),
458 should_scorer.size_hint(), should_limit
459 );
460 return Ok(Box::new(super::PredicatedScorer::new(
461 should_scorer, predicates, must_verifiers, must_not_verifiers,
462 )));
463 }
464
465 log::debug!(
467 "BooleanQuery planner: BooleanScorer fallback, size_hint={} < limit={}, \
468 {} must_v + {} must_not_v",
469 should_scorer.size_hint(), limit,
470 must_verifiers.len(), must_not_verifiers.len()
471 );
472 let mut scorer = BooleanScorer {
473 must: must_verifiers,
474 should: vec![should_scorer],
475 must_not: must_not_verifiers,
476 current_doc: 0,
477 };
478 scorer.current_doc = scorer.find_next_match();
479 return Ok(Box::new(scorer));
480 }
481
482 let mut must_scorers = Vec::with_capacity(must.len());
484 for q in must {
485 must_scorers.push(q.$scorer_fn(
486 reader, limit, scorer_options.without_threshold()
487 ) $(. $aw)* ?);
488 }
489 let mut should_scorers = Vec::with_capacity(should.len());
490 for q in should {
491 should_scorers.push(q.$scorer_fn(
492 reader, limit, scorer_options.without_threshold()
493 ) $(. $aw)* ?);
494 }
495 let mut must_not_scorers = Vec::with_capacity(must_not.len());
496 for q in must_not {
497 must_not_scorers.push(q.$scorer_fn(
498 reader, limit, scorer_options.without_threshold()
499 ) $(. $aw)* ?);
500 }
501 let mut scorer = BooleanScorer {
502 must: must_scorers,
503 should: should_scorers,
504 must_not: must_not_scorers,
505 current_doc: 0,
506 };
507 scorer.current_doc = scorer.find_next_match();
508 Ok(Box::new(scorer) as Box<dyn Scorer + '_>)
509 }};
510}
511
512impl Query for BooleanQuery {
513 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
514 self.scorer_with_options(reader, limit, super::ScorerOptions::with_positions())
515 }
516
517 fn scorer_with_options<'a>(
518 &self,
519 reader: &'a SegmentReader,
520 limit: usize,
521 options: super::ScorerOptions,
522 ) -> ScorerFuture<'a> {
523 let must = self.must.clone();
524 let should = self.should.clone();
525 let must_not = self.must_not.clone();
526 let global_stats = self.global_stats.clone();
527 Box::pin(async move {
528 boolean_plan!(
529 must,
530 should,
531 must_not,
532 global_stats.as_ref(),
533 reader,
534 limit,
535 options,
536 scorer_with_options,
537 get_postings,
538 execute,
539 await
540 )
541 })
542 }
543
544 #[cfg(feature = "sync")]
545 fn scorer_sync<'a>(
546 &self,
547 reader: &'a SegmentReader,
548 limit: usize,
549 ) -> crate::Result<Box<dyn Scorer + 'a>> {
550 self.scorer_sync_with_options(reader, limit, super::ScorerOptions::with_positions())
551 }
552
553 #[cfg(feature = "sync")]
554 fn scorer_sync_with_options<'a>(
555 &self,
556 reader: &'a SegmentReader,
557 limit: usize,
558 options: super::ScorerOptions,
559 ) -> crate::Result<Box<dyn Scorer + 'a>> {
560 boolean_plan!(
561 self.must,
562 self.should,
563 self.must_not,
564 self.global_stats.as_ref(),
565 reader,
566 limit,
567 options,
568 scorer_sync_with_options,
569 get_postings_sync,
570 execute_sync
571 )
572 }
573
574 fn decompose(&self) -> super::QueryDecomposition {
575 if self.should.is_empty() || self.must.iter().any(|query| !query.is_filter()) {
581 return super::QueryDecomposition::Opaque;
582 }
583 extract_all_sparse_infos(&self.should)
584 .map(super::QueryDecomposition::SparseTerms)
585 .unwrap_or(super::QueryDecomposition::Opaque)
586 }
587
588 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
589 if self.must.is_empty() && self.should.is_empty() {
590 return None;
591 }
592
593 let num_docs = reader.num_docs();
594
595 let mut result: Option<super::DocBitset> = None;
597 for q in &self.must {
598 let bs = q.as_doc_bitset(reader)?;
599 match result {
600 None => result = Some(bs),
601 Some(ref mut acc) => acc.intersect_with(&bs),
602 }
603 }
604
605 if !self.should.is_empty() {
607 let mut should_union = super::DocBitset::new(num_docs);
608 for q in &self.should {
609 let bs = q.as_doc_bitset(reader)?;
610 should_union.union_with(&bs);
611 }
612 match result {
613 None => result = Some(should_union),
614 Some(ref mut acc) => {
615 if self.must.is_empty() {
618 *acc = should_union;
619 }
620 }
621 }
622 }
623
624 if let Some(ref mut acc) = result {
626 for q in &self.must_not {
627 {
628 let bs = q.as_doc_bitset(reader)?;
629 acc.subtract(&bs);
630 }
631 }
632 }
633
634 result
635 }
636
637 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
638 if self.must.is_empty() && self.should.is_empty() {
640 return None;
641 }
642
643 let must_preds: Vec<_> = self
645 .must
646 .iter()
647 .map(|q| q.as_doc_predicate(reader))
648 .collect::<Option<Vec<_>>>()?;
649 let should_preds: Vec<_> = self
650 .should
651 .iter()
652 .map(|q| q.as_doc_predicate(reader))
653 .collect::<Option<Vec<_>>>()?;
654 let must_not_preds: Vec<_> = self
655 .must_not
656 .iter()
657 .map(|q| q.as_doc_predicate(reader))
658 .collect::<Option<Vec<_>>>()?;
659
660 let has_must = !must_preds.is_empty();
661
662 Some(Box::new(move |doc_id| {
663 if !must_preds.iter().all(|p| p(doc_id)) {
665 return false;
666 }
667 if !has_must && !should_preds.is_empty() && !should_preds.iter().any(|p| p(doc_id)) {
669 return false;
670 }
671 must_not_preds.iter().all(|p| !p(doc_id))
673 }))
674 }
675
676 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
677 let must = self.must.clone();
678 let should = self.should.clone();
679
680 Box::pin(async move {
681 if !must.is_empty() {
682 let mut estimates = Vec::with_capacity(must.len());
683 for q in &must {
684 estimates.push(q.count_estimate(reader).await?);
685 }
686 estimates
687 .into_iter()
688 .min()
689 .ok_or_else(|| crate::Error::Corruption("Empty must clause".to_string()))
690 } else if !should.is_empty() {
691 let mut sum = 0u32;
692 for q in &should {
693 sum = sum.saturating_add(q.count_estimate(reader).await?);
694 }
695 Ok(sum)
696 } else {
697 Ok(0)
698 }
699 })
700 }
701}
702
703struct BooleanScorer<'a> {
704 must: Vec<Box<dyn Scorer + 'a>>,
705 should: Vec<Box<dyn Scorer + 'a>>,
706 must_not: Vec<Box<dyn Scorer + 'a>>,
707 current_doc: DocId,
708}
709
710impl BooleanScorer<'_> {
711 fn find_next_match(&mut self) -> DocId {
712 if self.must.is_empty() && self.should.is_empty() {
713 return TERMINATED;
714 }
715
716 loop {
717 let candidate = if !self.must.is_empty() {
718 let mut max_doc = self
719 .must
720 .iter()
721 .map(|s| s.doc())
722 .max()
723 .unwrap_or(TERMINATED);
724
725 if max_doc == TERMINATED {
726 return TERMINATED;
727 }
728
729 loop {
730 let mut all_match = true;
731 for scorer in &mut self.must {
732 let doc = scorer.seek(max_doc);
733 if doc == TERMINATED {
734 return TERMINATED;
735 }
736 if doc > max_doc {
737 max_doc = doc;
738 all_match = false;
739 break;
740 }
741 }
742 if all_match {
743 break;
744 }
745 }
746 max_doc
747 } else {
748 self.should
749 .iter()
750 .map(|s| s.doc())
751 .filter(|&d| d != TERMINATED)
752 .min()
753 .unwrap_or(TERMINATED)
754 };
755
756 if candidate == TERMINATED {
757 return TERMINATED;
758 }
759
760 let excluded = self.must_not.iter_mut().any(|scorer| {
761 let doc = scorer.seek(candidate);
762 doc == candidate
763 });
764
765 if !excluded {
766 for scorer in &mut self.should {
768 scorer.seek(candidate);
769 }
770 self.current_doc = candidate;
771 return candidate;
772 }
773
774 if !self.must.is_empty() {
776 for scorer in &mut self.must {
777 scorer.advance();
778 }
779 } else {
780 for scorer in &mut self.should {
782 if scorer.doc() <= candidate && scorer.doc() != TERMINATED {
783 scorer.seek(candidate + 1);
784 }
785 }
786 }
787 }
788 }
789}
790
791impl super::docset::DocSet for BooleanScorer<'_> {
792 fn doc(&self) -> DocId {
793 self.current_doc
794 }
795
796 fn advance(&mut self) -> DocId {
797 if !self.must.is_empty() {
798 for scorer in &mut self.must {
799 scorer.advance();
800 }
801 } else {
802 for scorer in &mut self.should {
803 if scorer.doc() == self.current_doc {
804 scorer.advance();
805 }
806 }
807 }
808
809 self.current_doc = self.find_next_match();
810 self.current_doc
811 }
812
813 fn seek(&mut self, target: DocId) -> DocId {
814 for scorer in &mut self.must {
815 scorer.seek(target);
816 }
817
818 for scorer in &mut self.should {
819 scorer.seek(target);
820 }
821
822 self.current_doc = self.find_next_match();
823 self.current_doc
824 }
825
826 fn size_hint(&self) -> u32 {
827 if !self.must.is_empty() {
828 self.must.iter().map(|s| s.size_hint()).min().unwrap_or(0)
829 } else {
830 self.should.iter().map(|s| s.size_hint()).sum()
831 }
832 }
833}
834
835impl Scorer for BooleanScorer<'_> {
836 fn score(&self) -> Score {
837 let mut total = 0.0;
838
839 for scorer in &self.must {
840 if scorer.doc() == self.current_doc {
841 total += scorer.score();
842 }
843 }
844
845 for scorer in &self.should {
846 if scorer.doc() == self.current_doc {
847 total += scorer.score();
848 }
849 }
850
851 total
852 }
853
854 fn matched_positions(&self) -> Option<super::MatchedPositions> {
855 let mut all_positions: super::MatchedPositions = Vec::new();
856
857 for scorer in &self.must {
858 if scorer.doc() == self.current_doc
859 && let Some(positions) = scorer.matched_positions()
860 {
861 all_positions.extend(positions);
862 }
863 }
864
865 for scorer in &self.should {
866 if scorer.doc() == self.current_doc
867 && let Some(positions) = scorer.matched_positions()
868 {
869 all_positions.extend(positions);
870 }
871 }
872
873 if all_positions.is_empty() {
874 None
875 } else {
876 Some(all_positions)
877 }
878 }
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use crate::dsl::Field;
885 use crate::query::{QueryDecomposition, TermQuery};
886
887 #[test]
888 fn test_maxscore_eligible_pure_or_same_field() {
889 let query = BooleanQuery::new()
891 .should(TermQuery::text(Field(0), "hello"))
892 .should(TermQuery::text(Field(0), "world"))
893 .should(TermQuery::text(Field(0), "foo"));
894
895 assert!(
897 query
898 .should
899 .iter()
900 .all(|q| matches!(q.decompose(), QueryDecomposition::TextTerm(_)))
901 );
902
903 let infos: Vec<_> = query
905 .should
906 .iter()
907 .filter_map(|q| match q.decompose() {
908 QueryDecomposition::TextTerm(info) => Some(info),
909 _ => None,
910 })
911 .collect();
912 assert_eq!(infos.len(), 3);
913 assert!(infos.iter().all(|i| i.field == Field(0)));
914 }
915
916 #[test]
917 fn test_maxscore_not_eligible_different_fields() {
918 let query = BooleanQuery::new()
920 .should(TermQuery::text(Field(0), "hello"))
921 .should(TermQuery::text(Field(1), "world")); let infos: Vec<_> = query
924 .should
925 .iter()
926 .filter_map(|q| match q.decompose() {
927 QueryDecomposition::TextTerm(info) => Some(info),
928 _ => None,
929 })
930 .collect();
931 assert_eq!(infos.len(), 2);
932 assert!(infos[0].field != infos[1].field);
934 }
935
936 #[test]
937 fn test_maxscore_not_eligible_with_must() {
938 let query = BooleanQuery::new()
940 .must(TermQuery::text(Field(0), "required"))
941 .should(TermQuery::text(Field(0), "hello"))
942 .should(TermQuery::text(Field(0), "world"));
943
944 assert!(!query.must.is_empty());
946 }
947
948 #[test]
949 fn test_maxscore_not_eligible_with_must_not() {
950 let query = BooleanQuery::new()
952 .should(TermQuery::text(Field(0), "hello"))
953 .should(TermQuery::text(Field(0), "world"))
954 .must_not(TermQuery::text(Field(0), "excluded"));
955
956 assert!(!query.must_not.is_empty());
958 }
959
960 #[test]
961 fn test_maxscore_not_eligible_single_term() {
962 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
964
965 assert_eq!(query.should.len(), 1);
967 }
968
969 #[test]
970 fn test_term_query_info_extraction() {
971 let term_query = TermQuery::text(Field(42), "test");
972 match term_query.decompose() {
973 QueryDecomposition::TextTerm(info) => {
974 assert_eq!(info.field, Field(42));
975 assert_eq!(info.term, b"test");
976 }
977 _ => panic!("Expected TextTerm decomposition"),
978 }
979 }
980
981 #[test]
982 fn test_boolean_query_no_term_info() {
983 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
985
986 assert!(matches!(query.decompose(), QueryDecomposition::Opaque));
987 }
988}