Initial refactoring for phrase suggester

Adding initial serialization methods (readFrom, writeTo) to the
PhraseSuggestionBuilder, also adding the base test framework for
serialiazation testing, equals and hashCode. Moving SuggestionBuilder
out of the global SuggestBuilder for better readability.
This commit is contained in:
Christoph Büscher 2016-01-26 09:59:11 +01:00
parent 60180fecf8
commit c00c0fa020
15 changed files with 982 additions and 192 deletions

View File

@ -30,6 +30,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -99,10 +100,10 @@ public final class SuggestRequest extends BroadcastRequest<SuggestRequest> {
} }
/** /**
* set a new source using a {@link org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder} * set a new source using a {@link org.elasticsearch.search.suggest.SuggestionBuilder}
* for completion suggestion lookup * for completion suggestion lookup
*/ */
public SuggestRequest suggest(SuggestBuilder.SuggestionBuilder suggestionBuilder) { public SuggestRequest suggest(SuggestionBuilder suggestionBuilder) {
return suggest(suggestionBuilder.buildAsBytes(Requests.CONTENT_TYPE)); return suggest(suggestionBuilder.buildAsBytes(Requests.CONTENT_TYPE));
} }

View File

@ -27,7 +27,7 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder;
import java.io.IOException; import java.io.IOException;
@ -45,7 +45,7 @@ public class SuggestRequestBuilder extends BroadcastOperationRequestBuilder<Sugg
/** /**
* Add a definition for suggestions to the request * Add a definition for suggestions to the request
*/ */
public <T> SuggestRequestBuilder addSuggestion(SuggestionBuilder<T> suggestion) { public SuggestRequestBuilder addSuggestion(SuggestionBuilder<?> suggestion) {
suggest.addSuggestion(suggestion); suggest.addSuggestion(suggestion);
return this; return this;
} }

View File

@ -38,6 +38,7 @@ import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.search.rescore.RescoreBuilder; import org.elasticsearch.search.rescore.RescoreBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -281,6 +282,14 @@ public abstract class StreamInput extends InputStream {
return null; return null;
} }
@Nullable
public Float readOptionalFloat() throws IOException {
if (readBoolean()) {
return readFloat();
}
return null;
}
@Nullable @Nullable
public Integer readOptionalVInt() throws IOException { public Integer readOptionalVInt() throws IOException {
if (readBoolean()) { if (readBoolean()) {
@ -683,6 +692,13 @@ public abstract class StreamInput extends InputStream {
return readNamedWriteable(RescoreBuilder.class); return readNamedWriteable(RescoreBuilder.class);
} }
/**
* Reads a {@link SuggestionBuilder} from the current stream
*/
public SuggestionBuilder<?> readSuggestion() throws IOException {
return readNamedWriteable(SuggestionBuilder.class);
}
/** /**
* Reads a {@link org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder} from the current stream * Reads a {@link org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder} from the current stream
*/ */

View File

@ -37,6 +37,7 @@ import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.search.rescore.RescoreBuilder; import org.elasticsearch.search.rescore.RescoreBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.joda.time.ReadableInstant; import org.joda.time.ReadableInstant;
import java.io.EOFException; import java.io.EOFException;
@ -230,6 +231,15 @@ public abstract class StreamOutput extends OutputStream {
} }
} }
public void writeOptionalFloat(@Nullable Float floatValue) throws IOException {
if (floatValue == null) {
writeBoolean(false);
} else {
writeBoolean(true);
writeFloat(floatValue);
}
}
public void writeOptionalText(@Nullable Text text) throws IOException { public void writeOptionalText(@Nullable Text text) throws IOException {
if (text == null) { if (text == null) {
writeInt(-1); writeInt(-1);
@ -684,4 +694,11 @@ public abstract class StreamOutput extends OutputStream {
public void writeRescorer(RescoreBuilder<?> rescorer) throws IOException { public void writeRescorer(RescoreBuilder<?> rescorer) throws IOException {
writeNamedWriteable(rescorer); writeNamedWriteable(rescorer);
} }
/**
* Writes a {@link SuggestionBuilder} to the current stream
*/
public void writeSuggestion(SuggestionBuilder suggestion) throws IOException {
writeNamedWriteable(suggestion);
}
} }

View File

@ -42,14 +42,14 @@ public class SuggestBuilder extends ToXContentToBytes {
public SuggestBuilder() { public SuggestBuilder() {
this.name = null; this.name = null;
} }
public SuggestBuilder(String name) { public SuggestBuilder(String name) {
this.name = name; this.name = name;
} }
/** /**
* Sets the text to provide suggestions for. The suggest text is a required option that needs * Sets the text to provide suggestions for. The suggest text is a required option that needs
* to be set either via this setter or via the {@link org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder#setText(String)} method. * to be set either via this setter or via the {@link org.elasticsearch.search.suggest.SuggestionBuilder#text(String)} method.
* <p> * <p>
* The suggest text gets analyzed by the suggest analyzer or the suggest field search analyzer. * 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. * For each analyzed token, suggested terms are suggested if possible.
@ -67,7 +67,7 @@ public class SuggestBuilder extends ToXContentToBytes {
suggestions.add(suggestion); suggestions.add(suggestion);
return this; return this;
} }
/** /**
* Returns all suggestions with the defined names. * Returns all suggestions with the defined names.
*/ */
@ -82,7 +82,7 @@ public class SuggestBuilder extends ToXContentToBytes {
} else { } else {
builder.startObject(name); builder.startObject(name);
} }
if (globalText != null) { if (globalText != null) {
builder.field("text", globalText); builder.field("text", globalText);
} }
@ -92,125 +92,4 @@ public class SuggestBuilder extends ToXContentToBytes {
builder.endObject(); builder.endObject();
return builder; return builder;
} }
public static abstract class SuggestionBuilder<T> extends ToXContentToBytes {
private String name;
private String suggester;
private String text;
private String prefix;
private String regex;
private String field;
private String analyzer;
private Integer size;
private Integer shardSize;
public SuggestionBuilder(String name, String suggester) {
this.name = name;
this.suggester = suggester;
}
/**
* Same as in {@link SuggestBuilder#setText(String)}, but in the suggestion scope.
*/
@SuppressWarnings("unchecked")
public T text(String text) {
this.text = text;
return (T) this;
}
protected void setPrefix(String prefix) {
this.prefix = prefix;
}
protected void setRegex(String regex) {
this.regex = regex;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(name);
if (text != null) {
builder.field("text", text);
}
if (prefix != null) {
builder.field("prefix", prefix);
}
if (regex != null) {
builder.field("regex", regex);
}
builder.startObject(suggester);
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (field != null) {
builder.field("field", field);
}
if (size != null) {
builder.field("size", size);
}
if (shardSize != null) {
builder.field("shard_size", shardSize);
}
builder = innerToXContent(builder, params);
builder.endObject();
builder.endObject();
return builder;
}
protected abstract XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException;
/**
* Sets from what field to fetch the candidate suggestions from. This is an
* required option and needs to be set via this setter or
* {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder#field(String)}
* method
*/
@SuppressWarnings("unchecked")
public T field(String field) {
this.field = field;
return (T)this;
}
/**
* Sets the analyzer to analyse to suggest text with. Defaults to the search
* analyzer of the suggest field.
*/
@SuppressWarnings("unchecked")
public T analyzer(String analyzer) {
this.analyzer = analyzer;
return (T)this;
}
/**
* Sets the maximum suggestions to be returned per suggest text term.
*/
@SuppressWarnings("unchecked")
public T size(int size) {
if (size <= 0) {
throw new IllegalArgumentException("Size must be positive");
}
this.size = size;
return (T)this;
}
/**
* Sets the maximum number of suggested term to be retrieved from each
* individual shard. During the reduce phase the only the top N suggestions
* are returned based on the <code>size</code> option. Defaults to the
* <code>size</code> option.
* <p>
* Setting this to a value higher than the `size` can be useful in order to
* get a more accurate document frequency for suggested terms. Due to the
* fact that terms are partitioned amongst shards, the shard level document
* frequencies of suggestions may not be precise. Increasing this will make
* these document frequencies more precise.
*/
@SuppressWarnings("unchecked")
public T shardSize(Integer shardSize) {
this.shardSize = shardSize;
return (T)this;
}
}
} }

