Repository: searchbox-io/Jest
Pull request #544 (2017-08-29 20:57:03)

Feature/hits are not supporting parent child #543
contains feature impl of #543 


Patch:
diff --git a/jest-common/src/main/java/io/searchbox/core/SearchResult.java b/jest-common/src/main/java/io/searchbox/core/SearchResult.java
index cb10ee1b..5822cb62 100644
--- a/jest-common/src/main/java/io/searchbox/core/SearchResult.java
+++ b/jest-common/src/main/java/io/searchbox/core/SearchResult.java
@@ -116,6 +116,17 @@ public class SearchResult extends JestResult {
                 if (hitObject.has("_score") && !hitObject.get("_score").isJsonNull()) {
                     score = hitObject.get("_score").getAsDouble();
                 }
+                
+                String parent = null;
+                String routing = null;
+                
+                if (hitObject.has("_parent") && !hitObject.get("_parent").isJsonNull()) {
+                		parent = hitObject.get("_parent").getAsString();
+                }
+                
+                if (hitObject.has("_routing") && !hitObject.get("_routing").isJsonNull()) {
+            			routing = hitObject.get("_routing").getAsString();
+                }
 
                 JsonElement explanation = hitObject.get(EXPLANATION_KEY);
                 Map<String, List<String>> highlight = extractHighlight(hitObject.getAsJsonObject(HIGHLIGHT_KEY));
@@ -147,7 +158,9 @@ public class SearchResult extends JestResult {
                         index,
                         type,
                         id,
-                        score
+                        score,
+                        parent,
+                        routing
                 );
             }
         }
