Skip to content

Commit

Permalink
Changed inner_hits to work with the new join field type and
Browse files Browse the repository at this point in the history
at the same time maintaining support for the `_parent` meta field type/

Relates to elastic#20257
  • Loading branch information
martijnvg committed Jun 7, 2017
1 parent 14913fd commit db8aa8e
Show file tree
Hide file tree
Showing 16 changed files with 1,296 additions and 538 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,30 @@
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ESIntegTestCase;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
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;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllSuccessful;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHit;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -616,4 +613,25 @@ public void testInnerHitsWithIgnoreUnmapped() throws Exception {
assertSearchHits(response, "1", "3");
}

public void testDontExplode() throws Exception {
assertAcked(prepareCreate("index2").addMapping("type", "nested", "type=nested"));
client().prepareIndex("index2", "type", "1").setSource(jsonBuilder().startObject()
.startArray("nested")
.startObject()
.field("field", "value1")
.endObject()
.endArray()
.endObject())
.setRefreshPolicy(IMMEDIATE)
.get();

QueryBuilder query = nestedQuery("nested", matchQuery("nested.field", "value1"), ScoreMode.Avg)
.innerHit(new InnerHitBuilder().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1));
SearchResponse response = client().prepareSearch("index2")
.setQuery(query)
.get();
assertNoFailures(response);
assertHitCount(response, 1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException {

private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getMapperService());
if (joinFieldMapper == null) {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] no join field has been configured");
}
}

ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, false);
if (parentIdFieldMapper != null) {
Query parentFilter = parentIdFieldMapper.getParentFilter();
Expand All @@ -329,7 +337,8 @@ private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] join field has no parent type configured");
throw new QueryShardException(context, "[" + NAME + "] join field [" + joinFieldMapper.name() +
"] doesn't hold [" + type + "] as a child");
}
}
}
Expand Down Expand Up @@ -513,7 +522,7 @@ protected void extractInnerHitBuilders(Map<String, InnerHitContextBuilder> inner
InnerHitContextBuilder.extractInnerHits(query, children);
String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type;
InnerHitContextBuilder innerHitContextBuilder =
new HasParentQueryBuilder.ParentChildInnerHitContextBuilder(type, query, innerHitBuilder, children);
new ParentChildInnerHitContextBuilder(type, true, query, innerHitBuilder, children);
innerHits.put(name, innerHitContextBuilder);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,22 @@
*/
package org.elasticsearch.join.query;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DocValuesTermsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopDocsCollector;
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.search.join.ScoreMode;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.plain.SortedSetDVOrdinalsIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.ParentFieldMapper;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.InnerHitBuilder;
Expand All @@ -57,10 +45,6 @@
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.join.mapper.ParentIdFieldMapper;
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.fetch.subphase.InnerHitsContext;
import org.elasticsearch.search.internal.SearchContext;

import java.io.IOException;
import java.util.HashMap;
Expand All @@ -69,8 +53,6 @@
import java.util.Objects;
import java.util.Set;

import static org.elasticsearch.search.fetch.subphase.InnerHitsContext.intersect;

/**
* Builder for the 'has_parent' query.
*/
Expand Down Expand Up @@ -198,6 +180,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException {

private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getMapperService());
if (joinFieldMapper == null) {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] no join field has been configured");
}
}

ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, true);
if (parentIdFieldMapper != null) {
Query parentFilter = parentIdFieldMapper.getParentFilter();
Expand All @@ -212,7 +202,8 @@ private Query joinFieldDoToQuery(QueryShardContext context) throws IOException {
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
throw new QueryShardException(context, "[" + NAME + "] join field has no parent type configured");
throw new QueryShardException(context, "[" + NAME + "] join field [" + joinFieldMapper.name() +
"] doesn't hold [" + type + "] as a parent");
}
}
}
Expand Down Expand Up @@ -389,119 +380,9 @@ protected void extractInnerHitBuilders(Map<String, InnerHitContextBuilder> inner
InnerHitContextBuilder.extractInnerHits(query, children);
String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type;
InnerHitContextBuilder innerHitContextBuilder =
new ParentChildInnerHitContextBuilder(type, query, innerHitBuilder, children);
new ParentChildInnerHitContextBuilder(type, false, query, innerHitBuilder, children);
innerHits.put(name, innerHitContextBuilder);
}
}

