Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
83b507c
Add new dynamic until_limit option
felixbarny May 19, 2023
2993820
Add setting
felixbarny May 20, 2023
6402346
Update docs/changelog/96235.yaml
felixbarny May 20, 2023
0407747
Fix test
felixbarny May 20, 2023
677f749
Prevent infinite retry loops and simplify code by retrying all mappin…
felixbarny May 21, 2023
0634d26
Avoid catching exception
felixbarny May 24, 2023
9d3b685
Fix constant_keyword mapping
felixbarny May 24, 2023
ec511c7
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jul 9, 2023
7408c5d
Add mappingUpdateRetryCounter
felixbarny Jul 9, 2023
05dee62
Add comment to _ignored field's exists query implementation
felixbarny Jul 9, 2023
2d2c9c2
Refactor index setting into `dynamic: until_limit` mapping parameter
felixbarny Jul 10, 2023
c9ad4f3
Fix testExecuteBulkIndexRequestWithErrorWhileUpdatingMapping
felixbarny Jul 10, 2023
72a02ed
Remove references to ignore_dynamic_beyond_limit in docs
felixbarny Aug 3, 2023
180bec1
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Aug 3, 2023
d446da9
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Aug 29, 2023
0f8029d
Accept mappers with the same name
felixbarny Aug 31, 2023
51280ae
Apply spotless suggestions
felixbarny Aug 31, 2023
99ce1a9
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 7, 2023
9528a12
Avoid double counting dynamic mappers for dynamic array values
felixbarny Sep 7, 2023
9ab71ad
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 7, 2023
055f849
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 11, 2023
db224bd
Apply feedback from review
felixbarny Sep 13, 2023
92a1f1a
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 13, 2023
a0e5a01
Test and fix bug in Mapper#mapperSize
felixbarny Sep 13, 2023
def096d
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 13, 2023
2ea9418
Address nits
felixbarny Sep 14, 2023
b7ab897
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 14, 2023
df8af69
Make mappingUpdateRetryCounter a boolean
felixbarny Sep 14, 2023
6d06795
Only retry failed mapping updates when mappings have been updated con…
felixbarny Sep 14, 2023
94996cc
Apply spotless suggestions
felixbarny Sep 14, 2023
7880073
Compare total fields count rather than mapping version to check for c…
felixbarny Sep 15, 2023
a967603
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 20, 2023
f146cdf
Move mapperSize implementation from Mapper to Mapper.Builder
felixbarny Sep 20, 2023
b8a7a49
Apply spotless suggestions
felixbarny Sep 20, 2023
ca1c5b7
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 20, 2023
e1ff5a0
Revert TransportShardBulkActionTests changes
felixbarny Sep 20, 2023
4bae69a
More tests for Mapper.Builder#mapperSize
felixbarny Sep 20, 2023
d98ec65
Suppress unchecked warning
felixbarny Sep 20, 2023
6824f73
Fix LegacyGeoShapeFieldMapperTests via assertWarnings
felixbarny Sep 20, 2023
df1483c
Retry mapping updates if mapping was updated concurrently
felixbarny Sep 21, 2023
b0b957e
Apply spotless suggestions
felixbarny Sep 21, 2023
6d133d1
Guard against NPE
felixbarny Sep 21, 2023
5ca08fb
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Sep 25, 2023
7ddc5ae
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Nov 16, 2023
da99d7a
Revert changes to retry mapping update
felixbarny Nov 16, 2023
5f3b6dc
Limit the number of fields added during merge
felixbarny Nov 22, 2023
fac8dcb
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Nov 22, 2023
41da12a
Fix mapperSize for some FieldMapper builders
felixbarny Nov 24, 2023
1a735bd
Guard against mapping update request timeouts and infinite loops
felixbarny Nov 24, 2023
bd56de6
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Nov 24, 2023
f1413c4
Fix test and spotless
felixbarny Nov 24, 2023
0727ce6
A couple of fixes
felixbarny Nov 25, 2023
75acf0c
More field mapper test fixes
felixbarny Nov 25, 2023
54028bf
Remove unused import
felixbarny Nov 25, 2023
a0a0006
Adding diagnostics to debug build failure
felixbarny Nov 26, 2023
b3c8fda
Only throw if mapping version hasn't changed
felixbarny Nov 27, 2023
3656003
Add test case for infinite loop prevention
felixbarny Nov 27, 2023
435faf5
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Nov 27, 2023
1bd80fb
Add unit test for getNewDynamicMappersSize
felixbarny Nov 27, 2023
797e9ee
Revert "Refactor index setting into `dynamic: until_limit` mapping pa…
felixbarny Dec 1, 2023
4865ac0
Remove remains of true_until_limit
felixbarny Dec 1, 2023
470d192
Simplify MapperMergeContext by using ignore_dynamic_beyond_limit inde…
felixbarny Dec 1, 2023
7286290
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Dec 1, 2023
42a6f7e
Apply spotless suggestions
felixbarny Dec 1, 2023
379ff2b
Fix bug in addDynamicRuntimeField
felixbarny Dec 1, 2023
be47036
Revert adding unnecessary new line
felixbarny Dec 1, 2023
58426e5
Determine remaining fields to add in MapperService
felixbarny Dec 4, 2023
bd99efa
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Dec 4, 2023
6948a08
Polishing
felixbarny Dec 4, 2023
a71fbf3
Don't preserve order in mappers
felixbarny Dec 5, 2023
4ad0bca
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Dec 6, 2023
1f44c30
Fix performance regression
felixbarny Dec 6, 2023
94f25f5
Apply spotless suggestions
felixbarny Dec 6, 2023
c56d18b
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 4, 2024
0c53952
Fix SemanticTextFieldMapperTests
felixbarny Jan 4, 2024
cd0cc41
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 5, 2024
80d0c66
Remove Mapper.Builder#mapperSize
felixbarny Jan 5, 2024
6aab0ad
Remove unused import
felixbarny Jan 5, 2024
4c07538
Adapt DocumentParserContextTests
felixbarny Jan 5, 2024
3bcefac
Apply spotless suggestions
felixbarny Jan 5, 2024
baf3ab6
Fix RootObjectMapper#mapperSize
felixbarny Jan 5, 2024
ef45686
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 23, 2024
e9d3652
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 23, 2024
d38de54
Remove unused method
felixbarny Jan 23, 2024
cdd0ce6
Fix SearchResponse leak in DynamicMappingIT
felixbarny Jan 23, 2024
90390ee
Merge branch 'main' into ignore-dynamic-beyond-limit
elasticmachine Jan 24, 2024
f589bac
Merge branch 'main' into ignore-dynamic-beyond-limit
elasticmachine Jan 24, 2024
49191bf
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 26, 2024
eade390
Add docs for `index.mapping.total_fields.ignore_dynamic_beyond_limit`
felixbarny Jan 29, 2024
488befa
Align exceedsLimit with remainingFieldsUntilLimit
felixbarny Jan 29, 2024
58f900f
Make setters in IndexSettings private
felixbarny Jan 29, 2024
9efde90
Replace AtomicInteger with a private DynamicMapperSize class
felixbarny Jan 31, 2024
5b462d4
Make MapperService#mergeMappings static again
felixbarny Jan 31, 2024
f27c88c
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Jan 31, 2024
da16f8d
Merge remote-tracking branch 'origin/main' into ignore-dynamic-beyond…
felixbarny Feb 1, 2024
381a5d0
Merge branch 'main' into ignore-dynamic-beyond-limit
elasticmachine Feb 2, 2024
43d7412
Merge branch 'main' into ignore-dynamic-beyond-limit
elasticmachine Feb 2, 2024
059132d
Merge branch 'main' into ignore-dynamic-beyond-limit
elasticmachine Feb 2, 2024
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
5 changes: 5 additions & 0 deletions docs/changelog/96235.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 96235
summary: Add `true_until_limit` option to `dynamic` mapping parameter to ignore dynamic fields when field limit is reached
area: Mapping
type: enhancement
issues: []
6 changes: 4 additions & 2 deletions docs/reference/mapping/fields/ignored-field.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
The `_ignored` field indexes and stores the names of every field in a document
that has been ignored when the document was indexed. This can, for example,
be the case when the field was malformed and <<ignore-malformed,`ignore_malformed`>>
was turned on, or when a `keyword` fields value exceeds its optional
<<ignore-above,`ignore_above`>> setting.
was turned on, when a `keyword` field's value exceeds its optional
<<ignore-above,`ignore_above`>> setting, or when
<<mapping-settings-limit,`index.mapping.total_fields.limit`>> has been reached and
the mapping parameter <<dynamic-parameters, `dynamic: true_until_limit`>> is set.

