Search - add case insensitive flag for "term" family of queries #61596 (#62661)

Backport of fe9145f

Closes #61546
This commit is contained in:
markharwood 2020-09-22 13:56:51 +01:00 committed by GitHub
parent 0d5250c99b
commit a0df0fb074
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 883 additions and 137 deletions

View File

@ -41,6 +41,10 @@ provided `<field>`.
(Optional, string) Method used to rewrite the query. For valid values and more
information, see the <<query-dsl-multi-term-rewrite, `rewrite` parameter>>.
`case_insensitive`::
(Optional, boolean) allows ASCII case insensitive matching of the
value with the indexed field values when set to true. Setting to false is disallowed.
[[prefix-query-notes]]
==== Notes

View File

@ -62,6 +62,10 @@ Boost values are relative to the default value of `1.0`. A boost value between
`0` and `1.0` decreases the relevance score. A value greater than `1.0`
increases the relevance score.
`case_insensitive`::
(Optional, boolean) allows ASCII case insensitive matching of the
value with the indexed field values when set to true. Setting to false is disallowed.
[[term-query-notes]]
==== Notes
@ -84,7 +88,7 @@ The `term` query does *not* analyze the search term. The `term` query only
searches for the *exact* term you provide. This means the `term` query may
return poor or no results when searching `text` fields.
To see the difference in search results, try the following example.
To see the difference in search results, try the following example.
. Create an index with a `text` field called `full_text`.
+
@ -214,4 +218,4 @@ in the results.
}
----
// TESTRESPONSE[s/"took" : 1/"took" : $body.took/]
--
--

View File

@ -52,7 +52,7 @@ This parameter supports two wildcard operators:
WARNING: Avoid beginning patterns with `*` or `?`. This can increase
the iterations needed to find matching terms and slow search performance.
--
--
`boost`::
(Optional, float) Floating point number used to decrease or increase the
@ -69,6 +69,10 @@ increases the relevance score.
(Optional, string) Method used to rewrite the query. For valid values and more information, see the
<<query-dsl-multi-term-rewrite, `rewrite` parameter>>.
`case_insensitive`::
(Optional, boolean) allows case insensitive matching of the
pattern with the indexed field values when set to true. Setting to false is disallowed.
[[wildcard-query-notes]]
==== Notes
===== Allow expensive queries

View File

@ -281,11 +281,11 @@ public class SearchAsYouTypeFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (prefixField == null || prefixField.termLengthWithinBounds(value.length()) == false) {
return super.prefixQuery(value, method, context);
return super.prefixQuery(value, method, caseInsensitive, context);
} else {
final Query query = prefixField.prefixQuery(value, method, context);
final Query query = prefixField.prefixQuery(value, method, caseInsensitive, context);
if (method == null
|| method == MultiTermQuery.CONSTANT_SCORE_REWRITE
|| method == MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE) {
@ -365,8 +365,11 @@ public class SearchAsYouTypeFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (value.length() >= minChars) {
if(caseInsensitive) {
return super.termQueryCaseInsensitive(value, context);
}
return super.termQuery(value, context);
}
List<Automaton> automata = new ArrayList<>();
@ -507,11 +510,11 @@ public class SearchAsYouTypeFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (prefixFieldType == null || prefixFieldType.termLengthWithinBounds(value.length()) == false) {
return super.prefixQuery(value, method, context);
return super.prefixQuery(value, method, caseInsensitive, context);
} else {
final Query query = prefixFieldType.prefixQuery(value, method, context);
final Query query = prefixFieldType.prefixQuery(value, method, caseInsensitive, context);
if (method == null
|| method == MultiTermQuery.CONSTANT_SCORE_REWRITE
|| method == MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE) {

View File

@ -136,13 +136,15 @@ public class ICUCollationKeywordFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method,
boolean caseInsensitive, QueryShardContext context) {
throw new UnsupportedOperationException("[prefix] queries are not supported on [" + CONTENT_TYPE + "] fields.");
}
@Override
public Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) {
throw new UnsupportedOperationException("[wildcard] queries are not supported on [" + CONTENT_TYPE + "] fields.");
}

View File

@ -879,4 +879,18 @@ public class Strings {
return sb.toString();
}
}
public static String toLowercaseAscii(String in) {
StringBuilder out = new StringBuilder();
Iterator<Integer> iter = in.codePoints().iterator();
while (iter.hasNext()) {
int codepoint = iter.next();
if (codepoint > 128) {
out.appendCodePoint(codepoint);
} else {
out.appendCodePoint(Character.toLowerCase(codepoint));
}
}
return out.toString();
}
}

View File

@ -0,0 +1,152 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.lucene.search;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automata;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.MinimizationOperations;
import org.apache.lucene.util.automaton.Operations;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Helper functions for creating various forms of {@link AutomatonQuery}
*/
public class AutomatonQueries {
/** Build an automaton query accepting all terms with the specified prefix, ASCII case insensitive. */
public static Automaton caseInsensitivePrefix(String s) {
List<Automaton> list = new ArrayList<>();
Iterator<Integer> iter = s.codePoints().iterator();
while (iter.hasNext()) {
list.add(toCaseInsensitiveChar(iter.next(), Integer.MAX_VALUE));
}
list.add(Automata.makeAnyString());
Automaton a = Operations.concatenate(list);
a = MinimizationOperations.minimize(a, Integer.MAX_VALUE);
return a;
}
/** Build an automaton query accepting all terms with the specified prefix, ASCII case insensitive. */
public static AutomatonQuery caseInsensitivePrefixQuery(Term prefix) {
return new AutomatonQuery(prefix, caseInsensitivePrefix(prefix.text()));
}
/** Build an automaton accepting all terms ASCII case insensitive. */
public static AutomatonQuery caseInsensitiveTermQuery(Term term) {
BytesRef prefix = term.bytes();
return new AutomatonQuery(term, toCaseInsensitiveString(prefix,Integer.MAX_VALUE));
}
/** Build an automaton matching a wildcard pattern, ASCII case insensitive. */
public static AutomatonQuery caseInsensitiveWildcardQuery(Term wildcardquery) {
return new AutomatonQuery(wildcardquery, toCaseInsensitiveWildcardAutomaton(wildcardquery,Integer.MAX_VALUE));
}
/** String equality with support for wildcards */
public static final char WILDCARD_STRING = '*';
/** Char equality with support for wildcards */
public static final char WILDCARD_CHAR = '?';
/** Escape character */
public static final char WILDCARD_ESCAPE = '\\';
/**
* Convert Lucene wildcard syntax into an automaton.
*/
@SuppressWarnings("fallthrough")
public static Automaton toCaseInsensitiveWildcardAutomaton(Term wildcardquery, int maxDeterminizedStates) {
List<Automaton> automata = new ArrayList<>();
String wildcardText = wildcardquery.text();
for (int i = 0; i < wildcardText.length();) {
final int c = wildcardText.codePointAt(i);
int length = Character.charCount(c);
switch(c) {
case WILDCARD_STRING:
automata.add(Automata.makeAnyString());
break;
case WILDCARD_CHAR:
automata.add(Automata.makeAnyChar());
break;
case WILDCARD_ESCAPE:
// add the next codepoint instead, if it exists
if (i + length < wildcardText.length()) {
final int nextChar = wildcardText.codePointAt(i + length);
length += Character.charCount(nextChar);
automata.add(Automata.makeChar(nextChar));
break;
} // else fallthru, lenient parsing with a trailing \
default:
automata.add(toCaseInsensitiveChar(c, maxDeterminizedStates));
}
i += length;
}
return Operations.concatenate(automata);
}
protected static Automaton toCaseInsensitiveString(BytesRef br, int maxDeterminizedStates) {
return toCaseInsensitiveString(br.utf8ToString(), maxDeterminizedStates);
}
public static Automaton toCaseInsensitiveString(String s, int maxDeterminizedStates) {
List<Automaton> list = new ArrayList<>();
Iterator<Integer> iter = s.codePoints().iterator();
while (iter.hasNext()) {
list.add(toCaseInsensitiveChar(iter.next(), maxDeterminizedStates));
}
Automaton a = Operations.concatenate(list);
a = MinimizationOperations.minimize(a, maxDeterminizedStates);
return a;
}
protected static Automaton toCaseInsensitiveChar(int codepoint, int maxDeterminizedStates) {
Automaton case1 = Automata.makeChar(codepoint);
// For now we only work with ASCII characters
if (codepoint > 128) {
return case1;
}
int altCase = Character.isLowerCase(codepoint) ? Character.toUpperCase(codepoint) : Character.toLowerCase(codepoint);
Automaton result;
if (altCase != codepoint) {
result = Operations.union(case1, Automata.makeChar(altCase));
result = MinimizationOperations.minimize(result, maxDeterminizedStates);
} else {
result = case1;
}
return result;
}
}

View File