View File

@ -0,0 +1,299 @@
/*
* 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;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
/**
* Base class for the different suggestion implementations.
*/
public abstract class SuggestionBuilder<T extends SuggestionBuilder<T>> extends ToXContentToBytes implements NamedWriteable<T> {
protected final String name;
// TODO this seems mandatory and should be constructor arg
protected String fieldname;
protected String text;
protected String prefix;
protected String regex;
protected String analyzer;
protected Integer size;
protected Integer shardSize;
protected static final ParseField TEXT_FIELD = new ParseField("text");
protected static final ParseField PREFIX_FIELD = new ParseField("prefix");
protected static final ParseField REGEX_FIELD = new ParseField("regex");
protected static final ParseField FIELDNAME_FIELD = new ParseField("field");
protected static final ParseField ANALYZER_FIELD = new ParseField("analyzer");
protected static final ParseField SIZE_FIELD = new ParseField("size");
protected static final ParseField SHARDSIZE_FIELD = new ParseField("shard_size");
public SuggestionBuilder(String name) {
this.name = name;
}
/**
* get the name for this suggestion
*/
public String name() {
return this.name;
}
/**
* Same as in {@link SuggestBuilder#setText(String)}, but in the suggestion scope.
*/
@SuppressWarnings("unchecked")
public T text(String text) {
this.text = text;
return (T) this;
}
/**
* get the text for this suggestion
*/
public String text() {
return this.text;
}
@SuppressWarnings("unchecked")
protected T prefix(String prefix) {
this.prefix = prefix;
return (T) this;
}
/**
* get the prefix for this suggestion
*/
public String prefix() {
return this.prefix;
}
@SuppressWarnings("unchecked")
protected T regex(String regex) {
this.regex = regex;
return (T) this;
}
/**
* get the regex for this suggestion
*/
public String regex() {
return this.regex;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(name);
if (text != null) {
builder.field(TEXT_FIELD.getPreferredName(), text);
}
if (prefix != null) {
builder.field(PREFIX_FIELD.getPreferredName(), prefix);
}
if (regex != null) {
builder.field(REGEX_FIELD.getPreferredName(), regex);
}
builder.startObject(getSuggesterName());
if (analyzer != null) {
builder.field(ANALYZER_FIELD.getPreferredName(), analyzer);
}
if (fieldname != null) {
builder.field(FIELDNAME_FIELD.getPreferredName(), fieldname);
}
if (size != null) {
builder.field(SIZE_FIELD.getPreferredName(), size);
}
if (shardSize != null) {
builder.field(SHARDSIZE_FIELD.getPreferredName(), shardSize);
}
builder = innerToXContent(builder, params);
builder.endObject();
builder.endObject();
return builder;
}
private String getSuggesterName() {
//default impl returns the same as writeable name, but we keep the distinction between the two just to make sure
return getWriteableName();
}
protected abstract XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException;
/**
* Sets from what field to fetch the candidate suggestions from. This is an
* required option and needs to be set via this setter or
* {@link org.elasticsearch.search.suggest.term.TermSuggestionBuilder#field(String)}
* method
*/
@SuppressWarnings("unchecked")
public T field(String field) {
this.fieldname = field;
return (T)this;
}
/**
* get the {@link #field()} parameter
*/
public String field() {
return this.fieldname;
}
/**
* Sets the analyzer to analyse to suggest text with. Defaults to the search
* analyzer of the suggest field.
*/
@SuppressWarnings("unchecked")
public T analyzer(String analyzer) {
this.analyzer = analyzer;
return (T)this;
}
/**
* get the {@link #analyzer()} parameter
*/
public String analyzer() {
return this.analyzer;
}
/**
* Sets the maximum suggestions to be returned per suggest text term.
*/
@SuppressWarnings("unchecked")
public T size(int size) {
if (size <= 0) {
throw new IllegalArgumentException("Size must be positive");
}
this.size = size;
return (T)this;
}
/**
* get the {@link #size()} parameter
*/
public Integer size() {
return this.size;
}
/**
* Sets the maximum number of suggested term to be retrieved from each
* individual shard. During the reduce phase the only the top N suggestions
* are returned based on the <code>size</code> option. Defaults to the
* <code>size</code> option.
* <p>
* Setting this to a value higher than the `size` can be useful in order to
* get a more accurate document frequency for suggested terms. Due to the
* fact that terms are partitioned amongst shards, the shard level document
* frequencies of suggestions may not be precise. Increasing this will make
* these document frequencies more precise.
*/
@SuppressWarnings("unchecked")
public T shardSize(Integer shardSize) {
this.shardSize = shardSize;
return (T)this;
}
/**
* get the {@link #shardSize()} parameter
*/
public Integer shardSize() {
return this.shardSize;
}
@Override
public final T readFrom(StreamInput in) throws IOException {
String name = in.readString();
T suggestionBuilder = doReadFrom(in, name);
suggestionBuilder.fieldname = in.readOptionalString();
suggestionBuilder.text = in.readOptionalString();
suggestionBuilder.prefix = in.readOptionalString();
suggestionBuilder.regex = in.readOptionalString();
suggestionBuilder.analyzer = in.readOptionalString();
suggestionBuilder.size = in.readOptionalVInt();
suggestionBuilder.shardSize = in.readOptionalVInt();
return suggestionBuilder;
}
/**
* Subclass should return a new instance, reading itself from the input string
* @param in the input string to read from
* @param name the name of the suggestion (read from stream by {@link SuggestionBuilder}
*/
protected abstract T doReadFrom(StreamInput in, String name) throws IOException;
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
doWriteTo(out);
out.writeOptionalString(fieldname);
out.writeOptionalString(text);
out.writeOptionalString(prefix);
out.writeOptionalString(regex);
out.writeOptionalString(analyzer);
out.writeOptionalVInt(size);
out.writeOptionalVInt(shardSize);
}
protected abstract void doWriteTo(StreamOutput out) throws IOException;
@Override
public final boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
T other = (T) obj;
return Objects.equals(name, other.name()) &&
Objects.equals(text, other.text()) &&
Objects.equals(prefix, other.prefix()) &&
Objects.equals(regex, other.regex()) &&
Objects.equals(fieldname, other.field()) &&
Objects.equals(analyzer, other.analyzer()) &&
Objects.equals(size, other.size()) &&
Objects.equals(shardSize, other.shardSize()) &&
doEquals(other);
}
/**
* Indicates whether some other {@link SuggestionBuilder} of the same type is "equal to" this one.
*/
protected abstract boolean doEquals(T other);
@Override
public final int hashCode() {
return Objects.hash(name, text, prefix, regex, fieldname, analyzer, size, shardSize, doHashCode());
}
/**
* HashCode for the subclass of {@link SuggestionBuilder} to implement.
*/
protected abstract int doHashCode();
}

