diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 6d83c534b32..21ded1a7e60 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -92,6 +92,10 @@ Bug Fixes * LUCENE-7493: FacetCollector.search threw an unexpected exception if you asked for zero hits but wanted facets (Mahesh via Mike McCandless) +* LUCENE-7505: AnalyzingInfixSuggester returned invalid results when + allTermsRequired is false and context filters are specified (Mike + McCandless) + Improvements * LUCENE-7439: FuzzyQuery now matches all terms within the specified diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java index d05c39fbe24..aa602373f5a 100644 --- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java +++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggester.java @@ -560,12 +560,18 @@ public class AnalyzingInfixSuggester extends Lookup implements Closeable { } if (allMustNot) { - //all are MUST_NOT: add the contextQuery to the main query instead (not as sub-query) + // All are MUST_NOT: add the contextQuery to the main query instead (not as sub-query) for (BooleanClause clause : contextQuery.clauses()) { query.add(clause); } + } else if (allTermsRequired == false) { + // We must carefully upgrade the query clauses to MUST: + BooleanQuery.Builder newQuery = new BooleanQuery.Builder(); + newQuery.add(query.build(), BooleanClause.Occur.MUST); + newQuery.add(contextQuery, BooleanClause.Occur.MUST); + query = newQuery; } else { - //Add contextQuery as sub-query + // Add contextQuery as sub-query query.add(contextQuery, BooleanClause.Occur.MUST); } } @@ -577,7 +583,7 @@ public class AnalyzingInfixSuggester extends Lookup implements Closeable { Query finalQuery = finishQuery(query, allTermsRequired); - //System.out.println("finalQuery=" + query); + //System.out.println("finalQuery=" + finalQuery); // Sort by weight, descending: TopFieldCollector c = TopFieldCollector.create(SORT, num, true, false, false); diff --git a/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java b/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java index 69d3ed6a121..d98d0523f56 100644 --- a/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java +++ b/lucene/suggest/src/test/org/apache/lucene/search/suggest/analyzing/AnalyzingInfixSuggesterTest.java @@ -1258,4 +1258,80 @@ public class AnalyzingInfixSuggesterTest extends LuceneTestCase { a.close(); } } + + public void testContextNotAllTermsRequired() throws Exception { + + Input keys[] = new Input[] { + new Input("lend me your ear", 8, new BytesRef("foobar"), asSet("foo", "bar")), + new Input("a penny saved is a penny earned", 10, new BytesRef("foobaz"), asSet("foo", "baz")) + }; + Path tempDir = createTempDir("analyzingInfixContext"); + + Analyzer a = new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false); + AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(newFSDirectory(tempDir), a, a, 3, false); + suggester.build(new InputArrayIterator(keys)); + + // No context provided, all results returned + List results = suggester.lookup(TestUtil.stringToCharSequence("ear", random()), 10, false, true); + assertEquals(2, results.size()); + LookupResult result = results.get(0); + assertEquals("a penny saved is a penny earned", result.key); + assertEquals("a penny saved is a penny earned", result.highlightKey); + assertEquals(10, result.value); + assertEquals(new BytesRef("foobaz"), result.payload); + assertNotNull(result.contexts); + assertEquals(2, result.contexts.size()); + assertTrue(result.contexts.contains(new BytesRef("foo"))); + assertTrue(result.contexts.contains(new BytesRef("baz"))); + + result = results.get(1); + assertEquals("lend me your ear", result.key); + assertEquals("lend me your ear", result.highlightKey); + assertEquals(8, result.value); + assertEquals(new BytesRef("foobar"), result.payload); + assertNotNull(result.contexts); + assertEquals(2, result.contexts.size()); + assertTrue(result.contexts.contains(new BytesRef("foo"))); + assertTrue(result.contexts.contains(new BytesRef("bar"))); + + // Both have "foo" context: + results = suggester.lookup(TestUtil.stringToCharSequence("ear", random()), asSet("foo"), 10, false, true); + assertEquals(2, results.size()); + + result = results.get(0); + assertEquals("a penny saved is a penny earned", result.key); + assertEquals("a penny saved is a penny earned", result.highlightKey); + assertEquals(10, result.value); + assertEquals(new BytesRef("foobaz"), result.payload); + assertNotNull(result.contexts); + assertEquals(2, result.contexts.size()); + assertTrue(result.contexts.contains(new BytesRef("foo"))); + assertTrue(result.contexts.contains(new BytesRef("baz"))); + + result = results.get(1); + assertEquals("lend me your ear", result.key); + assertEquals("lend me your ear", result.highlightKey); + assertEquals(8, result.value); + assertEquals(new BytesRef("foobar"), result.payload); + assertNotNull(result.contexts); + assertEquals(2, result.contexts.size()); + assertTrue(result.contexts.contains(new BytesRef("foo"))); + assertTrue(result.contexts.contains(new BytesRef("bar"))); + + // Only one has "foo" context and len + results = suggester.lookup(TestUtil.stringToCharSequence("len", random()), asSet("foo"), 10, false, true); + assertEquals(1, results.size()); + + result = results.get(0); + assertEquals("lend me your ear", result.key); + assertEquals("lend me your ear", result.highlightKey); + assertEquals(8, result.value); + assertEquals(new BytesRef("foobar"), result.payload); + assertNotNull(result.contexts); + assertEquals(2, result.contexts.size()); + assertTrue(result.contexts.contains(new BytesRef("foo"))); + assertTrue(result.contexts.contains(new BytesRef("bar"))); + + suggester.close(); + } }