Skip to content

Commit dd86db3

Browse files
committed
Fixed incorrect results with has_child query with score mode if the parent type has nested object types. The inner objects (Separate Lucene docs) are also emitted as hits, which incorrectly decreased the count down short circuit mechanism in the has_child query.
Closes elastic#4341
1 parent 2b42a0f commit dd86db3

File tree

8 files changed

+116
-18
lines changed

8 files changed

+116
-18
lines changed

src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.index.search.child.ChildrenConstantScoreQuery;
3131
import org.elasticsearch.index.search.child.CustomQueryWrappingFilter;
3232
import org.elasticsearch.index.search.child.DeleteByQueryWrappingFilter;
33+
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
3334
import org.elasticsearch.search.internal.SearchContext;
3435

3536
import java.io.IOException;
@@ -134,8 +135,13 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar
134135
throw new QueryParsingException(parseContext.index(), "[has_child] Type [" + childType + "] points to a non existent parent type [" + parentType + "]");
135136
}
136137

138+
Filter nonNestedDocsFilter = null;
139+
if (parentDocMapper.hasNestedObjects()) {
140+
nonNestedDocsFilter = parseContext.cacheFilter(NonNestedDocsFilter.INSTANCE, null);
141+
}
142+
137143
Filter parentFilter = parseContext.cacheFilter(parentDocMapper.typeFilter(), null);
138-
Query childrenConstantScoreQuery = new ChildrenConstantScoreQuery(query, parentType, childType, parentFilter, shortCircuitParentDocSet);
144+
Query childrenConstantScoreQuery = new ChildrenConstantScoreQuery(query, parentType, childType, parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
139145

140146
if (filterName != null) {
141147
parseContext.addNamedQuery(filterName, childrenConstantScoreQuery);

src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.index.search.child.ChildrenQuery;
3232
import org.elasticsearch.index.search.child.DeleteByQueryWrappingFilter;
3333
import org.elasticsearch.index.search.child.ScoreType;
34+
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
3435
import org.elasticsearch.search.internal.SearchContext;
3536

3637
import java.io.IOException;
@@ -133,16 +134,21 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
133134
throw new QueryParsingException(parseContext.index(), "[has_child] Type [" + childType + "] points to a non existent parent type [" + parentType + "]");
134135
}
135136

137+
Filter nonNestedDocsFilter = null;
138+
if (parentDocMapper.hasNestedObjects()) {
139+
nonNestedDocsFilter = parseContext.cacheFilter(NonNestedDocsFilter.INSTANCE, null);
140+
}
141+
136142
// wrap the query with type query
137143
innerQuery = new XFilteredQuery(innerQuery, parseContext.cacheFilter(childDocMapper.typeFilter(), null));
138144

139145
boolean deleteByQuery = "delete_by_query".equals(SearchContext.current().source());
140146
Query query;
141147
Filter parentFilter = parseContext.cacheFilter(parentDocMapper.typeFilter(), null);
142148
if (!deleteByQuery && scoreType != null) {
143-
query = new ChildrenQuery(parentType, childType, parentFilter, innerQuery, scoreType, shortCircuitParentDocSet);
149+
query = new ChildrenQuery(parentType, childType, parentFilter, innerQuery, scoreType, shortCircuitParentDocSet, nonNestedDocsFilter);
144150
} else {
145-
query = new ChildrenConstantScoreQuery(innerQuery, parentType, childType, parentFilter, shortCircuitParentDocSet);
151+
query = new ChildrenConstantScoreQuery(innerQuery, parentType, childType, parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
146152
if (deleteByQuery) {
147153
query = new XConstantScoreQuery(new DeleteByQueryWrappingFilter(query));
148154
}

src/main/java/org/elasticsearch/index/search/child/ChildrenConstantScoreQuery.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ public class ChildrenConstantScoreQuery extends Query {
5353
private final String childType;
5454
private final Filter parentFilter;
5555
private final int shortCircuitParentDocSet;
56+
private final Filter nonNestedDocsFilter;
5657

5758
private Query rewrittenChildQuery;
5859
private IndexReader rewriteIndexReader;
5960

60-
public ChildrenConstantScoreQuery(Query childQuery, String parentType, String childType, Filter parentFilter, int shortCircuitParentDocSet) {
61+
public ChildrenConstantScoreQuery(Query childQuery, String parentType, String childType, Filter parentFilter, int shortCircuitParentDocSet, Filter nonNestedDocsFilter) {
6162
this.parentFilter = parentFilter;
6263
this.parentType = parentType;
6364
this.childType = childType;
6465
this.originalChildQuery = childQuery;
6566
this.shortCircuitParentDocSet = shortCircuitParentDocSet;
67+
this.nonNestedDocsFilter = nonNestedDocsFilter;
6668
}
6769

6870
@Override
@@ -106,7 +108,7 @@ public Weight createWeight(IndexSearcher searcher) throws IOException {
106108
BytesRef id = collectedUids.v().iterator().next().value.toBytesRef();
107109
shortCircuitFilter = new TermFilter(new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(parentType, id)));
108110
} else if (remaining <= shortCircuitParentDocSet) {
109-
shortCircuitFilter = new ParentIdsFilter(parentType, collectedUids.v().keys, collectedUids.v().allocated);
111+
shortCircuitFilter = new ParentIdsFilter(parentType, collectedUids.v().keys, collectedUids.v().allocated, nonNestedDocsFilter);
110112
}
111113

112114
ParentWeight parentWeight = new ParentWeight(parentFilter, shortCircuitFilter, searchContext, collectedUids);

src/main/java/org/elasticsearch/index/search/child/ChildrenQuery.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.common.bytes.HashedBytesArray;
3434
import org.elasticsearch.common.lease.Releasable;
3535
import org.elasticsearch.common.lucene.docset.DocIdSets;
36+
import org.elasticsearch.common.lucene.search.AndFilter;
3637
import org.elasticsearch.common.lucene.search.ApplyAcceptedDocsFilter;
3738
import org.elasticsearch.common.lucene.search.Queries;
3839
import org.elasticsearch.common.recycler.Recycler;
@@ -43,6 +44,8 @@
4344
import org.elasticsearch.search.internal.SearchContext;
4445

4546
import java.io.IOException;
47+
import java.util.Arrays;
48+
import java.util.List;
4649
import java.util.Set;
4750

4851
/**
@@ -62,17 +65,19 @@ public class ChildrenQuery extends Query {
6265
private final ScoreType scoreType;
6366
private final Query originalChildQuery;
6467
private final int shortCircuitParentDocSet;
68+
private final Filter nonNestedDocsFilter;
6569

6670
private Query rewrittenChildQuery;
6771
private IndexReader rewriteIndexReader;
6872

69-
public ChildrenQuery(String parentType, String childType, Filter parentFilter, Query childQuery, ScoreType scoreType, int shortCircuitParentDocSet) {
73+
public ChildrenQuery(String parentType, String childType, Filter parentFilter, Query childQuery, ScoreType scoreType, int shortCircuitParentDocSet, Filter nonNestedDocsFilter) {
7074
this.parentType = parentType;
7175
this.childType = childType;
7276
this.parentFilter = parentFilter;
7377
this.originalChildQuery = childQuery;
7478
this.scoreType = scoreType;
7579
this.shortCircuitParentDocSet = shortCircuitParentDocSet;
80+
this.nonNestedDocsFilter = nonNestedDocsFilter;
7681
}
7782

7883
@Override
@@ -164,12 +169,20 @@ public Weight createWeight(IndexSearcher searcher) throws IOException {
164169
return Queries.newMatchNoDocsQuery().createWeight(searcher);
165170
}
166171

167-
Filter parentFilter;
172+
final Filter parentFilter;
168173
if (size == 1) {
169174
BytesRef id = uidToScore.v().keys().iterator().next().value.toBytesRef();
170-
parentFilter = new TermFilter(new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(parentType, id)));
175+
if (nonNestedDocsFilter != null) {
176+
List<Filter> filters = Arrays.asList(
177+
new TermFilter(new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(parentType, id))),
178+
nonNestedDocsFilter
179+
);
180+
parentFilter = new AndFilter(filters);
181+
} else {
182+
parentFilter = new TermFilter(new Term(UidFieldMapper.NAME, Uid.createUidAsBytes(parentType, id)));
183+
}
171184
} else if (size <= shortCircuitParentDocSet) {
172-
parentFilter = new ParentIdsFilter(parentType, uidToScore.v().keys, uidToScore.v().allocated);
185+
parentFilter = new ParentIdsFilter(parentType, uidToScore.v().keys, uidToScore.v().allocated, nonNestedDocsFilter);
173186
} else {
174187
parentFilter = new ApplyAcceptedDocsFilter(this.parentFilter);
175188
}

src/main/java/org/elasticsearch/index/search/child/ParentIdsFilter.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ final class ParentIdsFilter extends Filter {
4848
private final Object[] keys;
4949
private final boolean[] allocated;
5050

51-
public ParentIdsFilter(String parentType, Object[] keys, boolean[] allocated) {
51+
private final Filter nonNestedDocsFilter;
52+
53+
public ParentIdsFilter(String parentType, Object[] keys, boolean[] allocated, Filter nonNestedDocsFilter) {
54+
this.nonNestedDocsFilter = nonNestedDocsFilter;
5255
this.parentTypeBr = new BytesRef(parentType);
5356
this.keys = keys;
5457
this.allocated = allocated;
@@ -65,6 +68,15 @@ public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws
6568
BytesRef uidSpare = new BytesRef();
6669
BytesRef idSpare = new BytesRef();
6770

71+
if (acceptDocs == null) {
72+
acceptDocs = context.reader().getLiveDocs();
73+
}
74+
75+
FixedBitSet nonNestedDocs = null;
76+
if (nonNestedDocsFilter != null) {
77+
nonNestedDocs = (FixedBitSet) nonNestedDocsFilter.getDocIdSet(context, acceptDocs);
78+
}
79+
6880
DocsEnum docsEnum = null;
6981
FixedBitSet result = null;
7082
for (int i = 0; i < allocated.length; i++) {
@@ -82,16 +94,22 @@ public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws
8294
docId = docsEnum.nextDoc();
8395
if (docId != DocIdSetIterator.NO_MORE_DOCS) {
8496
result = new FixedBitSet(context.reader().maxDoc());
85-
result.set(docId);
8697
} else {
8798
continue;
8899
}
100+
} else {
101+
docId = docsEnum.nextDoc();
102+
if (docId == DocIdSetIterator.NO_MORE_DOCS) {
103+
continue;
104+
}
89105
}
90-
for (docId = docsEnum.nextDoc(); docId < DocIdSetIterator.NO_MORE_DOCS; docId = docsEnum.nextDoc()) {
91-
result.set(docId);
106+
if (nonNestedDocs != null && !nonNestedDocs.get(docId)) {
107+
docId = nonNestedDocs.nextSetBit(docId);
92108
}
109+
result.set(docId);
110+
assert docsEnum.advance(docId + 1) == DocIdSetIterator.NO_MORE_DOCS : "DocId " + docId + " should have been the last one but docId " + docsEnum.docID() + " exists.";
93111
}
94112
}
95113
return result;
96114
}
97-
}
115+
}

src/test/java/org/elasticsearch/index/search/child/ChildrenConstantScoreQueryTests.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
5050
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
5151
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
52+
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
5253
import org.elasticsearch.index.service.IndexService;
5354
import org.elasticsearch.indices.cache.filter.IndicesFilterCache;
5455
import org.elasticsearch.node.settings.NodeSettingsService;
@@ -108,7 +109,7 @@ public void testSimple() throws Exception {
108109
TermQuery childQuery = new TermQuery(new Term("field1", "value" + (1 + random().nextInt(3))));
109110
TermFilter parentFilter = new TermFilter(new Term(TypeFieldMapper.NAME, "parent"));
110111
int shortCircuitParentDocSet = random().nextInt(5);
111-
ChildrenConstantScoreQuery query = new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet);
112+
ChildrenConstantScoreQuery query = new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet, null);
112113

113114
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
114115
searcher.search(query, collector);
@@ -248,15 +249,16 @@ public void testRandom() throws Exception {
248249
String childValue = childValues[random().nextInt(numUniqueChildValues)];
249250
TermQuery childQuery = new TermQuery(new Term("field1", childValue));
250251
int shortCircuitParentDocSet = random().nextInt(numParentDocs);
252+
Filter nonNestedDocsFilter = random().nextBoolean() ? NonNestedDocsFilter.INSTANCE : null;
251253
Query query;
252254
if (random().nextBoolean()) {
253255
// Usage in HasChildQueryParser
254-
query = new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet);
256+
query = new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
255257
} else {
256258
// Usage in HasChildFilterParser
257259
query = new XConstantScoreQuery(
258260
new CustomQueryWrappingFilter(
259-
new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet)
261+
new ChildrenConstantScoreQuery(childQuery, "parent", "child", parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter)
260262
)
261263
);
262264
}

src/test/java/org/elasticsearch/index/search/child/ChildrenQueryTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
3737
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
3838
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
39+
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
3940
import org.elasticsearch.search.internal.ContextIndexSearcher;
4041
import org.elasticsearch.search.internal.SearchContext;
4142
import org.elasticsearch.test.ElasticsearchLuceneTestCase;
@@ -197,7 +198,8 @@ public void testRandom() throws Exception {
197198
Query childQuery = new ConstantScoreQuery(new TermQuery(new Term("field1", childValue)));
198199
int shortCircuitParentDocSet = random().nextInt(numParentDocs);
199200
ScoreType scoreType = ScoreType.values()[random().nextInt(ScoreType.values().length)];
200-
Query query = new ChildrenQuery("parent", "child", parentFilter, childQuery, scoreType, shortCircuitParentDocSet);
201+
Filter nonNestedDocsFilter = random().nextBoolean() ? NonNestedDocsFilter.INSTANCE : null;
202+
Query query = new ChildrenQuery("parent", "child", parentFilter, childQuery, scoreType, shortCircuitParentDocSet, nonNestedDocsFilter);
201203
query = new XFilteredQuery(query, filterMe);
202204
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
203205
int numHits = 1 + random().nextInt(25);

src/test/java/org/elasticsearch/search/child/SimpleChildQuerySearchTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.common.settings.ImmutableSettings;
3636
import org.elasticsearch.index.mapper.MergeMappingException;
3737
import org.elasticsearch.index.query.*;
38+
import org.elasticsearch.index.search.child.ScoreType;
3839
import org.elasticsearch.search.facet.terms.TermsFacet;
3940
import org.elasticsearch.search.sort.SortBuilders;
4041
import org.elasticsearch.search.sort.SortOrder;
@@ -1955,6 +1956,54 @@ public void run() {
19551956
}
19561957
}
19571958

1959+
@Test
1960+
public void testHasChildQueryWithNestedInnerObjects() throws Exception {
1961+
client().admin().indices().prepareCreate("test")
1962+
.setSettings(
1963+
ImmutableSettings.settingsBuilder()
1964+
.put("index.number_of_shards", 1)
1965+
.put("index.number_of_replicas", 0)
1966+
)
1967+
.addMapping("parent", "objects", "type=nested")
1968+
.addMapping("child", "_parent", "type=parent")
1969+
.execute().actionGet();
1970+
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet();
1971+
1972+
client().prepareIndex("test", "parent", "p1")
1973+
.setSource(jsonBuilder().startObject().field("p_field", "1").startArray("objects")
1974+
.startObject().field("i_field", "1").endObject()
1975+
.startObject().field("i_field", "2").endObject()
1976+
.startObject().field("i_field", "3").endObject()
1977+
.startObject().field("i_field", "4").endObject()
1978+
.startObject().field("i_field", "5").endObject()
1979+
.startObject().field("i_field", "6").endObject()
1980+
.endArray().endObject())
1981+
.execute().actionGet();
1982+
client().prepareIndex("test", "parent", "p2")
1983+
.setSource(jsonBuilder().startObject().field("p_field", "2").startArray("objects")
1984+
.startObject().field("i_field", "1").endObject()
1985+
.startObject().field("i_field", "2").endObject()
1986+
.endArray().endObject())
1987+
.execute().actionGet();
1988+
client().prepareIndex("test", "child", "c1").setParent("p1").setSource("c_field", "blue").execute().actionGet();
1989+
client().prepareIndex("test", "child", "c2").setParent("p1").setSource("c_field", "red").execute().actionGet();
1990+
client().prepareIndex("test", "child", "c3").setParent("p2").setSource("c_field", "red").execute().actionGet();
1991+
client().admin().indices().prepareRefresh("test").execute().actionGet();
1992+
1993+
String scoreMode = ScoreType.values()[randomInt(ScoreType.values().length)].name().toLowerCase(Locale.ROOT);
1994+
SearchResponse searchResponse = client().prepareSearch("test")
1995+
.setQuery(filteredQuery(QueryBuilders.hasChildQuery("child", termQuery("c_field", "blue")).scoreType(scoreMode), notFilter(termFilter("p_field", "3"))))
1996+
.execute().actionGet();
1997+
assertNoFailures(searchResponse);
1998+
assertThat(searchResponse.getHits().totalHits(), equalTo(1l));
1999+
2000+
searchResponse = client().prepareSearch("test")
2001+
.setQuery(filteredQuery(QueryBuilders.hasChildQuery("child", termQuery("c_field", "red")).scoreType(scoreMode), notFilter(termFilter("p_field", "3"))))
2002+
.execute().actionGet();
2003+
assertNoFailures(searchResponse);
2004+
assertThat(searchResponse.getHits().totalHits(), equalTo(2l));
2005+
}
2006+
19582007
private static HasChildFilterBuilder hasChildFilter(String type, QueryBuilder queryBuilder) {
19592008
HasChildFilterBuilder hasChildFilterBuilder = FilterBuilders.hasChildFilter(type, queryBuilder);
19602009
hasChildFilterBuilder.setShortCircuitCutoff(randomInt(10));

0 commit comments

Comments
 (0)