Implements the ability to go from x-content to a term suggester.

This commit is contained in:
Ali Beyad 2016-02-04 18:05:53 -05:00
parent fab3b5568f
commit 7ca72542b3
7 changed files with 108 additions and 64 deletions

View File

@ -265,7 +265,7 @@ public class RestSearchAction extends BaseRestHandler {
searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion( searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion(
termSuggestion(suggestField).field(suggestField) termSuggestion(suggestField).field(suggestField)
.text(suggestText).size(suggestSize) .text(suggestText).size(suggestSize)
.suggestMode(SuggestMode.fromString(suggestMode)))); .suggestMode(SuggestMode.resolve(suggestMode))));
modified = true; modified = true;
} }
return modified; return modified;

View File

@ -49,6 +49,7 @@ import org.elasticsearch.index.analysis.ShingleTokenFilterFactory;
import org.elasticsearch.index.analysis.TokenFilterFactory; import org.elasticsearch.index.analysis.TokenFilterFactory;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext; import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Comparator; import java.util.Comparator;

View File

@ -19,10 +19,18 @@
package org.elasticsearch.search.suggest.term; package org.elasticsearch.search.suggest.term;
import org.apache.lucene.search.spell.DirectSpellChecker;
import org.apache.lucene.search.spell.JaroWinklerDistance;
import org.apache.lucene.search.spell.LevensteinDistance;
import org.apache.lucene.search.spell.LuceneLevenshteinDistance;
import org.apache.lucene.search.spell.NGramDistance;
import org.apache.lucene.search.spell.StringDistance;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.search.suggest.SuggestionBuilder; import org.elasticsearch.search.suggest.SuggestionBuilder;
@ -37,6 +45,16 @@ import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAUL
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MIN_DOC_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_MIN_WORD_LENGTH;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_PREFIX_LENGTH; import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_PREFIX_LENGTH;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.ACCURACY;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.MAX_EDITS;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.MAX_INSPECTIONS;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.MAX_TERM_FREQ;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.MIN_DOC_FREQ;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.MIN_WORD_LENGTH;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.PREFIX_LENGTH;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.SORT;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.STRING_DISTANCE;
import static org.elasticsearch.search.suggest.SuggestUtils.Fields.SUGGEST_MODE;
/** /**
* Defines the actual suggest command. Each command uses the global options * Defines the actual suggest command. Each command uses the global options
@ -309,42 +327,64 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
@Override @Override
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
if (suggestMode != null) { builder.field(SUGGEST_MODE.getPreferredName(), suggestMode);
builder.field("suggest_mode", suggestMode); builder.field(ACCURACY.getPreferredName(), accuracy);
} builder.field(SORT.getPreferredName(), sort);
if (accuracy != null) { builder.field(STRING_DISTANCE.getPreferredName(), stringDistance);
builder.field("accuracy", accuracy); builder.field(MAX_EDITS.getPreferredName(), maxEdits);
} builder.field(MAX_INSPECTIONS.getPreferredName(), maxInspections);
if (sort != null) { builder.field(MAX_TERM_FREQ.getPreferredName(), maxTermFreq);
builder.field("sort", sort); builder.field(PREFIX_LENGTH.getPreferredName(), prefixLength);
} builder.field(MIN_WORD_LENGTH.getPreferredName(), minWordLength);
if (stringDistance != null) { builder.field(MIN_DOC_FREQ.getPreferredName(), minDocFreq);
builder.field("string_distance", stringDistance);
}
if (maxEdits != null) {
builder.field("max_edits", maxEdits);
}
if (maxInspections != null) {
builder.field("max_inspections", maxInspections);
}
if (maxTermFreq != null) {
builder.field("max_term_freq", maxTermFreq);
}
if (prefixLength != null) {
builder.field("prefix_length", prefixLength);
}
if (minWordLength != null) {
builder.field("min_word_length", minWordLength);
}
if (minDocFreq != null) {
builder.field("min_doc_freq", minDocFreq);
}
return builder; return builder;
} }
@Override @Override
protected TermSuggestionBuilder innerFromXContent(QueryParseContext parseContext, String name) throws IOException { protected TermSuggestionBuilder innerFromXContent(QueryParseContext parseContext, String name) throws IOException {
return null; XContentParser parser = parseContext.parser();
TermSuggestionBuilder suggestion = new TermSuggestionBuilder(name);
ParseFieldMatcher parseFieldMatcher = parseContext.parseFieldMatcher();
XContentParser.Token token;
String fieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
} else if (token.isValue()) {
if (parseFieldMatcher.match(fieldName, SuggestionBuilder.ANALYZER_FIELD)) {
suggestion.analyzer(parser.text());
} else if (parseFieldMatcher.match(fieldName, SuggestionBuilder.FIELDNAME_FIELD)) {
suggestion.field(parser.text());
} else if (parseFieldMatcher.match(fieldName, SuggestionBuilder.SIZE_FIELD)) {
suggestion.size(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, SuggestionBuilder.SHARDSIZE_FIELD)) {
suggestion.shardSize(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, SUGGEST_MODE)) {
suggestion.suggestMode(SuggestMode.resolve(parser.text()));
} else if (parseFieldMatcher.match(fieldName, ACCURACY)) {
suggestion.accuracy(parser.floatValue());
} else if (parseFieldMatcher.match(fieldName, SORT)) {
suggestion.sort(SortBy.resolve(parser.text()));
} else if (parseFieldMatcher.match(fieldName, STRING_DISTANCE)) {
suggestion.stringDistance(StringDistanceImpl.resolve(parser.text()));
} else if (parseFieldMatcher.match(fieldName, MAX_EDITS)) {
suggestion.maxEdits(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, MAX_INSPECTIONS)) {
suggestion.maxInspections(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, MAX_TERM_FREQ)) {
suggestion.maxTermFreq(parser.floatValue());
} else if (parseFieldMatcher.match(fieldName, PREFIX_LENGTH)) {
suggestion.prefixLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, MIN_WORD_LENGTH)) {
suggestion.minWordLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, MIN_DOC_FREQ)) {
suggestion.minDocFreq(parser.floatValue());
}
} else {
throw new IllegalArgumentException("suggester[term] doesn't support field [" + fieldName + "]");
}
}
return suggestion;
} }
@Override @Override
@ -402,7 +442,6 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
maxTermFreq, prefixLength, minWordLength, minDocFreq); maxTermFreq, prefixLength, minWordLength, minDocFreq);
} }
/** An enum representing the valid suggest modes. */ /** An enum representing the valid suggest modes. */
public enum SuggestMode implements Writeable<SuggestMode> { public enum SuggestMode implements Writeable<SuggestMode> {
/** Only suggest terms in the suggest text that aren't in the index. This is the default. */ /** Only suggest terms in the suggest text that aren't in the index. This is the default. */
@ -428,7 +467,7 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
return values()[ordinal]; return values()[ordinal];
} }
public static SuggestMode fromString(final String str) { public static SuggestMode resolve(final String str) {
Objects.requireNonNull(str, "Input string is null"); Objects.requireNonNull(str, "Input string is null");
return valueOf(str.toUpperCase(Locale.ROOT)); return valueOf(str.toUpperCase(Locale.ROOT));
} }
@ -457,7 +496,7 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
return values()[ordinal]; return values()[ordinal];
} }
public static SortBy fromString(final String str) { public static SortBy resolve(final String str) {
Objects.requireNonNull(str, "Input string is null"); Objects.requireNonNull(str, "Input string is null");
return valueOf(str.toUpperCase(Locale.ROOT)); return valueOf(str.toUpperCase(Locale.ROOT));
} }
@ -493,9 +532,21 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
return values()[ordinal]; return values()[ordinal];
} }
public static StringDistanceImpl fromString(final String str) { public static StringDistanceImpl resolve(final String str) {
Objects.requireNonNull(str, "Input string is null"); Objects.requireNonNull(str, "Input string is null");
return valueOf(str.toUpperCase(Locale.ROOT)); final String distanceVal = str.toLowerCase(Locale.US);
switch (distanceVal) {
case "internal":
return INTERNAL;
case "damerau_levenshtein":
case "damerauLevenshtein":
return DAMERAU_LEVENSHTEIN;
case "levenstein":
return LEVENSTEIN;
case "ngram":
return NGRAM;
default: throw new IllegalArgumentException("Illegal distance option " + str);
}
} }
} }