@@ -240,6 +253,8 @@ public class SearchResult extends JestResult {
         public final String type;
         public final String id;
         public final Double score;
+        public final String parent;
+        public final String routing;
 
         public Hit(Class<T> sourceType, JsonElement source) {
             this(sourceType, source, null, null);
@@ -255,7 +270,7 @@ public class SearchResult extends JestResult {
         }
 
         public Hit(Class<T> sourceType, JsonElement source, Class<K> explanationType, JsonElement explanation,
-                   Map<String, List<String>> highlight, List<String> sort, String index, String type, String id, Double score) {
+                   Map<String, List<String>> highlight, List<String> sort, String index, String type, String id, Double score, String parent, String routing) {
             if (source == null) {
                 this.source = null;
             } else {
@@ -273,6 +288,14 @@ public class SearchResult extends JestResult {
             this.type = type;
             this.id = id;
             this.score = score;
+            
+            this.parent = parent;
+            this.routing = routing;
+        }
+        
+        public Hit(Class<T> sourceType, JsonElement source, Class<K> explanationType, JsonElement explanation,
+                Map<String, List<String>> highlight, List<String> sort, String index, String type, String id, Double score) {
+            this(sourceType, source, explanationType, explanation, highlight, sort, index, type, id, score, null, null);
         }
 
         public Hit(T source) {
@@ -297,6 +320,9 @@ public class SearchResult extends JestResult {
             this.type = type;
             this.id = id;
             this.score = score;
+            
+            this.parent = null;
+            this.routing = null;
         }
 
         @Override
diff --git a/jest-common/src/main/java/io/searchbox/core/search/aggregation/ChildrenAggregation.java b/jest-common/src/main/java/io/searchbox/core/search/aggregation/ChildrenAggregation.java
new file mode 100644
index 00000000..2f3bd520
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/core/search/aggregation/ChildrenAggregation.java
@@ -0,0 +1,15 @@
+package io.searchbox.core.search.aggregation;
+
+import com.google.gson.JsonObject;
+
+public class ChildrenAggregation extends MetricAggregation {
+
+	protected String name;
+	protected JsonObject jsonRoot;
+	public static final String TYPE = "child";
+
+	public ChildrenAggregation(String name, JsonObject childrenAggregation) {
+
+		super(name, childrenAggregation);
+	}
+}
diff --git a/jest-common/src/main/java/io/searchbox/core/search/aggregation/MetricAggregation.java b/jest-common/src/main/java/io/searchbox/core/search/aggregation/MetricAggregation.java
index d5e8a3b6..e2bcb91c 100644
--- a/jest-common/src/main/java/io/searchbox/core/search/aggregation/MetricAggregation.java
+++ b/jest-common/src/main/java/io/searchbox/core/search/aggregation/MetricAggregation.java
@@ -1,5 +1,6 @@
 package io.searchbox.core.search.aggregation;
 
+import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 
 import java.lang.reflect.Constructor;
@@ -248,8 +249,31 @@ public abstract class MetricAggregation extends Aggregation {
         return jsonRoot.has(aggName) ? new ValueCountAggregation(aggName, jsonRoot.getAsJsonObject(aggName)) : null;
     }
     
+    /**
+     * @param aggName Name of the TopHitsAggregation
+     * @return a new TopHitsAggregation object if aggName is found within sub-aggregations of current aggregation level or null if not found
+     */
     public TopHitsAggregation getTopHitsAggregation(String aggName) {
-	return jsonRoot.has(aggName) ? new TopHitsAggregation(aggName, jsonRoot.getAsJsonObject(aggName)) : null;
+    		return jsonRoot.has(aggName) ? new TopHitsAggregation(aggName, jsonRoot.getAsJsonObject(aggName)) : null;
+    }
+    
+    /**
+     * @param aggName Name of the TopHitsAggregation
+     * @param gson Custom Gson parser for top hit results
+     * @return a new TopHitsAggregation object if aggName is found within sub-aggregations of current aggregation level or null if not found
+     */
+    public TopHitsAggregation getTopHitsAggregation(String aggName, Gson gson) {
+    		return jsonRoot.has(aggName) ? new TopHitsAggregation(aggName, jsonRoot.getAsJsonObject(aggName), gson) : null;
     }
 
+    
+    /**
+     * @param aggName Name of the ChildrenAggregation
+     * @return a new ChildrenAggregation object if aggName is found within sub-aggregations of current aggregation level or null if not found
+     */
+
+    public ChildrenAggregation getChildrenAggregation(String aggName) {
+		return jsonRoot.has(aggName) ? new ChildrenAggregation(aggName, jsonRoot.getAsJsonObject(aggName)) : null;
+}
+
 }
diff --git a/jest-common/src/main/java/io/searchbox/core/search/aggregation/TopHitsAggregation.java b/jest-common/src/main/java/io/searchbox/core/search/aggregation/TopHitsAggregation.java
index 83344b3c..8a974941 100644
--- a/jest-common/src/main/java/io/searchbox/core/search/aggregation/TopHitsAggregation.java
+++ b/jest-common/src/main/java/io/searchbox/core/search/aggregation/TopHitsAggregation.java
@@ -9,21 +9,24 @@ import io.searchbox.core.SearchResult;
 
 public class TopHitsAggregation extends SearchResult {
 
-    protected String name;
-    protected JsonObject jsonRoot;
-    public static final String TYPE = "top_hits";
+	protected String name;
+	protected JsonObject jsonRoot;
+	public static final String TYPE = "top_hits";
 
-    public TopHitsAggregation(String name, JsonObject topHitAggregation) {
-	super(new Gson());
-	this.name = name;
+	public TopHitsAggregation(String name, JsonObject topHitAggregation) {
+		this(name, topHitAggregation, null);
+	}
 	
-	this.setSucceeded(true);
-	this.setJsonObject(topHitAggregation);
-	//this.setJsonString(topHitAggregation.getAsString());
-	this.setPathToResult("hits/hits/_source");
-    }
-
-    public String getName() {
-	return name;
-    }
+	public TopHitsAggregation(String name, JsonObject topHitAggregation, Gson gson) {
+		super(gson == null ? new Gson() : gson);
+		this.name = name;
+
+		this.setSucceeded(true);
+		this.setJsonObject(topHitAggregation);
+		this.setPathToResult("hits/hits/_source");
+	}
+
+	public String getName() {
+		return name;
+	}
 }


Test patch:
diff --git a/jest-common/src/test/java/io/searchbox/core/SearchResultTest.java b/jest-common/src/test/java/io/searchbox/core/SearchResultTest.java
index db72725a..7acd8a1e 100644
--- a/jest-common/src/test/java/io/searchbox/core/SearchResultTest.java
+++ b/jest-common/src/test/java/io/searchbox/core/SearchResultTest.java
@@ -327,6 +327,60 @@ public class SearchResultTest {
         assertNotNull(hit.score);
         assertEquals("Incorrect version", someVersion, hit.source.getVersion());
     }
+    
+    
+    /*
+     * This result is an "not real" example of a inner hit parent/child
+     */
+    @Test
+    public void testParentChild() {
+        Long someVersion = Integer.MAX_VALUE + 10L;
+
+        String jsonWithVersion = "{\n" +
+                "    \"_shards\":{\n" +
+                "        \"total\" : 5,\n" +
+                "        \"successful\" : 5,\n" +
+                "        \"failed\" : 0\n" +
+                "    },\n" +
+                "    \"hits\":{\n" +
+                "        \"total\" : 1,\n" +
+                "        \"hits\" : [\n" +
+                "            {\n" +
+                "                \"_index\" : \"twitter\",\n" +
+                "                \"_type\" : \"tweet\",\n" +
+                "                \"_score\" : \"1.02332\",\n" +
+                "				 \"_routing\": \"1\", \n" +
+                "				 \"_parent\": \"1\", \n" +                
+                "                \"_id\" : \"1\",\n" +
+                "                \"_version\" : \"" + someVersion + "\",\n" +
+                "                \"_source\" : {\n" +
+                "                    \"user\" : \"kimchy\",\n" +
+                "                    \"postDate\" : \"2009-11-15T14:12:12\",\n" +
+                "                    \"message\" : \"trying out Elasticsearch\"\n" +
+                "                },\n" +
+                "                \"sort\" : [\n" +
+                "                     1234.5678\n" +
+                "                ]\n" +
+                "            }\n" +
+                "        ]\n" +
+                "    }\n" +
+                "}";
+
+        SearchResult searchResult = new SearchResult(new Gson());
+        searchResult.setSucceeded(true);
+        searchResult.setJsonString(jsonWithVersion);
+        searchResult.setJsonObject(new JsonParser().parse(jsonWithVersion).getAsJsonObject());
+        searchResult.setPathToResult("hits/hits/_source");
+
+        SearchResult.Hit<TestObject, Void> hit = searchResult.getFirstHit(TestObject.class);
+        assertNotNull(hit.source);
+        assertNull(hit.explanation);
+        assertNotNull(hit.sort);
+        assertNotNull(hit.score);
+        assertNotNull(hit.parent);
+        assertNotNull(hit.routing);
+        assertEquals("Incorrect version", someVersion, hit.source.getVersion());
+    }  
 
     class TestObject {
         @JestId
diff --git a/jest-common/src/test/java/io/searchbox/search/aggregation/ChildAggregationTest.java b/jest-common/src/test/java/io/searchbox/search/aggregation/ChildAggregationTest.java
new file mode 100644
index 00000000..1223a644
--- /dev/null
+++ b/jest-common/src/test/java/io/searchbox/search/aggregation/ChildAggregationTest.java
@@ -0,0 +1,135 @@
+package io.searchbox.search.aggregation;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import io.searchbox.core.SearchResult;
+import io.searchbox.core.search.aggregation.ChildrenAggregation;
+import io.searchbox.core.search.aggregation.TermsAggregation;
+import io.searchbox.core.search.aggregation.TermsAggregation.Entry;
+import io.searchbox.core.search.aggregation.TopHitsAggregation;
+
+public class ChildAggregationTest {
+
+	// @formatter:off
+    String json = "{\n" +
+            "    \"_shards\":{\n" +
+            "        \"total\" : 5,\n" +
+            "        \"successful\" : 5,\n" +
+            "        \"failed\" : 0\n" +
+            "    },\n" +
+            "    \"hits\":{\n" +
+            "        \"total\" : 1,\n" +
+            "	     \"hits\": []\n" +
+            "    },\n" +
+            "    \"aggregations\": {\n" +
+            "        \"childtest\": {\n" +
+            "            \"test\": {\n" +
+            "                \"doc_count_error_upper_bound\": 1,\n" +
+            "                \"sum_other_doc_count\": 2,\n" +
+            "                \"buckets\": [\n" +
+            "                    {\n" +
+            "                        \"key\": \"keyTest1\",\n" +
+            "                        \"doc_count\": 2,\n" +
+            "                        \"top_hits_test\": {\n" +
+            "                            \"hits\": {\n" +
+            "                                \"total\": 2,\n" +
+            "                                \"max_score\": 1.22222,\n" +
+            "                                \"hits\": [\n" +
+            "                                    {\n" +
+            "                                        \"_index\": \"indexName\",\n" +
+            "                                        \"_type\": \"typeName\",\n" +
+            "                                        \"_id\": \"testId1\",\n" +
+            "                                        \"_score\": 1.11111,\n" +
+            "                                        \"_source\": {\n" +
+            "                                            \"field1\": \"field1Content\"\n" +
+            "                                        }\n" +
+            "                                    },\n" +
+            "                                    {\n" +
+            "                                        \"_index\": \"indexName\",\n" +
+            "                                        \"_type\": \"typeName\",\n" +
+            "                                        \"_id\": \"testId2\",\n" +
+            "                                        \"_score\": 1.0000,\n" +
+            "                                        \"_source\": {\n" +
+            "                                            \"field1\": \"field2Content\"\n" +
+            "                                        }\n" +
+            "                                    }\n" +
+            "                                ]\n" +
+            "                            }\n" +
+            "                        }\n" +
+            "                    },\n" +
+            "                    {\n" +
+            "                        \"key\": \"keyTest2\",\n" +
+            "                        \"doc_count\": 2,\n" +
+            "                        \"top_hits_test\": {\n" +
+            "                            \"hits\": {\n" +
+            "                                \"total\": 2,\n" +
+            "                                \"max_score\": 1.22222,\n" +
+            "                                \"hits\": [\n" +
+            "                                    {\n" +
+            "                                        \"_index\": \"indexName\",\n" +
+            "                                        \"_type\": \"typeName\",\n" +
+            "                                        \"_id\": \"testId21\",\n" +
+            "                                        \"_score\": 1.11111,\n" +
+            "                                        \"_source\": {\n" +
+            "                                            \"field1\": \"field21Content\"\n" +
+            "                                        }\n" +
+            "                                    },\n" +
+            "                                    {\n" +
+            "                                        \"_index\": \"indexName\",\n" +
+            "                                        \"_type\": \"typeName\",\n" +
+            "                                        \"_id\": \"testId22\",\n" +
+            "                                        \"_score\": 1.0000,\n" +
+            "                                        \"_source\": {\n" +
+            "                                            \"field1\": \"field22Content\"\n" +
+            "                                        }\n" +
+            "                                    }\n" +
+            "                                ]\n" +
+            "                            }\n" +
+            "                        }\n" +
+            "                    }\n" +
+            "                ]\n" +
+            "            }\n" +
+            "        }\n" +
+            "    }\n" + 
+            "}";
+	// @formatter:on
+
+	@Test
+	public void testGetMaxScoreWhenMissingWithChildren() {
+		System.out.println(json);
+		
+		SearchResult searchResult = new SearchResult(new Gson());
+		searchResult.setSucceeded(true);
+		searchResult.setJsonString(json);
+		searchResult.setJsonObject(new JsonParser().parse(json).getAsJsonObject());
+		searchResult.setPathToResult("hits/hits/_source");
+
+		Float maxScore = searchResult.getMaxScore();
+		assertNull(maxScore);
+		
+		ChildrenAggregation childAgg = searchResult.getAggregations().getChildrenAggregation("childtest");
+		assertNotNull(childAgg);
+
+		TermsAggregation termsAgg = childAgg.getTermsAggregation("test");
+
+		assertEquals(new Long(1), termsAgg.getDocCountErrorUpperBound());
+		assertEquals(new Long(2), termsAgg.getSumOtherDocCount());
+
+		List<Entry> termBuckets = termsAgg.getBuckets();
+
+		for (Entry entry : termBuckets) {
+			TopHitsAggregation topHitAgg = entry.getTopHitsAggregation("top_hits_test");
+
+			List<String> hits = topHitAgg.getSourceAsStringList();
+
+			assertEquals(2, hits.size());
+		}
+	}
+}