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(0.0f32);
210 return finish_text_maxscore(
211 posting_lists,
212 avg_field_len,
213 limit,
214 &shared_threshold,
215 reader.schema().index_label(),
216 reader.schema().get_field_name(text_field).unwrap_or("?"),
217 );
218 }
219
220 if let Some(infos) = extract_all_sparse_infos(should) {
223 if let Some((raw, info)) =
224 build_sparse_bmp_results(&infos, reader, limit)
225 {
226 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
227 }
228 if let Some((executor, info)) =
229 build_sparse_maxscore_executor(&infos, reader, limit, None)
230 {
231 let raw = executor.$execute_fn() $(. $aw)* ?;
232 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
233 }
234 }
235
236 if !scorer_options.collect_positions
238 && let Some(grouping) =
239 prepare_per_field_grouping(should, reader, limit, global_stats)
240 {
241 let mut scorers: Vec<Box<dyn Scorer + '_>> = Vec::new();
242 let shared_threshold = std::cell::Cell::new(0.0f32);
244 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
245 let mut posting_lists = Vec::with_capacity(infos.len());
246 for info in infos {
247 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
248 $(. $aw)* ?
249 {
250 let idf = compute_idf(
251 &pl, *field, &info.term, grouping.num_docs, global_stats,
252 );
253 posting_lists.push((pl, idf));
254 }
255 }
256 if !posting_lists.is_empty() {
257 scorers.push(finish_text_maxscore(
258 posting_lists,
259 *avg_field_len,
260 grouping.per_field_limit,
261 &shared_threshold,
262 reader.schema().index_label(),
263 reader.schema().get_field_name(*field).unwrap_or("?"),
264 )?);
265 }
266 }
267 for &idx in &grouping.fallback_indices {
268 scorers.push(should[idx].$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
269 }
270 return Ok(build_should_scorer(scorers));
271 }
272 }
273
274 if !scorer_options.collect_positions
276 && !should.is_empty()
277 && !must.is_empty()
278 && limit < usize::MAX / 4
279 {
280 let should_is_sparse = extract_all_sparse_infos(should).is_some();
287
288 let mut predicates: Vec<super::DocPredicate<'_>> = Vec::new();
294 let mut must_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
295 for q in must {
296 if let Some(pred) = q.as_doc_predicate(reader) {
297 log::debug!("BooleanQuery planner 3a: MUST clause → predicate ({})", q);
298 predicates.push(pred);
299 } else if should_is_sparse {
300 if let Some(bitset) = q.as_doc_bitset(reader) {
301 log::debug!("BooleanQuery planner 3a: MUST clause → bitset predicate ({})", q);
302 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
303 } else {
304 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
305 must_verifiers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
306 }
307 } else {
308 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
309 must_verifiers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
310 }
311 }
312 let mut must_not_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
314 for q in must_not {
315 if let Some(pred) = q.as_doc_predicate(reader) {
316 let negated: super::DocPredicate<'_> =
317 Box::new(move |doc_id| !pred(doc_id));
318 predicates.push(negated);
319 } else if should_is_sparse {
320 if let Some(bitset) = q.as_doc_bitset(reader) {
321 log::debug!("BooleanQuery planner 3a: MUST_NOT clause → bitset predicate ({})", q);
322 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
323 } else {
324 must_not_verifiers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
325 }
326 } else {
327 must_not_verifiers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
328 }
329 }
330
331 if must_verifiers.is_empty()
333 && must_not_verifiers.is_empty()
334 && !predicates.is_empty()
335 {
336 if let Some(infos) = extract_all_sparse_infos(should) {
337 let bitset_result = build_combined_bitset(must, must_not, reader);
340 if let Some(ref bitset) = bitset_result {
341 let bitset_pred = |doc_id: crate::DocId| bitset.contains(doc_id);
342 if let Some((raw, info)) =
343 build_sparse_bmp_results_filtered(&infos, reader, limit, &bitset_pred)
344 {
345 log::debug!(
346 "BooleanQuery planner: bitset-aware sparse BMP, {} dims, {} matching docs",
347 infos.len(),
348 bitset.count()
349 );
350 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
351 }
352 }
353
354 let combined = chain_predicates(predicates);
356 if let Some((raw, info)) =
357 build_sparse_bmp_results_filtered(&infos, reader, limit, &*combined)
358 {
359 log::debug!(
360 "BooleanQuery planner: predicate-aware sparse BMP, {} dims",
361 infos.len()
362 );
363 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
364 }
365 if let Some((executor, info)) =
367 build_sparse_maxscore_executor(&infos, reader, limit, Some(combined))
368 {
369 log::debug!(
370 "BooleanQuery planner: predicate-aware sparse MaxScore, {} dims",
371 infos.len()
372 );
373 let raw = executor.$execute_fn() $(. $aw)* ?;
374 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
375 }
376 predicates = Vec::new();
380 for q in must {
381 if let Some(pred) = q.as_doc_predicate(reader) {
382 predicates.push(pred);
383 } else if let Some(bitset) = q.as_doc_bitset(reader) {
384 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
385 }
386 }
387 for q in must_not {
388 if let Some(pred) = q.as_doc_predicate(reader) {
389 let negated: super::DocPredicate<'_> =
390 Box::new(move |doc_id| !pred(doc_id));
391 predicates.push(negated);
392 } else if let Some(bitset) = q.as_doc_bitset(reader) {
393 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
394 }
395 }
396 }
397 }
398
399 let has_filters = !predicates.is_empty()
401 || !must_verifiers.is_empty()
402 || !must_not_verifiers.is_empty();
403 let should_limit = if has_filters { limit * 4 } else { limit };
404 let should_scorer = if should.len() == 1 {
405 should[0].$scorer_fn(reader, should_limit, scorer_options) $(. $aw)* ?
406 } else {
407 let sub = BooleanQuery {
408 must: Vec::new(),
409 should: should.to_vec(),
410 must_not: Vec::new(),
411 global_stats: global_stats.cloned(),
412 };
413 sub.$scorer_fn(reader, should_limit, scorer_options) $(. $aw)* ?
414 };
415
416 let use_predicated =
417 must_verifiers.is_empty() || should_scorer.size_hint() >= limit as u32;
418
419 if use_predicated {
420 log::debug!(
421 "BooleanQuery planner: PredicatedScorer {} preds + {} must_v + {} must_not_v, \
422 SHOULD size_hint={}, over_fetch={}",
423 predicates.len(), must_verifiers.len(), must_not_verifiers.len(),
424 should_scorer.size_hint(), should_limit
425 );
426 return Ok(Box::new(super::PredicatedScorer::new(
427 should_scorer, predicates, must_verifiers, must_not_verifiers,
428 )));
429 }
430
431 log::debug!(
433 "BooleanQuery planner: BooleanScorer fallback, size_hint={} < limit={}, \
434 {} must_v + {} must_not_v",
435 should_scorer.size_hint(), limit,
436 must_verifiers.len(), must_not_verifiers.len()
437 );
438 let mut scorer = BooleanScorer {
439 must: must_verifiers,
440 should: vec![should_scorer],
441 must_not: must_not_verifiers,
442 current_doc: 0,
443 };
444 scorer.current_doc = scorer.find_next_match();
445 return Ok(Box::new(scorer));
446 }
447
448 let mut must_scorers = Vec::with_capacity(must.len());
450 for q in must {
451 must_scorers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
452 }
453 let mut should_scorers = Vec::with_capacity(should.len());
454 for q in should {
455 should_scorers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
456 }
457 let mut must_not_scorers = Vec::with_capacity(must_not.len());
458 for q in must_not {
459 must_not_scorers.push(q.$scorer_fn(reader, limit, scorer_options) $(. $aw)* ?);
460 }
461 let mut scorer = BooleanScorer {
462 must: must_scorers,
463 should: should_scorers,
464 must_not: must_not_scorers,
465 current_doc: 0,
466 };
467 scorer.current_doc = scorer.find_next_match();
468 Ok(Box::new(scorer) as Box<dyn Scorer + '_>)
469 }};
470}
471
472impl Query for BooleanQuery {
473 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
474 self.scorer_with_options(reader, limit, super::ScorerOptions::with_positions())
475 }
476
477 fn scorer_with_options<'a>(
478 &self,
479 reader: &'a SegmentReader,
480 limit: usize,
481 options: super::ScorerOptions,
482 ) -> ScorerFuture<'a> {
483 let must = self.must.clone();
484 let should = self.should.clone();
485 let must_not = self.must_not.clone();
486 let global_stats = self.global_stats.clone();
487 Box::pin(async move {
488 boolean_plan!(
489 must,
490 should,
491 must_not,
492 global_stats.as_ref(),
493 reader,
494 limit,
495 options,
496 scorer_with_options,
497 get_postings,
498 execute,
499 await
500 )
501 })
502 }
503
504 #[cfg(feature = "sync")]
505 fn scorer_sync<'a>(
506 &self,
507 reader: &'a SegmentReader,
508 limit: usize,
509 ) -> crate::Result<Box<dyn Scorer + 'a>> {
510 self.scorer_sync_with_options(reader, limit, super::ScorerOptions::with_positions())
511 }
512
513 #[cfg(feature = "sync")]
514 fn scorer_sync_with_options<'a>(
515 &self,
516 reader: &'a SegmentReader,
517 limit: usize,
518 options: super::ScorerOptions,
519 ) -> crate::Result<Box<dyn Scorer + 'a>> {
520 boolean_plan!(
521 self.must,
522 self.should,
523 self.must_not,
524 self.global_stats.as_ref(),
525 reader,
526 limit,
527 options,
528 scorer_sync_with_options,
529 get_postings_sync,
530 execute_sync
531 )
532 }
533
534 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
535 if self.must.is_empty() && self.should.is_empty() {
536 return None;
537 }
538
539 let num_docs = reader.num_docs();
540
541 let mut result: Option<super::DocBitset> = None;
543 for q in &self.must {
544 let bs = q.as_doc_bitset(reader)?;
545 match result {
546 None => result = Some(bs),
547 Some(ref mut acc) => acc.intersect_with(&bs),
548 }
549 }
550
551 if !self.should.is_empty() {
553 let mut should_union = super::DocBitset::new(num_docs);
554 for q in &self.should {
555 let bs = q.as_doc_bitset(reader)?;
556 should_union.union_with(&bs);
557 }
558 match result {
559 None => result = Some(should_union),
560 Some(ref mut acc) => {
561 if self.must.is_empty() {
564 *acc = should_union;
565 }
566 }
567 }
568 }
569
570 if let Some(ref mut acc) = result {
572 for q in &self.must_not {
573 {
574 let bs = q.as_doc_bitset(reader)?;
575 acc.subtract(&bs);
576 }
577 }
578 }
579
580 result
581 }
582
583 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
584 if self.must.is_empty() && self.should.is_empty() {
586 return None;
587 }
588
589 let must_preds: Vec<_> = self
591 .must
592 .iter()
593 .map(|q| q.as_doc_predicate(reader))
594 .collect::<Option<Vec<_>>>()?;
595 let should_preds: Vec<_> = self
596 .should
597 .iter()
598 .map(|q| q.as_doc_predicate(reader))
599 .collect::<Option<Vec<_>>>()?;
600 let must_not_preds: Vec<_> = self
601 .must_not
602 .iter()
603 .map(|q| q.as_doc_predicate(reader))
604 .collect::<Option<Vec<_>>>()?;
605
606 let has_must = !must_preds.is_empty();
607
608 Some(Box::new(move |doc_id| {
609 if !must_preds.iter().all(|p| p(doc_id)) {
611 return false;
612 }
613 if !has_must && !should_preds.is_empty() && !should_preds.iter().any(|p| p(doc_id)) {
615 return false;
616 }
617 must_not_preds.iter().all(|p| !p(doc_id))
619 }))
620 }
621
622 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
623 let must = self.must.clone();
624 let should = self.should.clone();
625
626 Box::pin(async move {
627 if !must.is_empty() {
628 let mut estimates = Vec::with_capacity(must.len());
629 for q in &must {
630 estimates.push(q.count_estimate(reader).await?);
631 }
632 estimates
633 .into_iter()
634 .min()
635 .ok_or_else(|| crate::Error::Corruption("Empty must clause".to_string()))
636 } else if !should.is_empty() {
637 let mut sum = 0u32;
638 for q in &should {
639 sum = sum.saturating_add(q.count_estimate(reader).await?);
640 }
641 Ok(sum)
642 } else {
643 Ok(0)
644 }
645 })
646 }
647}
648
649struct BooleanScorer<'a> {
650 must: Vec<Box<dyn Scorer + 'a>>,
651 should: Vec<Box<dyn Scorer + 'a>>,
652 must_not: Vec<Box<dyn Scorer + 'a>>,
653 current_doc: DocId,
654}
655
656impl BooleanScorer<'_> {
657 fn find_next_match(&mut self) -> DocId {
658 if self.must.is_empty() && self.should.is_empty() {
659 return TERMINATED;
660 }
661
662 loop {
663 let candidate = if !self.must.is_empty() {
664 let mut max_doc = self
665 .must
666 .iter()
667 .map(|s| s.doc())
668 .max()
669 .unwrap_or(TERMINATED);
670
671 if max_doc == TERMINATED {
672 return TERMINATED;
673 }
674
675 loop {
676 let mut all_match = true;
677 for scorer in &mut self.must {
678 let doc = scorer.seek(max_doc);
679 if doc == TERMINATED {
680 return TERMINATED;
681 }
682 if doc > max_doc {
683 max_doc = doc;
684 all_match = false;
685 break;
686 }
687 }
688 if all_match {
689 break;
690 }
691 }
692 max_doc
693 } else {
694 self.should
695 .iter()
696 .map(|s| s.doc())
697 .filter(|&d| d != TERMINATED)
698 .min()
699 .unwrap_or(TERMINATED)
700 };
701
702 if candidate == TERMINATED {
703 return TERMINATED;
704 }
705
706 let excluded = self.must_not.iter_mut().any(|scorer| {
707 let doc = scorer.seek(candidate);
708 doc == candidate
709 });
710
711 if !excluded {
712 for scorer in &mut self.should {
714 scorer.seek(candidate);
715 }
716 self.current_doc = candidate;
717 return candidate;
718 }
719
720 if !self.must.is_empty() {
722 for scorer in &mut self.must {
723 scorer.advance();
724 }
725 } else {
726 for scorer in &mut self.should {
728 if scorer.doc() <= candidate && scorer.doc() != TERMINATED {
729 scorer.seek(candidate + 1);
730 }
731 }
732 }
733 }
734 }
735}
736
737impl super::docset::DocSet for BooleanScorer<'_> {
738 fn doc(&self) -> DocId {
739 self.current_doc
740 }
741
742 fn advance(&mut self) -> DocId {
743 if !self.must.is_empty() {
744 for scorer in &mut self.must {
745 scorer.advance();
746 }
747 } else {
748 for scorer in &mut self.should {
749 if scorer.doc() == self.current_doc {
750 scorer.advance();
751 }
752 }
753 }
754
755 self.current_doc = self.find_next_match();
756 self.current_doc
757 }
758
759 fn seek(&mut self, target: DocId) -> DocId {
760 for scorer in &mut self.must {
761 scorer.seek(target);
762 }
763
764 for scorer in &mut self.should {
765 scorer.seek(target);
766 }
767
768 self.current_doc = self.find_next_match();
769 self.current_doc
770 }
771
772 fn size_hint(&self) -> u32 {
773 if !self.must.is_empty() {
774 self.must.iter().map(|s| s.size_hint()).min().unwrap_or(0)
775 } else {
776 self.should.iter().map(|s| s.size_hint()).sum()
777 }
778 }
779}
780
781impl Scorer for BooleanScorer<'_> {
782 fn score(&self) -> Score {
783 let mut total = 0.0;
784
785 for scorer in &self.must {
786 if scorer.doc() == self.current_doc {
787 total += scorer.score();
788 }
789 }
790
791 for scorer in &self.should {
792 if scorer.doc() == self.current_doc {
793 total += scorer.score();
794 }
795 }
796
797 total
798 }
799
800 fn matched_positions(&self) -> Option<super::MatchedPositions> {
801 let mut all_positions: super::MatchedPositions = Vec::new();
802
803 for scorer in &self.must {
804 if scorer.doc() == self.current_doc
805 && let Some(positions) = scorer.matched_positions()
806 {
807 all_positions.extend(positions);
808 }
809 }
810
811 for scorer in &self.should {
812 if scorer.doc() == self.current_doc
813 && let Some(positions) = scorer.matched_positions()
814 {
815 all_positions.extend(positions);
816 }
817 }
818
819 if all_positions.is_empty() {
820 None
821 } else {
822 Some(all_positions)
823 }
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use crate::dsl::Field;
831 use crate::query::{QueryDecomposition, TermQuery};
832
833 #[test]
834 fn test_maxscore_eligible_pure_or_same_field() {
835 let query = BooleanQuery::new()
837 .should(TermQuery::text(Field(0), "hello"))
838 .should(TermQuery::text(Field(0), "world"))
839 .should(TermQuery::text(Field(0), "foo"));
840
841 assert!(
843 query
844 .should
845 .iter()
846 .all(|q| matches!(q.decompose(), QueryDecomposition::TextTerm(_)))
847 );
848
849 let infos: Vec<_> = query
851 .should
852 .iter()
853 .filter_map(|q| match q.decompose() {
854 QueryDecomposition::TextTerm(info) => Some(info),
855 _ => None,
856 })
857 .collect();
858 assert_eq!(infos.len(), 3);
859 assert!(infos.iter().all(|i| i.field == Field(0)));
860 }
861
862 #[test]
863 fn test_maxscore_not_eligible_different_fields() {
864 let query = BooleanQuery::new()
866 .should(TermQuery::text(Field(0), "hello"))
867 .should(TermQuery::text(Field(1), "world")); let infos: Vec<_> = query
870 .should
871 .iter()
872 .filter_map(|q| match q.decompose() {
873 QueryDecomposition::TextTerm(info) => Some(info),
874 _ => None,
875 })
876 .collect();
877 assert_eq!(infos.len(), 2);
878 assert!(infos[0].field != infos[1].field);
880 }
881
882 #[test]
883 fn test_maxscore_not_eligible_with_must() {
884 let query = BooleanQuery::new()
886 .must(TermQuery::text(Field(0), "required"))
887 .should(TermQuery::text(Field(0), "hello"))
888 .should(TermQuery::text(Field(0), "world"));
889
890 assert!(!query.must.is_empty());
892 }
893
894 #[test]
895 fn test_maxscore_not_eligible_with_must_not() {
896 let query = BooleanQuery::new()
898 .should(TermQuery::text(Field(0), "hello"))
899 .should(TermQuery::text(Field(0), "world"))
900 .must_not(TermQuery::text(Field(0), "excluded"));
901
902 assert!(!query.must_not.is_empty());
904 }
905
906 #[test]
907 fn test_maxscore_not_eligible_single_term() {
908 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
910
911 assert_eq!(query.should.len(), 1);
913 }
914
915 #[test]
916 fn test_term_query_info_extraction() {
917 let term_query = TermQuery::text(Field(42), "test");
918 match term_query.decompose() {
919 QueryDecomposition::TextTerm(info) => {
920 assert_eq!(info.field, Field(42));
921 assert_eq!(info.term, b"test");
922 }
923 _ => panic!("Expected TextTerm decomposition"),
924 }
925 }
926
927 #[test]
928 fn test_boolean_query_no_term_info() {
929 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
931
932 assert!(matches!(query.decompose(), QueryDecomposition::Opaque));
933 }
934}