Skip to content

Commit 91e2330

Browse files
committed
Warn on badly-formed null values for date and IP field mappers (#62487)
In #57666 we changed when null_value was parsed for ip and date fields. Previously, the null value was stored as a string, and parsed into a date or InetAddress whenever a document containing a null value was encountered. Now, the values are parsed when the mappings are built, which means that bad values are detected up front; if you try and add a mapping with a badly-parsed ip or date for a null_value, the mapping will be rejected. This causes problems for upgrades in the case when you have a badly-formed null_value in a pre-7.9 cluster. This commit fixes the upgrade case by changing the logic to only logging a warning on the badly formed value, replicating the earlier behaviour. Fixes #62363
1 parent 4d272a2 commit 91e2330

File tree

7 files changed

+113
-208
lines changed

7 files changed

+113
-208
lines changed

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

+15-12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.elasticsearch.common.Nullable;
3737
import org.elasticsearch.common.geo.ShapeRelation;
3838
import org.elasticsearch.common.joda.Joda;
39+
import org.elasticsearch.common.logging.DeprecationLogger;
3940
import org.elasticsearch.common.lucene.BytesRefs;
4041
import org.elasticsearch.common.time.DateFormatter;
4142
import org.elasticsearch.common.time.DateFormatters;
@@ -73,6 +74,8 @@
7374
/** A {@link FieldMapper} for dates. */
7475
public final class DateFieldMapper extends ParametrizedFieldMapper {
7576

77+
private static final DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(DateFieldMapper.class);
78+
7679
public static final String CONTENT_TYPE = "date";
7780
public static final String DATE_NANOS_CONTENT_TYPE = "date_nanos";
7881
public static final DateFormatter DEFAULT_DATE_TIME_FORMATTER = DateFormatter.forPattern("strict_date_optional_time||epoch_millis");
@@ -205,8 +208,8 @@ public static class Builder extends ParametrizedFieldMapper.Builder {
205208
private final Resolution resolution;
206209
private final Version indexCreatedVersion;
207210

208-
public Builder(String name, Version indexCreatedVersion, Resolution resolution,
209-
DateFormatter dateFormatter, boolean ignoreMalformedByDefault) {
211+
public Builder(String name, Resolution resolution, DateFormatter dateFormatter,
212+
boolean ignoreMalformedByDefault, Version indexCreatedVersion) {
210213
super(name);
211214
this.resolution = resolution;
212215
this.indexCreatedVersion = indexCreatedVersion;
@@ -241,9 +244,10 @@ private Long parseNullValue(DateFieldType fieldType) {
241244
}
242245
try {
243246
return fieldType.parse(nullValue.getValue());
244-
}
245-
catch (Exception e) {
246-
throw new MapperParsingException("Error parsing [null_value] on field [" + name() + "]: " + e.getMessage(), e);
247+
} catch (Exception e) {
248+
DEPRECATION_LOGGER.deprecate("date_mapper_null_field", "Error parsing [" + nullValue.getValue()
249+
+ "] as date in [null_value] on field [" + name() + "]); [null_value] will be ignored");
250+
return null;
247251
}
248252
}
249253

@@ -254,18 +258,18 @@ public DateFieldMapper build(BuilderContext context) {
254258
ft.setBoost(boost.getValue());
255259
Long nullTimestamp = parseNullValue(ft);
256260
return new DateFieldMapper(name, ft, multiFieldsBuilder.build(this, context),
257-
copyTo.build(), nullTimestamp, resolution, indexCreatedVersion, this);
261+
copyTo.build(), nullTimestamp, resolution, this);
258262
}
259263
}
260264

261265
public static final TypeParser MILLIS_PARSER = new TypeParser((n, c) -> {
262266
boolean ignoreMalformedByDefault = IGNORE_MALFORMED_SETTING.get(c.getSettings());
263-
return new Builder(n, c.indexVersionCreated(), Resolution.MILLISECONDS, c.getDateFormatter(), ignoreMalformedByDefault);
267+
return new Builder(n, Resolution.MILLISECONDS, c.getDateFormatter(), ignoreMalformedByDefault, c.indexVersionCreated());
264268
});
265269

266270
public static final TypeParser NANOS_PARSER = new TypeParser((n, c) -> {
267271
boolean ignoreMalformedByDefault = IGNORE_MALFORMED_SETTING.get(c.getSettings());
268-
return new Builder(n, c.indexVersionCreated(), Resolution.NANOSECONDS, c.getDateFormatter(), ignoreMalformedByDefault);
272+
return new Builder(n, Resolution.NANOSECONDS, c.getDateFormatter(), ignoreMalformedByDefault, c.indexVersionCreated());
269273
});
270274

271275
public static final class DateFieldType extends MappedFieldType {
@@ -525,9 +529,9 @@ public DocValueFormat docValueFormat(@Nullable String format, ZoneId timeZone) {
525529
private final Long nullValue;
526530
private final String nullValueAsString;
527531
private final Resolution resolution;
528-
private final Version indexCreatedVersion;
529532

530533
private final boolean ignoreMalformedByDefault;
534+
private final Version indexCreatedVersion;
531535

532536
private DateFieldMapper(
533537
String simpleName,
@@ -536,7 +540,6 @@ private DateFieldMapper(
536540
CopyTo copyTo,
537541
Long nullValue,
538542
Resolution resolution,
539-
Version indexCreatedVersion,
540543
Builder builder) {
541544
super(simpleName, mappedFieldType, multiFields, copyTo);
542545
this.store = builder.store.getValue();
@@ -548,13 +551,13 @@ private DateFieldMapper(
548551
this.nullValueAsString = builder.nullValue.getValue();
549552
this.nullValue = nullValue;
550553
this.resolution = resolution;
551-
this.indexCreatedVersion = indexCreatedVersion;
552554
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue();
555+
this.indexCreatedVersion = builder.indexCreatedVersion;
553556
}
554557

555558
@Override
556559
public ParametrizedFieldMapper.Builder getMergeBuilder() {
557-
return new Builder(simpleName(), indexCreatedVersion, resolution, null, ignoreMalformedByDefault).init(this);
560+
return new Builder(simpleName(), resolution, null, ignoreMalformedByDefault, indexCreatedVersion).init(this);
558561
}
559562

560563
@Override

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,8 @@ private static Mapper.Builder<?> createBuilderFromDynamicValue(final ParseContex
696696
= context.root().findTemplateBuilder(context, currentFieldName, dateTimeFormatter);
697697
if (builder == null) {
698698
boolean ignoreMalformed = IGNORE_MALFORMED_SETTING.get(context.indexSettings().getSettings());
699-
Version indexCreatedVersion = context.indexSettings().getIndexVersionCreated();
700-
builder = new DateFieldMapper.Builder(currentFieldName, indexCreatedVersion,
701-
DateFieldMapper.Resolution.MILLISECONDS, dateTimeFormatter, ignoreMalformed);
699+
builder = new DateFieldMapper.Builder(currentFieldName, DateFieldMapper.Resolution.MILLISECONDS,
700+
dateTimeFormatter, ignoreMalformed, Version.indexCreated(context.indexSettings().getSettings()));
702701
}
703702
return builder;
704703

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

+33-17
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@
3030
import org.apache.lucene.search.TermQuery;
3131
import org.apache.lucene.util.ArrayUtil;
3232
import org.apache.lucene.util.BytesRef;
33+
import org.elasticsearch.Version;
3334
import org.elasticsearch.common.Nullable;
3435
import org.elasticsearch.common.collect.Tuple;
36+
import org.elasticsearch.common.logging.DeprecationLogger;
3537
import org.elasticsearch.common.network.InetAddresses;
36-
import org.elasticsearch.common.network.NetworkAddress;
3738
import org.elasticsearch.index.fielddata.IndexFieldData;
3839
import org.elasticsearch.index.fielddata.ScriptDocValues;
3940
import org.elasticsearch.index.fielddata.plain.SortedSetOrdinalsIndexFieldData;
@@ -55,6 +56,8 @@
5556
/** A {@link FieldMapper} for ip addresses. */
5657
public class IpFieldMapper extends ParametrizedFieldMapper {
5758

59+
private static final DeprecationLogger DEPRECATION_LOGGER = DeprecationLogger.getLogger(IpFieldMapper.class);
60+
5861
public static final String CONTENT_TYPE = "ip";
5962

6063
private static IpFieldMapper toType(FieldMapper in) {
@@ -68,36 +71,44 @@ public static class Builder extends ParametrizedFieldMapper.Builder {
6871
private final Parameter<Boolean> stored = Parameter.storeParam(m -> toType(m).stored, false);
6972

7073
private final Parameter<Boolean> ignoreMalformed;
71-
private final Parameter<InetAddress> nullValue = new Parameter<>("null_value", false, () -> null,
72-
(n, c, o) -> o == null ? null : InetAddresses.forString(o.toString()), m -> toType(m).nullValue)
73-
.setSerializer((b, f, v) -> {
74-
if (v == null) {
75-
b.nullField(f);
76-
} else {
77-
b.field(f, InetAddresses.toAddrString(v));
78-
}
79-
}, NetworkAddress::format)
80-
.acceptsNull();
74+
private final Parameter<String> nullValue
75+
= Parameter.stringParam("null_value", false, m -> toType(m).nullValueAsString, null).acceptsNull();
8176

8277
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
8378

8479
private final boolean ignoreMalformedByDefault;
80+
private final Version indexCreatedVersion;
8581

86-
public Builder(String name, boolean ignoreMalformedByDefault) {
82+
public Builder(String name, boolean ignoreMalformedByDefault, Version indexCreatedVersion) {
8783
super(name);
8884
this.ignoreMalformedByDefault = ignoreMalformedByDefault;
85+
this.indexCreatedVersion = indexCreatedVersion;
8986
this.ignoreMalformed
9087
= Parameter.boolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
9188
}
9289

93-
Builder nullValue(InetAddress nullValue) {
90+
Builder nullValue(String nullValue) {
9491
this.nullValue.setValue(nullValue);
9592
return this;
9693
}
9794

95+
private InetAddress parseNullValue() {
96+
String nullValueAsString = nullValue.getValue();
97+
if (nullValueAsString == null) {
98+
return null;
99+
}
100+
try {
101+
return InetAddresses.forString(nullValueAsString);
102+
} catch (Exception e) {
103+
DEPRECATION_LOGGER.deprecate("ip_mapper_null_field", "Error parsing [" + nullValue.getValue()
104+
+ "] as IP in [null_value] on field [" + name() + "]); [null_value] will be ignored");
105+
return null;
106+
}
107+
}
108+
98109
@Override
99110
protected List<Parameter<?>> getParameters() {
100-
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, nullValue);
111+
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, nullValue, meta);
101112
}
102113

103114
@Override
@@ -111,7 +122,7 @@ public IpFieldMapper build(BuilderContext context) {
111122

112123
public static final TypeParser PARSER = new TypeParser((n, c) -> {
113124
boolean ignoreMalformedByDefault = IGNORE_MALFORMED_SETTING.get(c.getSettings());
114-
return new Builder(n, ignoreMalformedByDefault);
125+
return new Builder(n, ignoreMalformedByDefault, c.indexVersionCreated());
115126
});
116127

117128
public static final class IpFieldType extends SimpleMappedFieldType {
@@ -322,9 +333,12 @@ public DocValueFormat docValueFormat(@Nullable String format, ZoneId timeZone) {
322333
private final boolean hasDocValues;
323334
private final boolean stored;
324335
private final boolean ignoreMalformed;
336+
325337
private final InetAddress nullValue;
338+
private final String nullValueAsString;
326339

327340
private final boolean ignoreMalformedByDefault;
341+
private final Version indexCreatedVersion;
328342

329343
private IpFieldMapper(
330344
String simpleName,
@@ -338,7 +352,9 @@ private IpFieldMapper(
338352
this.hasDocValues = builder.hasDocValues.getValue();
339353
this.stored = builder.stored.getValue();
340354
this.ignoreMalformed = builder.ignoreMalformed.getValue();
341-
this.nullValue = builder.nullValue.getValue();
355+
this.nullValue = builder.parseNullValue();
356+
this.nullValueAsString = builder.nullValue.getValue();
357+
this.indexCreatedVersion = builder.indexCreatedVersion;
342358
}
343359

344360
@Override
@@ -424,6 +440,6 @@ protected Object parseSourceValue(Object value) {
424440

425441
@Override
426442
public ParametrizedFieldMapper.Builder getMergeBuilder() {
427-
return new Builder(simpleName(), ignoreMalformedByDefault).init(this);
443+
return new Builder(simpleName(), ignoreMalformedByDefault, indexCreatedVersion).init(this);
428444
}
429445
}

server/src/test/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverServiceTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ public void testRolloverClusterStateForDataStream() throws Exception {
554554
try {
555555
Mapper.BuilderContext builderContext = new Mapper.BuilderContext(Settings.EMPTY, new ContentPath(0));
556556
DateFieldMapper dateFieldMapper
557-
= new DateFieldMapper.Builder("@timestamp", Version.CURRENT, DateFieldMapper.Resolution.MILLISECONDS, null, false)
557+
= new DateFieldMapper.Builder("@timestamp", DateFieldMapper.Resolution.MILLISECONDS, null, false, Version.CURRENT)
558558
.build(builderContext);
559559
MetadataFieldMapper mockedTimestampField = mock(MetadataFieldMapper.class);
560560
when(mockedTimestampField.name()).thenReturn("_data_stream_timestamp");

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

+5-9
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import java.util.Map;
4141

4242
import static org.hamcrest.Matchers.containsString;
43-
import static org.hamcrest.Matchers.equalTo;
4443
import static org.hamcrest.Matchers.notNullValue;
4544

4645
public class DateFieldMapperTests extends MapperTestCase {
@@ -221,14 +220,10 @@ public void testNanosNullValue() throws IOException {
221220
assertFalse(dvField.fieldType().stored());
222221
}
223222

224-
public void testBadNullValue() {
223+
public void testBadNullValue() throws IOException {
224+
createDocumentMapper(fieldMapping(b -> b.field("type", "date").field("null_value", "foo")));
225225

226-
MapperParsingException e = expectThrows(MapperParsingException.class,
227-
() -> createDocumentMapper(fieldMapping(b -> b.field("type", "date").field("null_value", ""))));
228-
229-
assertThat(e.getMessage(),
230-
equalTo("Failed to parse mapping [_doc]: Error parsing [null_value] on field [field]: cannot parse empty date"));
231-
}
226+
assertWarnings("Error parsing [foo] as date in [null_value] on field [field]); [null_value] will be ignored"); }
232227

233228
public void testNullConfigValuesFail() {
234229
Exception e = expectThrows(MapperParsingException.class,
@@ -364,7 +359,8 @@ private DateFieldMapper createMapper(Resolution resolution, String format, Strin
364359
mapping.put("null_value", nullValue);
365360
}
366361

367-
DateFieldMapper.Builder builder = new DateFieldMapper.Builder("field", Version.CURRENT, resolution, null, false);
362+
DateFieldMapper.Builder builder
363+
= new DateFieldMapper.Builder("field", resolution, null, false, Version.CURRENT);
368364
builder.parse("field", null, mapping);
369365
return builder.build(context);
370366
}

0 commit comments

Comments
 (0)