Browse Source

Enforce Completion Context Limit (#38675)

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

Closes #32741
Tamara Braun 6 years ago
parent
commit
c1ab8218f1

+ 16 - 0
docs/reference/migration/migrate_8_0.asciidoc

@@ -13,4 +13,20 @@ coming[8.0.0]
 
 * <<breaking_80_mappings_changes>>
 
+[float]
+=== Indices created before 7.0
+
+Elasticsearch 8.0 can read indices created in version 7.0 or above.  An
+Elasticsearch 8.0 node will not start in the presence of indices created in a
+version of Elasticsearch before 7.0.
+
+[IMPORTANT]
+.Reindex indices from Elasticsearch 6.x or before
+=========================================
+
+Indices created in Elasticsearch 6.x or before will need to be reindexed with
+Elasticsearch 7.x in order to be readable by Elasticsearch 8.x.
+
+=========================================
+
 include::migrate_8_0/mappings.asciidoc[]

+ 7 - 1
docs/reference/migration/migrate_8_0/mappings.asciidoc

@@ -7,4 +7,10 @@
 
 The `nGram` and `edgeNGram` token filter names that have been deprecated since
 version 6.4 have been removed. Both token filters should be used by their 
-alternative names `ngram` and `edge_ngram` instead.
+alternative names `ngram` and `edge_ngram` instead.
+
+[float]
+==== Limiting the number of completion contexts
+
+The number of completion contexts within a single completion field
+has been limited to 10.

+ 2 - 0
docs/reference/search/suggesters/context-suggest.asciidoc

@@ -16,6 +16,8 @@ the field mapping.
 NOTE: It is mandatory to provide a context when indexing and querying
       a context enabled completion field.
 
+NOTE: The maximum allowed number of completion field context mappings is 10.
+
 The following defines types, each with two context mappings for a completion
 field:
 

+ 25 - 0
server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java

@@ -18,6 +18,7 @@
  */
 package org.elasticsearch.index.mapper;
 
+import org.apache.logging.log4j.LogManager;
 import org.apache.lucene.codecs.PostingsFormat;
 import org.apache.lucene.index.IndexableField;
 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.SuggestField;
 import org.elasticsearch.Version;
+import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.ParsingException;
+import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.Fuzziness;
 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 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 final MappedFieldType FIELD_TYPE = new CompletionFieldType();
         static {
@@ -354,6 +362,8 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
         private boolean preserveSeparators = Defaults.DEFAULT_PRESERVE_SEPARATORS;
         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
          */
@@ -397,6 +407,7 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
 
         @Override
         public CompletionFieldMapper build(BuilderContext context) {
+            checkCompletionContextsLimit(context);
             setupFieldType(context);
             CompletionFieldType completionFieldType = (CompletionFieldType) this.fieldType;
             completionFieldType.setContextMappings(contextMappings);
@@ -405,6 +416,20 @@ public class CompletionFieldMapper extends FieldMapper implements ArrayValueMapp
             return new CompletionFieldMapper(name, this.fieldType, context.indexSettings(),
                 multiFieldsBuilder.build(this, context), copyTo, maxInputLength);
         }
+
+        private void checkCompletionContextsLimit(BuilderContext context) {
+            if (this.contextMappings != null && this.contextMappings.size() > COMPLETION_CONTEXTS_LIMIT) {
+                if (context.indexCreatedVersion().onOrAfter(Version.V_8_0_0)) {
+                    throw new IllegalArgumentException(
+                        "Limit of completion field contexts [" + COMPLETION_CONTEXTS_LIMIT + "] has been exceeded");
+                } else {
+                    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;

+ 22 - 0
server/src/test/java/org/elasticsearch/index/mapper/CompletionFieldMapperTests.java

@@ -908,6 +908,28 @@ public class CompletionFieldMapperTests extends ESSingleNodeTestCase {
         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 < CompletionFieldMapper.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);
+
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
+            createIndex(index).mapperService().documentMapperParser().parse("type1", new CompressedXContent(mappings));
+        });
+        assertTrue(e.getMessage(),
+            e.getMessage().contains("Limit of completion field contexts [" +
+                CompletionFieldMapper.COMPLETION_CONTEXTS_LIMIT + "] has been exceeded"));
+    }
+
     private Matcher<IndexableField> suggestField(String value) {
         return Matchers.allOf(hasProperty(IndexableField::stringValue, equalTo(value)),
             Matchers.instanceOf(SuggestField.class));