blob: 870ca040bc3e38b9b21c00d8f9fd44ce12739d5a [file] [log] [blame]
// Copyright (C) 2010 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.query.change;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gerrit.server.index.IndexRewriter;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.OrmRuntimeException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.List;
public class QueryProcessor {
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> userProvider;
private final ChangeControl.GenericFactory changeControlFactory;
private final IndexRewriter rewriter;
private final IndexConfig indexConfig;
private int limitFromCaller;
private int start;
private boolean enforceVisibility = true;
@Inject
QueryProcessor(Provider<ReviewDb> db,
Provider<CurrentUser> userProvider,
ChangeControl.GenericFactory changeControlFactory,
IndexRewriter rewriter,
IndexConfig indexConfig) {
this.db = db;
this.userProvider = userProvider;
this.changeControlFactory = changeControlFactory;
this.rewriter = rewriter;
this.indexConfig = indexConfig;
}
public QueryProcessor enforceVisibility(boolean enforce) {
enforceVisibility = enforce;
return this;
}
public QueryProcessor setLimit(int n) {
limitFromCaller = n;
return this;
}
public QueryProcessor setStart(int n) {
start = n;
return this;
}
/**
* Query for changes that match a structured query.
*
* @see #queryChanges(List)
* @param query the query.
* @return results of the query.
*/
public QueryResult queryChanges(Predicate<ChangeData> query)
throws OrmException, QueryParseException {
return queryChanges(ImmutableList.of(query)).get(0);
}
/*
* Perform multiple queries over a list of query strings.
* <p>
* If a limit was specified using {@link #setLimit(int)} this method may
* return up to {@code limit + 1} results, allowing the caller to determine if
* there are more than {@code limit} matches and suggest to its own caller
* that the query could be retried with {@link #setStart(int)}.
*
* @param queries the queries.
* @return results of the queries, one list per input query.
*/
public List<QueryResult> queryChanges(List<Predicate<ChangeData>> queries)
throws OrmException, QueryParseException {
try {
return queryChanges(null, queries);
} catch (OrmRuntimeException e) {
throw new OrmException(e.getMessage(), e);
}
}
static {
// In addition to this assumption, this queryChanges assumes the basic
// rewrites do not touch visibleto predicates either.
checkState(
!IsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
"QueryProcessor assumes visibleto is not used by the index rewriter.");
}
private List<QueryResult> queryChanges(List<String> queryStrings,
List<Predicate<ChangeData>> queries)
throws OrmException, QueryParseException {
Predicate<ChangeData> visibleToMe = enforceVisibility
? new IsVisibleToPredicate(db, changeControlFactory, userProvider.get())
: null;
int cnt = queries.size();
// Parse and rewrite all queries.
List<Integer> limits = new ArrayList<>(cnt);
List<Predicate<ChangeData>> predicates = new ArrayList<>(cnt);
List<ChangeDataSource> sources = new ArrayList<>(cnt);
for (Predicate<ChangeData> q : queries) {
int limit = getEffectiveLimit(q);
limits.add(limit);
// Always bump limit by 1, even if this results in exceeding the permitted
// max for this user. The only way to see if there are more changes is to
// ask for one more result from the query.
if (limit == getBackendSupportedLimit()) {
limit--;
}
int page = (start / limit) + 1;
if (page > indexConfig.maxPages()) {
throw new QueryParseException(
"Cannot go beyond page " + indexConfig.maxPages() + "of results");
}
QueryOptions opts = QueryOptions.create(indexConfig, start, limit + 1);
Predicate<ChangeData> s = rewriter.rewrite(q, opts);
if (!(s instanceof ChangeDataSource)) {
q = Predicate.and(open(), q);
s = rewriter.rewrite(q, opts);
}
if (!(s instanceof ChangeDataSource)) {
throw new QueryParseException("invalid query: " + s);
}
if (enforceVisibility) {
s = new AndSource(ImmutableList.of(s, visibleToMe), start);
}
predicates.add(s);
sources.add((ChangeDataSource) s);
}
// Run each query asynchronously, if supported.
List<ResultSet<ChangeData>> matches = new ArrayList<>(cnt);
for (ChangeDataSource s : sources) {
matches.add(s.read());
}
List<QueryResult> out = new ArrayList<>(cnt);
for (int i = 0; i < cnt; i++) {
out.add(QueryResult.create(
queryStrings != null ? queryStrings.get(i) : null,
predicates.get(i),
limits.get(i),
matches.get(i).toList()));
}
return out;
}
boolean isDisabled() {
return getPermittedLimit() <= 0;
}
private int getPermittedLimit() {
if (enforceVisibility) {
return userProvider.get().getCapabilities()
.getRange(GlobalCapability.QUERY_LIMIT)
.getMax();
}
return Integer.MAX_VALUE;
}
private int getBackendSupportedLimit() {
return indexConfig.maxLimit();
}
private int getEffectiveLimit(Predicate<ChangeData> p) {
List<Integer> possibleLimits = new ArrayList<>(4);
possibleLimits.add(getBackendSupportedLimit());
possibleLimits.add(getPermittedLimit());
if (limitFromCaller > 0) {
possibleLimits.add(limitFromCaller);
}
Integer limitFromPredicate = LimitPredicate.getLimit(p);
if (limitFromPredicate != null) {
possibleLimits.add(limitFromPredicate);
}
return Ordering.natural().min(possibleLimits);
}
}