Made sure that wrapped child query / parent query gets rewritten only once.

This commit is contained in:
Martijn van Groningen 2013-02-05 10:27:31 +01:00
parent 9e89323ad2
commit 19295280d9
4 changed files with 169 additions and 36 deletions

View File

@ -55,8 +55,9 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
private final String childType;
private final Filter parentFilter;
private final ScoreType scoreType;
private final Query childQuery;
private final Query originalChildQuery;
private Query rewrittenChildQuery;
private TObjectFloatHashMap<HashedBytesArray> uidToScore;
private TObjectIntHashMap<HashedBytesArray> uidToCount;
@ -65,7 +66,7 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
this.parentType = parentType;
this.childType = childType;
this.parentFilter = parentFilter;
this.childQuery = childQuery;
this.originalChildQuery = childQuery;
this.scoreType = scoreType;
}
@ -75,7 +76,8 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
this.childType = unProcessedQuery.childType;
this.parentFilter = unProcessedQuery.parentFilter;
this.scoreType = unProcessedQuery.scoreType;
this.childQuery = rewrittenChildQuery;
this.originalChildQuery = unProcessedQuery.originalChildQuery;
this.rewrittenChildQuery = rewrittenChildQuery;
this.uidToScore = unProcessedQuery.uidToScore;
this.uidToCount = unProcessedQuery.uidToCount;
@ -84,27 +86,33 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
@Override
public String toString(String field) {
StringBuilder sb = new StringBuilder();
sb.append("ChildrenQuery[").append(childType).append("/").append(parentType).append("](").append(childQuery
sb.append("ChildrenQuery[").append(childType).append("/").append(parentType).append("](").append(originalChildQuery
.toString(field)).append(')').append(ToStringUtils.boost(getBoost()));
return sb.toString();
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query rewrittenChildQuery = childQuery.rewrite(reader);
if (rewrittenChildQuery == childQuery) {
Query rewritten;
if (rewrittenChildQuery == null) {
rewritten = originalChildQuery.rewrite(reader);
} else {
rewritten = rewrittenChildQuery;
}
if (rewritten == rewrittenChildQuery) {
return this;
}
// See TopChildrenQuery#rewrite
int index = searchContext.rewrites().indexOf(this);
ChildrenQuery rewrite = new ChildrenQuery(this, rewrittenChildQuery);
ChildrenQuery rewrite = new ChildrenQuery(this, rewritten);
searchContext.rewrites().set(index, rewrite);
return rewrite;
}
@Override
public void extractTerms(Set<Term> terms) {
childQuery.extractTerms(terms);
rewrittenChildQuery.extractTerms(terms);
}
@Override
@ -121,6 +129,12 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
default:
collector = new ChildUidCollector(scoreType, searchContext, parentType, uidToScore);
}
Query childQuery;
if (rewrittenChildQuery == null) {
childQuery = rewrittenChildQuery = searchContext.searcher().rewrite(originalChildQuery);
} else {
childQuery = rewrittenChildQuery;
}
searchContext.searcher().search(childQuery, collector);
}
@ -142,7 +156,7 @@ public class ChildrenQuery extends Query implements SearchContext.Rewrite {
throw new ElasticSearchIllegalStateException("has_child query hasn't executed properly");
}
return new ParentWeight(childQuery.createWeight(searcher));
return new ParentWeight(rewrittenChildQuery.createWeight(searcher));
}
class ParentWeight extends Weight {

View File

@ -46,16 +46,17 @@ import java.util.Set;
public class ParentQuery extends Query implements SearchContext.Rewrite {
private final SearchContext searchContext;
private final Query parentQuery;
private final Query originalParentQuery;
private final String parentType;
private final Filter childrenFilter;
private final List<String> childTypes;
private Query rewrittenParentQuery;
private TObjectFloatHashMap<HashedBytesArray> uidToScore;
public ParentQuery(SearchContext searchContext, Query parentQuery, String parentType, List<String> childTypes, Filter childrenFilter) {
this.searchContext = searchContext;
this.parentQuery = parentQuery;
this.originalParentQuery = parentQuery;
this.parentType = parentType;
this.childTypes = childTypes;
this.childrenFilter = childrenFilter;
@ -63,11 +64,12 @@ public class ParentQuery extends Query implements SearchContext.Rewrite {
private ParentQuery(ParentQuery unwritten, Query rewrittenParentQuery) {
this.searchContext = unwritten.searchContext;
this.parentQuery = rewrittenParentQuery;
this.originalParentQuery = unwritten.originalParentQuery;
this.parentType = unwritten.parentType;
this.childrenFilter = unwritten.childrenFilter;
this.childTypes = unwritten.childTypes;
this.rewrittenParentQuery = rewrittenParentQuery;
this.uidToScore = unwritten.uidToScore;
}
@ -76,6 +78,12 @@ public class ParentQuery extends Query implements SearchContext.Rewrite {
searchContext.idCache().refresh(searchContext.searcher().getTopReaderContext().leaves());
uidToScore = CacheRecycler.popObjectFloatMap();
ParentUidCollector collector = new ParentUidCollector(uidToScore, searchContext, parentType);
Query parentQuery;
if (rewrittenParentQuery == null) {
parentQuery = rewrittenParentQuery = searchContext.searcher().rewrite(originalParentQuery);
} else {
parentQuery = rewrittenParentQuery;
}
searchContext.searcher().search(parentQuery, collector);
}
@ -91,18 +99,25 @@ public class ParentQuery extends Query implements SearchContext.Rewrite {
public String toString(String field) {
StringBuilder sb = new StringBuilder();
sb.append("ParentQuery[").append(parentType).append("/").append(childTypes)
.append("](").append(parentQuery.toString(field)).append(')')
.append("](").append(originalParentQuery.toString(field)).append(')')
.append(ToStringUtils.boost(getBoost()));
return sb.toString();
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query rewrittenChildQuery = parentQuery.rewrite(reader);
if (rewrittenChildQuery == parentQuery) {
Query rewritten;
if (rewrittenParentQuery == null) {
rewritten = originalParentQuery.rewrite(reader);
} else {
rewritten = rewrittenParentQuery;
}
if (rewritten == rewrittenParentQuery) {
return this;
}
ParentQuery rewrite = new ParentQuery(this, rewrittenChildQuery);
// See TopChildrenQuery#rewrite
ParentQuery rewrite = new ParentQuery(this, rewritten);
int index = searchContext.rewrites().indexOf(this);
searchContext.rewrites().set(index, rewrite);
return rewrite;
@ -110,7 +125,7 @@ public class ParentQuery extends Query implements SearchContext.Rewrite {
@Override
public void extractTerms(Set<Term> terms) {
parentQuery.extractTerms(terms);
rewrittenParentQuery.extractTerms(terms);
}
@Override
@ -118,7 +133,7 @@ public class ParentQuery extends Query implements SearchContext.Rewrite {
if (uidToScore == null) {
throw new ElasticSearchIllegalStateException("has_parent query hasn't executed properly");
}
return new ChildWeight(parentQuery.createWeight(searcher));
return new ChildWeight(rewrittenParentQuery.createWeight(searcher));
}
static class ParentUidCollector extends NoopCollector {

View File

@ -54,19 +54,20 @@ import java.util.Set;
public class TopChildrenQuery extends Query implements SearchContext.Rewrite {
private final SearchContext searchContext;
private final Query childQuery;
private final String parentType;
private final String childType;
private final ScoreType scoreType;
private final int factor;
private final int incrementalFactor;
private final Query originalChildQuery;
private Query rewrittenChildQuery;
private ExtTHashMap<Object, ParentDoc[]> parentDocs;
// Note, the query is expected to already be filtered to only child type docs
public TopChildrenQuery(SearchContext searchContext, Query childQuery, String childType, String parentType, ScoreType scoreType, int factor, int incrementalFactor) {
this.searchContext = searchContext;
this.childQuery = childQuery;
this.originalChildQuery = childQuery;
this.childType = childType;
this.parentType = parentType;
this.scoreType = scoreType;
@ -76,13 +77,44 @@ public class TopChildrenQuery extends Query implements SearchContext.Rewrite {
private TopChildrenQuery(TopChildrenQuery existing, Query rewrittenChildQuery) {
this.searchContext = existing.searchContext;
this.childQuery = rewrittenChildQuery;
this.originalChildQuery = existing.originalChildQuery;
this.parentType = existing.parentType;
this.childType = existing.childType;
this.scoreType = existing.scoreType;
this.factor = existing.factor;
this.incrementalFactor = existing.incrementalFactor;
this.parentDocs = existing.parentDocs;
this.rewrittenChildQuery = rewrittenChildQuery;
}
// Rewrite logic:
// 1) query_then_fetch (default): First contextRewrite and then rewrite is executed
// 2) dfs_query_then_fetch:: First rewrite and then contextRewrite is executed. During query phase rewrite isn't
// executed any more because searchContext#queryRewritten() returns true.
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query rewritten;
if (rewrittenChildQuery == null) {
rewritten = originalChildQuery.rewrite(reader);
} else {
rewritten = rewrittenChildQuery;
}
if (rewritten == rewrittenChildQuery) {
return this;
}
// We need to update the rewritten query also in the SearchContext#rewrites b/c we can run into this situation:
// 1) During parsing we set SearchContext#rewrites with queries that implement Rewrite.
// 2) Then during the dfs phase, the main query (which included this query and its child query) gets rewritten
// and updated in SearchContext. So different TopChildrenQuery instances are in SearchContext#rewrites and in the main query.
// 3) Then during the query phase first the queries that impl. Rewrite are executed, which will update their own data
// parentDocs Map. Then when the main query is executed, 0 results are found, b/c the main query holds a different
// TopChildrenQuery instance then in SearchContext#rewrites
int index = searchContext.rewrites().indexOf(this);
TopChildrenQuery rewrite = new TopChildrenQuery(this, rewritten);
searchContext.rewrites().set(index, rewrite);
return rewrite;
}
@Override
@ -96,6 +128,13 @@ public class TopChildrenQuery extends Query implements SearchContext.Rewrite {
numChildDocs = 1;
}
numChildDocs *= factor;
Query childQuery;
if (rewrittenChildQuery == null) {
childQuery = rewrittenChildQuery = searchContext.searcher().rewrite(originalChildQuery);
} else {
childQuery = rewrittenChildQuery;
}
while (true) {
parentDocs.clear();
TopDocs topChildDocs = searchContext.searcher().search(childQuery, numChildDocs);
@ -199,21 +238,9 @@ public class TopChildrenQuery extends Query implements SearchContext.Rewrite {
public float sumScores = 0;
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
Query rewrittenChildQuery = childQuery.rewrite(reader);
if (rewrittenChildQuery == childQuery) {
return this;
}
int index = searchContext.rewrites().indexOf(this);
TopChildrenQuery rewrite = new TopChildrenQuery(this, rewrittenChildQuery);
searchContext.rewrites().set(index, rewrite);
return rewrite;
}
@Override
public void extractTerms(Set<Term> terms) {
childQuery.extractTerms(terms);
rewrittenChildQuery.extractTerms(terms);
}
@Override
@ -222,12 +249,12 @@ public class TopChildrenQuery extends Query implements SearchContext.Rewrite {
throw new ElasticSearchIllegalStateException("top_children query hasn't executed properly");
}
return new ParentWeight(searcher, childQuery.createWeight(searcher));
return new ParentWeight(searcher, rewrittenChildQuery.createWeight(searcher));
}
public String toString(String field) {
StringBuilder sb = new StringBuilder();
sb.append("score_child[").append(childType).append("/").append(parentType).append("](").append(childQuery.toString(field)).append(')');
sb.append("score_child[").append(childType).append("/").append(parentType).append("](").append(originalChildQuery.toString(field)).append(')');
sb.append(ToStringUtils.boost(getBoost()));
return sb.toString();
}

View File

@ -28,6 +28,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.facet.terms.TermsFacet;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@ -1185,4 +1186,80 @@ public class SimpleChildQuerySearchTests extends AbstractNodesTests {
assertThat(searchResponse.hits().hits()[0].id(), equalTo("2"));
}
@Test
public void testSimpleQueryRewrite() throws Exception {
client.admin().indices().prepareDelete().execute().actionGet();
client.admin().indices().prepareCreate("test").setSettings(
ImmutableSettings.settingsBuilder()
.put("index.number_of_shards", 2)
.put("index.number_of_replicas", 0)
).execute().actionGet();
client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
client.admin().indices().preparePutMapping("test").setType("child").setSource(jsonBuilder().startObject().startObject("type")
.startObject("_parent").field("type", "parent").endObject()
.endObject().endObject()).execute().actionGet();
// index simple data
int childId = 0;
for (int i = 0; i < 10; i++) {
String parentId = String.format("p%03d", i);
client.prepareIndex("test", "parent", parentId)
.setSource("p_field", parentId)
.execute().actionGet();
int j = childId;
for (; j < childId + 50; j++) {
String childUid = String.format("c%03d", j);
client.prepareIndex("test", "child", childUid)
.setSource("c_field", childUid)
.setParent(parentId)
.execute().actionGet();
}
childId = j;
}
client.admin().indices().prepareRefresh().execute().actionGet();
SearchType[] searchTypes = new SearchType[]{SearchType.QUERY_THEN_FETCH, SearchType.DFS_QUERY_THEN_FETCH};
for (SearchType searchType : searchTypes) {
SearchResponse searchResponse = client.prepareSearch("test").setSearchType(searchType)
.setQuery(hasChildQuery("child", prefixQuery("c_field", "c")).scoreType("max"))
.addSort("p_field", SortOrder.ASC)
.setSize(5)
.execute().actionGet();
assertThat("Failures " + Arrays.toString(searchResponse.shardFailures()), searchResponse.shardFailures().length, equalTo(0));
assertThat(searchResponse.hits().totalHits(), equalTo(10L));
assertThat(searchResponse.hits().hits()[0].id(), equalTo("p000"));
assertThat(searchResponse.hits().hits()[1].id(), equalTo("p001"));
assertThat(searchResponse.hits().hits()[2].id(), equalTo("p002"));
assertThat(searchResponse.hits().hits()[3].id(), equalTo("p003"));
assertThat(searchResponse.hits().hits()[4].id(), equalTo("p004"));
searchResponse = client.prepareSearch("test").setSearchType(searchType)
.setQuery(hasParentQuery("parent", prefixQuery("p_field", "p")).scoreType("score"))
.addSort("c_field", SortOrder.ASC)
.setSize(5)
.execute().actionGet();
assertThat("Failures " + Arrays.toString(searchResponse.shardFailures()), searchResponse.shardFailures().length, equalTo(0));
assertThat(searchResponse.hits().totalHits(), equalTo(500L));
assertThat(searchResponse.hits().hits()[0].id(), equalTo("c000"));
assertThat(searchResponse.hits().hits()[1].id(), equalTo("c001"));
assertThat(searchResponse.hits().hits()[2].id(), equalTo("c002"));
assertThat(searchResponse.hits().hits()[3].id(), equalTo("c003"));
assertThat(searchResponse.hits().hits()[4].id(), equalTo("c004"));
searchResponse = client.prepareSearch("test").setSearchType(searchType)
.setQuery(topChildrenQuery("child", prefixQuery("c_field", "c")))
.addSort("p_field", SortOrder.ASC)
.setSize(5)
.execute().actionGet();
assertThat("Failures " + Arrays.toString(searchResponse.shardFailures()), searchResponse.shardFailures().length, equalTo(0));
assertThat(searchResponse.hits().totalHits(), equalTo(10L));
assertThat(searchResponse.hits().hits()[0].id(), equalTo("p000"));
assertThat(searchResponse.hits().hits()[1].id(), equalTo("p001"));
assertThat(searchResponse.hits().hits()[2].id(), equalTo("p002"));
assertThat(searchResponse.hits().hits()[3].id(), equalTo("p003"));
assertThat(searchResponse.hits().hits()[4].id(), equalTo("p004"));
}
}
}