This field is searchable with <<query-dsl-term-query,`term`>>,
<<query-dsl-terms-query,`terms`>> and <<query-dsl-exists-query,`exists`>>
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/mapping/mapping-settings-limit.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ limits the maximum number of clauses in a query.
+
[TIP]
====
If your field mappings contain a large, arbitrary set of keys, consider using the <<flattened,flattened>> data type.
If your field mappings contain a large, arbitrary set of keys, consider using the <<flattened,flattened>> data type,
or setting the mapping parameter <<dynamic-parameters, `dynamic: true_until_limit`>>.
====

`index.mapping.depth.limit`::
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/mapping/params/dynamic.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,18 @@ accepts the following parameters:

[horizontal]
`true`:: New fields are added to the mapping (default).
If the field limit (<<mapping-settings-limit,`index.mapping.total_fields.limit`>>) is exceeded as a result of adding a dynamic
field to the mapping, the corresponding index request fails with the message `Limit of total fields [X] has been exceeded`.
`runtime`:: New fields are added to the mapping as <<runtime,runtime fields>>.
These fields are not indexed, and are loaded from `_source` at query time.
If the field limit (<<mapping-settings-limit,`index.mapping.total_fields.limit`>>) is exceeded as a result of adding a dynamic
field to the mapping, the corresponding index request fails with the message `Limit of total fields [X] has been exceeded`.
`false`:: New fields are ignored. These fields will not be indexed
or searchable, but will still appear in the `_source` field of returned hits. These fields will not be added
to the mapping, and new fields must be added explicitly.
`strict`:: If new fields are detected, an exception is thrown and the document
is rejected. New fields must be explicitly added to the mapping.
`true_until_limit`:: New fields are added to the mapping as long as the field limit
(<<mapping-settings-limit,`index.mapping.total_fields.limit`>>) is not exceeded.
Fields that would exceed the limit are not added to the mapping, similar to `dynamic: false`.
Ignored fields are added to the <<mapping-ignored-field, `_ignored` metadata field>>.
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,24 @@ timing out in the browser's Developer Tools Network tab.
doesn't normally cause problems unless it's combined with overriding
<<mapping-settings-limit,`index.mapping.total_fields.limit`>>. The
default `1000` limit is considered generous, though overriding to `10000`
doesn't cause noticable impact depending on use case. However, to give
doesn't cause noticeable impact depending on use case. However, to give
a bad example, overriding to `100000` and this limit being hit
by mapping totals would usually have strong performance implications.

