Skip to content

Commit

Permalink
Provide more context variables in update scripts
Browse files Browse the repository at this point in the history
In addition to `_source`, the following variables are available through
the `ctx` map: `_index`, `_type`, `_id`, `_version`, `_routing`,
`_parent`, `_timestamp`, `_ttl`.

Some of these fields are more useful still within the context of an
Update By Query, see elastic#1607, elastic#2230, elastic#2231.
  • Loading branch information
Olivier Favre authored and ofavre committed Nov 13, 2014
1 parent 94c1a7d commit 8038920
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 4 deletions.
4 changes: 4 additions & 0 deletions docs/reference/docs/update.asciidoc
Expand Up @@ -234,3 +234,7 @@ It also allows to update the `ttl` of a document using `ctx._ttl` and
timestamp using `ctx._timestamp`. Note that if the timestamp is not
updated and not extracted from the `_source` it will be set to the
update date.

In addition to `_source`, the following variables are available through
the `ctx` map: `_index`, `_type`, `_id`, `_version`, `_routing`,
`_parent`, `_timestamp`, `_ttl`.
27 changes: 23 additions & 4 deletions src/main/java/org/elasticsearch/action/update/UpdateHelper.java
Expand Up @@ -41,6 +41,8 @@
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
import org.elasticsearch.index.mapper.internal.RoutingFieldMapper;
import org.elasticsearch.index.mapper.internal.TTLFieldMapper;
import org.elasticsearch.index.mapper.internal.TimestampFieldMapper;
import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.index.shard.service.IndexShard;
import org.elasticsearch.script.ExecutableScript;
Expand Down Expand Up @@ -74,7 +76,7 @@ public UpdateHelper(Settings settings, ScriptService scriptService) {
public Result prepare(UpdateRequest request, IndexShard indexShard) {
long getDate = System.currentTimeMillis();
final GetResult getResult = indexShard.getService().get(request.type(), request.id(),
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME},
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME},
true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE, false);

