Repository: searchbox-io/Jest (java)

Issue:
_update_by_query not supporterd
Please add it

Pull request description:
Update by query
Fixes #446.  Rebased pr #453 onto master.

Patch:
diff --git a/jest-common/src/main/java/io/searchbox/core/UpdateByQuery.java b/jest-common/src/main/java/io/searchbox/core/UpdateByQuery.java
new file mode 100644
index 00000000..f067dbeb
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/core/UpdateByQuery.java
@@ -0,0 +1,72 @@
+package io.searchbox.core;
+
+import com.google.gson.Gson;
+import io.searchbox.action.AbstractAction;
+import io.searchbox.action.AbstractMultiTypeActionBuilder;
+import java.util.Objects;
+
+/**
+ * @author Lior Knaany
+ */
+public class UpdateByQuery extends AbstractAction<UpdateByQueryResult> {
+
+    protected UpdateByQuery(Builder builder) {
+        super(builder);
+
+        this.payload = builder.payload;
+        setURI(buildURI());
+    }
+
+    @Override
+    protected String buildURI() {
+        return super.buildURI() + "/_update_by_query";
+    }
+
+    @Override
+    public String getPathToResult() {
+        return "ok";
+    }
+
+    @Override
+    public String getRestMethodName() {
+        return "POST";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+
+        return super.equals(obj);
+    }
+
+    @Override
+    public UpdateByQueryResult createNewElasticSearchResult(String responseBody, int statusCode, String reasonPhrase, Gson gson) {
+        return createNewElasticSearchResult(new UpdateByQueryResult(gson), responseBody, statusCode, reasonPhrase, gson);
+    }
+
+    public static class Builder extends AbstractMultiTypeActionBuilder<UpdateByQuery, Builder> {
+
+        private Object payload;
+
+        public Builder(Object payload) {this.payload = payload;}
+
+        @Override
+        public UpdateByQuery build() {
+            return new UpdateByQuery(this);
+        }
+    }
+
+}
diff --git a/jest-common/src/main/java/io/searchbox/core/UpdateByQueryResult.java b/jest-common/src/main/java/io/searchbox/core/UpdateByQueryResult.java
new file mode 100644
index 00000000..11d7b631
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/core/UpdateByQueryResult.java
@@ -0,0 +1,68 @@
+package io.searchbox.core;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import io.searchbox.client.JestResult;
+
+/**
+ * Created by Lior Knaany on 12/26/16.
+ */
+public class UpdateByQueryResult extends JestResult {
+
+    public UpdateByQueryResult(Gson gson) {
+        super(gson);
+    }
+
+    public boolean didTimeOut() {
+        return (jsonObject != null && jsonObject.has("timed_out")) ? jsonObject.get("timed_out").getAsBoolean() : false;
+    }
+
+    public long getConflictsCount() {
+        return (jsonObject != null && jsonObject.has("version_conflicts")) ? jsonObject.get("version_conflicts").getAsLong() : 0L;
+    }
+
+    public long getMillisTaken() {
+        return (jsonObject != null && jsonObject.has("took")) ? jsonObject.get("took").getAsLong() : 0L;
+    }
+
+    public long getUpdatedCount() {
+        return (jsonObject != null && jsonObject.has("updated")) ? jsonObject.get("updated").getAsLong() : 0L;
+    }
+
+    public int getBatchCount() {
+        return (jsonObject != null && jsonObject.has("batches")) ? jsonObject.get("batches").getAsInt() : 0;
+    }
+
+    public int getRetryCount() {
+        return getBulkRetryCount() + getSearchRetryCount();
+    }
+
+    public int getBulkRetryCount() {
+        return (jsonObject != null && jsonObject.has("retries") && jsonObject.getAsJsonObject("retries").has("bulk")) ? jsonObject.getAsJsonObject("retries").get("bulk").getAsInt() : 0;
+    }
+
+    public int getSearchRetryCount() {
+        return (jsonObject != null && jsonObject.has("retries") && jsonObject.getAsJsonObject("retries").has("search")) ? jsonObject.getAsJsonObject("retries").get("search").getAsInt() : 0;
+    }
+
+    public int getNoopCount() {
+        return (jsonObject != null && jsonObject.has("noops")) ? jsonObject.get("noops").getAsInt() : 0;
+    }
+
+    public JsonArray getFailures() {
+        return (jsonObject != null && jsonObject.has("failures")) ? jsonObject.get("failures").getAsJsonArray() : null;
+    }
+
+    @Override
+    public String toString() {
+        return "Updated: "             + getUpdatedCount()
+                + ", conflicts: "      + getConflictsCount()
+                + ", time taken: "     + getMillisTaken()
+                + ", did time out: "   + didTimeOut()
+                + ", batches: "        + getBatchCount()
+                + ", retries: "        + getRetryCount()
+                + ", bulk retries: "   + getBulkRetryCount()
+                + ", search retries: " + getSearchRetryCount()
+                + ", noops: "          + getNoopCount();
+    }
+}


