Serve label predicate from Lucene index
To build the Lucene query we need to know the maximum range a label can have. Since the label ranges can be defined per project the maximum range for a label is hard to compute. For this we would need to scan over all projects and parse the project.config files. Instead of doing this we hard code for now a maximum query range from -4 to +4. This range should be wide enough to support most labels. The query behaviour for searching labels with value 0 has changed. We now return all changes that have no voting on that label from any reviewer (this includes changes without any reviewer). Earlier we returned changes that had a reviewer for which an explicit 0 value was stored. This old behaviour was unexpected by most users. Change-Id: Ic0e5ae86933d605e67c9da8ef289a0b099d82cbf Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -45,7 +45,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class ChangeField {
|
||||
/** Increment whenever making schema changes. */
|
||||
public static final int SCHEMA_VERSION = 9;
|
||||
public static final int SCHEMA_VERSION = 10;
|
||||
|
||||
/** Legacy change ID. */
|
||||
public static final FieldDef<ChangeData, Integer> LEGACY_ID =
|
||||
@@ -203,6 +203,27 @@ public class ChangeField {
|
||||
}
|
||||
};
|
||||
|
||||
/** List of labels on the current patch set. */
|
||||
public static final FieldDef<ChangeData, Iterable<String>> LABEL =
|
||||
new FieldDef.Repeatable<ChangeData, String>(
|
||||
ChangeQueryBuilder.FIELD_LABEL, FieldType.EXACT, false) {
|
||||
@Override
|
||||
public Iterable<String> get(ChangeData input, FillArgs args)
|
||||
throws OrmException {
|
||||
Set<String> distinctApprovals = Sets.newHashSet();
|
||||
for (PatchSetApproval a : input.currentApprovals(args.db)) {
|
||||
if (a.getValue() != 0) {
|
||||
distinctApprovals.add(formatLabel(a.getLabel(), a.getValue()));
|
||||
}
|
||||
}
|
||||
return distinctApprovals;
|
||||
}
|
||||
};
|
||||
|
||||
public static String formatLabel(String label, int value) {
|
||||
return label.toLowerCase() + (value >= 0 ? "+" : "") + value;
|
||||
}
|
||||
|
||||
public static final ImmutableMap<String, FieldDef<ChangeData, ?>> ALL;
|
||||
|
||||
static {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (C) 2013 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 com.google.gerrit.common.data.LabelType;
|
||||
import com.google.gerrit.common.data.LabelTypes;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IndexPredicate;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
class EqualsLabelPredicate extends IndexPredicate<ChangeData> {
|
||||
private final ProjectCache projectCache;
|
||||
private final ChangeControl.GenericFactory ccFactory;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final String label;
|
||||
private final int expVal;
|
||||
|
||||
EqualsLabelPredicate(ProjectCache projectCache,
|
||||
ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
|
||||
String label, int expVal) {
|
||||
super(ChangeField.LABEL, ChangeField.formatLabel(label, expVal));
|
||||
this.ccFactory = ccFactory;
|
||||
this.projectCache = projectCache;
|
||||
this.userFactory = userFactory;
|
||||
this.dbProvider = dbProvider;
|
||||
this.label = label;
|
||||
this.expVal = expVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData object) throws OrmException {
|
||||
Change c = object.change(dbProvider);
|
||||
if (c == null) {
|
||||
// The change has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
ProjectState project = projectCache.get(c.getDest().getParentKey());
|
||||
if (project == null) {
|
||||
// The project has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
LabelType labelType = type(project.getLabelTypes(), label);
|
||||
boolean hasVote = false;
|
||||
for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
|
||||
if (labelType.matches(p)) {
|
||||
hasVote = true;
|
||||
if (match(c, p.getValue(), p.getAccountId(), labelType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasVote && expVal == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static LabelType type(LabelTypes types, String toFind) {
|
||||
if (types.byLabel(toFind) != null) {
|
||||
return types.byLabel(toFind);
|
||||
}
|
||||
|
||||
for (LabelType lt : types.getLabelTypes()) {
|
||||
if (toFind.equalsIgnoreCase(lt.getName())) {
|
||||
return lt;
|
||||
}
|
||||
}
|
||||
|
||||
for (LabelType lt : types.getLabelTypes()) {
|
||||
if (toFind.equalsIgnoreCase(lt.getAbbreviation())) {
|
||||
return lt;
|
||||
}
|
||||
}
|
||||
|
||||
return LabelType.withDefaultValues(toFind);
|
||||
}
|
||||
|
||||
private boolean match(Change change, int value, Account.Id approver,
|
||||
LabelType type) throws OrmException {
|
||||
int psVal = value;
|
||||
if (psVal == expVal) {
|
||||
// Double check the value is still permitted for the user.
|
||||
//
|
||||
try {
|
||||
ChangeControl cc = ccFactory.controlFor(change, //
|
||||
userFactory.create(dbProvider, approver));
|
||||
if (!cc.isVisible(dbProvider.get())) {
|
||||
// The user can't see the change anymore.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
psVal = cc.getRange(Permission.forLabel(type.getName())).squash(psVal);
|
||||
} catch (NoSuchChangeException e) {
|
||||
// The project has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
if (psVal == expVal) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -14,85 +14,107 @@
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.common.data.LabelType;
|
||||
import com.google.gerrit.common.data.LabelTypes;
|
||||
import com.google.gerrit.common.data.Permission;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.query.OperatorPredicate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gerrit.server.query.OrPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class LabelPredicate extends OperatorPredicate<ChangeData> {
|
||||
public class LabelPredicate extends OrPredicate<ChangeData> {
|
||||
private static final int MAX_LABEL_VALUE = 4;
|
||||
|
||||
private static enum Test {
|
||||
EQ {
|
||||
@Override
|
||||
public boolean match(int psValue, int expValue) {
|
||||
return psValue == expValue;
|
||||
}
|
||||
},
|
||||
GT_EQ {
|
||||
@Override
|
||||
public boolean match(int psValue, int expValue) {
|
||||
return psValue >= expValue;
|
||||
}
|
||||
},
|
||||
LT_EQ {
|
||||
@Override
|
||||
public boolean match(int psValue, int expValue) {
|
||||
return psValue <= expValue;
|
||||
}
|
||||
};
|
||||
EQ, GT_EQ, LT_EQ;
|
||||
|
||||
abstract boolean match(int psValue, int expValue);
|
||||
boolean isEq() {
|
||||
return EQ.equals(this);
|
||||
}
|
||||
|
||||
private static LabelType type(LabelTypes types, String toFind) {
|
||||
if (types.byLabel(toFind) != null) {
|
||||
return types.byLabel(toFind);
|
||||
boolean isGtEq() {
|
||||
return GT_EQ.equals(this);
|
||||
}
|
||||
|
||||
for (LabelType lt : types.getLabelTypes()) {
|
||||
if (toFind.equalsIgnoreCase(lt.getName())) {
|
||||
return lt;
|
||||
}
|
||||
}
|
||||
|
||||
for (LabelType lt : types.getLabelTypes()) {
|
||||
if (toFind.equalsIgnoreCase(lt.getAbbreviation())) {
|
||||
return lt;
|
||||
}
|
||||
}
|
||||
|
||||
return LabelType.withDefaultValues(toFind);
|
||||
}
|
||||
|
||||
private static Test op(String op) {
|
||||
static Test op(String op) {
|
||||
if ("=".equals(op)) {
|
||||
return Test.EQ;
|
||||
return EQ;
|
||||
|
||||
} else if (">=".equals(op)) {
|
||||
return Test.GT_EQ;
|
||||
return GT_EQ;
|
||||
|
||||
} else if ("<=".equals(op)) {
|
||||
return Test.LT_EQ;
|
||||
return LT_EQ;
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported operation " + op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final String value;
|
||||
|
||||
LabelPredicate(ProjectCache projectCache,
|
||||
ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
|
||||
String value) {
|
||||
super(predicates(projectCache, ccFactory, userFactory,
|
||||
dbProvider, value));
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static List<Predicate<ChangeData>> predicates(
|
||||
ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
|
||||
String value) {
|
||||
String label;
|
||||
Test test;
|
||||
int expVal;
|
||||
Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
|
||||
Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
|
||||
if (m1.find()) {
|
||||
label = value.substring(0, m1.start());
|
||||
test = Test.op(m1.group(1));
|
||||
expVal = value(m1.group(2));
|
||||
|
||||
} else if (m2.find()) {
|
||||
label = value.substring(0, m2.start());
|
||||
test = Test.EQ;
|
||||
expVal = value(m2.group(1));
|
||||
|
||||
} else {
|
||||
label = value;
|
||||
test = Test.EQ;
|
||||
expVal = 1;
|
||||
}
|
||||
|
||||
List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
|
||||
if (test.isEq()) {
|
||||
if (expVal != 0) {
|
||||
r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label, expVal));
|
||||
} else {
|
||||
r.add(noLabelQuery(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label));
|
||||
}
|
||||
} else {
|
||||
for (int i = test.isGtEq() ? expVal : neg(expVal); i <= MAX_LABEL_VALUE; i++) {
|
||||
if (i != 0) {
|
||||
r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label, test.isGtEq() ? i : neg(i)));
|
||||
} else {
|
||||
r.add(noLabelQuery(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label));
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static int value(String value) {
|
||||
if (value.startsWith("+")) {
|
||||
@@ -101,113 +123,31 @@ class LabelPredicate extends OperatorPredicate<ChangeData> {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
private final ProjectCache projectCache;
|
||||
private final ChangeControl.GenericFactory ccFactory;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final Test test;
|
||||
private final String type;
|
||||
private final int expVal;
|
||||
|
||||
LabelPredicate(ProjectCache projectCache,
|
||||
ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory,
|
||||
Provider<ReviewDb> dbProvider,
|
||||
String value) {
|
||||
super(ChangeQueryBuilder.FIELD_LABEL, value);
|
||||
this.ccFactory = ccFactory;
|
||||
this.projectCache = projectCache;
|
||||
this.userFactory = userFactory;
|
||||
this.dbProvider = dbProvider;
|
||||
|
||||
Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
|
||||
Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
|
||||
if (m1.find()) {
|
||||
type = value.substring(0, m1.start());
|
||||
test = op(m1.group(1));
|
||||
expVal = value(m1.group(2));
|
||||
|
||||
} else if (m2.find()) {
|
||||
type = value.substring(0, m2.start());
|
||||
test = Test.EQ;
|
||||
expVal = value(m2.group(1));
|
||||
|
||||
} else {
|
||||
type = value;
|
||||
test = Test.EQ;
|
||||
expVal = 1;
|
||||
private static int neg(int value) {
|
||||
return -1 * value;
|
||||
}
|
||||
|
||||
private static Predicate<ChangeData> noLabelQuery(ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider, String label) {
|
||||
List<Predicate<ChangeData>> r =
|
||||
Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
|
||||
for (int i = 1; i <= MAX_LABEL_VALUE; i++) {
|
||||
r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label, i)));
|
||||
r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label, neg(i))));
|
||||
}
|
||||
return and(r);
|
||||
}
|
||||
|
||||
private static Predicate<ChangeData> equalsLabelPredicate(ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
|
||||
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider, String label, int expVal) {
|
||||
return new EqualsLabelPredicate(projectCache, ccFactory, userFactory,
|
||||
dbProvider, label, expVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(final ChangeData object) throws OrmException {
|
||||
final Change c = object.change(dbProvider);
|
||||
if (c == null) {
|
||||
// The change has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
final ProjectState project = projectCache.get(c.getDest().getParentKey());
|
||||
if (project == null) {
|
||||
// The project has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
final LabelType labelType = type(project.getLabelTypes(), type);
|
||||
final Set<Account.Id> allApprovers = new HashSet<Account.Id>();
|
||||
final Set<Account.Id> approversThatVotedInCategory = new HashSet<Account.Id>();
|
||||
for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
|
||||
allApprovers.add(p.getAccountId());
|
||||
if (labelType.matches(p)) {
|
||||
approversThatVotedInCategory.add(p.getAccountId());
|
||||
if (match(c, p.getValue(), p.getAccountId(), labelType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Set<Account.Id> approversThatDidNotVoteInCategory = new HashSet<Account.Id>(allApprovers);
|
||||
approversThatDidNotVoteInCategory.removeAll(approversThatVotedInCategory);
|
||||
for (Account.Id a : approversThatDidNotVoteInCategory) {
|
||||
if (match(c, 0, a, labelType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean match(final Change change, final int value,
|
||||
final Account.Id approver, final LabelType type)
|
||||
throws OrmException {
|
||||
int psVal = value;
|
||||
if (test.match(psVal, expVal)) {
|
||||
// Double check the value is still permitted for the user.
|
||||
//
|
||||
try {
|
||||
ChangeControl cc = ccFactory.controlFor(change, //
|
||||
userFactory.create(dbProvider, approver));
|
||||
if (!cc.isVisible(dbProvider.get())) {
|
||||
// The user can't see the change anymore.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
psVal = cc.getRange(Permission.forLabel(type.getName())).squash(psVal);
|
||||
} catch (NoSuchChangeException e) {
|
||||
// The project has disappeared.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
if (test.match(psVal, expVal)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 2;
|
||||
public String toString() {
|
||||
return ChangeQueryBuilder.FIELD_LABEL + ":" + value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user