com.google.gerrit.server.index.change.IndexedChangeQuery Maven / Gradle / Ivy
// 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.change;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE_SPEC;
import static com.google.gerrit.server.index.change.ChangeField.NUMERIC_ID_STR_SPEC;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT_SPEC;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
import com.google.gerrit.index.query.DataSource;
import com.google.gerrit.index.query.IndexPredicate;
import com.google.gerrit.index.query.IndexedQuery;
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.ResultSet;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeDataSource;
import com.google.gerrit.server.query.change.ChangeIndexPostFilterPredicate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Wrapper combining an {@link IndexPredicate} together with a {@link ChangeDataSource} that returns
* matching results from the index.
*
* 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 IndexedQuery
implements ChangeDataSource, Matchable {
public static QueryOptions createOptions(
IndexConfig config, int start, int limit, Set fields) {
return createOptions(config, start, limit, config.pageSizeMultiplier(), limit, fields);
}
public static QueryOptions createOptions(
IndexConfig config,
int start,
int pageSize,
int pageSizeMultiplier,
int limit,
Set fields) {
return createOptions(
config,
start,
pageSize,
pageSizeMultiplier,
limit,
/* allowIncompleteResults= */ false,
fields);
}
public static QueryOptions createOptions(
IndexConfig config,
int start,
int pageSize,
int pageSizeMultiplier,
int limit,
boolean allowIncompleteResults,
Set fields) {
// Always include project and change id since both are needed to load the change from NoteDb.
if (!fields.contains(CHANGE_SPEC.getName())
&& !(fields.contains(PROJECT_SPEC.getName())
&& fields.contains(NUMERIC_ID_STR_SPEC.getName()))) {
fields = new HashSet<>(fields);
fields.add(PROJECT_SPEC.getName());
fields.add(NUMERIC_ID_STR_SPEC.getName());
}
return QueryOptions.create(
config, start, pageSize, pageSizeMultiplier, limit, allowIncompleteResults, fields);
}
@VisibleForTesting
static QueryOptions convertOptions(QueryOptions opts) {
opts = opts.convertForBackend();
return IndexedChangeQuery.createOptions(
opts.config(),
opts.start(),
opts.pageSize(),
opts.pageSizeMultiplier(),
opts.limit(),
opts.allowIncompleteResults(),
opts.fields());
}
private final Map> fromSource;
public IndexedChangeQuery(ChangeIndex index, Predicate pred, QueryOptions opts)
throws QueryParseException {
super(index, pred, convertOptions(opts));
this.fromSource = new HashMap<>();
}
@Override
public ResultSet read() {
final DataSource currSource = source;
final ResultSet rs = currSource.read();
return new ResultSet<>() {
@Override
public Iterator iterator() {
return Iterables.transform(
rs,
cd -> {
fromSource.put(cd, currSource);
return cd;
})
.iterator();
}
@Override
public ImmutableList toList() {
ImmutableList r = rs.toList();
for (ChangeData cd : r) {
fromSource.put(cd, currSource);
}
return r;
}
@Override
public void close() {
rs.close();
}
@Override
public Object searchAfter() {
return rs.searchAfter();
}
};
}
public boolean postIndexMatch(Predicate pred, ChangeData cd) {
if (pred instanceof ChangeIndexPostFilterPredicate) {
checkState(
pred.isMatchable(),
"match invoked, but child predicate %s doesn't implement %s",
pred,
Matchable.class.getName());
return pred.asMatchable().match(cd);
}
for (int i = 0; i < pred.getChildCount(); i++) {
if (!postIndexMatch(pred.getChild(i), cd)) {
return false;
}
}
return true;
}
@Override
public boolean match(ChangeData cd) {
if (index.getIndexFilter().isPresent()) {
// Evaluate the filter. If we pass the filter, then evaluate everything else.
if (!index.getIndexFilter().get().match(cd)) {
return false;
}
}
Predicate pred = getChild(0);
if (source != null && fromSource.get(cd) == source && postIndexMatch(pred, cd)) {
return true;
}
checkState(
pred.isMatchable(),
"match invoked, but child predicate %s doesn't implement %s",
pred,
Matchable.class.getName());
return pred.asMatchable().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 boolean hasChange() {
return index.getSchema().hasField(ChangeField.CHANGE_SPEC);
}
}