From d0e16001357b0238645d2e09db59e913b09fee07 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sun, 14 Dec 2025 10:10:45 +0100 Subject: [PATCH] fix bug with minimum_should_match and AllScorer (#2774) --- src/query/all_query.rs | 6 ++- src/query/boolean_query/boolean_weight.rs | 8 ++-- src/query/boolean_query/mod.rs | 47 ++++++++++++++++++++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/query/all_query.rs b/src/query/all_query.rs index 11172f9ed..16a83ec56 100644 --- a/src/query/all_query.rs +++ b/src/query/all_query.rs @@ -23,7 +23,11 @@ pub struct AllWeight; impl Weight for AllWeight { fn scorer(&self, reader: &SegmentReader, boost: Score) -> crate::Result> { let all_scorer = AllScorer::new(reader.max_doc()); - Ok(Box::new(BoostScorer::new(all_scorer, boost))) + if boost != 1.0 { + Ok(Box::new(BoostScorer::new(all_scorer, boost))) + } else { + Ok(Box::new(all_scorer)) + } } fn explain(&self, reader: &SegmentReader, doc: DocId) -> crate::Result { diff --git a/src/query/boolean_query/boolean_weight.rs b/src/query/boolean_query/boolean_weight.rs index 9e8cedf2e..a39249130 100644 --- a/src/query/boolean_query/boolean_weight.rs +++ b/src/query/boolean_query/boolean_weight.rs @@ -193,18 +193,18 @@ impl BooleanWeight { return Ok(SpecializedScorer::Other(Box::new(EmptyScorer))); } - let minimum_number_should_match = self + let effective_minimum_number_should_match = self .minimum_number_should_match .saturating_sub(should_special_scorer_counts.num_all_scorers); let should_scorers: ShouldScorersCombinationMethod = { let num_of_should_scorers = should_scorers.len(); - if minimum_number_should_match > num_of_should_scorers { + if effective_minimum_number_should_match > num_of_should_scorers { // We don't have enough scorers to satisfy the minimum number of should matches. // The request will match no documents. return Ok(SpecializedScorer::Other(Box::new(EmptyScorer))); } - match minimum_number_should_match { + match effective_minimum_number_should_match { 0 if num_of_should_scorers == 0 => ShouldScorersCombinationMethod::Ignored, 0 => ShouldScorersCombinationMethod::Optional(scorer_union( should_scorers, @@ -226,7 +226,7 @@ impl BooleanWeight { scorer_disjunction( should_scorers, score_combiner_fn(), - self.minimum_number_should_match, + effective_minimum_number_should_match, ), )), } diff --git a/src/query/boolean_query/mod.rs b/src/query/boolean_query/mod.rs index 0ddc5a26c..5dc042c46 100644 --- a/src/query/boolean_query/mod.rs +++ b/src/query/boolean_query/mod.rs @@ -9,12 +9,14 @@ pub use self::boolean_weight::BooleanWeight; #[cfg(test)] mod tests { + use std::ops::Bound; + use super::*; use crate::collector::tests::TEST_COLLECTOR_WITH_SCORE; - use crate::collector::TopDocs; + use crate::collector::{Count, TopDocs}; use crate::query::term_query::TermScorer; use crate::query::{ - AllScorer, EmptyScorer, EnableScoring, Intersection, Occur, Query, QueryParser, + AllScorer, EmptyScorer, EnableScoring, Intersection, Occur, Query, QueryParser, RangeQuery, RequiredOptionalScorer, Scorer, SumCombiner, TermQuery, }; use crate::schema::*; @@ -374,4 +376,45 @@ mod tests { } Ok(()) } + + #[test] + pub fn test_min_should_match_with_all_query() -> crate::Result<()> { + let mut schema_builder = Schema::builder(); + let text_field = schema_builder.add_text_field("text", TEXT); + let num_field = + schema_builder.add_i64_field("num", NumericOptions::default().set_fast().set_indexed()); + let schema = schema_builder.build(); + let index = Index::create_in_ram(schema); + let mut index_writer: IndexWriter = index.writer_for_tests()?; + + index_writer.add_document(doc!(text_field => "apple", num_field => 10i64))?; + index_writer.add_document(doc!(text_field => "banana", num_field => 20i64))?; + index_writer.commit()?; + + let searcher = index.reader()?.searcher(); + + let effective_all_match_query: Box = Box::new(RangeQuery::new( + Bound::Excluded(Term::from_field_i64(num_field, 0)), + Bound::Unbounded, + )); + let term_query: Box = Box::new(TermQuery::new( + Term::from_field_text(text_field, "apple"), + IndexRecordOption::Basic, + )); + + // in some previous version, we would remove the 2 all_match, but then say we need *4* + // matches out of the 3 term queries, which matches nothing. + let mut bool_query = BooleanQuery::new(vec![ + (Occur::Should, effective_all_match_query.box_clone()), + (Occur::Should, effective_all_match_query.box_clone()), + (Occur::Should, term_query.box_clone()), + (Occur::Should, term_query.box_clone()), + (Occur::Should, term_query.box_clone()), + ]); + bool_query.set_minimum_number_should_match(4); + let count = searcher.search(&bool_query, &Count)?; + assert_eq!(count, 1); + + Ok(()) + } }