View File

@ -22,11 +22,13 @@ import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery;
import org.apache.lucene.util.automaton.Operations; import org.apache.lucene.util.automaton.Operations;
import org.apache.lucene.util.automaton.RegExp; import org.apache.lucene.util.automaton.RegExp;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.RegexpFlag; import org.elasticsearch.index.query.RegexpFlag;
import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext; import org.elasticsearch.search.suggest.completion.context.CategoryQueryContext;
import org.elasticsearch.search.suggest.completion.context.GeoQueryContext; import org.elasticsearch.search.suggest.completion.context.GeoQueryContext;
@ -45,7 +47,7 @@ import java.util.Set;
* are created at index-time and so must be defined in the mapping with the type "completion" before * are created at index-time and so must be defined in the mapping with the type "completion" before
* indexing. * indexing.
*/ */
public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilder<CompletionSuggestionBuilder> { public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSuggestionBuilder> {
final static String SUGGESTION_NAME = "completion"; final static String SUGGESTION_NAME = "completion";
static final ParseField PAYLOAD_FIELD = new ParseField("payload"); static final ParseField PAYLOAD_FIELD = new ParseField("payload");
@ -56,7 +58,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
private final Set<String> payloadFields = new HashSet<>(); private final Set<String> payloadFields = new HashSet<>();
public CompletionSuggestionBuilder(String name) { public CompletionSuggestionBuilder(String name) {
super(name, SUGGESTION_NAME); super(name);
} }
/** /**
@ -255,8 +257,9 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
* Sets the prefix to provide completions for. * Sets the prefix to provide completions for.
* The prefix gets analyzed by the suggest analyzer. * The prefix gets analyzed by the suggest analyzer.
*/ */
@Override
public CompletionSuggestionBuilder prefix(String prefix) { public CompletionSuggestionBuilder prefix(String prefix) {
super.setPrefix(prefix); super.prefix(prefix);
return this; return this;
} }
@ -264,7 +267,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
* Same as {@link #prefix(String)} with fuzziness of <code>fuzziness</code> * Same as {@link #prefix(String)} with fuzziness of <code>fuzziness</code>
*/ */
public CompletionSuggestionBuilder prefix(String prefix, Fuzziness fuzziness) { public CompletionSuggestionBuilder prefix(String prefix, Fuzziness fuzziness) {
super.setPrefix(prefix); super.prefix(prefix);
this.fuzzyOptionsBuilder = new FuzzyOptionsBuilder().setFuzziness(fuzziness); this.fuzzyOptionsBuilder = new FuzzyOptionsBuilder().setFuzziness(fuzziness);
return this; return this;
} }
@ -274,7 +277,7 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
* see {@link FuzzyOptionsBuilder} * see {@link FuzzyOptionsBuilder}
*/ */
public CompletionSuggestionBuilder prefix(String prefix, FuzzyOptionsBuilder fuzzyOptionsBuilder) { public CompletionSuggestionBuilder prefix(String prefix, FuzzyOptionsBuilder fuzzyOptionsBuilder) {
super.setPrefix(prefix); super.prefix(prefix);
this.fuzzyOptionsBuilder = fuzzyOptionsBuilder; this.fuzzyOptionsBuilder = fuzzyOptionsBuilder;
return this; return this;
} }
@ -282,8 +285,9 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
/** /**
* Sets a regular expression pattern for prefixes to provide completions for. * Sets a regular expression pattern for prefixes to provide completions for.
*/ */
@Override
public CompletionSuggestionBuilder regex(String regex) { public CompletionSuggestionBuilder regex(String regex) {
super.setRegex(regex); super.regex(regex);
return this; return this;
} }
@ -362,4 +366,33 @@ public class CompletionSuggestionBuilder extends SuggestBuilder.SuggestionBuilde
} }
return builder; return builder;
} }
@Override
public String getWriteableName() {
return SUGGESTION_NAME;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
// NORELEASE
throw new UnsupportedOperationException();
}
@Override
public CompletionSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException {
// NORELEASE
throw new UnsupportedOperationException();
}
@Override
protected boolean doEquals(CompletionSuggestionBuilder other) {
// NORELEASE
return false;
}
@Override
protected int doHashCode() {
// NORELEASE
return 0;
}
} }

