diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 6dbd8fb2f28..91672abe220 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -219,6 +219,9 @@ Bug Fixes that rewrites to a MatchNoDocsQuery instead of throwing an exception. (Bjarke Mortensen, Andy Tran via David Smiley) +* LUCENE-8287: Ensure that empty regex completion queries always return no results. + (Julie Tibshirani via Jim Ferenczi) + Other * LUCENE-8301: Update randomizedtesting to 2.6.0. (Dawid Weiss) diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java index 467708a2728..6217ca38f85 100644 --- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java +++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java @@ -167,11 +167,19 @@ public class ContextQuery extends CompletionQuery { @Override public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { final CompletionWeight innerWeight = ((CompletionWeight) innerQuery.createWeight(searcher, scoreMode, boost)); + final Automaton innerAutomaton = innerWeight.getAutomaton(); + + // If the inner automaton matches nothing, then we return an empty weight to avoid + // traversing all contexts during scoring. + if (innerAutomaton.getNumStates() == 0) { + return new CompletionWeight(this, innerAutomaton); + } + // if separators are preserved the fst contains a SEP_LABEL // behind each gap. To have a matching automaton, we need to // include the SEP_LABEL in the query as well Automaton optionalSepLabel = Operations.optional(Automata.makeChar(CompletionAnalyzer.SEP_LABEL)); - Automaton prefixAutomaton = Operations.concatenate(optionalSepLabel, innerWeight.getAutomaton()); + Automaton prefixAutomaton = Operations.concatenate(optionalSepLabel, innerAutomaton); Automaton contextsAutomaton = Operations.concatenate(toContextAutomaton(contexts, matchAllContexts), prefixAutomaton); contextsAutomaton = Operations.determinize(contextsAutomaton, Operations.DEFAULT_MAX_DETERMINIZED_STATES); diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/RegexCompletionQuery.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/RegexCompletionQuery.java index b53ac34bef4..6fa547a7f60 100644 --- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/RegexCompletionQuery.java +++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/RegexCompletionQuery.java @@ -23,6 +23,8 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ScoreMode; import org.apache.lucene.search.Weight; import org.apache.lucene.search.suggest.BitsProducer; +import org.apache.lucene.util.automaton.Automata; +import org.apache.lucene.util.automaton.Automaton; import org.apache.lucene.util.automaton.Operations; import org.apache.lucene.util.automaton.RegExp; @@ -90,7 +92,12 @@ public class RegexCompletionQuery extends CompletionQuery { @Override public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { - return new CompletionWeight(this, new RegExp(getTerm().text(), flags).toAutomaton(maxDeterminizedStates)); + // If an empty regex is provided, we return an automaton that matches nothing. This ensures + // consistency with PrefixCompletionQuery, which returns no results for an empty term. + Automaton automaton = getTerm().text().isEmpty() + ? Automata.makeEmpty() + : new RegExp(getTerm().text(), flags).toAutomaton(maxDeterminizedStates); + return new CompletionWeight(this, automaton); } /** diff --git a/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestPrefixCompletionQuery.java b/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestPrefixCompletionQuery.java index 515ac2db32e..5e941dd4e2d 100644 --- a/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestPrefixCompletionQuery.java +++ b/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestPrefixCompletionQuery.java @@ -41,6 +41,7 @@ import org.apache.lucene.util.FixedBitSet; import org.apache.lucene.util.LuceneTestCase; import org.junit.After; import org.junit.Before; +import org.junit.Test; import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; import static org.apache.lucene.search.suggest.document.TestSuggestField.Entry; @@ -142,6 +143,29 @@ public class TestPrefixCompletionQuery extends LuceneTestCase { iw.close(); } + @Test + public void testEmptyPrefixQuery() throws Exception { + Analyzer analyzer = new MockAnalyzer(random()); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwcWithSuggestField(analyzer, "suggest_field")); + Document document = new Document(); + document.add(new SuggestField("suggest_field", "suggestion1", 1)); + iw.addDocument(document); + + if (rarely()) { + iw.commit(); + } + + DirectoryReader reader = iw.getReader(); + SuggestIndexSearcher suggestIndexSearcher = new SuggestIndexSearcher(reader); + PrefixCompletionQuery query = new PrefixCompletionQuery(analyzer, new Term("suggest_field", "")); + + TopSuggestDocs suggest = suggestIndexSearcher.suggest(query, 5, false); + assertEquals(0, suggest.scoreDocs.length); + + reader.close(); + iw.close(); + } + public void testMostlyFilteredOutDocuments() throws Exception { Analyzer analyzer = new MockAnalyzer(random()); RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwcWithSuggestField(analyzer, "suggest_field")); @@ -337,4 +361,27 @@ public class TestPrefixCompletionQuery extends LuceneTestCase { reader.close(); iw.close(); } + + public void testEmptyPrefixContextQuery() throws Exception { + Analyzer analyzer = new MockAnalyzer(random()); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwcWithSuggestField(analyzer, "suggest_field")); + Document document = new Document(); + document.add(new ContextSuggestField("suggest_field", "suggestion", 1, "type")); + iw.addDocument(document); + + if (rarely()) { + iw.commit(); + } + + DirectoryReader reader = iw.getReader(); + SuggestIndexSearcher suggestIndexSearcher = new SuggestIndexSearcher(reader); + ContextQuery query = new ContextQuery(new PrefixCompletionQuery(analyzer, new Term("suggest_field", ""))); + query.addContext("type", 1); + + TopSuggestDocs suggest = suggestIndexSearcher.suggest(query, 5, false); + assertEquals(0, suggest.scoreDocs.length); + + reader.close(); + iw.close(); + } } diff --git a/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestRegexCompletionQuery.java b/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestRegexCompletionQuery.java index 2dd718407fd..7684bb071bd 100644 --- a/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestRegexCompletionQuery.java +++ b/lucene/suggest/src/test/org/apache/lucene/search/suggest/document/TestRegexCompletionQuery.java @@ -75,6 +75,29 @@ public class TestRegexCompletionQuery extends LuceneTestCase { iw.close(); } + @Test + public void testEmptyRegexQuery() throws Exception { + Analyzer analyzer = new MockAnalyzer(random()); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwcWithSuggestField(analyzer, "suggest_field")); + Document document = new Document(); + document.add(new SuggestField("suggest_field", "suggestion1", 1)); + iw.addDocument(document); + + if (rarely()) { + iw.commit(); + } + + DirectoryReader reader = iw.getReader(); + SuggestIndexSearcher suggestIndexSearcher = new SuggestIndexSearcher(reader); + RegexCompletionQuery query = new RegexCompletionQuery(new Term("suggest_field", "")); + + TopSuggestDocs suggest = suggestIndexSearcher.suggest(query, 5, false); + assertEquals(0, suggest.scoreDocs.length); + + reader.close(); + iw.close(); + } + @Test public void testSimpleRegexContextQuery() throws Exception { Analyzer analyzer = new MockAnalyzer(random()); @@ -147,4 +170,28 @@ public class TestRegexCompletionQuery extends LuceneTestCase { reader.close(); iw.close(); } + + @Test + public void testEmptyRegexContextQuery() throws Exception { + Analyzer analyzer = new MockAnalyzer(random()); + RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwcWithSuggestField(analyzer, "suggest_field")); + Document document = new Document(); + document.add(new ContextSuggestField("suggest_field", "suggestion", 1, "type")); + iw.addDocument(document); + + if (rarely()) { + iw.commit(); + } + + DirectoryReader reader = iw.getReader(); + SuggestIndexSearcher suggestIndexSearcher = new SuggestIndexSearcher(reader); + ContextQuery query = new ContextQuery(new RegexCompletionQuery(new Term("suggest_field", ""))); + query.addContext("type", 1); + + TopSuggestDocs suggest = suggestIndexSearcher.suggest(query, 5, false); + assertEquals(0, suggest.scoreDocs.length); + + reader.close(); + iw.close(); + } }