parent/child: Removed ParentJoinFieldSubFetchPhase

This commit is contained in:
Martijn van Groningen 2017-07-05 12:17:03 +02:00
parent 31614c3ddb
commit d0f9f425bd
No known key found for this signature in database
GPG Key ID: AB236F4FCF2AF12A
9 changed files with 58 additions and 328 deletions

View File

@ -150,12 +150,7 @@ Will return:
"_score": null,
"_source": {
"text": "This is a parent document",
"my_join_field": "my_parent"
},
"fields": {
"my_join_field": [
"my_parent" <1>
]
"my_join_field": "my_parent" <1>
},
"sort": [
"1"
@ -168,12 +163,7 @@ Will return:
"_score": null,
"_source": {
"text": "This is a another parent document",
"my_join_field": "my_parent"
},
"fields": {
"my_join_field": [
"my_parent" <2>
]
"my_join_field": "my_parent" <2>
},
"sort": [
"2"
@ -192,14 +182,6 @@ Will return:
"parent": "1" <4>
}
},
"fields": {
"my_join_field": [
"my_child"
],
"my_join_field#my_parent": [
"1"
]
},
"sort": [
"3"
]
@ -217,14 +199,6 @@ Will return:
"parent": "1"
}
},
"fields": {
"my_join_field": [
"my_child"
],
"my_join_field#my_parent": [
"1"
]
},
"sort": [
"4"
]

View File

@ -498,11 +498,6 @@ An example of a response snippet that could be generated from the above search r
"number": 1,
"my_join_field": "my_parent"
},
"fields": {
"my_join_field": [
"my_parent"
]
},
"inner_hits": {
"my_child": {
"hits": {
@ -520,14 +515,6 @@ An example of a response snippet that could be generated from the above search r
"name": "my_child",
"parent": "1"
}
},
"fields": {
"my_join_field": [
"my_child"
],
"my_join_field#my_parent": [
"1"
]
}
}
]

View File