View File

@ -18,10 +18,12 @@
*/ */
package org.elasticsearch.search.suggest.phrase; package org.elasticsearch.search.suggest.phrase;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.script.Template; import org.elasticsearch.script.Template;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,12 +31,18 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
* Defines the actual suggest command for phrase suggestions ( <tt>phrase</tt>). * Defines the actual suggest command for phrase suggestions ( <tt>phrase</tt>).
*/ */
public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSuggestionBuilder> { public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSuggestionBuilder> {
static final String SUGGESTION_NAME = "phrase";
public static final PhraseSuggestionBuilder PROTOTYPE = new PhraseSuggestionBuilder("_na_");
private Float maxErrors; private Float maxErrors;
private String separator; private String separator;
private Float realWordErrorLikelihood; private Float realWordErrorLikelihood;
@ -51,7 +59,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
private Boolean collatePrune; private Boolean collatePrune;
public PhraseSuggestionBuilder(String name) { public PhraseSuggestionBuilder(String name) {
super(name, "phrase"); super(name);
} }
/** /**
@ -67,6 +75,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the {@link #gramSize(int)} parameter
*/
public Integer gramSize() {
return this.gramSize;
}
/** /**
* Sets the maximum percentage of the terms that at most considered to be * Sets the maximum percentage of the terms that at most considered to be
* misspellings in order to form a correction. This method accepts a float * misspellings in order to form a correction. This method accepts a float
@ -81,6 +96,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the maxErrors setting
*/
public Float maxErrors() {
return this.maxErrors;
}
/** /**
* Sets the separator that is used to separate terms in the bigram field. If * Sets the separator that is used to separate terms in the bigram field. If
* not set the whitespace character is used as a separator. * not set the whitespace character is used as a separator.
@ -90,6 +112,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the separator that is used to separate terms in the bigram field.
*/
public String separator() {
return this.separator;
}
/** /**
* Sets the likelihood of a term being a misspelled even if the term exists * Sets the likelihood of a term being a misspelled even if the term exists
* in the dictionary. The default it <tt>0.95</tt> corresponding to 5% or * in the dictionary. The default it <tt>0.95</tt> corresponding to 5% or
@ -100,6 +129,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the {@link #realWordErrorLikelihood(Float)} parameter
*/
public Float realWordErrorLikelihood() {
return this.realWordErrorLikelihood;
}
/** /**
* Sets the confidence level for this suggester. The confidence level * Sets the confidence level for this suggester. The confidence level
* defines a factor applied to the input phrases score which is used as a * defines a factor applied to the input phrases score which is used as a
@ -114,6 +150,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the {@link #confidence()} parameter
*/
public Float confidence() {
return this.confidence;
}
/** /**
* Adds a {@link CandidateGenerator} to this suggester. The * Adds a {@link CandidateGenerator} to this suggester. The
* {@link CandidateGenerator} is used to draw candidates for each individual * {@link CandidateGenerator} is used to draw candidates for each individual
@ -146,6 +189,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the setting for {@link #forceUnigrams()}
*/
public Boolean forceUnigrams() {
return this.forceUnigrams;
}
/** /**
* Sets an explicit smoothing model used for this suggester. The default is * Sets an explicit smoothing model used for this suggester. The default is
* {@link PhraseSuggestionBuilder.StupidBackoff}. * {@link PhraseSuggestionBuilder.StupidBackoff}.
@ -160,6 +210,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the {@link #tokenLimit(int)} parameter
*/
public Integer tokenLimit() {
return this.tokenLimit;
}
/** /**
* Setup highlighting for suggestions. If this is called a highlight field * Setup highlighting for suggestions. If this is called a highlight field
* is returned with suggestions wrapping changed tokens with preTag and postTag. * is returned with suggestions wrapping changed tokens with preTag and postTag.
@ -173,6 +230,20 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* get the pre-tag for the highlighter set with {@link #highlight(String, String)}
*/
public String preTag() {
return this.preTag;
}
/**
* get the post-tag for the highlighter set with {@link #highlight(String, String)}
*/
public String postTag() {
return this.postTag;
}
/** /**
* Sets a query used for filtering out suggested phrases (collation). * Sets a query used for filtering out suggested phrases (collation).
*/ */
@ -189,6 +260,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* gets the query used for filtering out suggested phrases (collation).
*/
public Template collateQuery() {
return this.collateQuery;
}
/** /**
* Sets additional params for collate script * Sets additional params for collate script
*/ */
@ -197,6 +275,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* gets additional params for collate script
*/
public Map<String, Object> collateParams() {
return this.collateParams;
}
/** /**
* Sets whether to prune suggestions after collation * Sets whether to prune suggestions after collation
*/ */
@ -205,6 +290,13 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
return this; return this;
} }
/**
* Gets whether to prune suggestions after collation
*/
public Boolean collatePrune() {
return this.collatePrune;
}
@Override @Override
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
if (realWordErrorLikelihood != null) { if (realWordErrorLikelihood != null) {
@ -428,7 +520,7 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
private Float minDocFreq; private Float minDocFreq;
/** /**
* @param field Sets from what field to fetch the candidate suggestions from. * @param field Sets from what field to fetch the candidate suggestions from.
*/ */
public DirectCandidateGenerator(String field) { public DirectCandidateGenerator(String field) {
super("direct_generator"); super("direct_generator");
@ -655,4 +747,82 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
} }
@Override
public String getWriteableName() {
return SUGGESTION_NAME;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
out.writeOptionalFloat(maxErrors);
out.writeOptionalFloat(realWordErrorLikelihood);
out.writeOptionalFloat(confidence);
out.writeOptionalVInt(gramSize);
// NORELEASE model.writeTo();
out.writeOptionalBoolean(forceUnigrams);
out.writeOptionalVInt(tokenLimit);
out.writeOptionalString(preTag);
out.writeOptionalString(postTag);
out.writeOptionalString(separator);
if (collateQuery != null) {
out.writeBoolean(true);
collateQuery.writeTo(out);
} else {
out.writeBoolean(false);
}
out.writeMap(collateParams);
out.writeOptionalBoolean(collatePrune);
// NORELEASE write Map<String, List<CandidateGenerator>> generators = new HashMap<>();
}
@Override
public PhraseSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException {
PhraseSuggestionBuilder builder = new PhraseSuggestionBuilder(name);
builder.maxErrors = in.readOptionalFloat();
builder.realWordErrorLikelihood = in.readOptionalFloat();
builder.confidence = in.readOptionalFloat();
builder.gramSize = in.readOptionalVInt();
// NORELEASE read model
builder.forceUnigrams = in.readOptionalBoolean();
builder.tokenLimit = in.readOptionalVInt();
builder.preTag = in.readOptionalString();
builder.postTag = in.readOptionalString();
builder.separator = in.readOptionalString();
if (in.readBoolean()) {
builder.collateQuery = Template.readTemplate(in);
}
builder.collateParams = in.readMap();
builder.collatePrune = in.readOptionalBoolean();
// NORELEASE read Map<String, List<CandidateGenerator>> generators;
return builder;
}
@Override
protected boolean doEquals(PhraseSuggestionBuilder other) {
return Objects.equals(maxErrors, other.maxErrors) &&
Objects.equals(separator, other.separator) &&
Objects.equals(realWordErrorLikelihood, other.realWordErrorLikelihood) &&
Objects.equals(confidence, other.confidence) &&
// NORELEASE Objects.equals(generator, other.generator) &&
Objects.equals(gramSize, other.gramSize) &&
// NORELEASE Objects.equals(model, other.model) &&
Objects.equals(forceUnigrams, other.forceUnigrams) &&
Objects.equals(tokenLimit, other.tokenLimit) &&
Objects.equals(preTag, other.preTag) &&
Objects.equals(postTag, other.postTag) &&
Objects.equals(collateQuery, other.collateQuery) &&
Objects.equals(collateParams, other.collateParams) &&
Objects.equals(collatePrune, other.collatePrune);
}
@Override
protected int doHashCode() {
return Objects.hash(maxErrors, separator, realWordErrorLikelihood, confidence,
/** NORELEASE generators, */
gramSize,
/** NORELEASE model, */
forceUnigrams, tokenLimit, preTag, postTag,
collateQuery, collateParams, collatePrune);
}
} }