if (!getResult.isExists()) {
Expand Down Expand Up @@ -148,7 +150,7 @@ public Result prepare(UpdateRequest request, IndexShard indexShard) {

Tuple<XContentType, Map<String, Object>> sourceAndContent = XContentHelper.convertToMap(getResult.internalSourceRef(), true);
String operation = null;
String timestamp;
String timestamp = null;
Long ttl = null;
final Map<String, Object> updatedSourceAsMap;
final XContentType updateSourceContentType = sourceAndContent.v1();
Expand Down Expand Up @@ -176,7 +178,17 @@ public Result prepare(UpdateRequest request, IndexShard indexShard) {
operation = "none";
}
} else {
Map<String, Object> ctx = new HashMap<>(2);
Map<String, Object> ctx = new HashMap<>(16);
Long originalTtl = getResult.getFields().containsKey(TTLFieldMapper.NAME) ? (Long) getResult.field(TTLFieldMapper.NAME).getValue() : null;
Long originalTimestamp = getResult.getFields().containsKey(TimestampFieldMapper.NAME) ? (Long) getResult.field(TimestampFieldMapper.NAME).getValue() : null;
ctx.put("_index", getResult.getIndex());
ctx.put("_type", getResult.getType());
ctx.put("_id", getResult.getId());
ctx.put("_version", getResult.getVersion());
ctx.put("_routing", routing);
ctx.put("_parent", parent);
ctx.put("_timestamp", originalTimestamp);
ctx.put("_ttl", originalTtl);
ctx.put("_source", sourceAndContent.v2());

try {
Expand All @@ -190,7 +202,14 @@ public Result prepare(UpdateRequest request, IndexShard indexShard) {
}

operation = (String) ctx.get("op");
timestamp = (String) ctx.get("_timestamp");

Object fetchedTimestamp = ctx.get("_timestamp");
if (fetchedTimestamp != null) {
timestamp = fetchedTimestamp.toString();
} else if (originalTimestamp != null) {
// No timestamp has been given in the update script, so we keep the previous timestamp if there is one
timestamp = originalTimestamp.toString();
}

ttl = getTTLFromScriptContext(ctx);

Expand Down
93 changes: 93 additions & 0 deletions src/test/java/org/elasticsearch/update/UpdateTests.java
Expand Up @@ -461,6 +461,99 @@ public void testUpdateRequestWithScriptAndShouldUpsertDoc() throws Exception {
}
}

@Test
public void testContextVariables() throws Exception {
createTestIndex();

// Add child type for testing the _parent context variable
client().admin().indices().preparePutMapping("test")
.setType("subtype1")
.setSource(XContentFactory.jsonBuilder()
.startObject()
.startObject("subtype1")
.startObject("_parent").field("type", "type1").endObject()
.startObject("_timestamp").field("enabled", true).field("store", "yes").endObject()
.startObject("_ttl").field("enabled", true).field("store", "yes").endObject()
.endObject()
.endObject())
.execute().actionGet();
ensureGreen();

// Index some documents
long timestamp = System.currentTimeMillis();
client().prepareIndex()
.setIndex("test")
.setType("type1")
.setId("parentId1")
.setTimestamp(String.valueOf(timestamp-1))
.setSource("field1", 0, "content", "bar")
.execute().actionGet();

client().prepareIndex()
.setIndex("test")
.setType("subtype1")
.setId("id1")
.setParent("parentId1")
.setRouting("routing1")
.setTimestamp(String.valueOf(timestamp))
.setTTL(111211211)
.setSource("field1", 1, "content", "foo")
.execute().actionGet();
long postIndexTs = System.currentTimeMillis();

// Update the first object and note context variables values
Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("delim", "_");
UpdateResponse updateResponse = client().prepareUpdate("test", "subtype1", "id1")
.setRouting("routing1")
.setScript(
"assert ctx._index == \"test\" : \"index should be \\\"test\\\"\"\n" +
"assert ctx._type == \"subtype1\" : \"type should be \\\"subtype1\\\"\"\n" +
"assert ctx._id == \"id1\" : \"id should be \\\"id1\\\"\"\n" +
"assert ctx._version == 1 : \"version should be 1\"\n" +
"assert ctx._parent == \"parentId1\" : \"parent should be \\\"parentId1\\\"\"\n" +
"assert ctx._routing == \"routing1\" : \"routing should be \\\"routing1\\\"\"\n" +
"assert ctx._timestamp == " + timestamp + " : \"timestamp should be " + timestamp + "\"\n" +
"def now = new Date().getTime()\n" +
"assert (111211211 - ctx._ttl) <= (now - " + postIndexTs + ") : \"ttl is not within acceptable range\"\n" +
"ctx._source.content = ctx._source.content + delim + ctx._source.content;\n" +
"ctx._source.field1 += 1;\n",
ScriptService.ScriptType.INLINE)
.setScriptParams(scriptParams)
.execute().actionGet();

assertEquals(2, updateResponse.getVersion());

GetResponse getResponse = client().prepareGet("test", "subtype1", "id1").setRouting("routing1").execute().actionGet();
assertEquals(2, getResponse.getSourceAsMap().get("field1"));
assertEquals("foo_foo", getResponse.getSourceAsMap().get("content"));

// Idem with the second object
scriptParams = new HashMap<>();
scriptParams.put("delim", "_");
updateResponse = client().prepareUpdate("test", "type1", "parentId1")
.setScript(
"assert ctx._index == \"test\" : \"index should be \\\"test\\\"\"\n" +
"assert ctx._type == \"type1\" : \"type should be \\\"type1\\\"\"\n" +
"assert ctx._id == \"parentId1\" : \"id should be \\\"parentId1\\\"\"\n" +
"assert ctx._version == 1 : \"version should be 1\"\n" +
"assert ctx._parent == null : \"parent should be null\"\n" +
"assert ctx._routing == null : \"routing should be null\"\n" +
"assert ctx._timestamp == " + (timestamp - 1) + " : \"timestamp should be " + (timestamp - 1) + "\"\n" +
"assert ctx._ttl == null : \"ttl should be null\"\n" +
"ctx._source.content = ctx._source.content + delim + ctx._source.content;\n" +
"ctx._source.field1 += 1;\n",
ScriptService.ScriptType.INLINE)
.setScriptParams(scriptParams)
.execute().actionGet();

assertEquals(2, updateResponse.getVersion());

getResponse = client().prepareGet("test", "type1", "parentId1").execute().actionGet();
assertEquals(1, getResponse.getSourceAsMap().get("field1"));
assertEquals("bar_bar", getResponse.getSourceAsMap().get("content"));
}

@Test
@Slow
public void testConcurrentUpdateWithRetryOnConflict() throws Exception {
Expand Down

0 comments on commit 8038920

Please sign in to comment.