Skip to content

Commit 6d02c02

Browse files
committed
[Mapper] Add ignore_missing option to timestamp
Related to #9049. By default, the default value for `timestamp` is `now` which means the date the document was processed by the indexing chain. You can now reject documents which not provide a `timestamp` value by setting `ignore_missing` to false (default to `true`): ```js { "tweet" : { "_timestamp" : { "enabled" : true, "ignore_missing" : false } } } ``` When you update the cluster to 1.5 or master, this index created with 1.4 we automatically migrate an index created with 1.4 to the 1.5 syntax. Let say you have defined this in elasticsearch 1.4.x: ```js DELETE test PUT test { "settings": { "number_of_shards": 1, "number_of_replicas": 0 } } PUT test/type/_mapping { "type" : { "_timestamp" : { "enabled" : true, "default" : null } } } ``` After migration, the mapping become: ```js { "test": { "mappings": { "type": { "_timestamp": { "enabled": true, "store": false, "ignore_missing": false }, "properties": {} } } } } ``` Closes #8882. (cherry picked from commit fb10346)
1 parent a2bcd49 commit 6d02c02

File tree

6 files changed

+206
-136
lines changed

6 files changed

+206
-136
lines changed

docs/reference/mapping/fields/timestamp-field.asciidoc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,20 @@ within the index request or in the `_source` document.
9191

9292
By default, the default value is `now` which means the date the document was processed by the indexing chain.
9393

94-
You can disable that default value by setting `default` to `null`. It means that `timestamp` is mandatory:
94+
You can reject documents which do not provide a `timestamp` value by setting `ignore_missing` to false (default to `true`):
9595

9696
[source,js]
9797
--------------------------------------------------
9898
{
9999
"tweet" : {
100100
"_timestamp" : {
101101
"enabled" : true,
102-
"default" : null
102+
"ignore_missing" : false
103103
}
104104
}
105105
}
106106
--------------------------------------------------
107107

108-
If you don't provide any timestamp value, indexation will fail.
109-
110108
You can also set the default value to any date respecting <<mapping-timestamp-field-format,timestamp format>>:
111109

112110
[source,js]
@@ -124,3 +122,10 @@ You can also set the default value to any date respecting <<mapping-timestamp-fi
124122

125123
If you don't provide any timestamp value, _timestamp will be set to this default value.
126124

125+
coming[1.5.0]
126+
127+
In elasticsearch 1.4, we allowed setting explicitly `"default":null` which is not possible anymore
128+
as we added a new `ignore_missing`setting.
129+
When reading an index created with elasticsearch 1.4 and using this, we automatically update it by
130+
removing `"default": null` and setting `"ignore_missing": false`
131+

src/main/java/org/elasticsearch/action/index/IndexRequest.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@
2121

