summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorAreek Zillur <areek.zillur@elasticsearch.com>2016-02-10 16:21:24 -0500
committerAreek Zillur <areek.zillur@elasticsearch.com>2016-02-10 16:21:55 -0500
commit2038429f63cd31721c0522d2d49eab66303c68fb (patch)
tree1deac75ae674b0d33bfc45008347d6926f2b7c03 /core
parentbbeb09eae7ac3c5d9837bb26eacfac6bba468929 (diff)
initial refactoring of completion suggester
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java10
-rw-r--r--core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java10
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java27
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java310
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java34
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/FuzzyOptions.java277
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/RegexOptions.java153
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java8
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java132
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java7
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java9
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java161
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java34
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java29
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java35
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java2
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/CategoryQueryContextTests.java95
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java135
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/FuzzyOptionsTests.java131
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/GeoQueryContextTests.java136
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/QueryContextTestCase.java60
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/RegexOptionsTests.java71
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java4
-rw-r--r--core/src/test/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorTests.java6
24 files changed, 1468 insertions, 408 deletions
diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
index 8c2dc444d2..10f7a5dba8 100644
--- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
+++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
@@ -39,6 +39,7 @@ import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.search.rescore.RescoreBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
+import org.elasticsearch.search.suggest.completion.context.QueryContext;
import org.elasticsearch.search.suggest.phrase.SmoothingModel;
import org.elasticsearch.tasks.Task;
import org.joda.time.DateTime;
@@ -694,6 +695,13 @@ public abstract class StreamInput extends InputStream {
}
/**
+ * Reads a completion {@link QueryContext} from the current stream
+ */
+ public QueryContext readCompletionSuggestionQueryContext() throws IOException {
+ return readNamedWriteable(QueryContext.class);
+ }
+
+ /**
* Reads a {@link org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder} from the current stream
*/
public ScoreFunctionBuilder<?> readScoreFunction() throws IOException {
@@ -706,7 +714,7 @@ public abstract class StreamInput extends InputStream {
public SmoothingModel readPhraseSuggestionSmoothingModel() throws IOException {
return readNamedWriteable(SmoothingModel.class);
}
-
+
/**
* Reads a {@link Task.Status} from the current stream.
*/
diff --git a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
index d14d6e77ff..4701948347 100644
--- a/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
+++ b/core/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
@@ -38,6 +38,7 @@ import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.search.rescore.RescoreBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
+import org.elasticsearch.search.suggest.completion.context.QueryContext;
import org.elasticsearch.search.suggest.phrase.SmoothingModel;
import org.elasticsearch.tasks.Task;
import org.joda.time.ReadableInstant;
@@ -678,7 +679,7 @@ public abstract class StreamOutput extends OutputStream {
public void writePhraseSuggestionSmoothingModel(SmoothingModel smoothinModel) throws IOException {
writeNamedWriteable(smoothinModel);
}
-
+
/**
* Writes a {@link Task.Status} to the current stream.
*/
@@ -717,4 +718,11 @@ public abstract class StreamOutput extends OutputStream {
public void writeSuggestion(SuggestionBuilder suggestion) throws IOException {
writeNamedWriteable(suggestion);
}
+
+ /**
+ * Writes a completion {@link QueryContext} to the current stream
+ */
+ public void writeCompletionSuggestionQueryContext(QueryContext queryContext) throws IOException {
+ writeNamedWriteable(queryContext);
+ }
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java
index 9d29525115..04f63042d4 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestParser.java
@@ -34,8 +34,6 @@ import org.elasticsearch.index.query.RegexpFlag;
import org.elasticsearch.search.suggest.SuggestContextParser;
import org.elasticsearch.search.suggest.SuggestUtils.Fields;
import org.elasticsearch.search.suggest.SuggestionSearchContext;
-import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder.FuzzyOptionsBuilder;
-import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder.RegexOptionsBuilder;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.elasticsearch.search.suggest.completion.context.ContextMappings;
@@ -76,14 +74,14 @@ import java.util.Map;
public class CompletionSuggestParser implements SuggestContextParser {
private static ObjectParser<CompletionSuggestionContext, ContextAndSuggest> TLP_PARSER = new ObjectParser<>(CompletionSuggestionBuilder.SUGGESTION_NAME, null);
- private static ObjectParser<CompletionSuggestionBuilder.RegexOptionsBuilder, ContextAndSuggest> REGEXP_PARSER = new ObjectParser<>(RegexOptionsBuilder.REGEX_OPTIONS.getPreferredName(), CompletionSuggestionBuilder.RegexOptionsBuilder::new);
- private static ObjectParser<CompletionSuggestionBuilder.FuzzyOptionsBuilder, ContextAndSuggest> FUZZY_PARSER = new ObjectParser<>(FuzzyOptionsBuilder.FUZZY_OPTIONS.getPreferredName(), CompletionSuggestionBuilder.FuzzyOptionsBuilder::new);
+ private static ObjectParser<RegexOptions.Builder, ContextAndSuggest> REGEXP_PARSER = new ObjectParser<>(RegexOptions.REGEX_OPTIONS.getPreferredName(), RegexOptions.Builder::new);
+ private static ObjectParser<FuzzyOptions.Builder, ContextAndSuggest> FUZZY_PARSER = new ObjectParser<>(FuzzyOptions.FUZZY_OPTIONS.getPreferredName(), FuzzyOptions.Builder::new);
static {
- FUZZY_PARSER.declareInt(CompletionSuggestionBuilder.FuzzyOptionsBuilder::setFuzzyMinLength, FuzzyOptionsBuilder.MIN_LENGTH_FIELD);
- FUZZY_PARSER.declareInt(CompletionSuggestionBuilder.FuzzyOptionsBuilder::setMaxDeterminizedStates, FuzzyOptionsBuilder.MAX_DETERMINIZED_STATES_FIELD);
- FUZZY_PARSER.declareBoolean(CompletionSuggestionBuilder.FuzzyOptionsBuilder::setUnicodeAware, FuzzyOptionsBuilder.UNICODE_AWARE_FIELD);
- FUZZY_PARSER.declareInt(CompletionSuggestionBuilder.FuzzyOptionsBuilder::setFuzzyPrefixLength, FuzzyOptionsBuilder.PREFIX_LENGTH_FIELD);
- FUZZY_PARSER.declareBoolean(CompletionSuggestionBuilder.FuzzyOptionsBuilder::setTranspositions, FuzzyOptionsBuilder.TRANSPOSITION_FIELD);
+ FUZZY_PARSER.declareInt(FuzzyOptions.Builder::setFuzzyMinLength, FuzzyOptions.MIN_LENGTH_FIELD);
+ FUZZY_PARSER.declareInt(FuzzyOptions.Builder::setMaxDeterminizedStates, FuzzyOptions.MAX_DETERMINIZED_STATES_FIELD);
+ FUZZY_PARSER.declareBoolean(FuzzyOptions.Builder::setUnicodeAware, FuzzyOptions.UNICODE_AWARE_FIELD);
+ FUZZY_PARSER.declareInt(FuzzyOptions.Builder::setFuzzyPrefixLength, FuzzyOptions.PREFIX_LENGTH_FIELD);
+ FUZZY_PARSER.declareBoolean(FuzzyOptions.Builder::setTranspositions, FuzzyOptions.TRANSPOSITION_FIELD);
FUZZY_PARSER.declareValue((a, b) -> {
try {
a.setFuzziness(Fuzziness.parse(b).asDistance());
@@ -91,12 +89,12 @@ public class CompletionSuggestParser implements SuggestContextParser {
throw new ElasticsearchException(e);
}
}, Fuzziness.FIELD);
- REGEXP_PARSER.declareInt(CompletionSuggestionBuilder.RegexOptionsBuilder::setMaxDeterminizedStates, RegexOptionsBuilder.MAX_DETERMINIZED_STATES);
- REGEXP_PARSER.declareStringOrNull(CompletionSuggestionBuilder.RegexOptionsBuilder::setFlags, RegexOptionsBuilder.FLAGS_VALUE);
+ REGEXP_PARSER.declareInt(RegexOptions.Builder::setMaxDeterminizedStates, RegexOptions.MAX_DETERMINIZED_STATES);
+ REGEXP_PARSER.declareStringOrNull(RegexOptions.Builder::setFlags, RegexOptions.FLAGS_VALUE);
TLP_PARSER.declareStringArray(CompletionSuggestionContext::setPayloadFields, CompletionSuggestionBuilder.PAYLOAD_FIELD);
- TLP_PARSER.declareObjectOrDefault(CompletionSuggestionContext::setFuzzyOptionsBuilder, FUZZY_PARSER, CompletionSuggestionBuilder.FuzzyOptionsBuilder::new, FuzzyOptionsBuilder.FUZZY_OPTIONS);
- TLP_PARSER.declareObject(CompletionSuggestionContext::setRegexOptionsBuilder, REGEXP_PARSER, RegexOptionsBuilder.REGEX_OPTIONS);
+ TLP_PARSER.declareObjectOrDefault(CompletionSuggestionContext::setFuzzyOptionsBuilder, FUZZY_PARSER, FuzzyOptions.Builder::new, FuzzyOptions.FUZZY_OPTIONS);
+ TLP_PARSER.declareObject(CompletionSuggestionContext::setRegexOptionsBuilder, REGEXP_PARSER, RegexOptions.REGEX_OPTIONS);
TLP_PARSER.declareString(SuggestionSearchContext.SuggestionContext::setField, Fields.FIELD);
TLP_PARSER.declareField((p, v, c) -> {
String analyzerName = p.text();
@@ -172,7 +170,4 @@ public class CompletionSuggestParser implements SuggestContextParser {
throw new IllegalArgumentException("Field [" + suggestion.getField() + "] is not a completion suggest field");
}
}
-
-
-
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
index 0bd37be128..fa8561998e 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java
@@ -18,9 +18,6 @@
*/
package org.elasticsearch.search.suggest.completion;
-import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery;
-import org.apache.lucene.util.automaton.Operations;
-import org.apache.lucene.util.automaton.RegExp;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -29,11 +26,11 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryShardContext;
-import org.elasticsearch.index.query.RegexpFlag;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
import org.elasticsearch.search.suggest.completion.context.GeoQueryContext;
+import org.elasticsearch.search.suggest.completion.context.QueryContext;
import java.io.IOException;
import java.util.ArrayList;
@@ -42,6 +39,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -57,9 +55,9 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
static final ParseField PAYLOAD_FIELD = new ParseField("payload");
static final ParseField CONTEXTS_FIELD = new ParseField("contexts", "context");
- private FuzzyOptionsBuilder fuzzyOptionsBuilder;
- private RegexOptionsBuilder regexOptionsBuilder;
- private final Map<String, List<ToXContent>> queryContexts = new HashMap<>();
+ private FuzzyOptions fuzzyOptions;
+ private RegexOptions regexOptions;
+ private final Map<String, List<QueryContext>> queryContexts = new HashMap<>();
private final Set<String> payloadFields = new HashSet<>();
public CompletionSuggestionBuilder(String name) {
@@ -67,198 +65,6 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
}
/**
- * Options for fuzzy queries
- */
- public static class FuzzyOptionsBuilder implements ToXContent {
- static final ParseField FUZZY_OPTIONS = new ParseField("fuzzy");
- static final ParseField TRANSPOSITION_FIELD = new ParseField("transpositions");
- static final ParseField MIN_LENGTH_FIELD = new ParseField("min_length");
- static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length");
- static final ParseField UNICODE_AWARE_FIELD = new ParseField("unicode_aware");
- static final ParseField MAX_DETERMINIZED_STATES_FIELD = new ParseField("max_determinized_states");
-
- private int editDistance = FuzzyCompletionQuery.DEFAULT_MAX_EDITS;
- private boolean transpositions = FuzzyCompletionQuery.DEFAULT_TRANSPOSITIONS;
- private int fuzzyMinLength = FuzzyCompletionQuery.DEFAULT_MIN_FUZZY_LENGTH;
- private int fuzzyPrefixLength = FuzzyCompletionQuery.DEFAULT_NON_FUZZY_PREFIX;
- private boolean unicodeAware = FuzzyCompletionQuery.DEFAULT_UNICODE_AWARE;
- private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
-
- public FuzzyOptionsBuilder() {
- }
-
- /**
- * Sets the level of fuzziness used to create suggestions using a {@link Fuzziness} instance.
- * The default value is {@link Fuzziness#ONE} which allows for an "edit distance" of one.
- */
- public FuzzyOptionsBuilder setFuzziness(int editDistance) {
- this.editDistance = editDistance;
- return this;
- }
-
- /**
- * Sets the level of fuzziness used to create suggestions using a {@link Fuzziness} instance.
- * The default value is {@link Fuzziness#ONE} which allows for an "edit distance" of one.
- */
- public FuzzyOptionsBuilder setFuzziness(Fuzziness fuzziness) {
- this.editDistance = fuzziness.asDistance();
- return this;
- }
-
- /**
- * Sets if transpositions (swapping one character for another) counts as one character
- * change or two.
- * Defaults to true, meaning it uses the fuzzier option of counting transpositions as
- * a single change.
- */
- public FuzzyOptionsBuilder setTranspositions(boolean transpositions) {
- this.transpositions = transpositions;
- return this;
- }
-
- /**
- * Sets the minimum length of input string before fuzzy suggestions are returned, defaulting
- * to 3.
- */
- public FuzzyOptionsBuilder setFuzzyMinLength(int fuzzyMinLength) {
- this.fuzzyMinLength = fuzzyMinLength;
- return this;
- }
-
- /**
- * Sets the minimum length of the input, which is not checked for fuzzy alternatives, defaults to 1
- */
- public FuzzyOptionsBuilder setFuzzyPrefixLength(int fuzzyPrefixLength) {
- this.fuzzyPrefixLength = fuzzyPrefixLength;
- return this;
- }
-
- /**
- * Sets the maximum automaton states allowed for the fuzzy expansion
- */
- public FuzzyOptionsBuilder setMaxDeterminizedStates(int maxDeterminizedStates) {
- this.maxDeterminizedStates = maxDeterminizedStates;
- return this;
- }
-
- /**
- * Set to true if all measurements (like edit distance, transpositions and lengths) are in unicode
- * code points (actual letters) instead of bytes. Default is false.
- */
- public FuzzyOptionsBuilder setUnicodeAware(boolean unicodeAware) {
- this.unicodeAware = unicodeAware;
- return this;
- }
-
- /**
- * Returns the maximum number of edits
- */
- int getEditDistance() {
- return editDistance;
- }
-
- /**
- * Returns if transpositions option is set
- *
- * if transpositions is set, then swapping one character for another counts as one edit instead of two.
- */
- boolean isTranspositions() {
- return transpositions;
- }
-
-
- /**
- * Returns the length of input prefix after which edits are applied
- */
- int getFuzzyMinLength() {
- return fuzzyMinLength;
- }
-
- /**
- * Returns the minimum length of the input prefix required to apply any edits
- */
- int getFuzzyPrefixLength() {
- return fuzzyPrefixLength;
- }
-
- /**
- * Returns if all measurements (like edit distance, transpositions and lengths) are in unicode code
- * points (actual letters) instead of bytes.
- */
- boolean isUnicodeAware() {
- return unicodeAware;
- }
-
- /**
- * Returns the maximum automaton states allowed for fuzzy expansion
- */
- int getMaxDeterminizedStates() {
- return maxDeterminizedStates;
- }
-
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- builder.startObject(FUZZY_OPTIONS.getPreferredName());
- builder.field(Fuzziness.FIELD.getPreferredName(), editDistance);
- builder.field(TRANSPOSITION_FIELD.getPreferredName(), transpositions);
- builder.field(MIN_LENGTH_FIELD.getPreferredName(), fuzzyMinLength);
- builder.field(PREFIX_LENGTH_FIELD.getPreferredName(), fuzzyPrefixLength);
- builder.field(UNICODE_AWARE_FIELD.getPreferredName(), unicodeAware);
- builder.field(MAX_DETERMINIZED_STATES_FIELD.getPreferredName(), maxDeterminizedStates);
- builder.endObject();
- return builder;
- }
- }
-
- /**
- * Options for regular expression queries
- */
- public static class RegexOptionsBuilder implements ToXContent {
- static final ParseField REGEX_OPTIONS = new ParseField("regex");
- static final ParseField FLAGS_VALUE = new ParseField("flags", "flags_value");
- static final ParseField MAX_DETERMINIZED_STATES = new ParseField("max_determinized_states");
- private int flagsValue = RegExp.ALL;
- private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
-
- public RegexOptionsBuilder() {
- }
-
- /**
- * Sets the regular expression syntax flags
- * see {@link RegexpFlag}
- */
- public RegexOptionsBuilder setFlags(String flags) {
- this.flagsValue = RegexpFlag.resolveValue(flags);
- return this;
- }
-
- /**
- * Sets the maximum automaton states allowed for the regular expression expansion
- */
- public RegexOptionsBuilder setMaxDeterminizedStates(int maxDeterminizedStates) {
- this.maxDeterminizedStates = maxDeterminizedStates;
- return this;
- }
-
- int getFlagsValue() {
- return flagsValue;
- }
-
- int getMaxDeterminizedStates() {
- return maxDeterminizedStates;
- }
-
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- builder.startObject(REGEX_OPTIONS.getPreferredName());
- builder.field(FLAGS_VALUE.getPreferredName(), flagsValue);
- builder.field(MAX_DETERMINIZED_STATES.getPreferredName(), maxDeterminizedStates);
- builder.endObject();
- return builder;
- }
- }
-
- /**
* Sets the prefix to provide completions for.
* The prefix gets analyzed by the suggest analyzer.
*/
@@ -273,17 +79,17 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
*/
public CompletionSuggestionBuilder prefix(String prefix, Fuzziness fuzziness) {
super.prefix(prefix);
- this.fuzzyOptionsBuilder = new FuzzyOptionsBuilder().setFuzziness(fuzziness);
+ this.fuzzyOptions = new FuzzyOptions.Builder().setFuzziness(fuzziness).build();
return this;
}
/**
* Same as {@link #prefix(String)} with full fuzzy options
- * see {@link FuzzyOptionsBuilder}
+ * see {@link FuzzyOptions.Builder}
*/
- public CompletionSuggestionBuilder prefix(String prefix, FuzzyOptionsBuilder fuzzyOptionsBuilder) {
+ public CompletionSuggestionBuilder prefix(String prefix, FuzzyOptions fuzzyOptions) {
super.prefix(prefix);
- this.fuzzyOptionsBuilder = fuzzyOptionsBuilder;
+ this.fuzzyOptions = fuzzyOptions;
return this;
}
@@ -298,11 +104,11 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
/**
* Same as {@link #regex(String)} with full regular expression options
- * see {@link RegexOptionsBuilder}
+ * see {@link RegexOptions.Builder}
*/
- public CompletionSuggestionBuilder regex(String regex, RegexOptionsBuilder regexOptionsBuilder) {
+ public CompletionSuggestionBuilder regex(String regex, RegexOptions regexOptions) {
this.regex(regex);
- this.regexOptionsBuilder = regexOptionsBuilder;
+ this.regexOptions = regexOptions;
return this;
}
@@ -310,8 +116,8 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
* Sets the fields to be returned as suggestion payload.
* Note: Only doc values enabled fields are supported
*/
- public CompletionSuggestionBuilder payload(String... fields) {
- Collections.addAll(this.payloadFields, fields);
+ public CompletionSuggestionBuilder payload(List<String> fields) {
+ this.payloadFields.addAll(fields);
return this;
}
@@ -333,8 +139,8 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
return contexts(name, queryContexts);
}
- private CompletionSuggestionBuilder contexts(String name, ToXContent... queryContexts) {
- List<ToXContent> contexts = this.queryContexts.get(name);
+ private CompletionSuggestionBuilder contexts(String name, QueryContext... queryContexts) {
+ List<QueryContext> contexts = this.queryContexts.get(name);
if (contexts == null) {
contexts = new ArrayList<>(2);
this.queryContexts.put(name, contexts);
@@ -345,22 +151,22 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
@Override
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
- if (payloadFields != null) {
+ if (payloadFields.isEmpty() == false) {
builder.startArray(PAYLOAD_FIELD.getPreferredName());
for (String field : payloadFields) {
builder.value(field);
}
builder.endArray();
}
- if (fuzzyOptionsBuilder != null) {
- fuzzyOptionsBuilder.toXContent(builder, params);
+ if (fuzzyOptions != null) {
+ fuzzyOptions.toXContent(builder, params);
}
- if (regexOptionsBuilder != null) {
- regexOptionsBuilder.toXContent(builder, params);
+ if (regexOptions != null) {
+ regexOptions.toXContent(builder, params);
}
if (queryContexts.isEmpty() == false) {
builder.startObject(CONTEXTS_FIELD.getPreferredName());
- for (Map.Entry<String, List<ToXContent>> entry : this.queryContexts.entrySet()) {
+ for (Map.Entry<String, List<QueryContext>> entry : this.queryContexts.entrySet()) {
builder.startArray(entry.getKey());
for (ToXContent queryContext : entry.getValue()) {
queryContext.toXContent(builder, params);
@@ -374,8 +180,8 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
@Override
protected CompletionSuggestionBuilder innerFromXContent(QueryParseContext parseContext, String name) throws IOException {
- // NORELEASE
- return new CompletionSuggestionBuilder(name);
+ // NORELEASE implement parsing logic
+ throw new UnsupportedOperationException();
}
@Override
@@ -391,25 +197,77 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
@Override
public void doWriteTo(StreamOutput out) throws IOException {
- // NORELEASE
- throw new UnsupportedOperationException();
+ boolean payloadFieldExists = payloadFields.isEmpty() == false;
+ out.writeBoolean(payloadFieldExists);
+ if (payloadFieldExists) {
+ out.writeVInt(payloadFields.size());
+ for (String payloadField : payloadFields) {
+ out.writeString(payloadField);
+ }
+ }
+ out.writeBoolean(fuzzyOptions != null);
+ if (fuzzyOptions != null) {
+ fuzzyOptions.writeTo(out);
+ }
+ out.writeBoolean(regexOptions != null);
+ if (regexOptions != null) {
+ regexOptions.writeTo(out);
+ }
+ boolean queryContextsExists = queryContexts.isEmpty() == false;
+ out.writeBoolean(queryContextsExists);
+ if (queryContextsExists) {
+ out.writeVInt(queryContexts.size());
+ for (Map.Entry<String, List<QueryContext>> namedQueryContexts : queryContexts.entrySet()) {
+ out.writeString(namedQueryContexts.getKey());
+ List<QueryContext> queryContexts = namedQueryContexts.getValue();
+ out.writeVInt(queryContexts.size());
+ for (QueryContext queryContext : queryContexts) {
+ out.writeCompletionSuggestionQueryContext(queryContext);
+ }
+ }
+ }
}
@Override
public CompletionSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException {
- // NORELEASE
- throw new UnsupportedOperationException();
+ CompletionSuggestionBuilder completionSuggestionBuilder = new CompletionSuggestionBuilder(name);
+ if (in.readBoolean()) {
+ int numPayloadField = in.readVInt();
+ for (int i = 0; i < numPayloadField; i++) {
+ completionSuggestionBuilder.payloadFields.add(in.readString());
+ }
+ }
+ if (in.readBoolean()) {
+ completionSuggestionBuilder.fuzzyOptions = FuzzyOptions.readFuzzyOptions(in);
+ }
+ if (in.readBoolean()) {
+ completionSuggestionBuilder.regexOptions = RegexOptions.readRegexOptions(in);
+ }
+ if (in.readBoolean()) {
+ int numNamedQueryContexts = in.readVInt();
+ for (int i = 0; i < numNamedQueryContexts; i++) {
+ String queryContextName = in.readString();
+ int numQueryContexts = in.readVInt();
+ List<QueryContext> queryContexts = new ArrayList<>(numQueryContexts);
+ for (int j = 0; j < numQueryContexts; j++) {
+ queryContexts.add(in.readCompletionSuggestionQueryContext());
+ }
+ completionSuggestionBuilder.queryContexts.put(queryContextName, queryContexts);
+ }
+ }
+ return completionSuggestionBuilder;
}
@Override
protected boolean doEquals(CompletionSuggestionBuilder other) {
- // NORELEASE
- return false;
+ return Objects.equals(payloadFields, other.payloadFields) &&
+ Objects.equals(fuzzyOptions, other.fuzzyOptions) &&
+ Objects.equals(regexOptions, other.regexOptions) &&
+ Objects.equals(queryContexts, other.queryContexts);
}
@Override
protected int doHashCode() {
- // NORELEASE
- return 0;
+ return Objects.hash(payloadFields, fuzzyOptions, regexOptions, queryContexts);
}
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
index f6d6de88f4..b20b9a5aee 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionContext.java
@@ -42,8 +42,8 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
}
private CompletionFieldMapper.CompletionFieldType fieldType;
- private CompletionSuggestionBuilder.FuzzyOptionsBuilder fuzzyOptionsBuilder;
- private CompletionSuggestionBuilder.RegexOptionsBuilder regexOptionsBuilder;
+ private FuzzyOptions fuzzyOptions;
+ private RegexOptions regexOptions;
private Map<String, List<ContextMapping.QueryContext>> queryContexts = Collections.emptyMap();
private Set<String> payloadFields = Collections.emptySet();
@@ -55,12 +55,12 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
this.fieldType = fieldType;
}
- void setRegexOptionsBuilder(CompletionSuggestionBuilder.RegexOptionsBuilder regexOptionsBuilder) {
- this.regexOptionsBuilder = regexOptionsBuilder;
+ void setRegexOptionsBuilder(RegexOptions.Builder regexOptionsBuilder) {
+ this.regexOptions = regexOptionsBuilder.build();
}
- void setFuzzyOptionsBuilder(CompletionSuggestionBuilder.FuzzyOptionsBuilder fuzzyOptionsBuilder) {
- this.fuzzyOptionsBuilder = fuzzyOptionsBuilder;
+ void setFuzzyOptionsBuilder(FuzzyOptions.Builder fuzzyOptionsBuilder) {
+ this.fuzzyOptions = fuzzyOptionsBuilder.build();
}
void setQueryContexts(Map<String, List<ContextMapping.QueryContext>> queryContexts) {
@@ -72,7 +72,7 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
}
void setPayloadFields(List<String> fields) {
- setPayloadFields(new HashSet<String>(fields));
+ setPayloadFields(new HashSet<>(fields));
}
Set<String> getPayloadFields() {
@@ -83,24 +83,24 @@ public class CompletionSuggestionContext extends SuggestionSearchContext.Suggest
CompletionFieldMapper.CompletionFieldType fieldType = getFieldType();
final CompletionQuery query;
if (getPrefix() != null) {
- if (fuzzyOptionsBuilder != null) {
+ if (fuzzyOptions != null) {
query = fieldType.fuzzyQuery(getPrefix().utf8ToString(),
- Fuzziness.fromEdits(fuzzyOptionsBuilder.getEditDistance()),
- fuzzyOptionsBuilder.getFuzzyPrefixLength(), fuzzyOptionsBuilder.getFuzzyMinLength(),
- fuzzyOptionsBuilder.getMaxDeterminizedStates(), fuzzyOptionsBuilder.isTranspositions(),
- fuzzyOptionsBuilder.isUnicodeAware());
+ Fuzziness.fromEdits(fuzzyOptions.getEditDistance()),
+ fuzzyOptions.getFuzzyPrefixLength(), fuzzyOptions.getFuzzyMinLength(),
+ fuzzyOptions.getMaxDeterminizedStates(), fuzzyOptions.isTranspositions(),
+ fuzzyOptions.isUnicodeAware());
} else {
query = fieldType.prefixQuery(getPrefix());
}
} else if (getRegex() != null) {
- if (fuzzyOptionsBuilder != null) {
+ if (fuzzyOptions != null) {
throw new IllegalArgumentException("can not use 'fuzzy' options with 'regex");
}
- if (regexOptionsBuilder == null) {
- regexOptionsBuilder = new CompletionSuggestionBuilder.RegexOptionsBuilder();
+ if (regexOptions == null) {
+ regexOptions = RegexOptions.builder().build();
}
- query = fieldType.regexpQuery(getRegex(), regexOptionsBuilder.getFlagsValue(),
- regexOptionsBuilder.getMaxDeterminizedStates());
+ query = fieldType.regexpQuery(getRegex(), regexOptions.getFlagsValue(),
+ regexOptions.getMaxDeterminizedStates());
} else {
throw new IllegalArgumentException("'prefix' or 'regex' must be defined");
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/FuzzyOptions.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/FuzzyOptions.java
new file mode 100644
index 0000000000..317ac049d6
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/FuzzyOptions.java
@@ -0,0 +1,277 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery;
+import org.apache.lucene.util.automaton.Operations;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Fuzzy options for completion suggester
+ */
+public class FuzzyOptions implements ToXContent, Writeable<FuzzyOptions> {
+ static final ParseField FUZZY_OPTIONS = new ParseField("fuzzy");
+ static final ParseField TRANSPOSITION_FIELD = new ParseField("transpositions");
+ static final ParseField MIN_LENGTH_FIELD = new ParseField("min_length");
+ static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length");
+ static final ParseField UNICODE_AWARE_FIELD = new ParseField("unicode_aware");
+ static final ParseField MAX_DETERMINIZED_STATES_FIELD = new ParseField("max_determinized_states");
+
+ private int editDistance;
+ private boolean transpositions;
+ private int fuzzyMinLength;
+ private int fuzzyPrefixLength;
+ private boolean unicodeAware;
+ private int maxDeterminizedStates;
+
+ private FuzzyOptions(int editDistance, boolean transpositions, int fuzzyMinLength, int fuzzyPrefixLength,
+ boolean unicodeAware, int maxDeterminizedStates) {
+ this.editDistance = editDistance;
+ this.transpositions = transpositions;
+ this.fuzzyMinLength = fuzzyMinLength;
+ this.fuzzyPrefixLength = fuzzyPrefixLength;
+ this.unicodeAware = unicodeAware;
+ this.maxDeterminizedStates = maxDeterminizedStates;
+ }
+
+ private FuzzyOptions() {
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the maximum number of edits
+ */
+ public int getEditDistance() {
+ return editDistance;
+ }
+
+ /**
+ * Returns if transpositions option is set
+ *
+ * if transpositions is set, then swapping one character for another counts as one edit instead of two.
+ */
+ public boolean isTranspositions() {
+ return transpositions;
+ }
+
+ /**
+ * Returns the length of input prefix after which edits are applied
+ */
+ public int getFuzzyMinLength() {
+ return fuzzyMinLength;
+ }
+
+ /**
+ * Returns the minimum length of the input prefix required to apply any edits
+ */
+ public int getFuzzyPrefixLength() {
+ return fuzzyPrefixLength;
+ }
+
+ /**
+ * Returns if all measurements (like edit distance, transpositions and lengths) are in unicode code
+ * points (actual letters) instead of bytes.
+ */
+ public boolean isUnicodeAware() {
+ return unicodeAware;
+ }
+
+ /**
+ * Returns the maximum automaton states allowed for fuzzy expansion
+ */
+ public int getMaxDeterminizedStates() {
+ return maxDeterminizedStates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FuzzyOptions that = (FuzzyOptions) o;
+
+ if (editDistance != that.editDistance) return false;
+ if (transpositions != that.transpositions) return false;
+ if (fuzzyMinLength != that.fuzzyMinLength) return false;
+ if (fuzzyPrefixLength != that.fuzzyPrefixLength) return false;
+ if (unicodeAware != that.unicodeAware) return false;
+ return maxDeterminizedStates == that.maxDeterminizedStates;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = editDistance;
+ result = 31 * result + (transpositions ? 1 : 0);
+ result = 31 * result + fuzzyMinLength;
+ result = 31 * result + fuzzyPrefixLength;
+ result = 31 * result + (unicodeAware ? 1 : 0);
+ result = 31 * result + maxDeterminizedStates;
+ return result;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject(FUZZY_OPTIONS.getPreferredName());
+ builder.field(Fuzziness.FIELD.getPreferredName(), editDistance);
+ builder.field(TRANSPOSITION_FIELD.getPreferredName(), transpositions);
+ builder.field(MIN_LENGTH_FIELD.getPreferredName(), fuzzyMinLength);
+ builder.field(PREFIX_LENGTH_FIELD.getPreferredName(), fuzzyPrefixLength);
+ builder.field(UNICODE_AWARE_FIELD.getPreferredName(), unicodeAware);
+ builder.field(MAX_DETERMINIZED_STATES_FIELD.getPreferredName(), maxDeterminizedStates);
+ builder.endObject();
+ return builder;
+ }
+
+ public static FuzzyOptions readFuzzyOptions(StreamInput in) throws IOException {
+ FuzzyOptions fuzzyOptions = new FuzzyOptions();
+ fuzzyOptions.readFrom(in);
+ return fuzzyOptions;
+ }
+
+ @Override
+ public FuzzyOptions readFrom(StreamInput in) throws IOException {
+ this.transpositions = in.readBoolean();
+ this.unicodeAware = in.readBoolean();
+ this.editDistance = in.readVInt();
+ this.fuzzyMinLength = in.readVInt();
+ this.fuzzyPrefixLength = in.readVInt();
+ this.maxDeterminizedStates = in.readVInt();
+ return this;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeBoolean(transpositions);
+ out.writeBoolean(unicodeAware);
+ out.writeVInt(editDistance);
+ out.writeVInt(fuzzyMinLength);
+ out.writeVInt(fuzzyPrefixLength);
+ out.writeVInt(maxDeterminizedStates);
+ }
+
+ /**
+ * Options for fuzzy queries
+ */
+ public static class Builder {
+
+ private int editDistance = FuzzyCompletionQuery.DEFAULT_MAX_EDITS;
+ private boolean transpositions = FuzzyCompletionQuery.DEFAULT_TRANSPOSITIONS;
+ private int fuzzyMinLength = FuzzyCompletionQuery.DEFAULT_MIN_FUZZY_LENGTH;
+ private int fuzzyPrefixLength = FuzzyCompletionQuery.DEFAULT_NON_FUZZY_PREFIX;
+ private boolean unicodeAware = FuzzyCompletionQuery.DEFAULT_UNICODE_AWARE;
+ private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
+
+ public Builder() {
+ }
+
+ /**
+ * Sets the level of fuzziness used to create suggestions using a {@link Fuzziness} instance.
+ * The default value is {@link Fuzziness#ONE} which allows for an "edit distance" of one.
+ */
+ public Builder setFuzziness(int editDistance) {
+ if (editDistance < 0 || editDistance > 2) {
+ throw new IllegalArgumentException("fuzziness must be between 0 and 2");
+ }
+ this.editDistance = editDistance;
+ return this;
+ }
+
+ /**
+ * Sets the level of fuzziness used to create suggestions using a {@link Fuzziness} instance.
+ * The default value is {@link Fuzziness#ONE} which allows for an "edit distance" of one.
+ */
+ public Builder setFuzziness(Fuzziness fuzziness) {
+ Objects.requireNonNull(fuzziness, "fuzziness must not be null");
+ return setFuzziness(fuzziness.asDistance());
+ }
+
+ /**
+ * Sets if transpositions (swapping one character for another) counts as one character
+ * change or two.
+ * Defaults to true, meaning it uses the fuzzier option of counting transpositions as
+ * a single change.
+ */
+ public Builder setTranspositions(boolean transpositions) {
+ this.transpositions = transpositions;
+ return this;
+ }
+
+ /**
+ * Sets the minimum length of input string before fuzzy suggestions are returned, defaulting
+ * to 3.
+ */
+ public Builder setFuzzyMinLength(int fuzzyMinLength) {
+ if (fuzzyMinLength < 0) {
+ throw new IllegalArgumentException("fuzzyMinLength must not be negative");
+ }
+ this.fuzzyMinLength = fuzzyMinLength;
+ return this;
+ }
+
+ /**
+ * Sets the minimum length of the input, which is not checked for fuzzy alternatives, defaults to 1
+ */
+ public Builder setFuzzyPrefixLength(int fuzzyPrefixLength) {
+ if (fuzzyPrefixLength < 0) {
+ throw new IllegalArgumentException("fuzzyPrefixLength must not be negative");
+ }
+ this.fuzzyPrefixLength = fuzzyPrefixLength;
+ return this;
+ }
+
+ /**
+ * Sets the maximum automaton states allowed for the fuzzy expansion
+ */
+ public Builder setMaxDeterminizedStates(int maxDeterminizedStates) {
+ if (maxDeterminizedStates < 0) {
+ throw new IllegalArgumentException("maxDeterminizedStates must not be negative");
+ }
+ this.maxDeterminizedStates = maxDeterminizedStates;
+ return this;
+ }
+
+ /**
+ * Set to true if all measurements (like edit distance, transpositions and lengths) are in unicode
+ * code points (actual letters) instead of bytes. Default is false.
+ */
+ public Builder setUnicodeAware(boolean unicodeAware) {
+ this.unicodeAware = unicodeAware;
+ return this;
+ }
+
+ public FuzzyOptions build() {
+ return new FuzzyOptions(editDistance, transpositions, fuzzyMinLength, fuzzyPrefixLength,
+ unicodeAware, maxDeterminizedStates);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/RegexOptions.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/RegexOptions.java
new file mode 100644
index 0000000000..fc183cdb1c
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/RegexOptions.java
@@ -0,0 +1,153 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.apache.lucene.util.automaton.Operations;
+import org.apache.lucene.util.automaton.RegExp;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.index.query.RegexpFlag;
+
+import java.io.IOException;
+
+/**
+ * Regular expression options for completion suggester
+ */
+public class RegexOptions implements ToXContent, Writeable<RegexOptions> {
+ static final String NAME = "regex";
+ static final ParseField REGEX_OPTIONS = new ParseField(NAME);
+ static final ParseField FLAGS_VALUE = new ParseField("flags", "flags_value");
+ static final ParseField MAX_DETERMINIZED_STATES = new ParseField("max_determinized_states");
+ private int flagsValue;
+ private int maxDeterminizedStates;
+
+ private RegexOptions() {
+ }
+
+ private RegexOptions(int flagsValue, int maxDeterminizedStates) {
+ this.flagsValue = flagsValue;
+ this.maxDeterminizedStates = maxDeterminizedStates;
+ }
+
+ /**
+ * Returns internal regular expression syntax flag value
+ * see {@link RegexpFlag#value()}
+ */
+ public int getFlagsValue() {
+ return flagsValue;
+ }
+
+ /**
+ * Returns the maximum automaton states allowed for fuzzy expansion
+ */
+ public int getMaxDeterminizedStates() {
+ return maxDeterminizedStates;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RegexOptions that = (RegexOptions) o;
+
+ if (flagsValue != that.flagsValue) return false;
+ return maxDeterminizedStates == that.maxDeterminizedStates;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = flagsValue;
+ result = 31 * result + maxDeterminizedStates;
+ return result;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject(REGEX_OPTIONS.getPreferredName());
+ builder.field(FLAGS_VALUE.getPreferredName(), flagsValue);
+ builder.field(MAX_DETERMINIZED_STATES.getPreferredName(), maxDeterminizedStates);
+ builder.endObject();
+ return builder;
+ }
+
+ public static RegexOptions readRegexOptions(StreamInput in) throws IOException {
+ RegexOptions regexOptions = new RegexOptions();
+ regexOptions.readFrom(in);
+ return regexOptions;
+ }
+
+ @Override
+ public RegexOptions readFrom(StreamInput in) throws IOException {
+ this.flagsValue = in.readVInt();
+ this.maxDeterminizedStates = in.readVInt();
+ return this;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeVInt(flagsValue);
+ out.writeVInt(maxDeterminizedStates);
+ }
+
+ /**
+ * Options for regular expression queries
+ */
+ public static class Builder {
+ private int flagsValue = RegExp.ALL;
+ private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
+
+ public Builder() {
+ }
+
+ /**
+ * Sets the regular expression syntax flags
+ * see {@link RegexpFlag}
+ */
+ public Builder setFlags(String flags) {
+ this.flagsValue = RegexpFlag.resolveValue(flags);
+ return this;
+ }
+
+ /**
+ * Sets the maximum automaton states allowed for the regular expression expansion
+ */
+ public Builder setMaxDeterminizedStates(int maxDeterminizedStates) {
+ if (maxDeterminizedStates < 0) {
+ throw new IllegalArgumentException("maxDeterminizedStates must not be negative");
+ }
+ this.maxDeterminizedStates = maxDeterminizedStates;
+ return this;
+ }
+
+ public RegexOptions build() {
+ return new RegexOptions(flagsValue, maxDeterminizedStates);
+ }
+ }
+}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java
index dffbb1aa80..10ac3935cc 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java
@@ -158,12 +158,12 @@ public class CategoryContextMapping extends ContextMapping {
List<QueryContext> queryContexts = new ArrayList<>();
Token token = parser.nextToken();
if (token == Token.START_OBJECT || token == Token.VALUE_STRING) {
- CategoryQueryContext parse = CategoryQueryContext.parse(parser);
- queryContexts.add(new QueryContext(parse.getCategory().toString(), parse.getBoost(), parse.isPrefix()));
+ CategoryQueryContext parse = CategoryQueryContext.PROTOTYPE.fromXContext(parser);
+ queryContexts.add(new QueryContext(parse.getCategory(), parse.getBoost(), parse.isPrefix()));
} else if (token == Token.START_ARRAY) {
while (parser.nextToken() != Token.END_ARRAY) {
- CategoryQueryContext parse = CategoryQueryContext.parse(parser);
- queryContexts.add(new QueryContext(parse.getCategory().toString(), parse.getBoost(), parse.isPrefix()));
+ CategoryQueryContext parse = CategoryQueryContext.PROTOTYPE.fromXContext(parser);
+ queryContexts.add(new QueryContext(parse.getCategory(), parse.getBoost(), parse.isPrefix()));
}
}
return queryContexts;
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java
index c493126577..8db9afe5ae 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java
@@ -21,12 +21,15 @@ package org.elasticsearch.search.suggest.completion.context;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ObjectParser;
-import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
+import java.util.Collections;
+import java.util.Objects;
import static org.elasticsearch.search.suggest.completion.context.CategoryContextMapping.CONTEXT_BOOST;
import static org.elasticsearch.search.suggest.completion.context.CategoryContextMapping.CONTEXT_PREFIX;
@@ -35,12 +38,15 @@ import static org.elasticsearch.search.suggest.completion.context.CategoryContex
/**
* Defines the query context for {@link CategoryContextMapping}
*/
-public final class CategoryQueryContext implements ToXContent {
- private final CharSequence category;
+public final class CategoryQueryContext implements QueryContext {
+ public static final String NAME = "category";
+ public static final CategoryQueryContext PROTOTYPE = new CategoryQueryContext("", 1, false);
+
+ private final String category;
private final boolean isPrefix;
private final int boost;
- private CategoryQueryContext(CharSequence category, int boost, boolean isPrefix) {
+ private CategoryQueryContext(String category, int boost, boolean isPrefix) {
this.category = category;
this.boost = boost;
this.isPrefix = isPrefix;
@@ -49,7 +55,7 @@ public final class CategoryQueryContext implements ToXContent {
/**
* Returns the category of the context
*/
- public CharSequence getCategory() {
+ public String getCategory() {
return category;
}
@@ -71,8 +77,81 @@ public final class CategoryQueryContext implements ToXContent {
return new Builder();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CategoryQueryContext that = (CategoryQueryContext) o;
+
+ if (isPrefix != that.isPrefix) return false;
+ if (boost != that.boost) return false;
+ return category != null ? category.equals(that.category) : that.category == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = category != null ? category.hashCode() : 0;
+ result = 31 * result + (isPrefix ? 1 : 0);
+ result = 31 * result + boost;
+ return result;
+ }
+
+ @Override
+ public String getWriteableName() {
+ return NAME;
+ }
+
+ private static ObjectParser<Builder, Void> CATEGORY_PARSER = new ObjectParser<>(NAME, null);
+ static {
+ CATEGORY_PARSER.declareString(Builder::setCategory, new ParseField(CONTEXT_VALUE));
+ CATEGORY_PARSER.declareInt(Builder::setBoost, new ParseField(CONTEXT_BOOST));
+ CATEGORY_PARSER.declareBoolean(Builder::setPrefix, new ParseField(CONTEXT_PREFIX));
+ }
+
+ @Override
+ public CategoryQueryContext fromXContext(XContentParser parser) throws IOException {
+ XContentParser.Token token = parser.currentToken();
+ Builder builder = builder();
+ if (token == XContentParser.Token.START_OBJECT) {
+ CATEGORY_PARSER.parse(parser, builder);
+ } else if (token == XContentParser.Token.VALUE_STRING) {
+ builder.setCategory(parser.text());
+ } else {
+ throw new ElasticsearchParseException("category context must be an object or string");
+ }
+ return builder.build();
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.field(CONTEXT_VALUE, category);
+ builder.field(CONTEXT_BOOST, boost);
+ builder.field(CONTEXT_PREFIX, isPrefix);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeBoolean(isPrefix);
+ out.writeVInt(boost);
+ out.writeString(category);
+ }
+
+ @Override
+ public QueryContext readFrom(StreamInput in) throws IOException {
+ Builder builder = new Builder();
+ builder.isPrefix = in.readBoolean();
+ builder.boost = in.readVInt();
+ builder.category = in.readString();
+ return builder.build();
+ }
+
public static class Builder {
- private CharSequence category;
+ private String category;
private boolean isPrefix = false;
private int boost = 1;
@@ -80,11 +159,12 @@ public final class CategoryQueryContext implements ToXContent {
}
/**
- * Sets the category of the context.
+ * Sets the category of the category.
* This is a required field
*/
- public Builder setCategory(CharSequence context) {
- this.category = context;
+ public Builder setCategory(String category) {
+ Objects.requireNonNull(category, "category must not be null");
+ this.category = category;
return this;
}
@@ -102,42 +182,16 @@ public final class CategoryQueryContext implements ToXContent {
* Defaults to 1.
*/
public Builder setBoost(int boost) {
+ if (boost <= 0) {
+ throw new IllegalArgumentException("boost must be greater than 0");
+ }
this.boost = boost;
return this;
}
public CategoryQueryContext build() {
+ Objects.requireNonNull(category, "category must not be null");
return new CategoryQueryContext(category, boost, isPrefix);
}
}
-
- private static ObjectParser<Builder, Void> CATEGORY_PARSER = new ObjectParser<>("category", null);
- static {
- CATEGORY_PARSER.declareString(Builder::setCategory, new ParseField("context"));
- CATEGORY_PARSER.declareInt(Builder::setBoost, new ParseField("boost"));
- CATEGORY_PARSER.declareBoolean(Builder::setPrefix, new ParseField("prefix"));
- }
-
- public static CategoryQueryContext parse(XContentParser parser) throws IOException {
- XContentParser.Token token = parser.currentToken();
- Builder builder = builder();
- if (token == XContentParser.Token.START_OBJECT) {
- CATEGORY_PARSER.parse(parser, builder);
- } else if (token == XContentParser.Token.VALUE_STRING) {
- builder.setCategory(parser.text());
- } else {
- throw new ElasticsearchParseException("category context must be an object or string");
- }
- return builder.build();
- }
-
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- builder.startObject();
- builder.field(CONTEXT_VALUE, category);
- builder.field(CONTEXT_BOOST, boost);
- builder.field(CONTEXT_PREFIX, isPrefix);
- builder.endObject();
- return builder;
- }
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java
index 9d4bed4f66..ccd4b2d584 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java
@@ -43,7 +43,6 @@ import java.util.Set;
import static org.elasticsearch.search.suggest.completion.context.ContextMapping.FIELD_NAME;
import static org.elasticsearch.search.suggest.completion.context.ContextMapping.FIELD_TYPE;
-import static org.elasticsearch.search.suggest.completion.context.ContextMapping.QueryContext;
import static org.elasticsearch.search.suggest.completion.context.ContextMapping.Type;
/**
@@ -153,7 +152,7 @@ public class ContextMappings implements ToXContent {
* @param queryContexts a map of context mapping name and collected query contexts
* @return a context-enabled query
*/
- public ContextQuery toContextQuery(CompletionQuery query, Map<String, List<QueryContext>> queryContexts) {
+ public ContextQuery toContextQuery(CompletionQuery query, Map<String, List<ContextMapping.QueryContext>> queryContexts) {
ContextQuery typedContextQuery = new ContextQuery(query);
if (queryContexts.isEmpty() == false) {
CharsRefBuilder scratch = new CharsRefBuilder();
@@ -162,9 +161,9 @@ public class ContextMappings implements ToXContent {
scratch.setCharAt(0, (char) typeId);
scratch.setLength(1);
ContextMapping mapping = contextMappings.get(typeId);
- List<QueryContext> queryContext = queryContexts.get(mapping.name());
+ List<ContextMapping.QueryContext> queryContext = queryContexts.get(mapping.name());
if (queryContext != null) {
- for (QueryContext context : queryContext) {
+ for (ContextMapping.QueryContext context : queryContext) {
scratch.append(context.context);
typedContextQuery.addContext(scratch.toCharsRef(), context.boost, !context.isPrefix);
scratch.setLength(1);
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java
index f2f3d10215..2c90429302 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java
@@ -247,18 +247,15 @@ public class GeoContextMapping extends ContextMapping {
List<GeoQueryContext> queryContexts = new ArrayList<>();
Token token = parser.nextToken();
if (token == Token.START_OBJECT || token == Token.VALUE_STRING) {
- queryContexts.add(GeoQueryContext.parse(parser));
+ queryContexts.add(GeoQueryContext.PROTOTYPE.fromXContext(parser));
} else if (token == Token.START_ARRAY) {
while (parser.nextToken() != Token.END_ARRAY) {
- queryContexts.add(GeoQueryContext.parse(parser));
+ queryContexts.add(GeoQueryContext.PROTOTYPE.fromXContext(parser));
}
}
List<QueryContext> queryContextList = new ArrayList<>();
for (GeoQueryContext queryContext : queryContexts) {
- int minPrecision = this.precision;
- if (queryContext.getPrecision() != -1) {
- minPrecision = Math.min(minPrecision, queryContext.getPrecision());
- }
+ int minPrecision = Math.min(this.precision, queryContext.getPrecision());
GeoPoint point = queryContext.getGeoPoint();
final Collection<String> locations = new HashSet<>();
String geoHash = GeoHashUtils.stringEncode(point.getLon(), point.getLat(), minPrecision);
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java
index da9191bf2d..5b406abc1d 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java
@@ -23,14 +23,17 @@ import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ObjectParser;
-import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import static org.elasticsearch.search.suggest.completion.context.GeoContextMapping.CONTEXT_BOOST;
import static org.elasticsearch.search.suggest.completion.context.GeoContextMapping.CONTEXT_NEIGHBOURS;
@@ -40,7 +43,10 @@ import static org.elasticsearch.search.suggest.completion.context.GeoContextMapp
/**
* Defines the query context for {@link GeoContextMapping}
*/
-public final class GeoQueryContext implements ToXContent {
+public final class GeoQueryContext implements QueryContext {
+ public static final String NAME = "geo";
+ public static final GeoQueryContext PROTOTYPE = new GeoQueryContext(null, 1, 12, Collections.emptyList());
+
private final GeoPoint geoPoint;
private final int boost;
private final int precision;
@@ -81,14 +87,109 @@ public final class GeoQueryContext implements ToXContent {
return neighbours;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GeoQueryContext that = (GeoQueryContext) o;
+
+ if (boost != that.boost) return false;
+ if (precision != that.precision) return false;
+ if (geoPoint != null ? !geoPoint.equals(that.geoPoint) : that.geoPoint != null) return false;
+ return neighbours != null ? neighbours.equals(that.neighbours) : that.neighbours == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = geoPoint != null ? geoPoint.hashCode() : 0;
+ result = 31 * result + boost;
+ result = 31 * result + precision;
+ result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0);
+ return result;
+ }
+
public static Builder builder() {
return new Builder();
}
+ @Override
+ public String getWriteableName() {
+ return NAME;
+ }
+
+ private static ObjectParser<GeoQueryContext.Builder, Void> GEO_CONTEXT_PARSER = new ObjectParser<>(NAME, null);
+ static {
+ GEO_CONTEXT_PARSER.declareField((parser, geoQueryContext, geoContextMapping) -> geoQueryContext.setGeoPoint(GeoUtils.parseGeoPoint(parser)), new ParseField(CONTEXT_VALUE), ObjectParser.ValueType.OBJECT);
+ GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setBoost, new ParseField(CONTEXT_BOOST));
+ // TODO : add string support for precision for GeoUtils.geoHashLevelsForPrecision()
+ GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setPrecision, new ParseField(CONTEXT_PRECISION));
+ // TODO : add string array support for precision for GeoUtils.geoHashLevelsForPrecision()
+ GEO_CONTEXT_PARSER.declareIntArray(GeoQueryContext.Builder::setNeighbours, new ParseField(CONTEXT_NEIGHBOURS));
+ GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLat, new ParseField("lat"));
+ GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLon, new ParseField("lon"));
+ }
+
+ @Override
+ public GeoQueryContext fromXContext(XContentParser parser) throws IOException {
+ XContentParser.Token token = parser.currentToken();
+ GeoQueryContext.Builder builder = new Builder();
+ if (token == XContentParser.Token.START_OBJECT) {
+ GEO_CONTEXT_PARSER.parse(parser, builder);
+ } else if (token == XContentParser.Token.VALUE_STRING) {
+ builder.setGeoPoint(GeoPoint.fromGeohash(parser.text()));
+ } else {
+ throw new ElasticsearchParseException("geo context must be an object or string");
+ }
+ return builder.build();
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ builder.startObject(CONTEXT_VALUE);
+ builder.field("lat", geoPoint.getLat());
+ builder.field("lon", geoPoint.getLon());
+ builder.endObject();
+ builder.field(CONTEXT_BOOST, boost);
+ builder.field(CONTEXT_NEIGHBOURS, neighbours);
+ builder.field(CONTEXT_PRECISION, precision);
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeGeoPoint(geoPoint);
+ out.writeVInt(boost);
+ out.writeInt(precision);
+ out.writeVInt(neighbours.size());
+ for (Integer neighbour : neighbours) {
+ out.writeVInt(neighbour);
+ }
+ }
+
+ @Override
+ public QueryContext readFrom(StreamInput in) throws IOException {
+ Builder builder = new Builder();
+ builder.geoPoint = in.readGeoPoint();
+ builder.boost = in.readVInt();
+ builder.precision = in.readInt();
+ int nNeighbour = in.readVInt();
+ if (nNeighbour != 0) {
+ builder.neighbours = new ArrayList<>(nNeighbour);
+ for (int i = 0; i < nNeighbour; i++) {
+ builder.neighbours.add(in.readVInt());
+ }
+ }
+ return builder.build();
+ }
+
public static class Builder {
private GeoPoint geoPoint;
private int boost = 1;
- private int precision = -1;
+ private int precision = 12;
private List<Integer> neighbours = Collections.emptyList();
public Builder() {
@@ -99,6 +200,9 @@ public final class GeoQueryContext implements ToXContent {
* Defaults to 1
*/
public Builder setBoost(int boost) {
+ if (boost <= 0) {
+ throw new IllegalArgumentException("boost must be greater than 0");
+ }
this.boost = boost;
return this;
}
@@ -108,6 +212,9 @@ public final class GeoQueryContext implements ToXContent {
* Defaults to using index-time precision level
*/
public Builder setPrecision(int precision) {
+ if (precision < 1 || precision > 12) {
+ throw new IllegalArgumentException("precision must be between 1 and 12");
+ }
this.precision = precision;
return this;
}
@@ -117,6 +224,11 @@ public final class GeoQueryContext implements ToXContent {
* Defaults to only considering neighbours at the index-time precision level
*/
public Builder setNeighbours(List<Integer> neighbours) {
+ for (int neighbour : neighbours) {
+ if (neighbour < 1 || neighbour > 12) {
+ throw new IllegalArgumentException("neighbour value must be between 1 and 12");
+ }
+ }
this.neighbours = neighbours;
return this;
}
@@ -126,6 +238,7 @@ public final class GeoQueryContext implements ToXContent {
* This is a required field
*/
public Builder setGeoPoint(GeoPoint geoPoint) {
+ Objects.requireNonNull(geoPoint, "geoPoint must not be null");
this.geoPoint = geoPoint;
return this;
}
@@ -144,50 +257,10 @@ public final class GeoQueryContext implements ToXContent {
if (geoPoint == null) {
if (Double.isNaN(lat) == false && Double.isNaN(lon) == false) {
geoPoint = new GeoPoint(lat, lon);
- } else {
- throw new IllegalArgumentException("no geohash or geo point provided");
}
}
+ Objects.requireNonNull(geoPoint, "geoPoint must not be null");
return new GeoQueryContext(geoPoint, boost, precision, neighbours);
}
}
-
- private static ObjectParser<GeoQueryContext.Builder, Void> GEO_CONTEXT_PARSER = new ObjectParser<>("geo", null);
- static {
- GEO_CONTEXT_PARSER.declareField((parser, geoQueryContext, geoContextMapping) -> geoQueryContext.setGeoPoint(GeoUtils.parseGeoPoint(parser)), new ParseField("context"), ObjectParser.ValueType.OBJECT);
- GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setBoost, new ParseField("boost"));
- // TODO : add string support for precision for GeoUtils.geoHashLevelsForPrecision()
- GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setPrecision, new ParseField("precision"));
- // TODO : add string array support for precision for GeoUtils.geoHashLevelsForPrecision()
- GEO_CONTEXT_PARSER.declareIntArray(GeoQueryContext.Builder::setNeighbours, new ParseField("neighbours"));
- GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLat, new ParseField("lat"));
- GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLon, new ParseField("lon"));
- }
-
- public static GeoQueryContext parse(XContentParser parser) throws IOException {
- XContentParser.Token token = parser.currentToken();
- GeoQueryContext.Builder builder = new Builder();
- if (token == XContentParser.Token.START_OBJECT) {
- GEO_CONTEXT_PARSER.parse(parser, builder);
- } else if (token == XContentParser.Token.VALUE_STRING) {
- builder.setGeoPoint(GeoPoint.fromGeohash(parser.text()));
- } else {
- throw new ElasticsearchParseException("geo context must be an object or string");
- }
- return builder.build();
- }
-
- @Override
- public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- builder.startObject();
- builder.startObject(CONTEXT_VALUE);
- builder.field("lat", geoPoint.getLat());
- builder.field("lon", geoPoint.getLon());
- builder.endObject();
- builder.field(CONTEXT_BOOST, boost);
- builder.field(CONTEXT_NEIGHBOURS, neighbours);
- builder.field(CONTEXT_PRECISION, precision);
- builder.endObject();
- return builder;
- }
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java
new file mode 100644
index 0000000000..ccfd4a8d3d
--- /dev/null
+++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java
@@ -0,0 +1,34 @@
+/*
+ * 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.search.suggest.completion.context;
+
+import org.elasticsearch.common.io.stream.NamedWriteable;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+/**
+ * Interface for serializing/de-serializing completion query context
+ */
+public interface QueryContext extends ToXContent, NamedWriteable<QueryContext> {
+
+ QueryContext fromXContext(XContentParser parser) throws IOException;
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java b/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java
index 71431e70f3..a7354f36cb 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java
@@ -69,9 +69,6 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.hamcrest.Matchers.equalTo;
@@ -276,9 +273,9 @@ public abstract class AbstractSuggestionBuilderTestCase<SB extends SuggestionBui
SuggestionSearchContext buildSuggestSearchContext = suggestBuilder.build(mockShardContext);
assertEquals(parsedSuggestionSearchContext.suggestions().size(), buildSuggestSearchContext.suggestions().size());
- Iterator<Entry<String, SuggestionContext>> iterator = buildSuggestSearchContext.suggestions().entrySet().iterator();
- for (Entry<String, SuggestionContext> entry : parsedSuggestionSearchContext.suggestions().entrySet()) {
- Entry<String, SuggestionContext> other = iterator.next();
+ Iterator<Map.Entry<String, SuggestionContext>> iterator = buildSuggestSearchContext.suggestions().entrySet().iterator();
+ for (Map.Entry<String, SuggestionContext> entry : parsedSuggestionSearchContext.suggestions().entrySet()) {
+ Map.Entry<String, SuggestionContext> other = iterator.next();
assertEquals(entry.getKey(), other.getKey());
SuggestionContext oldSchoolContext = entry.getValue();
@@ -353,29 +350,9 @@ public abstract class AbstractSuggestionBuilderTestCase<SB extends SuggestionBui
protected SB serializedCopy(SB original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
output.writeSuggestion(original);
- ;
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
return (SB) in.readSuggestion();
}
}
}
-
- protected static <T> void maybeSet(Consumer<T> consumer, T value) {
- if (randomBoolean()) {
- consumer.accept(value);
- }
- }
-
- /**
- * helper to get a random value in a certain range that's different from the
- * input
- */
- protected static <T> T randomValueOtherThan(T input, Supplier<T> randomSupplier) {
- T randomValue = null;
- do {
- randomValue = randomSupplier.get();
- } while (randomValue.equals(input));
- return randomValue;
- }
-
}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java b/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java
index 896d20895e..d12b70c59a 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java
@@ -47,7 +47,7 @@ import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.suggest.completion.CompletionStats;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
-import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder.FuzzyOptionsBuilder;
+import org.elasticsearch.search.suggest.completion.FuzzyOptions;
import org.elasticsearch.search.suggest.completion.context.CategoryContextMapping;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import org.elasticsearch.search.suggest.completion.context.GeoContextMapping;
@@ -56,6 +56,7 @@ import org.elasticsearch.test.ESIntegTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
@@ -197,7 +198,8 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
}
indexRandom(true, indexRequestBuilders);
- CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg").size(numDocs).payload("count");
+ CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg").
+ size(numDocs).payload(Collections.singletonList("count"));
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(prefix).execute().actionGet();
assertNoFailures(suggestResponse);
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("foo");
@@ -243,7 +245,8 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
client().prepareIndex(INDEX, TYPE, "2").setSource(FIELD, "suggestion")
);
indexRandom(true, indexRequestBuilders);
- CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg").payload("test_field");
+ CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg")
+ .payload(Collections.singletonList("test_field"));
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(prefix).execute().actionGet();
assertNoFailures(suggestResponse);
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("foo");
@@ -280,7 +283,8 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
indexRequestBuilders.add(client().prepareIndex(INDEX, TYPE, "2").setSource(source));
indexRandom(true, indexRequestBuilders);
- CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg").payload("title", "count");
+ CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg")
+ .payload(Arrays.asList("title", "count"));
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(prefix).execute().actionGet();
assertNoFailures(suggestResponse);
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("foo");
@@ -325,12 +329,13 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
int suggestionSize = randomIntBetween(1, numDocs);
int numRequestedPayloadFields = randomIntBetween(2, numPayloadFields);
- String[] payloadFields = new String[numRequestedPayloadFields];
+ List<String> payloadFields = new ArrayList<>(numRequestedPayloadFields);
for (int i = 0; i < numRequestedPayloadFields; i++) {
- payloadFields[i] = "test_field" + i;
+ payloadFields.add("test_field" + i);
}
- CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg").size(suggestionSize).payload(payloadFields);
+ CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("sugg")
+ .size(suggestionSize).payload(payloadFields);
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(prefix).execute().actionGet();
assertNoFailures(suggestResponse);
CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("foo");
@@ -702,7 +707,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
refresh();
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nriv", new FuzzyOptionsBuilder().setTranspositions(false)).size(10)
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nriv", FuzzyOptions.builder().setTranspositions(false).build()).size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo");
@@ -724,12 +729,12 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
refresh();
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nriva", new FuzzyOptionsBuilder().setFuzzyMinLength(6)).size(10)
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nriva", FuzzyOptions.builder().setFuzzyMinLength(6).build()).size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo");
suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nrivan", new FuzzyOptionsBuilder().setFuzzyMinLength(6)).size(10)
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nrivan", FuzzyOptions.builder().setFuzzyMinLength(6).build()).size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo", "Nirvana");
}
@@ -746,12 +751,12 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
refresh();
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nirw", new FuzzyOptionsBuilder().setFuzzyPrefixLength(4)).size(10)
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nirw", FuzzyOptions.builder().setFuzzyPrefixLength(4).build()).size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo");
suggestResponse = client().prepareSuggest(INDEX).addSuggestion(
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nirvo", new FuzzyOptionsBuilder().setFuzzyPrefixLength(4)).size(10)
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("Nirvo", FuzzyOptions.builder().setFuzzyPrefixLength(4).build()).size(10)
).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo", "Nirvana");
}
@@ -769,18 +774,18 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
// suggestion with a character, which needs unicode awareness
org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder completionSuggestionBuilder =
- SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", new FuzzyOptionsBuilder().setUnicodeAware(true)).size(10);
+ SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", FuzzyOptions.builder().setUnicodeAware(true).build()).size(10);
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(completionSuggestionBuilder).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo", "ööööö");
// removing unicode awareness leads to no result
- completionSuggestionBuilder = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", new FuzzyOptionsBuilder().setUnicodeAware(false)).size(10);
+ completionSuggestionBuilder = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", FuzzyOptions.builder().setUnicodeAware(false).build()).size(10);
suggestResponse = client().prepareSuggest(INDEX).addSuggestion(completionSuggestionBuilder).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo");
// increasing edit distance instead of unicode awareness works again, as this is only a single character
- completionSuggestionBuilder = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", new FuzzyOptionsBuilder().setUnicodeAware(false).setFuzziness(Fuzziness.TWO)).size(10);
+ completionSuggestionBuilder = SuggestBuilders.completionSuggestion("foo").field(FIELD).prefix("öööи", FuzzyOptions.builder().setUnicodeAware(false).setFuzziness(Fuzziness.TWO).build()).size(10);
suggestResponse = client().prepareSuggest(INDEX).addSuggestion(completionSuggestionBuilder).execute().actionGet();
assertSuggestions(suggestResponse, false, "foo", "ööööö");
}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java b/core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java
index f4551b3de9..8e324330fe 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/SuggestBuilderTests.java
@@ -47,7 +47,7 @@ public class SuggestBuilderTests extends WritableTestCase<SuggestBuilder> {
@Override
- protected NamedWriteableRegistry provideNamedWritbaleRegistry() {
+ protected NamedWriteableRegistry provideNamedWritableRegistry() {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry();
namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, TermSuggestionBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, PhraseSuggestionBuilder.PROTOTYPE);
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/CategoryQueryContextTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/CategoryQueryContextTests.java
new file mode 100644
index 0000000000..b4d80ebba9
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/CategoryQueryContextTests.java
@@ -0,0 +1,95 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
+
+import java.io.IOException;
+
+public class CategoryQueryContextTests extends QueryContextTestCase<CategoryQueryContext> {
+
+ public static CategoryQueryContext randomCategoryQueryContext() {
+ final CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
+ builder.setCategory(randomAsciiOfLength(10));
+ maybeSet(builder::setBoost, randomIntBetween(1, 10));
+ maybeSet(builder::setPrefix, randomBoolean());
+ return builder.build();
+ }
+
+ @Override
+ protected CategoryQueryContext createTestModel() {
+ return randomCategoryQueryContext();
+ }
+
+ @Override
+ protected CategoryQueryContext createMutation(CategoryQueryContext original) throws IOException {
+ final CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
+ builder.setCategory(original.getCategory()).setBoost(original.getBoost()).setPrefix(original.isPrefix());
+ switch (randomIntBetween(0, 2)) {
+ case 0:
+ builder.setCategory(randomValueOtherThan(original.getCategory(), () -> randomAsciiOfLength(10)));
+ break;
+ case 1:
+ builder.setBoost(randomValueOtherThan(original.getBoost(), () -> randomIntBetween(1, 5)));
+ break;
+ case 2:
+ builder.setPrefix(!original.isPrefix());
+ break;
+
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected CategoryQueryContext prototype() {
+ return CategoryQueryContext.PROTOTYPE;
+ }
+
+ public void testNullCategoryIsIllegal() {
+ final CategoryQueryContext categoryQueryContext = randomCategoryQueryContext();
+ final CategoryQueryContext.Builder builder = CategoryQueryContext.builder()
+ .setBoost(categoryQueryContext.getBoost())
+ .setPrefix(categoryQueryContext.isPrefix());
+ try {
+ builder.build();
+ fail("null category is illegal");
+ } catch (NullPointerException e) {
+ assertEquals(e.getMessage(), "category must not be null");
+ }
+ }
+
+ public void testIllegalArguments() {
+ final CategoryQueryContext.Builder builder = CategoryQueryContext.builder();
+
+ try {
+ builder.setCategory(null);
+ fail("category must not be null");
+ } catch (NullPointerException e) {
+ assertEquals(e.getMessage(), "category must not be null");
+ }
+
+ try {
+ builder.setBoost(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("boost must be positive");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "boost must be greater than 0");
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java
new file mode 100644
index 0000000000..9fc5988c07
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/CompletionSuggesterBuilderTests.java
@@ -0,0 +1,135 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
+import org.elasticsearch.search.suggest.SuggestionSearchContext;
+import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
+import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
+import org.elasticsearch.search.suggest.completion.context.GeoQueryContext;
+import org.elasticsearch.search.suggest.completion.context.QueryContext;
+import org.junit.BeforeClass;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class CompletionSuggesterBuilderTests extends AbstractSuggestionBuilderTestCase<CompletionSuggestionBuilder> {
+
+ @BeforeClass
+ public static void initQueryContexts() {
+ namedWriteableRegistry.registerPrototype(QueryContext.class, CategoryQueryContext.PROTOTYPE);
+ namedWriteableRegistry.registerPrototype(QueryContext.class, GeoQueryContext.PROTOTYPE);
+ }
+
+ @Override
+ protected CompletionSuggestionBuilder randomSuggestionBuilder() {
+ CompletionSuggestionBuilder testBuilder = new CompletionSuggestionBuilder(randomAsciiOfLength(10));
+ switch (randomIntBetween(0, 3)) {
+ case 0:
+ testBuilder.prefix(randomAsciiOfLength(10));
+ break;
+ case 1:
+ testBuilder.prefix(randomAsciiOfLength(10), FuzzyOptionsTests.randomFuzzyOptions());
+ break;
+ case 2:
+ testBuilder.prefix(randomAsciiOfLength(10), randomFrom(Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO));
+ break;
+ case 3:
+ testBuilder.regex(randomAsciiOfLength(10), RegexOptionsTests.randomRegexOptions());
+ break;
+ }
+ List<String> payloads = new ArrayList<>();
+ Collections.addAll(payloads, generateRandomStringArray(5, 10, false, false));
+ maybeSet(testBuilder::payload, payloads);
+ if (randomBoolean()) {
+ int numContext = randomIntBetween(1, 5);
+ CategoryQueryContext[] contexts = new CategoryQueryContext[numContext];
+ for (int i = 0; i < numContext; i++) {
+ contexts[i] = CategoryQueryContextTests.randomCategoryQueryContext();
+ }
+ testBuilder.categoryContexts(randomAsciiOfLength(10), contexts);
+ }
+ if (randomBoolean()) {
+ int numContext = randomIntBetween(1, 5);
+ GeoQueryContext[] contexts = new GeoQueryContext[numContext];
+ for (int i = 0; i < numContext; i++) {
+ contexts[i] = GeoQueryContextTests.randomGeoQueryContext();
+ }
+ testBuilder.geoContexts(randomAsciiOfLength(10), contexts);
+ }
+ return testBuilder;
+ }
+
+ @Override
+ protected void assertSuggestionContext(SuggestionContext oldSuggestion, SuggestionContext newSuggestion) {
+
+ }
+
+ @Override
+ public void testBuild() throws IOException {
+ // skip for now
+ }
+
+ @Override
+ public void testFromXContent() throws IOException {
+ // skip for now
+ }
+
+ protected void mutateSpecificParameters(CompletionSuggestionBuilder builder) throws IOException {
+ switch (randomIntBetween(0, 5)) {
+ case 0:
+ List<String> payloads = new ArrayList<>();
+ Collections.addAll(payloads, generateRandomStringArray(5, 10, false, false));
+ builder.payload(payloads);
+ break;
+ case 1:
+ int numCategoryContext = randomIntBetween(1, 5);
+ CategoryQueryContext[] categoryContexts = new CategoryQueryContext[numCategoryContext];
+ for (int i = 0; i < numCategoryContext; i++) {
+ categoryContexts[i] = CategoryQueryContextTests.randomCategoryQueryContext();
+ }
+ builder.categoryContexts(randomAsciiOfLength(10), categoryContexts);
+ break;
+ case 2:
+ int numGeoContext = randomIntBetween(1, 5);
+ GeoQueryContext[] geoContexts = new GeoQueryContext[numGeoContext];
+ for (int i = 0; i < numGeoContext; i++) {
+ geoContexts[i] = GeoQueryContextTests.randomGeoQueryContext();
+ }
+ builder.geoContexts(randomAsciiOfLength(10), geoContexts);
+ break;
+ case 3:
+ builder.prefix(randomAsciiOfLength(10), FuzzyOptionsTests.randomFuzzyOptions());
+ break;
+ case 4:
+ builder.prefix(randomAsciiOfLength(10), randomFrom(Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO));
+ break;
+ case 5:
+ builder.regex(randomAsciiOfLength(10), RegexOptionsTests.randomRegexOptions());
+ break;
+ default:
+ throw new IllegalStateException("should not through");
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/FuzzyOptionsTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/FuzzyOptionsTests.java
new file mode 100644
index 0000000000..848a9088bc
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/FuzzyOptionsTests.java
@@ -0,0 +1,131 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.unit.Fuzziness;
+
+import java.io.IOException;
+
+public class FuzzyOptionsTests extends WritableTestCase<FuzzyOptions> {
+
+ public static FuzzyOptions randomFuzzyOptions() {
+ final FuzzyOptions.Builder builder = FuzzyOptions.builder();
+ if (randomBoolean()) {
+ maybeSet(builder::setFuzziness, randomFrom(Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO));
+ } else {
+ maybeSet(builder::setFuzziness, randomFrom(0, 1, 2));
+ }
+ maybeSet(builder::setFuzzyMinLength, randomIntBetween(0, 10));
+ maybeSet(builder::setFuzzyPrefixLength, randomIntBetween(0, 10));
+ maybeSet(builder::setMaxDeterminizedStates, randomIntBetween(1, 1000));
+ maybeSet(builder::setTranspositions, randomBoolean());
+ maybeSet(builder::setUnicodeAware, randomBoolean());
+ return builder.build();
+ }
+
+ @Override
+ protected FuzzyOptions createTestModel() {
+ return randomFuzzyOptions();
+ }
+
+ @Override
+ protected FuzzyOptions createMutation(FuzzyOptions original) throws IOException {
+ final FuzzyOptions.Builder builder = FuzzyOptions.builder();
+ builder.setFuzziness(original.getEditDistance())
+ .setFuzzyPrefixLength(original.getFuzzyPrefixLength())
+ .setFuzzyMinLength(original.getFuzzyMinLength())
+ .setMaxDeterminizedStates(original.getMaxDeterminizedStates())
+ .setTranspositions(original.isTranspositions())
+ .setUnicodeAware(original.isUnicodeAware());
+ switch (randomIntBetween(0, 5)) {
+ case 0:
+ builder.setFuzziness(randomValueOtherThan(original.getEditDistance(), () -> randomFrom(0, 1, 2)));
+ break;
+ case 1:
+ builder.setFuzzyPrefixLength(randomValueOtherThan(original.getFuzzyPrefixLength(), () ->
+ randomIntBetween(1, 3)));
+ break;
+ case 2:
+ builder.setFuzzyMinLength(randomValueOtherThan(original.getFuzzyMinLength(), () ->
+ randomIntBetween(1, 3)));
+ break;
+ case 3:
+ builder.setMaxDeterminizedStates(randomValueOtherThan(original.getMaxDeterminizedStates(), () ->
+ randomIntBetween(1, 10)));
+ break;
+ case 4:
+ builder.setTranspositions(!original.isTranspositions());
+ break;
+ case 5:
+ builder.setUnicodeAware(!original.isUnicodeAware());
+ break;
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected FuzzyOptions readFrom(StreamInput in) throws IOException {
+ return FuzzyOptions.readFuzzyOptions(in);
+ }
+
+ public void testIllegalArguments() {
+ final FuzzyOptions.Builder builder = FuzzyOptions.builder();
+ try {
+ builder.setFuzziness(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("fuzziness must be > 0");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "fuzziness must be between 0 and 2");
+ }
+ try {
+ builder.setFuzziness(randomIntBetween(3, Integer.MAX_VALUE));
+ fail("fuzziness must be < 2");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "fuzziness must be between 0 and 2");
+ }
+ try {
+ builder.setFuzziness(null);
+ fail("fuzziness must not be null");
+ } catch (NullPointerException e) {
+ assertEquals(e.getMessage(), "fuzziness must not be null");
+ }
+
+ try {
+ builder.setFuzzyMinLength(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("fuzzyMinLength must be >= 0");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "fuzzyMinLength must not be negative");
+ }
+
+ try {
+ builder.setFuzzyPrefixLength(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("fuzzyPrefixLength must be >= 0");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "fuzzyPrefixLength must not be negative");
+ }
+
+ try {
+ builder.setMaxDeterminizedStates(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("max determinized state must be >= 0");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "maxDeterminizedStates must not be negative");
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/GeoQueryContextTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/GeoQueryContextTests.java
new file mode 100644
index 0000000000..d26be5036e
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/GeoQueryContextTests.java
@@ -0,0 +1,136 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.common.geo.GeoPoint;
+import org.elasticsearch.search.suggest.completion.context.GeoQueryContext;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GeoQueryContextTests extends QueryContextTestCase<GeoQueryContext> {
+
+ public static GeoQueryContext randomGeoQueryContext() {
+ final GeoQueryContext.Builder builder = GeoQueryContext.builder();
+ builder.setGeoPoint(new GeoPoint(randomDouble(), randomDouble()));
+ maybeSet(builder::setBoost, randomIntBetween(1, 10));
+ maybeSet(builder::setPrecision, randomIntBetween(1, 12));
+ List<Integer> neighbours = new ArrayList<>();
+ for (int i = 0; i < randomIntBetween(1, 12); i++) {
+ neighbours.add(randomIntBetween(1, 12));
+ }
+ maybeSet(builder::setNeighbours, neighbours);
+ return builder.build();
+ }
+
+ @Override
+ protected GeoQueryContext createTestModel() {
+ return randomGeoQueryContext();
+ }
+
+ @Override
+ protected GeoQueryContext createMutation(GeoQueryContext original) throws IOException {
+ final GeoQueryContext.Builder builder = GeoQueryContext.builder();
+ builder.setGeoPoint(original.getGeoPoint()).setBoost(original.getBoost())
+ .setNeighbours(original.getNeighbours()).setPrecision(original.getPrecision());
+ switch (randomIntBetween(0, 3)) {
+ case 0:
+ builder.setGeoPoint(randomValueOtherThan(original.getGeoPoint() ,() ->
+ new GeoPoint(randomDouble(), randomDouble())));
+ break;
+ case 1:
+ builder.setBoost(randomValueOtherThan(original.getBoost() ,() -> randomIntBetween(1, 5)));
+ break;
+ case 2:
+ builder.setPrecision(randomValueOtherThan(original.getPrecision() ,() -> randomIntBetween(1, 12)));
+ break;
+ case 3:
+ builder.setNeighbours(randomValueOtherThan(original.getNeighbours(), () -> {
+ List<Integer> newNeighbours = new ArrayList<>();
+ for (int i = 0; i < randomIntBetween(1, 12); i++) {
+ newNeighbours.add(randomIntBetween(1, 12));
+ }
+ return newNeighbours;
+ }));
+ break;
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected GeoQueryContext prototype() {
+ return GeoQueryContext.PROTOTYPE;
+ }
+
+ public void testNullGeoPointIsIllegal() {
+ final GeoQueryContext geoQueryContext = randomGeoQueryContext();
+ final GeoQueryContext.Builder builder = GeoQueryContext.builder()
+ .setNeighbours(geoQueryContext.getNeighbours())
+ .setPrecision(geoQueryContext.getPrecision())
+ .setBoost(geoQueryContext.getBoost());
+ try {
+ builder.build();
+ fail("null geo point is illegal");
+ } catch (NullPointerException e) {
+ assertThat(e.getMessage(), equalTo("geoPoint must not be null"));
+ }
+ }
+
+ public void testIllegalArguments() {
+ final GeoQueryContext.Builder builder = GeoQueryContext.builder();
+
+ try {
+ builder.setGeoPoint(null);
+ fail("geoPoint must not be null");
+ } catch (NullPointerException e) {
+ assertEquals(e.getMessage(), "geoPoint must not be null");
+ }
+ try {
+ builder.setBoost(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("boost must be positive");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "boost must be greater than 0");
+ }
+ int precision = 0;
+ try {
+ do {
+ precision = randomInt();
+ } while (precision >= 1 && precision <= 12);
+ builder.setPrecision(precision);
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "precision must be between 1 and 12");
+ }
+ try {
+ List<Integer> neighbours = new ArrayList<>();
+ neighbours.add(precision);
+ for (int i = 1; i < randomIntBetween(1, 11); i++) {
+ neighbours.add(i);
+ }
+ Collections.shuffle(neighbours, random());
+ builder.setNeighbours(neighbours);
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "neighbour value must be between 1 and 12");
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/QueryContextTestCase.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/QueryContextTestCase.java
new file mode 100644
index 0000000000..b4a6a5b1da
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/QueryContextTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.suggest.completion.context.QueryContext;
+
+import java.io.IOException;
+
+
+public abstract class QueryContextTestCase<QC extends QueryContext> extends WritableTestCase<QC> {
+
+ private static final int NUMBER_OF_RUNS = 20;
+
+ /**
+ * query context prototype to read serialized format
+ */
+ protected abstract QC prototype();
+
+ @Override
+ protected QC readFrom(StreamInput in) throws IOException {
+ return (QC) prototype().readFrom(in);
+ }
+
+ public void testToXContext() throws IOException {
+ for (int i = 0; i < NUMBER_OF_RUNS; i++) {
+ QueryContext toXContent = createTestModel();
+ XContentBuilder builder = XContentFactory.jsonBuilder();
+ toXContent.toXContent(builder, ToXContent.EMPTY_PARAMS);
+ BytesReference bytesReference = builder.bytes();
+ XContentParser parser = XContentFactory.xContent(bytesReference).createParser(bytesReference);
+ parser.nextToken();
+ QueryContext fromXContext = prototype().fromXContext(parser);
+ assertEquals(toXContent, fromXContext);
+ assertEquals(toXContent.hashCode(), fromXContext.hashCode());
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/RegexOptionsTests.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/RegexOptionsTests.java
new file mode 100644
index 0000000000..082e2bc268
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/RegexOptionsTests.java
@@ -0,0 +1,71 @@
+/*
+ * 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.search.suggest.completion;
+
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.index.query.RegexpFlag;
+
+import java.io.IOException;
+
+public class RegexOptionsTests extends WritableTestCase<RegexOptions> {
+
+ public static RegexOptions randomRegexOptions() {
+ final RegexOptions.Builder builder = RegexOptions.builder();
+ maybeSet(builder::setMaxDeterminizedStates, randomIntBetween(1, 1000));
+ StringBuilder sb = new StringBuilder();
+ for (RegexpFlag regexpFlag : RegexpFlag.values()) {
+ if (randomBoolean()) {
+ if (sb.length() != 0) {
+ sb.append("|");
+ }
+ sb.append(regexpFlag.name());
+ }
+ }
+ maybeSet(builder::setFlags, sb.toString());
+ return builder.build();
+ }
+
+ @Override
+ protected RegexOptions createTestModel() {
+ return randomRegexOptions();
+ }
+
+ @Override
+ protected RegexOptions createMutation(RegexOptions original) throws IOException {
+ final RegexOptions.Builder builder = RegexOptions.builder();
+ builder.setMaxDeterminizedStates(randomValueOtherThan(original.getMaxDeterminizedStates(), () -> randomIntBetween(1, 10)));
+ return builder.build();
+ }
+
+ @Override
+ protected RegexOptions readFrom(StreamInput in) throws IOException {
+ return RegexOptions.readRegexOptions(in);
+ }
+
+ public void testIllegalArgument() {
+ final RegexOptions.Builder builder = RegexOptions.builder();
+ try {
+ builder.setMaxDeterminizedStates(-randomIntBetween(1, Integer.MAX_VALUE));
+ fail("max determinized state must be positive");
+ } catch (IllegalArgumentException e) {
+ assertEquals(e.getMessage(), "maxDeterminizedStates must not be negative");
+ }
+ }
+}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java b/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java
index 47b3373342..68cc30f8de 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/completion/WritableTestCase.java
@@ -103,13 +103,13 @@ public abstract class WritableTestCase<M extends Writeable> extends ESTestCase {
private M copyModel(M original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
original.writeTo(output);
- try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), provideNamedWritbaleRegistry())) {
+ try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), provideNamedWritableRegistry())) {
return readFrom(in);
}
}
}
- protected NamedWriteableRegistry provideNamedWritbaleRegistry() {
+ protected NamedWriteableRegistry provideNamedWritableRegistry() {
return new NamedWriteableRegistry();
}
}
diff --git a/core/src/test/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorTests.java b/core/src/test/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorTests.java
index 9bf8447f8d..28c0647881 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorTests.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorTests.java
@@ -296,12 +296,6 @@ public class DirectCandidateGeneratorTests extends ESTestCase{
return generator;
}
- private static <T> void maybeSet(Consumer<T> consumer, T value) {
- if (randomBoolean()) {
- consumer.accept(value);
- }
- }
-
private static DirectCandidateGeneratorBuilder serializedCopy(DirectCandidateGeneratorBuilder original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
original.writeTo(output);