summaryrefslogtreecommitdiff
path: root/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java')
-rw-r--r--modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java189
1 files changed, 168 insertions, 21 deletions
diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java
index b216e886a5..30918233fa 100644
--- a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java
+++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java
@@ -18,36 +18,57 @@
*/
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;
+import org.elasticsearch.index.query.InnerHitContextBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;
+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;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import static org.elasticsearch.search.fetch.subphase.InnerHitsContext.intersect;
+
/**
* Builder for the 'has_parent' query.
*/
@@ -69,18 +90,18 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
private final QueryBuilder query;
private final String type;
private final boolean score;
- private InnerHitBuilder innerHit;
+ private InnerHitBuilder innerHitBuilder;
private boolean ignoreUnmapped = false;
public HasParentQueryBuilder(String type, QueryBuilder query, boolean score) {
this(type, query, score, null);
}
- private HasParentQueryBuilder(String type, QueryBuilder query, boolean score, InnerHitBuilder innerHit) {
+ private HasParentQueryBuilder(String type, QueryBuilder query, boolean score, InnerHitBuilder innerHitBuilder) {
this.type = requireValue(type, "[" + NAME + "] requires 'type' field");
this.query = requireValue(query, "[" + NAME + "] requires 'query' field");
this.score = score;
- this.innerHit = innerHit;
+ this.innerHitBuilder = innerHitBuilder;
}
/**
@@ -91,7 +112,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
type = in.readString();
score = in.readBoolean();
query = in.readNamedWriteable(QueryBuilder.class);
- innerHit = in.readOptionalWriteable(InnerHitBuilder::new);
+ innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new);
ignoreUnmapped = in.readBoolean();
}
@@ -100,7 +121,15 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
out.writeString(type);
out.writeBoolean(score);
out.writeNamedWriteable(query);
- out.writeOptionalWriteable(innerHit);
+ if (out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) {
+ final boolean hasInnerHit = innerHitBuilder != null;
+ out.writeBoolean(hasInnerHit);
+ if (hasInnerHit) {
+ innerHitBuilder.writeToParentChildBWC(out, query, type);
+ }
+ } else {
+ out.writeOptionalWriteable(innerHitBuilder);
+ }
out.writeBoolean(ignoreUnmapped);
}
@@ -129,11 +158,11 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
* Returns inner hit definition in the scope of this query and reusing the defined type and query.
*/
public InnerHitBuilder innerHit() {
- return innerHit;
+ return innerHitBuilder;
}
- public HasParentQueryBuilder innerHit(InnerHitBuilder innerHit, boolean ignoreUnmapped) {
- this.innerHit = new InnerHitBuilder(innerHit, query, type, ignoreUnmapped);
+ public HasParentQueryBuilder innerHit(InnerHitBuilder innerHit) {
+ this.innerHitBuilder = innerHit;
return this;
}
@@ -172,7 +201,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
if (ignoreUnmapped) {
return new MatchNoDocsQuery();
} else {
- throw new QueryShardException(context, "[" + NAME + "] query configured 'parent_type' [" + type + "] is not a valid type");
+ throw new QueryShardException(context,
+ "[" + NAME + "] query configured 'parent_type' [" + type + "] is not a valid type");
}
}
@@ -224,8 +254,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
builder.field(SCORE_FIELD.getPreferredName(), score);
builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
printBoostAndQueryName(builder);
- if (innerHit != null) {
- builder.field(INNER_HITS_FIELD.getPreferredName(), innerHit, params);
+ if (innerHitBuilder != null) {
+ builder.field(INNER_HITS_FIELD.getPreferredName(), innerHitBuilder, params);
}
builder.endObject();
}
@@ -251,7 +281,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
} else if (INNER_HITS_FIELD.match(currentFieldName)) {
innerHits = InnerHitBuilder.fromXContent(parseContext);
} else {
- throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + currentFieldName + "]");
+ throw new ParsingException(parser.getTokenLocation(),
+ "[has_parent] query does not support [" + currentFieldName + "]");
}
} else if (token.isValue()) {
if (TYPE_FIELD.match(currentFieldName)) {
@@ -275,7 +306,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName)) {
queryName = parser.text();
} else {
- throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + currentFieldName + "]");
+ throw new ParsingException(parser.getTokenLocation(),
+ "[has_parent] query does not support [" + currentFieldName + "]");
}
}
}
@@ -284,7 +316,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
.queryName(queryName)
.boost(boost);
if (innerHits != null) {
- queryBuilder.innerHit(innerHits, ignoreUnmapped);
+ queryBuilder.innerHit(innerHits);
}
return queryBuilder;
}
@@ -299,21 +331,20 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
return Objects.equals(query, that.query)
&& Objects.equals(type, that.type)
&& Objects.equals(score, that.score)
- && Objects.equals(innerHit, that.innerHit)
+ && Objects.equals(innerHitBuilder, that.innerHitBuilder)
&& Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
}
@Override
protected int doHashCode() {
- return Objects.hash(query, type, score, innerHit, ignoreUnmapped);
+ return Objects.hash(query, type, score, innerHitBuilder, ignoreUnmapped);
}
@Override
protected QueryBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException {
QueryBuilder rewrittenQuery = query.rewrite(queryShardContext);
if (rewrittenQuery != query) {
- InnerHitBuilder rewrittenInnerHit = InnerHitBuilder.rewrite(innerHit, rewrittenQuery);
- HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(type, rewrittenQuery, score, rewrittenInnerHit);
+ HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(type, rewrittenQuery, score, innerHitBuilder);
hasParentQueryBuilder.ignoreUnmapped(ignoreUnmapped);
return hasParentQueryBuilder;
}
@@ -321,9 +352,125 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
}
@Override
- protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
- if (innerHit!= null) {
- innerHit.inlineInnerHits(innerHits);
+ protected void extractInnerHitBuilders(Map<String, InnerHitContextBuilder> innerHits) {
+ if (innerHitBuilder != null) {
+ Map<String, InnerHitContextBuilder> children = new HashMap<>();
+ InnerHitContextBuilder.extractInnerHits(query, children);
+ String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type;
+ InnerHitContextBuilder innerHitContextBuilder =
+ new ParentChildInnerHitContextBuilder(type, 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());
}
}
}