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_chunked_text_maxscore, finish_text_maxscore,
13 prepare_per_field_grouping, prepare_text_maxscore, text_maxscore_allowed,
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 let Some((mut infos, text_field, avg_field_len, num_docs)) =
205 prepare_text_maxscore(should, reader, global_stats)
206 && text_maxscore_allowed(reader, text_field, scorer_options.collect_positions)
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 if reader.is_chunked_field(text_field) {
219 return finish_chunked_text_maxscore(posting_lists, limit, reader, text_field);
220 }
221 let shared_threshold = std::cell::Cell::new(scorer_options.initial_threshold);
227 return finish_text_maxscore(
228 posting_lists,
229 avg_field_len,
230 limit,
231 &shared_threshold,
232 reader.schema().index_label(),
233 reader.schema().get_field_name(text_field).unwrap_or("?"),
234 );
235 }
236
237 if let Some(infos) =
240 shared_or_extract_sparse_infos(scorer_options.lsp_plan.as_ref(), should)
241 {
242 if let Some((raw, info)) =
243 build_sparse_bmp_results(&infos, reader, limit, &scorer_options)?
244 {
245 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
246 }
247 if let Some((executor, info)) =
248 build_sparse_maxscore_executor(&infos, reader, limit, None)
249 {
250 let raw = executor.$execute_fn() $(. $aw)* ?;
251 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
252 }
253 }
254
255 if let Some(grouping) = prepare_per_field_grouping(
257 should,
258 reader,
259 limit,
260 global_stats,
261 scorer_options.collect_positions,
262 ) {
263 let mut scorers: Vec<Box<dyn Scorer + '_>> = Vec::new();
264 let shared_threshold = std::cell::Cell::new(0.0f32);
266 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
267 let corpus_size = reader.text_corpus_size(*field);
269 let mut posting_lists = Vec::with_capacity(infos.len());
270 for info in infos {
271 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
272 $(. $aw)* ?
273 {
274 let idf = compute_idf(
275 &pl, *field, &info.term, corpus_size, global_stats,
276 );
277 posting_lists.push((pl, idf));
278 }
279 }
280 if reader.is_chunked_field(*field) {
281 scorers.push(finish_chunked_text_maxscore(
282 posting_lists,
283 grouping.per_field_limit,
284 reader,
285 *field,
286 )?);
287 } else if !posting_lists.is_empty() {
288 scorers.push(finish_text_maxscore(
289 posting_lists,
290 *avg_field_len,
291 grouping.per_field_limit,
292 &shared_threshold,
293 reader.schema().index_label(),
294 reader.schema().get_field_name(*field).unwrap_or("?"),
295 )?);
296 }
297 }
298 for &idx in &grouping.fallback_indices {
299 scorers.push(should[idx].$scorer_fn(
300 reader,
301 limit,
302 scorer_options.without_threshold(),
303 ) $(. $aw)* ?);
304 }
305 return Ok(build_should_scorer(scorers));
306 }
307 }
308
309 if !should.is_empty() && !must.is_empty() {
316 let should_is_sparse = scorer_options.lsp_plan.is_some()
323 || extract_all_sparse_infos(should).is_some();
324 let bitset_predicates_allowed = should_is_sparse && !scorer_options.collect_positions;
325
326 let mut predicates: Vec<super::DocPredicate<'_>> = Vec::new();
332 let mut must_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
333 for q in must {
334 if let Some(pred) = q.as_doc_predicate(reader) {
335 log::debug!("BooleanQuery planner 3a: MUST clause → predicate ({})", q);
336 predicates.push(pred);
337 } else if bitset_predicates_allowed {
338 if let Some(bitset) = q.as_doc_bitset(reader) {
339 log::debug!("BooleanQuery planner 3a: MUST clause → bitset predicate ({})", q);
340 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
341 } else {
342 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
343 must_verifiers.push(q.$scorer_fn(
344 reader, limit, scorer_options.without_threshold()
345 ) $(. $aw)* ?);
346 }
347 } else {
348 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
349 must_verifiers.push(q.$scorer_fn(
350 reader, limit, scorer_options.without_threshold()
351 ) $(. $aw)* ?);
352 }
353 }
354 let mut must_not_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
356 for q in must_not {
357 if let Some(pred) = q.as_doc_predicate(reader) {
358 let negated: super::DocPredicate<'_> =
359 Box::new(move |doc_id| !pred(doc_id));
360 predicates.push(negated);
361 } else if bitset_predicates_allowed {
362 if let Some(bitset) = q.as_doc_bitset(reader) {
363 log::debug!("BooleanQuery planner 3a: MUST_NOT clause → bitset predicate ({})", q);
364 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
365 } else {
366 must_not_verifiers.push(q.$scorer_fn(
367 reader, limit, scorer_options.without_threshold()
368 ) $(. $aw)* ?);
369 }
370 } else {
371 must_not_verifiers.push(q.$scorer_fn(
372 reader, limit, scorer_options.without_threshold()
373 ) $(. $aw)* ?);
374 }
375 }
376
377 if must_verifiers.is_empty()
379 && must_not_verifiers.is_empty()
380 && !predicates.is_empty()
381 {
382 let sparse_infos =
383 shared_or_extract_sparse_infos(scorer_options.lsp_plan.as_ref(), should);
384 if let Some(infos) = sparse_infos {
385 let bitset_result = build_combined_bitset(must, must_not, reader);
388 if let Some(ref bitset) = bitset_result {
389 let bitset_pred = |doc_id: crate::DocId| bitset.contains(doc_id);
390 if let Some((raw, info)) =
391 build_sparse_bmp_results_filtered(
392 &infos, reader, limit, &bitset_pred, &scorer_options
393 )?
394 {
395 log::debug!(
396 "BooleanQuery planner: bitset-aware sparse BMP, {} dims, {} matching docs",
397 infos.len(),
398 bitset.count()
399 );
400 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
401 }
402 }
403
404 let combined = chain_predicates(predicates);
406 if let Some((raw, info)) =
407 build_sparse_bmp_results_filtered(
408 &infos, reader, limit, &*combined, &scorer_options
409 )?
410 {
411 log::debug!(
412 "BooleanQuery planner: predicate-aware sparse BMP, {} dims",
413 infos.len()
414 );
415 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
416 }
417 if let Some((executor, info)) =
419 build_sparse_maxscore_executor(&infos, reader, limit, Some(combined))
420 {
421 log::debug!(
422 "BooleanQuery planner: predicate-aware sparse MaxScore, {} dims",
423 infos.len()
424 );
425 let raw = executor.$execute_fn() $(. $aw)* ?;
426 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
427 }
428 predicates = Vec::new();
432 for q in must {
433 if let Some(pred) = q.as_doc_predicate(reader) {
434 predicates.push(pred);
435 } else if let Some(bitset) = q.as_doc_bitset(reader) {
436 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
437 }
438 }
439 for q in must_not {
440 if let Some(pred) = q.as_doc_predicate(reader) {
441 let negated: super::DocPredicate<'_> =
442 Box::new(move |doc_id| !pred(doc_id));
443 predicates.push(negated);
444 } else if let Some(bitset) = q.as_doc_bitset(reader) {
445 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
446 }
447 }
448 }
449 }
450
451 let has_filters = !predicates.is_empty()
454 || !must_verifiers.is_empty()
455 || !must_not_verifiers.is_empty();
456 let should_limit = if has_filters {
457 super::max_candidate_limit(limit)
458 } else {
459 limit
460 };
461 let mut should_options = scorer_options.without_threshold();
462 if should_is_sparse {
463 should_options.lsp_plan = scorer_options.lsp_plan.clone();
469 }
470 let should_scorer = if should.len() == 1 {
471 should[0].$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
472 } else {
473 let sub = BooleanQuery {
474 must: Vec::new(),
475 should: should.to_vec(),
476 must_not: Vec::new(),
477 global_stats: global_stats.cloned(),
478 };
479 sub.$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
480 };
481
482 let use_predicated =
483 must_verifiers.is_empty() || should_scorer.size_hint() >= limit as u32;
484
485 if use_predicated {
486 log::debug!(
487 "BooleanQuery planner: PredicatedScorer {} preds + {} must_v + {} must_not_v, \
488 SHOULD size_hint={}, over_fetch={}",
489 predicates.len(), must_verifiers.len(), must_not_verifiers.len(),
490 should_scorer.size_hint(), should_limit
491 );
492 return Ok(Box::new(super::PredicatedScorer::new(
493 should_scorer, predicates, must_verifiers, must_not_verifiers,
494 )));
495 }
496
497 log::debug!(
499 "BooleanQuery planner: BooleanScorer fallback, size_hint={} < limit={}, \
500 {} must_v + {} must_not_v",
501 should_scorer.size_hint(), limit,
502 must_verifiers.len(), must_not_verifiers.len()
503 );
504 let mut scorer = BooleanScorer {
505 must: must_verifiers,
506 should: vec![should_scorer],
507 must_not: must_not_verifiers,
508 current_doc: 0,
509 };
510 scorer.current_doc = scorer.find_next_match();
511 return Ok(Box::new(scorer));
512 }
513
514 let mut must_scorers = Vec::with_capacity(must.len());
516 for q in must {
517 must_scorers.push(q.$scorer_fn(
518 reader, limit, scorer_options.without_threshold()
519 ) $(. $aw)* ?);
520 }
521 let mut should_scorers = Vec::with_capacity(should.len());
522 for q in should {
523 should_scorers.push(q.$scorer_fn(
524 reader, limit, scorer_options.without_threshold()
525 ) $(. $aw)* ?);
526 }
527 let mut must_not_scorers = Vec::with_capacity(must_not.len());
528 for q in must_not {
529 must_not_scorers.push(q.$scorer_fn(
530 reader, limit, scorer_options.without_threshold()
531 ) $(. $aw)* ?);
532 }
533 let mut scorer = BooleanScorer {
534 must: must_scorers,
535 should: should_scorers,
536 must_not: must_not_scorers,
537 current_doc: 0,
538 };
539 scorer.current_doc = scorer.find_next_match();
540 Ok(Box::new(scorer) as Box<dyn Scorer + '_>)
541 }};
542}
543
544impl Query for BooleanQuery {
545 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
546 self.scorer_with_options(reader, limit, super::ScorerOptions::with_positions())
547 }
548
549 fn scorer_with_options<'a>(
550 &self,
551 reader: &'a SegmentReader,
552 limit: usize,
553 options: super::ScorerOptions,
554 ) -> ScorerFuture<'a> {
555 let must = self.must.clone();
556 let should = self.should.clone();
557 let must_not = self.must_not.clone();
558 let global_stats = self.global_stats.clone();
559 Box::pin(async move {
560 boolean_plan!(
561 must,
562 should,
563 must_not,
564 global_stats.as_ref(),
565 reader,
566 limit,
567 options,
568 scorer_with_options,
569 get_postings,
570 execute,
571 await
572 )
573 })
574 }
575
576 #[cfg(feature = "sync")]
577 fn scorer_sync<'a>(
578 &self,
579 reader: &'a SegmentReader,
580 limit: usize,
581 ) -> crate::Result<Box<dyn Scorer + 'a>> {
582 self.scorer_sync_with_options(reader, limit, super::ScorerOptions::with_positions())
583 }
584
585 #[cfg(feature = "sync")]
586 fn scorer_sync_with_options<'a>(
587 &self,
588 reader: &'a SegmentReader,
589 limit: usize,
590 options: super::ScorerOptions,
591 ) -> crate::Result<Box<dyn Scorer + 'a>> {
592 boolean_plan!(
593 self.must,
594 self.should,
595 self.must_not,
596 self.global_stats.as_ref(),
597 reader,
598 limit,
599 options,
600 scorer_sync_with_options,
601 get_postings_sync,
602 execute_sync
603 )
604 }
605
606 fn decompose(&self) -> super::QueryDecomposition {
607 if self.should.is_empty() || self.must.iter().any(|query| !query.is_filter()) {
613 return super::QueryDecomposition::Opaque;
614 }
615 extract_all_sparse_infos(&self.should)
616 .map(super::QueryDecomposition::SparseTerms)
617 .unwrap_or(super::QueryDecomposition::Opaque)
618 }
619
620 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
621 if self.must.is_empty() && self.should.is_empty() {
622 return None;
623 }
624
625 let num_docs = reader.num_docs();
626
627 let mut result: Option<super::DocBitset> = None;
629 for q in &self.must {
630 let bs = q.as_doc_bitset(reader)?;
631 match result {
632 None => result = Some(bs),
633 Some(ref mut acc) => acc.intersect_with(&bs),
634 }
635 }
636
637 if !self.should.is_empty() {
639 let mut should_union = super::DocBitset::new(num_docs);
640 for q in &self.should {
641 let bs = q.as_doc_bitset(reader)?;
642 should_union.union_with(&bs);
643 }
644 match result {
645 None => result = Some(should_union),
646 Some(ref mut acc) => {
647 if self.must.is_empty() {
650 *acc = should_union;
651 }
652 }
653 }
654 }
655
656 if let Some(ref mut acc) = result {
658 for q in &self.must_not {
659 {
660 let bs = q.as_doc_bitset(reader)?;
661 acc.subtract(&bs);
662 }
663 }
664 }
665
666 result
667 }
668
669 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
670 if self.must.is_empty() && self.should.is_empty() {
672 return None;
673 }
674
675 let must_preds: Vec<_> = self
677 .must
678 .iter()
679 .map(|q| q.as_doc_predicate(reader))
680 .collect::<Option<Vec<_>>>()?;
681 let should_preds: Vec<_> = self
682 .should
683 .iter()
684 .map(|q| q.as_doc_predicate(reader))
685 .collect::<Option<Vec<_>>>()?;
686 let must_not_preds: Vec<_> = self
687 .must_not
688 .iter()
689 .map(|q| q.as_doc_predicate(reader))
690 .collect::<Option<Vec<_>>>()?;
691
692 let has_must = !must_preds.is_empty();
693
694 Some(Box::new(move |doc_id| {
695 if !must_preds.iter().all(|p| p(doc_id)) {
697 return false;
698 }
699 if !has_must && !should_preds.is_empty() && !should_preds.iter().any(|p| p(doc_id)) {
701 return false;
702 }
703 must_not_preds.iter().all(|p| !p(doc_id))
705 }))
706 }
707
708 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
709 let must = self.must.clone();
710 let should = self.should.clone();
711
712 Box::pin(async move {
713 if !must.is_empty() {
714 let mut estimates = Vec::with_capacity(must.len());
715 for q in &must {
716 estimates.push(q.count_estimate(reader).await?);
717 }
718 estimates
719 .into_iter()
720 .min()
721 .ok_or_else(|| crate::Error::Corruption("Empty must clause".to_string()))
722 } else if !should.is_empty() {
723 let mut sum = 0u32;
724 for q in &should {
725 sum = sum.saturating_add(q.count_estimate(reader).await?);
726 }
727 Ok(sum)
728 } else {
729 Ok(0)
730 }
731 })
732 }
733}
734
735struct BooleanScorer<'a> {
736 must: Vec<Box<dyn Scorer + 'a>>,
737 should: Vec<Box<dyn Scorer + 'a>>,
738 must_not: Vec<Box<dyn Scorer + 'a>>,
739 current_doc: DocId,
740}
741
742impl BooleanScorer<'_> {
743 fn find_next_match(&mut self) -> DocId {
744 if self.must.is_empty() && self.should.is_empty() {
745 return TERMINATED;
746 }
747
748 loop {
749 let candidate = if !self.must.is_empty() {
750 let mut max_doc = self
751 .must
752 .iter()
753 .map(|s| s.doc())
754 .max()
755 .unwrap_or(TERMINATED);
756
757 if max_doc == TERMINATED {
758 return TERMINATED;
759 }
760
761 loop {
762 let mut all_match = true;
763 for scorer in &mut self.must {
764 let doc = scorer.seek(max_doc);
765 if doc == TERMINATED {
766 return TERMINATED;
767 }
768 if doc > max_doc {
769 max_doc = doc;
770 all_match = false;
771 break;
772 }
773 }
774 if all_match {
775 break;
776 }
777 }
778 max_doc
779 } else {
780 self.should
781 .iter()
782 .map(|s| s.doc())
783 .filter(|&d| d != TERMINATED)
784 .min()
785 .unwrap_or(TERMINATED)
786 };
787
788 if candidate == TERMINATED {
789 return TERMINATED;
790 }
791
792 let excluded = self.must_not.iter_mut().any(|scorer| {
793 let doc = scorer.seek(candidate);
794 doc == candidate
795 });
796
797 if !excluded {
798 for scorer in &mut self.should {
800 scorer.seek(candidate);
801 }
802 self.current_doc = candidate;
803 return candidate;
804 }
805
806 if !self.must.is_empty() {
808 for scorer in &mut self.must {
809 scorer.advance();
810 }
811 } else {
812 for scorer in &mut self.should {
814 if scorer.doc() <= candidate && scorer.doc() != TERMINATED {
815 scorer.seek(candidate + 1);
816 }
817 }
818 }
819 }
820 }
821}
822
823impl super::docset::DocSet for BooleanScorer<'_> {
824 fn doc(&self) -> DocId {
825 self.current_doc
826 }
827
828 fn advance(&mut self) -> DocId {
829 if !self.must.is_empty() {
830 for scorer in &mut self.must {
831 scorer.advance();
832 }
833 } else {
834 for scorer in &mut self.should {
835 if scorer.doc() == self.current_doc {
836 scorer.advance();
837 }
838 }
839 }
840
841 self.current_doc = self.find_next_match();
842 self.current_doc
843 }
844
845 fn seek(&mut self, target: DocId) -> DocId {
846 for scorer in &mut self.must {
847 scorer.seek(target);
848 }
849
850 for scorer in &mut self.should {
851 scorer.seek(target);
852 }
853
854 self.current_doc = self.find_next_match();
855 self.current_doc
856 }
857
858 fn size_hint(&self) -> u32 {
859 if !self.must.is_empty() {
860 self.must.iter().map(|s| s.size_hint()).min().unwrap_or(0)
861 } else {
862 self.should.iter().map(|s| s.size_hint()).sum()
863 }
864 }
865}
866
867impl Scorer for BooleanScorer<'_> {
868 fn score(&self) -> Score {
869 let mut total = 0.0;
870
871 for scorer in &self.must {
872 if scorer.doc() == self.current_doc {
873 total += scorer.score();
874 }
875 }
876
877 for scorer in &self.should {
878 if scorer.doc() == self.current_doc {
879 total += scorer.score();
880 }
881 }
882
883 total
884 }
885
886 fn matched_positions(&self) -> Option<super::MatchedPositions> {
887 let mut all_positions: super::MatchedPositions = Vec::new();
888
889 for scorer in &self.must {
890 if scorer.doc() == self.current_doc
891 && let Some(positions) = scorer.matched_positions()
892 {
893 all_positions.extend(positions);
894 }
895 }
896
897 for scorer in &self.should {
898 if scorer.doc() == self.current_doc
899 && let Some(positions) = scorer.matched_positions()
900 {
901 all_positions.extend(positions);
902 }
903 }
904
905 if all_positions.is_empty() {
906 None
907 } else {
908 Some(merge_matched_positions(all_positions))
909 }
910 }
911}
912
913pub(super) fn merge_matched_positions(
921 positions: super::MatchedPositions,
922) -> super::MatchedPositions {
923 if positions.len() < 2 {
924 return positions;
925 }
926 let mut merged: super::MatchedPositions = Vec::with_capacity(positions.len());
927 for (field_id, scored) in positions {
928 match merged
929 .iter_mut()
930 .find(|(existing, _)| *existing == field_id)
931 {
932 Some((_, existing)) => existing.extend(scored),
933 None => merged.push((field_id, scored)),
934 }
935 }
936 for (_, scored) in &mut merged {
937 if scored.len() < 2 {
938 continue;
939 }
940 scored.sort_by_key(|sp| sp.position);
941 let mut write = 0usize;
942 for read in 1..scored.len() {
943 if scored[read].position == scored[write].position {
944 scored[write].score += scored[read].score;
945 } else {
946 write += 1;
947 scored[write] = scored[read];
948 }
949 }
950 scored.truncate(write + 1);
951 }
952 merged
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958 use crate::dsl::Field;
959 use crate::query::{QueryDecomposition, TermQuery};
960
961 #[test]
962 fn test_maxscore_eligible_pure_or_same_field() {
963 let query = BooleanQuery::new()
965 .should(TermQuery::text(Field(0), "hello"))
966 .should(TermQuery::text(Field(0), "world"))
967 .should(TermQuery::text(Field(0), "foo"));
968
969 assert!(
971 query
972 .should
973 .iter()
974 .all(|q| matches!(q.decompose(), QueryDecomposition::TextTerm(_)))
975 );
976
977 let infos: Vec<_> = query
979 .should
980 .iter()
981 .filter_map(|q| match q.decompose() {
982 QueryDecomposition::TextTerm(info) => Some(info),
983 _ => None,
984 })
985 .collect();
986 assert_eq!(infos.len(), 3);
987 assert!(infos.iter().all(|i| i.field == Field(0)));
988 }
989
990 #[test]
991 fn test_maxscore_not_eligible_different_fields() {
992 let query = BooleanQuery::new()
994 .should(TermQuery::text(Field(0), "hello"))
995 .should(TermQuery::text(Field(1), "world")); let infos: Vec<_> = query
998 .should
999 .iter()
1000 .filter_map(|q| match q.decompose() {
1001 QueryDecomposition::TextTerm(info) => Some(info),
1002 _ => None,
1003 })
1004 .collect();
1005 assert_eq!(infos.len(), 2);
1006 assert!(infos[0].field != infos[1].field);
1008 }
1009
1010 #[test]
1011 fn test_maxscore_not_eligible_with_must() {
1012 let query = BooleanQuery::new()
1014 .must(TermQuery::text(Field(0), "required"))
1015 .should(TermQuery::text(Field(0), "hello"))
1016 .should(TermQuery::text(Field(0), "world"));
1017
1018 assert!(!query.must.is_empty());
1020 }
1021
1022 #[test]
1023 fn test_maxscore_not_eligible_with_must_not() {
1024 let query = BooleanQuery::new()
1026 .should(TermQuery::text(Field(0), "hello"))
1027 .should(TermQuery::text(Field(0), "world"))
1028 .must_not(TermQuery::text(Field(0), "excluded"));
1029
1030 assert!(!query.must_not.is_empty());
1032 }
1033
1034 #[test]
1035 fn test_maxscore_not_eligible_single_term() {
1036 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
1038
1039 assert_eq!(query.should.len(), 1);
1041 }
1042
1043 #[test]
1044 fn test_term_query_info_extraction() {
1045 let term_query = TermQuery::text(Field(42), "test");
1046 match term_query.decompose() {
1047 QueryDecomposition::TextTerm(info) => {
1048 assert_eq!(info.field, Field(42));
1049 assert_eq!(info.term, b"test");
1050 }
1051 _ => panic!("Expected TextTerm decomposition"),
1052 }
1053 }
1054
1055 #[test]
1056 fn test_boolean_query_no_term_info() {
1057 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
1059
1060 assert!(matches!(query.decompose(), QueryDecomposition::Opaque));
1061 }
1062}