Line data Source code
1 : // Copyright (C) 2013 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.index.change;
16 :
17 : import static com.google.common.base.Preconditions.checkState;
18 : import static com.google.gerrit.server.index.change.ChangeField.CHANGE;
19 : import static com.google.gerrit.server.index.change.ChangeField.PROJECT_SPEC;
20 :
21 : import com.google.common.annotations.VisibleForTesting;
22 : import com.google.common.collect.ImmutableList;
23 : import com.google.common.collect.ImmutableSet;
24 : import com.google.common.collect.Iterables;
25 : import com.google.gerrit.entities.Change;
26 : import com.google.gerrit.index.IndexConfig;
27 : import com.google.gerrit.index.QueryOptions;
28 : import com.google.gerrit.index.query.DataSource;
29 : import com.google.gerrit.index.query.IndexPredicate;
30 : import com.google.gerrit.index.query.IndexedQuery;
31 : import com.google.gerrit.index.query.Matchable;
32 : import com.google.gerrit.index.query.Predicate;
33 : import com.google.gerrit.index.query.QueryParseException;
34 : import com.google.gerrit.index.query.ResultSet;
35 : import com.google.gerrit.server.query.change.ChangeData;
36 : import com.google.gerrit.server.query.change.ChangeDataSource;
37 : import com.google.gerrit.server.query.change.ChangeIndexPostFilterPredicate;
38 : import java.util.HashMap;
39 : import java.util.HashSet;
40 : import java.util.Iterator;
41 : import java.util.Map;
42 : import java.util.Set;
43 :
44 : /**
45 : * Wrapper combining an {@link IndexPredicate} together with a {@link ChangeDataSource} that returns
46 : * matching results from the index.
47 : *
48 : * <p>Appropriate to return as the rootmost predicate that can be processed using the secondary
49 : * index; such predicates must also implement {@link ChangeDataSource} to be chosen by the query
50 : * processor.
51 : */
52 : public class IndexedChangeQuery extends IndexedQuery<Change.Id, ChangeData>
53 : implements ChangeDataSource, Matchable<ChangeData> {
54 : public static QueryOptions oneResult() {
55 0 : IndexConfig config = IndexConfig.createDefault();
56 0 : return createOptions(config, 0, 1, config.pageSizeMultiplier(), 1, ImmutableSet.of());
57 : }
58 :
59 : public static QueryOptions createOptions(
60 : IndexConfig config, int start, int limit, Set<String> fields) {
61 6 : return createOptions(config, start, limit, config.pageSizeMultiplier(), limit, fields);
62 : }
63 :
64 : public static QueryOptions createOptions(
65 : IndexConfig config,
66 : int start,
67 : int pageSize,
68 : int pageSizeMultiplier,
69 : int limit,
70 : Set<String> fields) {
71 : // Always include project since it is needed to load the change from NoteDb.
72 111 : if (!fields.contains(CHANGE.getName()) && !fields.contains(PROJECT_SPEC.getName())) {
73 91 : fields = new HashSet<>(fields);
74 91 : fields.add(PROJECT_SPEC.getName());
75 : }
76 111 : return QueryOptions.create(config, start, pageSize, pageSizeMultiplier, limit, fields);
77 : }
78 :
79 : @VisibleForTesting
80 : static QueryOptions convertOptions(QueryOptions opts) {
81 111 : opts = opts.convertForBackend();
82 111 : return IndexedChangeQuery.createOptions(
83 111 : opts.config(),
84 111 : opts.start(),
85 111 : opts.pageSize(),
86 111 : opts.pageSizeMultiplier(),
87 111 : opts.limit(),
88 111 : opts.fields());
89 : }
90 :
91 : private final Map<ChangeData, DataSource<ChangeData>> fromSource;
92 :
93 : public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred, QueryOptions opts)
94 : throws QueryParseException {
95 111 : super(index, pred, convertOptions(opts));
96 111 : this.fromSource = new HashMap<>();
97 111 : }
98 :
99 : @Override
100 : public ResultSet<ChangeData> read() {
101 111 : final DataSource<ChangeData> currSource = source;
102 111 : final ResultSet<ChangeData> rs = currSource.read();
103 :
104 111 : return new ResultSet<>() {
105 : @Override
106 : public Iterator<ChangeData> iterator() {
107 109 : return Iterables.transform(
108 : rs,
109 : cd -> {
110 97 : fromSource.put(cd, currSource);
111 97 : return cd;
112 : })
113 109 : .iterator();
114 : }
115 :
116 : @Override
117 : public ImmutableList<ChangeData> toList() {
118 84 : ImmutableList<ChangeData> r = rs.toList();
119 84 : for (ChangeData cd : r) {
120 80 : fromSource.put(cd, currSource);
121 80 : }
122 84 : return r;
123 : }
124 :
125 : @Override
126 : public void close() {
127 0 : rs.close();
128 0 : }
129 :
130 : @Override
131 : public Object searchAfter() {
132 97 : return rs.searchAfter();
133 : }
134 : };
135 : }
136 :
137 : public boolean postIndexMatch(Predicate<ChangeData> pred, ChangeData cd) {
138 75 : if (pred instanceof ChangeIndexPostFilterPredicate) {
139 9 : checkState(
140 9 : pred.isMatchable(),
141 : "match invoked, but child predicate %s doesn't implement %s",
142 : pred,
143 9 : Matchable.class.getName());
144 9 : return pred.asMatchable().match(cd);
145 : }
146 75 : for (int i = 0; i < pred.getChildCount(); i++) {
147 71 : if (!postIndexMatch(pred.getChild(i), cd)) {
148 9 : return false;
149 : }
150 : }
151 75 : return true;
152 : }
153 :
154 : @Override
155 : public boolean match(ChangeData cd) {
156 75 : Predicate<ChangeData> pred = getChild(0);
157 75 : if (source != null && fromSource.get(cd) == source && postIndexMatch(pred, cd)) {
158 75 : return true;
159 : }
160 :
161 9 : checkState(
162 9 : pred.isMatchable(),
163 : "match invoked, but child predicate %s doesn't implement %s",
164 : pred,
165 9 : Matchable.class.getName());
166 9 : return pred.asMatchable().match(cd);
167 : }
168 :
169 : @Override
170 : public int getCost() {
171 : // Index queries are assumed to be cheaper than any other type of query, so
172 : // so try to make sure they get picked. Note that pred's cost may be higher
173 : // because it doesn't know whether it's being used in an index query or not.
174 103 : return 1;
175 : }
176 :
177 : @Override
178 : public boolean hasChange() {
179 0 : return index.getSchema().hasField(ChangeField.CHANGE);
180 : }
181 : }
|