Skip to content

Commit 1f65597

Browse files
committed
Added index setting for field meta character limit
1 parent 27c54ab commit 1f65597

File tree

5 files changed

+97
-39
lines changed

5 files changed

+97
-39
lines changed

server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
206206
IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING,
207207
IndexSettings.RECOVERY_USE_SYNTHETIC_SOURCE_SETTING,
208208
InferenceMetadataFieldsMapper.USE_LEGACY_SEMANTIC_TEXT_FORMAT,
209+
IndexSettings.INDEX_MAPPING_META_LENGTH_LIMIT_SETTING,
209210

210211
// validate that built-in similarities don't get redefined
211212
Setting.groupSetting("index.similarity.", (s) -> {

server/src/main/java/org/elasticsearch/index/IndexSettings.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,14 @@ private static String getIgnoreAboveDefaultValue(final Settings settings) {
856856
Property.Final
857857
);
858858

859+
public static final Setting<Integer> INDEX_MAPPING_META_LENGTH_LIMIT_SETTING = Setting.intSetting(
860+
"index.mapping.meta.length_limit",
861+
500, // default value
862+
20, // minimum value
863+
Property.IndexScope,
864+
Property.Final
865+
);
866+
859867
private final Index index;
860868
private final IndexVersion version;
861869
private final Logger logger;

server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ public static Parameter<Map<String, String>> metaParam() {
12981298
"meta",
12991299
true,
13001300
Map::of,
1301-
(n, c, o) -> TypeParsers.parseMeta(n, o),
1301+
(n, c, o) -> TypeParsers.parseMeta(n, o, c),
13021302
m -> m.fieldType().meta(),
13031303
XContentBuilder::stringStringMap,
13041304
Objects::toString

server/src/main/java/org/elasticsearch/index/mapper/TypeParsers.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,12 @@
2424

2525
import static org.elasticsearch.common.xcontent.support.XContentMapValues.isArray;
2626
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeStringValue;
27+
import static org.elasticsearch.index.IndexSettings.INDEX_MAPPING_META_LENGTH_LIMIT_SETTING;
2728

2829
public class TypeParsers {
2930
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(TypeParsers.class);
3031

31-
/**
32-
* Parse the {@code meta} key of the mapping.
33-
*/
34-
public static Map<String, String> parseMeta(String name, Object metaObject) {
32+
public static Map<String, String> parseMeta(String name, Object metaObject, MappingParserContext parserContext) {
3533
if (metaObject instanceof Map == false) {
3634
throw new MapperParsingException(
3735
"[meta] must be an object, got " + metaObject.getClass().getSimpleName() + "[" + metaObject + "] for field [" + name + "]"
@@ -52,11 +50,18 @@ public static Map<String, String> parseMeta(String name, Object metaObject) {
5250
);
5351
}
5452
}
53+
int metaValueLengthLimit = INDEX_MAPPING_META_LENGTH_LIMIT_SETTING.get(parserContext.getIndexSettings().getSettings());
5554
for (Object value : meta.values()) {
5655
if (value instanceof String sValue) {
57-
if (sValue.codePointCount(0, sValue.length()) > 500) {
56+
if (sValue.codePointCount(0, sValue.length()) > metaValueLengthLimit) {
5857
throw new MapperParsingException(
59-
"[meta] values can't be longer than 500 chars, but got [" + value + "] for field [" + name + "]"
58+
"[meta] values can't be longer than "
59+
+ metaValueLengthLimit
60+
+ " chars, but got ["
61+
+ value
62+
+ "] for field ["
63+
+ name
64+
+ "]"
6065
);
6166
}
6267
} else if (value == null) {

server/src/test/java/org/elasticsearch/index/mapper/TypeParsersTests.java

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,48 +52,32 @@ private static Map<String, NamedAnalyzer> defaultAnalyzers() {
5252
return analyzers;
5353
}
5454

55-
public void testMultiFieldWithinMultiField() throws IOException {
56-
57-
XContentBuilder mapping = XContentFactory.jsonBuilder()
58-
.startObject()
59-
.field("type", "keyword")
60-
.startObject("fields")
61-
.startObject("sub-field")
62-
.field("type", "keyword")
63-
.startObject("fields")
64-
.startObject("sub-sub-field")
65-
.field("type", "keyword")
66-
.endObject()
67-
.endObject()
68-
.endObject()
69-
.endObject()
70-
.endObject();
55+
private Settings buildSettings() {
56+
return Settings.builder()
57+
.put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())
58+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1)
59+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
60+
.build();
61+
}
7162

63+
private MappingParserContext createParserContext(Settings settings) {
7264
Mapper.TypeParser typeParser = KeywordFieldMapper.PARSER;
7365

7466
MapperService mapperService = mock(MapperService.class);
7567
IndexAnalyzers indexAnalyzers = IndexAnalyzers.of(defaultAnalyzers());
7668
when(mapperService.getIndexAnalyzers()).thenReturn(indexAnalyzers);
7769

78-
Settings settings = Settings.builder()
79-
.put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())
80-
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1)
81-
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
82-
.build();
8370
IndexMetadata metadata = IndexMetadata.builder("test").settings(settings).build();
8471
IndexSettings indexSettings = new IndexSettings(metadata, Settings.EMPTY);
8572
when(mapperService.getIndexSettings()).thenReturn(indexSettings);
8673

87-
// For indices created in 8.0 or later, we should throw an error.
88-
Map<String, Object> fieldNodeCopy = XContentHelper.convertToMap(BytesReference.bytes(mapping), true, mapping.contentType()).v2();
89-
9074
IndexVersion version = IndexVersionUtils.randomVersionBetween(random(), IndexVersions.V_8_0_0, IndexVersion.current());
9175
TransportVersion transportVersion = TransportVersionUtils.randomVersionBetween(
9276
random(),
9377
TransportVersions.V_8_0_0,
9478
TransportVersion.current()
9579
);
96-
MappingParserContext context = new MappingParserContext(
80+
return new MappingParserContext(
9781
null,
9882
type -> typeParser,
9983
type -> null,
@@ -108,6 +92,29 @@ public void testMultiFieldWithinMultiField() throws IOException {
10892
throw new UnsupportedOperationException();
10993
}
11094
);
95+
}
96+
97+
public void testMultiFieldWithinMultiField() throws IOException {
98+
99+
XContentBuilder mapping = XContentFactory.jsonBuilder()
100+
.startObject()
101+
.field("type", "keyword")
102+
.startObject("fields")
103+
.startObject("sub-field")
104+
.field("type", "keyword")
105+
.startObject("fields")
106+
.startObject("sub-sub-field")
107+
.field("type", "keyword")
108+
.endObject()
109+
.endObject()
110+
.endObject()
111+
.endObject()
112+
.endObject();
113+
114+
// For indices created in 8.0 or later, we should throw an error.
115+
Map<String, Object> fieldNodeCopy = XContentHelper.convertToMap(BytesReference.bytes(mapping), true, mapping.contentType()).v2();
116+
117+
MappingParserContext context = createParserContext(buildSettings());
111118

112119
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
113120
TextFieldMapper.PARSER.parse("textField", fieldNodeCopy, context);
@@ -122,49 +129,86 @@ public void testMultiFieldWithinMultiField() throws IOException {
122129
}
123130

124131
public void testParseMeta() {
132+
MappingParserContext parserContext = createParserContext(buildSettings());
133+
125134
{
126-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", 3));
135+
MapperParsingException e = expectThrows(
136+
MapperParsingException.class,
137+
() -> TypeParsers.parseMeta("foo", 3, parserContext)
138+
);
127139
assertEquals("[meta] must be an object, got Integer[3] for field [foo]", e.getMessage());
128140
}
129141

130142
{
131143
MapperParsingException e = expectThrows(
132144
MapperParsingException.class,
133-
() -> TypeParsers.parseMeta("foo", Map.of("veryloooooooooooongkey", 3L))
145+
() -> TypeParsers.parseMeta("foo", Map.of("veryloooooooooooongkey", 3L), parserContext)
134146
);
135147
assertEquals("[meta] keys can't be longer than 20 chars, but got [veryloooooooooooongkey] for field [foo]", e.getMessage());
136148
}
137149

138150
{
139151
Map<String, Object> mapping = Map.of("foo1", 3L, "foo2", 4L, "foo3", 5L, "foo4", 6L, "foo5", 7L, "foo6", 8L);
140-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", mapping));
152+
MapperParsingException e = expectThrows(
153+
MapperParsingException.class,
154+
() -> TypeParsers.parseMeta("foo", mapping, parserContext)
155+
);
141156
assertEquals("[meta] can't have more than 5 entries, but got 6 on field [foo]", e.getMessage());
142157
}
143158

144159
{
145160
Map<String, Object> mapping = Map.of("foo", Map.of("bar", "baz"));
146-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", mapping));
161+
MapperParsingException e = expectThrows(
162+
MapperParsingException.class,
163+
() -> TypeParsers.parseMeta("foo", mapping, parserContext)
164+
);
147165
assertEquals("[meta] values can only be strings, but got Map1[{bar=baz}] for field [foo]", e.getMessage());
148166
}
149167

150168
{
151169
Map<String, Object> mapping = Map.of("bar", "baz", "foo", 3);
152-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", mapping));
170+
MapperParsingException e = expectThrows(
171+
MapperParsingException.class,
172+
() -> TypeParsers.parseMeta("foo", mapping, parserContext)
173+
);
153174
assertEquals("[meta] values can only be strings, but got Integer[3] for field [foo]", e.getMessage());
154175
}
155176

156177
{
157178
Map<String, String> meta = new HashMap<>();
158179
meta.put("foo", null);
159-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", meta));
180+
MapperParsingException e = expectThrows(
181+
MapperParsingException.class,
182+
() -> TypeParsers.parseMeta("foo", meta, parserContext)
183+
);
160184
assertEquals("[meta] values can't be null (field [foo])", e.getMessage());
161185
}
162186

163187
{
164188
String longString = IntStream.range(0, 501).mapToObj(Integer::toString).collect(Collectors.joining());
165189
Map<String, Object> mapping = Map.of("foo", longString);
166-
MapperParsingException e = expectThrows(MapperParsingException.class, () -> TypeParsers.parseMeta("foo", mapping));
190+
MapperParsingException e = expectThrows(
191+
MapperParsingException.class,
192+
() -> TypeParsers.parseMeta("foo", mapping, parserContext)
193+
);
167194
assertThat(e.getMessage(), Matchers.startsWith("[meta] values can't be longer than 500 chars"));
168195
}
196+
197+
{
198+
Settings otherSettings = Settings.builder()
199+
.put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())
200+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1)
201+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
202+
.put(IndexSettings.INDEX_MAPPING_META_LENGTH_LIMIT_SETTING.getKey(), 300)
203+
.build();
204+
MappingParserContext otherParserContext = createParserContext(otherSettings);
205+
String longString = IntStream.range(0, 301).mapToObj(Integer::toString).collect(Collectors.joining());
206+
Map<String, Object> mapping = Map.of("foo", longString);
207+
MapperParsingException e = expectThrows(
208+
MapperParsingException.class,
209+
() -> TypeParsers.parseMeta("foo", mapping, otherParserContext)
210+
);
211+
assertThat(e.getMessage(), Matchers.startsWith("[meta] values can't be longer than 300 chars"));
212+
}
169213
}
170214
}

0 commit comments

Comments
 (0)