@ -19,11 +19,9 @@
package org.elasticsearch.join;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder;
import org.elasticsearch.join.aggregations.InternalChildren;
import org.elasticsearch.join.fetch.ParentJoinFieldSubFetchPhase;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.join.query.HasChildQueryBuilder;
import org.elasticsearch.join.query.HasParentQueryBuilder;
@ -31,7 +29,6 @@ import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.SearchPlugin;
import org.elasticsearch.search.fetch.FetchSubPhase;
import java.util.Arrays;
import java.util.Collections;
@ -39,7 +36,9 @@ import java.util.List;
import java.util.Map;
public class ParentJoinPlugin extends Plugin implements SearchPlugin, MapperPlugin {
public ParentJoinPlugin(Settings settings) {}
public ParentJoinPlugin() {
}
@Override
public List<QuerySpec<?>> getQueries() {
@ -62,9 +61,4 @@ public class ParentJoinPlugin extends Plugin implements SearchPlugin, MapperPlug
public Map<String, Mapper.TypeParser> getMappers() {
return Collections.singletonMap(ParentJoinFieldMapper.CONTENT_TYPE, new ParentJoinFieldMapper.TypeParser());
}
@Override
public List<FetchSubPhase> getFetchSubPhases(FetchPhaseConstructionContext context) {
return Collections.singletonList(new ParentJoinFieldSubFetchPhase());
}
}

View File

@ -1,88 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.join.fetch;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.join.mapper.ParentIdFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.search.fetch.FetchSubPhase;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A sub fetch phase that retrieves the join name and the parent id for each document containing
* a {@link ParentJoinFieldMapper} field.
*/
public final class ParentJoinFieldSubFetchPhase implements FetchSubPhase {
@Override
public void hitExecute(SearchContext context, HitContext hitContext) {
if (context.storedFieldsContext() != null && context.storedFieldsContext().fetchFields() == false) {
return;
}
ParentJoinFieldMapper mapper = ParentJoinFieldMapper.getMapper(context.mapperService());
if (mapper == null) {
// hit has no join field.
return;
}
String joinName = getSortedDocValue(mapper.name(), hitContext.reader(), hitContext.docId());
if (joinName == null) {
return;
}
// if the hit is a children we extract the parentId (if it's a parent we can use the _id field directly)
ParentIdFieldMapper parentMapper = mapper.getParentIdFieldMapper(joinName, false);
String parentId = null;
if (parentMapper != null) {
parentId = getSortedDocValue(parentMapper.name(), hitContext.reader(), hitContext.docId());
}
Map<String, DocumentField> fields = hitContext.hit().fieldsOrNull();
if (fields == null) {
fields = new HashMap<>();
hitContext.hit().fields(fields);
}
fields.put(mapper.name(), new DocumentField(mapper.name(), Collections.singletonList(joinName)));
if (parentId != null) {
fields.put(parentMapper.name(), new DocumentField(parentMapper.name(), Collections.singletonList(parentId)));
}
}
private String getSortedDocValue(String field, LeafReader reader, int docId) {
try {
SortedDocValues docValues = reader.getSortedDocValues(field);
if (docValues == null || docValues.advanceExact(docId) == false) {
return null;
}
int ord = docValues.ordValue();
BytesRef joinName = docValues.lookupOrd(ord);
return joinName.utf8ToString();
} catch (IOException e) {
throw ExceptionsHelper.convertToElastic(e);
}
}
}

View File

@ -19,6 +19,8 @@
package org.elasticsearch.join.query;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.ReaderUtil;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
@ -32,6 +34,8 @@ import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.index.mapper.DocumentMapper;
@ -49,6 +53,7 @@ import org.elasticsearch.search.fetch.subphase.InnerHitsContext;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.search.fetch.subphase.InnerHitsContext.intersect;
@ -126,8 +131,8 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
TopDocs[] result = new TopDocs[hits.length];
for (int i = 0; i < hits.length; i++) {
SearchHit hit = hits[i];
DocumentField joinField = hit.getFields().get(joinFieldMapper.name());
if (joinField == null) {
String joinName = getSortedDocValue(joinFieldMapper.name(), context, hit.docId());
if (joinName == null) {
result[i] = Lucene.EMPTY_TOP_DOCS;
continue;
}
@ -150,8 +155,8 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
.add(joinFieldMapper.fieldType().termQuery(typeName, qsc), BooleanClause.Occur.FILTER)
.build();
} else {
DocumentField parentIdField = hit.getFields().get(parentIdFieldMapper.name());
q = context.mapperService().fullName(IdFieldMapper.NAME).termQuery(parentIdField.getValue(), qsc);
String parentId = getSortedDocValue(parentIdFieldMapper.name(), context, hit.docId());
q = context.mapperService().fullName(IdFieldMapper.NAME).termQuery(parentId, qsc);
}
Weight weight = context.searcher().createNormalizedWeight(q, false);
@ -181,6 +186,24 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
}
return result;
}
private String getSortedDocValue(String field, SearchContext context, int docId) {
try {
List<LeafReaderContext> ctxs = context.searcher().getIndexReader().leaves();
LeafReaderContext ctx = ctxs.get(ReaderUtil.subIndex(docId, ctxs));
SortedDocValues docValues = ctx.reader().getSortedDocValues(field);
int segmentDocId = docId - ctx.docBase;
if (docValues == null || docValues.advanceExact(segmentDocId) == false) {
return null;
}
int ord = docValues.ordValue();
BytesRef joinName = docValues.lookupOrd(ord);
return joinName.utf8ToString();
} catch (IOException e) {
throw ExceptionsHelper.convertToElastic(e);
}
}
}
static final class ParentChildInnerHitSubContext extends InnerHitsContext.InnerHitSubContext {

View File

@ -1,161 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.join.fetch;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.join.ParentJoinPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.test.ESSingleNodeTestCase;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
public class ParentJoinFieldSubFetchPhaseTests extends ESSingleNodeTestCase {
@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
return Collections.singletonList(ParentJoinPlugin.class);
}
public void testSingleParentJoinField() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject()
.startObject("properties")
.startObject("join_field")
.field("type", "join")
.startObject("relations")
.field("parent", "child")
.field("child", "grand_child")
.field("product", "item")
.endObject()
.endObject()
.endObject()
.endObject().string();
IndexService service = createIndex("test", Settings.EMPTY);
service.mapperService().merge("doc", new CompressedXContent(mapping),
MapperService.MergeReason.MAPPING_UPDATE, true);
// empty document
client().prepareIndex("test", "doc", "0")
.setSource().setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
// parent document
client().prepareIndex("test", "doc", "1")
.setSource("join_field", Collections.singletonMap("name", "parent"))
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
// child document
Map<String, String> joinField = new HashMap<>();
joinField.put("name", "child");
joinField.put("parent", "1");
client().prepareIndex("test", "doc", "2")
.setSource("join_field", joinField).setRouting("1")
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
// grand_child document
joinField.clear();
joinField.put("name", "grand_child");
joinField.put("parent", "2");
client().prepareIndex("test", "doc", "3")
.setSource("join_field", joinField).setRouting("2")
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
// product document
client().prepareIndex("test", "doc", "4")
.setSource("join_field", Collections.singletonMap("name", "product"))
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
// item document
joinField.clear();
joinField.put("name", "item");
joinField.put("parent", "4");
client().prepareIndex("test", "doc", "5")
.setSource("join_field", joinField).setRouting("4").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
SearchResponse response = client().prepareSearch("test")
.setQuery(QueryBuilders.termQuery("join_field", "parent"))
.get();
assertThat(response.getHits().totalHits, equalTo(1L));
assertThat(response.getHits().getHits().length, equalTo(1));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("parent"));
assertNull(response.getHits().getHits()[0].field("join_field#parent"));
response = client().prepareSearch("test")
.setQuery(QueryBuilders.termQuery("join_field", "child"))
.get();
assertThat(response.getHits().totalHits, equalTo(1L));
assertThat(response.getHits().getHits().length, equalTo(1));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("child"));
assertThat(response.getHits().getHits()[0].field("join_field#parent").getValue(), equalTo("1"));
assertNull(response.getHits().getHits()[0].field("join_field#child"));
response = client().prepareSearch("test")
.setQuery(QueryBuilders.termQuery("join_field", "grand_child"))
.get();
assertThat(response.getHits().totalHits, equalTo(1L));
assertThat(response.getHits().getHits().length, equalTo(1));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("grand_child"));
assertThat(response.getHits().getHits()[0].field("join_field#child").getValue(), equalTo("2"));
response = client().prepareSearch("test")
.setQuery(QueryBuilders.termQuery("join_field", "product"))
.get();
assertThat(response.getHits().totalHits, equalTo(1L));
assertThat(response.getHits().getHits().length, equalTo(1));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("product"));
assertNull(response.getHits().getHits()[0].field("join_field#product"));
response = client().prepareSearch("test")
.setQuery(QueryBuilders.termQuery("join_field", "item"))
.get();
assertThat(response.getHits().totalHits, equalTo(1L));
assertThat(response.getHits().getHits().length, equalTo(1));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("item"));
assertThat(response.getHits().getHits()[0].field("join_field#product").getValue(), equalTo("4"));
response = client().prepareSearch("test")
.addSort(SortBuilders.fieldSort("join_field"))
.get();
assertThat(response.getHits().totalHits, equalTo(6L));
assertThat(response.getHits().getHits().length, equalTo(6));
assertThat(response.getHits().getHits()[0].field("join_field").getValue(), equalTo("child"));
assertThat(response.getHits().getHits()[0].field("join_field#parent").getValue(), equalTo("1"));
assertNull(response.getHits().getHits()[0].field("join_field#child"));
assertThat(response.getHits().getHits()[1].field("join_field").getValue(), equalTo("grand_child"));
assertThat(response.getHits().getHits()[1].field("join_field#child").getValue(), equalTo("2"));
assertThat(response.getHits().getHits()[2].field("join_field").getValue(), equalTo("item"));
assertThat(response.getHits().getHits()[2].field("join_field#product").getValue(), equalTo("4"));
assertThat(response.getHits().getHits()[3].field("join_field").getValue(), equalTo("parent"));
assertNull(response.getHits().getHits()[3].field("join_field#parent"));
assertThat(response.getHits().getHits()[4].field("join_field").getValue(), equalTo("product"));
assertNull(response.getHits().getHits()[4].field("join_field#product"));
assertNull(response.getHits().getHits()[5].field("join_field"));
}
}

