Skip to content

Commit c5ca20e

Browse files
author
Christoph Büscher
authored
[7.16] Better error message for long keys in flattened fields (#80433) (#80487)
When a keyed ´flattened` field that is composed of both the fields key, a separator and the actual value, exceeds Lucenes term limit, the user currently gets a confusing IAE that among other things only mentions the fields `_keyed` subfield as the source of the offending long term. Since it might be both key and value that might trip this we can check earlier and throw a nices IAE that reports both key and value lengths and the prefix of the offending key. Closes #78248
1 parent cdde094 commit c5ca20e

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldParser.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.lucene.document.Field;
1212
import org.apache.lucene.document.SortedSetDocValuesField;
1313
import org.apache.lucene.document.StringField;
14+
import org.apache.lucene.index.IndexWriter;
1415
import org.apache.lucene.index.IndexableField;
1516
import org.apache.lucene.util.BytesRef;
1617
import org.elasticsearch.common.xcontent.XContentParserUtils;
@@ -131,15 +132,33 @@ private void addField(ContentPath path, String currentName, String value, List<I
131132
);
132133
}
133134
String keyedValue = createKeyedValue(key, value);
134-
135+
BytesRef bytesKeyedValue = new BytesRef(keyedValue);
136+
// check the keyed value doesn't exceed the IndexWriter.MAX_TERM_LENGTH limit enforced by Lucene at index time
137+
// in that case we can already throw a more user friendly exception here which includes the offending fields key and value lengths
138+
if (bytesKeyedValue.length > IndexWriter.MAX_TERM_LENGTH) {
139+
String msg = "Flattened field ["
140+
+ rootFieldName
141+
+ "] contains one immense field"
142+
+ " whose keyed encoding is longer than the allowed max length of "
143+
+ IndexWriter.MAX_TERM_LENGTH
144+
+ " bytes. Key length: "
145+
+ key.length()
146+
+ ", value length: "
147+
+ value.length()
148+
+ " for key starting with ["
149+
+ key.substring(0, Math.min(key.length(), 50))
150+
+ "]";
151+
throw new IllegalArgumentException(msg);
152+
}
153+
BytesRef bytesValue = new BytesRef(value);
135154
if (fieldType.isSearchable()) {
136-
fields.add(new StringField(rootFieldName, new BytesRef(value), Field.Store.NO));
137-
fields.add(new StringField(keyedFieldName, new BytesRef(keyedValue), Field.Store.NO));
155+
fields.add(new StringField(rootFieldName, bytesValue, Field.Store.NO));
156+
fields.add(new StringField(keyedFieldName, bytesKeyedValue, Field.Store.NO));
138157
}
139158

140159
if (fieldType.hasDocValues()) {
141-
fields.add(new SortedSetDocValuesField(rootFieldName, new BytesRef(value)));
142-
fields.add(new SortedSetDocValuesField(keyedFieldName, new BytesRef(keyedValue)));
160+
fields.add(new SortedSetDocValuesField(rootFieldName, bytesValue));
161+
fields.add(new SortedSetDocValuesField(keyedFieldName, bytesKeyedValue));
143162
}
144163
}
145164

server/src/test/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapperTests.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ public void testEagerGlobalOrdinals() throws IOException {
262262

263263
public void testIgnoreAbove() throws IOException {
264264
// First verify the default behavior when ignore_above is not set.
265-
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
265+
MapperService mapperService = createMapperService(fieldMapping(this::minimalMapping));
266+
DocumentMapper mapper = mapperService.documentMapper();
266267

267268
ParsedDocument parsedDoc = mapper.parse(source(b -> {
268269
b.startArray("field");
@@ -280,15 +281,66 @@ public void testIgnoreAbove() throws IOException {
280281
b.field("ignore_above", 10);
281282
}));
282283

283-
ParsedDocument newParsedDoc = newMapper.parse(source(b -> {
284+
parsedDoc = newMapper.parse(source(b -> {
284285
b.startArray("field");
285286
{
286287
b.startObject().field("key", "a longer then usual value").endObject();
287288
}
288289
b.endArray();
289290
}));
290-
IndexableField[] newFields = newParsedDoc.rootDoc().getFields("field");
291+
IndexableField[] newFields = parsedDoc.rootDoc().getFields("field");
291292
assertEquals(0, newFields.length);
293+
294+
// using a key bigger than ignore_above should not prevent the field from being indexed, although we store key:value pairs
295+
parsedDoc = newMapper.parse(source(b -> {
296+
b.startArray("field");
297+
{
298+
b.startObject().field("key_longer_than_10chars", "value").endObject();
299+
}
300+
b.endArray();
301+
}));
302+
newFields = parsedDoc.rootDoc().getFields("field");
303+
assertEquals(2, fields.length);
304+
}
305+
306+
/**
307+
* using a key:value pair above the Lucene term length limit would throw an error on indexing
308+
* that we pre-empt with a nices exception
309+
*/
310+
public void testImmenseKeyedTermException() throws IOException {
311+
DocumentMapper newMapper = createDocumentMapper(fieldMapping(b -> { b.field("type", "flattened"); }));
312+
313+
String longKey = new String(new char[32800]).replace('\0', 'x');
314+
MapperParsingException ex = expectThrows(MapperParsingException.class, () -> newMapper.parse(source(b -> {
315+
b.startArray("field");
316+
{
317+
b.startObject().field(longKey, "value").endObject();
318+
}
319+
b.endArray();
320+
})));
321+
assertEquals(
322+
"Flattened field [field] contains one immense field whose keyed encoding is longer "
323+
+ "than the allowed max length of 32766 bytes. Key length: "
324+
+ longKey.length()
325+
+ ", value length: 5 for key starting with [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]",
326+
ex.getCause().getMessage()
327+
);
328+
329+
String value = new String(new char[32800]).replace('\0', 'x');
330+
ex = expectThrows(MapperParsingException.class, () -> newMapper.parse(source(b -> {
331+
b.startArray("field");
332+
{
333+
b.startObject().field("key", value).endObject();
334+
}
335+
b.endArray();
336+
})));
337+
assertEquals(
338+
"Flattened field [field] contains one immense field whose keyed encoding is longer "
339+
+ "than the allowed max length of 32766 bytes. Key length: 3, value length: "
340+
+ value.length()
341+
+ " for key starting with [key]",
342+
ex.getCause().getMessage()
343+
);
292344
}
293345

294346
public void testNullValues() throws Exception {

0 commit comments

Comments
 (0)