Skip to content

Check array size at parse time #129997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9e3aad9
Add control for array size
drempapis Jun 25, 2025
d4c2c2b
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 25, 2025
08f6e68
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jun 25, 2025
b70d02d
apply spot
drempapis Jun 25, 2025
369dc05
Merge branch 'check-array-size-at-parse-time' of github.com:drempapis…
drempapis Jun 25, 2025
9d6e147
update for test execution
drempapis Jun 25, 2025
f7437d8
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 25, 2025
2234112
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jun 25, 2025
5e2a748
update
drempapis Jun 26, 2025
d26055a
Merge branch 'check-array-size-at-parse-time' of github.com:drempapis…
drempapis Jun 26, 2025
bbb9340
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jun 26, 2025
6f301f5
Add test for the new control
drempapis Jun 26, 2025
5aa4f46
Merge branch 'check-array-size-at-parse-time' of github.com:drempapis…
drempapis Jun 26, 2025
8fc0ec5
Remove explicit mapping
drempapis Jun 26, 2025
01e2aa0
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 26, 2025
1020dff
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jul 2, 2025
d21ca69
update after review
drempapis Jul 2, 2025
26c5ccb
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jul 2, 2025
ad4c0a3
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jul 2, 2025
24286f4
Merge branch 'main' into check-array-size-at-parse-time
drempapis Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.index.mapper.DocumentParsingException;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.indices.TestIndexNameExpressionResolver;
import org.elasticsearch.plugins.NetworkPlugin;
Expand All @@ -83,6 +85,8 @@
import org.elasticsearch.transport.TransportInterceptor;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.junit.After;
import org.junit.Before;

Expand All @@ -100,6 +104,7 @@

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
Expand Down Expand Up @@ -582,6 +587,36 @@ public void testSearchDfsQueryThenFetch() throws Exception {
);
}

public void testRejectDocumentWithTooManyArrayObjectFields() throws Exception {
String indexName = "array-limit-test";
int arrayLimit = 10;

assertAcked(
prepareCreate(indexName).setSettings(
Settings.builder().put(MapperService.INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING.getKey(), arrayLimit).build()
)
);

try (XContentBuilder doc = XContentFactory.jsonBuilder()) {
doc.startObject();
doc.startArray("array");
for (int i = 0; i < arrayLimit + 1; i++) {
doc.startObject();
doc.field("value", i);
doc.endObject();
}
doc.endArray();
doc.endObject();

Exception e = expectThrows(DocumentParsingException.class, () -> { client().prepareIndex(indexName).setSource(doc).get(); });

assertThat(
e.getMessage(),
containsString("The number of array objects has exceeded " + "the allowed limit of [" + arrayLimit + "]")
);
}
}

private static void assertSameIndices(IndicesRequest originalRequest, String... actions) {
assertSameIndices(originalRequest, false, actions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
Store.INDEX_STORE_STATS_REFRESH_INTERVAL_SETTING,
MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING,
MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
Expand Down
12 changes: 12 additions & 0 deletions server/src/main/java/org/elasticsearch/index/IndexSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_INDEX_VERSION_CREATED;
import static org.elasticsearch.cluster.routing.allocation.ExistingShardsAllocator.EXISTING_SHARDS_ALLOCATOR_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING;
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING;
Expand Down Expand Up @@ -925,6 +926,7 @@ private void setRetentionLeaseMillis(final TimeValue retentionLease) {
private volatile String requiredPipeline;
private volatile long mappingNestedFieldsLimit;
private volatile long mappingNestedDocsLimit;
private volatile long mappingArrayObjectsLimit;
private volatile long mappingTotalFieldsLimit;
private volatile boolean ignoreDynamicFieldsBeyondLimit;
private volatile long mappingDepthLimit;
Expand Down Expand Up @@ -1100,6 +1102,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
defaultPipeline = scopedSettings.get(DEFAULT_PIPELINE);
mappingNestedFieldsLimit = scopedSettings.get(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING);
mappingNestedDocsLimit = scopedSettings.get(INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING);
mappingArrayObjectsLimit = scopedSettings.get(INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING);
mappingTotalFieldsLimit = scopedSettings.get(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING);
ignoreDynamicFieldsBeyondLimit = scopedSettings.get(INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING);
mappingDepthLimit = scopedSettings.get(INDEX_MAPPING_DEPTH_LIMIT_SETTING);
Expand Down Expand Up @@ -1215,6 +1218,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
scopedSettings.addSettingsUpdateConsumer(INDEX_SOFT_DELETES_RETENTION_LEASE_PERIOD_SETTING, this::setRetentionLeaseMillis);
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING, this::setMappingNestedFieldsLimit);
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING, this::setMappingNestedDocsLimit);
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING, this::setMappingArrayObjectsLimit);
scopedSettings.addSettingsUpdateConsumer(
INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING,
this::setIgnoreDynamicFieldsBeyondLimit
Expand Down Expand Up @@ -1754,6 +1758,14 @@ private void setMappingNestedDocsLimit(long value) {
this.mappingNestedDocsLimit = value;
}

public long getMappingArrayObjectsLimit() {
return mappingArrayObjectsLimit;
}

private void setMappingArrayObjectsLimit(long value) {
this.mappingArrayObjectsLimit = value;
}

public long getMappingTotalFieldsLimit() {
return mappingTotalFieldsLimit;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,8 +749,20 @@ private static void parseNonDynamicArray(
XContentParser.Token token;
XContentParser.Token previousToken = parser.currentToken();
int elements = 0;
int countArray = 0;
long arrayObjectsLimit = context.indexSettings().getMappingArrayObjectsLimit();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.START_OBJECT) {
if (countArray++ >= arrayObjectsLimit) {
throw new IllegalStateException(
"The number of array objects has exceeded the allowed limit of ["
+ arrayObjectsLimit
+ "]. "
+ "This limit can be set by changing the ["
+ MapperService.INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING.getKey()
+ "] index level setting."
);
}
elements = 2;
parseObject(context, lastFieldName);
} else if (token == XContentParser.Token.START_ARRAY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ public boolean isAutoUpdate() {
Property.Dynamic,
Property.IndexScope
);
// maximum allowed number of objects for an array field in a document
public static final Setting<Long> INDEX_MAPPING_ARRAY_OBJECTS_LIMIT_SETTING = Setting.longSetting(
"index.mapping.array_objects.limit",
20000L,
0,
Property.Dynamic,
Property.IndexScope
);

public static final Setting<Long> INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING = Setting.longSetting(
"index.mapping.total_fields.limit",
1000L,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ public void testDynamicIndexSettingsAreClassified() {
replicatedSettings.add(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING);
replicatedSettings.add(MapperService.INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING);
replicatedSettings.add(MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING);
replicatedSettings.add(MapperService.INDEX_MAPPING_ARRAY_OBJECTS_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);
Expand Down