Building the term suggesters from the builder object

This commit is contained in:
Ali Beyad 2016-02-09 00:25:47 -05:00
parent 4b736d2e0c
commit 73b819bf9b
17 changed files with 202 additions and 87 deletions

View File

@ -39,7 +39,6 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.engine.Engine;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.index.suggest.stats.ShardSuggestMetric;
import org.elasticsearch.indices.IndicesService;
@ -132,7 +131,6 @@ public class TransportSuggestAction extends TransportBroadcastAction<SuggestRequ
protected ShardSuggestResponse shardOperation(ShardSuggestRequest request) {
IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
IndexShard indexShard = indexService.getShard(request.shardId().id());
QueryShardContext shardContext = indexShard.getQueryShardContext();
ShardSuggestMetric suggestMetric = indexShard.getSuggestMetric();
suggestMetric.preSuggest();
long startTime = System.nanoTime();
@ -144,7 +142,7 @@ public class TransportSuggestAction extends TransportBroadcastAction<SuggestRequ
if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
throw new IllegalArgumentException("suggest content missing");
}
final SuggestionSearchContext context = suggestPhase.parseElement().parseInternal(parser, indexShard.getQueryShardContext());
final SuggestionSearchContext context = suggestPhase.parseElement().parseInternal(parser, indexService.newQueryShardContext());
final Suggest result = suggestPhase.execute(context, searcher.searcher());
return new ShardSuggestResponse(request.shardId(), result);
}

View File

