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 25faf568561..eae35d0fb23 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
@@ -70,17 +70,9 @@ import static org.elasticsearch.ElasticsearchException.readStackTrace;
public abstract class StreamInput extends InputStream {
- private final NamedWriteableRegistry namedWriteableRegistry;
-
private Version version = Version.CURRENT;
- protected StreamInput() {
- this.namedWriteableRegistry = new NamedWriteableRegistry();
- }
-
- protected StreamInput(NamedWriteableRegistry namedWriteableRegistry) {
- this.namedWriteableRegistry = namedWriteableRegistry;
- }
+ protected StreamInput() { }
public Version getVersion() {
return this.version;
diff --git a/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java
index e58caea5320..3e91a1c1280 100644
--- a/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java
+++ b/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java
@@ -55,6 +55,7 @@ import static org.elasticsearch.common.unit.TimeValue.parseTimeValue;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.search.suggest.SuggestBuilders.termSuggestion;
+import static org.elasticsearch.search.suggest.term.TermSuggestionBuilder.SuggestMode;
/**
*
@@ -262,7 +263,9 @@ public class RestSearchAction extends BaseRestHandler {
int suggestSize = request.paramAsInt("suggest_size", 5);
String suggestMode = request.param("suggest_mode");
searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion(
- termSuggestion(suggestField).field(suggestField).text(suggestText).size(suggestSize).suggestMode(suggestMode)));
+ termSuggestion(suggestField).field(suggestField)
+ .text(suggestText).size(suggestSize)
+ .suggestMode(SuggestMode.fromString(suggestMode))));
modified = true;
}
return modified;
diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java
index 739b97034bf..1f8e926bf81 100644
--- a/core/src/main/java/org/elasticsearch/search/SearchModule.java
+++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java
@@ -223,6 +223,10 @@ import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.rescore.RescoreBuilder;
import org.elasticsearch.search.suggest.Suggester;
import org.elasticsearch.search.suggest.Suggesters;
+import org.elasticsearch.search.suggest.SuggestionBuilder;
+import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
+import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
+import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -365,6 +369,9 @@ public class SearchModule extends AbstractModule {
protected void configureSuggesters() {
suggesters.bind(binder());
+ namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, TermSuggestionBuilder.PROTOTYPE);
+ namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, PhraseSuggestionBuilder.PROTOTYPE);
+ namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, CompletionSuggestionBuilder.PROTOTYPE);
}
protected void configureHighlighters() {
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/DirectSpellcheckerSettings.java b/core/src/main/java/org/elasticsearch/search/suggest/DirectSpellcheckerSettings.java
index 2b4687c8497..a173781de8b 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/DirectSpellcheckerSettings.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/DirectSpellcheckerSettings.java
@@ -25,16 +25,30 @@ import org.apache.lucene.util.automaton.LevenshteinAutomata;
public class DirectSpellcheckerSettings {
- private SuggestMode suggestMode = SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX;
- private float accuracy = 0.5f;
- private Suggest.Suggestion.Sort sort = Suggest.Suggestion.Sort.SCORE;
- private StringDistance stringDistance = DirectSpellChecker.INTERNAL_LEVENSHTEIN;
- private int maxEdits = LevenshteinAutomata.MAXIMUM_SUPPORTED_DISTANCE;
- private int maxInspections = 5;
- private float maxTermFreq = 0.01f;
- private int prefixLength = 1;
- private int minWordLength = 4;
- private float minDocFreq = 0f;
+ // NB: If this changes, make sure to change the default in TermBuilderSuggester
+ public static SuggestMode DEFAULT_SUGGEST_MODE = SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX;
+ public static float DEFAULT_ACCURACY = 0.5f;
+ // NB: If this changes, make sure to change the default in TermBuilderSuggester
+ public static Suggest.Suggestion.Sort DEFAULT_SORT = Suggest.Suggestion.Sort.SCORE;
+ // NB: If this changes, make sure to change the default in TermBuilderSuggester
+ public static StringDistance DEFAULT_STRING_DISTANCE = DirectSpellChecker.INTERNAL_LEVENSHTEIN;
+ public static int DEFAULT_MAX_EDITS = LevenshteinAutomata.MAXIMUM_SUPPORTED_DISTANCE;
+ public static int DEFAULT_MAX_INSPECTIONS = 5;
+ public static float DEFAULT_MAX_TERM_FREQ = 0.01f;
+ public static int DEFAULT_PREFIX_LENGTH = 1;
+ public static int DEFAULT_MIN_WORD_LENGTH = 4;
+ public static float DEFAULT_MIN_DOC_FREQ = 0f;
+
+ private SuggestMode suggestMode = DEFAULT_SUGGEST_MODE;
+ private float accuracy = DEFAULT_ACCURACY;
+ private Suggest.Suggestion.Sort sort = DEFAULT_SORT;
+ private StringDistance stringDistance = DEFAULT_STRING_DISTANCE;
+ private int maxEdits = DEFAULT_MAX_EDITS;
+ private int maxInspections = DEFAULT_MAX_INSPECTIONS;
+ private float maxTermFreq = DEFAULT_MAX_TERM_FREQ;
+ private int prefixLength = DEFAULT_PREFIX_LENGTH;
+ private int minWordLength = DEFAULT_MIN_WORD_LENGTH;
+ private float minDocFreq = DEFAULT_MIN_DOC_FREQ;
public SuggestMode suggestMode() {
return suggestMode;
@@ -116,4 +130,4 @@ public class DirectSpellcheckerSettings {
this.minDocFreq = minDocFreq;
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java
index 92661b21f18..8037646f152 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestBuilder.java
@@ -19,32 +19,30 @@
package org.elasticsearch.search.suggest;
import org.elasticsearch.action.support.ToXContentToBytes;
+import org.elasticsearch.common.Nullable;
+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.XContentBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Defines how to perform suggesting. This builders allows a number of global options to be specified and
- * an arbitrary number of {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder} instances.
+ * an arbitrary number of {@link SuggestionBuilder} instances.
*
- * Suggesting works by suggesting terms that appear in the suggest text that are similar compared to the terms in
- * provided text. These spelling suggestions are based on several options described in this class.
+ * Suggesting works by suggesting terms/phrases that appear in the suggest text that are similar compared
+ * to the terms in provided text. These suggestions are based on several options described in this class.
*/
-public class SuggestBuilder extends ToXContentToBytes {
+public class SuggestBuilder extends ToXContentToBytes implements Writeable {
- private final String name;
private String globalText;
-
private final List> suggestions = new ArrayList<>();
public SuggestBuilder() {
- this.name = null;
- }
-
- public SuggestBuilder(String name) {
- this.name = name;
}
/**
@@ -54,7 +52,7 @@ public class SuggestBuilder extends ToXContentToBytes {
* The suggest text gets analyzed by the suggest analyzer or the suggest field search analyzer.
* For each analyzed token, suggested terms are suggested if possible.
*/
- public SuggestBuilder setText(String globalText) {
+ public SuggestBuilder setText(@Nullable String globalText) {
this.globalText = globalText;
return this;
}
@@ -77,12 +75,7 @@ public class SuggestBuilder extends ToXContentToBytes {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
- if(name == null) {
- builder.startObject();
- } else {
- builder.startObject(name);
- }
-
+ builder.startObject();
if (globalText != null) {
builder.field("text", globalText);
}
@@ -92,4 +85,45 @@ public class SuggestBuilder extends ToXContentToBytes {
builder.endObject();
return builder;
}
+
+ @Override
+ public SuggestBuilder readFrom(StreamInput in) throws IOException {
+ final SuggestBuilder builder = new SuggestBuilder();
+ builder.globalText = in.readOptionalString();
+ final int size = in.readVInt();
+ for (int i = 0; i < size; i++) {
+ builder.suggestions.add(in.readSuggestion());
+ }
+ return builder;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeOptionalString(globalText);
+ final int size = suggestions.size();
+ out.writeVInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeSuggestion(suggestions.get(i));
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ @SuppressWarnings("unchecked")
+ SuggestBuilder o = (SuggestBuilder)other;
+ return Objects.equals(globalText, o.globalText) &&
+ Objects.equals(suggestions, o.suggestions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(globalText, suggestions);
+ }
+
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/SuggestUtils.java b/core/src/main/java/org/elasticsearch/search/suggest/SuggestUtils.java
index 2509f792ecc..b9f2e29321f 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/SuggestUtils.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestUtils.java
@@ -226,22 +226,22 @@ public final class SuggestUtils {
} else if (parseFieldMatcher.match(fieldName, Fields.SORT)) {
suggestion.sort(SuggestUtils.resolveSort(parser.text()));
} else if (parseFieldMatcher.match(fieldName, Fields.STRING_DISTANCE)) {
- suggestion.stringDistance(SuggestUtils.resolveDistance(parser.text()));
+ suggestion.stringDistance(SuggestUtils.resolveDistance(parser.text()));
} else if (parseFieldMatcher.match(fieldName, Fields.MAX_EDITS)) {
- suggestion.maxEdits(parser.intValue());
+ suggestion.maxEdits(parser.intValue());
if (suggestion.maxEdits() < 1 || suggestion.maxEdits() > LevenshteinAutomata.MAXIMUM_SUPPORTED_DISTANCE) {
throw new IllegalArgumentException("Illegal max_edits value " + suggestion.maxEdits());
}
} else if (parseFieldMatcher.match(fieldName, Fields.MAX_INSPECTIONS)) {
- suggestion.maxInspections(parser.intValue());
+ suggestion.maxInspections(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, Fields.MAX_TERM_FREQ)) {
- suggestion.maxTermFreq(parser.floatValue());
+ suggestion.maxTermFreq(parser.floatValue());
} else if (parseFieldMatcher.match(fieldName, Fields.PREFIX_LENGTH)) {
- suggestion.prefixLength(parser.intValue());
+ suggestion.prefixLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, Fields.MIN_WORD_LENGTH)) {
- suggestion.minQueryLength(parser.intValue());
+ suggestion.minQueryLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, Fields.MIN_DOC_FREQ)) {
- suggestion.minDocFreq(parser.floatValue());
+ suggestion.minDocFreq(parser.floatValue());
} else {
return false;
}
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java
index 7705f2201d1..59304fdd578 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/SuggestionBuilder.java
@@ -53,6 +53,7 @@ public abstract class SuggestionBuilder> extends
protected static final ParseField SHARDSIZE_FIELD = new ParseField("shard_size");
public SuggestionBuilder(String name) {
+ Objects.requireNonNull(name, "Suggester 'name' cannot be null");
this.name = name;
}
@@ -296,4 +297,5 @@ public abstract class SuggestionBuilder> extends
* HashCode for the subclass of {@link SuggestionBuilder} to implement.
*/
protected abstract int doHashCode();
-}
\ No newline at end of file
+
+}
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 1b515e75409..afa0760e704 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
@@ -49,9 +49,11 @@ import java.util.Set;
*/
public class CompletionSuggestionBuilder extends SuggestionBuilder {
+ public static final CompletionSuggestionBuilder PROTOTYPE = new CompletionSuggestionBuilder("_na_"); // name doesn't matter
final static String SUGGESTION_NAME = "completion";
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> queryContexts = new HashMap<>();
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java b/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java
index e2a14c1a2b2..bd318e1a013 100644
--- a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java
+++ b/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestionBuilder.java
@@ -16,13 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
+
package org.elasticsearch.search.suggest.term;
+
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.XContentBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import java.io.IOException;
+import java.util.Locale;
+import java.util.Objects;
+
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_ACCURACY;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_EDITS;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_INSPECTIONS;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_TERM_FREQ;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MIN_DOC_FREQ;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MIN_WORD_LENGTH;
+import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_PREFIX_LENGTH;
/**
* Defines the actual suggest command. Each command uses the global options
@@ -31,18 +44,19 @@ import java.io.IOException;
*/
public class TermSuggestionBuilder extends SuggestionBuilder {
+ public static final TermSuggestionBuilder PROTOTYPE = new TermSuggestionBuilder("_na_"); // name doesn't matter
static final String SUGGESTION_NAME = "term";
- private String suggestMode;
- private Float accuracy;
- private String sort;
- private String stringDistance;
- private Integer maxEdits;
- private Integer maxInspections;
- private Float maxTermFreq;
- private Integer prefixLength;
- private Integer minWordLength;
- private Float minDocFreq;
+ private SuggestMode suggestMode = SuggestMode.MISSING;
+ private Float accuracy = DEFAULT_ACCURACY;
+ private SortBy sort = SortBy.SCORE;
+ private StringDistanceImpl stringDistance = StringDistanceImpl.INTERNAL;
+ private Integer maxEdits = DEFAULT_MAX_EDITS;
+ private Integer maxInspections = DEFAULT_MAX_INSPECTIONS;
+ private Float maxTermFreq = DEFAULT_MAX_TERM_FREQ;
+ private Integer prefixLength = DEFAULT_PREFIX_LENGTH;
+ private Integer minWordLength = DEFAULT_MIN_WORD_LENGTH;
+ private Float minDocFreq = DEFAULT_MIN_DOC_FREQ;
/**
* @param name
@@ -65,11 +79,19 @@ public class TermSuggestionBuilder extends SuggestionBuilder
*/
- public TermSuggestionBuilder suggestMode(String suggestMode) {
+ public TermSuggestionBuilder suggestMode(SuggestMode suggestMode) {
+ Objects.requireNonNull(suggestMode, "suggestMode must not be null");
this.suggestMode = suggestMode;
return this;
}
+ /**
+ * Get the suggest mode setting.
+ */
+ public SuggestMode suggestMode() {
+ return suggestMode;
+ }
+
/**
* s how similar the suggested terms at least need to be compared to the
* original suggest text tokens. A value between 0 and 1 can be specified.
@@ -78,11 +100,21 @@ public class TermSuggestionBuilder extends SuggestionBuilder
* Default is 0.5
*/
- public TermSuggestionBuilder setAccuracy(float accuracy) {
+ public TermSuggestionBuilder accuracy(float accuracy) {
+ if (accuracy < 0.0f || accuracy > 1.0f) {
+ throw new IllegalArgumentException("accuracy must be between 0 and 1");
+ }
this.accuracy = accuracy;
return this;
}
+ /**
+ * Get the accuracy setting.
+ */
+ public Float accuracy() {
+ return accuracy;
+ }
+
/**
* Sets how to sort the suggest terms per suggest text token. Two possible
* values:
@@ -90,19 +122,27 @@ public class TermSuggestionBuilder extends SuggestionBuilderscore
- Sort should first be based on score, then
* document frequency and then the term itself.
* frequency
- Sort should first be based on document
- * frequency, then scotr and then the term itself.
+ * frequency, then score and then the term itself.
*
*
* What the score is depends on the suggester being used.
*/
- public TermSuggestionBuilder sort(String sort) {
+ public TermSuggestionBuilder sort(SortBy sort) {
+ Objects.requireNonNull(sort, "sort must not be null");
this.sort = sort;
return this;
}
+ /**
+ * Get the sort setting.
+ */
+ public SortBy sort() {
+ return sort;
+ }
+
/**
* Sets what string distance implementation to use for comparing how similar
- * suggested terms are. Four possible values can be specified:
+ * suggested terms are. Five possible values can be specified:
*
* internal
- This is the default and is based on
* damerau_levenshtein
, but highly optimized for comparing
@@ -117,32 +157,60 @@ public class TermSuggestionBuilder extends SuggestionBuilder
*/
- public TermSuggestionBuilder stringDistance(String stringDistance) {
+ public TermSuggestionBuilder stringDistance(StringDistanceImpl stringDistance) {
+ Objects.requireNonNull(stringDistance, "stringDistance must not be null");
this.stringDistance = stringDistance;
return this;
}
+ /**
+ * Get the string distance implementation setting.
+ */
+ public StringDistanceImpl stringDistance() {
+ return stringDistance;
+ }
+
/**
* Sets the maximum edit distance candidate suggestions can have in order to
* be considered as a suggestion. Can only be a value between 1 and 2. Any
* other value result in an bad request error being thrown. Defaults to
* 2.
*/
- public TermSuggestionBuilder maxEdits(Integer maxEdits) {
+ public TermSuggestionBuilder maxEdits(int maxEdits) {
+ if (maxEdits < 1 || maxEdits > 2) {
+ throw new IllegalArgumentException("maxEdits must be between 1 and 2");
+ }
this.maxEdits = maxEdits;
return this;
}
+ /**
+ * Get the maximum edit distance setting.
+ */
+ public Integer maxEdits() {
+ return maxEdits;
+ }
+
/**
* A factor that is used to multiply with the size in order to inspect more
* candidate suggestions. Can improve accuracy at the cost of performance.
* Defaults to 5.
*/
- public TermSuggestionBuilder maxInspections(Integer maxInspections) {
+ public TermSuggestionBuilder maxInspections(int maxInspections) {
+ if (maxInspections < 0) {
+ throw new IllegalArgumentException("maxInspections must be positive");
+ }
this.maxInspections = maxInspections;
return this;
}
+ /**
+ * Get the factor for inspecting more candidate suggestions setting.
+ */
+ public Integer maxInspections() {
+ return maxInspections;
+ }
+
/**
* Sets a maximum threshold in number of documents a suggest text token can
* exist in order to be corrected. Can be a relative percentage number (e.g
@@ -155,10 +223,23 @@ public class TermSuggestionBuilder extends SuggestionBuilder 1.0f && maxTermFreq != Math.floor(maxTermFreq)) {
+ throw new IllegalArgumentException("if maxTermFreq is greater than 1, it must not be a fraction");
+ }
this.maxTermFreq = maxTermFreq;
return this;
}
+ /**
+ * Get the maximum term frequency threshold setting.
+ */
+ public Float maxTermFreq() {
+ return maxTermFreq;
+ }
+
/**
* Sets the number of minimal prefix characters that must match in order be
* a candidate suggestion. Defaults to 1. Increasing this number improves
@@ -166,19 +247,39 @@ public class TermSuggestionBuilder extends SuggestionBuilder4.
*/
public TermSuggestionBuilder minWordLength(int minWordLength) {
+ if (minWordLength < 1) {
+ throw new IllegalArgumentException("minWordLength must be greater or equal to 1");
+ }
this.minWordLength = minWordLength;
return this;
}
+ /**
+ * Get the minimum length of a text term to be corrected setting.
+ */
+ public Integer minWordLength() {
+ return minWordLength;
+ }
+
/**
* Sets a minimal threshold in number of documents a suggested term should
* appear in. This can be specified as an absolute number or as a relative
@@ -187,10 +288,24 @@ public class TermSuggestionBuilder extends SuggestionBuilder 1.0f && minDocFreq != Math.floor(minDocFreq)) {
+ throw new IllegalArgumentException("if minDocFreq is greater than 1, it must not be a fraction");
+ }
this.minDocFreq = minDocFreq;
return this;
}
+ /**
+ * Get the minimal threshold for the frequency of a term appearing in the
+ * document set setting.
+ */
+ public Float minDocFreq() {
+ return minDocFreq;
+ }
+
@Override
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
if (suggestMode != null) {
@@ -233,25 +348,149 @@ public class TermSuggestionBuilder extends SuggestionBuilder {
+ /** Only suggest terms in the suggest text that aren't in the index. This is the default. */
+ MISSING,
+ /** Only suggest terms that occur in more docs then the original suggest text term. */
+ POPULAR,
+ /** Suggest any matching suggest terms based on tokens in the suggest text. */
+ ALWAYS;
+
+ protected static SuggestMode PROTOTYPE = MISSING;
+
+ @Override
+ public void writeTo(final StreamOutput out) throws IOException {
+ out.writeVInt(ordinal());
+ }
+
+ @Override
+ public SuggestMode readFrom(final StreamInput in) throws IOException {
+ int ordinal = in.readVInt();
+ if (ordinal < 0 || ordinal >= values().length) {
+ throw new IOException("Unknown SuggestMode ordinal [" + ordinal + "]");
+ }
+ return values()[ordinal];
+ }
+
+ public static SuggestMode fromString(final String str) {
+ Objects.requireNonNull(str, "Input string is null");
+ return valueOf(str.toUpperCase(Locale.ROOT));
+ }
+ }
+
+ /** An enum representing the valid sorting options */
+ public enum SortBy implements Writeable {
+ /** Sort should first be based on score, then document frequency and then the term itself. */
+ SCORE,
+ /** Sort should first be based on document frequency, then score and then the term itself. */
+ FREQUENCY;
+
+ protected static SortBy PROTOTYPE = SCORE;
+
+ @Override
+ public void writeTo(final StreamOutput out) throws IOException {
+ out.writeVInt(ordinal());
+ }
+
+ @Override
+ public SortBy readFrom(final StreamInput in) throws IOException {
+ int ordinal = in.readVInt();
+ if (ordinal < 0 || ordinal >= values().length) {
+ throw new IOException("Unknown SortBy ordinal [" + ordinal + "]");
+ }
+ return values()[ordinal];
+ }
+
+ public static SortBy fromString(final String str) {
+ Objects.requireNonNull(str, "Input string is null");
+ return valueOf(str.toUpperCase(Locale.ROOT));
+ }
+ }
+
+ /** An enum representing the valid string edit distance algorithms for determining suggestions. */
+ public enum StringDistanceImpl implements Writeable {
+ /** This is the default and is based on damerau_levenshtein
, but highly optimized
+ * for comparing string distance for terms inside the index. */
+ INTERNAL,
+ /** String distance algorithm based on Damerau-Levenshtein algorithm. */
+ DAMERAU_LEVENSHTEIN,
+ /** String distance algorithm based on Levenstein edit distance algorithm. */
+ LEVENSTEIN,
+ /** String distance algorithm based on Jaro-Winkler algorithm. */
+ JAROWINKLER,
+ /** String distance algorithm based on character n-grams. */
+ NGRAM;
+
+ protected static StringDistanceImpl PROTOTYPE = INTERNAL;
+
+ @Override
+ public void writeTo(final StreamOutput out) throws IOException {
+ out.writeVInt(ordinal());
+ }
+
+ @Override
+ public StringDistanceImpl readFrom(final StreamInput in) throws IOException {
+ int ordinal = in.readVInt();
+ if (ordinal < 0 || ordinal >= values().length) {
+ throw new IOException("Unknown StringDistanceImpl ordinal [" + ordinal + "]");
+ }
+ return values()[ordinal];
+ }
+
+ public static StringDistanceImpl fromString(final String str) {
+ Objects.requireNonNull(str, "Input string is null");
+ return valueOf(str.toUpperCase(Locale.ROOT));
+ }
+ }
+
}
diff --git a/core/src/test/java/org/elasticsearch/common/io/stream/AbstractWriteableEnumTestCase.java b/core/src/test/java/org/elasticsearch/common/io/stream/AbstractWriteableEnumTestCase.java
new file mode 100644
index 00000000000..b8be6fb1493
--- /dev/null
+++ b/core/src/test/java/org/elasticsearch/common/io/stream/AbstractWriteableEnumTestCase.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.common.io.stream;
+
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * Abstract class offering base functionality for testing @{link Writeable} enums.
+ */
+public abstract class AbstractWriteableEnumTestCase extends ESTestCase {
+
+ /**
+ * Test that the ordinals for the enum are consistent (i.e. the order hasn't changed)
+ * because writing an enum to a stream often uses the ordinal value.
+ */
+ public abstract void testValidOrdinals();
+
+ /**
+ * Test that the conversion from a string to enum is correct.
+ */
+ public abstract void testFromString();
+
+ /**
+ * Test that the correct enum value is produced from the serialized value in the {@link StreamInput}.
+ */
+ public abstract void testReadFrom() throws IOException;
+
+ /**
+ * Test that the correct serialized value is produced from the {@link StreamOutput}.
+ */
+ public abstract void testWriteTo() throws IOException;
+
+ // a convenience method for testing the write of a writeable enum
+ protected static void assertWriteToStream(final Writeable writeableEnum, final int ordinal) throws IOException {
+ try (BytesStreamOutput out = new BytesStreamOutput()) {
+ writeableEnum.writeTo(out);
+ try (StreamInput in = StreamInput.wrap(out.bytes())) {
+ assertThat(in.readVInt(), equalTo(ordinal));
+ }
+ }
+ }
+
+ // a convenience method for testing the read of a writeable enum
+ protected static > void assertReadFromStream(final int ordinal, final Writeable expected) throws IOException {
+ try (BytesStreamOutput out = new BytesStreamOutput()) {
+ out.writeVInt(ordinal);
+ try (StreamInput in = StreamInput.wrap(out.bytes())) {
+ assertThat(expected.readFrom(in), equalTo(expected));
+ }
+ }
+ }
+
+}
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 61f678b5a08..77aada31a46 100644
--- a/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java
+++ b/core/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java
@@ -23,7 +23,9 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
+import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.test.ESTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -46,7 +48,9 @@ public abstract class AbstractSuggestionBuilderTestCase {
+
+ @Override
+ protected TermSuggestionBuilder randomSuggestionBuilder() {
+ TermSuggestionBuilder testBuilder = new TermSuggestionBuilder(randomAsciiOfLength(10));
+ maybeSet(testBuilder::suggestMode, randomSuggestMode());
+ maybeSet(testBuilder::accuracy, randomFloat());
+ maybeSet(testBuilder::sort, randomSort());
+ maybeSet(testBuilder::stringDistance, randomStringDistance());
+ maybeSet(testBuilder::maxEdits, randomIntBetween(1, 2));
+ maybeSet(testBuilder::maxInspections, randomInt(Integer.MAX_VALUE));
+ maybeSet(testBuilder::maxTermFreq, randomFloat());
+ maybeSet(testBuilder::prefixLength, randomInt(Integer.MAX_VALUE));
+ maybeSet(testBuilder::minWordLength, randomInt(Integer.MAX_VALUE));
+ maybeSet(testBuilder::minDocFreq, randomFloat());
+ return testBuilder;
+ }
+
+ private SuggestMode randomSuggestMode() {
+ final int randomVal = randomIntBetween(0, 2);
+ switch (randomVal) {
+ case 0: return SuggestMode.MISSING;
+ case 1: return SuggestMode.POPULAR;
+ case 2: return SuggestMode.ALWAYS;
+ default: throw new IllegalArgumentException("No suggest mode with an ordinal of " + randomVal);
+ }
+ }
+
+ private SortBy randomSort() {
+ int randomVal = randomIntBetween(0, 1);
+ switch (randomVal) {
+ case 0: return SortBy.SCORE;
+ case 1: return SortBy.FREQUENCY;
+ default: throw new IllegalArgumentException("No sort mode with an ordinal of " + randomVal);
+ }
+ }
+
+ private StringDistanceImpl randomStringDistance() {
+ int randomVal = randomIntBetween(0, 4);
+ switch (randomVal) {
+ case 0: return StringDistanceImpl.INTERNAL;
+ case 1: return StringDistanceImpl.DAMERAU_LEVENSHTEIN;
+ case 2: return StringDistanceImpl.LEVENSTEIN;
+ case 3: return StringDistanceImpl.JAROWINKLER;
+ case 4: return StringDistanceImpl.NGRAM;
+ default: throw new IllegalArgumentException("No string distance algorithm with an ordinal of " + randomVal);
+ }
+ }
+
+ @Override
+ protected void mutateSpecificParameters(TermSuggestionBuilder builder) throws IOException {
+ switch (randomIntBetween(0, 9)) {
+ case 0:
+ builder.suggestMode(randomValueOtherThan(builder.suggestMode(), () -> randomSuggestMode()));
+ break;
+ case 1:
+ builder.accuracy(randomValueOtherThan(builder.accuracy(), () -> randomFloat()));
+ break;
+ case 2:
+ builder.sort(randomValueOtherThan(builder.sort(), () -> randomSort()));
+ break;
+ case 3:
+ builder.stringDistance(randomValueOtherThan(builder.stringDistance(), () -> randomStringDistance()));
+ break;
+ case 4:
+ builder.maxEdits(randomValueOtherThan(builder.maxEdits(), () -> randomIntBetween(1, 2)));
+ break;
+ case 5:
+ builder.maxInspections(randomValueOtherThan(builder.maxInspections(), () -> randomInt(Integer.MAX_VALUE)));
+ break;
+ case 6:
+ builder.maxTermFreq(randomValueOtherThan(builder.maxTermFreq(), () -> randomFloat()));
+ break;
+ case 7:
+ builder.prefixLength(randomValueOtherThan(builder.prefixLength(), () -> randomInt(Integer.MAX_VALUE)));
+ break;
+ case 8:
+ builder.minWordLength(randomValueOtherThan(builder.minWordLength(), () -> randomInt(Integer.MAX_VALUE)));
+ break;
+ case 9:
+ builder.minDocFreq(randomValueOtherThan(builder.minDocFreq(), () -> randomFloat()));
+ break;
+ default:
+ break; // do nothing
+ }
+ }
+
+ public void testInvalidParameters() throws IOException {
+ TermSuggestionBuilder builder = new TermSuggestionBuilder(randomAsciiOfLength(10));
+ // test invalid accuracy values
+ try {
+ builder.accuracy(-0.5f);
+ fail("Should not allow accuracy to be set to a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.accuracy(1.1f);
+ fail("Should not allow accuracy to be greater than 1.0.");
+ } catch (IllegalArgumentException e) {
+ }
+ // test invalid max edit distance values
+ try {
+ builder.maxEdits(0);
+ fail("Should not allow maxEdits to be less than 1.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.maxEdits(-1);
+ fail("Should not allow maxEdits to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.maxEdits(3);
+ fail("Should not allow maxEdits to be greater than 2.");
+ } catch (IllegalArgumentException e) {
+ }
+ // test invalid max inspections values
+ try {
+ builder.maxInspections(-1);
+ fail("Should not allow maxInspections to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ // test invalid max term freq values
+ try {
+ builder.maxTermFreq(-0.5f);
+ fail("Should not allow max term freq to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.maxTermFreq(1.5f);
+ fail("If max term freq is greater than 1, it must be a whole number.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.maxTermFreq(2.0f); // this should be allowed
+ } catch (IllegalArgumentException e) {
+ fail("A max term freq greater than 1 that is a whole number should be allowed.");
+ }
+ // test invalid min doc freq values
+ try {
+ builder.minDocFreq(-0.5f);
+ fail("Should not allow min doc freq to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.minDocFreq(1.5f);
+ fail("If min doc freq is greater than 1, it must be a whole number.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.minDocFreq(2.0f); // this should be allowed
+ } catch (IllegalArgumentException e) {
+ fail("A min doc freq greater than 1 that is a whole number should be allowed.");
+ }
+ // test invalid min word length values
+ try {
+ builder.minWordLength(0);
+ fail("A min word length < 1 should not be allowed.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.minWordLength(-1);
+ fail("Should not allow min word length to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ // test invalid prefix length values
+ try {
+ builder.prefixLength(-1);
+ fail("Should not allow prefix length to be a negative value.");
+ } catch (IllegalArgumentException e) {
+ }
+ // test invalid size values
+ try {
+ builder.size(0);
+ fail("Size must be a positive value.");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ builder.size(-1);
+ fail("Size must be a positive value.");
+ } catch (IllegalArgumentException e) {
+ }
+ // null values not allowed for enums
+ try {
+ builder.sort(null);
+ fail("Should not allow setting a null sort value.");
+ } catch (NullPointerException e) {
+ }
+ try {
+ builder.stringDistance(null);
+ fail("Should not allow setting a null string distance value.");
+ } catch (NullPointerException e) {
+ }
+ try {
+ builder.suggestMode(null);
+ fail("Should not allow setting a null suggest mode value.");
+ } catch (NullPointerException e) {
+ }
+ }
+
+ public void testDefaultValuesSet() {
+ TermSuggestionBuilder builder = new TermSuggestionBuilder(randomAsciiOfLength(10));
+ assertThat(builder.accuracy(), notNullValue());
+ assertThat(builder.maxEdits(), notNullValue());
+ assertThat(builder.maxInspections(), notNullValue());
+ assertThat(builder.maxTermFreq(), notNullValue());
+ assertThat(builder.minDocFreq(), notNullValue());
+ assertThat(builder.minWordLength(), notNullValue());
+ assertThat(builder.prefixLength(), notNullValue());
+ assertThat(builder.sort(), notNullValue());
+ assertThat(builder.stringDistance(), notNullValue());
+ assertThat(builder.suggestMode(), notNullValue());
+ }
+
+}
diff --git a/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java b/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java
index 6360a444a23..d846ff47307 100644
--- a/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java
+++ b/modules/lang-mustache/src/test/java/org/elasticsearch/messy/tests/SuggestSearchTests.java
@@ -27,6 +27,8 @@ import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.search.suggest.SuggestBuilders.phraseSuggestion;
import static org.elasticsearch.search.suggest.SuggestBuilders.termSuggestion;
import static org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.candidateGenerator;
+import static org.elasticsearch.search.suggest.SuggestionBuilder.SortBy;
+import static org.elasticsearch.search.suggest.SuggestionBuilder.SuggestMode;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSuggestion;
@@ -100,7 +102,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
refresh();
TermSuggestionBuilder termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("abcd")
.field("text");
logger.info("--> run suggestions with one index");
@@ -114,7 +116,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
index("test_1", "type1", "4", "text", "ab cc");
refresh();
termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("ab cd")
.minWordLength(1)
.field("text");
@@ -141,7 +143,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
refresh();
termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("ab cd")
.minWordLength(1)
.field("text");
@@ -161,7 +163,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("ABCD")
.minWordLength(1)
.field("text");
@@ -241,7 +243,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
assertThat("didn't ask for suggestions but got some", search.getSuggest(), nullValue());
TermSuggestionBuilder termSuggestion = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("abcd")
.field("text")
.size(10);
@@ -316,7 +318,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
assertThat("didn't ask for suggestions but got some", search.getSuggest(), nullValue());
TermSuggestionBuilder termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("abcd")
.field("text");
Suggest suggest = searchSuggest( termSuggest);
@@ -336,7 +338,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
refresh();
TermSuggestionBuilder termSuggest = termSuggestion("test")
- .suggestMode("always") // Always, otherwise the results can vary between requests.
+ .suggestMode(SuggestMode.ALWAYS) // Always, otherwise the results can vary between requests.
.text("abcd")
.field("text");
Suggest suggest = searchSuggest( termSuggest);
@@ -361,13 +363,13 @@ public class SuggestSearchTests extends ESIntegTestCase {
Suggest suggest = searchSuggest(
termSuggestion("size1")
.size(1).text("prefix_abcd").maxTermFreq(10).prefixLength(1).minDocFreq(0)
- .field("field1").suggestMode("always"),
+ .field("field1").suggestMode(SuggestMode.ALWAYS),
termSuggestion("field2")
.field("field2").text("prefix_eeeh prefix_efgh")
- .maxTermFreq(10).minDocFreq(0).suggestMode("always"),
+ .maxTermFreq(10).minDocFreq(0).suggestMode(SuggestMode.ALWAYS),
termSuggestion("accuracy")
- .field("field2").text("prefix_efgh").setAccuracy(1f)
- .maxTermFreq(10).minDocFreq(0).suggestMode("always"));
+ .field("field2").text("prefix_efgh").accuracy(1f)
+ .maxTermFreq(10).minDocFreq(0).suggestMode(SuggestMode.ALWAYS));
assertSuggestion(suggest, 0, "size1", "prefix_aacd");
assertThat(suggest.getSuggestion("field2").getEntries().get(0).getText().string(), equalTo("prefix_eeeh"));
assertSuggestion(suggest, 0, "field2", "prefix_efgh");
@@ -403,15 +405,15 @@ public class SuggestSearchTests extends ESIntegTestCase {
Suggest suggest = searchSuggest( "prefix_abcd",
termSuggestion("size3SortScoreFirst")
- .size(3).minDocFreq(0).field("field1").suggestMode("always"),
+ .size(3).minDocFreq(0).field("field1").suggestMode(SuggestMode.ALWAYS),
termSuggestion("size10SortScoreFirst")
- .size(10).minDocFreq(0).field("field1").suggestMode("always").shardSize(50),
+ .size(10).minDocFreq(0).field("field1").suggestMode(SuggestMode.ALWAYS).shardSize(50),
termSuggestion("size3SortScoreFirstMaxEdits1")
.maxEdits(1)
- .size(10).minDocFreq(0).field("field1").suggestMode("always"),
+ .size(10).minDocFreq(0).field("field1").suggestMode(SuggestMode.ALWAYS),
termSuggestion("size10SortFrequencyFirst")
- .size(10).sort("frequency").shardSize(1000)
- .minDocFreq(0).field("field1").suggestMode("always"));
+ .size(10).sort(SortBy.FREQUENCY).shardSize(1000)
+ .minDocFreq(0).field("field1").suggestMode(SuggestMode.ALWAYS));
// The commented out assertions fail sometimes because suggestions are based off of shard frequencies instead of index frequencies.
assertSuggestion(suggest, 0, "size3SortScoreFirst", "prefix_aacd", "prefix_abcc", "prefix_accd");
@@ -784,7 +786,7 @@ public class SuggestSearchTests extends ESIntegTestCase {
Suggest suggest = searchSuggest( "foobar",
termSuggestion("simple")
- .size(10).minDocFreq(0).field("field1").suggestMode("always"));
+ .size(10).minDocFreq(0).field("field1").suggestMode(SuggestMode.ALWAYS));
ElasticsearchAssertions.assertSuggestionSize(suggest, 0, 3, "simple");
}