Test patch:
diff --git a/jest-common/src/test/java/io/searchbox/core/UpdateByQueryTest.java b/jest-common/src/test/java/io/searchbox/core/UpdateByQueryTest.java
new file mode 100644
index 00000000..37ad5eba
--- /dev/null
+++ b/jest-common/src/test/java/io/searchbox/core/UpdateByQueryTest.java
@@ -0,0 +1,86 @@
+package io.searchbox.core;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * @author Lior Knaany
+ */
+public class UpdateByQueryTest {
+
+    @Test
+    public void getURIWithoutIndexAndType() {
+        assertEquals("_all/_update_by_query", new UpdateByQuery.Builder(null).build().getURI());
+    }
+
+    @Test
+    public void getURIWithOnlyOneIndex() {
+        assertEquals("twitter/_update_by_query", new UpdateByQuery.Builder(null).addIndex("twitter").build().getURI());
+    }
+
+    @Test
+    public void getURIWithOnlyMultipleType() {
+        assertEquals("_all/tweet%2Cjest/_update_by_query", new UpdateByQuery.Builder(null).addType("tweet").addType("jest").build().getURI());
+    }
+
+    @Test
+    public void getURIWithOneIndexAndOneType() {
+        assertEquals("twitter/tweet/_update_by_query", new UpdateByQuery.Builder(null).addIndex("twitter").addType("tweet").build().getURI());
+    }
+
+    @Test
+    public void getURIWithOnlyMultipleIndex() {
+        assertEquals("twitter%2Csearchbox/_update_by_query",
+                new UpdateByQuery.Builder(null).addIndex("twitter").addIndex("searchbox").build().getURI());
+    }
+
+    @Test
+    public void getURIWithMultipleIndexAndTypes() {
+        assertEquals("twitter%2Csearchbox/tweet%2Cjest/_update_by_query", new UpdateByQuery.Builder(null)
+                .addIndex("twitter")
+                .addIndex("searchbox")
+                .addType("tweet")
+                .addType("jest")
+                .build()
+                .getURI());
+    }
+
+    @Test
+    public void equals() {
+        UpdateByQuery deleteUserKramer = new UpdateByQuery.Builder("{\"user\":\"kramer\"}")
+                .addIndex("twitter")
+                .addIndex("searchbox")
+                .addType("tweet")
+                .addType("jest")
+                .build();
+        UpdateByQuery deleteUserKramerDuplicate = new UpdateByQuery.Builder("{\"user\":\"kramer\"}")
+                .addIndex("twitter")
+                .addIndex("searchbox")
+                .addType("tweet")
+                .addType("jest")
+                .build();
+
+        assertEquals(deleteUserKramer, deleteUserKramerDuplicate);
+    }
+
+    @Test
+    public void equalsReturnsFalseForDifferentQueries() {
+        UpdateByQuery deleteUserKramer = new UpdateByQuery.Builder("{\"user\":\"kramer\"}")
+                .addIndex("twitter")
+                .addIndex("searchbox")
+                .addType("tweet")
+                .addType("jest")
+                .build();
+        UpdateByQuery deleteUserJerry = new UpdateByQuery.Builder("{\"user\":\"jerry\"}")
+                .addIndex("twitter")
+                .addIndex("searchbox")
+                .addType("tweet")
+                .addType("jest")
+                .build();
+
+        assertNotEquals(deleteUserKramer, deleteUserJerry);
+    }
+
+}
diff --git a/jest/src/test/java/io/searchbox/core/MultiSearchIntegrationTest.java b/jest/src/test/java/io/searchbox/core/MultiSearchIntegrationTest.java
index a68cce92..16a09468 100644
--- a/jest/src/test/java/io/searchbox/core/MultiSearchIntegrationTest.java
+++ b/jest/src/test/java/io/searchbox/core/MultiSearchIntegrationTest.java
@@ -42,7 +42,7 @@ public class MultiSearchIntegrationTest extends AbstractIntegrationTest {
         SearchResult complexSearchResult = complexSearchResponse.searchResult;
         assertTrue(complexSearchResult.isSucceeded());
         assertNull(complexSearchResult.getErrorMessage());
-        assertEquals(Integer.valueOf(2), complexSearchResult.getTotal());
+        assertEquals(Long.valueOf(2), complexSearchResult.getTotal());
         List<SearchResult.Hit<Comment, Void>> complexSearchHits = complexSearchResult.getHits(Comment.class);
         assertEquals(2, complexSearchHits.size());
 
@@ -52,7 +52,7 @@ public class MultiSearchIntegrationTest extends AbstractIntegrationTest {
         SearchResult simpleSearchResult = simpleSearchResponse.searchResult;
         assertTrue(simpleSearchResult.isSucceeded());
         assertNull(simpleSearchResult.getErrorMessage());
-        assertEquals(Integer.valueOf(3), simpleSearchResult.getTotal());
+        assertEquals(Long.valueOf(3), simpleSearchResult.getTotal());
         List<SearchResult.Hit<Comment, Void>> simpleSearchHits = simpleSearchResult.getHits(Comment.class);
         assertEquals(3, simpleSearchHits.size());
     }
diff --git a/jest/src/test/java/io/searchbox/core/UpdateByQueryIntegrationTest.java b/jest/src/test/java/io/searchbox/core/UpdateByQueryIntegrationTest.java
new file mode 100644
index 00000000..de46338e
--- /dev/null
+++ b/jest/src/test/java/io/searchbox/core/UpdateByQueryIntegrationTest.java
@@ -0,0 +1,92 @@
+package io.searchbox.core;
+
+import com.google.gson.GsonBuilder;
+
+import io.searchbox.common.AbstractIntegrationTest;
+import io.searchbox.core.UpdateByQueryResult;
+import org.elasticsearch.action.DocWriteResponse;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.reindex.ReindexPlugin;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.test.ESIntegTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+
+
+/**
+ * @author Lior Knaany
+ */
+@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 1)
+public class UpdateByQueryIntegrationTest extends AbstractIntegrationTest {
+
+    private static final String INDEX = "twitter";
+    private static final String TYPE = "tweet";
+
+    @Override
+    protected Collection<Class<? extends Plugin>> nodePlugins() {
+        final ArrayList<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins());
+        plugins.add(ReindexPlugin.class);
+        return plugins;
+    }
+
+    @Override
+    protected Settings nodeSettings(int nodeOrdinal) {
+        final Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
+        return builder
+                .put(super.nodeSettings(nodeOrdinal))
+                .put("script.inline", "true")
+                .build();
+    }
+
+    @Test
+    public void update() throws IOException, InterruptedException {
+
+        // create a tweet
+        assertTrue(index(INDEX, TYPE, "1", "{\"user\":\"lior\",\"num\":1}").getResult().equals(DocWriteResponse.Result.CREATED));
+        assertTrue(index(INDEX, TYPE, "2", "{\"user\":\"kimchy\",\"num\":2}").getResult().equals(DocWriteResponse.Result.CREATED));
+
+       refresh();
+       ensureSearchable(INDEX);
+
+        // run the search and update
+        final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("user", "lior"));
+        final String script = "ctx._source.user = ctx._source.user + '_updated';";
+
+        final String payload = jsonBuilder()
+                .startObject()
+                .field("query", queryBuilder)
+                .startObject("script")
+                .field("inline", script)
+                .endObject()
+                .endObject().string();
+
+        UpdateByQuery updateByQuery = new UpdateByQuery.Builder(payload)
+                .addIndex(INDEX)
+                .addType(TYPE)
+                .build();
+
+        UpdateByQueryResult result = client.execute(updateByQuery);
+
+        // Checks
+        assertTrue(result.getErrorMessage(), result.isSucceeded());
+
+        assertFalse(result.didTimeOut());
+        assertEquals(0, result.getConflictsCount());
+        assertTrue(result.getMillisTaken() > 0);
+        assertEquals(1, result.getUpdatedCount());
+        assertEquals(0, result.getRetryCount());
+        assertEquals(0, result.getBulkRetryCount());
+        assertEquals(0, result.getSearchRetryCount());
+        assertEquals(0, result.getNoopCount());
+        assertEquals(0, result.getFailures().size());
+    }
+
+}