Line data Source code
1 : // Copyright (C) 2014 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.query.change;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.gerrit.index.query.Predicate.and;
19 : import static com.google.gerrit.index.query.Predicate.not;
20 : import static com.google.gerrit.index.query.Predicate.or;
21 : import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
22 :
23 : import com.google.common.annotations.VisibleForTesting;
24 : import com.google.common.base.Strings;
25 : import com.google.common.collect.ImmutableList;
26 : import com.google.common.collect.Iterables;
27 : import com.google.common.collect.Lists;
28 : import com.google.common.collect.Sets;
29 : import com.google.gerrit.entities.BranchNameKey;
30 : import com.google.gerrit.entities.Change;
31 : import com.google.gerrit.entities.Project;
32 : import com.google.gerrit.entities.RefNames;
33 : import com.google.gerrit.index.IndexConfig;
34 : import com.google.gerrit.index.query.InternalQuery;
35 : import com.google.gerrit.index.query.Predicate;
36 : import com.google.gerrit.server.index.change.ChangeIndexCollection;
37 : import com.google.gerrit.server.notedb.ChangeNotes;
38 : import com.google.inject.Inject;
39 : import com.google.inject.Provider;
40 : import java.io.IOException;
41 : import java.util.ArrayList;
42 : import java.util.Collection;
43 : import java.util.Collections;
44 : import java.util.HashSet;
45 : import java.util.List;
46 : import java.util.Set;
47 : import java.util.function.Supplier;
48 : import org.eclipse.jgit.lib.ObjectId;
49 : import org.eclipse.jgit.lib.Ref;
50 : import org.eclipse.jgit.lib.Repository;
51 :
52 : /**
53 : * Query wrapper for the change index.
54 : *
55 : * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
56 : * holding on to a single instance.
57 : */
58 : public class InternalChangeQuery extends InternalQuery<ChangeData, InternalChangeQuery> {
59 : private static Predicate<ChangeData> ref(BranchNameKey branch) {
60 102 : return ChangePredicates.ref(branch.branch());
61 : }
62 :
63 : private static Predicate<ChangeData> change(Change.Key key) {
64 94 : return ChangePredicates.idPrefix(key.get());
65 : }
66 :
67 : private static Predicate<ChangeData> project(Project.NameKey project) {
68 104 : return ChangePredicates.project(project);
69 : }
70 :
71 : private static Predicate<ChangeData> status(Change.Status status) {
72 53 : return ChangeStatusPredicate.forStatus(status);
73 : }
74 :
75 : private static Predicate<ChangeData> commit(String id) {
76 82 : return ChangePredicates.commitPrefix(id);
77 : }
78 :
79 : private final ChangeData.Factory changeDataFactory;
80 : private final ChangeNotes.Factory notesFactory;
81 :
82 : @Inject
83 : InternalChangeQuery(
84 : ChangeQueryProcessor queryProcessor,
85 : ChangeIndexCollection indexes,
86 : IndexConfig indexConfig,
87 : ChangeData.Factory changeDataFactory,
88 : ChangeNotes.Factory notesFactory) {
89 111 : super(queryProcessor, indexes, indexConfig);
90 111 : this.changeDataFactory = changeDataFactory;
91 111 : this.notesFactory = notesFactory;
92 111 : }
93 :
94 : public List<ChangeData> byKey(Change.Key key) {
95 3 : return byKeyPrefix(key.get());
96 : }
97 :
98 : public List<ChangeData> byKeyPrefix(String prefix) {
99 89 : return query(ChangePredicates.idPrefix(prefix));
100 : }
101 :
102 : public List<ChangeData> byLegacyChangeId(Change.Id id) {
103 68 : return query(ChangePredicates.idStr(id));
104 : }
105 :
106 : public List<ChangeData> byLegacyChangeIds(Collection<Change.Id> ids) {
107 0 : List<Predicate<ChangeData>> preds = new ArrayList<>(ids.size());
108 0 : for (Change.Id id : ids) {
109 0 : preds.add(ChangePredicates.idStr(id));
110 0 : }
111 0 : return query(or(preds));
112 : }
113 :
114 : public List<ChangeData> byBranchKey(BranchNameKey branch, Change.Key key) {
115 94 : return query(byBranchKeyPred(branch, key));
116 : }
117 :
118 : public List<ChangeData> byBranchKeyOpen(Project.NameKey project, String branch, Change.Key key) {
119 0 : return query(and(byBranchKeyPred(BranchNameKey.create(project, branch), key), open()));
120 : }
121 :
122 : public static Predicate<ChangeData> byBranchKeyOpenPred(
123 : Project.NameKey project, String branch, Change.Key key) {
124 0 : return and(byBranchKeyPred(BranchNameKey.create(project, branch), key), open());
125 : }
126 :
127 : private static Predicate<ChangeData> byBranchKeyPred(BranchNameKey branch, Change.Key key) {
128 94 : return and(ref(branch), project(branch.project()), change(key));
129 : }
130 :
131 : public List<ChangeData> byProject(Project.NameKey project) {
132 8 : return query(project(project));
133 : }
134 :
135 : public List<ChangeData> byBranchOpen(BranchNameKey branch) {
136 39 : return query(and(ref(branch), project(branch.project()), open()));
137 : }
138 :
139 : public List<ChangeData> byBranchNew(BranchNameKey branch) {
140 14 : return query(and(ref(branch), project(branch.project()), status(Change.Status.NEW)));
141 : }
142 :
143 : public Iterable<ChangeData> byCommitsOnBranchNotMerged(
144 : Repository repo, BranchNameKey branch, Collection<String> hashes) throws IOException {
145 53 : return byCommitsOnBranchNotMerged(
146 : repo,
147 : branch,
148 : hashes,
149 : // Account for all commit predicates plus ref, project, status.
150 53 : indexConfig.maxTerms() - 3);
151 : }
152 :
153 : @VisibleForTesting
154 : Iterable<ChangeData> byCommitsOnBranchNotMerged(
155 : Repository repo, BranchNameKey branch, Collection<String> hashes, int indexLimit)
156 : throws IOException {
157 53 : if (hashes.size() > indexLimit || !indexes.getSearchIndex().isEnabled()) {
158 4 : return byCommitsOnBranchNotMergedFromDatabase(repo, branch, hashes);
159 : }
160 53 : return byCommitsOnBranchNotMergedFromIndex(branch, hashes);
161 : }
162 :
163 : private Iterable<ChangeData> byCommitsOnBranchNotMergedFromDatabase(
164 : Repository repo, BranchNameKey branch, Collection<String> hashes) throws IOException {
165 4 : Set<Change.Id> changeIds = Sets.newHashSetWithExpectedSize(hashes.size());
166 4 : String lastPrefix = null;
167 4 : for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) {
168 4 : String r = ref.getName();
169 4 : if ((lastPrefix != null && r.startsWith(lastPrefix))
170 4 : || !hashes.contains(ref.getObjectId().name())) {
171 0 : continue;
172 : }
173 4 : Change.Id id = Change.Id.fromRef(r);
174 4 : if (id == null) {
175 0 : continue;
176 : }
177 4 : if (changeIds.add(id)) {
178 4 : lastPrefix = r.substring(0, r.lastIndexOf('/'));
179 : }
180 4 : }
181 :
182 4 : List<ChangeNotes> notes =
183 4 : notesFactory.create(
184 : repo,
185 4 : branch.project(),
186 : changeIds,
187 : cn -> {
188 4 : Change c = cn.getChange();
189 4 : return c.getDest().equals(branch) && !c.isMerged();
190 : });
191 4 : return Lists.transform(notes, n -> changeDataFactory.create(n));
192 : }
193 :
194 : private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex(
195 : BranchNameKey branch, Collection<String> hashes) {
196 53 : return query(
197 53 : and(
198 53 : ref(branch),
199 53 : project(branch.project()),
200 53 : not(status(Change.Status.MERGED)),
201 53 : or(commits(hashes))));
202 : }
203 :
204 : private static List<Predicate<ChangeData>> commits(Collection<String> hashes) {
205 53 : List<Predicate<ChangeData>> commits = new ArrayList<>(hashes.size());
206 53 : for (String s : hashes) {
207 53 : commits.add(commit(s));
208 53 : }
209 53 : return commits;
210 : }
211 :
212 : public List<ChangeData> byProjectOpen(Project.NameKey project) {
213 14 : return query(and(project(project), open()));
214 : }
215 :
216 : public List<ChangeData> byTopicOpen(String topic) {
217 17 : return query(and(ChangePredicates.exactTopic(topic), open()));
218 : }
219 :
220 : public List<ChangeData> byCommit(ObjectId id) {
221 6 : return byCommit(id.name());
222 : }
223 :
224 : public List<ChangeData> byCommit(String hash) {
225 12 : return query(commit(hash));
226 : }
227 :
228 : public List<ChangeData> byProjectCommit(Project.NameKey project, ObjectId id) {
229 22 : return byProjectCommit(project, id.name());
230 : }
231 :
232 : public List<ChangeData> byProjectCommit(Project.NameKey project, String hash) {
233 31 : return query(and(project(project), commit(hash)));
234 : }
235 :
236 : public List<ChangeData> byProjectCommits(Project.NameKey project, List<String> hashes) {
237 1 : int n = indexConfig.maxTerms() - 1;
238 1 : checkArgument(hashes.size() <= n, "cannot exceed %s commits", n);
239 1 : return query(and(project(project), or(commits(hashes))));
240 : }
241 :
242 : public List<ChangeData> byBranchCommit(String project, String branch, String hash) {
243 54 : return query(byBranchCommitPred(project, branch, hash));
244 : }
245 :
246 : public List<ChangeData> byBranchCommit(BranchNameKey branch, String hash) {
247 52 : return byBranchCommit(branch.project().get(), branch.branch(), hash);
248 : }
249 :
250 : public List<ChangeData> byBranchCommitOpen(String project, String branch, String hash) {
251 3 : return query(and(byBranchCommitPred(project, branch, hash), open()));
252 : }
253 :
254 : public static Predicate<ChangeData> byBranchCommitOpenPred(
255 : Project.NameKey project, String branch, String hash) {
256 0 : return and(byBranchCommitPred(project.get(), branch, hash), open());
257 : }
258 :
259 : private static Predicate<ChangeData> byBranchCommitPred(
260 : String project, String branch, String hash) {
261 54 : return and(
262 54 : ChangePredicates.project(Project.nameKey(project)),
263 54 : ChangePredicates.ref(branch),
264 54 : commit(hash));
265 : }
266 :
267 : public List<ChangeData> bySubmissionId(String cs) {
268 26 : if (Strings.isNullOrEmpty(cs)) {
269 0 : return Collections.emptyList();
270 : }
271 26 : return query(ChangePredicates.submissionId(cs));
272 : }
273 :
274 : private static Predicate<ChangeData> byProjectGroupsPredicate(
275 : IndexConfig indexConfig, Project.NameKey project, Collection<String> groups) {
276 7 : int n = indexConfig.maxTerms() - 1;
277 7 : checkArgument(groups.size() <= n, "cannot exceed %s groups", n);
278 7 : List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
279 7 : for (String g : groups) {
280 7 : groupPredicates.add(new GroupPredicate(g));
281 7 : }
282 7 : return and(project(project), or(groupPredicates));
283 : }
284 :
285 : public static ImmutableList<ChangeData> byProjectGroups(
286 : Provider<InternalChangeQuery> queryProvider,
287 : IndexConfig indexConfig,
288 : Project.NameKey project,
289 : Collection<String> groups) {
290 : // These queries may be complex along multiple dimensions:
291 : // * Many groups per change, if there are very many patch sets. This requires partitioning the
292 : // list of predicates and combining results.
293 : // * Many changes with the same set of groups, if the relation chain is very long. This
294 : // requires querying exhaustively with pagination.
295 : // For both cases, we need to invoke the queryProvider multiple times, since each
296 : // InternalChangeQuery is single-use.
297 :
298 7 : Supplier<InternalChangeQuery> querySupplier = () -> queryProvider.get().enforceVisibility(true);
299 7 : int batchSize = indexConfig.maxTerms() - 1;
300 7 : if (groups.size() <= batchSize) {
301 7 : return queryExhaustively(
302 7 : querySupplier, byProjectGroupsPredicate(indexConfig, project, groups));
303 : }
304 1 : Set<Change.Id> seen = new HashSet<>();
305 1 : ImmutableList.Builder<ChangeData> result = ImmutableList.builder();
306 1 : for (List<String> part : Iterables.partition(groups, batchSize)) {
307 : for (ChangeData cd :
308 1 : queryExhaustively(querySupplier, byProjectGroupsPredicate(indexConfig, project, part))) {
309 1 : if (!seen.add(cd.getId())) {
310 1 : result.add(cd);
311 : }
312 1 : }
313 1 : }
314 1 : return result.build();
315 : }
316 : }
|