@ -79,15 +79,39 @@ public class Regex {
* Match a String against the given pattern, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality.
* Matching is case sensitive.
*
* @param pattern the pattern to match against
* @param str the String to match
* @return whether the String matches the given pattern
*/
public static boolean simpleMatch(String pattern, String str) {
return simpleMatch(pattern, str, false);
}
/**
* Match a String against the given pattern, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality.
*
* @param pattern the pattern to match against
* @param str the String to match
* @param caseInsensitive true if ASCII case differences should be ignored
* @return whether the String matches the given pattern
*/
public static boolean simpleMatch(String pattern, String str, boolean caseInsensitive) {
if (pattern == null || str == null) {
return false;
}
if (caseInsensitive) {
pattern = Strings.toLowercaseAscii(pattern);
str = Strings.toLowercaseAscii(str);
}
return simpleMatchWithNormalizedStrings(pattern, str);
}
private static boolean simpleMatchWithNormalizedStrings(String pattern, String str) {
final int firstIndex = pattern.indexOf('*');
if (firstIndex == -1) {
return pattern.equals(str);
@ -102,12 +126,12 @@ public class Regex {
return str.regionMatches(str.length() - pattern.length() + 1, pattern, 1, pattern.length() - 1);
} else if (nextIndex == 1) {
// Double wildcard "**" - skipping the first "*"
return simpleMatch(pattern.substring(1), str);
return simpleMatchWithNormalizedStrings(pattern.substring(1), str);
}
final String part = pattern.substring(1, nextIndex);
int partIndex = str.indexOf(part);
while (partIndex != -1) {
if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
if (simpleMatchWithNormalizedStrings(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
return true;
}
partIndex = str.indexOf(part, partIndex + 1);
@ -116,9 +140,9 @@ public class Regex {
}
return str.regionMatches(0, pattern, 0, firstIndex)
&& (firstIndex == pattern.length() - 1 // only wildcard in pattern is at the end, so no need to look at the rest of the string
|| simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
}
|| simpleMatchWithNormalizedStrings(pattern.substring(firstIndex), str.substring(firstIndex)));
}
/**
* Match a String against the given patterns, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an

View File

@ -59,7 +59,7 @@ public abstract class ConstantFieldType extends MappedFieldType {
* Return whether the constant value of this field matches the provided {@code pattern}
* as documented in {@link Regex#simpleMatch}.
*/
protected abstract boolean matches(String pattern, QueryShardContext context);
protected abstract boolean matches(String pattern, boolean caseInsensitive, QueryShardContext context);
private static String valueToString(Object value) {
return value instanceof BytesRef
@ -70,31 +70,42 @@ public abstract class ConstantFieldType extends MappedFieldType {
@Override
public final Query termQuery(Object value, QueryShardContext context) {
String pattern = valueToString(value);
if (matches(pattern, context)) {
if (matches(pattern, false, context)) {
return Queries.newMatchAllQuery();
} else {
return new MatchNoDocsQuery();
}
}
@Override
public final Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
String pattern = valueToString(value);
if (matches(pattern, true, context)) {
return Queries.newMatchAllQuery();
} else {
return new MatchNoDocsQuery();
}
}
@Override
public final Query termsQuery(List<?> values, QueryShardContext context) {
for (Object value : values) {
String pattern = valueToString(value);
if (matches(pattern, context)) {
if (matches(pattern, false, context)) {
// `terms` queries are a disjunction, so one matching term is enough
return Queries.newMatchAllQuery();
}
}
return new MatchNoDocsQuery();
}
}
@Override
public final Query prefixQuery(String prefix,
@Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) {
String pattern = prefix + "*";
if (matches(pattern, context)) {
if (matches(pattern, caseInsensitive, context)) {
return Queries.newMatchAllQuery();
} else {
return new MatchNoDocsQuery();
@ -104,8 +115,9 @@ public abstract class ConstantFieldType extends MappedFieldType {
@Override
public final Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) {
if (matches(value, context)) {
if (matches(value, caseInsensitive, context)) {
return Queries.newMatchAllQuery();
} else {
return new MatchNoDocsQuery();

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
import org.elasticsearch.index.query.QueryShardContext;
@ -52,7 +53,12 @@ public class IndexFieldMapper extends MetadataFieldMapper {
}
@Override
protected boolean matches(String pattern, QueryShardContext context) {
protected boolean matches(String pattern, boolean caseInsensitive, QueryShardContext context) {
if (caseInsensitive) {
// Thankfully, all index names are lower-cased so we don't have to pass a case_insensitive mode flag
// down to all the index name-matching logic. We just lower-case the search string
pattern = Strings.toLowercaseAscii(pattern);
}
return context.indexMatches(pattern);
}

View File

@ -176,6 +176,13 @@ public abstract class MappedFieldType {
*/
// TODO: Standardize exception types
public abstract Query termQuery(Object value, @Nullable QueryShardContext context);
// Case insensitive form of term query (not supported by all fields so must be overridden to enable)
public Query termQueryCaseInsensitive(Object value, @Nullable QueryShardContext context) {
throw new QueryShardException(context, "[" + name + "] field which is of type [" + typeName() +
"], does not support case insensitive term queries");
}
/** Build a constant-scoring query that matches all values. The default implementation uses a
* {@link ConstantScoreQuery} around a {@link BooleanQuery} whose {@link Occur#SHOULD} clauses
@ -206,14 +213,27 @@ public abstract class MappedFieldType {
+ "] which is of type [" + typeName() + "]");
}
public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, QueryShardContext context) {
// Case sensitive form of prefix query
public final Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, QueryShardContext context) {
return prefixQuery(value, method, false, context);
}
public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, boolean caseInsensitve,
QueryShardContext context) {
throw new QueryShardException(context, "Can only use prefix queries on keyword, text and wildcard fields - not on [" + name
+ "] which is of type [" + typeName() + "]");
}
// Case sensitive form of wildcard query
public final Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method, QueryShardContext context
) {
return wildcardQuery(value, method, false, context);
}
public Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method,
QueryShardContext context) {
boolean caseInsensitve, QueryShardContext context) {
throw new QueryShardException(context, "Can only use wildcard queries on keyword, text and wildcard fields - not on [" + name
+ "] which is of type [" + typeName() + "]");
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery;
@ -32,6 +33,7 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
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.index.query.QueryShardContext;
import org.elasticsearch.index.query.support.QueryParsers;
@ -68,13 +70,21 @@ public abstract class StringFieldType extends TermBasedFieldType {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException("[prefix] queries cannot be executed when '" +
ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false. For optimised prefix queries on text " +
"fields please enable [index_prefixes].");
}
failIfNotIndexed();
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitivePrefixQuery((new Term(name(), indexedValueForSearch(value))));
if (method != null) {
query.setRewriteMethod(method);
}
return query;
}
PrefixQuery query = new PrefixQuery(new Term(name(), indexedValueForSearch(value)));
if (method != null) {
query.setRewriteMethod(method);
@ -113,7 +123,7 @@ public abstract class StringFieldType extends TermBasedFieldType {
}
@Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
failIfNotIndexed();
if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException("[wildcard] queries cannot be executed when '" +
@ -127,7 +137,11 @@ public abstract class StringFieldType extends TermBasedFieldType {
} else {
term = new Term(name(), indexedValueForSearch(value));
}
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitiveWildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
}
WildcardQuery query = new WildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;

View File

@ -26,6 +26,7 @@ import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.index.query.QueryShardContext;
import java.util.List;
@ -47,6 +48,16 @@ public abstract class TermBasedFieldType extends SimpleMappedFieldType {
return BytesRefs.toBytesRef(value);
}
@Override
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
failIfNotIndexed();
Query query = AutomatonQueries.caseInsensitiveTermQuery(new Term(name(), indexedValueForSearch(value)));
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
failIfNotIndexed();

View File

@ -60,6 +60,7 @@ import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.Version;
import org.elasticsearch.common.collect.Iterators;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
@ -439,12 +440,20 @@ public class TextFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (value.length() >= minChars) {
if (caseInsensitive) {
return super.termQueryCaseInsensitive(value, context);
}
return super.termQuery(value, context);
}
List<Automaton> automata = new ArrayList<>();
automata.add(Automata.makeString(value));
if (caseInsensitive) {
automata.add(AutomatonQueries.toCaseInsensitiveString(value, Integer.MAX_VALUE));
} else {
automata.add(Automata.makeString(value));
}
for (int i = value.length(); i < minChars; i++) {
automata.add(Automata.makeAnyChar());
}
@ -636,11 +645,11 @@ public class TextFieldMapper extends FieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (prefixFieldType == null || prefixFieldType.accept(value.length()) == false) {
return super.prefixQuery(value, method, context);
return super.prefixQuery(value, method, caseInsensitive,context);
}
Query tq = prefixFieldType.prefixQuery(value, method, context);
Query tq = prefixFieldType.prefixQuery(value, method, caseInsensitive, context);
if (method == null || method == MultiTermQuery.CONSTANT_SCORE_REWRITE
|| method == MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE) {
return new ConstantScoreQuery(tq);

View File

@ -26,6 +26,7 @@ import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermStates;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
@ -38,6 +39,7 @@ import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
@ -156,7 +158,7 @@ public class TypeFieldMapper extends MetadataFieldMapper {
}
@Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
Query termQuery = termQuery(value, context);
if (termQuery instanceof MatchNoDocsQuery || termQuery instanceof MatchAllDocsQuery) {
return termQuery;
@ -168,6 +170,12 @@ public class TypeFieldMapper extends MetadataFieldMapper {
}
Term term = MappedFieldType.extractTerm(termQuery);
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitiveWildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
}
WildcardQuery query = new WildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;

View File

@ -152,18 +152,23 @@ public abstract class BaseTermQueryBuilder<QB extends BaseTermQueryBuilder<QB>>
builder.startObject(getName());
builder.startObject(fieldName);
builder.field(VALUE_FIELD.getPreferredName(), maybeConvertToString(this.value));
addExtraXContent(builder, params);
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
}
protected void addExtraXContent(XContentBuilder builder, Params params) throws IOException {
// Do nothing but allows subclasses to override.
}
@Override
protected final int doHashCode() {
protected int doHashCode() {
return Objects.hash(fieldName, value);
}
@Override
protected final boolean doEquals(QB other) {
protected boolean doEquals(QB other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value);
}

View File

@ -23,6 +23,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
@ -50,6 +51,11 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
private final String fieldName;
private final String value;
public static final boolean DEFAULT_CASE_INSENSITIVITY = false;
private static final ParseField CASE_INSENSITIVE_FIELD = new ParseField("case_insensitive");
private boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
private String rewrite;
@ -78,6 +84,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
fieldName = in.readString();
value = in.readString();
rewrite = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
caseInsensitive = in.readBoolean();
}
}
@Override
@ -85,6 +94,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
out.writeString(fieldName);
out.writeString(value);
out.writeOptionalString(rewrite);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(caseInsensitive);
}
}
@Override
@ -95,6 +107,18 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
public String value() {
return this.value;
}
public PrefixQueryBuilder caseInsensitive(boolean caseInsensitive) {
if (caseInsensitive == false) {
throw new IllegalArgumentException("The case insensitive setting cannot be set to false.");
}
this.caseInsensitive = caseInsensitive;
return this;
}
public boolean caseInsensitive() {
return this.caseInsensitive;
}
public PrefixQueryBuilder rewrite(String rewrite) {
this.rewrite = rewrite;
@ -113,6 +137,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
if (rewrite != null) {
builder.field(REWRITE_FIELD.getPreferredName(), rewrite);
}
if (caseInsensitive != DEFAULT_CASE_INSENSITIVITY) {
builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive);
}
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
@ -125,6 +152,7 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -145,6 +173,12 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
boost = parser.floatValue();
} else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
rewrite = parser.textOrNull();
} else if (CASE_INSENSITIVE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
caseInsensitive = parser.booleanValue();
if (caseInsensitive == false) {
throw new ParsingException(parser.getTokenLocation(),
"[prefix] query does not support [" + currentFieldName + "] = false");
}
} else {
throw new ParsingException(parser.getTokenLocation(),
"[prefix] query does not support [" + currentFieldName + "]");
@ -158,10 +192,14 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
}
}
return new PrefixQueryBuilder(fieldName, value)
PrefixQueryBuilder result = new PrefixQueryBuilder(fieldName, value)
.rewrite(rewrite)
.boost(boost)
.queryName(queryName);
if (caseInsensitive) {
result.caseInsensitive(caseInsensitive);
}
return result;
}
@Override
@ -180,7 +218,7 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
// This logic is correct for all field types, but by only applying it to constant
// fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread.
Query query = fieldType.prefixQuery(value, null, context); // the rewrite method doesn't matter
Query query = fieldType.prefixQuery(value, null, caseInsensitive, context); // the rewrite method doesn't matter
if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) {
@ -202,18 +240,19 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
if (fieldType == null) {
throw new IllegalStateException("Rewrite first");
}
return fieldType.prefixQuery(value, method, context);
return fieldType.prefixQuery(value, method, caseInsensitive, context);
}
@Override
protected final int doHashCode() {
return Objects.hash(fieldName, value, rewrite);
return Objects.hash(fieldName, value, rewrite, caseInsensitive);
}
@Override
protected boolean doEquals(PrefixQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) &&
Objects.equals(rewrite, other.rewrite);
Objects.equals(rewrite, other.rewrite) &&
Objects.equals(caseInsensitive, other.caseInsensitive);
}
}

View File

@ -22,20 +22,31 @@ package org.elasticsearch.index.query;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.ToXContent.Params;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.ConstantFieldType;
import java.io.IOException;
import java.util.Objects;
/**
* A Query that matches documents containing a term.
*/
public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
public static final String NAME = "term";
public static final boolean DEFAULT_CASE_INSENSITIVITY = false;
private static final ParseField CASE_INSENSITIVE_FIELD = new ParseField("case_insensitive");
private boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
private static final ParseField TERM_FIELD = new ParseField("term");
private static final ParseField VALUE_FIELD = new ParseField("value");
@ -74,19 +85,43 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
public TermQueryBuilder(String fieldName, Object value) {
super(fieldName, value);
}
public TermQueryBuilder caseInsensitive(boolean caseInsensitive) {
if (caseInsensitive == false) {
throw new IllegalArgumentException("The case insensitive setting cannot be set to false.");
}
this.caseInsensitive = caseInsensitive;
return this;
}
public boolean caseInsensitive() {
return this.caseInsensitive;
}
/**
* Read from a stream.
*/
public TermQueryBuilder(StreamInput in) throws IOException {
super(in);
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
caseInsensitive = in.readBoolean();
}
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
super.doWriteTo(out);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(caseInsensitive);
}
}
public static TermQueryBuilder fromXContent(XContentParser parser) throws IOException {
String queryName = null;
String fieldName = null;
Object value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -107,6 +142,12 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
queryName = parser.text();
} else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
boost = parser.floatValue();
} else if (CASE_INSENSITIVE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
caseInsensitive = parser.booleanValue();
if (caseInsensitive == false) {
throw new ParsingException(parser.getTokenLocation(),
"[term] query does not support [" + currentFieldName + "] = false");
}
} else {
throw new ParsingException(parser.getTokenLocation(),
"[term] query does not support [" + currentFieldName + "]");
@ -127,9 +168,19 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
if (queryName != null) {
termQuery.queryName(queryName);
}
if (caseInsensitive) {
termQuery.caseInsensitive(caseInsensitive);
}
return termQuery;
}
@Override
protected void addExtraXContent(XContentBuilder builder, Params params) throws IOException {
if (caseInsensitive != DEFAULT_CASE_INSENSITIVITY) {
builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive);
}
}
@Override
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
QueryShardContext context = queryRewriteContext.convertToShardContext();
@ -141,7 +192,13 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
// This logic is correct for all field types, but by only applying it to constant
// fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread.
Query query = fieldType.termQuery(value, context);
Query query = null;
if (caseInsensitive) {
query = fieldType.termQueryCaseInsensitive(value, context);
} else {
query = fieldType.termQuery(value, context);
}
if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) {
@ -160,11 +217,27 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
if (mapper == null) {
throw new IllegalStateException("Rewrite first");
}
return mapper.termQuery(this.value, context);
if (caseInsensitive) {
return mapper.termQueryCaseInsensitive(value, context);
}
return mapper.termQuery(value, context);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected final int doHashCode() {
return Objects.hash(super.doHashCode(), caseInsensitive);
}
@Override
protected final boolean doEquals(TermQueryBuilder other) {
return super.doEquals(other) &&
Objects.equals(caseInsensitive, other.caseInsensitive);
}
}

View File

@ -23,6 +23,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
@ -59,6 +60,10 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
private String rewrite;
public static final boolean DEFAULT_CASE_INSENSITIVITY = false;
private static final ParseField CASE_INSENSITIVE_FIELD = new ParseField("case_insensitive");
private boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
/**
* Implements the wildcard search query. Supported wildcards are {@code *}, which
* matches any character sequence (including the empty one), and {@code ?},
@ -89,6 +94,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
fieldName = in.readString();
value = in.readString();
rewrite = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
caseInsensitive = in.readBoolean();
}
}
@Override
@ -96,6 +104,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
out.writeString(fieldName);
out.writeString(value);
out.writeOptionalString(rewrite);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(caseInsensitive);
}
}
@Override
@ -115,6 +126,18 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
public String rewrite() {
return this.rewrite;
}
public WildcardQueryBuilder caseInsensitive(boolean caseInsensitive) {
if (caseInsensitive == false) {
throw new IllegalArgumentException("The case insensitive setting cannot be set to false.");
}
this.caseInsensitive = caseInsensitive;
return this;
}
public boolean caseInsensitive() {
return this.caseInsensitive;
}
@Override
public String getWriteableName() {
@ -129,6 +152,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
if (rewrite != null) {
builder.field(REWRITE_FIELD.getPreferredName(), rewrite);
}
if (caseInsensitive != DEFAULT_CASE_INSENSITIVITY) {
builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive);
}
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
@ -139,6 +165,7 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
String rewrite = null;
String value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String queryName = null;
String currentFieldName = null;
XContentParser.Token token;
@ -160,6 +187,12 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
boost = parser.floatValue();
} else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
rewrite = parser.textOrNull();
} else if (CASE_INSENSITIVE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
caseInsensitive = parser.booleanValue();
if (caseInsensitive == false) {
throw new ParsingException(parser.getTokenLocation(),
"[prefix] query does not support [" + currentFieldName + "] = false");
}
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
queryName = parser.text();
} else {
@ -175,10 +208,14 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
}
}
return new WildcardQueryBuilder(fieldName, value)
WildcardQueryBuilder result = new WildcardQueryBuilder(fieldName, value)
.rewrite(rewrite)
.boost(boost)
.queryName(queryName);
if (caseInsensitive) {
result.caseInsensitive(caseInsensitive);
}
return result;
}
@Override
@ -192,7 +229,7 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
// This logic is correct for all field types, but by only applying it to constant
// fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread.
Query query = fieldType.wildcardQuery(value, null, context); // the rewrite method doesn't matter
Query query = fieldType.wildcardQuery(value, null, caseInsensitive, context); // the rewrite method doesn't matter
if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) {
@ -216,18 +253,19 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(
rewrite, null, LoggingDeprecationHandler.INSTANCE);
return fieldType.wildcardQuery(value, method, context);
return fieldType.wildcardQuery(value, method, caseInsensitive, context);
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName, value, rewrite);
return Objects.hash(fieldName, value, rewrite, caseInsensitive);
}
@Override
protected boolean doEquals(WildcardQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) &&
Objects.equals(rewrite, other.rewrite);
Objects.equals(rewrite, other.rewrite)&&
Objects.equals(caseInsensitive, other.caseInsensitive);
}
}

View File

@ -20,6 +20,7 @@ package org.elasticsearch.common.regex;
import org.elasticsearch.test.ESTestCase;
import java.util.Locale;
import java.util.Random;
import java.util.regex.Pattern;
@ -54,13 +55,17 @@ public class RegexTests extends ESTestCase {
public void testDoubleWildcardMatch() {
assertTrue(Regex.simpleMatch("ddd", "ddd"));
assertTrue(Regex.simpleMatch("ddd", "Ddd", true));
assertFalse(Regex.simpleMatch("ddd", "Ddd"));
assertTrue(Regex.simpleMatch("d*d*d", "dadd"));
assertTrue(Regex.simpleMatch("**ddd", "dddd"));
assertTrue(Regex.simpleMatch("**ddD", "dddd", true));
assertFalse(Regex.simpleMatch("**ddd", "fff"));
assertTrue(Regex.simpleMatch("fff*ddd", "fffabcddd"));
assertTrue(Regex.simpleMatch("fff**ddd", "fffabcddd"));
assertFalse(Regex.simpleMatch("fff**ddd", "fffabcdd"));
assertTrue(Regex.simpleMatch("fff*******ddd", "fffabcddd"));
assertTrue(Regex.simpleMatch("fff*******ddd", "FffAbcdDd", true));
assertFalse(Regex.simpleMatch("fff******ddd", "fffabcdd"));
}
@ -76,6 +81,8 @@ public class RegexTests extends ESTestCase {
pattern = pattern.substring(0, shrinkStart) + "*" + pattern.substring(shrinkEnd);
}
assertTrue("[" + pattern + "] should match [" + matchingString + "]", Regex.simpleMatch(pattern, matchingString));
assertTrue("[" + pattern + "] should match [" + matchingString.toUpperCase(Locale.ROOT) + "]",
Regex.simpleMatch(pattern, matchingString.toUpperCase(Locale.ROOT), true));
// construct a pattern that does not match this string by inserting a non-matching character (a digit)
final int insertPos = between(0, pattern.length());

View File

@ -46,7 +46,9 @@ public class IndexFieldTypeTests extends ESTestCase {
MappedFieldType ft = IndexFieldMapper.IndexFieldType.INSTANCE;
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("ind*x", null, createContext()));
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("iNd*x", null, true, createContext()));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("other_ind*x", null, createContext()));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("Other_ind*x", null, true, createContext()));
}
public void testRegexpQuery() {

View File

@ -42,6 +42,7 @@ import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType;
import org.elasticsearch.index.mapper.RangeFieldMapper.RangeFieldType;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.test.IndexSettingsModule;
import org.joda.time.DateTime;
import org.junit.Before;
@ -480,4 +481,14 @@ public class RangeFieldTypeTests extends FieldTypeTestCase {
assertEquals(getExpectedRangeQuery(relation, value, value, includeLower, includeUpper),
ft.termQuery(value, context));
}
public void testCaseInsensitiveQuery() throws Exception {
QueryShardContext context = createContext();
RangeFieldType ft = createDefaultFieldType();
Object value = nextFrom();
QueryShardException ex = expectThrows(QueryShardException.class,
() -> ft.termQueryCaseInsensitive(value, context));
assertTrue(ex.getMessage().contains("does not support case insensitive term queries"));
}
}

View File

@ -36,6 +36,7 @@ import org.apache.lucene.util.automaton.Automaton;
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.index.mapper.TextFieldMapper.TextFieldType;
@ -63,6 +64,7 @@ public class TextFieldTypeTests extends FieldTypeTestCase {
public void testTermQuery() {
MappedFieldType ft = createFieldType();
assertEquals(new TermQuery(new Term("field", "foo")), ft.termQuery("foo", null));
assertEquals(AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "fOo")), ft.termQueryCaseInsensitive("fOo", null));
MappedFieldType unsearchable = new TextFieldType("field", false, Collections.emptyMap());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@ -132,18 +134,22 @@ public class TextFieldTypeTests extends FieldTypeTestCase {
TextFieldType ft = createFieldType();
ft.setPrefixFieldType(new TextFieldMapper.PrefixFieldType(ft, "field._index_prefix", 2, 10, true));
Query q = ft.prefixQuery("goin", CONSTANT_SCORE_REWRITE, randomMockShardContext());
Query q = ft.prefixQuery("goin", CONSTANT_SCORE_REWRITE, false, randomMockShardContext());
assertEquals(new ConstantScoreQuery(new TermQuery(new Term("field._index_prefix", "goin"))), q);
q = ft.prefixQuery("internationalisatio", CONSTANT_SCORE_REWRITE, MOCK_QSC);
q = ft.prefixQuery("internationalisatio", CONSTANT_SCORE_REWRITE, false, MOCK_QSC);
assertEquals(new PrefixQuery(new Term("field", "internationalisatio")), q);
q = ft.prefixQuery("Internationalisatio", CONSTANT_SCORE_REWRITE, true, MOCK_QSC);
assertEquals(AutomatonQueries.caseInsensitivePrefixQuery(new Term("field", "Internationalisatio")), q);
ElasticsearchException ee = expectThrows(ElasticsearchException.class,
() -> ft.prefixQuery("internationalisatio", null, MOCK_QSC_DISALLOW_EXPENSIVE));
() -> ft.prefixQuery("internationalisatio", null, false, MOCK_QSC_DISALLOW_EXPENSIVE));
assertEquals("[prefix] queries cannot be executed when 'search.allow_expensive_queries' is set to false. " +
"For optimised prefix queries on text fields please enable [index_prefixes].", ee.getMessage());
q = ft.prefixQuery("g", CONSTANT_SCORE_REWRITE, randomMockShardContext());
q = ft.prefixQuery("g", CONSTANT_SCORE_REWRITE, false, randomMockShardContext());
Automaton automaton
= Operations.concatenate(Arrays.asList(Automata.makeChar('g'), Automata.makeAnyChar()));

View File

@ -18,12 +18,14 @@
*/
package org.elasticsearch.index.mapper;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexSettings;
@ -61,6 +63,9 @@ public class TypeFieldTypeTests extends ESTestCase {
query = ft.termQuery("my_type", context);
assertEquals(new MatchAllDocsQuery(), query);
query = ft.termQueryCaseInsensitive("my_Type", context);
assertEquals(AutomatonQueries.caseInsensitiveTermQuery(new Term("_type", "my_Type")), query);
Mockito.when(mapperService.hasNested()).thenReturn(true);
query = ft.termQuery("my_type", context);
assertEquals(Queries.newNonNestedFilter(context.indexVersionCreated()), query);
@ -70,5 +75,9 @@ public class TypeFieldTypeTests extends ESTestCase {
Mockito.when(mapperService.documentMapper()).thenReturn(mapper);
query = ft.termQuery("my_type", context);
assertEquals(new MatchNoDocsQuery(), query);
query = ft.termQueryCaseInsensitive("other_Type", context);
assertEquals(AutomatonQueries.caseInsensitiveTermQuery(new Term("_type", "other_Type")), query);
}
}

View File

@ -101,7 +101,8 @@ public class PrefixQueryBuilderTests extends AbstractQueryTestCase<PrefixQueryBu
public void testFromJson() throws IOException {
String json =
"{ \"prefix\" : { \"user\" : { \"value\" : \"ki\", \"boost\" : 2.0 } }}";
"{ \"prefix\" : { \"user\" : { \"value\" : \"ki\", \"boost\" : 2.0"
+ "} }}";
PrefixQueryBuilder parsed = (PrefixQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);

View File

@ -22,6 +22,7 @@ package org.elasticsearch.index.query;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.PointRangeQuery;
import org.apache.lucene.search.Query;
@ -87,12 +88,14 @@ public class TermQueryBuilderTests extends AbstractTermQueryTestCase<TermQueryBu
*/
@Override
protected TermQueryBuilder createQueryBuilder(String fieldName, Object value) {
return new TermQueryBuilder(fieldName, value);
TermQueryBuilder result = new TermQueryBuilder(fieldName, value);
return result;
}
@Override
protected void doAssertLuceneQuery(TermQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class)).or(instanceOf(MatchNoDocsQuery.class)));
assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class)).or(instanceOf(MatchNoDocsQuery.class))
.or(instanceOf(AutomatonQuery.class)));
MappedFieldType mapper = context.fieldMapper(queryBuilder.fieldName());
if (query instanceof TermQuery) {
TermQuery termQuery = (TermQuery) query;
@ -100,14 +103,21 @@ public class TermQueryBuilderTests extends AbstractTermQueryTestCase<TermQueryBu
String expectedFieldName = expectedFieldName(queryBuilder.fieldName());
assertThat(termQuery.getTerm().field(), equalTo(expectedFieldName));
Term term = ((TermQuery) mapper.termQuery(queryBuilder.value(), null)).getTerm();
Term term = ((TermQuery) termQuery(mapper, queryBuilder.value(), queryBuilder.caseInsensitive())).getTerm();
assertThat(termQuery.getTerm(), equalTo(term));
} else if (mapper != null) {
assertEquals(query, mapper.termQuery(queryBuilder.value(), null));
assertEquals(query, termQuery(mapper, queryBuilder.value(), queryBuilder.caseInsensitive()));
} else {
assertThat(query, instanceOf(MatchNoDocsQuery.class));
}
}
private Query termQuery(MappedFieldType mapper, Object value, boolean caseInsensitive) {
if (caseInsensitive) {
return mapper.termQueryCaseInsensitive(value, null);
}
return mapper.termQuery(value, null);
}
public void testTermArray() throws IOException {
String queryAsString = "{\n" +

View File

@ -103,7 +103,8 @@ public class WildcardQueryBuilderTests extends AbstractQueryTestCase<WildcardQue
}
public void testFromJson() throws IOException {
String json = "{ \"wildcard\" : { \"user\" : { \"wildcard\" : \"ki*y\", \"boost\" : 2.0 } }}";
String json = "{ \"wildcard\" : { \"user\" : { \"wildcard\" : \"ki*y\", \"boost\" : 2.0"
+ " } }}";
WildcardQueryBuilder parsed = (WildcardQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed);
assertEquals(json, "ki*y", parsed.value());

View File

@ -132,11 +132,11 @@ public class ConstantKeywordFieldMapper extends ParametrizedFieldMapper {
}
@Override
protected boolean matches(String pattern, QueryShardContext context) {
protected boolean matches(String pattern, boolean caseInsensitive, QueryShardContext context) {
if (value == null) {
return false;
}
return Regex.simpleMatch(pattern, value);
return Regex.simpleMatch(pattern, value, caseInsensitive);
}
@Override

View File

@ -21,9 +21,12 @@ public class ConstantKeywordFieldTypeTests extends FieldTypeTestCase {
public void testTermQuery() {
ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.termQuery("foo", null));
assertEquals(new MatchAllDocsQuery(), ft.termQueryCaseInsensitive("fOo", null));
assertEquals(new MatchNoDocsQuery(), ft.termQuery("bar", null));
assertEquals(new MatchNoDocsQuery(), ft.termQueryCaseInsensitive("bAr", null));
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar");
assertEquals(new MatchNoDocsQuery(), bar.termQuery("foo", null));
assertEquals(new MatchNoDocsQuery(), bar.termQueryCaseInsensitive("fOo", null));
}
public void testTermsQuery() {
@ -39,18 +42,24 @@ public class ConstantKeywordFieldTypeTests extends FieldTypeTestCase {
public void testWildcardQuery() {
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar");
assertEquals(new MatchNoDocsQuery(), bar.wildcardQuery("f*o", null, null));
assertEquals(new MatchNoDocsQuery(), bar.wildcardQuery("f*o", null, false, null));
assertEquals(new MatchNoDocsQuery(), bar.wildcardQuery("F*o", null, true, null));
ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("f*o", null, null));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("b*r", null, null));
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("f*o", null, false, null));
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("F*o", null, true, null));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("b*r", null, false, null));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("B*r", null, true, null));
}
public void testPrefixQuery() {
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar");
assertEquals(new MatchNoDocsQuery(), bar.prefixQuery("fo", null, null));
assertEquals(new MatchNoDocsQuery(), bar.prefixQuery("fo", null, false, null));
assertEquals(new MatchNoDocsQuery(), bar.prefixQuery("fO", null, true, null));
ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.prefixQuery("fo", null, null));
assertEquals(new MatchNoDocsQuery(), ft.prefixQuery("ba", null, null));
assertEquals(new MatchAllDocsQuery(), ft.prefixQuery("fo", null, false, null));
assertEquals(new MatchAllDocsQuery(), ft.prefixQuery("fO", null, true, null));
assertEquals(new MatchNoDocsQuery(), ft.prefixQuery("ba", null, false, null));
assertEquals(new MatchNoDocsQuery(), ft.prefixQuery("Ba", null, true, null));
}
public void testExistsQuery() {

View File

@ -12,6 +12,7 @@ import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.OrdinalMap;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery;
@ -20,6 +21,7 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -292,11 +294,23 @@ public final class FlatObjectFieldMapper extends DynamicKeyFieldMapper {
@Override
public Query wildcardQuery(String value,
MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) {
throw new UnsupportedOperationException("[wildcard] queries are not currently supported on keyed " +
"[" + CONTENT_TYPE + "] fields.");
}
@Override
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
Query query = AutomatonQueries.caseInsensitiveTermQuery(new Term(name(), indexedValueForSearch(value)));
if (boost() != 1f) {
query = new BoostQuery(query, boost());
}
return query;
}
@Override
public BytesRef indexedValueForSearch(Object value) {
if (value == null) {

View File

@ -15,6 +15,7 @@ import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.FieldTypeTestCase;
import org.elasticsearch.xpack.flattened.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType;
@ -48,6 +49,11 @@ public class KeyedFlatObjectFieldTypeTests extends FieldTypeTestCase {
Query expected = new TermQuery(new Term("field", "key\0value"));
assertEquals(expected, ft.termQuery("value", null));
expected = AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "key\0value"));
assertEquals(expected, ft.termQueryCaseInsensitive("value", null));
KeyedFlatObjectFieldType unsearchable = new KeyedFlatObjectFieldType("field", false, true, "key",
false, Collections.emptyMap());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@ -81,10 +87,14 @@ public class KeyedFlatObjectFieldTypeTests extends FieldTypeTestCase {
KeyedFlatObjectFieldType ft = createFieldType();
Query expected = new PrefixQuery(new Term("field", "key\0val"));
assertEquals(expected, ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, MOCK_QSC));
assertEquals(expected, ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, false, MOCK_QSC));
expected = AutomatonQueries.caseInsensitivePrefixQuery(new Term("field", "key\0vAl"));
assertEquals(expected, ft.prefixQuery("vAl", MultiTermQuery.CONSTANT_SCORE_REWRITE, true, MOCK_QSC));
ElasticsearchException ee = expectThrows(ElasticsearchException.class,
() -> ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, MOCK_QSC_DISALLOW_EXPENSIVE));
() -> ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, false, MOCK_QSC_DISALLOW_EXPENSIVE));
assertEquals("[prefix] queries cannot be executed when 'search.allow_expensive_queries' is set to false. " +
"For optimised prefix queries on text fields please enable [index_prefixes].", ee.getMessage());
}
@ -138,7 +148,7 @@ public class KeyedFlatObjectFieldTypeTests extends FieldTypeTestCase {
KeyedFlatObjectFieldType ft = createFieldType();
UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class,
() -> ft.wildcardQuery("valu*", null, randomMockShardContext()));
() -> ft.wildcardQuery("valu*", null, false, randomMockShardContext()));
assertEquals("[wildcard] queries are not currently supported on keyed [flattened] fields.", e.getMessage());
}
}

