Skip to content

Commit 3937b20

Browse files
authored
Convert RangeFieldMapper to parametrized form (#62058)
This also adds the ability to define a serialization check on Parameters, used in this case to only serialize format and locale parameters if the mapper is a date range.
1 parent ba39f46 commit 3937b20

File tree

8 files changed

+130
-136
lines changed

8 files changed

+130
-136
lines changed

modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ static BinaryFieldMapper createQueryBuilderFieldBuilder(BuilderContext context)
161161
}
162162

163163
static RangeFieldMapper createExtractedRangeFieldBuilder(String name, RangeType rangeType, BuilderContext context) {
164-
RangeFieldMapper.Builder builder = new RangeFieldMapper.Builder(name, rangeType);
164+
RangeFieldMapper.Builder builder = new RangeFieldMapper.Builder(name, rangeType, true);
165165
// For now no doc values, because in processQuery(...) only the Lucene range fields get added:
166166
builder.docValues(false);
167167
return builder.build(context);

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Objects;
4343
import java.util.Set;
4444
import java.util.function.BiFunction;
45+
import java.util.function.BooleanSupplier;
4546
import java.util.function.Consumer;
4647
import java.util.function.Function;
4748
import java.util.function.Supplier;
@@ -145,6 +146,7 @@ public static final class Parameter<T> {
145146
private boolean acceptsNull = false;
146147
private Consumer<T> validator = null;
147148
private Serializer<T> serializer = XContentBuilder::field;
149+
private BooleanSupplier serializerPredicate = () -> true;
148150
private Function<T, String> conflictSerializer = Object::toString;
149151
private T value;
150152
private boolean isSet;
@@ -229,6 +231,15 @@ public Parameter<T> setSerializer(Serializer<T> serializer, Function<T, String>
229231
return this;
230232
}
231233

234+
/**
235+
* Sets an additional check on whether or not this parameter should be serialized,
236+
* after the existing 'set' and 'include_defaults' checks.
237+
*/
238+
public Parameter<T> setShouldSerialize(BooleanSupplier shouldSerialize) {
239+
this.serializerPredicate = shouldSerialize;
240+
return this;
241+
}
242+
232243
private void validate() {
233244
if (validator != null) {
234245
validator.accept(getValue());
@@ -254,7 +265,7 @@ private void merge(FieldMapper toMerge, Conflicts conflicts) {
254265
}
255266

256267
private void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
257-
if (includeDefaults || isConfigured()) {
268+
if ((includeDefaults || isConfigured()) && serializerPredicate.getAsBoolean()) {
258269
serializer.serialize(builder, name, getValue());
259270
}
260271
}

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

+75-119
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
package org.elasticsearch.index.mapper;
2121

22-
import org.apache.lucene.document.FieldType;
23-
import org.apache.lucene.index.IndexOptions;
2422
import org.apache.lucene.index.Term;
2523
import org.apache.lucene.search.BoostQuery;
2624
import org.apache.lucene.search.DocValuesFieldExistsQuery;
@@ -34,12 +32,11 @@
3432
import org.elasticsearch.common.lucene.Lucene;
3533
import org.elasticsearch.common.network.InetAddresses;
3634
import org.elasticsearch.common.settings.Setting;
35+
import org.elasticsearch.common.settings.Settings;
3736
import org.elasticsearch.common.time.DateFormatter;
3837
import org.elasticsearch.common.time.DateMathParser;
3938
import org.elasticsearch.common.util.LocaleUtils;
40-
import org.elasticsearch.common.xcontent.XContentBuilder;
4139
import org.elasticsearch.common.xcontent.XContentParser;
42-
import org.elasticsearch.common.xcontent.support.XContentMapValues;
4340
import org.elasticsearch.index.fielddata.IndexFieldData;
4441
import org.elasticsearch.index.fielddata.plain.BinaryIndexFieldData;
4542
import org.elasticsearch.index.query.QueryShardContext;
@@ -55,7 +52,6 @@
5552
import java.util.Collections;
5653
import java.util.HashMap;
5754
import java.util.HashSet;
58-
import java.util.Iterator;
5955
import java.util.List;
6056
import java.util.Locale;
6157
import java.util.Map;
@@ -69,117 +65,87 @@
6965
import static org.elasticsearch.index.query.RangeQueryBuilder.LT_FIELD;
7066

7167
/** A {@link FieldMapper} for indexing numeric and date ranges, and creating queries */
72-
public class RangeFieldMapper extends FieldMapper {
68+
public class RangeFieldMapper extends ParametrizedFieldMapper {
7369
public static final boolean DEFAULT_INCLUDE_UPPER = true;
7470
public static final boolean DEFAULT_INCLUDE_LOWER = true;
7571

7672
public static class Defaults {
7773
public static final Explicit<Boolean> COERCE = new Explicit<>(true, false);
78-
public static final FieldType FIELD_TYPE = new FieldType();
79-
static {
80-
FIELD_TYPE.setStored(false);
81-
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
82-
FIELD_TYPE.freeze();
83-
}
8474
public static final DateFormatter DATE_FORMATTER = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER;
8575
}
8676

8777
// this is private since it has a different default
8878
static final Setting<Boolean> COERCE_SETTING =
8979
Setting.boolSetting("index.mapping.coerce", true, Setting.Property.IndexScope);
9080

91-
public static class Builder extends FieldMapper.Builder<Builder> {
92-
private Boolean coerce;
93-
private Locale locale = Locale.ROOT;
94-
private String pattern;
81+
private static RangeFieldMapper toType(FieldMapper in) {
82+
return (RangeFieldMapper) in;
83+
}
84+
85+
public static class Builder extends ParametrizedFieldMapper.Builder {
86+
87+
private final Parameter<Boolean> index = Parameter.indexParam(m -> toType(m).index, true);
88+
private final Parameter<Boolean> hasDocValues = Parameter.docValuesParam(m -> toType(m).hasDocValues, true);
89+
private final Parameter<Boolean> store = Parameter.storeParam(m -> toType(m).store, false);
90+
private final Parameter<Explicit<Boolean>> coerce;
91+
private final Parameter<String> format
92+
= Parameter.stringParam("format", false, m -> toType(m).format, Defaults.DATE_FORMATTER.pattern());
93+
private final Parameter<Locale> locale = new Parameter<>("locale", false, () -> Locale.ROOT,
94+
(n, c, o) -> LocaleUtils.parse(o.toString()), m -> toType(m).locale);
95+
private final Parameter<Float> boost = Parameter.boostParam();
96+
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
97+
9598
private final RangeType type;
9699

97-
public Builder(String name, RangeType type) {
98-
super(name, Defaults.FIELD_TYPE);
99-
this.type = type;
100-
builder = this;
100+
public Builder(String name, RangeType type, Settings settings) {
101+
this(name, type, COERCE_SETTING.get(settings));
101102
}
102103

103-
public Builder coerce(boolean coerce) {
104-
this.coerce = coerce;
105-
return builder;
104+
public Builder(String name, RangeType type, boolean coerceByDefault) {
105+
super(name);
106+
this.type = type;
107+
this.coerce = Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
108+
if (this.type != RangeType.DATE) {
109+
format.setShouldSerialize(() -> false);
110+
locale.setShouldSerialize(() -> false);
111+
}
106112
}
107113

108-
protected Explicit<Boolean> coerce(BuilderContext context) {
109-
if (coerce != null) {
110-
return new Explicit<>(coerce, true);
111-
}
112-
if (context.indexSettings() != null) {
113-
return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
114-
}
115-
return Defaults.COERCE;
114+
public void docValues(boolean hasDocValues) {
115+
this.hasDocValues.setValue(hasDocValues);
116116
}
117117

118-
public Builder format(String format) {
119-
this.pattern = format;
118+
Builder format(String format) {
119+
this.format.setValue(format);
120120
return this;
121121
}
122122

123-
public void locale(Locale locale) {
124-
this.locale = locale;
123+
@Override
124+
protected List<Parameter<?>> getParameters() {
125+
return List.of(index, hasDocValues, store, coerce, format, locale, boost, meta);
125126
}
126127

127128
protected RangeFieldType setupFieldType(BuilderContext context) {
128-
if (pattern != null) {
129+
if (format.isConfigured()) {
129130
if (type != RangeType.DATE) {
130131
throw new IllegalArgumentException("field [" + name() + "] of type [range]"
131132
+ " should not define a dateTimeFormatter unless it is a " + RangeType.DATE + " type");
132133
}
133-
return new RangeFieldType(buildFullName(context), indexed, hasDocValues,
134-
DateFormatter.forPattern(pattern).withLocale(locale), meta);
134+
return new RangeFieldType(buildFullName(context), index.getValue(), hasDocValues.getValue(),
135+
DateFormatter.forPattern(format.getValue()).withLocale(locale.getValue()), meta.getValue());
135136
}
136137
if (type == RangeType.DATE) {
137-
return new RangeFieldType(buildFullName(context), indexed, hasDocValues, Defaults.DATE_FORMATTER, meta);
138+
return new RangeFieldType(buildFullName(context), index.getValue(), hasDocValues.getValue(),
139+
Defaults.DATE_FORMATTER, meta.getValue());
138140
}
139-
return new RangeFieldType(buildFullName(context), type, indexed, hasDocValues, meta);
141+
return new RangeFieldType(buildFullName(context), type, index.getValue(), hasDocValues.getValue(), meta.getValue());
140142
}
141143

142144
@Override
143145
public RangeFieldMapper build(BuilderContext context) {
144-
setupFieldType(context);
145-
return new RangeFieldMapper(name, fieldType, setupFieldType(context), coerce(context),
146-
multiFieldsBuilder.build(this, context), copyTo);
147-
}
148-
}
149-
150-
public static class TypeParser implements Mapper.TypeParser {
151-
final RangeType type;
152-
153-
public TypeParser(RangeType type) {
154-
this.type = type;
155-
}
156-
157-
@Override
158-
public Mapper.Builder<?> parse(String name, Map<String, Object> node,
159-
ParserContext parserContext) throws MapperParsingException {
160-
Builder builder = new Builder(name, type);
161-
TypeParsers.parseField(builder, name, node, parserContext);
162-
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
163-
Map.Entry<String, Object> entry = iterator.next();
164-
String propName = entry.getKey();
165-
Object propNode = entry.getValue();
166-
if (propName.equals("null_value")) {
167-
throw new MapperParsingException("Property [null_value] is not supported for [" + this.type.name
168-
+ "] field types.");
169-
} else if (propName.equals("coerce")) {
170-
builder.coerce(XContentMapValues.nodeBooleanValue(propNode, name + ".coerce"));
171-
iterator.remove();
172-
} else if (propName.equals("locale")) {
173-
builder.locale(LocaleUtils.parse(propNode.toString()));
174-
iterator.remove();
175-
} else if (propName.equals("format")) {
176-
builder.format(propNode.toString());
177-
iterator.remove();
178-
} else if (TypeParsers.parseMultiField(builder::addMultiField, name, parserContext, propName, propNode)) {
179-
iterator.remove();
180-
}
181-
}
182-
return builder;
146+
RangeFieldType ft = setupFieldType(context);
147+
ft.setBoost(boost.getValue());
148+
return new RangeFieldMapper(name, ft, multiFieldsBuilder.build(this, context), copyTo.build(), type, this);
183149
}
184150
}
185151

@@ -281,17 +247,37 @@ public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower
281247
}
282248
}
283249

284-
private Explicit<Boolean> coerce;
250+
private final RangeType type;
251+
private final boolean index;
252+
private final boolean hasDocValues;
253+
private final boolean store;
254+
private final Explicit<Boolean> coerce;
255+
private final String format;
256+
private final Locale locale;
257+
258+
private final boolean coerceByDefault;
285259

286260
private RangeFieldMapper(
287261
String simpleName,
288-
FieldType fieldType,
289262
MappedFieldType mappedFieldType,
290-
Explicit<Boolean> coerce,
291263
MultiFields multiFields,
292-
CopyTo copyTo) {
293-
super(simpleName, fieldType, mappedFieldType, multiFields, copyTo);
294-
this.coerce = coerce;
264+
CopyTo copyTo,
265+
RangeType type,
266+
Builder builder) {
267+
super(simpleName, mappedFieldType, multiFields, copyTo);
268+
this.type = type;
269+
this.index = builder.index.getValue();
270+
this.hasDocValues = builder.hasDocValues.getValue();
271+
this.store = builder.store.getValue();
272+
this.coerce = builder.coerce.getValue();
273+
this.format = builder.format.getValue();
274+
this.locale = builder.locale.getValue();
275+
this.coerceByDefault = builder.coerce.getDefaultValue().value();
276+
}
277+
278+
@Override
279+
public ParametrizedFieldMapper.Builder getMergeBuilder() {
280+
return new Builder(simpleName(), type, coerceByDefault).init(this);
295281
}
296282

297283
@Override
@@ -366,12 +352,9 @@ protected void parseCreateField(ParseContext context) throws IOException {
366352
+ name() + "], expected an object but got " + parser.currentName());
367353
}
368354
}
369-
boolean docValued = fieldType().hasDocValues();
370-
boolean indexed = fieldType().isSearchable();
371-
boolean stored = fieldType.stored();
372-
context.doc().addAll(fieldType().rangeType.createFields(context, name(), range, indexed, docValued, stored));
355+
context.doc().addAll(fieldType().rangeType.createFields(context, name(), range, index, hasDocValues, store));
373356

374-
if (docValued == false && (indexed || stored)) {
357+
if (hasDocValues == false && (index || store)) {
375358
createFieldNamesField(context);
376359
}
377360
}
@@ -407,33 +390,6 @@ protected Object parseSourceValue(Object value) {
407390
};
408391
}
409392

410-
@Override
411-
protected void mergeOptions(FieldMapper other, List<String> conflicts) {
412-
RangeFieldMapper mergeWith = (RangeFieldMapper) other;
413-
if (mergeWith.coerce.explicit()) {
414-
this.coerce = mergeWith.coerce;
415-
}
416-
}
417-
418-
@Override
419-
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
420-
super.doXContentBody(builder, includeDefaults, params);
421-
422-
if (fieldType().rangeType == RangeType.DATE
423-
&& (includeDefaults || (fieldType().dateTimeFormatter() != null
424-
&& fieldType().dateTimeFormatter().pattern().equals(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.pattern()) == false))) {
425-
builder.field("format", fieldType().dateTimeFormatter().pattern());
426-
}
427-
if (fieldType().rangeType == RangeType.DATE
428-
&& (includeDefaults || (fieldType().dateTimeFormatter() != null
429-
&& fieldType().dateTimeFormatter().locale() != Locale.ROOT))) {
430-
builder.field("locale", fieldType().dateTimeFormatter().locale());
431-
}
432-
if (includeDefaults || coerce.explicit()) {
433-
builder.field("coerce", coerce.value());
434-
}
435-
}
436-
437393
private static Range parseIpRangeFromCidr(final XContentParser parser) throws IOException {
438394
final Tuple<InetAddress, Integer> cidr = InetAddresses.parseCidr(parser.text());
439395
// create the lower value by zeroing out the host portion, upper value by filling it with all ones.
@@ -456,8 +412,8 @@ public static class Range {
456412
RangeType type;
457413
Object from;
458414
Object to;
459-
private boolean includeFrom;
460-
private boolean includeTo;
415+
private final boolean includeFrom;
416+
private final boolean includeTo;
461417

462418
public Range(RangeType type, Object from, Object to, boolean includeFrom, boolean includeTo) {
463419
this.type = type;

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

+4
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,10 @@ public boolean isNumeric() {
706706
public abstract Query dvRangeQuery(String field, BinaryDocValuesRangeQuery.QueryType queryType, Object from, Object to,
707707
boolean includeFrom, boolean includeTo);
708708

709+
public final Mapper.TypeParser parser() {
710+
return new ParametrizedFieldMapper.TypeParser((n, c) -> new RangeFieldMapper.Builder(n, this, c.getSettings()));
711+
}
712+
709713
public final String name;
710714
private final NumberFieldMapper.NumberType numberType;
711715
public final LengthType lengthType;

server/src/main/java/org/elasticsearch/indices/IndicesModule.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import org.elasticsearch.index.mapper.NestedPathFieldMapper;
4646
import org.elasticsearch.index.mapper.NumberFieldMapper;
4747
import org.elasticsearch.index.mapper.ObjectMapper;
48-
import org.elasticsearch.index.mapper.RangeFieldMapper;
4948
import org.elasticsearch.index.mapper.RangeType;
5049
import org.elasticsearch.index.mapper.RoutingFieldMapper;
5150
import org.elasticsearch.index.mapper.SeqNoFieldMapper;
@@ -108,7 +107,7 @@ public static Map<String, Mapper.TypeParser> getMappers(List<MapperPlugin> mappe
108107
mappers.put(type.typeName(), type.parser());
109108
}
110109
for (RangeType type : RangeType.values()) {
111-
mappers.put(type.typeName(), new RangeFieldMapper.TypeParser(type));
110+
mappers.put(type.typeName(), type.parser());
112111
}
113112
mappers.put(BooleanFieldMapper.CONTENT_TYPE, BooleanFieldMapper.PARSER);
114113
mappers.put(BinaryFieldMapper.CONTENT_TYPE, BinaryFieldMapper.PARSER);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void testFetchSourceValue() {
9090
Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT.id).build();
9191
Mapper.BuilderContext context = new Mapper.BuilderContext(settings, new ContentPath());
9292

93-
RangeFieldMapper mapper = new RangeFieldMapper.Builder("field", RangeType.IP).build(context);
93+
RangeFieldMapper mapper = new RangeFieldMapper.Builder("field", RangeType.IP, true).build(context);
9494
Map<String, Object> range = Map.of("gte", "2001:db8:0:0:0:0:2:1");
9595
assertEquals(List.of(Map.of("gte", "2001:db8::2:1")), fetchSourceValue(mapper, range));
9696
assertEquals(List.of("2001:db8::2:1/32"), fetchSourceValue(mapper, "2001:db8:0:0:0:0:2:1/32"));

0 commit comments

Comments
 (0)