Enforce Completion Context Limit (#38675) (#39075)

This change adds a limit to the number of completion contexts that a completion field can define.

Closes #32741
This commit is contained in:
Jim Ferenczi 2019-02-19 08:52:24 +01:00 committed by GitHub
parent 59e9a0f4f4
commit 199155f5fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 0 deletions

View File

@ -72,3 +72,10 @@ or `quadtree`. This will ensure compatibility with previously created indexes.
The following type parameters are deprecated for the `geo_shape` field type: `tree`, The following type parameters are deprecated for the `geo_shape` field type: `tree`,
`precision`, `tree_levels`, `distance_error_pct`, `points_only`, and `strategy`. They `precision`, `tree_levels`, `distance_error_pct`, `points_only`, and `strategy`. They
will be removed in a future version. will be removed in a future version.
[float]
==== Limiting the number of completion contexts
The maximum allowed number of completion contexts in a mapping will be limited
to 10 in the next major version. Completion fields that define more than 10
contexts in a mapping will log a deprecation warning in this version.

View File

@ -18,6 +18,7 @@
*/ */
package org.elasticsearch.index.mapper; package org.elasticsearch.index.mapper;
import org.apache.logging.log4j.LogManager;
import org.apache.lucene.codecs.PostingsFormat; import org.apache.lucene.codecs.PostingsFormat;
import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
@ -31,8 +32,10 @@ import org.apache.lucene.search.suggest.document.PrefixCompletionQuery;
import org.apache.lucene.search.suggest.document.RegexCompletionQuery; import org.apache.lucene.search.suggest.document.RegexCompletionQuery;
import org.apache.lucene.search.suggest.document.SuggestField; import org.apache.lucene.search.suggest.document.SuggestField;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
@ -85,6 +88,11 @@ import static org.elasticsearch.index.mapper.TypeParsers.parseMultiField;
public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapperParser { public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapperParser {
public static final String CONTENT_TYPE = "completion"; public static final String CONTENT_TYPE = "completion";
/**
* Maximum allowed number of completion contexts in a mapping.
*/
static final int COMPLETION_CONTEXTS_LIMIT = 10;
public static class Defaults { public static class Defaults {
public static final MappedFieldType FIELD_TYPE = new CompletionFieldType(); public static final MappedFieldType FIELD_TYPE = new CompletionFieldType();
static { static {
@ -354,6 +362,8 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
private boolean preserveSeparators = Defaults.DEFAULT_PRESERVE_SEPARATORS; private boolean preserveSeparators = Defaults.DEFAULT_PRESERVE_SEPARATORS;
private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS; private boolean preservePositionIncrements = Defaults.DEFAULT_POSITION_INCREMENTS;
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(Builder.class));
/** /**
* @param name of the completion field to build * @param name of the completion field to build
*/ */
@ -397,6 +407,7 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
@Override @Override
public CompletionFieldMapper build(BuilderContext context) { public CompletionFieldMapper build(BuilderContext context) {
checkCompletionContextsLimit(context);
setupFieldType(context); setupFieldType(context);
CompletionFieldType completionFieldType = (CompletionFieldType) this.fieldType; CompletionFieldType completionFieldType = (CompletionFieldType) this.fieldType;
completionFieldType.setContextMappings(contextMappings); completionFieldType.setContextMappings(contextMappings);
@ -405,6 +416,15 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
return new CompletionFieldMapper(name, this.fieldType, context.indexSettings(), return new CompletionFieldMapper(name, this.fieldType, context.indexSettings(),
multiFieldsBuilder.build(this, context), copyTo, maxInputLength); multiFieldsBuilder.build(this, context), copyTo, maxInputLength);
} }
private void checkCompletionContextsLimit(BuilderContext context) {
if (this.contextMappings != null && this.contextMappings.size() > COMPLETION_CONTEXTS_LIMIT) {
deprecationLogger.deprecated("You have defined more than [" + COMPLETION_CONTEXTS_LIMIT + "] completion contexts" +
" in the mapping for index [" + context.indexSettings().get(IndexMetaData.SETTING_INDEX_PROVIDED_NAME) + "]. " +
"The maximum allowed number of completion contexts in a mapping will be limited to " +
"[" + COMPLETION_CONTEXTS_LIMIT + "] starting in version [8.0].");
}
}
} }
private int maxInputLength; private int maxInputLength;

View File

@ -54,6 +54,7 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.mapper.CompletionFieldMapper.COMPLETION_CONTEXTS_LIMIT;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -908,6 +909,27 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
assertThat(e.getMessage(), containsString("name cannot be empty string")); assertThat(e.getMessage(), containsString("name cannot be empty string"));
} }
public void testLimitOfContextMappings() throws Throwable {
final String index = "test";
XContentBuilder mappingBuilder = XContentFactory.jsonBuilder().startObject().startObject("properties")
.startObject("suggest").field("type", "completion").startArray("contexts");
for (int i = 0; i < COMPLETION_CONTEXTS_LIMIT + 1; i++) {
mappingBuilder.startObject();
mappingBuilder.field("name", Integer.toString(i));
mappingBuilder.field("type", "category");
mappingBuilder.endObject();
}
mappingBuilder.endArray().endObject().endObject().endObject();
String mappings = Strings.toString(mappingBuilder);
DocumentMapper mapper = createIndex(index).mapperService().documentMapperParser()
.parse("type1", new CompressedXContent(mappings));
assertWarnings("You have defined more than [" + COMPLETION_CONTEXTS_LIMIT + "] completion contexts" +
" in the mapping for index [test]. The maximum allowed number of completion contexts in a mapping will be limited to " +
"[" + COMPLETION_CONTEXTS_LIMIT + "] starting in version [8.0].");
}
private Matcher<IndexableField> suggestField(String value) { private Matcher<IndexableField> suggestField(String value) {
return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)), return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)),
Matchers.instanceOf(SuggestField.class)); Matchers.instanceOf(SuggestField.class));