View File

@ -16,6 +16,7 @@ import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.FieldTypeTestCase;
@ -43,6 +44,10 @@ public class RootFlatObjectFieldTypeTests extends FieldTypeTestCase {
Query expected = new TermQuery(new Term("field", "value"));
assertEquals(expected, ft.termQuery("value", null));
expected = AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "Value"));
assertEquals(expected, ft.termQueryCaseInsensitive("Value", null));
RootFlatObjectFieldType unsearchable = new RootFlatObjectFieldType("field", false, true,
Collections.emptyMap(), false);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,

View File

@ -133,12 +133,12 @@ abstract class AbstractScriptMappedFieldType<LeafFactory> extends MappedFieldTyp
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
throw new IllegalArgumentException(unsupported("prefix", "keyword, text and wildcard"));
}
@Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
throw new IllegalArgumentException(unsupported("wildcard", "keyword, text and wildcard"));
}

View File

@ -10,6 +10,7 @@ import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.index.mapper.BooleanFieldMapper;
@ -138,10 +139,16 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
return termsQuery(trueAllowed, falseAllowed, context);
}
@Override
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context.lookup()), name(), toBoolean(value, true));
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), toBoolean(value));
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), toBoolean(value, false));
}
@Override
@ -152,7 +159,7 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
boolean trueAllowed = false;
boolean falseAllowed = false;
for (Object value : values) {
if (toBoolean(value)) {
if (toBoolean(value, false)) {
trueAllowed = true;
} else {
falseAllowed = true;
@ -177,10 +184,14 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
return new MatchNoDocsQuery("neither true nor false allowed");
}
private static boolean toBoolean(Object value) {
return toBoolean(value, false);
}
/**
* Convert the term into a boolean. Inspired by {@link BooleanFieldMapper.BooleanFieldType#indexedValueForSearch(Object)}.
*/
private static boolean toBoolean(Object value) {
private static boolean toBoolean(Object value, boolean caseInsensitive) {
if (value == null) {
return false;
}
@ -193,6 +204,9 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
} else {
sValue = value.toString();
}
if (caseInsensitive) {
sValue = Strings.toLowercaseAscii(sValue);
}
return Booleans.parseBoolean(sValue);
}
}

View File

@ -88,9 +88,14 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
}
@Override
public Query prefixQuery(String value, RewriteMethod method, org.elasticsearch.index.query.QueryShardContext context) {
public Query prefixQuery(
String value,
RewriteMethod method,
boolean caseInsensitive,
org.elasticsearch.index.query.QueryShardContext context
) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value);
return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value, caseInsensitive);
}
@Override
@ -128,13 +133,39 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
if (matchFlags != 0) {
throw new IllegalArgumentException("Match flags not yet implemented [" + matchFlags + "]");
}
return new StringScriptFieldRegexpQuery(script, leafFactory(context), name(), value, syntaxFlags, maxDeterminizedStates);
return new StringScriptFieldRegexpQuery(
script,
leafFactory(context),
name(),
value,
syntaxFlags,
matchFlags,
maxDeterminizedStates
);
}
@Override
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldTermQuery(
script,
leafFactory(context),
name(),
BytesRefs.toString(Objects.requireNonNull(value)),
true
);
}
@Override
public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldTermQuery(script, leafFactory(context), name(), BytesRefs.toString(Objects.requireNonNull(value)));
return new StringScriptFieldTermQuery(
script,
leafFactory(context),
name(),
BytesRefs.toString(Objects.requireNonNull(value)),
false
);
}
@Override
@ -145,8 +176,8 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
}
@Override
public Query wildcardQuery(String value, RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String value, RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
checkAllowExpensiveQueries(context);
return new StringScriptFieldWildcardQuery(script, leafFactory(context), name(), value);
return new StringScriptFieldWildcardQuery(script, leafFactory(context), name(), value, caseInsensitive);
}
}