View File

@ -17,8 +17,10 @@
* under the License. * under the License.
*/ */
package org.elasticsearch.search.suggest.term; package org.elasticsearch.search.suggest.term;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder;
import java.io.IOException; import java.io.IOException;
@ -29,6 +31,8 @@ import java.io.IOException;
*/ */
public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuilder> { public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuilder> {
static final String SUGGESTION_NAME = "term";
private String suggestMode; private String suggestMode;
private Float accuracy; private Float accuracy;
private String sort; private String sort;
@ -39,13 +43,13 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
private Integer prefixLength; private Integer prefixLength;
private Integer minWordLength; private Integer minWordLength;
private Float minDocFreq; private Float minDocFreq;
/** /**
* @param name * @param name
* The name of this suggestion. This is a required parameter. * The name of this suggestion. This is a required parameter.
*/ */
public TermSuggestionBuilder(String name) { public TermSuggestionBuilder(String name) {
super(name, "term"); super(name);
} }
/** /**
@ -221,4 +225,33 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
} }
return builder; return builder;
} }
@Override
public String getWriteableName() {
return SUGGESTION_NAME;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
// NORELEASE
throw new UnsupportedOperationException();
}
@Override
public TermSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException {
// NORELEASE
throw new UnsupportedOperationException();
}
@Override
protected boolean doEquals(TermSuggestionBuilder other) {
// NORELEASE
return false;
}
@Override
protected int doHashCode() {
// NORELEASE
return 0;
}
} }

View File

@ -0,0 +1,186 @@
/*
* 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;
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.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.test.ESTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
public abstract class AbstractSuggestionBuilderTestCase<SB extends SuggestionBuilder<SB>> extends ESTestCase {
private static final int NUMBER_OF_TESTBUILDERS = 20;
private static NamedWriteableRegistry namedWriteableRegistry;
/**
* setup for the whole base test class
*/
@BeforeClass
public static void init() {
namedWriteableRegistry = new NamedWriteableRegistry();
namedWriteableRegistry.registerPrototype(SuggestionBuilder.class, PhraseSuggestionBuilder.PROTOTYPE);
}
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
}
/**
* Test serialization and deserialization of the suggestion builder
*/
public void testSerialization() throws IOException {
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
SB original = randomTestBuilder();
SB deserialized = serializedCopy(original);
assertEquals(deserialized, original);
assertEquals(deserialized.hashCode(), original.hashCode());
assertNotSame(deserialized, original);
}
}
/**
* returns a random suggestion builder, setting the common options randomly
*/
protected SB randomTestBuilder() {
SB randomSuggestion = randomSuggestionBuilder();
maybeSet(randomSuggestion::text, randomAsciiOfLengthBetween(2, 20));
maybeSet(randomSuggestion::prefix, randomAsciiOfLengthBetween(2, 20));
maybeSet(randomSuggestion::regex, randomAsciiOfLengthBetween(2, 20));
maybeSet(randomSuggestion::field, randomAsciiOfLengthBetween(2, 20));
maybeSet(randomSuggestion::analyzer, randomAsciiOfLengthBetween(2, 20));
maybeSet(randomSuggestion::size, randomIntBetween(1, 20));
maybeSet(randomSuggestion::shardSize, randomInt(20));
return randomSuggestion;
}
/**
* create a randomized {@link SuggestBuilder} that is used in further tests
*/
protected abstract SB randomSuggestionBuilder();
/**
* Test equality and hashCode properties
*/
public void testEqualsAndHashcode() throws IOException {
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
SB firstBuilder = randomTestBuilder();
assertFalse("suggestion builder is equal to null", firstBuilder.equals(null));
assertFalse("suggestion builder is equal to incompatible type", firstBuilder.equals(""));
assertTrue("suggestion builder is not equal to self", firstBuilder.equals(firstBuilder));
assertThat("same suggestion builder's hashcode returns different values if called multiple times", firstBuilder.hashCode(),
equalTo(firstBuilder.hashCode()));
assertThat("different suggestion builders should not be equal", mutate(firstBuilder), not(equalTo(firstBuilder)));
SB secondBuilder = serializedCopy(firstBuilder);
assertTrue("suggestion builder is not equal to self", secondBuilder.equals(secondBuilder));
assertTrue("suggestion builder is not equal to its copy", firstBuilder.equals(secondBuilder));
assertTrue("equals is not symmetric", secondBuilder.equals(firstBuilder));
assertThat("suggestion builder copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(firstBuilder.hashCode()));
SB thirdBuilder = serializedCopy(secondBuilder);
assertTrue("suggestion builder is not equal to self", thirdBuilder.equals(thirdBuilder));
assertTrue("suggestion builder is not equal to its copy", secondBuilder.equals(thirdBuilder));
assertThat("suggestion builder copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(thirdBuilder.hashCode()));
assertTrue("equals is not transitive", firstBuilder.equals(thirdBuilder));
assertThat("suggestion builder copy's hashcode is different from original hashcode", firstBuilder.hashCode(), equalTo(thirdBuilder.hashCode()));
assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder));
assertTrue("equals is not symmetric", thirdBuilder.equals(firstBuilder));
}
}
private SB mutate(SB firstBuilder) throws IOException {
SB mutation = serializedCopy(firstBuilder);
assertNotSame(mutation, firstBuilder);
if (randomBoolean()) {
// change one of the common SuggestionBuilder parameters
switch (randomIntBetween(0, 6)) {
case 0:
mutation.text(randomValueOtherThan(mutation.text(), () -> randomAsciiOfLengthBetween(2, 20)));
break;
case 1:
mutation.prefix(randomValueOtherThan(mutation.prefix(), () -> randomAsciiOfLengthBetween(2, 20)));
break;
case 2:
mutation.regex(randomValueOtherThan(mutation.regex(), () -> randomAsciiOfLengthBetween(2, 20)));
break;
case 3:
mutation.field(randomValueOtherThan(mutation.field(), () -> randomAsciiOfLengthBetween(2, 20)));
break;
case 4:
mutation.analyzer(randomValueOtherThan(mutation.analyzer(), () -> randomAsciiOfLengthBetween(2, 20)));
break;
case 5:
mutation.size(randomValueOtherThan(mutation.size(), () -> randomIntBetween(1, 20)));
break;
case 6:
mutation.shardSize(randomValueOtherThan(mutation.shardSize(), () -> randomIntBetween(1, 20)));
break;
}
} else {
mutateSpecificParameters(firstBuilder);
}
return mutation;
}
/**
* take and input {@link SuggestBuilder} and return another one that is different in one aspect (to test non-equality)
*/
protected abstract void mutateSpecificParameters(SB firstBuilder) throws IOException;
@SuppressWarnings("unchecked")
protected SB serializedCopy(SB original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
output.writeSuggestion(original);;
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
return (SB) in.readSuggestion();
}
}
}
protected static <T> void maybeSet(Consumer<T> consumer, T value) {
if (randomBoolean()) {
consumer.accept(value);
}
}
/**
* helper to get a random value in a certain range that's different from the input
*/
protected static <T> T randomValueOtherThan(T input, Supplier<T> randomSupplier) {
T randomValue = null;
do {
randomValue = randomSupplier.get();
} while (randomValue.equals(input));
return randomValue;
}
}