@ -22,14 +22,14 @@ import org.apache.lucene.search.spell.DirectSpellChecker;
import org.apache.lucene.search.spell.StringDistance;
import org.apache.lucene.search.spell.SuggestMode;
import org.apache.lucene.util.automaton.LevenshteinAutomata;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
public class DirectSpellcheckerSettings {
// 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;
public static TermSuggestionBuilder.SortBy DEFAULT_SORT = TermSuggestionBuilder.SortBy.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;
@ -41,7 +41,7 @@ public class DirectSpellcheckerSettings {
private SuggestMode suggestMode = DEFAULT_SUGGEST_MODE;
private float accuracy = DEFAULT_ACCURACY;
private Suggest.Suggestion.Sort sort = DEFAULT_SORT;
private TermSuggestionBuilder.SortBy sort = DEFAULT_SORT;
private StringDistance stringDistance = DEFAULT_STRING_DISTANCE;
private int maxEdits = DEFAULT_MAX_EDITS;
private int maxInspections = DEFAULT_MAX_INSPECTIONS;
@ -66,11 +66,11 @@ public class DirectSpellcheckerSettings {
this.accuracy = accuracy;
}
public Suggest.Suggestion.Sort sort() {
public TermSuggestionBuilder.SortBy sort() {
return sort;
}
public void sort(Suggest.Suggestion.Sort sort) {
public void sort(TermSuggestionBuilder.SortBy sort) {
this.sort = sort;
}
@ -118,8 +118,8 @@ public class DirectSpellcheckerSettings {
return minWordLength;
}
public void minQueryLength(int minQueryLength) {
this.minWordLength = minQueryLength;
public void minWordLength(int minWordLength) {
this.minWordLength = minWordLength;
}
public float minDocFreq() {
@ -130,4 +130,20 @@ public class DirectSpellcheckerSettings {
this.minDocFreq = minDocFreq;
}
@Override
public String toString() {
return "[" +
"suggestMode=" + suggestMode +
",sort=" + sort +
",stringDistance=" + stringDistance +
",accuracy=" + accuracy +
",maxEdits=" + maxEdits +
",maxInspections=" + maxInspections +
",maxTermFreq=" + maxTermFreq +
",prefixLength=" + prefixLength +
",minWordLength=" + minWordLength +
",minDocFreq=" + minDocFreq +
"]";
}
}

View File

@ -19,7 +19,6 @@
package org.elasticsearch.search.suggest;
import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
@ -643,39 +642,6 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
}
}
}
public enum Sort {
/**
* Sort should first be based on score.
*/
SCORE((byte) 0x0),
/**
* Sort should first be based on document frequency.
*/
FREQUENCY((byte) 0x1);
private byte id;
private Sort(byte id) {
this.id = id;
}
public byte id() {
return id;
}
public static Sort fromId(byte id) {
if (id == 0) {
return SCORE;
} else if (id == 1) {
return FREQUENCY;
} else {
throw new ElasticsearchException("Illegal suggest sort " + id);
}
}
}
}
@Override

View File

@ -95,6 +95,15 @@ public class SuggestBuilder extends ToXContentToBytes implements Writeable<Sugge
return suggestions;
}
/**
* Returns the possibly null global suggest text that
* should be applied as the text for all suggesters.
*/
@Nullable
public String getGlobalText() {
return globalText;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();

View File

@ -44,7 +44,7 @@ public final class SuggestParseElement implements SearchParseElement {
@Override
public void parse(XContentParser parser, SearchContext context) throws Exception {
SuggestionSearchContext suggestionSearchContext = parseInternal(parser, context.indexShard().getQueryShardContext());
SuggestionSearchContext suggestionSearchContext = parseInternal(parser, context.getQueryShardContext());
context.suggest(suggestionSearchContext);
}

View File

@ -55,6 +55,7 @@ import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import java.io.IOException;
import java.util.Comparator;
import java.util.Locale;
import java.util.Objects;
public final class SuggestUtils {
public static final Comparator<SuggestWord> LUCENE_FREQUENCY = new SuggestWordFrequencyComparator();
@ -172,12 +173,12 @@ public final class SuggestUtils {
}
}
public static Suggest.Suggestion.Sort resolveSort(String sortVal) {
public static TermSuggestionBuilder.SortBy resolveSort(String sortVal) {
sortVal = sortVal.toLowerCase(Locale.US);
if ("score".equals(sortVal)) {
return Suggest.Suggestion.Sort.SCORE;
return TermSuggestionBuilder.SortBy.SCORE;
} else if ("frequency".equals(sortVal)) {
return Suggest.Suggestion.Sort.FREQUENCY;
return TermSuggestionBuilder.SortBy.FREQUENCY;
} else {
throw new IllegalArgumentException("Illegal suggest sort " + sortVal);
}
@ -201,6 +202,28 @@ public final class SuggestUtils {
}
}
public static SuggestMode resolveSuggestMode(TermSuggestionBuilder.SuggestMode suggestMode) {
Objects.requireNonNull(suggestMode, "suggestMode must not be null");
switch (suggestMode) {
case MISSING: return SuggestMode.SUGGEST_WHEN_NOT_IN_INDEX;
case POPULAR: return SuggestMode.SUGGEST_MORE_POPULAR;
case ALWAYS: return SuggestMode.SUGGEST_ALWAYS;
default: throw new IllegalArgumentException("Unknown suggestMode [" + suggestMode + "]");
}
}
public static StringDistance resolveStringDistance(TermSuggestionBuilder.StringDistanceImpl stringDistance) {
Objects.requireNonNull(stringDistance, "stringDistance must not be null");
switch (stringDistance) {
case INTERNAL: return DirectSpellChecker.INTERNAL_LEVENSHTEIN;
case DAMERAU_LEVENSHTEIN: return new LuceneLevenshteinDistance();
case LEVENSTEIN: return new LevensteinDistance();
case JAROWINKLER: return new JaroWinklerDistance();
case NGRAM: return new NGramDistance();
default: throw new IllegalArgumentException("Illegal distance option " + stringDistance);
}
}
public static class Fields {
public static final ParseField STRING_DISTANCE = new ParseField("string_distance");
public static final ParseField SUGGEST_MODE = new ParseField("suggest_mode");
@ -243,7 +266,7 @@ public final class SuggestUtils {
} else if (parseFieldMatcher.match(fieldName, Fields.PREFIX_LENGTH)) {
suggestion.prefixLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, Fields.MIN_WORD_LENGTH)) {
suggestion.minQueryLength(parser.intValue());
suggestion.minWordLength(parser.intValue());
} else if (parseFieldMatcher.match(fieldName, Fields.MIN_DOC_FREQ)) {
suggestion.minDocFreq(parser.floatValue());
} else {

View File

@ -19,7 +19,9 @@
package org.elasticsearch.search.suggest;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.io.stream.NamedWriteable;
@ -194,7 +196,20 @@ public abstract class SuggestionBuilder<T extends SuggestionBuilder<T>> extends
protected abstract SuggestionBuilder<T> innerFromXContent(QueryParseContext parseContext, String name) throws IOException;
protected abstract SuggestionContext build(QueryShardContext context) throws IOException;
public SuggestionContext build(QueryShardContext context, @Nullable String globalText) throws IOException {
SuggestionContext suggestionContext = innerBuild(context);
// copy over common settings to each suggestion builder
SuggestUtils.suggestionToSuggestionContext(this, context.getMapperService(), suggestionContext);
SuggestUtils.verifySuggestion(context.getMapperService(), new BytesRef(globalText), suggestionContext);
suggestionContext.setShardContext(context);
// TODO make field mandatory in the builder, then remove this
if (suggestionContext.getField() == null) {
throw new IllegalArgumentException("The required field option is missing");
}
return suggestionContext;
}
protected abstract SuggestionContext innerBuild(QueryShardContext context) throws IOException;
public String getSuggesterName() {
//default impl returns the same as writeable name, but we keep the distinction between the two just to make sure

View File

@ -127,6 +127,21 @@ public class SuggestionSearchContext {
public QueryShardContext getShardContext() {
return this.shardContext;
}
@Override
public String toString() {
return "[" +
"text=" + text +
",field=" + field +
",prefix=" + prefix +
",regex=" + regex +
",size=" + size +
",shardSize=" + shardSize +
",suggester=" + suggester +
",analyzer=" + analyzer +
",shardContext=" + shardContext +
"]";
}
}
}

View File

@ -379,7 +379,7 @@ public class CompletionSuggestionBuilder extends SuggestionBuilder<CompletionSug
}
@Override
protected SuggestionContext build(QueryShardContext context) throws IOException {
protected SuggestionContext innerBuild(QueryShardContext context) throws IOException {
// NORELEASE
throw new UnsupportedOperationException();
}

View File

@ -381,7 +381,7 @@ public final class DirectCandidateGeneratorBuilder
transferIfNotNull(this.maxInspections, generator::maxInspections);
transferIfNotNull(this.maxTermFreq, generator::maxTermFreq);
transferIfNotNull(this.prefixLength, generator::prefixLength);
transferIfNotNull(this.minWordLength, generator::minQueryLength);
transferIfNotNull(this.minWordLength, generator::minWordLength);
transferIfNotNull(this.minDocFreq, generator::minDocFreq);
return generator;
}
@ -487,4 +487,4 @@ public final class DirectCandidateGeneratorBuilder
Objects.equals(minWordLength, other.minWordLength) &&
Objects.equals(minDocFreq, other.minDocFreq);
}
}
}