View File

@ -9,7 +9,9 @@ package org.elasticsearch.xpack.runtimefields.query;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.QueryVisitor;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.ByteRunAutomaton;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.script.Script;
import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript;
@ -18,26 +20,63 @@ import java.util.Objects;
public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery {
private final String prefix;
private final boolean caseInsensitive;
public StringScriptFieldPrefixQuery(Script script, StringFieldScript.LeafFactory leafFactory, String fieldName, String prefix) {
public StringScriptFieldPrefixQuery(
Script script,
StringFieldScript.LeafFactory leafFactory,
String fieldName,
String prefix,
boolean caseInsensitive
) {
super(script, leafFactory, fieldName);
this.prefix = Objects.requireNonNull(prefix);
this.caseInsensitive = caseInsensitive;
}
@Override
protected boolean matches(List<String> values) {
for (String value : values) {
if (value != null && value.startsWith(prefix)) {
if (startsWith(value, prefix, caseInsensitive)) {
return true;
}
}
return false;
}
/**
* <p>Check if a String starts with a specified prefix (optionally case insensitive).</p>
*
* @see java.lang.String#startsWith(String)
* @param str the String to check, may be null
* @param prefix the prefix to find, may be null
* @param ignoreCase inidicates whether the compare should ignore case
* (case insensitive) or not.
* @return <code>true</code> if the String starts with the prefix or
* both <code>null</code>
*/
private static boolean startsWith(String str, String prefix, boolean ignoreCase) {
if (str == null || prefix == null) {
return (str == null && prefix == null);
}
if (prefix.length() > str.length()) {
return false;
}
return str.regionMatches(ignoreCase, 0, prefix, 0, prefix.length());
}
@Override
public void visit(QueryVisitor visitor) {
if (visitor.acceptField(fieldName())) {
visitor.consumeTermsMatching(this, fieldName(), () -> new ByteRunAutomaton(PrefixQuery.toAutomaton(new BytesRef(prefix))));
visitor.consumeTermsMatching(this, fieldName(), () -> new ByteRunAutomaton(buildAutomaton(new BytesRef(prefix))));
}
}
Automaton buildAutomaton(BytesRef prefix) {
if (caseInsensitive) {
return AutomatonQueries.caseInsensitivePrefix(prefix.utf8ToString());
} else {
return PrefixQuery.toAutomaton(prefix);
}
}
@ -51,7 +90,7 @@ public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), prefix);
return Objects.hash(super.hashCode(), prefix, caseInsensitive);
}
@Override
@ -60,10 +99,14 @@ public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery
return false;
}
StringScriptFieldPrefixQuery other = (StringScriptFieldPrefixQuery) obj;
return prefix.equals(other.prefix);
return prefix.equals(other.prefix) && caseInsensitive == other.caseInsensitive;
}
String prefix() {
return prefix;
}
boolean caseInsensitive() {
return caseInsensitive;
}
}