static class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
private final String typeName;

ParentChildInnerHitContextBuilder(String typeName, QueryBuilder query, InnerHitBuilder innerHitBuilder,
Map<String, InnerHitContextBuilder> children) {
super(query, innerHitBuilder, children);
this.typeName = typeName;
}

@Override
public void build(SearchContext parentSearchContext, InnerHitsContext innerHitsContext) throws IOException {
QueryShardContext queryShardContext = parentSearchContext.getQueryShardContext();
DocumentMapper documentMapper = queryShardContext.documentMapper(typeName);
if (documentMapper == null) {
if (innerHitBuilder.isIgnoreUnmapped() == false) {
throw new IllegalStateException("[" + query.getName() + "] no mapping found for type [" + typeName + "]");
} else {
return;
}
}
String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : documentMapper.type();
ParentChildInnerHitSubContext parentChildInnerHits = new ParentChildInnerHitSubContext(
name, parentSearchContext, queryShardContext.getMapperService(), documentMapper
);
setupInnerHitsContext(queryShardContext, parentChildInnerHits);
innerHitsContext.addInnerHitDefinition(parentChildInnerHits);
}
}

static final class ParentChildInnerHitSubContext extends InnerHitsContext.InnerHitSubContext {
private final MapperService mapperService;
private final DocumentMapper documentMapper;

ParentChildInnerHitSubContext(String name, SearchContext context, MapperService mapperService, DocumentMapper documentMapper) {
super(name, context);
this.mapperService = mapperService;
this.documentMapper = documentMapper;
}

@Override
public TopDocs[] topDocs(SearchHit[] hits) throws IOException {
Weight innerHitQueryWeight = createInnerHitQueryWeight();
TopDocs[] result = new TopDocs[hits.length];
for (int i = 0; i < hits.length; i++) {
SearchHit hit = hits[i];
final Query hitQuery;
if (isParentHit(hit)) {
String field = ParentFieldMapper.joinField(hit.getType());
hitQuery = new DocValuesTermsQuery(field, hit.getId());
} else if (isChildHit(hit)) {
DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType());
final String parentType = hitDocumentMapper.parentFieldMapper().type();
SearchHitField parentField = hit.field(ParentFieldMapper.NAME);
if (parentField == null) {
throw new IllegalStateException("All children must have a _parent");
}
Term uidTerm = context.mapperService().createUidTerm(parentType, parentField.getValue());
if (uidTerm == null) {
hitQuery = new MatchNoDocsQuery("Missing type: " + parentType);
} else {
hitQuery = new TermQuery(uidTerm);
}
} else {
result[i] = Lucene.EMPTY_TOP_DOCS;
continue;
}

BooleanQuery q = new BooleanQuery.Builder()
// Only include docs that have the current hit as parent
.add(hitQuery, BooleanClause.Occur.FILTER)
// Only include docs that have this inner hits type
.add(documentMapper.typeFilter(context.getQueryShardContext()), BooleanClause.Occur.FILTER)
.build();
Weight weight = context.searcher().createNormalizedWeight(q, false);
if (size() == 0) {
TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector();
for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) {
intersect(weight, innerHitQueryWeight, totalHitCountCollector, ctx);
}
result[i] = new TopDocs(totalHitCountCollector.getTotalHits(), Lucene.EMPTY_SCORE_DOCS, 0);
} else {
int topN = Math.min(from() + size(), context.searcher().getIndexReader().maxDoc());
TopDocsCollector<?> topDocsCollector;
if (sort() != null) {
topDocsCollector = TopFieldCollector.create(sort().sort, topN, true, trackScores(), trackScores());
} else {
topDocsCollector = TopScoreDocCollector.create(topN);
}
try {
for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) {
intersect(weight, innerHitQueryWeight, topDocsCollector, ctx);
}
} finally {
clearReleasables(Lifetime.COLLECTION);
}
result[i] = topDocsCollector.topDocs(from(), size());
}
}
return result;
}

private boolean isParentHit(SearchHit hit) {
return hit.getType().equals(documentMapper.parentFieldMapper().type());
}

private boolean isChildHit(SearchHit hit) {
DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType());
return documentMapper.type().equals(hitDocumentMapper.parentFieldMapper().type());
}
}
}
Loading

0 comments on commit db8aa8e

Please sign in to comment.