View File

@ -903,14 +903,11 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
@Override
public SuggestionContext build(QueryShardContext context) throws IOException {
public SuggestionContext innerBuild(QueryShardContext context) throws IOException {
PhraseSuggestionContext suggestionContext = new PhraseSuggestionContext(PhraseSuggester.PROTOTYPE);
MapperService mapperService = context.getMapperService();
suggestionContext.setShardContext(context);
// copy common fields
SuggestUtils.suggestionToSuggestionContext(this, mapperService, suggestionContext);
suggestionContext.setSeparator(BytesRefs.toBytesRef(this.separator));
suggestionContext.setRealWordErrorLikelihood(this.realWordErrorLikelihood);
suggestionContext.setConfidence(this.confidence);
@ -945,11 +942,6 @@ public final class PhraseSuggestionBuilder extends SuggestionBuilder<PhraseSugge
suggestionContext.setCollatePrune(this.collatePrune);
}
// TODO make field mandatory in the builder, then remove this
if (suggestionContext.getField() == null) {
throw new IllegalArgumentException("The required field option is missing");
}
MappedFieldType fieldType = mapperService.fullName(suggestionContext.getField());
if (fieldType == null) {
throw new IllegalArgumentException("No mapping found for field [" + suggestionContext.getField() + "]");

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.search.suggest.term;
import org.apache.lucene.search.Sort;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
@ -80,12 +81,12 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
}
public static final int TYPE = 1;
private Sort sort;
private TermSuggestionBuilder.SortBy sort;
public TermSuggestion() {
}
public TermSuggestion(String name, int size, Sort sort) {
public TermSuggestion(String name, int size, TermSuggestionBuilder.SortBy sort) {
super(name, size);
this.sort = sort;
}
@ -110,7 +111,7 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
@Override
protected void innerReadFrom(StreamInput in) throws IOException {
super.innerReadFrom(in);
sort = Sort.fromId(in.readByte());
sort = TermSuggestionBuilder.SortBy.fromId(in.readByte());
}
@Override

View File

@ -20,6 +20,7 @@
package org.elasticsearch.search.suggest.term;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
@ -27,6 +28,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.suggest.DirectSpellcheckerSettings;
import org.elasticsearch.search.suggest.SuggestUtils;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
@ -383,6 +386,12 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
return suggestion;
}
@Override
protected SuggestionContext innerBuild(QueryShardContext context) throws IOException {
TermSuggestionContext suggestionContext = new TermSuggestionContext(TermSuggester.PROTOTYPE);
return fillSuggestionContext(suggestionContext);
}
@Override
public String getWriteableName() {
return SUGGESTION_NAME;
@ -438,6 +447,23 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
maxTermFreq, prefixLength, minWordLength, minDocFreq);
}
// Transfers the builder settings to the target TermSuggestionContext
private TermSuggestionContext fillSuggestionContext(TermSuggestionContext context) {
DirectSpellcheckerSettings settings = context.getDirectSpellCheckerSettings();
settings.accuracy(accuracy);
settings.maxEdits(maxEdits);
settings.maxInspections(maxInspections);
settings.maxTermFreq(maxTermFreq);
settings.minDocFreq(minDocFreq);
settings.minWordLength(minWordLength);
settings.prefixLength(prefixLength);
settings.sort(sort);
settings.stringDistance(SuggestUtils.resolveStringDistance(stringDistance));
settings.suggestMode(SuggestUtils.resolveSuggestMode(suggestMode));
return context;
}
/** An enum representing the valid suggest modes. */
public enum SuggestMode implements Writeable<SuggestMode> {
/** Only suggest terms in the suggest text that aren't in the index. This is the default. */
@ -472,12 +498,18 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
/** An enum representing the valid sorting options */
public enum SortBy implements Writeable<SortBy> {
/** Sort should first be based on score, then document frequency and then the term itself. */
SCORE,
SCORE((byte) 0x0),
/** Sort should first be based on document frequency, then score and then the term itself. */
FREQUENCY;
FREQUENCY((byte) 0x1);
protected static SortBy PROTOTYPE = SCORE;
private byte id;
SortBy(byte id) {
this.id = id;
}
@Override
public void writeTo(final StreamOutput out) throws IOException {
out.writeVInt(ordinal());
@ -496,6 +528,20 @@ public class TermSuggestionBuilder extends SuggestionBuilder<TermSuggestionBuild
Objects.requireNonNull(str, "Input string is null");
return valueOf(str.toUpperCase(Locale.ROOT));
}
public byte id() {
return id;
}
public static SortBy fromId(byte id) {
if (id == 0) {
return SCORE;
} else if (id == 1) {
return FREQUENCY;
} else {
throw new ElasticsearchException("Illegal suggest sort " + id);
}
}
}
/** An enum representing the valid string edit distance algorithms for determining suggestions. */

View File

@ -34,4 +34,9 @@ final class TermSuggestionContext extends SuggestionContext {
return settings;
}
}
@Override
public String toString() {
return "SpellcheckerSettings" + settings + ", BaseSettings" + super.toString();
}
}

