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 (Optional, string) Method used to rewrite the query. For valid values and more
information, see the <<query-dsl-multi-term-rewrite, `rewrite` parameter>>. 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]] [[prefix-query-notes]]
==== 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` `0` and `1.0` decreases the relevance score. A value greater than `1.0`
increases the relevance score. 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]] [[term-query-notes]]
==== Notes ==== Notes

View File

@ -69,6 +69,10 @@ increases the relevance score.
(Optional, string) Method used to rewrite the query. For valid values and more information, see the (Optional, string) Method used to rewrite the query. For valid values and more information, see the
<<query-dsl-multi-term-rewrite, `rewrite` parameter>>. <<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]] [[wildcard-query-notes]]
==== Notes ==== Notes
===== Allow expensive queries ===== Allow expensive queries

View File

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

View File

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

View File

@ -879,4 +879,18 @@ public class Strings {
return sb.toString(); 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 * Match a String against the given pattern, supporting the following simple
* pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality. * arbitrary number of pattern parts), as well as direct equality.
* Matching is case sensitive.
* *
* @param pattern the pattern to match against * @param pattern the pattern to match against
* @param str the String to match * @param str the String to match
* @return whether the String matches the given pattern * @return whether the String matches the given pattern
*/ */
public static boolean simpleMatch(String pattern, String str) { 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) { if (pattern == null || str == null) {
return false; 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('*'); final int firstIndex = pattern.indexOf('*');
if (firstIndex == -1) { if (firstIndex == -1) {
return pattern.equals(str); return pattern.equals(str);
@ -102,12 +126,12 @@ public class Regex {
return str.regionMatches(str.length() - pattern.length() + 1, pattern, 1, pattern.length() - 1); return str.regionMatches(str.length() - pattern.length() + 1, pattern, 1, pattern.length() - 1);
} else if (nextIndex == 1) { } else if (nextIndex == 1) {
// Double wildcard "**" - skipping the first "*" // Double wildcard "**" - skipping the first "*"
return simpleMatch(pattern.substring(1), str); return simpleMatchWithNormalizedStrings(pattern.substring(1), str);
} }
final String part = pattern.substring(1, nextIndex); final String part = pattern.substring(1, nextIndex);
int partIndex = str.indexOf(part); int partIndex = str.indexOf(part);
while (partIndex != -1) { 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; return true;
} }
partIndex = str.indexOf(part, partIndex + 1); partIndex = str.indexOf(part, partIndex + 1);
@ -116,7 +140,7 @@ public class Regex {
} }
return str.regionMatches(0, pattern, 0, firstIndex) 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 && (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)));
} }
/** /**

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} * Return whether the constant value of this field matches the provided {@code pattern}
* as documented in {@link Regex#simpleMatch}. * 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) { private static String valueToString(Object value) {
return value instanceof BytesRef return value instanceof BytesRef
@ -70,7 +70,17 @@ public abstract class ConstantFieldType extends MappedFieldType {
@Override @Override
public final Query termQuery(Object value, QueryShardContext context) { public final Query termQuery(Object value, QueryShardContext context) {
String pattern = valueToString(value); 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(); return Queries.newMatchAllQuery();
} else { } else {
return new MatchNoDocsQuery(); return new MatchNoDocsQuery();
@ -81,7 +91,7 @@ public abstract class ConstantFieldType extends MappedFieldType {
public final Query termsQuery(List<?> values, QueryShardContext context) { public final Query termsQuery(List<?> values, QueryShardContext context) {
for (Object value : values) { for (Object value : values) {
String pattern = valueToString(value); String pattern = valueToString(value);
if (matches(pattern, context)) { if (matches(pattern, false, context)) {
// `terms` queries are a disjunction, so one matching term is enough // `terms` queries are a disjunction, so one matching term is enough
return Queries.newMatchAllQuery(); return Queries.newMatchAllQuery();
} }
@ -92,9 +102,10 @@ public abstract class ConstantFieldType extends MappedFieldType {
@Override @Override
public final Query prefixQuery(String prefix, public final Query prefixQuery(String prefix,
@Nullable MultiTermQuery.RewriteMethod method, @Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) { QueryShardContext context) {
String pattern = prefix + "*"; String pattern = prefix + "*";
if (matches(pattern, context)) { if (matches(pattern, caseInsensitive, context)) {
return Queries.newMatchAllQuery(); return Queries.newMatchAllQuery();
} else { } else {
return new MatchNoDocsQuery(); return new MatchNoDocsQuery();
@ -104,8 +115,9 @@ public abstract class ConstantFieldType extends MappedFieldType {
@Override @Override
public final Query wildcardQuery(String value, public final Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method, @Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) { QueryShardContext context) {
if (matches(value, context)) { if (matches(value, caseInsensitive, context)) {
return Queries.newMatchAllQuery(); return Queries.newMatchAllQuery();
} else { } else {
return new MatchNoDocsQuery(); 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.MatchAllDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData; import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardContext;
@ -52,7 +53,12 @@ public class IndexFieldMapper extends MetadataFieldMapper {
} }
@Override @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); return context.indexMatches(pattern);
} }

View File

@ -177,6 +177,13 @@ public abstract class MappedFieldType {
// TODO: Standardize exception types // TODO: Standardize exception types
public abstract Query termQuery(Object value, @Nullable QueryShardContext context); 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 /** 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 * {@link ConstantScoreQuery} around a {@link BooleanQuery} whose {@link Occur#SHOULD} clauses
* are generated with {@link #termQuery}. */ * are generated with {@link #termQuery}. */
@ -206,14 +213,27 @@ public abstract class MappedFieldType {
+ "] which is of type [" + typeName() + "]"); + "] 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 throw new QueryShardException(context, "Can only use prefix queries on keyword, text and wildcard fields - not on [" + name
+ "] which is of type [" + typeName() + "]"); + "] 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, public Query wildcardQuery(String value,
@Nullable MultiTermQuery.RewriteMethod method, @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 throw new QueryShardException(context, "Can only use wildcard queries on keyword, text and wildcard fields - not on [" + name
+ "] which is of type [" + typeName() + "]"); + "] 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.analysis.Analyzer;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.PrefixQuery;
@ -32,6 +33,7 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder; import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.support.QueryParsers; import org.elasticsearch.index.query.support.QueryParsers;
@ -68,13 +70,21 @@ public abstract class StringFieldType extends TermBasedFieldType {
} }
@Override @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) { if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException("[prefix] queries cannot be executed when '" + throw new ElasticsearchException("[prefix] queries cannot be executed when '" +
ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false. For optimised prefix queries on text " + ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false. For optimised prefix queries on text " +
"fields please enable [index_prefixes]."); "fields please enable [index_prefixes].");
} }
failIfNotIndexed(); 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))); PrefixQuery query = new PrefixQuery(new Term(name(), indexedValueForSearch(value)));
if (method != null) { if (method != null) {
query.setRewriteMethod(method); query.setRewriteMethod(method);
@ -113,7 +123,7 @@ public abstract class StringFieldType extends TermBasedFieldType {
} }
@Override @Override
public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) { public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
failIfNotIndexed(); failIfNotIndexed();
if (context.allowExpensiveQueries() == false) { if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException("[wildcard] queries cannot be executed when '" + throw new ElasticsearchException("[wildcard] queries cannot be executed when '" +
@ -127,7 +137,11 @@ public abstract class StringFieldType extends TermBasedFieldType {
} else { } else {
term = new Term(name(), indexedValueForSearch(value)); term = new Term(name(), indexedValueForSearch(value));
} }
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitiveWildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
}
WildcardQuery query = new WildcardQuery(term); WildcardQuery query = new WildcardQuery(term);
QueryParsers.setRewriteMethod(query, method); QueryParsers.setRewriteMethod(query, method);
return query; return query;

View File

@ -26,6 +26,7 @@ import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardContext;
import java.util.List; import java.util.List;
@ -47,6 +48,16 @@ public abstract class TermBasedFieldType extends SimpleMappedFieldType {
return BytesRefs.toBytesRef(value); 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 @Override
public Query termQuery(Object value, QueryShardContext context) { public Query termQuery(Object value, QueryShardContext context) {
failIfNotIndexed(); failIfNotIndexed();

View File

@ -60,6 +60,7 @@ import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.common.collect.Iterators;
import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery; import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.common.xcontent.support.XContentMapValues;
@ -439,12 +440,20 @@ public class TextFieldMapper extends FieldMapper {
} }
@Override @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 (value.length() >= minChars) {
if (caseInsensitive) {
return super.termQueryCaseInsensitive(value, context);
}
return super.termQuery(value, context); return super.termQuery(value, context);
} }
List<Automaton> automata = new ArrayList<>(); List<Automaton> automata = new ArrayList<>();
if (caseInsensitive) {
automata.add(AutomatonQueries.toCaseInsensitiveString(value, Integer.MAX_VALUE));
} else {
automata.add(Automata.makeString(value)); automata.add(Automata.makeString(value));
}
for (int i = value.length(); i < minChars; i++) { for (int i = value.length(); i < minChars; i++) {
automata.add(Automata.makeAnyChar()); automata.add(Automata.makeAnyChar());
} }
@ -636,11 +645,11 @@ public class TextFieldMapper extends FieldMapper {
} }
@Override @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) { 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 if (method == null || method == MultiTermQuery.CONSTANT_SCORE_REWRITE
|| method == MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE) { || method == MultiTermQuery.CONSTANT_SCORE_BOOLEAN_REWRITE) {
return new ConstantScoreQuery(tq); 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.IndexReader;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermStates; import org.apache.lucene.index.TermStates;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery; 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.search.WildcardQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData; import org.elasticsearch.index.fielddata.plain.ConstantIndexFieldData;
@ -156,7 +158,7 @@ public class TypeFieldMapper extends MetadataFieldMapper {
} }
@Override @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); Query termQuery = termQuery(value, context);
if (termQuery instanceof MatchNoDocsQuery || termQuery instanceof MatchAllDocsQuery) { if (termQuery instanceof MatchNoDocsQuery || termQuery instanceof MatchAllDocsQuery) {
return termQuery; return termQuery;
@ -168,6 +170,12 @@ public class TypeFieldMapper extends MetadataFieldMapper {
} }
Term term = MappedFieldType.extractTerm(termQuery); Term term = MappedFieldType.extractTerm(termQuery);
if (caseInsensitive) {
AutomatonQuery query = AutomatonQueries.caseInsensitiveWildcardQuery(term);
QueryParsers.setRewriteMethod(query, method);
return query;
}
WildcardQuery query = new WildcardQuery(term); WildcardQuery query = new WildcardQuery(term);
QueryParsers.setRewriteMethod(query, method); QueryParsers.setRewriteMethod(query, method);
return query; return query;

View File

@ -152,18 +152,23 @@ public abstract class BaseTermQueryBuilder<QB extends BaseTermQueryBuilder<QB>>
builder.startObject(getName()); builder.startObject(getName());
builder.startObject(fieldName); builder.startObject(fieldName);
builder.field(VALUE_FIELD.getPreferredName(), maybeConvertToString(this.value)); builder.field(VALUE_FIELD.getPreferredName(), maybeConvertToString(this.value));
addExtraXContent(builder, params);
printBoostAndQueryName(builder); printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
} }
protected void addExtraXContent(XContentBuilder builder, Params params) throws IOException {
// Do nothing but allows subclasses to override.
}
@Override @Override
protected final int doHashCode() { protected int doHashCode() {
return Objects.hash(fieldName, value); return Objects.hash(fieldName, value);
} }
@Override @Override
protected final boolean doEquals(QB other) { protected boolean doEquals(QB other) {
return Objects.equals(fieldName, other.fieldName) && return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value); 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.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -51,6 +52,11 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
private final String value; 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; private String rewrite;
/** /**
@ -78,6 +84,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
fieldName = in.readString(); fieldName = in.readString();
value = in.readString(); value = in.readString();
rewrite = in.readOptionalString(); rewrite = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
caseInsensitive = in.readBoolean();
}
} }
@Override @Override
@ -85,6 +94,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
out.writeString(fieldName); out.writeString(fieldName);
out.writeString(value); out.writeString(value);
out.writeOptionalString(rewrite); out.writeOptionalString(rewrite);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(caseInsensitive);
}
} }
@Override @Override
@ -96,6 +108,18 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
return this.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) { public PrefixQueryBuilder rewrite(String rewrite) {
this.rewrite = rewrite; this.rewrite = rewrite;
return this; return this;
@ -113,6 +137,9 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
if (rewrite != null) { if (rewrite != null) {
builder.field(REWRITE_FIELD.getPreferredName(), rewrite); builder.field(REWRITE_FIELD.getPreferredName(), rewrite);
} }
if (caseInsensitive != DEFAULT_CASE_INSENSITIVITY) {
builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive);
}
printBoostAndQueryName(builder); printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
@ -125,6 +152,7 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
String queryName = null; String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -145,6 +173,12 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
boost = parser.floatValue(); boost = parser.floatValue();
} else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { } else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
rewrite = parser.textOrNull(); 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 { } else {
throw new ParsingException(parser.getTokenLocation(), throw new ParsingException(parser.getTokenLocation(),
"[prefix] query does not support [" + currentFieldName + "]"); "[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) .rewrite(rewrite)
.boost(boost) .boost(boost)
.queryName(queryName); .queryName(queryName);
if (caseInsensitive) {
result.caseInsensitive(caseInsensitive);
}
return result;
} }
@Override @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 // 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 // fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread. // 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) { if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder(); return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) { } else if (query instanceof MatchNoDocsQuery) {
@ -202,18 +240,19 @@ public class PrefixQueryBuilder extends AbstractQueryBuilder<PrefixQueryBuilder>
if (fieldType == null) { if (fieldType == null) {
throw new IllegalStateException("Rewrite first"); throw new IllegalStateException("Rewrite first");
} }
return fieldType.prefixQuery(value, method, context); return fieldType.prefixQuery(value, method, caseInsensitive, context);
} }
@Override @Override
protected final int doHashCode() { protected final int doHashCode() {
return Objects.hash(fieldName, value, rewrite); return Objects.hash(fieldName, value, rewrite, caseInsensitive);
} }
@Override @Override
protected boolean doEquals(PrefixQueryBuilder other) { protected boolean doEquals(PrefixQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) && return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) && 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.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput; 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.XContentParser;
import org.elasticsearch.common.xcontent.ToXContent.Params;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.ConstantFieldType; import org.elasticsearch.index.mapper.ConstantFieldType;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* A Query that matches documents containing a term. * A Query that matches documents containing a term.
*/ */
public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> { public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
public static final String NAME = "term"; 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 TERM_FIELD = new ParseField("term");
private static final ParseField VALUE_FIELD = new ParseField("value"); private static final ParseField VALUE_FIELD = new ParseField("value");
@ -75,11 +86,34 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
super(fieldName, 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. * Read from a stream.
*/ */
public TermQueryBuilder(StreamInput in) throws IOException { public TermQueryBuilder(StreamInput in) throws IOException {
super(in); 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 { public static TermQueryBuilder fromXContent(XContentParser parser) throws IOException {
@ -87,6 +121,7 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
String fieldName = null; String fieldName = null;
Object value = null; Object value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -107,6 +142,12 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
queryName = parser.text(); queryName = parser.text();
} else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { } else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
boost = parser.floatValue(); 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 { } else {
throw new ParsingException(parser.getTokenLocation(), throw new ParsingException(parser.getTokenLocation(),
"[term] query does not support [" + currentFieldName + "]"); "[term] query does not support [" + currentFieldName + "]");
@ -127,9 +168,19 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
if (queryName != null) { if (queryName != null) {
termQuery.queryName(queryName); termQuery.queryName(queryName);
} }
if (caseInsensitive) {
termQuery.caseInsensitive(caseInsensitive);
}
return termQuery; 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 @Override
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException { protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
QueryShardContext context = queryRewriteContext.convertToShardContext(); 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 // 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 // fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread. // 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) { if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder(); return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) { } else if (query instanceof MatchNoDocsQuery) {
@ -160,11 +217,27 @@ public class TermQueryBuilder extends BaseTermQueryBuilder<TermQueryBuilder> {
if (mapper == null) { if (mapper == null) {
throw new IllegalStateException("Rewrite first"); throw new IllegalStateException("Rewrite first");
} }
return mapper.termQuery(this.value, context); if (caseInsensitive) {
return mapper.termQueryCaseInsensitive(value, context);
}
return mapper.termQuery(value, context);
} }
@Override @Override
public String getWriteableName() { public String getWriteableName() {
return NAME; 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.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -59,6 +60,10 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
private String rewrite; 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 * Implements the wildcard search query. Supported wildcards are {@code *}, which
* matches any character sequence (including the empty one), and {@code ?}, * matches any character sequence (including the empty one), and {@code ?},
@ -89,6 +94,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
fieldName = in.readString(); fieldName = in.readString();
value = in.readString(); value = in.readString();
rewrite = in.readOptionalString(); rewrite = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_10_0)) {
caseInsensitive = in.readBoolean();
}
} }
@Override @Override
@ -96,6 +104,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
out.writeString(fieldName); out.writeString(fieldName);
out.writeString(value); out.writeString(value);
out.writeOptionalString(rewrite); out.writeOptionalString(rewrite);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(caseInsensitive);
}
} }
@Override @Override
@ -116,6 +127,18 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
return this.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 @Override
public String getWriteableName() { public String getWriteableName() {
return NAME; return NAME;
@ -129,6 +152,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
if (rewrite != null) { if (rewrite != null) {
builder.field(REWRITE_FIELD.getPreferredName(), rewrite); builder.field(REWRITE_FIELD.getPreferredName(), rewrite);
} }
if (caseInsensitive != DEFAULT_CASE_INSENSITIVITY) {
builder.field(CASE_INSENSITIVE_FIELD.getPreferredName(), caseInsensitive);
}
printBoostAndQueryName(builder); printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
@ -139,6 +165,7 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
String rewrite = null; String rewrite = null;
String value = null; String value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean caseInsensitive = DEFAULT_CASE_INSENSITIVITY;
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
@ -160,6 +187,12 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
boost = parser.floatValue(); boost = parser.floatValue();
} else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { } else if (REWRITE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
rewrite = parser.textOrNull(); 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())) { } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
@ -175,10 +208,14 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
} }
} }
return new WildcardQueryBuilder(fieldName, value) WildcardQueryBuilder result = new WildcardQueryBuilder(fieldName, value)
.rewrite(rewrite) .rewrite(rewrite)
.boost(boost) .boost(boost)
.queryName(queryName); .queryName(queryName);
if (caseInsensitive) {
result.caseInsensitive(caseInsensitive);
}
return result;
} }
@Override @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 // 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 // fields we also have the guarantee that it doesn't perform I/O, which is important
// since rewrites might happen on a network thread. // 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) { if (query instanceof MatchAllDocsQuery) {
return new MatchAllQueryBuilder(); return new MatchAllQueryBuilder();
} else if (query instanceof MatchNoDocsQuery) { } else if (query instanceof MatchNoDocsQuery) {
@ -216,18 +253,19 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod( MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(
rewrite, null, LoggingDeprecationHandler.INSTANCE); rewrite, null, LoggingDeprecationHandler.INSTANCE);
return fieldType.wildcardQuery(value, method, context); return fieldType.wildcardQuery(value, method, caseInsensitive, context);
} }
@Override @Override
protected int doHashCode() { protected int doHashCode() {
return Objects.hash(fieldName, value, rewrite); return Objects.hash(fieldName, value, rewrite, caseInsensitive);
} }
@Override @Override
protected boolean doEquals(WildcardQueryBuilder other) { protected boolean doEquals(WildcardQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) && return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) && 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 org.elasticsearch.test.ESTestCase;
import java.util.Locale;
import java.util.Random; import java.util.Random;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -54,13 +55,17 @@ public class RegexTests extends ESTestCase {
public void testDoubleWildcardMatch() { public void testDoubleWildcardMatch() {
assertTrue(Regex.simpleMatch("ddd", "ddd")); 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("d*d*d", "dadd"));
assertTrue(Regex.simpleMatch("**ddd", "dddd")); assertTrue(Regex.simpleMatch("**ddd", "dddd"));
assertTrue(Regex.simpleMatch("**ddD", "dddd", true));
assertFalse(Regex.simpleMatch("**ddd", "fff")); assertFalse(Regex.simpleMatch("**ddd", "fff"));
assertTrue(Regex.simpleMatch("fff*ddd", "fffabcddd")); assertTrue(Regex.simpleMatch("fff*ddd", "fffabcddd"));
assertTrue(Regex.simpleMatch("fff**ddd", "fffabcddd")); assertTrue(Regex.simpleMatch("fff**ddd", "fffabcddd"));
assertFalse(Regex.simpleMatch("fff**ddd", "fffabcdd")); assertFalse(Regex.simpleMatch("fff**ddd", "fffabcdd"));
assertTrue(Regex.simpleMatch("fff*******ddd", "fffabcddd")); assertTrue(Regex.simpleMatch("fff*******ddd", "fffabcddd"));
assertTrue(Regex.simpleMatch("fff*******ddd", "FffAbcdDd", true));
assertFalse(Regex.simpleMatch("fff******ddd", "fffabcdd")); assertFalse(Regex.simpleMatch("fff******ddd", "fffabcdd"));
} }
@ -76,6 +81,8 @@ public class RegexTests extends ESTestCase {
pattern = pattern.substring(0, shrinkStart) + "*" + pattern.substring(shrinkEnd); pattern = pattern.substring(0, shrinkStart) + "*" + pattern.substring(shrinkEnd);
} }
assertTrue("[" + pattern + "] should match [" + matchingString + "]", Regex.simpleMatch(pattern, matchingString)); 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) // construct a pattern that does not match this string by inserting a non-matching character (a digit)
final int insertPos = between(0, pattern.length()); final int insertPos = between(0, pattern.length());

View File

@ -46,7 +46,9 @@ public class IndexFieldTypeTests extends ESTestCase {
MappedFieldType ft = IndexFieldMapper.IndexFieldType.INSTANCE; MappedFieldType ft = IndexFieldMapper.IndexFieldType.INSTANCE;
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("ind*x", null, createContext())); 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, createContext()));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("Other_ind*x", null, true, createContext()));
} }
public void testRegexpQuery() { 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.DateFieldMapper.DateFieldType;
import org.elasticsearch.index.mapper.RangeFieldMapper.RangeFieldType; import org.elasticsearch.index.mapper.RangeFieldMapper.RangeFieldType;
import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.test.IndexSettingsModule; import org.elasticsearch.test.IndexSettingsModule;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
@ -480,4 +481,14 @@ public class RangeFieldTypeTests extends FieldTypeTestCase {
assertEquals(getExpectedRangeQuery(relation, value, value, includeLower, includeUpper), assertEquals(getExpectedRangeQuery(relation, value, value, includeLower, includeUpper),
ft.termQuery(value, context)); 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.apache.lucene.util.automaton.Operations;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.TextFieldMapper.TextFieldType; import org.elasticsearch.index.mapper.TextFieldMapper.TextFieldType;
@ -63,6 +64,7 @@ public class TextFieldTypeTests extends FieldTypeTestCase {
public void testTermQuery() { public void testTermQuery() {
MappedFieldType ft = createFieldType(); MappedFieldType ft = createFieldType();
assertEquals(new TermQuery(new Term("field", "foo")), ft.termQuery("foo", null)); 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()); MappedFieldType unsearchable = new TextFieldType("field", false, Collections.emptyMap());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@ -132,18 +134,22 @@ public class TextFieldTypeTests extends FieldTypeTestCase {
TextFieldType ft = createFieldType(); TextFieldType ft = createFieldType();
ft.setPrefixFieldType(new TextFieldMapper.PrefixFieldType(ft, "field._index_prefix", 2, 10, true)); 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); 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); 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, 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. " + 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()); "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 Automaton automaton
= Operations.concatenate(Arrays.asList(Automata.makeChar('g'), Automata.makeAnyChar())); = Operations.concatenate(Arrays.asList(Automata.makeChar('g'), Automata.makeAnyChar()));

View File

@ -18,12 +18,14 @@
*/ */
package org.elasticsearch.index.mapper; package org.elasticsearch.index.mapper;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexSettings;
@ -61,6 +63,9 @@ public class TypeFieldTypeTests extends ESTestCase {
query = ft.termQuery("my_type", context); query = ft.termQuery("my_type", context);
assertEquals(new MatchAllDocsQuery(), query); 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); Mockito.when(mapperService.hasNested()).thenReturn(true);
query = ft.termQuery("my_type", context); query = ft.termQuery("my_type", context);
assertEquals(Queries.newNonNestedFilter(context.indexVersionCreated()), query); assertEquals(Queries.newNonNestedFilter(context.indexVersionCreated()), query);
@ -70,5 +75,9 @@ public class TypeFieldTypeTests extends ESTestCase {
Mockito.when(mapperService.documentMapper()).thenReturn(mapper); Mockito.when(mapperService.documentMapper()).thenReturn(mapper);
query = ft.termQuery("my_type", context); query = ft.termQuery("my_type", context);
assertEquals(new MatchNoDocsQuery(), query); 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 { public void testFromJson() throws IOException {
String json = String json =
"{ \"prefix\" : { \"user\" : { \"value\" : \"ki\", \"boost\" : 2.0 } }}"; "{ \"prefix\" : { \"user\" : { \"value\" : \"ki\", \"boost\" : 2.0"
+ "} }}";
PrefixQueryBuilder parsed = (PrefixQueryBuilder) parseQuery(json); PrefixQueryBuilder parsed = (PrefixQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed); checkGeneratedJson(json, parsed);

View File

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

View File

@ -103,7 +103,8 @@ public class WildcardQueryBuilderTests extends AbstractQueryTestCase<WildcardQue
} }
public void testFromJson() throws IOException { 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); WildcardQueryBuilder parsed = (WildcardQueryBuilder) parseQuery(json);
checkGeneratedJson(json, parsed); checkGeneratedJson(json, parsed);
assertEquals(json, "ki*y", parsed.value()); assertEquals(json, "ki*y", parsed.value());

View File

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

View File

@ -21,9 +21,12 @@ public class ConstantKeywordFieldTypeTests extends FieldTypeTestCase {
public void testTermQuery() { public void testTermQuery() {
ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo"); ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.termQuery("foo", null)); 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.termQuery("bar", null));
assertEquals(new MatchNoDocsQuery(), ft.termQueryCaseInsensitive("bAr", null));
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar"); ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar");
assertEquals(new MatchNoDocsQuery(), bar.termQuery("foo", null)); assertEquals(new MatchNoDocsQuery(), bar.termQuery("foo", null));
assertEquals(new MatchNoDocsQuery(), bar.termQueryCaseInsensitive("fOo", null));
} }
public void testTermsQuery() { public void testTermsQuery() {
@ -39,18 +42,24 @@ public class ConstantKeywordFieldTypeTests extends FieldTypeTestCase {
public void testWildcardQuery() { public void testWildcardQuery() {
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar"); 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"); ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("f*o", null, null)); assertEquals(new MatchAllDocsQuery(), ft.wildcardQuery("f*o", null, false, null));
assertEquals(new MatchNoDocsQuery(), ft.wildcardQuery("b*r", null, 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() { public void testPrefixQuery() {
ConstantKeywordFieldType bar = new ConstantKeywordFieldType("f", "bar"); 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"); ConstantKeywordFieldType ft = new ConstantKeywordFieldType("f", "foo");
assertEquals(new MatchAllDocsQuery(), ft.prefixQuery("fo", null, null)); assertEquals(new MatchAllDocsQuery(), ft.prefixQuery("fo", null, false, null));
assertEquals(new MatchNoDocsQuery(), ft.prefixQuery("ba", null, 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() { 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.LeafReaderContext;
import org.apache.lucene.index.OrdinalMap; import org.apache.lucene.index.OrdinalMap;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.DocValuesFieldExistsQuery; import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery; 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.search.TermQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -292,11 +294,23 @@ public final class FlatObjectFieldMapper extends DynamicKeyFieldMapper {
@Override @Override
public Query wildcardQuery(String value, public Query wildcardQuery(String value,
MultiTermQuery.RewriteMethod method, MultiTermQuery.RewriteMethod method,
boolean caseInsensitive,
QueryShardContext context) { QueryShardContext context) {
throw new UnsupportedOperationException("[wildcard] queries are not currently supported on keyed " + throw new UnsupportedOperationException("[wildcard] queries are not currently supported on keyed " +
"[" + CONTENT_TYPE + "] fields."); "[" + 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 @Override
public BytesRef indexedValueForSearch(Object value) { public BytesRef indexedValueForSearch(Object value) {
if (value == null) { 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.search.TermRangeQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.FieldTypeTestCase; import org.elasticsearch.index.mapper.FieldTypeTestCase;
import org.elasticsearch.xpack.flattened.mapper.FlatObjectFieldMapper.KeyedFlatObjectFieldType; 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")); Query expected = new TermQuery(new Term("field", "key\0value"));
assertEquals(expected, ft.termQuery("value", null)); 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", KeyedFlatObjectFieldType unsearchable = new KeyedFlatObjectFieldType("field", false, true, "key",
false, Collections.emptyMap()); false, Collections.emptyMap());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
@ -81,10 +87,14 @@ public class KeyedFlatObjectFieldTypeTests extends FieldTypeTestCase {
KeyedFlatObjectFieldType ft = createFieldType(); KeyedFlatObjectFieldType ft = createFieldType();
Query expected = new PrefixQuery(new Term("field", "key\0val")); 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, 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. " + 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()); "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(); KeyedFlatObjectFieldType ft = createFieldType();
UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, 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()); 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.search.WildcardQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper; import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.FieldTypeTestCase; import org.elasticsearch.index.mapper.FieldTypeTestCase;
@ -43,6 +44,10 @@ public class RootFlatObjectFieldTypeTests extends FieldTypeTestCase {
Query expected = new TermQuery(new Term("field", "value")); Query expected = new TermQuery(new Term("field", "value"));
assertEquals(expected, ft.termQuery("value", null)); 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, RootFlatObjectFieldType unsearchable = new RootFlatObjectFieldType("field", false, true,
Collections.emptyMap(), false); Collections.emptyMap(), false);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, IllegalArgumentException e = expectThrows(IllegalArgumentException.class,

View File

@ -133,12 +133,12 @@ abstract class AbstractScriptMappedFieldType<LeafFactory> extends MappedFieldTyp
} }
@Override @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")); throw new IllegalArgumentException(unsupported("prefix", "keyword, text and wildcard"));
} }
@Override @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")); 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.search.Query;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.index.mapper.BooleanFieldMapper; import org.elasticsearch.index.mapper.BooleanFieldMapper;
@ -138,10 +139,16 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
return termsQuery(trueAllowed, falseAllowed, context); 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 @Override
public Query termQuery(Object value, QueryShardContext context) { public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(context); checkAllowExpensiveQueries(context);
return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), toBoolean(value)); return new BooleanScriptFieldTermQuery(script, leafFactory(context), name(), toBoolean(value, false));
} }
@Override @Override
@ -152,7 +159,7 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
boolean trueAllowed = false; boolean trueAllowed = false;
boolean falseAllowed = false; boolean falseAllowed = false;
for (Object value : values) { for (Object value : values) {
if (toBoolean(value)) { if (toBoolean(value, false)) {
trueAllowed = true; trueAllowed = true;
} else { } else {
falseAllowed = true; falseAllowed = true;
@ -177,10 +184,14 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
return new MatchNoDocsQuery("neither true nor false allowed"); 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)}. * 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) { if (value == null) {
return false; return false;
} }
@ -193,6 +204,9 @@ public class BooleanScriptMappedFieldType extends AbstractScriptMappedFieldType<
} else { } else {
sValue = value.toString(); sValue = value.toString();
} }
if (caseInsensitive) {
sValue = Strings.toLowercaseAscii(sValue);
}
return Booleans.parseBoolean(sValue); return Booleans.parseBoolean(sValue);
} }
} }

View File

@ -88,9 +88,14 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
} }
@Override @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); checkAllowExpensiveQueries(context);
return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value); return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value, caseInsensitive);
} }
@Override @Override
@ -128,13 +133,39 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
if (matchFlags != 0) { if (matchFlags != 0) {
throw new IllegalArgumentException("Match flags not yet implemented [" + matchFlags + "]"); 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 @Override
public Query termQuery(Object value, QueryShardContext context) { public Query termQuery(Object value, QueryShardContext context) {
checkAllowExpensiveQueries(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 @Override
@ -145,8 +176,8 @@ public final class KeywordScriptMappedFieldType extends AbstractScriptMappedFiel
} }
@Override @Override
public Query wildcardQuery(String value, RewriteMethod method, QueryShardContext context) { public Query wildcardQuery(String value, RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
checkAllowExpensiveQueries(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.PrefixQuery;
import org.apache.lucene.search.QueryVisitor; import org.apache.lucene.search.QueryVisitor;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.ByteRunAutomaton; import org.apache.lucene.util.automaton.ByteRunAutomaton;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript; import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript;
@ -18,26 +20,63 @@ import java.util.Objects;
public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery { public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery {
private final String prefix; 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); super(script, leafFactory, fieldName);
this.prefix = Objects.requireNonNull(prefix); this.prefix = Objects.requireNonNull(prefix);
this.caseInsensitive = caseInsensitive;
} }
@Override @Override
protected boolean matches(List<String> values) { protected boolean matches(List<String> values) {
for (String value : values) { for (String value : values) {
if (value != null && value.startsWith(prefix)) { if (startsWith(value, prefix, caseInsensitive)) {
return true; return true;
} }
} }
return false; 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 @Override
public void visit(QueryVisitor visitor) { public void visit(QueryVisitor visitor) {
if (visitor.acceptField(fieldName())) { 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 @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), prefix); return Objects.hash(super.hashCode(), prefix, caseInsensitive);
} }
@Override @Override
@ -60,10 +99,14 @@ public class StringScriptFieldPrefixQuery extends AbstractStringScriptFieldQuery
return false; return false;
} }
StringScriptFieldPrefixQuery other = (StringScriptFieldPrefixQuery) obj; StringScriptFieldPrefixQuery other = (StringScriptFieldPrefixQuery) obj;
return prefix.equals(other.prefix); return prefix.equals(other.prefix) && caseInsensitive == other.caseInsensitive;
} }
String prefix() { String prefix() {
return prefix; return prefix;
} }
boolean caseInsensitive() {
return caseInsensitive;
}
} }

View File

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

View File

@ -16,16 +16,28 @@ import java.util.Objects;
public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery { public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
private final String term; 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); super(script, leafFactory, fieldName);
this.term = Objects.requireNonNull(term); this.term = Objects.requireNonNull(term);
this.caseInsensitive = caseInsensitive;
} }
@Override @Override
protected boolean matches(List<String> values) { protected boolean matches(List<String> values) {
for (String value : values) { for (String value : values) {
if (term.equals(value)) { if (caseInsensitive) {
if (term.equalsIgnoreCase(value)) {
return true;
}
} else if (term.equals(value)) {
return true; return true;
} }
} }
@ -47,7 +59,7 @@ public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), term); return Objects.hash(super.hashCode(), term, caseInsensitive);
} }
@Override @Override
@ -56,10 +68,14 @@ public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery {
return false; return false;
} }
StringScriptFieldTermQuery other = (StringScriptFieldTermQuery) obj; StringScriptFieldTermQuery other = (StringScriptFieldTermQuery) obj;
return term.equals(other.term); return term.equals(other.term) && caseInsensitive == other.caseInsensitive;
} }
String term() { String term() {
return 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.index.Term;
import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.ByteRunAutomaton; import org.apache.lucene.util.automaton.ByteRunAutomaton;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript; import org.elasticsearch.xpack.runtimefields.mapper.StringFieldScript;
@ -16,15 +18,30 @@ import java.util.Objects;
public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAutomatonQuery { public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAutomatonQuery {
private final String pattern; 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( super(
script, script,
leafFactory, leafFactory,
fieldName, 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.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 @Override
@ -37,7 +54,7 @@ public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAut
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), pattern); return Objects.hash(super.hashCode(), pattern, caseInsensitive);
} }
@Override @Override
@ -46,10 +63,14 @@ public class StringScriptFieldWildcardQuery extends AbstractStringScriptFieldAut
return false; return false;
} }
StringScriptFieldWildcardQuery other = (StringScriptFieldWildcardQuery) obj; StringScriptFieldWildcardQuery other = (StringScriptFieldWildcardQuery) obj;
return pattern.equals(other.pattern); return pattern.equals(other.pattern) && caseInsensitive == other.caseInsensitive;
} }
String pattern() { String pattern() {
return 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) { 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 @Override

View File

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

View File

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

View File

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

View File

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

View File

@ -138,8 +138,8 @@ public class VersionStringFieldMapper extends ParametrizedFieldMapper {
} }
@Override @Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) { public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, boolean caseInsensitive, QueryShardContext context) {
return wildcardQuery(value + "*", method, context); return wildcardQuery(value + "*", method, caseInsensitive, context);
} }
/** /**
@ -237,7 +237,7 @@ public class VersionStringFieldMapper extends ParametrizedFieldMapper {
} }
@Override @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) { if (context.allowExpensiveQueries() == false) {
throw new ElasticsearchException( throw new ElasticsearchException(
"[wildcard] queries cannot be executed when '" + ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false." "[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.geo.ShapeRelation;
import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.AutomatonQueries;
import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -218,7 +219,7 @@ public class WildcardFieldMapper extends FieldMapper {
} }
@Override @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)); String ngramIndexPattern = addLineEndChars(toLowerCase(wildcardPattern));
@ -276,7 +277,11 @@ public class WildcardFieldMapper extends FieldMapper {
clauseCount++; clauseCount++;
} }
Supplier<Automaton> deferredAutomatonSupplier = () -> { Supplier<Automaton> deferredAutomatonSupplier = () -> {
if(caseInsensitive) {
return AutomatonQueries.toCaseInsensitiveWildcardAutomaton(new Term(name(), wildcardPattern), Integer.MAX_VALUE);
} else {
return WildcardQuery.toAutomaton(new Term(name(), wildcardPattern)); return WildcardQuery.toAutomaton(new Term(name(), wildcardPattern));
}
}; };
AutomatonQueryOnBinaryDv verifyingQuery = new AutomatonQueryOnBinaryDv(name(), wildcardPattern, deferredAutomatonSupplier); AutomatonQueryOnBinaryDv verifyingQuery = new AutomatonQueryOnBinaryDv(name(), wildcardPattern, deferredAutomatonSupplier);
if (clauseCount > 0) { if (clauseCount > 0) {
@ -845,7 +850,7 @@ public class WildcardFieldMapper extends FieldMapper {
@Override @Override
public Query termQuery(Object value, QueryShardContext context) { public Query termQuery(Object value, QueryShardContext context) {
String searchTerm = BytesRefs.toString(value); 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) { private String escapeWildcardSyntax(String term) {
@ -864,8 +869,14 @@ public class WildcardFieldMapper extends FieldMapper {
} }
@Override @Override
public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) { public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
return wildcardQuery(escapeWildcardSyntax(value) + "*", method, 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, boolean caseInsensitive, QueryShardContext context) {
return wildcardQuery(escapeWildcardSyntax(value) + "*", method, caseInsensitive, context);
} }
@Override @Override

View File

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