Skip to content

Commit

Permalink
Fix NPE thrown by prefix and regex query in strange scenarios (#94369)
Browse files Browse the repository at this point in the history
In certain scenarios, running a MultiTerm query sets a `null` rewrite method. While `null` is usually checked, there are branches in the code where this is not adequately checked.

Additionally, `MultiTermQuery#setRewriteMethod` has been deprecated for a while. So, to correct this bug, 

 - Remove calls to `MultiTermQuery#setRewriteMethod` where possible
 - Always check for `null` rewrite method


closes: #94364
  • Loading branch information
benwtrent committed Mar 8, 2023
1 parent 20588b1 commit bc2755f
Show file tree
Hide file tree
Showing 27 changed files with 210 additions and 133 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/94369.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 94369
summary: Fix NPE thrown by prefix query in strange scenarios
area: Search
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,13 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
MultiTermQuery.RewriteMethod rewriteMethod
) {
// Disable scoring
return new ConstantScoreQuery(super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context));
return new ConstantScoreQuery(
super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context, rewriteMethod)
);
}

@Override
Expand All @@ -276,8 +279,14 @@ public IntervalsSource fuzzyIntervals(
boolean transpositions,
SearchExecutionContext context
) {
FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term(name(), term), maxDistance, prefixLength, 128, transpositions);
fuzzyQuery.setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_REWRITE);
FuzzyQuery fuzzyQuery = new FuzzyQuery(
new Term(name(), term),
maxDistance,
prefixLength,
128,
transpositions,
MultiTermQuery.CONSTANT_SCORE_REWRITE
);
IntervalsSource fuzzyIntervals = Intervals.multiterm(fuzzyQuery.getAutomata(), term);
return toIntervalsSource(fuzzyIntervals, fuzzyQuery, context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,9 @@ public SpanQuery spanPrefixQuery(String value, SpanMultiTermQueryWrapper.SpanRew
SpanMultiTermQueryWrapper<?> spanMulti = new SpanMultiTermQueryWrapper<>(
new PrefixQuery(new Term(name(), indexedValueForSearch(value)))
);
spanMulti.setRewriteMethod(method);
if (method != null) {
spanMulti.setRewriteMethod(method);
}
return spanMulti;
}
}
Expand Down Expand Up @@ -464,8 +466,9 @@ public Query prefixQuery(
automata.add(Automata.makeAnyChar());
}
Automaton automaton = Operations.concatenate(automata);
AutomatonQuery query = new AutomatonQuery(new Term(name(), value + "*"), automaton);
query.setRewriteMethod(method);
AutomatonQuery query = method == null
? new AutomatonQuery(new Term(name(), value + "*"), automaton, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, false)
: new AutomatonQuery(new Term(name(), value + "*"), automaton, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, false, method);
return new BooleanQuery.Builder().add(query, BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(parentField, value)), BooleanClause.Occur.SHOULD)
.build();
Expand Down Expand Up @@ -649,7 +652,9 @@ public SpanQuery spanPrefixQuery(String value, SpanMultiTermQueryWrapper.SpanRew
SpanMultiTermQueryWrapper<?> spanMulti = new SpanMultiTermQueryWrapper<>(
new PrefixQuery(new Term(name(), indexedValueForSearch(value)))
);
spanMulti.setRewriteMethod(method);
if (method != null) {
spanMulti.setRewriteMethod(method);
}
return spanMulti;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void testPrefixQuery() {
// this term should be a length that can be rewriteable to a term query on the prefix field
final String withinBoundsTerm = "foo";
assertThat(
fieldType.prefixQuery(withinBoundsTerm, CONSTANT_SCORE_REWRITE, randomMockContext()),
fieldType.prefixQuery(withinBoundsTerm, randomBoolean() ? CONSTANT_SCORE_REWRITE : null, randomMockContext()),
equalTo(new ConstantScoreQuery(new TermQuery(new Term(NAME + "._index_prefix", withinBoundsTerm))))
);

Expand All @@ -118,13 +118,13 @@ public void testPrefixQuery() {
// this term should be too long to be rewriteable to a term query on the prefix field
final String longTerm = "toolongforourprefixfieldthistermis";
assertThat(
fieldType.prefixQuery(longTerm, CONSTANT_SCORE_REWRITE, MOCK_CONTEXT),
fieldType.prefixQuery(longTerm, randomBoolean() ? null : CONSTANT_SCORE_REWRITE, MOCK_CONTEXT),
equalTo(new PrefixQuery(new Term(NAME, longTerm)))
);

ElasticsearchException ee = expectThrows(
ElasticsearchException.class,
() -> fieldType.prefixQuery(longTerm, CONSTANT_SCORE_REWRITE, MOCK_CONTEXT_DISALLOW_EXPENSIVE)
() -> fieldType.prefixQuery(longTerm, randomBoolean() ? null : CONSTANT_SCORE_REWRITE, MOCK_CONTEXT_DISALLOW_EXPENSIVE)
);
assertEquals(
"[prefix] queries cannot be executed when 'search.allow_expensive_queries' is set to false. "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
throw new UnsupportedOperationException("[fuzzy] queries are not supported on [" + CONTENT_TYPE + "] fields.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.script.CompositeFieldScript;
import org.elasticsearch.script.Script;
Expand Down Expand Up @@ -103,7 +104,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
throw new IllegalArgumentException(unsupported("fuzzy", "keyword and text"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,11 +468,12 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
failIfNotIndexedNorDocValuesFallback(context);
if (isIndexed()) {
return super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context);
return super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context, rewriteMethod);
} else {
return StringScriptFieldFuzzyQuery.build(
new Script(""),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

package org.elasticsearch.index.mapper;

import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.MultiTermQuery.RewriteMethod;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.StringScriptFieldData;
import org.elasticsearch.index.query.SearchExecutionContext;
Expand Down Expand Up @@ -124,7 +126,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
applyScriptContext(context);
return StringScriptFieldFuzzyQuery.build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,25 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
throw new IllegalArgumentException(
"Can only use fuzzy queries on keyword and text fields - not on [" + name + "] which is of type [" + typeName() + "]"
);
}

public Query fuzzyQuery(
Object value,
Fuzziness fuzziness,
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
) {
return fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context, null);
}

// Case sensitive form of prefix query
public final Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, SearchExecutionContext context) {
return prefixQuery(value, method, false, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
throw new QueryShardException(context, fail("fuzzy query"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.index.query.support.QueryParsers;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.elasticsearch.common.lucene.search.AutomatonQueries.caseInsensitivePrefix;
import static org.elasticsearch.common.lucene.search.AutomatonQueries.toCaseInsensitiveWildcardAutomaton;
import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;

/** Base class for {@link MappedFieldType} implementations that use the same
Expand Down Expand Up @@ -59,7 +61,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException(
Expand Down Expand Up @@ -87,19 +90,19 @@ public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, bool
);
}
failIfNotIndexed();
Term prefix = new Term(name(), indexedValueForSearch(value));
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitivePrefixQuery((new Term(name(), indexedValueForSearch(value))));
if (method != null) {
query.setRewriteMethod(method);
}
return query;

return method == null
? new AutomatonQuery(prefix, caseInsensitivePrefix(prefix.text()), Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, false)
: new AutomatonQuery(
prefix,
caseInsensitivePrefix(prefix.text()),
Operations.DEFAULT_DETERMINIZE_WORK_LIMIT,
false,
method
);
}
PrefixQuery query = new PrefixQuery(new Term(name(), indexedValueForSearch(value)));
if (method != null) {
query.setRewriteMethod(method);
}
return query;
return method == null ? new PrefixQuery(prefix) : new PrefixQuery(prefix, method);
}

public static final String normalizeWildcardPattern(String fieldname, String value, Analyzer normalizer) {
Expand Down Expand Up @@ -164,13 +167,17 @@ protected Query wildcardQuery(
term = new Term(name(), indexedValueForSearch(value));
}
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitiveWildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
return method == null
? new AutomatonQuery(term, toCaseInsensitiveWildcardAutomaton(term, Integer.MAX_VALUE))
: new AutomatonQuery(
term,
toCaseInsensitiveWildcardAutomaton(term, Integer.MAX_VALUE),
Operations.DEFAULT_DETERMINIZE_WORK_LIMIT,
false,
method
);
}
WildcardQuery query = new WildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
return method == null ? new WildcardQuery(term) : new WildcardQuery(term, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, method);
}

@Override
Expand All @@ -188,11 +195,16 @@ public Query regexpQuery(
);
}
failIfNotIndexed();
RegexpQuery query = new RegexpQuery(new Term(name(), indexedValueForSearch(value)), syntaxFlags, matchFlags, maxDeterminizedStates);
if (method != null) {
query.setRewriteMethod(method);
}
return query;
return method == null
? new RegexpQuery(new Term(name(), indexedValueForSearch(value)), syntaxFlags, matchFlags, maxDeterminizedStates)
: new RegexpQuery(
new Term(name(), indexedValueForSearch(value)),
syntaxFlags,
matchFlags,
RegexpQuery.DEFAULT_PROVIDER,
maxDeterminizedStates,
method
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.analysis.AnalyzerScope;
import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.analysis.NamedAnalyzer;
Expand Down Expand Up @@ -618,8 +619,9 @@ public Query prefixQuery(
automata.add(Automata.makeAnyChar());
}
Automaton automaton = Operations.concatenate(automata);
AutomatonQuery query = new AutomatonQuery(new Term(name(), value + "*"), automaton);
query.setRewriteMethod(method);
AutomatonQuery query = method == null
? new AutomatonQuery(new Term(name(), value + "*"), automaton, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, false)
: new AutomatonQuery(new Term(name(), value + "*"), automaton, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, false, method);
return new BooleanQuery.Builder().add(query, BooleanClause.Occur.SHOULD)
.add(new TermQuery(new Term(parentField.name(), value)), BooleanClause.Occur.SHOULD)
.build();
Expand Down Expand Up @@ -809,7 +811,9 @@ public SpanQuery spanPrefixQuery(String value, SpanMultiTermQueryWrapper.SpanRew
SpanMultiTermQueryWrapper<?> spanMulti = new SpanMultiTermQueryWrapper<>(
new PrefixQuery(new Term(name(), indexedValueForSearch(value)))
);
spanMulti.setRewriteMethod(method);
if (method != null) {
spanMulti.setRewriteMethod(method);
}
return spanMulti;
}
}
Expand Down Expand Up @@ -1064,10 +1068,13 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
// Disable scoring
return new ConstantScoreQuery(super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context));
return new ConstantScoreQuery(
super.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context, rewriteMethod)
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.FieldDataContext;
Expand Down Expand Up @@ -272,7 +273,8 @@ public Query fuzzyQuery(
int prefixLength,
int maxExpansions,
boolean transpositions,
SearchExecutionContext context
SearchExecutionContext context,
@Nullable MultiTermQuery.RewriteMethod rewriteMethod
) {
throw new UnsupportedOperationException(
"[fuzzy] queries are not currently supported on keyed " + "[" + CONTENT_TYPE + "] fields."
Expand Down

0 comments on commit bc2755f

Please sign in to comment.