View File

@ -251,6 +251,7 @@ public abstract class AbstractSuggestionBuilderTestCase<SB extends SuggestionBui
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
SuggestBuilder suggestBuilder = new SuggestBuilder();
suggestBuilder.setText(randomAsciiOfLength(10));
SB suggestionBuilder = randomTestBuilder();
suggestBuilder.addSuggestion(suggestionBuilder);
@ -274,11 +275,17 @@ public abstract class AbstractSuggestionBuilderTestCase<SB extends SuggestionBui
SuggestionSearchContext suggestionSearchContext = parseElement.parseInternal(parser, mockShardContext);
SuggestionContext oldSchoolContext = suggestionSearchContext.suggestions().get(suggestionBuilder.name());
SuggestionContext newSchoolContext = suggestionBuilder.build(mockShardContext);
SuggestionContext newSchoolContext = suggestionBuilder.build(mockShardContext, suggestBuilder.getGlobalText());
assertNotSame(oldSchoolContext, newSchoolContext);
// deep comparison of analyzers is difficult here, but we check they are same class
assertEquals(oldSchoolContext.getAnalyzer().getClass(), newSchoolContext.getAnalyzer().getClass());
if (oldSchoolContext.getAnalyzer() == null) {
assertNull(newSchoolContext.getAnalyzer());
} else if (newSchoolContext.getAnalyzer() == null) {
assertNull(oldSchoolContext.getAnalyzer());
} else {
assertEquals(oldSchoolContext.getAnalyzer().getClass(), newSchoolContext.getAnalyzer().getClass());
}
assertEquals(oldSchoolContext.getField(), newSchoolContext.getField());
// TODO consolidate text/prefix/regex
//assertEquals(oldSchoolContext.getPrefix(), newSchoolContext.getPrefix());

