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, cap_terms, chain_predicates, combine_sparse_results,
12 compute_idf, 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(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 proximity: Option<super::ProximityConfig>,
31 text_heap_factor: f32,
34 max_terms: usize,
37}
38
39fn shared_or_extract_sparse_infos<'a>(
40 plan: Option<&'a Arc<super::bmp::LspSegmentPlan>>,
41 should: &[Arc<dyn Query>],
42) -> Option<std::borrow::Cow<'a, [super::SparseTermQueryInfo]>> {
43 plan.map(|plan| std::borrow::Cow::Borrowed(plan.infos.as_ref()))
44 .or_else(|| extract_all_sparse_infos(should).map(std::borrow::Cow::Owned))
45}
46
47impl std::fmt::Debug for BooleanQuery {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("BooleanQuery")
50 .field("must_count", &self.must.len())
51 .field("should_count", &self.should.len())
52 .field("must_not_count", &self.must_not.len())
53 .field("has_global_stats", &self.global_stats.is_some())
54 .field("proximity", &self.proximity)
55 .finish()
56 }
57}
58
59impl std::fmt::Display for BooleanQuery {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "Boolean(")?;
62 let mut first = true;
63 for q in &self.must {
64 if !first {
65 write!(f, " ")?;
66 }
67 write!(f, "+{}", q)?;
68 first = false;
69 }
70 for q in &self.should {
71 if !first {
72 write!(f, " ")?;
73 }
74 write!(f, "{}", q)?;
75 first = false;
76 }
77 for q in &self.must_not {
78 if !first {
79 write!(f, " ")?;
80 }
81 write!(f, "-{}", q)?;
82 first = false;
83 }
84 if let Some(proximity) = &self.proximity {
85 write!(f, " ~proximity({}, {})", proximity.weight, proximity.window)?;
86 }
87 if self.text_heap_factor > 1.0 {
88 write!(f, " ~heap({})", self.text_heap_factor)?;
89 }
90 if self.max_terms > 0 {
91 write!(f, " ~max_terms({})", self.max_terms)?;
92 }
93 write!(f, ")")
94 }
95}
96
97impl Default for BooleanQuery {
98 fn default() -> Self {
99 Self {
100 must: Vec::new(),
101 should: Vec::new(),
102 must_not: Vec::new(),
103 global_stats: None,
104 proximity: None,
105 text_heap_factor: 1.0,
106 max_terms: 0,
107 }
108 }
109}
110
111impl BooleanQuery {
112 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn must(mut self, query: impl Query + 'static) -> Self {
117 self.must.push(Arc::new(query));
118 self
119 }
120
121 pub fn should(mut self, query: impl Query + 'static) -> Self {
122 self.should.push(Arc::new(query));
123 self
124 }
125
126 pub fn must_not(mut self, query: impl Query + 'static) -> Self {
127 self.must_not.push(Arc::new(query));
128 self
129 }
130
131 pub fn with_global_stats(mut self, stats: Arc<GlobalStats>) -> Self {
133 self.global_stats = Some(stats);
134 self
135 }
136
137 pub fn with_proximity(mut self, config: super::ProximityConfig) -> Self {
141 self.proximity = config.is_active().then_some(config);
142 self
143 }
144
145 pub fn with_text_heap_factor(mut self, heap_factor: f32) -> Self {
148 self.text_heap_factor = if heap_factor > 1.0 { heap_factor } else { 1.0 };
149 self
150 }
151
152 pub fn with_max_terms(mut self, max_terms: usize) -> Self {
155 self.max_terms = max_terms;
156 self
157 }
158}
159
160fn build_should_scorer<'a>(scorers: Vec<Box<dyn Scorer + 'a>>) -> Box<dyn Scorer + 'a> {
162 if scorers.is_empty() {
163 return Box::new(EmptyScorer);
164 }
165 if scorers.len() == 1 {
166 return scorers.into_iter().next().unwrap();
167 }
168 let mut scorer = BooleanScorer {
169 must: vec![],
170 should: scorers,
171 must_not: vec![],
172 current_doc: 0,
173 };
174 scorer.current_doc = scorer.find_next_match();
175 Box::new(scorer)
176}
177
178macro_rules! boolean_plan {
192 ($must:expr, $should:expr, $must_not:expr, $global_stats:expr, $proximity:expr, $text_tuning:expr,
193 $reader:expr, $limit:expr, $scorer_options:expr,
194 $scorer_fn:ident, $get_postings_fn:ident, $execute_fn:ident
195 $(, $aw:tt)*) => {{
196 let must: &[Arc<dyn Query>] = &$must;
197 let should_all: &[Arc<dyn Query>] = &$should;
198 let must_not: &[Arc<dyn Query>] = &$must_not;
199 let global_stats: Option<&Arc<GlobalStats>> = $global_stats;
200 let reader: &SegmentReader = $reader;
201 let limit: usize = $limit;
202 let scorer_options: super::ScorerOptions = $scorer_options;
203
204 let should_capped: Vec<Arc<dyn Query>>;
207 let should: &[Arc<dyn Query>] = if should_all.len() > super::MAX_QUERY_TERMS {
208 let is_predicate: Vec<bool> = should_all
209 .iter()
210 .map(|q| q.is_filter() || q.as_doc_predicate(reader).is_some())
211 .collect();
212 let cursor_count = is_predicate.iter().filter(|&&p| !p).count();
213
214 if cursor_count > super::MAX_QUERY_TERMS {
215 let mut kept = Vec::with_capacity(should_all.len());
216 let mut cursor_kept = 0usize;
217 for (q, &is_pred) in should_all.iter().zip(is_predicate.iter()) {
218 if is_pred {
219 kept.push(q.clone());
220 } else if cursor_kept < super::MAX_QUERY_TERMS {
221 kept.push(q.clone());
222 cursor_kept += 1;
223 }
224 }
225 log::debug!(
226 "BooleanQuery: capping cursor SHOULD from {} to {} ({} fast-field predicates exempt)",
227 cursor_count,
228 super::MAX_QUERY_TERMS,
229 kept.len() - cursor_kept,
230 );
231 should_capped = kept;
232 &should_capped
233 } else {
234 log::debug!(
235 "BooleanQuery: {} SHOULD clauses OK ({} need cursors, {} fast-field predicates)",
236 should_all.len(),
237 cursor_count,
238 should_all.len() - cursor_count,
239 );
240 should_all
241 }
242 } else {
243 should_all
244 };
245
246 if must_not.is_empty() {
248 if must.len() == 1 && should.is_empty() {
249 return must[0].$scorer_fn(reader, limit, scorer_options) $(. $aw)* ;
250 }
251 if should.len() == 1 && must.is_empty() {
252 return should[0].$scorer_fn(reader, limit, scorer_options) $(. $aw)* ;
253 }
254 }
255
256 if must.is_empty() && must_not.is_empty() && should.len() >= 2 {
258 if let Some((mut infos, text_field, avg_field_len, num_docs)) =
260 prepare_text_maxscore(should, reader, global_stats)
261 && text_maxscore_allowed(reader, text_field, scorer_options.collect_positions)
262 {
263 let mut posting_lists = Vec::with_capacity(infos.len());
264 let mut term_bytes: Vec<Vec<u8>> = Vec::new();
265 for info in infos.drain(..) {
266 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
267 $(. $aw)* ?
268 {
269 let idf = compute_idf(&pl, info.field, &info.term, num_docs, global_stats) * info.weight;
270 posting_lists.push((pl, idf));
271 term_bytes.push(info.term.clone());
272 }
273 }
274 cap_terms(&mut posting_lists, &mut term_bytes, $text_tuning.1);
275 if reader.is_chunked_field(text_field) {
277 return finish_chunked_text_maxscore(
278 posting_lists, limit, reader, text_field, None,
279 $proximity.map(|config| (config, term_bytes)),
280 $text_tuning.0,
281 scorer_options.shared_threshold.as_ref(),
282 );
283 }
284 let shared_threshold = std::cell::Cell::new(scorer_options.initial_threshold);
290 return finish_text_maxscore(
291 posting_lists,
292 avg_field_len,
293 reader.doc_lengths(text_field),
294 limit,
295 &shared_threshold,
296 reader,
297 text_field,
298 None,
299 super::Bm25Params::for_field(reader.schema(), text_field),
300 $proximity.map(|config| (config, term_bytes)),
301 $text_tuning.0,
302 scorer_options.shared_threshold.as_ref(),
303 );
304 }
305
306 if let Some(infos) =
309 shared_or_extract_sparse_infos(scorer_options.lsp_plan.as_ref(), should)
310 {
311 if let Some((raw, info)) =
312 build_sparse_bmp_results(&infos, reader, limit, &scorer_options)?
313 {
314 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
315 }
316 if let Some((executor, info)) =
317 build_sparse_maxscore_executor(&infos, reader, limit, None)
318 {
319 let raw = executor.$execute_fn() $(. $aw)* ?;
320 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
321 }
322 }
323
324 if let Some(grouping) = prepare_per_field_grouping(
326 should,
327 reader,
328 limit,
329 global_stats,
330 scorer_options.collect_positions,
331 ) {
332 let mut scorers: Vec<Box<dyn Scorer + '_>> = Vec::new();
333 let shared_threshold = std::cell::Cell::new(0.0f32);
335 for (field, avg_field_len, infos) in &grouping.multi_term_groups {
336 let corpus_size = reader.text_corpus_size(*field);
338 let mut posting_lists = Vec::with_capacity(infos.len());
339 let mut term_bytes: Vec<Vec<u8>> = Vec::new();
340 for info in infos {
341 if let Some(pl) = reader.$get_postings_fn(info.field, &info.term)
342 $(. $aw)* ?
343 {
344 let idf = compute_idf(
345 &pl, *field, &info.term, corpus_size, global_stats,
346 ) * info.weight;
347 posting_lists.push((pl, idf));
348 term_bytes.push(info.term.clone());
349 }
350 }
351 cap_terms(&mut posting_lists, &mut term_bytes, $text_tuning.1);
352 if reader.is_chunked_field(*field) {
353 scorers.push(finish_chunked_text_maxscore(
354 posting_lists,
355 grouping.per_field_limit,
356 reader,
357 *field,
358 None,
359 $proximity.map(|config| (config, term_bytes)),
360 $text_tuning.0,
361 scorer_options.shared_threshold.as_ref(),
362 )?);
363 } else if !posting_lists.is_empty() {
364 scorers.push(finish_text_maxscore(
365 posting_lists,
366 *avg_field_len,
367 reader.doc_lengths(*field),
368 grouping.per_field_limit,
369 &shared_threshold,
370 reader,
371 *field,
372 None,
373 super::Bm25Params::for_field(reader.schema(), *field),
374 $proximity.map(|config| (config, term_bytes)),
375 $text_tuning.0,
376 scorer_options.shared_threshold.as_ref(),
377 )?);
378 }
379 }
380 for &idx in &grouping.fallback_indices {
381 scorers.push(should[idx].$scorer_fn(
382 reader,
383 limit,
384 scorer_options.without_threshold(),
385 ) $(. $aw)* ?);
386 }
387 return Ok(build_should_scorer(scorers));
388 }
389 }
390
391 if !should.is_empty() && !must.is_empty() {
398 let text_groups: Option<Vec<(crate::Field, Vec<super::TermQueryInfo>)>> = {
410 let mut groups: Vec<(crate::Field, Vec<super::TermQueryInfo>)> = Vec::new();
411 let mut all_text = true;
412 for q in should {
413 match q.decompose() {
414 super::QueryDecomposition::TextTerm(info)
415 if text_maxscore_allowed(
416 reader, info.field, scorer_options.collect_positions,
417 ) =>
418 {
419 match groups.iter_mut().find(|(f, _)| *f == info.field) {
420 Some((_, infos)) => infos.push(info),
421 None => groups.push((info.field, vec![info])),
422 }
423 }
424 _ => {
425 all_text = false;
426 break;
427 }
428 }
429 }
430 all_text.then_some(groups)
431 };
432 if let Some(groups) = text_groups
433 && let Some(bitset) = build_combined_bitset(must, must_not, reader)
434 {
435 let bitset = std::sync::Arc::new(bitset);
436 let single_field = groups.len() == 1;
437 let group_limit = if single_field {
438 limit
439 } else {
440 super::max_candidate_limit(limit)
441 .min(reader.num_docs() as usize)
442 .max(1)
443 };
444 let shared_threshold = std::cell::Cell::new(if single_field {
448 scorer_options.initial_threshold
449 } else {
450 0.0
451 });
452 let mut scorers: Vec<Box<dyn Scorer + '_>> = Vec::new();
453 let mut found = 0u32;
454 let mut complete = true;
455 for (field, infos) in groups {
456 let corpus_size = reader.text_corpus_size(field);
457 let avg_field_len = global_stats
458 .map(|s| s.avg_field_len(field))
459 .unwrap_or_else(|| reader.avg_field_len(field));
460 let mut posting_lists = Vec::with_capacity(infos.len());
461 let mut term_bytes: Vec<Vec<u8>> = Vec::new();
462 for info in &infos {
463 if let Some(pl) = reader.$get_postings_fn(field, &info.term) $(. $aw)* ? {
464 let idf = compute_idf(&pl, field, &info.term, corpus_size, global_stats) * info.weight;
465 posting_lists.push((pl, idf));
466 term_bytes.push(info.term.clone());
467 }
468 }
469 cap_terms(&mut posting_lists, &mut term_bytes, $text_tuning.1);
470 let filter = bitset.clone();
471 let predicate: super::DocPredicate<'_> =
472 Box::new(move |doc_id| filter.contains(doc_id));
473 let scorer = if reader.is_chunked_field(field) {
474 finish_chunked_text_maxscore(
475 posting_lists, group_limit, reader, field, Some(predicate),
476 $proximity.map(|config| (config, term_bytes)),
477 $text_tuning.0,
478 scorer_options.shared_threshold.as_ref(),
479 )?
480 } else {
481 finish_text_maxscore(
482 posting_lists,
483 avg_field_len,
484 reader.doc_lengths(field),
485 group_limit,
486 &shared_threshold,
487 reader,
488 field,
489 Some(predicate),
490 super::Bm25Params::for_field(reader.schema(), field),
491 $proximity.map(|config| (config, term_bytes)),
492 $text_tuning.0,
493 scorer_options.shared_threshold.as_ref(),
494 )?
495 };
496 let hits = scorer.size_hint();
497 found = found.saturating_add(hits);
498 if hits as usize >= group_limit {
499 complete = false;
500 }
501 scorers.push(scorer);
502 }
503 log::debug!(
504 "BooleanQuery planner: bitset-aware text MaxScore, {} field group(s), \
505 {} filtered docs, {} scored hits",
506 scorers.len(),
507 bitset.count(),
508 found
509 );
510 let should_scorer = build_should_scorer(scorers);
511 if complete && (found as usize) < limit && bitset.count() > found {
512 return Ok(Box::new(super::planner::BitsetFillScorer::new(
513 should_scorer,
514 bitset,
515 )));
516 }
517 return Ok(should_scorer);
518 }
519
520 let should_is_sparse = scorer_options.lsp_plan.is_some()
527 || extract_all_sparse_infos(should).is_some();
528 let bitset_predicates_allowed = should_is_sparse && !scorer_options.collect_positions;
529
530 let mut predicates: Vec<super::DocPredicate<'_>> = Vec::new();
536 let mut must_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
537 for q in must {
538 if let Some(pred) = q.as_doc_predicate(reader) {
539 log::debug!("BooleanQuery planner 3a: MUST clause → predicate ({})", q);
540 predicates.push(pred);
541 } else if bitset_predicates_allowed {
542 if let Some(bitset) = q.as_doc_bitset(reader) {
543 log::debug!("BooleanQuery planner 3a: MUST clause → bitset predicate ({})", q);
544 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
545 } else {
546 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
547 must_verifiers.push(q.$scorer_fn(
548 reader, limit, scorer_options.without_threshold()
549 ) $(. $aw)* ?);
550 }
551 } else {
552 log::debug!("BooleanQuery planner 3a: MUST clause → verifier scorer ({})", q);
553 must_verifiers.push(q.$scorer_fn(
554 reader, limit, scorer_options.without_threshold()
555 ) $(. $aw)* ?);
556 }
557 }
558 let mut must_not_verifiers: Vec<Box<dyn super::Scorer + '_>> = Vec::new();
560 for q in must_not {
561 if let Some(pred) = q.as_doc_predicate(reader) {
562 let negated: super::DocPredicate<'_> =
563 Box::new(move |doc_id| !pred(doc_id));
564 predicates.push(negated);
565 } else if bitset_predicates_allowed {
566 if let Some(bitset) = q.as_doc_bitset(reader) {
567 log::debug!("BooleanQuery planner 3a: MUST_NOT clause → bitset predicate ({})", q);
568 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
569 } else {
570 must_not_verifiers.push(q.$scorer_fn(
571 reader, limit, scorer_options.without_threshold()
572 ) $(. $aw)* ?);
573 }
574 } else {
575 must_not_verifiers.push(q.$scorer_fn(
576 reader, limit, scorer_options.without_threshold()
577 ) $(. $aw)* ?);
578 }
579 }
580
581 if must_verifiers.is_empty()
583 && must_not_verifiers.is_empty()
584 && !predicates.is_empty()
585 {
586 let sparse_infos =
587 shared_or_extract_sparse_infos(scorer_options.lsp_plan.as_ref(), should);
588 if let Some(infos) = sparse_infos {
589 let bitset_result = build_combined_bitset(must, must_not, reader);
592 if let Some(ref bitset) = bitset_result {
593 let bitset_pred = |doc_id: crate::DocId| bitset.contains(doc_id);
594 if let Some((raw, info)) =
595 build_sparse_bmp_results_filtered(
596 &infos, reader, limit, &bitset_pred, &scorer_options
597 )?
598 {
599 log::debug!(
600 "BooleanQuery planner: bitset-aware sparse BMP, {} dims, {} matching docs",
601 infos.len(),
602 bitset.count()
603 );
604 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
605 }
606 }
607
608 let combined = chain_predicates(predicates);
610 if let Some((raw, info)) =
611 build_sparse_bmp_results_filtered(
612 &infos, reader, limit, &*combined, &scorer_options
613 )?
614 {
615 log::debug!(
616 "BooleanQuery planner: predicate-aware sparse BMP, {} dims",
617 infos.len()
618 );
619 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
620 }
621 if let Some((executor, info)) =
623 build_sparse_maxscore_executor(&infos, reader, limit, Some(combined))
624 {
625 log::debug!(
626 "BooleanQuery planner: predicate-aware sparse MaxScore, {} dims",
627 infos.len()
628 );
629 let raw = executor.$execute_fn() $(. $aw)* ?;
630 return Ok(combine_sparse_results(raw, info.combiner, info.field, limit));
631 }
632 predicates = Vec::new();
636 for q in must {
637 if let Some(pred) = q.as_doc_predicate(reader) {
638 predicates.push(pred);
639 } else if let Some(bitset) = q.as_doc_bitset(reader) {
640 predicates.push(Box::new(move |doc_id| bitset.contains(doc_id)));
641 }
642 }
643 for q in must_not {
644 if let Some(pred) = q.as_doc_predicate(reader) {
645 let negated: super::DocPredicate<'_> =
646 Box::new(move |doc_id| !pred(doc_id));
647 predicates.push(negated);
648 } else if let Some(bitset) = q.as_doc_bitset(reader) {
649 predicates.push(Box::new(move |doc_id| !bitset.contains(doc_id)));
650 }
651 }
652 }
653 }
654
655 let has_filters = !predicates.is_empty()
658 || !must_verifiers.is_empty()
659 || !must_not_verifiers.is_empty();
660 let should_limit = if has_filters {
661 super::max_candidate_limit(limit)
662 } else {
663 limit
664 };
665 let mut should_options = scorer_options.without_threshold();
666 if should_is_sparse {
667 should_options.lsp_plan = scorer_options.lsp_plan.clone();
673 }
674 let should_scorer = if should.len() == 1 {
675 should[0].$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
676 } else {
677 let sub = BooleanQuery {
678 must: Vec::new(),
679 should: should.to_vec(),
680 must_not: Vec::new(),
681 global_stats: global_stats.cloned(),
682 proximity: $proximity,
683 text_heap_factor: $text_tuning.0,
684 max_terms: $text_tuning.1,
685 };
686 sub.$scorer_fn(reader, should_limit, should_options) $(. $aw)* ?
687 };
688
689 let use_predicated =
690 must_verifiers.is_empty() || should_scorer.size_hint() >= limit as u32;
691
692 if use_predicated {
693 log::debug!(
694 "BooleanQuery planner: PredicatedScorer {} preds + {} must_v + {} must_not_v, \
695 SHOULD size_hint={}, over_fetch={}",
696 predicates.len(), must_verifiers.len(), must_not_verifiers.len(),
697 should_scorer.size_hint(), should_limit
698 );
699 return Ok(Box::new(super::PredicatedScorer::new(
700 should_scorer, predicates, must_verifiers, must_not_verifiers,
701 )));
702 }
703
704 log::debug!(
706 "BooleanQuery planner: BooleanScorer fallback, size_hint={} < limit={}, \
707 {} must_v + {} must_not_v",
708 should_scorer.size_hint(), limit,
709 must_verifiers.len(), must_not_verifiers.len()
710 );
711 let mut scorer = BooleanScorer {
712 must: must_verifiers,
713 should: vec![should_scorer],
714 must_not: must_not_verifiers,
715 current_doc: 0,
716 };
717 scorer.current_doc = scorer.find_next_match();
718 return Ok(Box::new(scorer));
719 }
720
721 let mut must_scorers = Vec::with_capacity(must.len());
723 for q in must {
724 must_scorers.push(q.$scorer_fn(
725 reader, limit, scorer_options.without_threshold()
726 ) $(. $aw)* ?);
727 }
728 let mut should_scorers = Vec::with_capacity(should.len());
729 for q in should {
730 should_scorers.push(q.$scorer_fn(
731 reader, limit, scorer_options.without_threshold()
732 ) $(. $aw)* ?);
733 }
734 let mut must_not_scorers = Vec::with_capacity(must_not.len());
735 for q in must_not {
736 must_not_scorers.push(q.$scorer_fn(
737 reader, limit, scorer_options.without_threshold()
738 ) $(. $aw)* ?);
739 }
740 let mut scorer = BooleanScorer {
741 must: must_scorers,
742 should: should_scorers,
743 must_not: must_not_scorers,
744 current_doc: 0,
745 };
746 scorer.current_doc = scorer.find_next_match();
747 Ok(Box::new(scorer) as Box<dyn Scorer + '_>)
748 }};
749}
750
751impl Query for BooleanQuery {
752 fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
753 self.scorer_with_options(reader, limit, super::ScorerOptions::with_positions())
754 }
755
756 fn scorer_with_options<'a>(
757 &self,
758 reader: &'a SegmentReader,
759 limit: usize,
760 options: super::ScorerOptions,
761 ) -> ScorerFuture<'a> {
762 let must = self.must.clone();
763 let should = self.should.clone();
764 let must_not = self.must_not.clone();
765 let global_stats = self
766 .global_stats
767 .clone()
768 .or_else(|| options.global_stats.clone());
769 let proximity = self.proximity;
770 let text_tuning = (self.text_heap_factor, self.max_terms);
771 Box::pin(async move {
772 boolean_plan!(
773 must,
774 should,
775 must_not,
776 global_stats.as_ref(),
777 proximity,
778 text_tuning,
779 reader,
780 limit,
781 options,
782 scorer_with_options,
783 get_postings,
784 execute,
785 await
786 )
787 })
788 }
789
790 #[cfg(feature = "sync")]
791 fn scorer_sync<'a>(
792 &self,
793 reader: &'a SegmentReader,
794 limit: usize,
795 ) -> crate::Result<Box<dyn Scorer + 'a>> {
796 self.scorer_sync_with_options(reader, limit, super::ScorerOptions::with_positions())
797 }
798
799 #[cfg(feature = "sync")]
800 fn scorer_sync_with_options<'a>(
801 &self,
802 reader: &'a SegmentReader,
803 limit: usize,
804 options: super::ScorerOptions,
805 ) -> crate::Result<Box<dyn Scorer + 'a>> {
806 let global_stats = self
807 .global_stats
808 .clone()
809 .or_else(|| options.global_stats.clone());
810 boolean_plan!(
811 self.must,
812 self.should,
813 self.must_not,
814 global_stats.as_ref(),
815 self.proximity,
816 (self.text_heap_factor, self.max_terms),
817 reader,
818 limit,
819 options,
820 scorer_sync_with_options,
821 get_postings_sync,
822 execute_sync
823 )
824 }
825
826 fn text_terms(&self, out: &mut Vec<(crate::dsl::Field, Vec<u8>)>) {
827 for clause in self.must.iter().chain(&self.should).chain(&self.must_not) {
828 clause.text_terms(out);
829 }
830 }
831
832 fn decompose(&self) -> super::QueryDecomposition {
833 if self.should.is_empty() || self.must.iter().any(|query| !query.is_filter()) {
839 return super::QueryDecomposition::Opaque;
840 }
841 extract_all_sparse_infos(&self.should)
842 .map(super::QueryDecomposition::SparseTerms)
843 .unwrap_or(super::QueryDecomposition::Opaque)
844 }
845
846 fn as_doc_bitset(&self, reader: &SegmentReader) -> Option<super::DocBitset> {
847 if self.must.is_empty() && self.should.is_empty() {
848 return None;
849 }
850
851 let num_docs = reader.num_docs();
852
853 let mut result: Option<super::DocBitset> = None;
855 for q in &self.must {
856 let bs = q.as_doc_bitset(reader)?;
857 match result {
858 None => result = Some(bs),
859 Some(ref mut acc) => acc.intersect_with(&bs),
860 }
861 }
862
863 if !self.should.is_empty() {
865 let mut should_union = super::DocBitset::new(num_docs);
866 for q in &self.should {
867 let bs = q.as_doc_bitset(reader)?;
868 should_union.union_with(&bs);
869 }
870 match result {
871 None => result = Some(should_union),
872 Some(ref mut acc) => {
873 if self.must.is_empty() {
876 *acc = should_union;
877 }
878 }
879 }
880 }
881
882 if let Some(ref mut acc) = result {
884 for q in &self.must_not {
885 {
886 let bs = q.as_doc_bitset(reader)?;
887 acc.subtract(&bs);
888 }
889 }
890 }
891
892 result
893 }
894
895 fn as_doc_predicate<'a>(&self, reader: &'a SegmentReader) -> Option<super::DocPredicate<'a>> {
896 if self.must.is_empty() && self.should.is_empty() {
898 return None;
899 }
900
901 let must_preds: Vec<_> = self
903 .must
904 .iter()
905 .map(|q| q.as_doc_predicate(reader))
906 .collect::<Option<Vec<_>>>()?;
907 let should_preds: Vec<_> = self
908 .should
909 .iter()
910 .map(|q| q.as_doc_predicate(reader))
911 .collect::<Option<Vec<_>>>()?;
912 let must_not_preds: Vec<_> = self
913 .must_not
914 .iter()
915 .map(|q| q.as_doc_predicate(reader))
916 .collect::<Option<Vec<_>>>()?;
917
918 let has_must = !must_preds.is_empty();
919
920 Some(Box::new(move |doc_id| {
921 if !must_preds.iter().all(|p| p(doc_id)) {
923 return false;
924 }
925 if !has_must && !should_preds.is_empty() && !should_preds.iter().any(|p| p(doc_id)) {
927 return false;
928 }
929 must_not_preds.iter().all(|p| !p(doc_id))
931 }))
932 }
933
934 fn count_estimate<'a>(&self, reader: &'a SegmentReader) -> CountFuture<'a> {
935 let must = self.must.clone();
936 let should = self.should.clone();
937
938 Box::pin(async move {
939 if !must.is_empty() {
940 let mut estimates = Vec::with_capacity(must.len());
941 for q in &must {
942 estimates.push(q.count_estimate(reader).await?);
943 }
944 estimates
945 .into_iter()
946 .min()
947 .ok_or_else(|| crate::Error::Corruption("Empty must clause".to_string()))
948 } else if !should.is_empty() {
949 let mut sum = 0u32;
950 for q in &should {
951 sum = sum.saturating_add(q.count_estimate(reader).await?);
952 }
953 Ok(sum)
954 } else {
955 Ok(0)
956 }
957 })
958 }
959}
960
961struct BooleanScorer<'a> {
962 must: Vec<Box<dyn Scorer + 'a>>,
963 should: Vec<Box<dyn Scorer + 'a>>,
964 must_not: Vec<Box<dyn Scorer + 'a>>,
965 current_doc: DocId,
966}
967
968impl BooleanScorer<'_> {
969 fn find_next_match(&mut self) -> DocId {
970 if self.must.is_empty() && self.should.is_empty() {
971 return TERMINATED;
972 }
973
974 loop {
975 let candidate = if !self.must.is_empty() {
976 let mut max_doc = self
977 .must
978 .iter()
979 .map(|s| s.doc())
980 .max()
981 .unwrap_or(TERMINATED);
982
983 if max_doc == TERMINATED {
984 return TERMINATED;
985 }
986
987 loop {
988 let mut all_match = true;
989 for scorer in &mut self.must {
990 let doc = scorer.seek(max_doc);
991 if doc == TERMINATED {
992 return TERMINATED;
993 }
994 if doc > max_doc {
995 max_doc = doc;
996 all_match = false;
997 break;
998 }
999 }
1000 if all_match {
1001 break;
1002 }
1003 }
1004 max_doc
1005 } else {
1006 self.should
1007 .iter()
1008 .map(|s| s.doc())
1009 .filter(|&d| d != TERMINATED)
1010 .min()
1011 .unwrap_or(TERMINATED)
1012 };
1013
1014 if candidate == TERMINATED {
1015 return TERMINATED;
1016 }
1017
1018 let excluded = self.must_not.iter_mut().any(|scorer| {
1019 let doc = scorer.seek(candidate);
1020 doc == candidate
1021 });
1022
1023 if !excluded {
1024 for scorer in &mut self.should {
1026 scorer.seek(candidate);
1027 }
1028 self.current_doc = candidate;
1029 return candidate;
1030 }
1031
1032 if !self.must.is_empty() {
1034 for scorer in &mut self.must {
1035 scorer.advance();
1036 }
1037 } else {
1038 for scorer in &mut self.should {
1040 if scorer.doc() <= candidate && scorer.doc() != TERMINATED {
1041 scorer.seek(candidate + 1);
1042 }
1043 }
1044 }
1045 }
1046 }
1047}
1048
1049impl super::docset::DocSet for BooleanScorer<'_> {
1050 fn doc(&self) -> DocId {
1051 self.current_doc
1052 }
1053
1054 fn advance(&mut self) -> DocId {
1055 if !self.must.is_empty() {
1056 for scorer in &mut self.must {
1057 scorer.advance();
1058 }
1059 } else {
1060 for scorer in &mut self.should {
1061 if scorer.doc() == self.current_doc {
1062 scorer.advance();
1063 }
1064 }
1065 }
1066
1067 self.current_doc = self.find_next_match();
1068 self.current_doc
1069 }
1070
1071 fn seek(&mut self, target: DocId) -> DocId {
1072 for scorer in &mut self.must {
1073 scorer.seek(target);
1074 }
1075
1076 for scorer in &mut self.should {
1077 scorer.seek(target);
1078 }
1079
1080 self.current_doc = self.find_next_match();
1081 self.current_doc
1082 }
1083
1084 fn size_hint(&self) -> u32 {
1085 if !self.must.is_empty() {
1086 self.must.iter().map(|s| s.size_hint()).min().unwrap_or(0)
1087 } else {
1088 self.should.iter().map(|s| s.size_hint()).sum()
1089 }
1090 }
1091}
1092
1093impl Scorer for BooleanScorer<'_> {
1094 fn score(&self) -> Score {
1095 let mut total = 0.0;
1096
1097 for scorer in &self.must {
1098 if scorer.doc() == self.current_doc {
1099 total += scorer.score();
1100 }
1101 }
1102
1103 for scorer in &self.should {
1104 if scorer.doc() == self.current_doc {
1105 total += scorer.score();
1106 }
1107 }
1108
1109 total
1110 }
1111
1112 fn matched_positions(&self) -> Option<super::MatchedPositions> {
1113 let mut all_positions: super::MatchedPositions = Vec::new();
1114
1115 for scorer in &self.must {
1116 if scorer.doc() == self.current_doc
1117 && let Some(positions) = scorer.matched_positions()
1118 {
1119 all_positions.extend(positions);
1120 }
1121 }
1122
1123 for scorer in &self.should {
1124 if scorer.doc() == self.current_doc
1125 && let Some(positions) = scorer.matched_positions()
1126 {
1127 all_positions.extend(positions);
1128 }
1129 }
1130
1131 if all_positions.is_empty() {
1132 None
1133 } else {
1134 Some(merge_matched_positions(all_positions))
1135 }
1136 }
1137}
1138
1139pub(super) fn merge_matched_positions(
1147 positions: super::MatchedPositions,
1148) -> super::MatchedPositions {
1149 if positions.len() < 2 {
1150 return positions;
1151 }
1152 let mut merged: super::MatchedPositions = Vec::with_capacity(positions.len());
1153 for (field_id, scored) in positions {
1154 match merged
1155 .iter_mut()
1156 .find(|(existing, _)| *existing == field_id)
1157 {
1158 Some((_, existing)) => existing.extend(scored),
1159 None => merged.push((field_id, scored)),
1160 }
1161 }
1162 for (_, scored) in &mut merged {
1163 if scored.len() < 2 {
1164 continue;
1165 }
1166 scored.sort_by_key(|sp| sp.position);
1167 let mut write = 0usize;
1168 for read in 1..scored.len() {
1169 if scored[read].position == scored[write].position {
1170 scored[write].score += scored[read].score;
1171 } else {
1172 write += 1;
1173 scored[write] = scored[read];
1174 }
1175 }
1176 scored.truncate(write + 1);
1177 }
1178 merged
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183 use super::*;
1184 use crate::dsl::Field;
1185 use crate::query::{QueryDecomposition, TermQuery};
1186
1187 #[test]
1188 fn test_maxscore_eligible_pure_or_same_field() {
1189 let query = BooleanQuery::new()
1191 .should(TermQuery::text(Field(0), "hello"))
1192 .should(TermQuery::text(Field(0), "world"))
1193 .should(TermQuery::text(Field(0), "foo"));
1194
1195 assert!(
1197 query
1198 .should
1199 .iter()
1200 .all(|q| matches!(q.decompose(), QueryDecomposition::TextTerm(_)))
1201 );
1202
1203 let infos: Vec<_> = query
1205 .should
1206 .iter()
1207 .filter_map(|q| match q.decompose() {
1208 QueryDecomposition::TextTerm(info) => Some(info),
1209 _ => None,
1210 })
1211 .collect();
1212 assert_eq!(infos.len(), 3);
1213 assert!(infos.iter().all(|i| i.field == Field(0)));
1214 }
1215
1216 #[test]
1217 fn test_maxscore_not_eligible_different_fields() {
1218 let query = BooleanQuery::new()
1220 .should(TermQuery::text(Field(0), "hello"))
1221 .should(TermQuery::text(Field(1), "world")); let infos: Vec<_> = query
1224 .should
1225 .iter()
1226 .filter_map(|q| match q.decompose() {
1227 QueryDecomposition::TextTerm(info) => Some(info),
1228 _ => None,
1229 })
1230 .collect();
1231 assert_eq!(infos.len(), 2);
1232 assert!(infos[0].field != infos[1].field);
1234 }
1235
1236 #[test]
1237 fn test_maxscore_not_eligible_with_must() {
1238 let query = BooleanQuery::new()
1240 .must(TermQuery::text(Field(0), "required"))
1241 .should(TermQuery::text(Field(0), "hello"))
1242 .should(TermQuery::text(Field(0), "world"));
1243
1244 assert!(!query.must.is_empty());
1246 }
1247
1248 #[test]
1249 fn test_maxscore_not_eligible_with_must_not() {
1250 let query = BooleanQuery::new()
1252 .should(TermQuery::text(Field(0), "hello"))
1253 .should(TermQuery::text(Field(0), "world"))
1254 .must_not(TermQuery::text(Field(0), "excluded"));
1255
1256 assert!(!query.must_not.is_empty());
1258 }
1259
1260 #[test]
1261 fn test_maxscore_not_eligible_single_term() {
1262 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
1264
1265 assert_eq!(query.should.len(), 1);
1267 }
1268
1269 #[test]
1270 fn test_term_query_info_extraction() {
1271 let term_query = TermQuery::text(Field(42), "test");
1272 match term_query.decompose() {
1273 QueryDecomposition::TextTerm(info) => {
1274 assert_eq!(info.field, Field(42));
1275 assert_eq!(info.term, b"test");
1276 }
1277 _ => panic!("Expected TextTerm decomposition"),
1278 }
1279 }
1280
1281 #[test]
1282 fn test_boolean_query_no_term_info() {
1283 let query = BooleanQuery::new().should(TermQuery::text(Field(0), "hello"));
1285
1286 assert!(matches!(query.decompose(), QueryDecomposition::Opaque));
1287 }
1288}