2222
import com.google.common.base.Charsets;
2323
import org.elasticsearch.*;
24-
import org.elasticsearch.action.ActionRequest;
25-
import org.elasticsearch.action.ActionRequestValidationException;
26-
import org.elasticsearch.action.RoutingMissingException;
27-
import org.elasticsearch.action.TimestampParsingException;
28-
import org.elasticsearch.action.DocumentRequest;
24+
import org.elasticsearch.action.*;
2925
import org.elasticsearch.action.support.replication.ShardReplicationOperationRequest;
3026
import org.elasticsearch.client.Requests;
3127
import org.elasticsearch.cluster.metadata.MappingMetaData;
@@ -659,11 +655,13 @@ public void process(MetaData metaData, @Nullable MappingMetaData mappingMd, bool
659655
if (timestamp == null) {
660656
String defaultTimestamp = TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP;
661657
if (mappingMd != null && mappingMd.timestamp() != null) {
658+
// If we explicitly ask to reject null timestamp
659+
if (mappingMd.timestamp().ignoreMissing() != null && mappingMd.timestamp().ignoreMissing() == false) {
660+
throw new TimestampParsingException("timestamp is required by mapping");
661+
}
662662
defaultTimestamp = mappingMd.timestamp().defaultTimestamp();
663663
}
664-
if (!Strings.hasText(defaultTimestamp)) {
665-
throw new TimestampParsingException("timestamp is required by mapping");
666-
}
664+
667665
if (defaultTimestamp.equals(TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP)) {
668666
timestamp = Long.toString(System.currentTimeMillis());
669667
} else {

src/main/java/org/elasticsearch/cluster/metadata/MappingMetaData.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public static String parseStringTimestamp(String timestampAsString, FormatDateTi
177177

178178

179179
public static final Timestamp EMPTY = new Timestamp(false, null, TimestampFieldMapper.DEFAULT_DATE_TIME_FORMAT,
180-
TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP);
180+
TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP, null);
181181

182182
private final boolean enabled;
183183

@@ -191,7 +191,9 @@ public static String parseStringTimestamp(String timestampAsString, FormatDateTi
191191

192192
private final String defaultTimestamp;
193193

194-
public Timestamp(boolean enabled, String path, String format, String defaultTimestamp) {
194+
private final Boolean ignoreMissing;
195+
196+
public Timestamp(boolean enabled, String path, String format, String defaultTimestamp, Boolean ignoreMissing) {
195197
this.enabled = enabled;
196198
this.path = path;
197199
if (path == null) {
@@ -202,6 +204,7 @@ public Timestamp(boolean enabled, String path, String format, String defaultTime
202204
this.format = format;
203205
this.dateTimeFormatter = Joda.forPattern(format);
204206
this.defaultTimestamp = defaultTimestamp;
207+
this.ignoreMissing = ignoreMissing;
205208
}
206209

207210
public boolean enabled() {
@@ -232,6 +235,10 @@ public boolean hasDefaultTimestamp() {
232235
return this.defaultTimestamp != null;
233236
}
234237

238+
public Boolean ignoreMissing() {
239+
return ignoreMissing;
240+
}
241+
235242
public FormatDateTimeFormatter dateTimeFormatter() {
236243
return this.dateTimeFormatter;
237244
}
@@ -247,6 +254,7 @@ public boolean equals(Object o) {
247254
if (format != null ? !format.equals(timestamp.format) : timestamp.format != null) return false;
248255
if (path != null ? !path.equals(timestamp.path) : timestamp.path != null) return false;
249256
if (defaultTimestamp != null ? !defaultTimestamp.equals(timestamp.defaultTimestamp) : timestamp.defaultTimestamp != null) return false;
257+
if (ignoreMissing != null ? !ignoreMissing.equals(timestamp.ignoreMissing) : timestamp.ignoreMissing != null) return false;
250258
if (!Arrays.equals(pathElements, timestamp.pathElements)) return false;
251259

252260
return true;
@@ -260,6 +268,7 @@ public int hashCode() {
260268
result = 31 * result + (pathElements != null ? Arrays.hashCode(pathElements) : 0);
261269
result = 31 * result + (dateTimeFormatter != null ? dateTimeFormatter.hashCode() : 0);
262270
result = 31 * result + (defaultTimestamp != null ? defaultTimestamp.hashCode() : 0);
271+
result = 31 * result + (ignoreMissing != null ? ignoreMissing.hashCode() : 0);
263272
return result;
264273
}
265274
}
@@ -278,7 +287,9 @@ public MappingMetaData(DocumentMapper docMapper) {
278287
this.source = docMapper.mappingSource();
279288
this.id = new Id(docMapper.idFieldMapper().path());
280289
this.routing = new Routing(docMapper.routingFieldMapper().required(), docMapper.routingFieldMapper().path());
281-
this.timestamp = new Timestamp(docMapper.timestampFieldMapper().enabled(), docMapper.timestampFieldMapper().path(), docMapper.timestampFieldMapper().dateTimeFormatter().format(), docMapper.timestampFieldMapper().defaultTimestamp());
290+
this.timestamp = new Timestamp(docMapper.timestampFieldMapper().enabled(), docMapper.timestampFieldMapper().path(),
291+
docMapper.timestampFieldMapper().dateTimeFormatter().format(), docMapper.timestampFieldMapper().defaultTimestamp(),
292+
docMapper.timestampFieldMapper().ignoreMissing());
282293
this.hasParentField = docMapper.parentFieldMapper().active();
283294
}
284295

@@ -344,6 +355,7 @@ private void initMappers(Map<String, Object> withoutType) {
344355
String path = null;
345356
String format = TimestampFieldMapper.DEFAULT_DATE_TIME_FORMAT;
346357
String defaultTimestamp = TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP;
358+
Boolean ignoreMissing = null;
347359
Map<String, Object> timestampNode = (Map<String, Object>) withoutType.get("_timestamp");
348360
for (Map.Entry<String, Object> entry : timestampNode.entrySet()) {
349361
String fieldName = Strings.toUnderscoreCase(entry.getKey());
@@ -356,9 +368,11 @@ private void initMappers(Map<String, Object> withoutType) {
356368
format = fieldNode.toString();
357369
} else if (fieldName.equals("default") && fieldNode != null) {
358370
defaultTimestamp = fieldNode.toString();
371+
} else if (fieldName.equals("ignore_missing")) {
372+
ignoreMissing = nodeBooleanValue(fieldNode);
359373
}
360374
}
361-
this.timestamp = new Timestamp(enabled, path, format, defaultTimestamp);
375+
this.timestamp = new Timestamp(enabled, path, format, defaultTimestamp, ignoreMissing);
362376
} else {
363377
this.timestamp = Timestamp.EMPTY;
364378
}
@@ -554,6 +568,10 @@ public static void writeTo(MappingMetaData mappingMd, StreamOutput out) throws I
554568
out.writeBoolean(false);
555569
}
556570
}
571+
// TODO Remove the test in elasticsearch 2.0.0
572+
if (out.getVersion().onOrAfter(Version.V_1_5_0)) {
573+
out.writeOptionalBoolean(mappingMd.timestamp().ignoreMissing());
574+
}
557575
out.writeBoolean(mappingMd.hasParentField());
558576
}
559577

@@ -591,8 +609,19 @@ public static MappingMetaData readFrom(StreamInput in) throws IOException {
591609
// routing
592610
Routing routing = new Routing(in.readBoolean(), in.readBoolean() ? in.readString() : null);
593611
// timestamp
594-
Timestamp timestamp = new Timestamp(in.readBoolean(), in.readBoolean() ? in.readString() : null, in.readString(),
595-
in.getVersion().onOrAfter(Version.V_1_4_0_Beta1) ? (in.readBoolean() ? in.readString() : null) : TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP);
612+
613+
boolean enabled = in.readBoolean();
614+
String path = in.readOptionalString();
615+
String format = in.readString();
616+
String defaultTimestamp = in.getVersion().onOrAfter(Version.V_1_4_0_Beta1) ? (in.readBoolean() ? in.readString() : null) : TimestampFieldMapper.Defaults.DEFAULT_TIMESTAMP;
617+
Boolean ignoreMissing = null;
618+
619+
// TODO Remove the test in elasticsearch 2.0.0
620+
if (in.getVersion().onOrAfter(Version.V_1_5_0)) {
621+
ignoreMissing = in.readOptionalBoolean();
622+
}
623+
624+
final Timestamp timestamp = new Timestamp(enabled, path, format, defaultTimestamp, ignoreMissing);
596625
final boolean hasParentField = in.readBoolean();
597626
return new MappingMetaData(type, source, id, routing, timestamp, hasParentField);
598627
}

src/main/java/org/elasticsearch/index/mapper/internal/TimestampFieldMapper.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.lucene.document.Field;
2323
import org.apache.lucene.document.FieldType;
2424
import org.apache.lucene.document.NumericDocValuesField;
25+
import org.elasticsearch.Version;
26+
import org.elasticsearch.action.TimestampParsingException;
2527
import org.elasticsearch.common.Explicit;
2628
import org.elasticsearch.common.Nullable;
2729
import org.elasticsearch.common.Strings;
@@ -80,6 +82,7 @@ public static class Builder extends NumberFieldMapper.Builder<Builder, Timestamp
8082
private String path = Defaults.PATH;
8183
private FormatDateTimeFormatter dateTimeFormatter = Defaults.DATE_TIME_FORMATTER;
8284
private String defaultTimestamp = Defaults.DEFAULT_TIMESTAMP;
85+
private Boolean ignoreMissing = null;
8386

8487
public Builder() {
8588
super(Defaults.NAME, new FieldType(Defaults.FIELD_TYPE), Defaults.PRECISION_STEP_64_BIT);
@@ -105,6 +108,11 @@ public Builder defaultTimestamp(String defaultTimestamp) {
105108
return builder;
106109
}
107110

111+
public Builder ignoreMissing(boolean ignoreMissing) {
112+
this.ignoreMissing = ignoreMissing;
113+
return builder;
114+
}
115+
108116
@Override
109117
public TimestampFieldMapper build(BuilderContext context) {
110118
boolean roundCeil = Defaults.ROUND_CEIL;
@@ -113,6 +121,7 @@ public TimestampFieldMapper build(BuilderContext context) {
113121
roundCeil = settings.getAsBoolean("index.mapping.date.round_ceil", settings.getAsBoolean("index.mapping.date.parse_upper_inclusive", Defaults.ROUND_CEIL));
114122
}
115123
return new TimestampFieldMapper(fieldType, docValues, enabledState, path, dateTimeFormatter, defaultTimestamp, roundCeil,
124+
ignoreMissing,
116125
ignoreMalformed(context), coerce(context), postingsProvider, docValuesProvider, normsLoading, fieldDataSettings, context.indexSettings());
117126
}
118127
}
@@ -122,6 +131,8 @@ public static class TypeParser implements Mapper.TypeParser {
122131
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
123132
TimestampFieldMapper.Builder builder = timestamp();
124133
parseField(builder, builder.name, node, parserContext);
134+
boolean defaultSet = false;
135+
Boolean ignoreMissing = null;
125136
for (Map.Entry<String, Object> entry : node.entrySet()) {
126137
String fieldName = Strings.toUnderscoreCase(entry.getKey());
127138
Object fieldNode = entry.getValue();
@@ -134,8 +145,31 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
134145
builder.dateTimeFormatter(parseDateTimeFormatter(fieldNode.toString()));
135146
} else if (fieldName.equals("default")) {
136147
builder.defaultTimestamp(fieldNode == null ? null : fieldNode.toString());
148+
if (fieldNode == null) {
149+
if (parserContext.indexVersionCreated().onOrAfter(Version.V_1_4_0_Beta1) &&
150+
parserContext.indexVersionCreated().before(Version.V_1_5_0)) {
151+
// We are reading an index created in 1.4 with feature #7036
152+
// `default: null` was explicitly set. We need to change this index to
153+
// `ignore_missing: false`
154+
builder.ignoreMissing(false);
155+
} else {
156+
throw new TimestampParsingException("default timestamp can not be set to null");
157+
}
158+
} else {
159+
builder.defaultTimestamp(fieldNode.toString());
160+
defaultSet = true;
161+
}
162+
} else if (fieldName.equals("ignore_missing")) {
163+
ignoreMissing = nodeBooleanValue(fieldNode);
164+
builder.ignoreMissing(ignoreMissing);
137165
}
138166
}
167+
168+
// We can not accept a default value and rejecting null values at the same time
169+
if (defaultSet && (ignoreMissing != null && ignoreMissing == false)) {
170+
throw new TimestampParsingException("default timestamp can not be set with ignore_missing set to false");
171+
}
172+
139173
return builder;
140174
}
141175
}
@@ -145,14 +179,16 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
145179

146180
private final String path;
147181
private final String defaultTimestamp;
182+
private final Boolean ignoreMissing;
148183

149184
public TimestampFieldMapper(Settings indexSettings) {
150185
this(new FieldType(Defaults.FIELD_TYPE), null, Defaults.ENABLED, Defaults.PATH, Defaults.DATE_TIME_FORMATTER, Defaults.DEFAULT_TIMESTAMP,
151-
Defaults.ROUND_CEIL, Defaults.IGNORE_MALFORMED, Defaults.COERCE, null, null, null, null, indexSettings);
186+
Defaults.ROUND_CEIL, null, Defaults.IGNORE_MALFORMED, Defaults.COERCE, null, null, null, null, indexSettings);
152187
}
153188

154189
protected TimestampFieldMapper(FieldType fieldType, Boolean docValues, EnabledAttributeMapper enabledState, String path,
155190
FormatDateTimeFormatter dateTimeFormatter, String defaultTimestamp, boolean roundCeil,
191+
Boolean ignoreMissing,
156192
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce, PostingsFormatProvider postingsProvider,
157193
DocValuesFormatProvider docValuesProvider, Loading normsLoading,
158194
@Nullable Settings fieldDataSettings, Settings indexSettings) {
@@ -164,6 +200,7 @@ protected TimestampFieldMapper(FieldType fieldType, Boolean docValues, EnabledAt
164200
this.enabledState = enabledState;
165201
this.path = path;
166202
this.defaultTimestamp = defaultTimestamp;
203+
this.ignoreMissing = ignoreMissing;
167204
}
168205

169206
@Override
@@ -183,6 +220,10 @@ public String defaultTimestamp() {
183220
return this.defaultTimestamp;
184221
}
185222

223+
public Boolean ignoreMissing() {
224+
return this.ignoreMissing;
225+
}
226+
186227
public FormatDateTimeFormatter dateTimeFormatter() {
187228
return this.dateTimeFormatter;
188229
}
@@ -269,6 +310,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
269310
if (includeDefaults || !Defaults.DEFAULT_TIMESTAMP.equals(defaultTimestamp)) {
270311
builder.field("default", defaultTimestamp);
271312
}
313+
if (includeDefaults || ignoreMissing != null) {
314+
builder.field("ignore_missing", ignoreMissing);
315+
}
272316
if (customFieldDataSettings != null) {
273317
builder.field("fielddata", (Map) customFieldDataSettings.getAsMap());
274318
} else if (includeDefaults) {

0 commit comments

Comments
 (0)