Mapping With a `null` Default Timestamp Causes NullPointerException on Merge

I have a field with a `null` [default `_timestamp` value](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-timestamp-field.html#mapping-timestamp-field-default) and when I try to update the mapping I get a server error caused by a `NullPointerException`

```
[2015-01-08 17:28:56,040][DEBUG][action.admin.indices.mapping.put] [...] failed to put mappings on indices [[feed_170_v1, feed_204_v1, feed_229_v1, feed_232_v1, feed_239_v1, feed_248_v1, feed_268_v1, feed_256_v1, feed_272_v1, feed_159_v1, feed_255_v1, feed_164_v1, feed_259_v1, feed_266_v1, feed_188_v1, feed_240_v1, feed_233_v1, feed_13_v1, feed_184_v1, feed_261_v1, feed_267_v1, feed_271_v1, feed_257_v1, feed_172_v1, feed_238_v1, feed_254_v1, feed_223_v1, feed_274_v1, feed_203_v1, feed_269_v1, feed_262_v1, feed_205_v1, feed_168_v1, feed_219_v1, feed_253_v1, feed_251_v1, feed_173_v1, feed_252_v1, feed_210_v1, feed_216_v1, feed_218_v1, feed_118_v1, feed_273_v1, feed_227_v1, feed_166_v1, feed_213_v1, feed_226_v1]], type [history]
java.lang.NullPointerException
        at org.elasticsearch.index.mapper.internal.TimestampFieldMapper.merge(TimestampFieldMapper.java:287)
        at org.elasticsearch.index.mapper.object.ObjectMapper.merge(ObjectMapper.java:936)
        at org.elasticsearch.index.mapper.DocumentMapper.merge(DocumentMapper.java:693)
        at org.elasticsearch.cluster.metadata.MetaDataMappingService$4.execute(MetaDataMappingService.java:508)
        at org.elasticsearch.cluster.service.InternalClusterService$UpdateTask.run(InternalClusterService.java:329)
        at org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:153)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)
```

https://github.com/elasticsearch/elasticsearch/blob/v1.4.2/src/main/java/org/elasticsearch/index/mapper/internal/TimestampFieldMapper.java#L286

Looks like the existence of default timestamp is not checked before use. The next line also has the same issue -- uses of default timestamp without checked to see if it's not null.

To reproduce:

```
$ curl -XPUT localhost:9200/twitter2

$ curl -XPUT localhost:9200/twitter2/tweet/_mapping -d '{
     "tweet" : {
         "_timestamp" : {
             "enabled" : true,
             "default" : null
         }
     }
}'

$ curl -XPUT localhost:9200/twitter2/tweet/_mapping -d '{
     "tweet" : {
         "_timestamp" : {
             "enabled" : true,
             "default" : null
         },
         "properties": {
             "user": {"type": "string"}
         }
     }
}'
```

Closes #9204.

(cherry picked from commit 62c6d63)
This commit is contained in:
David Pilato 2015-01-08 20:07:17 +01:00
parent 7f9ffea97c
commit 6d58db8868
2 changed files with 90 additions and 1 deletions

View File

@ -305,7 +305,14 @@ public class TimestampFieldMapper extends DateFieldMapper implements InternalMap
this.enabledState = timestampFieldMapperMergeWith.enabledState;
}
} else {
if (!timestampFieldMapperMergeWith.defaultTimestamp().equals(defaultTimestamp)) {
if (timestampFieldMapperMergeWith.defaultTimestamp() == null && defaultTimestamp == null) {
return;
}
if (defaultTimestamp == null) {
mergeContext.addConflict("Cannot update default in _timestamp value. Value is null now encountering " + timestampFieldMapperMergeWith.defaultTimestamp());
} else if (timestampFieldMapperMergeWith.defaultTimestamp() == null) {
mergeContext.addConflict("Cannot update default in _timestamp value. Value is \" + defaultTimestamp.toString() + \" now encountering null");
} else if (!timestampFieldMapperMergeWith.defaultTimestamp().equals(defaultTimestamp)) {
mergeContext.addConflict("Cannot update default in _timestamp value. Value is " + defaultTimestamp.toString() + " now encountering " + timestampFieldMapperMergeWith.defaultTimestamp());
}
if (this.path != null) {

View File

@ -22,6 +22,7 @@ package org.elasticsearch.index.mapper.timestamp;
import org.apache.lucene.index.IndexOptions;
import org.elasticsearch.Version;
import org.elasticsearch.action.TimestampParsingException;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
@ -541,6 +542,87 @@ public class TimestampMappingTests extends ElasticsearchSingleNodeTest {
}
}
/**
* Test case for #9204
*/
@Test
public void testMergingNullValues() throws Exception {
// From trying to add another field with default = null
String mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", (String) null)
.endObject()
.endObject().endObject().string();
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
DocumentMapper docMapper = parser.parse(mapping);
mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", (String) null)
.endObject()
.startObject("properties")
.startObject("foo")
.field("type", "string")
.endObject()
.endObject()
.endObject().endObject().string();
DocumentMapper.MergeResult mergeResult = docMapper.merge(parser.parse(mapping), DocumentMapper.MergeFlags.mergeFlags().simulate(true));
assertThat(mergeResult.hasConflicts(), is(false));
client().admin().indices().delete(new DeleteIndexRequest("test")).get();
// From trying to update from null to non null
mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", (String) null)
.endObject()
.endObject().endObject().string();
parser = createIndex("test").mapperService().documentMapperParser();
docMapper = parser.parse(mapping);
mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", "now")
.endObject()
.endObject().endObject().string();
mergeResult = docMapper.merge(parser.parse(mapping), DocumentMapper.MergeFlags.mergeFlags().simulate(true));
assertThat(mergeResult.hasConflicts(), is(true));
client().admin().indices().delete(new DeleteIndexRequest("test")).get();
// From trying to update from non null to null
mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", "now")
.endObject()
.endObject().endObject().string();
parser = createIndex("test").mapperService().documentMapperParser();
docMapper = parser.parse(mapping);
mapping = XContentFactory.jsonBuilder().startObject()
.startObject("type")
.startObject("_timestamp")
.field("enabled", true)
.field("default", (String) null)
.endObject()
.endObject().endObject().string();
mergeResult = docMapper.merge(parser.parse(mapping), DocumentMapper.MergeFlags.mergeFlags().simulate(true));
assertThat(mergeResult.hasConflicts(), is(true));
}
@Test
public void testMergePaths() throws Exception {
String[] possiblePathValues = {"some_path", "anotherPath", null};