Skip to content

Commit f4750d6

Browse files
Olivier Favredakrone
Olivier Favre
authored andcommitted
Provide more context variables in update scripts
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 #1607, #2230, #2231.
1 parent e79ec15 commit f4750d6

File tree

3 files changed

+120
-4
lines changed

3 files changed

+120
-4
lines changed

docs/reference/docs/update.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,7 @@ It also allows to update the `ttl` of a document using `ctx._ttl` and
234234
timestamp using `ctx._timestamp`. Note that if the timestamp is not
235235
updated and not extracted from the `_source` it will be set to the
236236
update date.
237+
238+
In addition to `_source`, the following variables are available through
239+
the `ctx` map: `_index`, `_type`, `_id`, `_version`, `_routing`,
240+
`_parent`, `_timestamp`, `_ttl`.

src/main/java/org/elasticsearch/action/update/UpdateHelper.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
4242
import org.elasticsearch.index.mapper.internal.RoutingFieldMapper;
4343
import org.elasticsearch.index.mapper.internal.TTLFieldMapper;
44+
import org.elasticsearch.index.mapper.internal.TimestampFieldMapper;
45+
import org.elasticsearch.index.service.IndexService;
4446
import org.elasticsearch.index.shard.ShardId;
4547
import org.elasticsearch.index.shard.service.IndexShard;
4648
import org.elasticsearch.script.ExecutableScript;
@@ -74,7 +76,7 @@ public UpdateHelper(Settings settings, ScriptService scriptService) {
7476
public Result prepare(UpdateRequest request, IndexShard indexShard) {
7577
long getDate = System.currentTimeMillis();
7678
final GetResult getResult = indexShard.getService().get(request.type(), request.id(),
77-
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME},
79+
new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME},
7880
true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE, false);
7981

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

149151
Tuple<XContentType, Map<String, Object>> sourceAndContent = XContentHelper.convertToMap(getResult.internalSourceRef(), true);
150152
String operation = null;
151-
String timestamp;
153+
String timestamp = null;
152154
Long ttl = null;
153155
final Map<String, Object> updatedSourceAsMap;
154156
final XContentType updateSourceContentType = sourceAndContent.v1();
@@ -176,7 +178,17 @@ public Result prepare(UpdateRequest request, IndexShard indexShard) {
176178
operation = "none";
177179
}
178180
} else {
179-
Map<String, Object> ctx = new HashMap<>(2);
181+
Map<String, Object> ctx = new HashMap<>(16);
182+
Long originalTtl = getResult.getFields().containsKey(TTLFieldMapper.NAME) ? (Long) getResult.field(TTLFieldMapper.NAME).getValue() : null;
183+
Long originalTimestamp = getResult.getFields().containsKey(TimestampFieldMapper.NAME) ? (Long) getResult.field(TimestampFieldMapper.NAME).getValue() : null;
184+
ctx.put("_index", getResult.getIndex());
185+
ctx.put("_type", getResult.getType());
186+
ctx.put("_id", getResult.getId());
187+
ctx.put("_version", getResult.getVersion());
188+
ctx.put("_routing", routing);
189+
ctx.put("_parent", parent);
190+
ctx.put("_timestamp", originalTimestamp);
191+
ctx.put("_ttl", originalTtl);
180192
ctx.put("_source", sourceAndContent.v2());
181193

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

192204
operation = (String) ctx.get("op");
193-
timestamp = (String) ctx.get("_timestamp");
205+
206+
Object fetchedTimestamp = ctx.get("_timestamp");
207+
if (fetchedTimestamp != null) {
208+
timestamp = fetchedTimestamp.toString();
209+
} else if (originalTimestamp != null) {
210+
// No timestamp has been given in the update script, so we keep the previous timestamp if there is one
211+
timestamp = originalTimestamp.toString();
212+
}
194213

195214
ttl = getTTLFromScriptContext(ctx);
196215

src/test/java/org/elasticsearch/update/UpdateTests.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,99 @@ public void testUpdateRequestWithScriptAndShouldUpsertDoc() throws Exception {
461461
}
462462
}
463463