View File

@ -34,8 +34,10 @@ import org.elasticsearch.test.ESIntegTestCase.Scope;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
@ -135,9 +137,11 @@ public class CustomSuggesterSearchIT extends ESIntegTestCase {
}
@Override
protected SuggestionContext build(QueryShardContext context) throws IOException {
// NORELEASE
return null;
protected SuggestionContext innerBuild(QueryShardContext context) throws IOException {
Map<String, Object> options = new HashMap<>();
options.put("field", randomField);
options.put("suffix", randomSuffix);
return new CustomSuggester.CustomSuggestionsContext(new CustomSuggester(), options);
}
}

View File

@ -20,7 +20,11 @@
package org.elasticsearch.search.suggest.term;
import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
import org.elasticsearch.search.suggest.DirectSpellcheckerSettings;
import org.elasticsearch.search.suggest.SuggestionSearchContext.SuggestionContext;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder.SortBy;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder.StringDistanceImpl;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder.SuggestMode;
import java.io.IOException;
@ -31,20 +35,9 @@ import static org.hamcrest.Matchers.notNullValue;
*/
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
}
/**
* creates random suggestion builder, renders it to xContent and back to new instance that should be equal to original
*/
@Override
public void testBuild() throws IOException {
// skip for now
public void testFromXContent() {
// NORELEASE : remove this when TermSuggestionBuilder's fromXContent is in
}
@Override
@ -261,7 +254,32 @@ public class TermSuggestionBuilderTests extends AbstractSuggestionBuilderTestCas
@Override
protected void assertSuggestionContext(SuggestionContext oldSuggestion, SuggestionContext newSuggestion) {
// put assertions on TermSuggestionContext here
@SuppressWarnings("unchecked")
TermSuggestionContext oldContext = (TermSuggestionContext) oldSuggestion;
@SuppressWarnings("unchecked")
TermSuggestionContext newContext = (TermSuggestionContext) newSuggestion;
assertSpellcheckerSettings(oldContext.getDirectSpellCheckerSettings(), newContext.getDirectSpellCheckerSettings());
}
private void assertSpellcheckerSettings(DirectSpellcheckerSettings oldSettings, DirectSpellcheckerSettings newSettings) {
final double delta = 0.0d;
// make sure the objects aren't the same
assertNotSame(oldSettings, newSettings);
// make sure the objects aren't null
assertNotNull(oldSettings);
assertNotNull(newSettings);
// and now, make sure they are equal..
assertEquals(oldSettings.accuracy(), newSettings.accuracy(), delta);
assertEquals(oldSettings.maxEdits(), newSettings.maxEdits());
assertEquals(oldSettings.maxInspections(), newSettings.maxInspections());
assertEquals(oldSettings.maxTermFreq(), newSettings.maxTermFreq(), delta);
assertEquals(oldSettings.minDocFreq(), newSettings.minDocFreq(), delta);
assertEquals(oldSettings.minWordLength(), newSettings.minWordLength());
assertEquals(oldSettings.prefixLength(), newSettings.prefixLength());
assertEquals(oldSettings.sort(), newSettings.sort());
assertEquals(oldSettings.stringDistance().getClass(), newSettings.stringDistance().getClass());
assertEquals(oldSettings.suggestMode().getClass(), newSettings.suggestMode().getClass());
}
}