Skip to content

Commit 5b0f267

Browse files
authored
Remove 'external values', and replace with swapped out XContentParsers (#72203) (#72448)
The majority of field mappers read a single value from their positioned XContentParser, and do not need to call nextToken. There is a general assumption that the same holds for any multifields defined on them, and so the XContentParser is passed down to their multifields builder as-is. This assumption does not hold for mappers that accept json objects, and so we have a second mechanism for passing values around called 'external values', where a mapper can set a specific value on its context and child mappers can then check for these external values before reading from xcontent. The disadvantage of this is that every field mapper now needs to check its context for external values. Because the values are defined by their java class, we can also know that in the vast majority of cases this functionality is unused. We have only two mappers that actually make use of this, CompletionFieldMapper and GeoPointFieldMapper. This commit removes external values entirely, and replaces it with the ability to pass a modified XContentParser to multifields. FieldMappers can just check the parser attached to their context for data and don't need to worry about multiple sources. Plugins implementing field mappers will need to take the removal of external values into account. Implementations that are passing structured objects as external values should instead use ParseContext.switchParser and wrap the objects using MapXContentParser.wrapObject(). GeoPointFieldMapper passes on a fake parser that just wraps its input data formatted as a geohash; CompletionFieldMapper has a slightly more complicated parser that in general wraps its metadata, but if textOrNull() is called without the parser being advanced just returns its text input. Relates to #56063
1 parent 78f18c0 commit 5b0f267

File tree

42 files changed

+487
-955
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+487
-955
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.common.xcontent;
10+
11+
import org.elasticsearch.common.CheckedFunction;
12+
13+
import java.io.IOException;
14+
import java.nio.CharBuffer;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.function.Supplier;
18+
19+
/**
20+
* Filters an existing XContentParser by using a delegate
21+
*/
22+
public abstract class FilterXContentParser implements XContentParser {
23+
24+
protected final XContentParser in;
25+
26+
protected FilterXContentParser(XContentParser in) {
27+
this.in = in;
28+
}
29+
30+
@Override
31+
public XContentType contentType() {
32+
return in.contentType();
33+
}
34+
35+
@Override
36+
public Token nextToken() throws IOException {
37+
return in.nextToken();
38+
}
39+
40+
@Override
41+
public void skipChildren() throws IOException {
42+
in.skipChildren();
43+
}
44+
45+
@Override
46+
public Token currentToken() {
47+
return in.currentToken();
48+
}
49+
50+
@Override
51+
public String currentName() throws IOException {
52+
return in.currentName();
53+
}
54+
55+
@Override
56+
public Map<String, Object> map() throws IOException {
57+
return in.map();
58+
}
59+
60+
@Override
61+
public Map<String, Object> mapOrdered() throws IOException {
62+
return in.mapOrdered();
63+
}
64+
65+
@Override
66+
public Map<String, String> mapStrings() throws IOException {
67+
return in.mapStrings();
68+
}
69+
70+
@Override
71+
public <T> Map<String, T> map(
72+
Supplier<Map<String, T>> mapFactory, CheckedFunction<XContentParser, T, IOException> mapValueParser) throws IOException {
73+
return in.map(mapFactory, mapValueParser);
74+
}
75+
76+
@Override
77+
public List<Object> list() throws IOException {
78+
return in.list();
79+
}
80+
81+
@Override
82+
public List<Object> listOrderedMap() throws IOException {
83+
return in.listOrderedMap();
84+
}
85+
86+
@Override
87+
public String text() throws IOException {
88+
return in.text();
89+
}
90+
91+
@Override
92+
public String textOrNull() throws IOException {
93+
return in.textOrNull();
94+
}
95+
96+
@Override
97+
public CharBuffer charBufferOrNull() throws IOException {
98+
return in.charBufferOrNull();
99+
}
100+
101+
@Override
102+
public CharBuffer charBuffer() throws IOException {
103+
return in.charBuffer();
104+
}
105+
106+
@Override
107+
public Object objectText() throws IOException {
108+
return in.objectText();
109+
}
110+
111+
@Override
112+
public Object objectBytes() throws IOException {
113+
return in.objectBytes();
114+
}
115+
116+
@Override
117+
public boolean hasTextCharacters() {
118+
return in.hasTextCharacters();
119+
}
120+
121+
@Override
122+
public char[] textCharacters() throws IOException {
123+
return in.textCharacters();
124+
}
125+
126+
@Override
127+
public int textLength() throws IOException {
128+
return in.textLength();
129+
}
130+
131+
@Override
132+
public int textOffset() throws IOException {
133+
return in.textOffset();
134+
}
135+
136+
@Override
137+
public Number numberValue() throws IOException {
138+
return in.numberValue();
139+
}
140+
141+
@Override
142+
public NumberType numberType() throws IOException {
143+
return in.numberType();
144+
}
145+
146+
@Override
147+
public short shortValue(boolean coerce) throws IOException {
148+
return in.shortValue(coerce);
149+
}
150+
151+
@Override
152+
public int intValue(boolean coerce) throws IOException {
153+
return in.intValue(coerce);
154+
}
155+
156+
@Override
157+
public long longValue(boolean coerce) throws IOException {
158+
return in.longValue(coerce);
159+
}
160+
161+
@Override
162+
public float floatValue(boolean coerce) throws IOException {
163+
return in.floatValue(coerce);
164+
}
165+
166+
@Override
167+
public double doubleValue(boolean coerce) throws IOException {
168+
return in.doubleValue(coerce);
169+
}
170+
171+
@Override
172+
public short shortValue() throws IOException {
173+
return in.shortValue();
174+
}
175+
176+
@Override
177+
public int intValue() throws IOException {
178+
return in.intValue();
179+
}
180+
181+
@Override
182+
public long longValue() throws IOException {
183+
return in.longValue();
184+
}
185+
186+
@Override
187+
public float floatValue() throws IOException {
188+
return in.floatValue();
189+
}
190+
191+
@Override
192+
public double doubleValue() throws IOException {
193+
return in.doubleValue();
194+
}
195+
196+
@Override
197+
public boolean isBooleanValue() throws IOException {
198+
return in.isBooleanValue();
199+
}
200+
201+
@Override
202+
public boolean booleanValue() throws IOException {
203+
return in.booleanValue();
204+
}
205+
206+
@Override
207+
public byte[] binaryValue() throws IOException {
208+
return in.binaryValue();
209+
}
210+
211+
@Override
212+
public XContentLocation getTokenLocation() {
213+
return in.getTokenLocation();
214+
}
215+
216+
@Override
217+
public <T> T namedObject(Class<T> categoryClass, String name, Object context) throws IOException {
218+
return in.namedObject(categoryClass, name, context);
219+
}
220+
221+
@Override
222+
public NamedXContentRegistry getXContentRegistry() {
223+
return in.getXContentRegistry();
224+
}
225+
226+
@Override
227+
public boolean isClosed() {
228+
return in.isClosed();
229+
}
230+
231+
@Override
232+
public void close() throws IOException {
233+
in.close();
234+
}
235+
236+
@Override
237+
public DeprecationHandler getDeprecationHandler() {
238+
return in.getDeprecationHandler();
239+
}
240+
}

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/MatchOnlyTextFieldMapper.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,7 @@ public FieldMapper.Builder getMergeBuilder() {
318318

319319
@Override
320320
protected void parseCreateField(ParseContext context) throws IOException {
321-
final String value;
322-
if (context.externalValueSet()) {
323-
value = context.externalValue().toString();
324-
} else {
325-
value = context.parser().textOrNull();
326-
}
321+
final String value = context.parser().textOrNull();
327322

328323
if (value == null) {
329324
return;

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeatureFieldMapper.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ public RankFeatureFieldType fieldType() {
136136
@Override
137137
protected void parseCreateField(ParseContext context) throws IOException {
138138
float value;
139-
if (context.externalValueSet()) {
140-
Object v = context.externalValue();
141-
value = objectToFloat(v);
142-
} else if (context.parser().currentToken() == Token.VALUE_NULL) {
139+
if (context.parser().currentToken() == Token.VALUE_NULL) {
143140
// skip
144141
return;
145142
} else {

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapper.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ name, new RankFeaturesFieldType(buildFullName(contentPath), meta.getValue(), pos
5757
}
5858
}
5959

60-
public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n));
60+
public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n), notInMultiFields(CONTENT_TYPE));
6161

6262
public static final class RankFeaturesFieldType extends MappedFieldType {
6363

@@ -118,9 +118,6 @@ public RankFeaturesFieldType fieldType() {
118118

119119
@Override
120120
public void parse(ParseContext context) throws IOException {
121-
if (context.externalValueSet()) {
122-
throw new IllegalArgumentException("[rank_features] fields can't be used in multi-fields");
123-
}
124121

125122
if (context.parser().currentToken() != Token.START_OBJECT) {
126123
throw new IllegalArgumentException("[rank_features] fields must be json objects, expected a START_OBJECT but got: " +

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,7 @@ protected void parseCreateField(ParseContext context) throws IOException {
330330
XContentParser parser = context.parser();
331331
Object value;
332332
Number numericValue = null;
333-
if (context.externalValueSet()) {
334-
value = context.externalValue();
335-
} else if (parser.currentToken() == Token.VALUE_NULL) {
333+
if (parser.currentToken() == Token.VALUE_NULL) {
336334
value = null;
337335
} else if (coerce.value()
338336
&& parser.currentToken() == Token.VALUE_STRING

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ public SearchAsYouTypeFieldMapper(String simpleName,
571571

572572
@Override
573573
protected void parseCreateField(ParseContext context) throws IOException {
574-
final String value = context.externalValueSet() ? context.externalValue().toString() : context.parser().textOrNull();
574+
final String value = context.parser().textOrNull();
575575
if (value == null) {
576576
return;
577577
}

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,7 @@ protected TokenCountFieldMapper(String simpleName, MappedFieldType defaultFieldT
111111

112112
@Override
113113
protected void parseCreateField(ParseContext context) throws IOException {
114-
final String value;
115-
if (context.externalValueSet()) {
116-
value = context.externalValue().toString();
117-
} else {
118-
value = context.parser().textOrNull();
119-
}
114+
final String value = context.parser().textOrNull();
120115

121116
if (value == null && nullValue == null) {
122117
return;

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/RankFeaturesFieldMapperTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Arrays;
2121
import java.util.Collection;
2222

23+
import static org.hamcrest.Matchers.containsString;
24+
2325
public class RankFeaturesFieldMapperTests extends MapperTestCase {
2426

2527
@Override
@@ -140,6 +142,18 @@ public void testRejectMultiValuedFields() throws MapperParsingException, IOExcep
140142
"the same document", e.getCause().getMessage());
141143
}
142144

145+
public void testCannotBeUsedInMultifields() {
146+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
147+
b.field("type", "keyword");
148+
b.startObject("fields");
149+
b.startObject("feature");
150+
b.field("type", "rank_features");
151+
b.endObject();
152+
b.endObject();
153+
})));
154+
assertThat(e.getMessage(), containsString("Field [feature] of type [rank_features] can't be used in multifields"));
155+
}
156+
143157
@Override
144158
protected Object generateRandomInputValue(MappedFieldType ft) {
145159
assumeFalse("Test implemented in a follow up", true);

plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -438,15 +438,11 @@ public FieldMapper.Builder getMergeBuilder() {
438438
@Override
439439
protected void parseCreateField(ParseContext context) throws IOException {
440440
final String value;
441-
if (context.externalValueSet()) {
442-
value = context.externalValue().toString();
441+
XContentParser parser = context.parser();
442+
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
443+
value = nullValue;
443444
} else {
444-
XContentParser parser = context.parser();
445-
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
446-
value = nullValue;
447-
} else {
448-
value = parser.textOrNull();
449-
}
445+
value = parser.textOrNull();
450446
}
451447

452448
if (value == null || value.length() > ignoreAbove) {

plugins/mapper-annotated-text/src/main/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapper.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -524,12 +524,7 @@ protected AnnotatedTextFieldMapper(String simpleName, FieldType fieldType, Annot
524524

525525
@Override
526526
protected void parseCreateField(ParseContext context) throws IOException {
527-
final String value;
528-
if (context.externalValueSet()) {
529-
value = context.externalValue().toString();
530-
} else {
531-
value = context.parser().textOrNull();
532-
}
527+
final String value = context.parser().textOrNull();
533528

534529
if (value == null) {
535530
return;

plugins/mapper-murmur3/src/main/java/org/elasticsearch/index/mapper/murmur3/Murmur3FieldMapper.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,7 @@ protected String contentType() {
124124
@Override
125125
protected void parseCreateField(ParseContext context)
126126
throws IOException {
127-
final Object value;
128-
if (context.externalValueSet()) {
129-
value = context.externalValue();
130-
} else {
131-
value = context.parser().textOrNull();
132-
}
127+
final String value = context.parser().textOrNull();
133128
if (value != null) {
134129
final BytesRef bytes = new BytesRef(value.toString());
135130
final long hash = MurmurHash3.hash128(bytes.bytes, bytes.offset, bytes.length, 0, new MurmurHash3.Hash128()).h1;

0 commit comments

Comments
 (0)