View File

@ -15,24 +15,27 @@ import java.util.Objects;
public class StringScriptFieldRegexpQuery extends AbstractStringScriptFieldAutomatonQuery {
private final String pattern;
private final int flags;
private final int syntaxFlags;
private final int matchFlags;
public StringScriptFieldRegexpQuery(
Script script,
StringFieldScript.LeafFactory leafFactory,
String fieldName,
String pattern,
int flags,
int syntaxFlags,
int matchFlags,
int maxDeterminizedStates
) {
super(
script,
leafFactory,
fieldName,
new ByteRunAutomaton(new RegExp(Objects.requireNonNull(pattern), flags).toAutomaton(maxDeterminizedStates))
new ByteRunAutomaton(new RegExp(Objects.requireNonNull(pattern), syntaxFlags, matchFlags).toAutomaton(maxDeterminizedStates))
);
this.pattern = pattern;
this.flags = flags;
this.syntaxFlags = syntaxFlags;
this.matchFlags = matchFlags;
}
@Override
@ -46,7 +49,7 @@ public class StringScriptFieldRegexpQuery extends AbstractStringScriptFieldAutom
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), pattern, flags);
return Objects.hash(super.hashCode(), pattern, syntaxFlags, matchFlags);
}
@Override
@ -55,14 +58,18 @@ public class StringScriptFieldRegexpQuery extends AbstractStringScriptFieldAutom
return false;
}
StringScriptFieldRegexpQuery other = (StringScriptFieldRegexpQuery) obj;
return pattern.equals(other.pattern) && flags == other.flags;
return pattern.equals(other.pattern) && syntaxFlags == other.syntaxFlags && matchFlags == other.matchFlags;
}
String pattern() {
return pattern;
}
int flags() {
return flags;
int syntaxFlags() {
return syntaxFlags;
}
int matchFlags() {
return matchFlags;
}
}