View File

@ -20,6 +20,7 @@ package org.elasticsearch.search.suggest;
import com.carrotsearch.hppc.ObjectLongHashMap; import com.carrotsearch.hppc.ObjectLongHashMap;
import com.carrotsearch.randomizedtesting.generators.RandomStrings; import com.carrotsearch.randomizedtesting.generators.RandomStrings;
import org.apache.lucene.analysis.TokenStreamToAutomaton; import org.apache.lucene.analysis.TokenStreamToAutomaton;
import org.apache.lucene.search.suggest.document.ContextSuggestField; import org.apache.lucene.search.suggest.document.ContextSuggestField;
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs; import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
@ -907,7 +908,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
} }
public void assertSuggestions(String suggestionName, SuggestBuilder.SuggestionBuilder suggestBuilder, String... suggestions) { public void assertSuggestions(String suggestionName, SuggestionBuilder suggestBuilder, String... suggestions) {
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder
).execute().actionGet(); ).execute().actionGet();
assertSuggestions(suggestResponse, suggestionName, suggestions); assertSuggestions(suggestResponse, suggestionName, suggestions);

View File

@ -632,7 +632,7 @@ public class ContextCompletionSuggestSearchIT extends ESIntegTestCase {
assertEquals("Hotel Amsterdam in Berlin", suggestResponse.getSuggest().getSuggestion(suggestionName).iterator().next().getOptions().iterator().next().getText().string()); assertEquals("Hotel Amsterdam in Berlin", suggestResponse.getSuggest().getSuggestion(suggestionName).iterator().next().getOptions().iterator().next().getText().string());
} }
public void assertSuggestions(String suggestionName, SuggestBuilder.SuggestionBuilder suggestBuilder, String... suggestions) { public void assertSuggestions(String suggestionName, SuggestionBuilder suggestBuilder, String... suggestions) {
SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder SuggestResponse suggestResponse = client().prepareSuggest(INDEX).addSuggestion(suggestBuilder
).execute().actionGet(); ).execute().actionGet();
CompletionSuggestSearchIT.assertSuggestions(suggestResponse, suggestionName, suggestions); CompletionSuggestSearchIT.assertSuggestions(suggestResponse, suggestionName, suggestions);

View File

@ -20,6 +20,8 @@ package org.elasticsearch.search.suggest;
import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
@ -31,6 +33,7 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
@ -59,16 +62,7 @@ public class CustomSuggesterSearchIT extends ESIntegTestCase {
String randomField = randomAsciiOfLength(10); String randomField = randomAsciiOfLength(10);
String randomSuffix = randomAsciiOfLength(10); String randomSuffix = randomAsciiOfLength(10);
SuggestBuilder suggestBuilder = new SuggestBuilder(); SuggestBuilder suggestBuilder = new SuggestBuilder();
suggestBuilder.addSuggestion( suggestBuilder.addSuggestion(new CustomSuggestionBuilder("someName", randomField, randomSuffix).text(randomText));
new SuggestBuilder.SuggestionBuilder<SuggestBuilder.SuggestionBuilder>("someName", "custom") {
@Override
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("field", randomField);
builder.field("suffix", randomSuffix);
return builder;
}
}.text(randomText)
);
SearchRequestBuilder searchRequestBuilder = client().prepareSearch("test").setTypes("test").setFrom(0).setSize(1) SearchRequestBuilder searchRequestBuilder = client().prepareSearch("test").setTypes("test").setFrom(0).setSize(1)
.suggest(suggestBuilder); .suggest(suggestBuilder);
@ -83,4 +77,51 @@ public class CustomSuggesterSearchIT extends ESIntegTestCase {
assertThat(suggestions.get(1).getText().string(), is(String.format(Locale.ROOT, "%s-%s-%s-123", randomText, randomField, randomSuffix))); assertThat(suggestions.get(1).getText().string(), is(String.format(Locale.ROOT, "%s-%s-%s-123", randomText, randomField, randomSuffix)));
} }
class CustomSuggestionBuilder extends SuggestionBuilder<CustomSuggestionBuilder> {
private String randomField;
private String randomSuffix;
public CustomSuggestionBuilder(String name, String randomField, String randomSuffix) {
super(name);
this.randomField = randomField;
this.randomSuffix = randomSuffix;
}
@Override
protected XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("field", randomField);
builder.field("suffix", randomSuffix);
return builder;
}
@Override
public String getWriteableName() {
return "custom";
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
out.writeString(randomField);
out.writeString(randomSuffix);
}
@Override
public CustomSuggestionBuilder doReadFrom(StreamInput in, String name) throws IOException {
return new CustomSuggestionBuilder(in.readString(), in.readString(), in.readString());
}
@Override
protected boolean doEquals(CustomSuggestionBuilder other) {
return Objects.equals(randomField, other.randomField) &&
Objects.equals(randomSuffix, other.randomSuffix);
}
@Override
protected int doHashCode() {
return Objects.hash(randomField, randomSuffix);
}
}
} }

