Push open/closed subindex into Lucene implementation

Storing open/closed changes in separate indexes is an optimization
specific to the Lucene implementation. Other implementations may want
to do similar splitting, but the unit of splitting is not necessarily
a fully self-contained index.

Leave getPossibleStatus in IndexRewriteImpl, since it may be useful
for other implementations' internal optimizations as well.

Change-Id: Ib2b69f2eb0b1b9246390b9c6c8bc054a9e60836e
This commit is contained in:
Dave Borowitz 2013-06-07 12:02:28 -07:00
parent f2055007bc
commit 0066d9aa02
12 changed files with 287 additions and 317 deletions

View File

@ -36,8 +36,8 @@ import java.util.Map;
public class IndexVersionCheck implements LifecycleListener {
public static final Map<String, Integer> SCHEMA_VERSIONS = ImmutableMap.of(
"changes_open", ChangeField.SCHEMA_VERSION,
"changes_closed", ChangeField.SCHEMA_VERSION);
LuceneChangeIndex.CHANGES_OPEN, ChangeField.SCHEMA_VERSION,
LuceneChangeIndex.CHANGES_CLOSED, ChangeField.SCHEMA_VERSION);
public static File gerritIndexConfig(SitePaths sitePaths) {
return new File(sitePaths.index_dir, "gerrit_index.config");

View File

@ -15,13 +15,18 @@
package com.google.gerrit.lucene;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_CHANGE;
import static com.google.gerrit.server.query.change.IndexRewriteImpl.CLOSED_STATUSES;
import static com.google.gerrit.server.query.change.IndexRewriteImpl.OPEN_STATUSES;
import static org.apache.lucene.search.BooleanClause.Occur.MUST;
import static org.apache.lucene.search.BooleanClause.Occur.MUST_NOT;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.FieldDef;
@ -35,38 +40,33 @@ import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gerrit.server.query.change.IndexRewriteImpl;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Secondary index implementation using Apache Lucene.
@ -76,81 +76,86 @@ import java.util.List;
* though there may be some lag between a committed write and it showing up to
* other threads' searchers.
*/
public class LuceneChangeIndex implements ChangeIndex {
private static final Logger log =
LoggerFactory.getLogger(LuceneChangeIndex.class);
@Singleton
public class LuceneChangeIndex implements ChangeIndex, LifecycleListener {
public static final Version LUCENE_VERSION = Version.LUCENE_43;
public static final String CHANGES_OPEN = "changes_open";
public static final String CHANGES_CLOSED = "changes_closed";
private final FillArgs fillArgs;
private final Directory dir;
private final IndexWriter writer;
private final SearcherManager searcherManager;
private final SubIndex openIndex;
private final SubIndex closedIndex;
LuceneChangeIndex(File file, FillArgs fillArgs) throws IOException {
@Inject
LuceneChangeIndex(SitePaths sitePaths, FillArgs fillArgs) throws IOException {
this.fillArgs = fillArgs;
dir = FSDirectory.open(file);
IndexWriterConfig writerConfig =
new IndexWriterConfig(LUCENE_VERSION, new StandardAnalyzer(LUCENE_VERSION));
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = new IndexWriter(dir, writerConfig);
searcherManager = new SearcherManager(writer, true, null);
openIndex = new SubIndex(new File(sitePaths.index_dir, CHANGES_OPEN));
closedIndex = new SubIndex(new File(sitePaths.index_dir, CHANGES_CLOSED));
}
void close() {
try {
searcherManager.close();
} catch (IOException e) {
log.warn("error closing Lucene searcher", e);
}
try {
writer.close(true);
} catch (IOException e) {
log.warn("error closing Lucene writer", e);
}
try {
dir.close();
} catch (IOException e) {
log.warn("error closing Lucene directory", e);
}
@Override
public void start() {
// Do nothing.
}
@Override
public void stop() {
openIndex.close();
closedIndex.close();
}
@Override
public void insert(ChangeData cd) throws IOException {
writer.addDocument(toDocument(cd));
commit();
Term id = idTerm(cd);
Document doc = toDocument(cd);
if (cd.getChange().getStatus().isOpen()) {
closedIndex.delete(id);
openIndex.insert(doc);
} else {
openIndex.delete(id);
closedIndex.insert(doc);
}
}
@Override
public void replace(ChangeData cd) throws IOException {
writer.updateDocument(intTerm(FIELD_CHANGE, cd.getId().get()),
toDocument(cd));
commit();
Term id = idTerm(cd);
Document doc = toDocument(cd);
if (cd.getChange().getStatus().isOpen()) {
closedIndex.delete(id);
openIndex.replace(id, doc);
} else {
openIndex.delete(id);
closedIndex.replace(id, doc);
}
}
@Override
public void delete(ChangeData cd) throws IOException {
writer.deleteDocuments(intTerm(FIELD_CHANGE, cd.getId().get()));
commit();
Term id = idTerm(cd);
if (cd.getChange().getStatus().isOpen()) {
openIndex.delete(id);
} else {
closedIndex.delete(id);
}
}
@Override
public ChangeDataSource getSource(Predicate<ChangeData> p)
throws QueryParseException {
return new QuerySource(toQuery(p));
Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
List<SubIndex> indexes = Lists.newArrayListWithCapacity(2);
if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
indexes.add(openIndex);
}
if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
indexes.add(closedIndex);
}
return new QuerySource(indexes, toQuery(p));
}
public Directory getDirectory() {
return dir;
}
public IndexWriter getWriter() {
return writer;
}
private void commit() throws IOException {
writer.commit();
searcherManager.maybeRefresh();
private Term idTerm(ChangeData cd) {
return intTerm(FIELD_CHANGE, cd.getId().get());
}
private Query toQuery(Predicate<ChangeData> p) throws QueryParseException {
@ -214,9 +219,11 @@ public class LuceneChangeIndex implements ChangeIndex {
// TODO(dborowitz): Push limit down from predicate tree.
private static final int LIMIT = 1000;
private final List<SubIndex> indexes;
private final Query query;
public QuerySource(Query query) {
public QuerySource(List<SubIndex> indexes, Query query) {
this.indexes = indexes;
this.query = query;
}
@ -233,36 +240,28 @@ public class LuceneChangeIndex implements ChangeIndex {
@Override
public ResultSet<ChangeData> read() throws OrmException {
try {
IndexSearcher searcher = searcherManager.acquire();
try {
ScoreDoc[] docs = searcher.search(query, LIMIT).scoreDocs;
List<ChangeData> result = Lists.newArrayListWithCapacity(docs.length);
for (ScoreDoc sd : docs) {
Document doc = searcher.doc(sd.doc);
Number v = doc.getField(FIELD_CHANGE).numericValue();
result.add(new ChangeData(new Change.Id(v.intValue())));
}
final List<ChangeData> r = Collections.unmodifiableList(result);
return new ResultSet<ChangeData>() {
@Override
public Iterator<ChangeData> iterator() {
return r.iterator();
}
@Override
public List<ChangeData> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} finally {
searcherManager.release(searcher);
List<ChangeData> result =
Lists.newArrayListWithExpectedSize(2 * getCardinality());
for (SubIndex index : indexes) {
result.addAll(index.search(query, LIMIT));
}
final List<ChangeData> r = Collections.unmodifiableList(result);
return new ResultSet<ChangeData>() {
@Override
public Iterator<ChangeData> iterator() {
return r.iterator();
}
@Override
public List<ChangeData> toList() {
return r;
}
@Override
public void close() {
// Do nothing.
}
};
} catch (IOException e) {
throw new OrmException(e);
}

View File

@ -1,70 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package com.google.gerrit.server.git;
package com.google.gerrit.lucene;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.ChangeIndex;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@Singleton
class LuceneChangeIndexManager implements ChangeIndex.Manager,
LifecycleListener {
private final LoadingCache<String, LuceneChangeIndex> indexes;
@Inject
LuceneChangeIndexManager(final SitePaths sitePaths, final FillArgs fillArgs) {
indexes = CacheBuilder.newBuilder().build(
new CacheLoader<String, LuceneChangeIndex>() {
@Override
public LuceneChangeIndex load(String key) throws IOException {
return new LuceneChangeIndex(
new File(sitePaths.index_dir, key), fillArgs);
}
});
}
@Override
public void start() {
// Do nothing.
}
@Override
public void stop() {
for (LuceneChangeIndex index : indexes.asMap().values()) {
index.close();
}
}
@Override
public LuceneChangeIndex get(String name) throws IOException {
try {
return indexes.get(name);
} catch (ExecutionException e) {
Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
throw new IOException(e);
}
}
}

View File

@ -34,8 +34,8 @@ public class LuceneIndexModule extends LifecycleModule {
@Override
protected void configure() {
install(new IndexModule(threads));
bind(ChangeIndex.Manager.class).to(LuceneChangeIndexManager.class);
listener().to(LuceneChangeIndexManager.class);
bind(ChangeIndex.class).to(LuceneChangeIndex.class);
listener().to(LuceneChangeIndex.class);
if (checkVersion) {
listener().to(IndexVersionCheck.class);
}

View File

@ -0,0 +1,115 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package com.google.gerrit.server.git;
package com.google.gerrit.lucene;
import static com.google.gerrit.lucene.LuceneChangeIndex.LUCENE_VERSION;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_CHANGE;
import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/** Piece of the change index that is implemented as a separate Lucene index. */
class SubIndex {
private static final Logger log =
LoggerFactory.getLogger(LuceneChangeIndex.class);
private final Directory dir;
private final IndexWriter writer;
private final SearcherManager searcherManager;
SubIndex(File file) throws IOException {
dir = FSDirectory.open(file);
IndexWriterConfig writerConfig =
new IndexWriterConfig(LUCENE_VERSION, new StandardAnalyzer(LUCENE_VERSION));
writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = new IndexWriter(dir, writerConfig);
searcherManager = new SearcherManager(writer, true, null);
}
void close() {
try {
searcherManager.close();
} catch (IOException e) {
log.warn("error closing Lucene searcher", e);
}
try {
writer.close(true);
} catch (IOException e) {
log.warn("error closing Lucene writer", e);
}
try {
dir.close();
} catch (IOException e) {
log.warn("error closing Lucene directory", e);
}
}
void insert(Document doc) throws IOException {
writer.addDocument(doc);
commit();
}
void replace(Term term, Document doc) throws IOException {
writer.updateDocument(term, doc);
commit();
}
void delete(Term term) throws IOException {
writer.deleteDocuments(term);
commit();
}
List<ChangeData> search(Query query, int limit) throws IOException {
IndexSearcher searcher = searcherManager.acquire();
try {
ScoreDoc[] docs = searcher.search(query, limit).scoreDocs;
List<ChangeData> result = Lists.newArrayListWithCapacity(docs.length);
for (ScoreDoc sd : docs) {
Document doc = searcher.doc(sd.doc);
Number v = doc.getField(FIELD_CHANGE).numericValue();
result.add(new ChangeData(new Change.Id(v.intValue())));
}
return Collections.unmodifiableList(result);
} finally {
searcherManager.release(searcher);
}
}
private void commit() throws IOException {
writer.commit();
searcherManager.maybeRefresh();
}
}

View File

@ -32,38 +32,29 @@ import java.io.IOException;
* appropriate.
*/
public interface ChangeIndex {
public static interface Manager {
/** Instance indicating secondary index is disabled. */
public static final Manager DISABLED = new Manager() {
@Override
public ChangeIndex get(String name) throws IOException {
return new ChangeIndex() {
@Override
public void insert(ChangeData cd) throws IOException {
// Do nothing.
}
/** Instance indicating secondary index is disabled. */
public static final ChangeIndex DISABLED = new ChangeIndex() {
@Override
public void insert(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public void replace(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public void replace(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public void delete(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public void delete(ChangeData cd) throws IOException {
// Do nothing.
}
@Override
public ChangeDataSource getSource(Predicate<ChangeData> p)
throws QueryParseException {
throw new UnsupportedOperationException();
}
};
}
};
ChangeIndex get(String name) throws IOException;
}
@Override
public ChangeDataSource getSource(Predicate<ChangeData> p)
throws QueryParseException {
throw new UnsupportedOperationException();
}
};
/**
* Insert a change document into the index.

View File

@ -37,15 +37,13 @@ public class ChangeIndexerImpl implements ChangeIndexer {
LoggerFactory.getLogger(ChangeIndexerImpl.class);
private final ListeningScheduledExecutorService executor;
private final ChangeIndex openIndex;
private final ChangeIndex closedIndex;
private final ChangeIndex index;
@Inject
ChangeIndexerImpl(@IndexExecutor ListeningScheduledExecutorService executor,
ChangeIndex.Manager indexManager) throws IOException {
ChangeIndex index) throws IOException {
this.executor = executor;
this.openIndex = indexManager.get("changes_open");
this.closedIndex = indexManager.get("changes_closed");
this.index = index;
}
@Override
@ -74,13 +72,7 @@ public class ChangeIndexerImpl implements ChangeIndexer {
public void run() {
ChangeData cd = new ChangeData(change);
try {
if (change.getStatus().isOpen()) {
closedIndex.delete(cd);
openIndex.replace(cd);
} else {
openIndex.delete(cd);
closedIndex.replace(cd);
}
index.replace(cd);
} catch (IOException e) {
log.error("Error indexing change", e);
}

View File

@ -24,7 +24,7 @@ public class NoIndexModule extends AbstractModule {
@Override
protected void configure() {
bind(ChangeIndex.Manager.class).toInstance(ChangeIndex.Manager.DISABLED);
bind(ChangeIndex.class).toInstance(ChangeIndex.DISABLED);
bind(ChangeIndexer.class).toInstance(ChangeIndexer.DISABLED);
bind(IndexRewrite.class).toInstance(IndexRewrite.DISABLED);
}

View File

@ -14,9 +14,6 @@
package com.google.gerrit.server.index;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
@ -25,9 +22,6 @@ import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Wrapper combining an {@link IndexPredicate} together with a
@ -40,68 +34,27 @@ import java.util.List;
public class PredicateWrapper extends Predicate<ChangeData> implements
ChangeDataSource {
private final Predicate<ChangeData> pred;
private final List<ChangeDataSource> sources;
private final ChangeDataSource source;
public PredicateWrapper(Predicate<ChangeData> pred, ChangeIndex index)
public PredicateWrapper(ChangeIndex index, Predicate<ChangeData> pred)
throws QueryParseException {
this(pred, ImmutableList.of(index));
}
public PredicateWrapper(Predicate<ChangeData> pred,
Collection<ChangeIndex> indexes) throws QueryParseException {
this.pred = pred;
sources = Lists.newArrayListWithCapacity(indexes.size());
for (ChangeIndex index : indexes) {
sources.add(index.getSource(pred));
}
this.source = index.getSource(pred);
}
@Override
public int getCardinality() {
int n = 0;
for (ChangeDataSource source : sources) {
n += source.getCardinality();
}
return n;
return source.getCardinality();
}
@Override
public boolean hasChange() {
for (ChangeDataSource source : sources) {
if (!source.hasChange()) {
return false;
}
}
return true;
return source.hasChange();
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
final List<ResultSet<ChangeData>> results =
Lists.newArrayListWithCapacity(sources.size());
for (ChangeDataSource source : sources) {
results.add(source.read());
}
return new ResultSet<ChangeData>() {
@Override
public Iterator<ChangeData> iterator() {
// TODO(dborowitz): May return duplicates since moving a document
// between indexes is not atomic.
return Iterables.concat(results).iterator();
}
@Override
public List<ChangeData> toList() {
return Collections.unmodifiableList(Lists.newArrayList(iterator()));
}
@Override
public void close() {
for (ResultSet<ChangeData> rs : results) {
rs.close();
}
}
};
return source.read();
}
@Override

View File

@ -114,7 +114,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
final PatchListCache patchListCache;
final GitRepositoryManager repoManager;
final ProjectCache projectCache;
final ChangeIndex.Manager indexManager;
final ChangeIndex index;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
@ -128,7 +128,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
PatchListCache patchListCache,
GitRepositoryManager repoManager,
ProjectCache projectCache,
ChangeIndex.Manager indexManager) {
ChangeIndex index) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
@ -140,7 +140,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.patchListCache = patchListCache;
this.repoManager = repoManager;
this.projectCache = projectCache;
this.indexManager = indexManager;
this.index = index;
}
}
@ -298,7 +298,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
if (file.startsWith("^")) {
throw error("regular expression not permitted here: file:" + file);
}
if (args.indexManager == ChangeIndex.Manager.DISABLED) {
if (args.index == ChangeIndex.DISABLED) {
throw error("secondary index must be enabled for file:" + file);
}
return new EqualsFilePredicate(args.dbProvider, args.patchListCache, file);

View File

@ -14,7 +14,6 @@
package com.google.gerrit.server.query.change;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Change;
@ -28,7 +27,6 @@ import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.List;
@ -36,8 +34,11 @@ import java.util.Set;
/** Rewriter that pushes boolean logic into the secondary index. */
public class IndexRewriteImpl implements IndexRewrite {
private static final Set<Change.Status> OPEN_STATUSES;
private static final Set<Change.Status> CLOSED_STATUSES;
/** Set of all open change statuses. */
public static final Set<Change.Status> OPEN_STATUSES;
/** Set of all closed change statuses. */
public static final Set<Change.Status> CLOSED_STATUSES;
static {
EnumSet<Change.Status> open = EnumSet.noneOf(Change.Status.class);
@ -53,18 +54,44 @@ public class IndexRewriteImpl implements IndexRewrite {
CLOSED_STATUSES = Sets.immutableEnumSet(closed);
}
private final ChangeIndex openIndex;
private final ChangeIndex closedIndex;
@Inject
IndexRewriteImpl(ChangeIndex.Manager indexManager) throws IOException {
this(indexManager.get("changes_open"), indexManager.get("changes_closed"));
/**
* Get the set of statuses that changes matching the given predicate may have.
*
* @param in predicate
* @return the maximal set of statuses that any changes matching the input
* predicates may have, based on examining boolean and
* {@link ChangeStatusPredicate}s.
*/
public static EnumSet<Change.Status> getPossibleStatus(Predicate<ChangeData> in) {
if (in instanceof ChangeStatusPredicate) {
return EnumSet.of(((ChangeStatusPredicate) in).getStatus());
} else if (in.getClass() == NotPredicate.class) {
return EnumSet.complementOf(getPossibleStatus(in.getChild(0)));
} else if (in.getClass() == OrPredicate.class) {
EnumSet<Change.Status> s = EnumSet.noneOf(Change.Status.class);
for (int i = 0; i < in.getChildCount(); i++) {
s.addAll(getPossibleStatus(in.getChild(i)));
}
return s;
} else if (in.getClass() == AndPredicate.class) {
EnumSet<Change.Status> s = EnumSet.allOf(Change.Status.class);
for (int i = 0; i < in.getChildCount(); i++) {
s.retainAll(getPossibleStatus(in.getChild(i)));
}
return s;
} else if (in.getChildCount() == 0) {
return EnumSet.allOf(Change.Status.class);
} else {
throw new IllegalStateException(
"Invalid predicate type in change index query: " + in.getClass());
}
}
@VisibleForTesting
IndexRewriteImpl(ChangeIndex openIndex, ChangeIndex closedIndex) {
this.openIndex = openIndex;
this.closedIndex = closedIndex;
private final ChangeIndex index;
@Inject
IndexRewriteImpl(ChangeIndex index) {
this.index = index;
}
@Override
@ -181,43 +208,9 @@ public class IndexRewriteImpl implements IndexRewrite {
}
}
@VisibleForTesting
static EnumSet<Change.Status> getPossibleStatus(Predicate<ChangeData> in) {
if (in instanceof ChangeStatusPredicate) {
return EnumSet.of(((ChangeStatusPredicate) in).getStatus());
} else if (in.getClass() == NotPredicate.class) {
return EnumSet.complementOf(getPossibleStatus(in.getChild(0)));
} else if (in.getClass() == OrPredicate.class) {
EnumSet<Change.Status> s = EnumSet.noneOf(Change.Status.class);
for (int i = 0; i < in.getChildCount(); i++) {
s.addAll(getPossibleStatus(in.getChild(i)));
}
return s;
} else if (in.getClass() == AndPredicate.class) {
EnumSet<Change.Status> s = EnumSet.allOf(Change.Status.class);
for (int i = 0; i < in.getChildCount(); i++) {
s.retainAll(getPossibleStatus(in.getChild(i)));
}
return s;
} else if (in.getChildCount() == 0) {
return EnumSet.allOf(Change.Status.class);
} else {
throw new IllegalStateException(
"Invalid predicate type in change index query: " + in.getClass());
}
}
private PredicateWrapper wrap(Predicate<ChangeData> p) {
try {
Set<Change.Status> possibleStatus = getPossibleStatus(p);
List<ChangeIndex> indexes = Lists.newArrayListWithCapacity(2);
if (!Sets.intersection(possibleStatus, OPEN_STATUSES).isEmpty()) {
indexes.add(openIndex);
}
if (!Sets.intersection(possibleStatus, CLOSED_STATUSES).isEmpty()) {
indexes.add(closedIndex);
}
return new PredicateWrapper(p, indexes);
return new PredicateWrapper(index, p);
} catch (QueryParseException e) {
throw new IllegalStateException(
"Failed to convert " + p + " to index predicate", e);

View File

@ -79,22 +79,19 @@ public class IndexRewriteTest extends TestCase {
}
}
private DummyIndex openIndex;
private DummyIndex closedIndex;
private DummyIndex index;
private ChangeQueryBuilder queryBuilder;
private IndexRewrite rewrite;
@Override
public void setUp() throws Exception {
super.setUp();
openIndex = new DummyIndex();
closedIndex = new DummyIndex();
index = new DummyIndex();
queryBuilder = new ChangeQueryBuilder(
new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
null, null, null, null, null, null),
null);
rewrite = new IndexRewriteImpl(openIndex, closedIndex);
rewrite = new IndexRewriteImpl(index);
}
public void testIndexPredicate() throws Exception {
@ -208,7 +205,7 @@ public class IndexRewriteTest extends TestCase {
private PredicateWrapper wrap(Predicate<ChangeData> p)
throws QueryParseException {
return new PredicateWrapper(p, openIndex);
return new PredicateWrapper(index, p);
}
private Set<Change.Status> status(String query) throws QueryParseException {