blob: 09d66e5445817142118962effd02e0c27267bb89 [file] [log] [blame]
// 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.index;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Objects;
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;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gerrit.server.query.change.Paginated;
import com.google.gerrit.server.query.change.SortKeyPredicate;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* Wrapper combining an {@link IndexPredicate} together with a
* {@link ChangeDataSource} that returns matching results from the index.
* <p>
* Appropriate to return as the rootmost predicate that can be processed using
* the secondary index; such predicates must also implement
* {@link ChangeDataSource} to be chosen by the query processor.
*/
public class IndexedChangeQuery extends Predicate<ChangeData>
implements ChangeDataSource, Paginated {
/**
* Replace all {@link SortKeyPredicate}s in a tree.
* <p>
* Strictly speaking this should replace only the {@link SortKeyPredicate} at
* the top-level AND node, but this implementation is simpler, and the
* behavior of having multiple sortkey operators is undefined anyway.
*
* @param p predicate to replace in.
* @param newValue new cut value to replace all sortkey operators with.
* @return a copy of {@code p} with all sortkey predicates replaced; or p
* itself.
*/
@VisibleForTesting
static Predicate<ChangeData> replaceSortKeyPredicates(
Predicate<ChangeData> p, String newValue) {
if (p instanceof SortKeyPredicate) {
return ((SortKeyPredicate) p).copy(newValue);
} else if (p.getChildCount() > 0) {
List<Predicate<ChangeData>> newChildren =
Lists.newArrayListWithCapacity(p.getChildCount());
boolean replaced = false;
for (Predicate<ChangeData> c : p.getChildren()) {
Predicate<ChangeData> nc = replaceSortKeyPredicates(c, newValue);
newChildren.add(nc);
if (nc != c) {
replaced = true;
}
}
return replaced ? p.copy(newChildren) : p;
} else {
return p;
}
}
private final ChangeIndex index;
private final int limit;
private Predicate<ChangeData> pred;
private ChangeDataSource source;
public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred,
int limit) throws QueryParseException {
this.index = index;
this.limit = limit;
this.pred = pred;
this.source = index.getSource(pred, 0, limit);
}
@Override
public int getChildCount() {
return 1;
}
@Override
public Predicate<ChangeData> getChild(int i) {
if (i == 0) {
return pred;
}
throw new ArrayIndexOutOfBoundsException(i);
}
@Override
public List<Predicate<ChangeData>> getChildren() {
return ImmutableList.of(pred);
}
@Override
public int limit() {
return limit;
}
@Override
public int getCardinality() {
return source != null ? source.getCardinality() : limit();
}
@Override
public boolean hasChange() {
return index.getSchema().getFields()
.containsKey(ChangeField.CHANGE.getName());
}
@Override
public ResultSet<ChangeData> read() throws OrmException {
final ChangeDataSource currSource = source;
final ResultSet<ChangeData> rs = currSource.read();
return new ResultSet<ChangeData>() {
@Override
public Iterator<ChangeData> iterator() {
return Iterables.transform(
rs,
new Function<ChangeData, ChangeData>() {
@Override
public
ChangeData apply(ChangeData input) {
input.cacheFromSource(currSource);
return input;
}
}).iterator();
}
@Override
public List<ChangeData> toList() {
List<ChangeData> r = rs.toList();
for (ChangeData cd : r) {
cd.cacheFromSource(currSource);
}
return r;
}
@Override
public void close() {
rs.close();
}
};
}
@Override
public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
pred = replaceSortKeyPredicates(pred, last.change().getSortKey());
try {
source = index.getSource(pred, 0, limit);
} catch (QueryParseException e) {
// Don't need to show this exception to the user; the only thing that
// changed about pred was its SortKeyPredicates, and any other QPEs
// that might happen should have already thrown from the constructor.
throw new OrmException(e);
}
return read();
}
@Override
public ResultSet<ChangeData> restart(int start) throws OrmException {
try {
source = index.getSource(pred, start, limit);
} catch (QueryParseException e) {
// Don't need to show this exception to the user; the only thing that
// changed about pred was its SortKeyPredicates, and any other QPEs
// that might happen should have already thrown from the constructor.
throw new OrmException(e);
}
return read();
}
@Override
public Predicate<ChangeData> copy(
Collection<? extends Predicate<ChangeData>> children) {
return this;
}
@Override
public boolean match(ChangeData cd) throws OrmException {
return (source != null && cd.isFromSource(source)) || pred.match(cd);
}
@Override
public int getCost() {
// Index queries are assumed to be cheaper than any other type of query, so
// so try to make sure they get picked. Note that pred's cost may be higher
// because it doesn't know whether it's being used in an index query or not.
return 1;
}
@Override
public int hashCode() {
return pred.hashCode();
}
@Override
public boolean equals(Object other) {
if (other == null || getClass() != other.getClass()) {
return false;
}
IndexedChangeQuery o = (IndexedChangeQuery) other;
return pred.equals(o.pred)
&& limit == o.limit;
}
@Override
public String toString() {
return Objects.toStringHelper("index")
.add("p", pred)
.add("limit", limit)
.toString();
}
}