View File

@ -0,0 +1,114 @@
/*
* 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.phrase;
import org.elasticsearch.script.Template;
import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class PhraseSuggestionBuilderTests extends AbstractSuggestionBuilderTestCase<PhraseSuggestionBuilder> {
@Override
protected PhraseSuggestionBuilder randomSuggestionBuilder() {
PhraseSuggestionBuilder testBuilder = new PhraseSuggestionBuilder(randomAsciiOfLength(10));
maybeSet(testBuilder::maxErrors, randomFloat());
maybeSet(testBuilder::separator, randomAsciiOfLengthBetween(1, 10));
maybeSet(testBuilder::realWordErrorLikelihood, randomFloat());
maybeSet(testBuilder::confidence, randomFloat());
maybeSet(testBuilder::collatePrune, randomBoolean());
maybeSet(testBuilder::collateQuery, randomAsciiOfLengthBetween(3, 20));
if (randomBoolean()) {
// preTag, postTag
testBuilder.highlight(randomAsciiOfLengthBetween(3, 20), randomAsciiOfLengthBetween(3, 20));
}
maybeSet(testBuilder::gramSize, randomIntBetween(1, 5));
maybeSet(testBuilder::forceUnigrams, randomBoolean());
maybeSet(testBuilder::tokenLimit, randomInt(20));
if (randomBoolean()) {
Map<String, Object> collateParams = new HashMap<>();
collateParams.put(randomAsciiOfLength(5), randomAsciiOfLength(5));
testBuilder.collateParams(collateParams );
}
if (randomBoolean()) {
// NORELEASE add random model
}
if (randomBoolean()) {
// NORELEASE add random generator
}
return testBuilder;
}
@Override
protected void mutateSpecificParameters(PhraseSuggestionBuilder builder) throws IOException {
switch (randomIntBetween(0, 7)) {
case 0:
builder.maxErrors(randomValueOtherThan(builder.maxErrors(), () -> randomFloat()));
break;
case 1:
builder.realWordErrorLikelihood(randomValueOtherThan(builder.realWordErrorLikelihood(), () -> randomFloat()));
break;
case 2:
builder.confidence(randomValueOtherThan(builder.confidence(), () -> randomFloat()));
break;
case 3:
builder.gramSize(randomValueOtherThan(builder.gramSize(), () -> randomIntBetween(1, 5)));
break;
case 4:
builder.tokenLimit(randomValueOtherThan(builder.tokenLimit(), () -> randomInt(20)));
break;
case 5:
builder.separator(randomValueOtherThan(builder.separator(), () -> randomAsciiOfLengthBetween(1, 10)));
break;
case 6:
Template collateQuery = builder.collateQuery();
if (collateQuery != null) {
builder.collateQuery(randomValueOtherThan(collateQuery.getScript(), () -> randomAsciiOfLengthBetween(3, 20)));
} else {
builder.collateQuery(randomAsciiOfLengthBetween(3, 20));
}
break;
case 7:
builder.collatePrune(builder.collatePrune() == null ? randomBoolean() : !builder.collatePrune() );
break;
case 8:
// preTag, postTag
String currentPre = builder.preTag();
if (currentPre != null) {
// simply double both values
builder.highlight(builder.preTag() + builder.preTag(), builder.postTag() + builder.postTag());
} else {
builder.highlight(randomAsciiOfLengthBetween(3, 20), randomAsciiOfLengthBetween(3, 20));
}
break;
case 9:
builder.forceUnigrams(builder.forceUnigrams() == null ? randomBoolean() : ! builder.forceUnigrams());
break;
case 10:
builder.collateParams().put(randomAsciiOfLength(5), randomAsciiOfLength(5));
break;
// TODO mutate random Model && generator
}
}
}

View File

@ -20,43 +20,6 @@
package org.elasticsearch.messy.tests; package org.elasticsearch.messy.tests;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.ReduceSearchPhaseException;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.action.suggest.SuggestRequestBuilder;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder.SuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.DirectCandidateGenerator;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
import static org.elasticsearch.common.settings.Settings.settingsBuilder; import static org.elasticsearch.common.settings.Settings.settingsBuilder;
@ -76,6 +39,43 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.ReduceSearchPhaseException;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.action.suggest.SuggestRequestBuilder;
import org.elasticsearch.action.suggest.SuggestResponse;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder.DirectCandidateGenerator;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
/** /**
* Integration tests for term and phrase suggestions. Many of these tests many requests that vary only slightly from one another. Where * Integration tests for term and phrase suggestions. Many of these tests many requests that vary only slightly from one another. Where
* possible these tests should declare for the first request, make the request, modify the configuration for the next request, make that * possible these tests should declare for the first request, make the request, modify the configuration for the next request, make that