Push limit term into secondary index queries

Change-Id: Id4ec5fe0375229ff8b82ec46fbe300c78098dbbe
This commit is contained in:
Dave Borowitz 2013-08-08 15:46:10 -07:00
parent 30b280d309
commit 76e7b42490
8 changed files with 71 additions and 32 deletions

View File

@ -231,7 +231,7 @@ public class LuceneChangeIndex implements ChangeIndex {
} }
@Override @Override
public ChangeDataSource getSource(Predicate<ChangeData> p) public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
throws QueryParseException { throws QueryParseException {
Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p); Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
List<SubIndex> indexes = Lists.newArrayListWithCapacity(2); List<SubIndex> indexes = Lists.newArrayListWithCapacity(2);
@ -241,7 +241,7 @@ public class LuceneChangeIndex implements ChangeIndex {
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) { if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
indexes.add(closedIndex); indexes.add(closedIndex);
} }
return new QuerySource(indexes, QueryBuilder.toQuery(p)); return new QuerySource(indexes, QueryBuilder.toQuery(p), limit);
} }
@Override @Override
@ -256,16 +256,16 @@ public class LuceneChangeIndex implements ChangeIndex {
} }
private static class QuerySource implements ChangeDataSource { private static class QuerySource implements ChangeDataSource {
// TODO(dborowitz): Push limit down from predicate tree.
private static final int LIMIT = 1000;
private static final ImmutableSet<String> FIELDS = ImmutableSet.of(ID_FIELD); private static final ImmutableSet<String> FIELDS = ImmutableSet.of(ID_FIELD);
private final List<SubIndex> indexes; private final List<SubIndex> indexes;
private final Query query; private final Query query;
private final int limit;
public QuerySource(List<SubIndex> indexes, Query query) { public QuerySource(List<SubIndex> indexes, Query query, int limit) {
this.indexes = indexes; this.indexes = indexes;
this.query = query; this.query = query;
this.limit = limit;
} }
@Override @Override
@ -295,9 +295,9 @@ public class LuceneChangeIndex implements ChangeIndex {
TopDocs[] hits = new TopDocs[indexes.size()]; TopDocs[] hits = new TopDocs[indexes.size()];
for (int i = 0; i < indexes.size(); i++) { for (int i = 0; i < indexes.size(); i++) {
searchers[i] = indexes.get(i).acquire(); searchers[i] = indexes.get(i).acquire();
hits[i] = searchers[i].search(query, LIMIT, sort); hits[i] = searchers[i].search(query, limit, sort);
} }
TopDocs docs = TopDocs.merge(sort, LIMIT, hits); TopDocs docs = TopDocs.merge(sort, limit, hits);
List<ChangeData> result = List<ChangeData> result =
Lists.newArrayListWithCapacity(docs.scoreDocs.length); Lists.newArrayListWithCapacity(docs.scoreDocs.length);

View File

@ -62,7 +62,7 @@ public interface ChangeIndex {
} }
@Override @Override
public ChangeDataSource getSource(Predicate<ChangeData> p) { public ChangeDataSource getSource(Predicate<ChangeData> p, int limit) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -133,12 +133,13 @@ public interface ChangeIndex {
* @param p the predicate to match. Must be a tree containing only AND, OR, * @param p the predicate to match. Must be a tree containing only AND, OR,
* or NOT predicates as internal nodes, and {@link IndexPredicate}s as * or NOT predicates as internal nodes, and {@link IndexPredicate}s as
* leaves. * leaves.
* @param limit maximum number of results to return.
* @return a source of documents matching the predicate. * @return a source of documents matching the predicate.
* *
* @throws QueryParseException if the predicate could not be converted to an * @throws QueryParseException if the predicate could not be converted to an
* indexed data source. * indexed data source.
*/ */
public ChangeDataSource getSource(Predicate<ChangeData> p) public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
throws QueryParseException; throws QueryParseException;
/** /**

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.index; package com.google.gerrit.server.index;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
@ -28,6 +29,7 @@ import com.google.gerrit.server.query.QueryRewriter;
import com.google.gerrit.server.query.change.AndSource; import com.google.gerrit.server.query.change.AndSource;
import com.google.gerrit.server.query.change.BasicChangeRewrites; import com.google.gerrit.server.query.change.BasicChangeRewrites;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryRewriter; import com.google.gerrit.server.query.change.ChangeQueryRewriter;
import com.google.gerrit.server.query.change.ChangeStatusPredicate; import com.google.gerrit.server.query.change.ChangeStatusPredicate;
import com.google.gerrit.server.query.change.OrSource; import com.google.gerrit.server.query.change.OrSource;
@ -62,6 +64,9 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
CLOSED_STATUSES = Sets.immutableEnumSet(closed); CLOSED_STATUSES = Sets.immutableEnumSet(closed);
} }
@VisibleForTesting
static final int MAX_LIMIT = 1000;
/** /**
* Get the set of statuses that changes matching the given predicate may have. * Get the set of statuses that changes matching the given predicate may have.
* *
@ -134,11 +139,15 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
@Override @Override
public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) { public Predicate<ChangeData> rewrite(Predicate<ChangeData> in) {
in = basicRewrites.rewrite(in); in = basicRewrites.rewrite(in);
// Add 1 to specified limit to match behavior of QueryProcessor.
int limit = ChangeQueryBuilder.hasLimit(in)
? ChangeQueryBuilder.getLimit(in) + 1
: MAX_LIMIT;
ChangeIndex index = indexes.getSearchIndex(); ChangeIndex index = indexes.getSearchIndex();
Predicate<ChangeData> out = rewriteImpl(in, index); Predicate<ChangeData> out = rewriteImpl(in, index, limit);
if (in == out || out instanceof IndexPredicate) { if (in == out || out instanceof IndexPredicate) {
return query(out, index); return query(out, index, limit);
} else if (out == null /* cannot rewrite */) { } else if (out == null /* cannot rewrite */) {
return in; return in;
} else { } else {
@ -151,6 +160,7 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
* *
* @param in predicate to rewrite. * @param in predicate to rewrite.
* @param index index whose schema determines which fields are indexed. * @param index index whose schema determines which fields are indexed.
* @param limit maximum number of results to return.
* @return {@code null} if no part of this subtree can be queried in the * @return {@code null} if no part of this subtree can be queried in the
* index directly. {@code in} if this subtree and all its children can be * index directly. {@code in} if this subtree and all its children can be
* queried directly in the index. Otherwise, a predicate that is * queried directly in the index. Otherwise, a predicate that is
@ -158,7 +168,7 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
* index directly. * index directly.
*/ */
private Predicate<ChangeData> rewriteImpl(Predicate<ChangeData> in, private Predicate<ChangeData> rewriteImpl(Predicate<ChangeData> in,
ChangeIndex index) { ChangeIndex index, int limit) {
if (isIndexPredicate(in, index)) { if (isIndexPredicate(in, index)) {
return in; return in;
} else if (!isRewritePossible(in)) { } else if (!isRewritePossible(in)) {
@ -172,7 +182,7 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
List<Predicate<ChangeData>> newChildren = Lists.newArrayListWithCapacity(n); List<Predicate<ChangeData>> newChildren = Lists.newArrayListWithCapacity(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
Predicate<ChangeData> c = in.getChild(i); Predicate<ChangeData> c = in.getChild(i);
Predicate<ChangeData> nc = rewriteImpl(c, index); Predicate<ChangeData> nc = rewriteImpl(c, index, limit);
if (nc == c) { if (nc == c) {
isIndexed.set(i); isIndexed.set(i);
newChildren.add(c); newChildren.add(c);
@ -192,7 +202,7 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
} else if (rewritten.cardinality() == n) { } else if (rewritten.cardinality() == n) {
return in.copy(newChildren); // All children were rewritten. return in.copy(newChildren); // All children were rewritten.
} }
return partitionChildren(in, newChildren, isIndexed, index); return partitionChildren(in, newChildren, isIndexed, index, limit);
} }
private boolean isIndexPredicate(Predicate<ChangeData> in, ChangeIndex index) { private boolean isIndexPredicate(Predicate<ChangeData> in, ChangeIndex index) {
@ -207,10 +217,11 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
Predicate<ChangeData> in, Predicate<ChangeData> in,
List<Predicate<ChangeData>> newChildren, List<Predicate<ChangeData>> newChildren,
BitSet isIndexed, BitSet isIndexed,
ChangeIndex index) { ChangeIndex index,
int limit) {
if (isIndexed.cardinality() == 1) { if (isIndexed.cardinality() == 1) {
int i = isIndexed.nextSetBit(0); int i = isIndexed.nextSetBit(0);
newChildren.add(0, query(newChildren.remove(i), index)); newChildren.add(0, query(newChildren.remove(i), index, limit));
return copy(in, newChildren); return copy(in, newChildren);
} }
@ -230,7 +241,7 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
all.add(c); all.add(c);
} }
} }
all.add(0, query(in.copy(indexed), index)); all.add(0, query(in.copy(indexed), index, limit));
return copy(in, all); return copy(in, all);
} }
@ -245,9 +256,10 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
return in.copy(all); return in.copy(all);
} }
private IndexedChangeQuery query(Predicate<ChangeData> p, ChangeIndex index) { private IndexedChangeQuery query(Predicate<ChangeData> p, ChangeIndex index,
int limit) {
try { try {
return new IndexedChangeQuery(index, p); return new IndexedChangeQuery(index, p, limit);
} catch (QueryParseException e) { } catch (QueryParseException e) {
throw new IllegalStateException( throw new IllegalStateException(
"Failed to convert " + p + " to index predicate", e); "Failed to convert " + p + " to index predicate", e);

View File

@ -15,6 +15,8 @@
package com.google.gerrit.server.index; package com.google.gerrit.server.index;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.Predicate;
@ -39,12 +41,14 @@ import java.util.List;
public class IndexedChangeQuery extends Predicate<ChangeData> public class IndexedChangeQuery extends Predicate<ChangeData>
implements ChangeDataSource { implements ChangeDataSource {
private final Predicate<ChangeData> pred; private final Predicate<ChangeData> pred;
private final int limit;
private final ChangeDataSource source; private final ChangeDataSource source;
public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred) public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred, int limit)
throws QueryParseException { throws QueryParseException {
this.pred = pred; this.pred = pred;
this.source = index.getSource(pred); this.limit = limit;
this.source = index.getSource(pred, limit);
} }
@Override @Override
@ -132,13 +136,19 @@ public class IndexedChangeQuery extends Predicate<ChangeData>
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return other != null if (other == null || getClass() != other.getClass()) {
&& getClass() == other.getClass() return false;
&& pred.equals(((IndexedChangeQuery) other).pred); }
IndexedChangeQuery o = (IndexedChangeQuery) other;
return pred.equals(o.pred)
&& limit == o.limit;
} }
@Override @Override
public String toString() { public String toString() {
return "index(" + source + ")"; return Objects.toStringHelper("index")
.add("p", source)
.add("limit", limit)
.toString();
} }
} }

View File

@ -38,7 +38,7 @@ class CommentPredicate extends IndexPredicate<ChangeData> {
public boolean match(ChangeData object) throws OrmException { public boolean match(ChangeData object) throws OrmException {
try { try {
for (ChangeData cData : index.getSource( for (ChangeData cData : index.getSource(
Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this)) Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this), 1)
.read()) { .read()) {
if (cData.getId().equals(object.getId())) { if (cData.getId().equals(object.getId())) {
return true; return true;

View File

@ -42,7 +42,7 @@ class MessagePredicate extends IndexPredicate<ChangeData> {
public boolean match(ChangeData object) throws OrmException { public boolean match(ChangeData object) throws OrmException {
try { try {
for (ChangeData cData : index.getSource( for (ChangeData cData : index.getSource(
Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this)) Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this), 1)
.read()) { .read()) {
if (cData.getId().equals(object.getId())) { if (cData.getId().equals(object.getId())) {
return true; return true;

View File

@ -79,7 +79,7 @@ public class IndexRewriteTest extends TestCase {
} }
@Override @Override
public ChangeDataSource getSource(Predicate<ChangeData> p) public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
throws QueryParseException { throws QueryParseException {
return new Source(p); return new Source(p);
} }
@ -269,6 +269,16 @@ public class IndexRewriteTest extends TestCase {
out.getChildren()); out.getChildren());
} }
public void testLimit() throws Exception {
Predicate<ChangeData> in = parse("file:a limit:3");
Predicate<ChangeData> out = rewrite(in);
assertSame(AndSource.class, out.getClass());
assertEquals(ImmutableList.of(
query(in.getChild(0), 4),
in.getChild(1)),
out.getChildren());
}
public void testGetPossibleStatus() throws Exception { public void testGetPossibleStatus() throws Exception {
assertEquals(EnumSet.allOf(Change.Status.class), status("file:a")); assertEquals(EnumSet.allOf(Change.Status.class), status("file:a"));
assertEquals(EnumSet.of(NEW), status("is:new")); assertEquals(EnumSet.of(NEW), status("is:new"));
@ -308,7 +318,12 @@ public class IndexRewriteTest extends TestCase {
private IndexedChangeQuery query(Predicate<ChangeData> p) private IndexedChangeQuery query(Predicate<ChangeData> p)
throws QueryParseException { throws QueryParseException {
return new IndexedChangeQuery(index, p); return query(p, IndexRewriteImpl.MAX_LIMIT);
}
private IndexedChangeQuery query(Predicate<ChangeData> p, int limit)
throws QueryParseException {
return new IndexedChangeQuery(index, p, limit);
} }
private Set<Change.Status> status(String query) throws QueryParseException { private Set<Change.Status> status(String query) throws QueryParseException {

View File

@ -194,7 +194,7 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
} }
@Override @Override
public ChangeDataSource getSource(Predicate<ChangeData> p) public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
throws QueryParseException { throws QueryParseException {
Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p); Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
List<SolrServer> indexes = Lists.newArrayListWithCapacity(2); List<SolrServer> indexes = Lists.newArrayListWithCapacity(2);
@ -204,7 +204,7 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) { if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
indexes.add(closedIndex); indexes.add(closedIndex);
} }
return new QuerySource(indexes, QueryBuilder.toQuery(p)); return new QuerySource(indexes, QueryBuilder.toQuery(p), limit);
} }
private void commit(SolrServer server) throws IOException { private void commit(SolrServer server) throws IOException {
@ -219,11 +219,12 @@ class SolrChangeIndex implements ChangeIndex, LifecycleListener {
private final List<SolrServer> indexes; private final List<SolrServer> indexes;
private final SolrQuery query; private final SolrQuery query;
public QuerySource(List<SolrServer> indexes, Query q) { public QuerySource(List<SolrServer> indexes, Query q, int limit) {
this.indexes = indexes; this.indexes = indexes;
query = new SolrQuery(q.toString()); query = new SolrQuery(q.toString());
query.setParam("shards.tolerant", true); query.setParam("shards.tolerant", true);
query.setParam("rows", Integer.toString(limit));
query.setFields(ID_FIELD); query.setFields(ID_FIELD);
query.setSort( query.setSort(
ChangeField.UPDATED.getName(), ChangeField.UPDATED.getName(),