View File

@ -16,16 +16,28 @@ import java.util.Objects;
public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
private final String term;
private final boolean caseInsensitive;
public StringScriptFieldTermQuery(Script script, StringFieldScript.LeafFactory leafFactory, String fieldName, String term) {
public StringScriptFieldTermQuery(
Script script,
StringFieldScript.LeafFactory leafFactory,
String fieldName,
String term,
boolean caseInsensitive
) {
super(script, leafFactory, fieldName);
this.term = Objects.requireNonNull(term);
this.caseInsensitive = caseInsensitive;
}
@Override
protected boolean matches(List<String> values) {
for (String value : values) {
if (term.equals(value)) {
if (caseInsensitive) {
if (term.equalsIgnoreCase(value)) {
return true;
}
} else if (term.equals(value)) {
return true;
}
}
@ -47,7 +59,7 @@ public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), term);
return Objects.hash(super.hashCode(), term, caseInsensitive);
}
@Override
@ -56,10 +68,14 @@ public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
return false;
}
StringScriptFieldTermQuery other = (StringScriptFieldTermQuery) obj;
return term.equals(other.term);
return term.equals(other.term) && caseInsensitive == other.caseInsensitive;
}
String term() {
return term;
}
boolean caseInsensitive() {
return caseInsensitive;
}
}

View File

