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