View File

@ -60,6 +60,7 @@ import java.util.Map;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.idsQuery;
@ -216,8 +217,8 @@ public class ChildQuerySearchIT extends ParentChildTestCase {
assertNoFailures(searchResponse);
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1L));
assertThat(searchResponse.getHits().getAt(0).getId(), equalTo("c1"));
assertThat(searchResponse.getHits().getAt(0).field("join_field").getValue(), equalTo("child"));
assertThat(searchResponse.getHits().getAt(0).field("join_field#parent").getValue(), equalTo("p1"));
assertThat(extractValue("join_field.name", searchResponse.getHits().getAt(0).getSourceAsMap()), equalTo("child"));
assertThat(extractValue("join_field.parent", searchResponse.getHits().getAt(0).getSourceAsMap()), equalTo("p1"));
}
// TEST matching on parent
@ -236,11 +237,11 @@ public class ChildQuerySearchIT extends ParentChildTestCase {
assertNoFailures(searchResponse);
assertThat(searchResponse.getHits().getTotalHits(), equalTo(2L));
assertThat(searchResponse.getHits().getAt(0).getId(), anyOf(equalTo("c1"), equalTo("c2")));
assertThat(searchResponse.getHits().getAt(0).field("join_field").getValue(), equalTo("child"));
assertThat(searchResponse.getHits().getAt(0).field("join_field#parent").getValue(), equalTo("p1"));
assertThat(extractValue("join_field.name", searchResponse.getHits().getAt(0).getSourceAsMap()), equalTo("child"));
assertThat(extractValue("join_field.parent", searchResponse.getHits().getAt(0).getSourceAsMap()), equalTo("p1"));
assertThat(searchResponse.getHits().getAt(1).getId(), anyOf(equalTo("c1"), equalTo("c2")));
assertThat(searchResponse.getHits().getAt(1).field("join_field").getValue(), equalTo("child"));
assertThat(searchResponse.getHits().getAt(1).field("join_field#parent").getValue(), equalTo("p1"));
assertThat(extractValue("join_field.name", searchResponse.getHits().getAt(1).getSourceAsMap()), equalTo("child"));
assertThat(extractValue("join_field.parent", searchResponse.getHits().getAt(1).getSourceAsMap()), equalTo("p1"));
}
if (legacy()) {

View File

@ -26,7 +26,6 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.join.ParentJoinPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.MockScriptEngine;
import org.elasticsearch.script.MockScriptPlugin;
@ -40,7 +39,6 @@ import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -49,6 +47,7 @@ import java.util.Map;
import java.util.function.Function;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
@ -550,7 +549,8 @@ public class InnerHitsIT extends ParentChildTestCase {
if (legacy()) {
assertThat(hit.getInnerHits().get("child_type").getAt(0).field("_parent").getValue(), equalTo("1"));
} else {
assertThat(hit.getInnerHits().get("child_type").getAt(0).field("join_field#parent_type").getValue(), equalTo("1"));
String parentId = (String) extractValue("join_field.parent", hit.getInnerHits().get("child_type").getAt(0).getSourceAsMap());
assertThat(parentId, equalTo("1"));
}
assertThat(hit.getInnerHits().get("child_type").getAt(0).getInnerHits().get("nested_type").getAt(0).field("_parent"), nullValue());
}

View File

@ -71,36 +71,36 @@ setup:
- match: { hits.hits.0._index: "test" }
- match: { hits.hits.0._type: "doc" }
- match: { hits.hits.0._id: "3" }
- match: { hits.hits.0.fields.join_field: ["child"] }
- match: { hits.hits.0.fields.join_field#parent: ["1"] }
- match: { hits.hits.0._source.join_field.name: "child" }
- match: { hits.hits.0._source.join_field.parent: "1" }
- is_false: hits.hits.0.fields.join_field#child }
- match: { hits.hits.1._index: "test" }
- match: { hits.hits.1._type: "doc" }
- match: { hits.hits.1._id: "4" }
- match: { hits.hits.1.fields.join_field: ["child"] }
- match: { hits.hits.1.fields.join_field#parent: ["1"] }
- match: { hits.hits.1._source.join_field.name: "child" }
- match: { hits.hits.1._source.join_field.parent: "1" }
- is_false: hits.hits.1.fields.join_field#child }
- match: { hits.hits.2._index: "test" }
- match: { hits.hits.2._type: "doc" }
- match: { hits.hits.2._id: "5" }
- match: { hits.hits.2.fields.join_field: ["child"] }
- match: { hits.hits.2.fields.join_field#parent: ["2"] }
- match: { hits.hits.2._source.join_field.name: "child" }
- match: { hits.hits.2._source.join_field.parent: "2" }
- is_false: hits.hits.2.fields.join_field#child }
- match: { hits.hits.3._index: "test" }
- match: { hits.hits.3._type: "doc" }
- match: { hits.hits.3._id: "6" }
- match: { hits.hits.3.fields.join_field: ["grand_child"] }
- match: { hits.hits.3.fields.join_field#child: ["5"] }
- match: { hits.hits.3._source.join_field.name: "grand_child" }
- match: { hits.hits.3._source.join_field.parent: "5" }
- match: { hits.hits.4._index: "test" }
- match: { hits.hits.4._type: "doc" }
- match: { hits.hits.4._id: "1" }
- match: { hits.hits.4.fields.join_field: ["parent"] }
- is_false: hits.hits.4.fields.join_field#parent
- match: { hits.hits.4._source.join_field.name: "parent" }
- is_false: hits.hits.4._source.join_field.parent
- match: { hits.hits.5._index: "test" }
- match: { hits.hits.5._type: "doc" }
- match: { hits.hits.5._id: "2" }
- match: { hits.hits.5.fields.join_field: ["parent"] }
- is_false: hits.hits.5.fields.join_field#parent
- match: { hits.hits.5._source.join_field.name: "parent" }
- is_false: hits.hits.5._source.join_field.parent
---
"Test parent_id query":
@ -121,12 +121,12 @@ setup:
- match: { hits.hits.0._index: "test" }
- match: { hits.hits.0._type: "doc" }
- match: { hits.hits.0._id: "3" }
- match: { hits.hits.0.fields.join_field: ["child"] }
- match: { hits.hits.0.fields.join_field#parent: ["1"] }
- match: { hits.hits.0._source.join_field.name: "child" }
- match: { hits.hits.0._source.join_field.parent: "1" }
- match: { hits.hits.1._index: "test" }
- match: { hits.hits.1._type: "doc" }
- match: { hits.hits.1._id: "4" }
- match: { hits.hits.1.fields.join_field: ["child"] }
- match: { hits.hits.1.fields.join_field#parent: ["1"] }
- match: { hits.hits.1._source.join_field.name: "child" }
- match: { hits.hits.1._source.join_field.parent: "1" }