@ -8,7 +8,9 @@ package org.elasticsearch.xpack.runtimefields.query;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.ByteRunAutomaton;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.script.Script;
import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript;
@ -16,15 +18,30 @@ import java.util.Objects;
public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAutomatonQuery {
private final String pattern;
private final boolean caseInsensitive;
public StringScriptFieldWildcardQuery(Script script, StringFieldScript.LeafFactory leafFactory, String fieldName, String pattern) {
public StringScriptFieldWildcardQuery(
Script script,
StringFieldScript.LeafFactory leafFactory,
String fieldName,
String pattern,
boolean caseInsensitive
) {
super(
script,
leafFactory,
fieldName,
new ByteRunAutomaton(WildcardQuery.toAutomaton(new Term(fieldName, Objects.requireNonNull(pattern))))
new ByteRunAutomaton(buildAutomaton(new Term(fieldName, Objects.requireNonNull(pattern)), caseInsensitive))
);
this.pattern = pattern;
this.caseInsensitive = caseInsensitive;
}
private static Automaton buildAutomaton(Term term, boolean caseInsensitive) {
if (caseInsensitive) {
return AutomatonQueries.toCaseInsensitiveWildcardAutomaton(term, Integer.MAX_VALUE);
}
return WildcardQuery.toAutomaton(term);
}
@Override
@ -37,7 +54,7 @@ public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAut
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), pattern);
return Objects.hash(super.hashCode(), pattern, caseInsensitive);
}
@Override
@ -46,10 +63,14 @@ public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAut
return false;
}
StringScriptFieldWildcardQuery other = (StringScriptFieldWildcardQuery) obj;
return pattern.equals(other.pattern);
return pattern.equals(other.pattern) && caseInsensitive == other.caseInsensitive;
}
String pattern() {
return pattern;
}
boolean caseInsensitive() {
return caseInsensitive;
}
}

View File

@ -272,7 +272,7 @@ public class KeywordScriptMappedFieldTypeTests extends AbstractScriptMappedField
}
private Query randomRegexpQuery(MappedFieldType ft, QueryShardContext ctx) {
return ft.regexpQuery(randomAlphaOfLengthBetween(1, 1000), randomInt(0xFFFF), 0, Integer.MAX_VALUE, null, ctx);
return ft.regexpQuery(randomAlphaOfLengthBetween(1, 1000), randomInt(0xFF), 0, Integer.MAX_VALUE, null, ctx);
}
@Override

View File

@ -16,12 +16,18 @@ import static org.hamcrest.Matchers.is;
public class StringScriptFieldPrefixQueryTests extends AbstractStringScriptFieldQueryTestCase<StringScriptFieldPrefixQuery> {
@Override
protected StringScriptFieldPrefixQuery createTestInstance() {
return new StringScriptFieldPrefixQuery(randomScript(), leafFactory, randomAlphaOfLength(5), randomAlphaOfLength(6));
return new StringScriptFieldPrefixQuery(
randomScript(),
leafFactory,
randomAlphaOfLength(5),
randomAlphaOfLength(6),
randomBoolean()
);
}
@Override
protected StringScriptFieldPrefixQuery copy(StringScriptFieldPrefixQuery orig) {
return new StringScriptFieldPrefixQuery(orig.script(), leafFactory, orig.fieldName(), orig.prefix());
return new StringScriptFieldPrefixQuery(orig.script(), leafFactory, orig.fieldName(), orig.prefix(), orig.caseInsensitive());
}
@Override
@ -29,6 +35,7 @@ public class StringScriptFieldPrefixQueryTests extends AbstractStringScriptField
Script script = orig.script();
String fieldName = orig.fieldName();
String prefix = orig.prefix();
boolean caseInsensitive = orig.caseInsensitive();
switch (randomInt(2)) {
case 0:
script = randomValueOtherThan(script, this::randomScript);
@ -39,19 +46,30 @@ public class StringScriptFieldPrefixQueryTests extends AbstractStringScriptField
case 2:
prefix += "modified";
break;
case 3:
caseInsensitive = !caseInsensitive;
break;
default:
fail();
}
return new StringScriptFieldPrefixQuery(script, leafFactory, fieldName, prefix);
return new StringScriptFieldPrefixQuery(script, leafFactory, fieldName, prefix, caseInsensitive);
}
@Override
public void testMatches() {
StringScriptFieldPrefixQuery query = new StringScriptFieldPrefixQuery(randomScript(), leafFactory, "test", "foo");
StringScriptFieldPrefixQuery query = new StringScriptFieldPrefixQuery(randomScript(), leafFactory, "test", "foo", false);
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("foo")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("Foo")));
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("foooo")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("Foooo")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("fo")));
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("fo", "foo")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("Fo", "fOo")));
StringScriptFieldPrefixQuery ciQuery = new StringScriptFieldPrefixQuery(randomScript(), leafFactory, "test", "foo", true);
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("fOo")));
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("Foooo")));
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("fo", "foO")));
}
@Override

View File

@ -18,12 +18,14 @@ import static org.hamcrest.Matchers.is;
public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptFieldQueryTestCase<StringScriptFieldRegexpQuery> {
@Override
protected StringScriptFieldRegexpQuery createTestInstance() {
int matchFlags = randomBoolean() ? 0 : RegExp.ASCII_CASE_INSENSITIVE;
return new StringScriptFieldRegexpQuery(
randomScript(),
leafFactory,
randomAlphaOfLength(5),
randomAlphaOfLength(6),
randomInt(RegExp.ALL),
matchFlags,
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
}
@ -35,7 +37,8 @@ public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptField
leafFactory,
orig.fieldName(),
orig.pattern(),
orig.flags(),
orig.syntaxFlags(),
orig.matchFlags(),
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
}
@ -45,8 +48,9 @@ public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptField
Script script = orig.script();
String fieldName = orig.fieldName();
String pattern = orig.pattern();
int flags = orig.flags();
switch (randomInt(3)) {
int syntaxFlags = orig.syntaxFlags();
int matchFlags = orig.matchFlags();
switch (randomInt(4)) {
case 0:
script = randomValueOtherThan(script, this::randomScript);
break;
@ -57,12 +61,23 @@ public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptField
pattern += "modified";
break;
case 3:
flags = randomValueOtherThan(flags, () -> randomInt(RegExp.ALL));
syntaxFlags = randomValueOtherThan(syntaxFlags, () -> randomInt(RegExp.ALL));
break;
case 4:
matchFlags = (matchFlags & RegExp.ASCII_CASE_INSENSITIVE) != 0 ? 0 : RegExp.ASCII_CASE_INSENSITIVE;
break;
default:
fail();
}
return new StringScriptFieldRegexpQuery(script, leafFactory, fieldName, pattern, flags, Operations.DEFAULT_MAX_DETERMINIZED_STATES);
return new StringScriptFieldRegexpQuery(
script,
leafFactory,
fieldName,
pattern,
syntaxFlags,
matchFlags,
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
}
@Override
@ -73,14 +88,28 @@ public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptField
"test",
"a.+b",
0,
0,
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("astuffb")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("astuffB")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("fffff")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("ab")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("aasdf")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("dsfb")));
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("astuffb", "fffff")));
StringScriptFieldRegexpQuery ciQuery = new StringScriptFieldRegexpQuery(
randomScript(),
leafFactory,
"test",
"a.+b",
0,
RegExp.ASCII_CASE_INSENSITIVE,
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("astuffB")));
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("Astuffb", "fffff")));
}
@Override
@ -96,6 +125,7 @@ public class StringScriptFieldRegexpQueryTests extends AbstractStringScriptField
"test",
"a.+b",
0,
0,
Operations.DEFAULT_MAX_DETERMINIZED_STATES
);
ByteRunAutomaton automaton = visitForSingleAutomata(query);

View File

@ -22,12 +22,12 @@ import static org.hamcrest.Matchers.equalTo;
public class StringScriptFieldTermQueryTests extends AbstractStringScriptFieldQueryTestCase<StringScriptFieldTermQuery> {
@Override
protected StringScriptFieldTermQuery createTestInstance() {
return new StringScriptFieldTermQuery(randomScript(), leafFactory, randomAlphaOfLength(5), randomAlphaOfLength(6));
return new StringScriptFieldTermQuery(randomScript(), leafFactory, randomAlphaOfLength(5), randomAlphaOfLength(6), randomBoolean());
}
@Override
protected StringScriptFieldTermQuery copy(StringScriptFieldTermQuery orig) {
return new StringScriptFieldTermQuery(orig.script(), leafFactory, orig.fieldName(), orig.term());
return new StringScriptFieldTermQuery(orig.script(), leafFactory, orig.fieldName(), orig.term(), orig.caseInsensitive());
}
@Override
@ -35,7 +35,8 @@ public class StringScriptFieldTermQueryTests extends AbstractStringScriptFieldQu
Script script = orig.script();
String fieldName = orig.fieldName();
String term = orig.term();
switch (randomInt(2)) {
boolean caseInsensitive = orig.caseInsensitive();
switch (randomInt(3)) {
case 0:
script = randomValueOtherThan(script, this::randomScript);
break;
@ -45,18 +46,26 @@ public class StringScriptFieldTermQueryTests extends AbstractStringScriptFieldQu
case 2:
term += "modified";
break;
case 3:
caseInsensitive = !caseInsensitive;
break;
default:
fail();
}
return new StringScriptFieldTermQuery(script, leafFactory, fieldName, term);
return new StringScriptFieldTermQuery(script, leafFactory, fieldName, term, caseInsensitive);
}
@Override
public void testMatches() {
StringScriptFieldTermQuery query = new StringScriptFieldTermQuery(randomScript(), leafFactory, "test", "foo");
StringScriptFieldTermQuery query = new StringScriptFieldTermQuery(randomScript(), leafFactory, "test", "foo", false);
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("foo")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("foO")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("bar")));
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("foo", "bar")));
StringScriptFieldTermQuery ciQuery = new StringScriptFieldTermQuery(randomScript(), leafFactory, "test", "foo", true);
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("Foo")));
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("fOo", "bar")));
}
@Override

