summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/search/suggest
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/src/main/java/org/elasticsearch/search/suggest
parentbbeb09eae7ac3c5d9837bb26eacfac6bba468929 (diff)
initial refactoring of completion suggester
Diffstat (limited to 'core/src/main/java/org/elasticsearch/search/suggest')
-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
11 files changed, 796 insertions, 356 deletions
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;
+}