If your index mapped fields expect to contain a large, arbitrary set of
keys, you may instead consider:

* Setting the mapping parameter <<dynamic-parameters, `dynamic: true_until_limit`>>.
Instead of rejecting documents that add dynamic fields when the field limit is reached, this will ignore dynamic fields beyond the limit.

* Using the <<flattened,flattened>> data type. Please note,
however, that flattened objects is link:https://github.com/elastic/kibana/issues/25820[not fully supported in {kib}] yet. For example, this could apply to sub-mappings like { `host.name` ,
`host.os`, `host.version` }. Desired fields are still accessed by
<<runtime-search-request,runtime fields>>.

* Disable <<dynamic-mapping,dynamic mappings>>.
This cannot effect current index mapping, but can apply going forward via an <<index-templates,index template>>.
This cannot effect current index mapping, but can apply
going forward via an <<index-templates,index template>>.

Modifying to the <<nested,nested>> data type would not resolve the core
issue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public void setup() throws Exception {
new MetadataFieldMapper[] { dtfm },
Collections.emptyMap()
);
MappingLookup mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of(), List.of());
MappingLookup mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of(), List.of(), 1);
indicesService = DataStreamTestHelper.mockIndicesServices(mappingLookup);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ protected void registerParameters(ParameterChecker checker) throws IOException {
checker.registerUpdateCheck(b -> b.field("distance_error_pct", 0.8), m -> {});
}