View File

@ -15,12 +15,18 @@ import static org.hamcrest.Matchers.equalTo;
public class StringScriptFieldWildcardQueryTests extends AbstractStringScriptFieldQueryTestCase<StringScriptFieldWildcardQuery> {
@Override
protected StringScriptFieldWildcardQuery createTestInstance() {
return new StringScriptFieldWildcardQuery(randomScript(), leafFactory, randomAlphaOfLength(5), randomAlphaOfLength(6));
return new StringScriptFieldWildcardQuery(
randomScript(),
leafFactory,
randomAlphaOfLength(5),
randomAlphaOfLength(6),
randomBoolean()
);
}
@Override
protected StringScriptFieldWildcardQuery copy(StringScriptFieldWildcardQuery orig) {
return new StringScriptFieldWildcardQuery(orig.script(), leafFactory, orig.fieldName(), orig.pattern());
return new StringScriptFieldWildcardQuery(orig.script(), leafFactory, orig.fieldName(), orig.pattern(), orig.caseInsensitive());
}
@Override
@ -28,7 +34,8 @@ public class StringScriptFieldWildcardQueryTests extends AbstractStringScriptFie
Script script = orig.script();
String fieldName = orig.fieldName();
String pattern = orig.pattern();
switch (randomInt(2)) {
boolean caseInsensitive = orig.caseInsensitive();
switch (randomInt(3)) {
case 0:
script = randomValueOtherThan(script, this::randomScript);
break;
@ -38,22 +45,31 @@ public class StringScriptFieldWildcardQueryTests extends AbstractStringScriptFie
case 2:
pattern += "modified";
break;
case 3:
caseInsensitive = !caseInsensitive;
break;
default:
fail();
}
return new StringScriptFieldWildcardQuery(script, leafFactory, fieldName, pattern);
return new StringScriptFieldWildcardQuery(script, leafFactory, fieldName, pattern, caseInsensitive);
}
@Override
public void testMatches() {
StringScriptFieldWildcardQuery query = new StringScriptFieldWildcardQuery(randomScript(), leafFactory, "test", "a*b");
StringScriptFieldWildcardQuery query = new StringScriptFieldWildcardQuery(randomScript(), leafFactory, "test", "a*b", false);
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("astuffb")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("Astuffb")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("fffff")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("a")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("b")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("aasdf")));
assertFalse(query.matches(org.elasticsearch.common.collect.List.of("dsfb")));
assertTrue(query.matches(org.elasticsearch.common.collect.List.of("astuffb", "fffff")));
StringScriptFieldWildcardQuery ciQuery = new StringScriptFieldWildcardQuery(randomScript(), leafFactory, "test", "a*b", true);
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("Astuffb")));
assertTrue(ciQuery.matches(org.elasticsearch.common.collect.List.of("astuffB", "fffff")));
}
@Override
@ -63,7 +79,7 @@ public class StringScriptFieldWildcardQueryTests extends AbstractStringScriptFie
@Override
public void testVisit() {
StringScriptFieldWildcardQuery query = new StringScriptFieldWildcardQuery(randomScript(), leafFactory, "test", "a*b");
StringScriptFieldWildcardQuery query = new StringScriptFieldWildcardQuery(randomScript(), leafFactory, "test", "a*b", false);
ByteRunAutomaton automaton = visitForSingleAutomata(query);
BytesRef term = new BytesRef("astuffb");
assertTrue(automaton.run(term.bytes, term.offset, term.length));

View File

@ -138,8 +138,8 @@ public class VersionStringFieldMapper extends ParametrizedFieldMapper {
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
return wildcardQuery(value + "*", method, context);
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
return wildcardQuery(value + "*", method, caseInsensitive, context);
}
/**
@ -237,7 +237,7 @@ public class VersionStringFieldMapper extends ParametrizedFieldMapper {
}
@Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException(
"[wildcard] queries cannot be executed when '" + ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false."

View File

@ -40,6 +40,7 @@ import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -218,7 +219,7 @@ public class WildcardFieldMapper extends FieldMapper {
}
@Override
public Query wildcardQuery(String wildcardPattern, RewriteMethod method, QueryShardContext context) {
public Query wildcardQuery(String wildcardPattern, RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
String ngramIndexPattern = addLineEndChars(toLowerCase(wildcardPattern));
@ -276,7 +277,11 @@ public class WildcardFieldMapper extends FieldMapper {
clauseCount++;
}
Supplier<Automaton> deferredAutomatonSupplier = () -> {
return WildcardQuery.toAutomaton(new Term(name(), wildcardPattern));
if(caseInsensitive) {
return AutomatonQueries.toCaseInsensitiveWildcardAutomaton(new Term(name(), wildcardPattern), Integer.MAX_VALUE);
} else {
return WildcardQuery.toAutomaton(new Term(name(), wildcardPattern));
}
};
AutomatonQueryOnBinaryDv verifyingQuery = new AutomatonQueryOnBinaryDv(name(), wildcardPattern, deferredAutomatonSupplier);
if (clauseCount > 0) {
@ -845,7 +850,7 @@ public class WildcardFieldMapper extends FieldMapper {
@Override
public Query termQuery(Object value, QueryShardContext context) {
String searchTerm = BytesRefs.toString(value);
return wildcardQuery(escapeWildcardSyntax(searchTerm), MultiTermQuery.CONSTANT_SCORE_REWRITE, context);
return wildcardQuery(escapeWildcardSyntax(searchTerm), MultiTermQuery.CONSTANT_SCORE_REWRITE, false, context);
}
private String escapeWildcardSyntax(String term) {
@ -862,10 +867,16 @@ public class WildcardFieldMapper extends FieldMapper {
}
return result.toString();
}
@Override
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
String searchTerm = BytesRefs.toString(value);
return wildcardQuery(escapeWildcardSyntax(searchTerm), MultiTermQuery.CONSTANT_SCORE_REWRITE, true, context);
}
@Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) {
return wildcardQuery(escapeWildcardSyntax(value) + "*", method, context);
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
return wildcardQuery(escapeWildcardSyntax(value) + "*", method, caseInsensitive, context);
}
@Override
@ -876,7 +887,7 @@ public class WildcardFieldMapper extends FieldMapper {
}
return new ConstantScoreQuery(bq.build());
}
@Override
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier<SearchLookup> searchLookup) {
failIfNoDocValues();

View File

@ -263,18 +263,21 @@ public class WildcardFieldMapperTests extends ESTestCase {
switch (randomInt(4)) {
case 0:
pattern = getRandomWildcardPattern();
wildcardFieldQuery = wildcardFieldType.fieldType().wildcardQuery(pattern, null, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().wildcardQuery(pattern, null, MOCK_QSC);
boolean caseInsensitive = randomBoolean();
wildcardFieldQuery = wildcardFieldType.fieldType().wildcardQuery(pattern, null, caseInsensitive, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().wildcardQuery(pattern, null, caseInsensitive, MOCK_QSC);
break;
case 1:
pattern = getRandomRegexPattern(values);
wildcardFieldQuery = wildcardFieldType.fieldType().regexpQuery(pattern, RegExp.ALL, 0, 20000, null, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().regexpQuery(pattern, RegExp.ALL, 0,20000, null, MOCK_QSC);
int matchFlags = randomBoolean()? 0 : RegExp.ASCII_CASE_INSENSITIVE;
wildcardFieldQuery = wildcardFieldType.fieldType().regexpQuery(pattern, RegExp.ALL, matchFlags, 20000, null, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().regexpQuery(pattern, RegExp.ALL, matchFlags,20000, null, MOCK_QSC);
break;
case 2:
pattern = randomABString(5);
wildcardFieldQuery = wildcardFieldType.fieldType().prefixQuery(pattern, null, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().prefixQuery(pattern, null, MOCK_QSC);
boolean caseInsensitivePrefix = randomBoolean();
wildcardFieldQuery = wildcardFieldType.fieldType().prefixQuery(pattern, null, caseInsensitivePrefix, MOCK_QSC);
keywordFieldQuery = keywordFieldType.fieldType().prefixQuery(pattern, null, caseInsensitivePrefix, MOCK_QSC);
break;
case 3:
int edits = randomInt(2);