Add a soft limit to the field name length (#40309)
Adds an optional limit to the length of field names, throws an IllegalArgumentException if the limit is breached. Closes #33651
This commit is contained in:
parent
f2b5960f90
commit
fb8ad0cf30
|
@ -97,6 +97,12 @@ causing a mapping explosion:
|
|||
100 objects within a nested field, will actually create 101 documents, as
|
||||
each nested object will be indexed as a separate hidden document.
|
||||
|
||||
`index.mapping.field_name_length.limit`::
|
||||
Setting for the maximum length of a field name. The default value is
|
||||
Long.MAX_VALUE (no limit). This setting isn't really something that addresses
|
||||
mappings explosion but might still be useful if you want to limit the field length.
|
||||
It usually shouldn't be necessary to set this setting. The default is okay
|
||||
unless a user starts to add a huge number of fields with really long names.
|
||||
|
||||
[float]
|
||||
== Dynamic mapping
|
||||
|
|
|
@ -154,6 +154,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
|
|||
MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING,
|
||||
MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
|
||||
MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
|
||||
MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING,
|
||||
BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING,
|
||||
IndexModule.INDEX_STORE_TYPE_SETTING,
|
||||
IndexModule.INDEX_STORE_PRE_LOAD_SETTING,
|
||||
|
|
|
@ -69,6 +69,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
|
@ -101,7 +102,9 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
|
|||
public static final Setting<Long> INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING =
|
||||
Setting.longSetting("index.mapping.total_fields.limit", 1000L, 0, Property.Dynamic, Property.IndexScope);
|
||||
public static final Setting<Long> INDEX_MAPPING_DEPTH_LIMIT_SETTING =
|
||||
Setting.longSetting("index.mapping.depth.limit", 20L, 1, Property.Dynamic, Property.IndexScope);
|
||||
Setting.longSetting("index.mapping.depth.limit", 20L, 1, Property.Dynamic, Property.IndexScope);
|
||||
public static final Setting<Long> INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING =
|
||||
Setting.longSetting("index.mapping.field_name_length.limit", Long.MAX_VALUE, 1L, Property.Dynamic, Property.IndexScope);
|
||||
public static final boolean INDEX_MAPPER_DYNAMIC_DEFAULT = true;
|
||||
@Deprecated
|
||||
public static final Setting<Boolean> INDEX_MAPPER_DYNAMIC_SETTING =
|
||||
|
@ -503,6 +506,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
|
|||
// Also, don't take metadata mappers into account for the field limit check
|
||||
checkTotalFieldsLimit(objectMappers.size() + fieldMappers.size() - metadataMappers.length
|
||||
+ fieldAliasMappers.size() );
|
||||
checkFieldNameSoftLimit(objectMappers, fieldMappers, fieldAliasMappers);
|
||||
}
|
||||
|
||||
results.put(newMapper.type(), newMapper);
|
||||
|
@ -623,6 +627,24 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkFieldNameSoftLimit(Collection<ObjectMapper> objectMappers,
|
||||
Collection<FieldMapper> fieldMappers,
|
||||
Collection<FieldAliasMapper> fieldAliasMappers) {
|
||||
final long maxFieldNameLength = indexSettings.getValue(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING);
|
||||
|
||||
Stream.of(objectMappers.stream(), fieldMappers.stream(), fieldAliasMappers.stream())
|
||||
.reduce(Stream::concat)
|
||||
.orElseGet(Stream::empty)
|
||||
.forEach(mapper -> {
|
||||
String name = mapper.simpleName();
|
||||
if (name.length() > maxFieldNameLength) {
|
||||
throw new IllegalArgumentException("Field name [" + name + "] in index [" + index().getName() +
|
||||
"] is too long. The limit is set to [" + maxFieldNameLength + "] characters but was ["
|
||||
+ name.length() + "] characters");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkPartitionedIndexConstraints(DocumentMapper newMapper) {
|
||||
if (indexSettings.getIndexMetaData().isRoutingPartitionedIndex()) {
|
||||
if (!newMapper.routingFieldMapper().required()) {
|
||||
|
|
|
@ -323,4 +323,115 @@ public class MapperServiceTests extends ESSingleNodeTestCase {
|
|||
+ " that indices can have at most one type.", e.getMessage());
|
||||
}
|
||||
|
||||
public void testFieldNameLengthLimit() throws Throwable {
|
||||
int maxFieldNameLength = randomIntBetween(15, 20);
|
||||
String testString = new String(new char[maxFieldNameLength + 1]).replace("\0", "a");
|
||||
Settings settings = Settings.builder().put(MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING.getKey(), maxFieldNameLength)
|
||||
.build();
|
||||
MapperService mapperService = createIndex("test1", settings).mapperService();
|
||||
|
||||
CompressedXContent mapping = new CompressedXContent(BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||
.startObject("properties")
|
||||
.startObject("field")
|
||||
.field("type", "text")
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject()));
|
||||
|
||||
mapperService.merge("type", mapping, MergeReason.MAPPING_UPDATE);
|
||||
|
||||
CompressedXContent mappingUpdate = new CompressedXContent(BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject()
|
||||
.startObject("properties")
|
||||
.startObject(testString)
|
||||
.field("type", "text")
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject()));
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
|
||||
mapperService.merge("type", mappingUpdate, MergeReason.MAPPING_UPDATE);
|
||||
});
|
||||
|
||||
assertEquals("Field name [" + testString + "] in index [test1] is too long. " +
|
||||
"The limit is set to [" + maxFieldNameLength + "] characters but was ["
|
||||
+ testString.length() + "] characters", e.getMessage());
|
||||
}
|
||||
|
||||
public void testObjectNameLengthLimit() throws Throwable {
|
||||
int maxFieldNameLength = randomIntBetween(15, 20);
|
||||
String testString = new String(new char[maxFieldNameLength + 1]).replace("\0", "a");
|
||||
Settings settings = Settings.builder().put(MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING.getKey(), maxFieldNameLength)
|
||||
.build();
|
||||
MapperService mapperService = createIndex("test1", settings).mapperService();
|
||||
|
||||
CompressedXContent mapping = new CompressedXContent(BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||
.startObject("properties")
|
||||
.startObject(testString)
|
||||
.field("type", "object")
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject()));
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
|
||||
mapperService.merge("type", mapping, MergeReason.MAPPING_UPDATE);
|
||||
});
|
||||
|
||||
assertEquals("Field name [" + testString + "] in index [test1] is too long. " +
|
||||
"The limit is set to [" + maxFieldNameLength + "] characters but was ["
|
||||
+ testString.length() + "] characters", e.getMessage());
|
||||
}
|
||||
|
||||
public void testAliasFieldNameLengthLimit() throws Throwable {
|
||||
int maxFieldNameLength = randomIntBetween(15, 20);
|
||||
String testString = new String(new char[maxFieldNameLength + 1]).replace("\0", "a");
|
||||
Settings settings = Settings.builder().put(MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING.getKey(), maxFieldNameLength)
|
||||
.build();
|
||||
MapperService mapperService = createIndex("test1", settings).mapperService();
|
||||
|
||||
CompressedXContent mapping = new CompressedXContent(BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||
.startObject("properties")
|
||||
.startObject(testString)
|
||||
.field("type", "alias")
|
||||
.field("path", "field")
|
||||
.endObject()
|
||||
.startObject("field")
|
||||
.field("type", "text")
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject()));
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
|
||||
mapperService.merge("type", mapping, MergeReason.MAPPING_UPDATE);
|
||||
});
|
||||
|
||||
assertEquals("Field name [" + testString + "] in index [test1] is too long. " +
|
||||
"The limit is set to [" + maxFieldNameLength + "] characters but was ["
|
||||
+ testString.length() + "] characters", e.getMessage());
|
||||
}
|
||||
|
||||
public void testMappingRecoverySkipFieldNameLengthLimit() throws Throwable {
|
||||
int maxFieldNameLength = randomIntBetween(15, 20);
|
||||
String testString = new String(new char[maxFieldNameLength + 1]).replace("\0", "a");
|
||||
Settings settings = Settings.builder().put(MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING.getKey(), maxFieldNameLength)
|
||||
.build();
|
||||
MapperService mapperService = createIndex("test1", settings).mapperService();
|
||||
|
||||
CompressedXContent mapping = new CompressedXContent(BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||
.startObject("properties")
|
||||
.startObject(testString)
|
||||
.field("type", "text")
|
||||
.endObject()
|
||||
.endObject()
|
||||
.endObject().endObject()));
|
||||
|
||||
DocumentMapper documentMapper = mapperService.merge("type", mapping, MergeReason.MAPPING_RECOVERY);
|
||||
|
||||
assertEquals(testString, documentMapper.mappers().getMapper(testString).simpleName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ public class TransportResumeFollowActionTests extends ESTestCase {
|
|||
validate(request, leaderIMD, followIMD, UUIDs, mapperService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testDynamicIndexSettingsAreClassified() {
|
||||
// We should be conscious which dynamic settings are replicated from leader to follower index.
|
||||
// This is the list of settings that should be replicated:
|
||||
|
@ -230,6 +230,7 @@ public class TransportResumeFollowActionTests extends ESTestCase {
|
|||
replicatedSettings.add(MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING);
|
||||
replicatedSettings.add(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING);
|
||||
replicatedSettings.add(MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING);
|
||||
replicatedSettings.add(MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING);
|
||||
replicatedSettings.add(MapperService.INDEX_MAPPER_DYNAMIC_SETTING);
|
||||
replicatedSettings.add(IndexSettings.MAX_NGRAM_DIFF_SETTING);
|
||||
replicatedSettings.add(IndexSettings.MAX_SHINGLE_DIFF_SETTING);
|
||||
|
|
Loading…
Reference in New Issue