@Override
public void testMapperBuilderSize() throws IOException {
super.testMapperBuilderSize();
assertWarnings("Parameter [strategy] is deprecated and will be removed in a future version");
}

@Override
public void testMapperBuilderSizeMultiField() throws IOException {
super.testMapperBuilderSizeMultiField();
assertWarnings("Parameter [strategy] is deprecated and will be removed in a future version");
}

@Override
protected Collection<? extends Plugin> getPlugins() {
return List.of(new TestLegacyGeoShapeFieldMapperPlugin());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.GeoBoundingBoxQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.InternalSettingsPlugin;
import org.elasticsearch.xcontent.XContentBuilder;
Expand All @@ -39,10 +41,13 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

Expand All @@ -51,8 +56,10 @@
import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.MIN_DIMS_FOR_DYNAMIC_FLOAT_MAPPING;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.instanceOf;

public class DynamicMappingIT extends ESIntegTestCase {
Expand Down Expand Up @@ -90,18 +97,60 @@ public void testConflictingDynamicMappingsBulk() {
assertTrue(bulkResponse.hasFailures());
}

private static void assertMappingsHaveField(GetMappingsResponse mappings, String index, String field) throws IOException {
MappingMetadata indexMappings = mappings.getMappings().get("index");
assertNotNull(indexMappings);
Map<String, Object> typeMappingsMap = indexMappings.getSourceAsMap();
@SuppressWarnings("unchecked")
Map<String, Object> properties = (Map<String, Object>) typeMappingsMap.get("properties");
assertTrue("Could not find [" + field + "] in " + typeMappingsMap.toString(), properties.containsKey(field));
public void testArrayWithDifferentTypes() {
Comment thread
felixbarny marked this conversation as resolved.
createIndex("index");
BulkResponse bulkResponse = client().prepareBulk()
.add(client().prepareIndex("index").setId("1").setSource("foo", List.of(42, "bar")))
.get();

assertTrue(bulkResponse.hasFailures());
assertEquals(
"mapper [foo] cannot be changed from type [long] to [text]",
bulkResponse.getItems()[0].getFailure().getCause().getMessage()
);
}

public void testArraysCountAsOneTowardsFieldLimit() {
createIndex("index", Settings.builder().put(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), 2).build());
BulkResponse bulkResponse = client().prepareBulk()
.add(client().prepareIndex("index").setId("1").setSource("field1", List.of(1, 2), "field2", 1))
.get();

assertFalse(bulkResponse.hasFailures());
}

public void testConcurrentDynamicUpdates() throws Throwable {
createIndex("index");
final Thread[] indexThreads = new Thread[32];
int numberOfFieldsToCreate = 32;
Map<String, Object> properties = indexConcurrently(numberOfFieldsToCreate, Settings.builder(), Map.of());
assertThat(properties, aMapWithSize(numberOfFieldsToCreate));
for (int i = 0; i < numberOfFieldsToCreate; i++) {
assertThat(properties, hasKey("field" + i));
}
}

public void testConcurrentDynamicIgnoreBeyondLimitUpdates() throws Throwable {
int numberOfFieldsToCreate = 32;
Map<String, Object> properties = indexConcurrently(
numberOfFieldsToCreate,
Settings.builder().put(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), numberOfFieldsToCreate),
Map.of("dynamic", "true_until_limit")
);
// every field is a multi-field (text + keyword)
assertThat(properties, aMapWithSize(16));
SearchResponse response = client().prepareSearch("index")
.setQuery(new MatchAllQueryBuilder())
.setSize(numberOfFieldsToCreate)
.addFetchField("*")
.get();
long ignoredFields = Arrays.stream(response.getHits().getHits()).filter(hit -> hit.field("_ignored") != null).count();
assertEquals(16, ignoredFields);
}

private Map<String, Object> indexConcurrently(int numberOfFieldsToCreate, Settings.Builder settings, Map<String, Object> mapping)
throws Throwable {
indicesAdmin().prepareCreate("index").setSettings(settings).setMapping(mapping).get();
ensureGreen("index");
final Thread[] indexThreads = new Thread[numberOfFieldsToCreate];
final CountDownLatch startLatch = new CountDownLatch(1);
final AtomicReference<Throwable> error = new AtomicReference<>();
for (int i = 0; i < indexThreads.length; ++i) {
Expand Down Expand Up @@ -129,14 +178,17 @@ public void run() {
if (error.get() != null) {
throw error.get();
}
Thread.sleep(2000);
Comment thread
felixbarny marked this conversation as resolved.
GetMappingsResponse mappings = indicesAdmin().prepareGetMappings("index").get();
for (int i = 0; i < indexThreads.length; ++i) {
assertMappingsHaveField(mappings, "index", "field" + i);
}
for (int i = 0; i < indexThreads.length; ++i) {
client().admin().indices().prepareRefresh("index").get();
for (int i = 0; i < numberOfFieldsToCreate; ++i) {
assertTrue(client().prepareGet("index", Integer.toString(i)).get().isExists());
}
GetMappingsResponse mappings = indicesAdmin().prepareGetMappings("index").get();
MappingMetadata indexMappings = mappings.getMappings().get("index");
assertNotNull(indexMappings);
Map<String, Object> typeMappingsMap = indexMappings.getSourceAsMap();
@SuppressWarnings("unchecked")
Map<String, Object> properties = (Map<String, Object>) typeMappingsMap.get("properties");
return properties;
}

public void testPreflightCheckAvoidsMaster() throws InterruptedException, IOException {
Expand Down Expand Up @@ -227,15 +279,111 @@ public void onFailure(Exception e) {
Exception e = expectThrows(DocumentParsingException.class, () -> indexRequestBuilder.get(TimeValue.timeValueSeconds(10)));
assertThat(e.getMessage(), Matchers.containsString("failed to parse"));
assertThat(e.getCause(), instanceOf(IllegalArgumentException.class));
assertThat(
e.getCause().getMessage(),
Matchers.containsString("Limit of total fields [2] has been exceeded while adding new fields [1]")
);
assertThat(e.getCause().getMessage(), Matchers.containsString("Limit of total fields [2] has been exceeded"));
} finally {
indexingCompletedLatch.countDown();
}
}

public void testIgnoreDynamicBeyondLimitMultiField() throws Exception {
var fields = indexIgnoreDynamicBeyond(2, orderedMap("field1", 1, "field2", "text")).getFields();
assertThat(fields.keySet(), equalTo(Set.of("field1", "_ignored")));
assertThat(fields.get("field1").getValues(), equalTo(List.of(1L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("field2")));
}

public void testIgnoreDynamicArrayField() throws Exception {
var fields = indexIgnoreDynamicBeyond(1, orderedMap("field1", 1, "field2", List.of(1, 2))).getFields();
assertThat(fields.keySet(), equalTo(Set.of("field1", "_ignored")));
assertThat(fields.get("field1").getValues(), equalTo(List.of(1L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("field2")));
}

public void testIgnoreDynamicBeyondLimitObjectField() throws Exception {
var fields = indexIgnoreDynamicBeyond(3, orderedMap("a.b", 1, "a.c", 2, "a.d", 3)).getFields();
assertThat(fields.keySet(), equalTo(Set.of("a.b", "a.c", "_ignored")));
assertThat(fields.get("a.b").getValues(), equalTo(List.of(1L)));
assertThat(fields.get("a.c").getValues(), equalTo(List.of(2L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("a.d")));
}

public void testIgnoreDynamicBeyondLimitObjectField2() throws Exception {
var fields = indexIgnoreDynamicBeyond(3, orderedMap("a.b", 1, "a.c", 2, "b.a", 3)).getFields();
assertThat(fields.keySet(), equalTo(Set.of("a.b", "a.c", "_ignored")));
assertThat(fields.get("a.b").getValues(), equalTo(List.of(1L)));
assertThat(fields.get("a.c").getValues(), equalTo(List.of(2L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("b")));
}

public void testIgnoreDynamicBeyondLimitDottedObjectMultiField() throws Exception {
var fields = indexIgnoreDynamicBeyond(4, orderedMap("a.b", "foo", "a.c", 2, "a.d", 3)).getFields();
assertThat(fields.keySet(), equalTo(Set.of("a.b", "a.b.keyword", "a.c", "_ignored")));
assertThat(fields.get("a.b").getValues(), equalTo(List.of("foo")));
assertThat(fields.get("a.b.keyword").getValues(), equalTo(List.of("foo")));
assertThat(fields.get("a.c").getValues(), equalTo(List.of(2L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("a.d")));
}

public void testIgnoreDynamicBeyondLimitObjectMultiField() throws Exception {
var fields = indexIgnoreDynamicBeyond(5, orderedMap("a", orderedMap("b", "foo", "c", "bar", "d", 3))).getFields();
assertThat(fields.keySet(), equalTo(Set.of("a.b", "a.b.keyword", "a.c", "a.c.keyword", "_ignored")));
assertThat(fields.get("a.b").getValues(), equalTo(List.of("foo")));
assertThat(fields.get("a.b.keyword").getValues(), equalTo(List.of("foo")));
assertThat(fields.get("a.c").getValues(), equalTo(List.of("bar")));
assertThat(fields.get("a.c.keyword").getValues(), equalTo(List.of("bar")));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("a.d")));
}

public void testFieldLimitRuntimeAndDynamic() throws Exception {
assertAcked(
indicesAdmin().prepareCreate("test")
.setSettings(Settings.builder().put(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), 5))
.setMapping("""
{
"dynamic": "runtime",
"properties": {
"runtime": {
"type": "object"
},
"mapped_obj": {
"type": "object",
"dynamic": "true_until_limit"
}
}
}""")
.get()
);

client().index(
new IndexRequest("test").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
.source(orderedMap("dynamic.keyword", "foo", "mapped_obj.number", 1, "mapped_obj.string", "foo"))
).get();

var fields = client().prepareSearch("test").setQuery(new MatchAllQueryBuilder()).addFetchField("*").get().getHits().getHits()[0]
.getFields();
assertThat(fields.keySet(), equalTo(Set.of("dynamic.keyword", "mapped_obj.number", "_ignored")));
assertThat(fields.get("dynamic.keyword").getValues(), equalTo(List.of("foo")));
assertThat(fields.get("mapped_obj.number").getValues(), equalTo(List.of(1L)));
assertThat(fields.get("_ignored").getValues(), equalTo(List.of("mapped_obj.string")));
}

private LinkedHashMap<String, Object> orderedMap(Object... entries) {
var map = new LinkedHashMap<String, Object>();
for (int i = 0; i < entries.length; i += 2) {
map.put((String) entries[i], entries[i + 1]);
}
return map;
}

private SearchHit indexIgnoreDynamicBeyond(int fieldLimit, Map<String, Object> source) throws Exception {
prepareCreate("index", Settings.builder().put(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), fieldLimit)).setMapping(
Map.of("dynamic", "true_until_limit")
).get();
ensureGreen("index");
client().prepareIndex("index").setId("1").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).setSource(source).get();
return client().prepareSearch("index").setQuery(new MatchAllQueryBuilder()).addFetchField("*").get().getHits().getHits()[0];
}

public void testTotalFieldsLimitWithRuntimeFields() {
Settings indexSettings = Settings.builder()
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
Expand Down
Loading