diff options
Diffstat (limited to 'java/com/google/gerrit/server/query/change/InternalChangeQuery.java')
-rw-r--r-- | java/com/google/gerrit/server/query/change/InternalChangeQuery.java | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java new file mode 100644 index 0000000000..495d27c800 --- /dev/null +++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java @@ -0,0 +1,345 @@ +// Copyright (C) 2014 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.checkArgument; +import static com.google.gerrit.index.query.Predicate.and; +import static com.google.gerrit.index.query.Predicate.not; +import static com.google.gerrit.index.query.Predicate.or; +import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gerrit.index.FieldDef; +import com.google.gerrit.index.IndexConfig; +import com.google.gerrit.index.query.InternalQuery; +import com.google.gerrit.index.query.Predicate; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.index.change.ChangeIndexCollection; +import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; + +/** + * Query wrapper for the change index. + * + * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than + * holding on to a single instance. + */ +public class InternalChangeQuery extends InternalQuery<ChangeData> { + private static Predicate<ChangeData> ref(Branch.NameKey branch) { + return new RefPredicate(branch.get()); + } + + private static Predicate<ChangeData> change(Change.Key key) { + return new ChangeIdPredicate(key.get()); + } + + private static Predicate<ChangeData> project(Project.NameKey project) { + return new ProjectPredicate(project.get()); + } + + private static Predicate<ChangeData> status(Change.Status status) { + return ChangeStatusPredicate.forStatus(status); + } + + private static Predicate<ChangeData> commit(String id) { + return new CommitPredicate(id); + } + + private final ChangeData.Factory changeDataFactory; + private final ChangeNotes.Factory notesFactory; + + @Inject + InternalChangeQuery( + ChangeQueryProcessor queryProcessor, + ChangeIndexCollection indexes, + IndexConfig indexConfig, + ChangeData.Factory changeDataFactory, + ChangeNotes.Factory notesFactory) { + super(queryProcessor, indexes, indexConfig); + this.changeDataFactory = changeDataFactory; + this.notesFactory = notesFactory; + } + + @Override + public InternalChangeQuery setLimit(int n) { + super.setLimit(n); + return this; + } + + @Override + public InternalChangeQuery enforceVisibility(boolean enforce) { + super.enforceVisibility(enforce); + return this; + } + + @SafeVarargs + @Override + public final InternalChangeQuery setRequestedFields(FieldDef<ChangeData, ?>... fields) { + super.setRequestedFields(fields); + return this; + } + + @Override + public InternalChangeQuery noFields() { + super.noFields(); + return this; + } + + public List<ChangeData> byKey(Change.Key key) throws OrmException { + return byKeyPrefix(key.get()); + } + + public List<ChangeData> byKeyPrefix(String prefix) throws OrmException { + return query(new ChangeIdPredicate(prefix)); + } + + public List<ChangeData> byLegacyChangeId(Change.Id id) throws OrmException { + return query(new LegacyChangeIdPredicate(id)); + } + + public List<ChangeData> byLegacyChangeIds(Collection<Change.Id> ids) throws OrmException { + List<Predicate<ChangeData>> preds = new ArrayList<>(ids.size()); + for (Change.Id id : ids) { + preds.add(new LegacyChangeIdPredicate(id)); + } + return query(or(preds)); + } + + public List<ChangeData> byBranchKey(Branch.NameKey branch, Change.Key key) throws OrmException { + return query(byBranchKeyPred(branch, key)); + } + + public List<ChangeData> byBranchKeyOpen(Project.NameKey project, String branch, Change.Key key) + throws OrmException { + return query(and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open())); + } + + public static Predicate<ChangeData> byBranchKeyOpenPred( + Project.NameKey project, String branch, Change.Key key) { + return and(byBranchKeyPred(new Branch.NameKey(project, branch), key), open()); + } + + private static Predicate<ChangeData> byBranchKeyPred(Branch.NameKey branch, Change.Key key) { + return and(ref(branch), project(branch.getParentKey()), change(key)); + } + + public List<ChangeData> byProject(Project.NameKey project) throws OrmException { + return query(project(project)); + } + + public List<ChangeData> byBranchOpen(Branch.NameKey branch) throws OrmException { + return query(and(ref(branch), project(branch.getParentKey()), open())); + } + + public List<ChangeData> byBranchNew(Branch.NameKey branch) throws OrmException { + return query(and(ref(branch), project(branch.getParentKey()), status(Change.Status.NEW))); + } + + public Iterable<ChangeData> byCommitsOnBranchNotMerged( + Repository repo, ReviewDb db, Branch.NameKey branch, Collection<String> hashes) + throws OrmException, IOException { + return byCommitsOnBranchNotMerged( + repo, + db, + branch, + hashes, + // Account for all commit predicates plus ref, project, status. + indexConfig.maxTerms() - 3); + } + + @VisibleForTesting + Iterable<ChangeData> byCommitsOnBranchNotMerged( + Repository repo, + ReviewDb db, + Branch.NameKey branch, + Collection<String> hashes, + int indexLimit) + throws OrmException, IOException { + if (hashes.size() > indexLimit) { + return byCommitsOnBranchNotMergedFromDatabase(repo, db, branch, hashes); + } + return byCommitsOnBranchNotMergedFromIndex(branch, hashes); + } + + private Iterable<ChangeData> byCommitsOnBranchNotMergedFromDatabase( + Repository repo, ReviewDb db, Branch.NameKey branch, Collection<String> hashes) + throws OrmException, IOException { + Set<Change.Id> changeIds = Sets.newHashSetWithExpectedSize(hashes.size()); + String lastPrefix = null; + for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) { + String r = ref.getName(); + if ((lastPrefix != null && r.startsWith(lastPrefix)) + || !hashes.contains(ref.getObjectId().name())) { + continue; + } + Change.Id id = Change.Id.fromRef(r); + if (id == null) { + continue; + } + if (changeIds.add(id)) { + lastPrefix = r.substring(0, r.lastIndexOf('/')); + } + } + + List<ChangeNotes> notes = + notesFactory.create( + db, + branch.getParentKey(), + changeIds, + cn -> { + Change c = cn.getChange(); + return c.getDest().equals(branch) && c.getStatus() != Change.Status.MERGED; + }); + return Lists.transform(notes, n -> changeDataFactory.create(db, n)); + } + + private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex( + Branch.NameKey branch, Collection<String> hashes) throws OrmException { + return query( + and( + ref(branch), + project(branch.getParentKey()), + not(status(Change.Status.MERGED)), + or(commits(hashes)))); + } + + private static List<Predicate<ChangeData>> commits(Collection<String> hashes) { + List<Predicate<ChangeData>> commits = new ArrayList<>(hashes.size()); + for (String s : hashes) { + commits.add(commit(s)); + } + return commits; + } + + public List<ChangeData> byProjectOpen(Project.NameKey project) throws OrmException { + return query(and(project(project), open())); + } + + public List<ChangeData> byTopicOpen(String topic) throws OrmException { + return query(and(new ExactTopicPredicate(topic), open())); + } + + public List<ChangeData> byCommit(ObjectId id) throws OrmException { + return byCommit(id.name()); + } + + public List<ChangeData> byCommit(String hash) throws OrmException { + return query(commit(hash)); + } + + public List<ChangeData> byProjectCommit(Project.NameKey project, ObjectId id) + throws OrmException { + return byProjectCommit(project, id.name()); + } + + public List<ChangeData> byProjectCommit(Project.NameKey project, String hash) + throws OrmException { + return query(and(project(project), commit(hash))); + } + + public List<ChangeData> byProjectCommits(Project.NameKey project, List<String> hashes) + throws OrmException { + int n = indexConfig.maxTerms() - 1; + checkArgument(hashes.size() <= n, "cannot exceed %s commits", n); + return query(and(project(project), or(commits(hashes)))); + } + + public List<ChangeData> byBranchCommit(String project, String branch, String hash) + throws OrmException { + return query(byBranchCommitPred(project, branch, hash)); + } + + public List<ChangeData> byBranchCommit(Branch.NameKey branch, String hash) throws OrmException { + return byBranchCommit(branch.getParentKey().get(), branch.get(), hash); + } + + public List<ChangeData> byBranchCommitOpen(String project, String branch, String hash) + throws OrmException { + return query(and(byBranchCommitPred(project, branch, hash), open())); + } + + public static Predicate<ChangeData> byBranchCommitOpenPred( + Project.NameKey project, String branch, String hash) { + return and(byBranchCommitPred(project.get(), branch, hash), open()); + } + + private static Predicate<ChangeData> byBranchCommitPred( + String project, String branch, String hash) { + return and(new ProjectPredicate(project), new RefPredicate(branch), commit(hash)); + } + + public List<ChangeData> bySubmissionId(String cs) throws OrmException { + if (Strings.isNullOrEmpty(cs)) { + return Collections.emptyList(); + } + return query(new SubmissionIdPredicate(cs)); + } + + private List<ChangeData> byProjectGroups(Project.NameKey project, Collection<String> groups) + throws OrmException { + int n = indexConfig.maxTerms() - 1; + checkArgument(groups.size() <= n, "cannot exceed %s groups", n); + List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size()); + for (String g : groups) { + groupPredicates.add(new GroupPredicate(g)); + } + return query(and(project(project), or(groupPredicates))); + } + + // Batching via multiple queries requires passing in a Provider since the underlying + // QueryProcessor instance is not reusable. + public static List<ChangeData> byProjectGroups( + Provider<InternalChangeQuery> queryProvider, + IndexConfig indexConfig, + Project.NameKey project, + Collection<String> groups) + throws OrmException { + int batchSize = indexConfig.maxTerms() - 1; + if (groups.size() <= batchSize) { + return queryProvider.get().enforceVisibility(true).byProjectGroups(project, groups); + } + Set<Change.Id> seen = new HashSet<>(); + List<ChangeData> result = new ArrayList<>(); + for (List<String> part : Iterables.partition(groups, batchSize)) { + for (ChangeData cd : + queryProvider.get().enforceVisibility(true).byProjectGroups(project, part)) { + if (!seen.add(cd.getId())) { + result.add(cd); + } + } + } + return result; + } +} |