Line data Source code
1 : // Copyright (C) 2016 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.index.query; 16 : 17 : import static com.google.common.base.Preconditions.checkArgument; 18 : import static java.util.stream.Collectors.toSet; 19 : 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.common.collect.Lists; 23 : import com.google.gerrit.common.Nullable; 24 : import com.google.gerrit.exceptions.StorageException; 25 : import com.google.gerrit.index.Index; 26 : import com.google.gerrit.index.IndexCollection; 27 : import com.google.gerrit.index.IndexConfig; 28 : import com.google.gerrit.index.Schema; 29 : import com.google.gerrit.index.SchemaFieldDefs.SchemaField; 30 : import java.util.Arrays; 31 : import java.util.List; 32 : import java.util.function.Supplier; 33 : 34 : /** 35 : * Execute a single query over a secondary index, for use by Gerrit internals. 36 : * 37 : * <p>By default, visibility of returned entities is not enforced (unlike in {@link 38 : * QueryProcessor}). The methods in this class are not typically used by user-facing paths, but 39 : * rather by internal callers that need to process all matching results. 40 : * 41 : * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than 42 : * holding on to a single instance. 43 : */ 44 : public class InternalQuery<T, Q extends InternalQuery<T, Q>> { 45 : private final QueryProcessor<T> queryProcessor; 46 : protected final IndexCollection<?, T, ? extends Index<?, T>> indexes; 47 : 48 : protected final IndexConfig indexConfig; 49 : 50 : protected InternalQuery( 51 : QueryProcessor<T> queryProcessor, 52 : IndexCollection<?, T, ? extends Index<?, T>> indexes, 53 151 : IndexConfig indexConfig) { 54 151 : this.queryProcessor = queryProcessor.enforceVisibility(false); 55 151 : this.indexes = indexes; 56 151 : this.indexConfig = indexConfig; 57 151 : } 58 : 59 : @SuppressWarnings("unchecked") 60 : protected final Q self() { 61 115 : return (Q) this; 62 : } 63 : 64 : final Q setStart(int start) { 65 7 : queryProcessor.setStart(start); 66 7 : return self(); 67 : } 68 : 69 : public final Q setLimit(int n) { 70 110 : queryProcessor.setUserProvidedLimit(n); 71 110 : return self(); 72 : } 73 : 74 : public final Q enforceVisibility(boolean enforce) { 75 109 : queryProcessor.enforceVisibility(enforce); 76 109 : return self(); 77 : } 78 : 79 : @SafeVarargs 80 : public final Q setRequestedFields(SchemaField<T, ?>... fields) { 81 102 : checkArgument(fields.length > 0, "requested field list is empty"); 82 102 : queryProcessor.setRequestedFields( 83 102 : Arrays.stream(fields).map(SchemaField::getName).collect(toSet())); 84 102 : return self(); 85 : } 86 : 87 : public final Q noFields() { 88 91 : queryProcessor.setRequestedFields(ImmutableSet.of()); 89 91 : return self(); 90 : } 91 : 92 : public final List<T> query(Predicate<T> p) { 93 151 : return queryResults(p).entities(); 94 : } 95 : 96 : final QueryResult<T> queryResults(Predicate<T> p) { 97 : try { 98 151 : return queryProcessor.query(p); 99 0 : } catch (QueryParseException e) { 100 0 : throw new StorageException(e); 101 : } 102 : } 103 : 104 : /** 105 : * Run multiple queries in parallel. 106 : * 107 : * <p>If a limit was specified using {@link #setLimit(int)}, that limit is applied to each query 108 : * independently. 109 : * 110 : * @param queries list of queries. 111 : * @return results of the queries, one list of results per input query, in the same order as the 112 : * input. 113 : */ 114 : public final List<List<T>> query(List<Predicate<T>> queries) { 115 : try { 116 0 : return Lists.transform(queryProcessor.query(queries), QueryResult::entities); 117 0 : } catch (QueryParseException e) { 118 0 : throw new StorageException(e); 119 : } 120 : } 121 : 122 : @Nullable 123 : protected final Schema<T> schema() { 124 51 : Index<?, T> index = indexes != null ? indexes.getSearchIndex() : null; 125 51 : return index != null ? index.getSchema() : null; 126 : } 127 : 128 : /** 129 : * Query a predicate repeatedly until all results are exhausted. 130 : * 131 : * <p>Capable of iterating through all results regardless of limits. The passed {@code 132 : * querySupplier} may choose to pre-set limits or not; this only affects the number of queries 133 : * that may be issued, not the size of the final results. 134 : * 135 : * <p>Since multiple queries may be issued, this method is subject to races when the result set 136 : * changes mid-iteration. This may result in skipped results, if an entity gets modified to jump 137 : * to the front of the list after this method has passed it. It may also result in duplicate 138 : * results, if an entity at the end of one batch of results gets pushed back further, putting it 139 : * at the beginning of the next batch. This race cannot be avoided unless we change the underlying 140 : * index interface to support true continuation tokens. 141 : * 142 : * @param querySupplier supplier for queries. Callers will generally pass a lambda that invokes an 143 : * underlying {@code Provider<InternalFooQuery>}, since the instances are not reusable. The 144 : * lambda may also call additional methods on the newly-created query, such as {@link 145 : * #enforceVisibility(boolean)}. 146 : * @param predicate predicate to search for. 147 : * @param <T> result type. 148 : * @return exhaustive list of results, subject to the race condition described above. 149 : */ 150 : protected static <T> ImmutableList<T> queryExhaustively( 151 : Supplier<? extends InternalQuery<T, ?>> querySupplier, Predicate<T> predicate) { 152 7 : ImmutableList.Builder<T> b = null; 153 7 : int start = 0; 154 : while (true) { 155 7 : QueryResult<T> qr = querySupplier.get().setStart(start).queryResults(predicate); 156 7 : if (b == null) { 157 7 : if (!qr.more()) { 158 7 : return qr.entities(); 159 : } 160 1 : b = ImmutableList.builder(); 161 : } 162 1 : b.addAll(qr.entities()); 163 1 : if (!qr.more()) { 164 1 : return b.build(); 165 : } 166 1 : start += qr.entities().size(); 167 1 : } 168 : } 169 : }