Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide more context variables in update scripts #5724

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/reference/docs/update.asciidoc
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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