464+
@Test
465+
public void testContextVariables() throws Exception {
466+
createTestIndex();
467+
468+
// Add child type for testing the _parent context variable
469+
client().admin().indices().preparePutMapping("test")
470+
.setType("subtype1")
471+
.setSource(XContentFactory.jsonBuilder()
472+
.startObject()
473+
.startObject("subtype1")
474+
.startObject("_parent").field("type", "type1").endObject()
475+
.startObject("_timestamp").field("enabled", true).field("store", "yes").endObject()
476+
.startObject("_ttl").field("enabled", true).field("store", "yes").endObject()
477+
.endObject()
478+
.endObject())
479+
.execute().actionGet();
480+
ensureGreen();
481+
482+
// Index some documents
483+
long timestamp = System.currentTimeMillis();
484+
client().prepareIndex()
485+
.setIndex("test")
486+
.setType("type1")
487+
.setId("parentId1")
488+
.setTimestamp(String.valueOf(timestamp-1))
489+
.setSource("field1", 0, "content", "bar")
490+
.execute().actionGet();
491+
492+
client().prepareIndex()
493+
.setIndex("test")
494+
.setType("subtype1")
495+
.setId("id1")
496+
.setParent("parentId1")
497+
.setRouting("routing1")
498+
.setTimestamp(String.valueOf(timestamp))
499+
.setTTL(111211211)
500+
.setSource("field1", 1, "content", "foo")
501+
.execute().actionGet();
502+
long postIndexTs = System.currentTimeMillis();
503+
504+
// Update the first object and note context variables values
505+
Map<String, Object> scriptParams = new HashMap<>();
506+
scriptParams.put("delim", "_");
507+
UpdateResponse updateResponse = client().prepareUpdate("test", "subtype1", "id1")
508+
.setRouting("routing1")
509+
.setScript(
510+
"assert ctx._index == \"test\" : \"index should be \\\"test\\\"\"\n" +
511+
"assert ctx._type == \"subtype1\" : \"type should be \\\"subtype1\\\"\"\n" +
512+
"assert ctx._id == \"id1\" : \"id should be \\\"id1\\\"\"\n" +
513+
"assert ctx._version == 1 : \"version should be 1\"\n" +
514+
"assert ctx._parent == \"parentId1\" : \"parent should be \\\"parentId1\\\"\"\n" +
515+
"assert ctx._routing == \"routing1\" : \"routing should be \\\"routing1\\\"\"\n" +
516+
"assert ctx._timestamp == " + timestamp + " : \"timestamp should be " + timestamp + "\"\n" +
517+
"def now = new Date().getTime()\n" +
518+
"assert (111211211 - ctx._ttl) <= (now - " + postIndexTs + ") : \"ttl is not within acceptable range\"\n" +
519+
"ctx._source.content = ctx._source.content + delim + ctx._source.content;\n" +
520+
"ctx._source.field1 += 1;\n",
521+
ScriptService.ScriptType.INLINE)
522+
.setScriptParams(scriptParams)
523+
.execute().actionGet();
524+
525+
assertEquals(2, updateResponse.getVersion());
526+
527+
GetResponse getResponse = client().prepareGet("test", "subtype1", "id1").setRouting("routing1").execute().actionGet();
528+
assertEquals(2, getResponse.getSourceAsMap().get("field1"));
529+
assertEquals("foo_foo", getResponse.getSourceAsMap().get("content"));
530+
531+
// Idem with the second object
532+
scriptParams = new HashMap<>();
533+
scriptParams.put("delim", "_");
534+
updateResponse = client().prepareUpdate("test", "type1", "parentId1")
535+
.setScript(
536+
"assert ctx._index == \"test\" : \"index should be \\\"test\\\"\"\n" +
537+
"assert ctx._type == \"type1\" : \"type should be \\\"type1\\\"\"\n" +
538+
"assert ctx._id == \"parentId1\" : \"id should be \\\"parentId1\\\"\"\n" +
539+
"assert ctx._version == 1 : \"version should be 1\"\n" +
540+
"assert ctx._parent == null : \"parent should be null\"\n" +
541+
"assert ctx._routing == null : \"routing should be null\"\n" +
542+
"assert ctx._timestamp == " + (timestamp - 1) + " : \"timestamp should be " + (timestamp - 1) + "\"\n" +
543+
"assert ctx._ttl == null : \"ttl should be null\"\n" +
544+
"ctx._source.content = ctx._source.content + delim + ctx._source.content;\n" +
545+
"ctx._source.field1 += 1;\n",
546+
ScriptService.ScriptType.INLINE)
547+
.setScriptParams(scriptParams)
548+
.execute().actionGet();
549+
550+
assertEquals(2, updateResponse.getVersion());
551+
552+
getResponse = client().prepareGet("test", "type1", "parentId1").execute().actionGet();
553+
assertEquals(1, getResponse.getSourceAsMap().get("field1"));
554+
assertEquals("bar_bar", getResponse.getSourceAsMap().get("content"));
555+
}
556+
464557
@Test
465558
@Slow
466559
public void testConcurrentUpdateWithRetryOnConflict() throws Exception {

0 commit comments

Comments
 (0)