View File

@ -39,17 +39,17 @@ public class SortByTests extends AbstractWriteableEnumTestCase {
@Override @Override
public void testFromString() { public void testFromString() {
assertThat(SortBy.fromString("score"), equalTo(SortBy.SCORE)); assertThat(SortBy.resolve("score"), equalTo(SortBy.SCORE));
assertThat(SortBy.fromString("frequency"), equalTo(SortBy.FREQUENCY)); assertThat(SortBy.resolve("frequency"), equalTo(SortBy.FREQUENCY));
final String doesntExist = "doesnt_exist"; final String doesntExist = "doesnt_exist";
try { try {
SortBy.fromString(doesntExist); SortBy.resolve(doesntExist);
fail("SortBy should not have an element " + doesntExist); fail("SortBy should not have an element " + doesntExist);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
} }
try { try {
SortBy.fromString(null); SortBy.resolve(null);
fail("SortBy.fromString on a null value should throw an exception."); fail("SortBy.resolve on a null value should throw an exception.");
} catch (NullPointerException e) { } catch (NullPointerException e) {
assertThat(e.getMessage(), equalTo("Input string is null")); assertThat(e.getMessage(), equalTo("Input string is null"));
} }

View File

@ -42,20 +42,20 @@ public class StringDistanceImplTests extends AbstractWriteableEnumTestCase {
@Override @Override
public void testFromString() { public void testFromString() {
assertThat(StringDistanceImpl.fromString("internal"), equalTo(StringDistanceImpl.INTERNAL)); assertThat(StringDistanceImpl.resolve("internal"), equalTo(StringDistanceImpl.INTERNAL));
assertThat(StringDistanceImpl.fromString("damerau_levenshtein"), equalTo(StringDistanceImpl.DAMERAU_LEVENSHTEIN)); assertThat(StringDistanceImpl.resolve("damerau_levenshtein"), equalTo(StringDistanceImpl.DAMERAU_LEVENSHTEIN));
assertThat(StringDistanceImpl.fromString("levenstein"), equalTo(StringDistanceImpl.LEVENSTEIN)); assertThat(StringDistanceImpl.resolve("levenstein"), equalTo(StringDistanceImpl.LEVENSTEIN));
assertThat(StringDistanceImpl.fromString("jarowinkler"), equalTo(StringDistanceImpl.JAROWINKLER)); assertThat(StringDistanceImpl.resolve("jarowinkler"), equalTo(StringDistanceImpl.JAROWINKLER));
assertThat(StringDistanceImpl.fromString("ngram"), equalTo(StringDistanceImpl.NGRAM)); assertThat(StringDistanceImpl.resolve("ngram"), equalTo(StringDistanceImpl.NGRAM));
final String doesntExist = "doesnt_exist"; final String doesntExist = "doesnt_exist";
try { try {
StringDistanceImpl.fromString(doesntExist); StringDistanceImpl.resolve(doesntExist);
fail("StringDistanceImpl should not have an element " + doesntExist); fail("StringDistanceImpl should not have an element " + doesntExist);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
} }
try { try {
StringDistanceImpl.fromString(null); StringDistanceImpl.resolve(null);
fail("StringDistanceImpl.fromString on a null value should throw an exception."); fail("StringDistanceImpl.resolve on a null value should throw an exception.");
} catch (NullPointerException e) { } catch (NullPointerException e) {
assertThat(e.getMessage(), equalTo("Input string is null")); assertThat(e.getMessage(), equalTo("Input string is null"));
} }

View File

@ -40,18 +40,18 @@ public class SuggestModeTests extends AbstractWriteableEnumTestCase {
@Override @Override
public void testFromString() { public void testFromString() {
assertThat(SuggestMode.fromString("missing"), equalTo(SuggestMode.MISSING)); assertThat(SuggestMode.resolve("missing"), equalTo(SuggestMode.MISSING));
assertThat(SuggestMode.fromString("popular"), equalTo(SuggestMode.POPULAR)); assertThat(SuggestMode.resolve("popular"), equalTo(SuggestMode.POPULAR));
assertThat(SuggestMode.fromString("always"), equalTo(SuggestMode.ALWAYS)); assertThat(SuggestMode.resolve("always"), equalTo(SuggestMode.ALWAYS));
final String doesntExist = "doesnt_exist"; final String doesntExist = "doesnt_exist";
try { try {
SuggestMode.fromString(doesntExist); SuggestMode.resolve(doesntExist);
fail("SuggestMode should not have an element " + doesntExist); fail("SuggestMode should not have an element " + doesntExist);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
} }
try { try {
SuggestMode.fromString(null); SuggestMode.resolve(null);
fail("SuggestMode.fromString on a null value should throw an exception."); fail("SuggestMode.resolve on a null value should throw an exception.");
} catch (NullPointerException e) { } catch (NullPointerException e) {
assertThat(e.getMessage(), equalTo("Input string is null")); assertThat(e.getMessage(), equalTo("Input string is null"));
} }

View File

@ -33,14 +33,6 @@ import static org.hamcrest.Matchers.notNullValue;
*/ */
public class TermSuggestionBuilderTests extends AbstractSuggestionBuilderTestCase<TermSuggestionBuilder> { public class TermSuggestionBuilderTests extends AbstractSuggestionBuilderTestCase<TermSuggestionBuilder> {
/**
* creates random suggestion builder, renders it to xContent and back to new instance that should be equal to original
*/
@Override
public void testFromXContent() throws IOException {
// skip for now
}
@Override @Override
protected TermSuggestionBuilder randomSuggestionBuilder() { protected TermSuggestionBuilder randomSuggestionBuilder() {
TermSuggestionBuilder testBuilder = new TermSuggestionBuilder(randomAsciiOfLength(10)); TermSuggestionBuilder testBuilder = new TermSuggestionBuilder(randomAsciiOfLength(10));