Add search fields for # of changed lines.
Based off https://gerrit-review.googlesource.com/#/c/52190, but implementing the final suggestion of indexing raw delta counts and allowing arbitrary range queries off of those. Also upgrade Lucene to 4.8.1 as this was released since the last schema change (which was on 4.7.0). Change-Id: Ia8a677e71e133f68eced4c5394df1d23efe7f12a
This commit is contained in:
parent
063f658042
commit
45d0a772e1
@ -314,6 +314,18 @@ status:abandoned::
|
||||
+
|
||||
Change has been abandoned.
|
||||
|
||||
[[size]]
|
||||
added:'RELATION''LINES', deleted:'RELATION''LINES', delta/size:'RELATION''LINES'::
|
||||
+
|
||||
True if the number of lines added/deleted/changed satisfies the given relation
|
||||
for the given number of lines.
|
||||
+
|
||||
For example, added:>50 will be true for any change which adds at least 50
|
||||
lines.
|
||||
+
|
||||
Valid relations are >=, >, <=, <, or no relation, which will match if the
|
||||
number of lines is exactly equal.
|
||||
|
||||
|
||||
== Argument Quoting
|
||||
|
||||
|
@ -119,6 +119,11 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
|
||||
suggestions.add("status:merged");
|
||||
suggestions.add("status:abandoned");
|
||||
|
||||
suggestions.add("added:");
|
||||
suggestions.add("deleted:");
|
||||
suggestions.add("delta:");
|
||||
suggestions.add("size:");
|
||||
|
||||
suggestions.add("AND");
|
||||
suggestions.add("OR");
|
||||
suggestions.add("NOT");
|
||||
|
@ -127,6 +127,8 @@ public class LuceneChangeIndex implements ChangeIndex {
|
||||
Version lucene44 = Version.LUCENE_44;
|
||||
@SuppressWarnings("deprecation")
|
||||
Version lucene46 = Version.LUCENE_46;
|
||||
@SuppressWarnings("deprecation")
|
||||
Version lucene47 = Version.LUCENE_47;
|
||||
for (Map.Entry<Integer, Schema<ChangeData>> e
|
||||
: ChangeSchemas.ALL.entrySet()) {
|
||||
if (e.getKey() <= 3) {
|
||||
@ -135,8 +137,10 @@ public class LuceneChangeIndex implements ChangeIndex {
|
||||
versions.put(e.getValue(), lucene44);
|
||||
} else if (e.getKey() <= 8) {
|
||||
versions.put(e.getValue(), lucene46);
|
||||
} else if (e.getKey() <= 10) {
|
||||
versions.put(e.getValue(), lucene47);
|
||||
} else {
|
||||
versions.put(e.getValue(), Version.LUCENE_47);
|
||||
versions.put(e.getValue(), Version.LUCENE_48);
|
||||
}
|
||||
}
|
||||
LUCENE_VERSIONS = versions.build();
|
||||
@ -497,7 +501,7 @@ public class LuceneChangeIndex implements ChangeIndex {
|
||||
FieldType<?> type = values.getField().getType();
|
||||
Store store = store(values.getField());
|
||||
|
||||
if (type == FieldType.INTEGER) {
|
||||
if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
|
||||
for (Object value : values.getValues()) {
|
||||
doc.add(new IntField(name, (Integer) value, store));
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.FieldType;
|
||||
import com.google.gerrit.server.index.IndexPredicate;
|
||||
import com.google.gerrit.server.index.IntegerRangePredicate;
|
||||
import com.google.gerrit.server.index.RegexPredicate;
|
||||
import com.google.gerrit.server.index.Schema;
|
||||
import com.google.gerrit.server.index.TimestampRangePredicate;
|
||||
@ -135,6 +136,8 @@ public class QueryBuilder {
|
||||
throws QueryParseException {
|
||||
if (p.getType() == FieldType.INTEGER) {
|
||||
return intQuery(p);
|
||||
} else if (p.getType() == FieldType.INTEGER_RANGE) {
|
||||
return intRangeQuery(p);
|
||||
} else if (p.getType() == FieldType.TIMESTAMP) {
|
||||
return timestampQuery(p);
|
||||
} else if (p.getType() == FieldType.EXACT) {
|
||||
@ -169,6 +172,28 @@ public class QueryBuilder {
|
||||
return new TermQuery(intTerm(p.getField().getName(), value));
|
||||
}
|
||||
|
||||
private Query intRangeQuery(IndexPredicate<ChangeData> p)
|
||||
throws QueryParseException {
|
||||
if (p instanceof IntegerRangePredicate) {
|
||||
IntegerRangePredicate<ChangeData> r =
|
||||
(IntegerRangePredicate<ChangeData>) p;
|
||||
int minimum = r.getMinimumValue();
|
||||
int maximum = r.getMaximumValue();
|
||||
if (minimum == maximum) {
|
||||
// Just fall back to a standard integer query.
|
||||
return new TermQuery(intTerm(p.getField().getName(), minimum));
|
||||
} else {
|
||||
return NumericRangeQuery.newIntRange(
|
||||
r.getField().getName(),
|
||||
minimum,
|
||||
maximum,
|
||||
true,
|
||||
true);
|
||||
}
|
||||
}
|
||||
throw new QueryParseException("not an integer range: " + p);
|
||||
}
|
||||
|
||||
private Query sortKeyQuery(SortKeyPredicate p) {
|
||||
long min = p.getMinValue(schema);
|
||||
long max = p.getMaxValue(schema);
|
||||
|
@ -29,6 +29,7 @@ import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
||||
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
|
||||
import com.google.gwtorm.protobuf.CodecFactory;
|
||||
import com.google.gwtorm.protobuf.ProtobufCodec;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
@ -428,6 +429,40 @@ public class ChangeField {
|
||||
}
|
||||
};
|
||||
|
||||
/** The number of inserted lines in this change. */
|
||||
public static final FieldDef<ChangeData, Integer> ADDED =
|
||||
new FieldDef.Single<ChangeData, Integer>(
|
||||
ChangeQueryBuilder.FIELD_ADDED, FieldType.INTEGER_RANGE, true) {
|
||||
@Override
|
||||
public Integer get(ChangeData input, FillArgs args)
|
||||
throws OrmException {
|
||||
return input.changedLines().insertions;
|
||||
}
|
||||
};
|
||||
|
||||
/** The number of deleted lines in this change. */
|
||||
public static final FieldDef<ChangeData, Integer> DELETED =
|
||||
new FieldDef.Single<ChangeData, Integer>(
|
||||
ChangeQueryBuilder.FIELD_DELETED, FieldType.INTEGER_RANGE, true) {
|
||||
@Override
|
||||
public Integer get(ChangeData input, FillArgs args)
|
||||
throws OrmException {
|
||||
return input.changedLines().deletions;
|
||||
}
|
||||
};
|
||||
|
||||
/** The total number of modified lines in this change. */
|
||||
public static final FieldDef<ChangeData, Integer> DELTA =
|
||||
new FieldDef.Single<ChangeData, Integer>(
|
||||
ChangeQueryBuilder.FIELD_DELTA, FieldType.INTEGER_RANGE, false) {
|
||||
@Override
|
||||
public Integer get(ChangeData input, FillArgs args)
|
||||
throws OrmException {
|
||||
ChangedLines changedLines = input.changedLines();
|
||||
return changedLines.insertions + changedLines.deletions;
|
||||
}
|
||||
};
|
||||
|
||||
private static <T> List<byte[]> toProtos(ProtobufCodec<T> codec, Collection<T> objs)
|
||||
throws OrmException {
|
||||
List<byte[]> result = Lists.newArrayListWithCapacity(objs.size());
|
||||
|
@ -217,6 +217,32 @@ public class ChangeSchemas {
|
||||
ChangeField.APPROVAL,
|
||||
ChangeField.MERGEABLE);
|
||||
|
||||
static final Schema<ChangeData> V11 = release(
|
||||
ChangeField.LEGACY_ID,
|
||||
ChangeField.ID,
|
||||
ChangeField.STATUS,
|
||||
ChangeField.PROJECT,
|
||||
ChangeField.PROJECTS,
|
||||
ChangeField.REF,
|
||||
ChangeField.TOPIC,
|
||||
ChangeField.UPDATED,
|
||||
ChangeField.FILE_PART,
|
||||
ChangeField.PATH,
|
||||
ChangeField.OWNER,
|
||||
ChangeField.REVIEWER,
|
||||
ChangeField.COMMIT,
|
||||
ChangeField.TR,
|
||||
ChangeField.LABEL,
|
||||
ChangeField.REVIEWED,
|
||||
ChangeField.COMMIT_MESSAGE,
|
||||
ChangeField.COMMENT,
|
||||
ChangeField.CHANGE,
|
||||
ChangeField.APPROVAL,
|
||||
ChangeField.MERGEABLE,
|
||||
ChangeField.ADDED,
|
||||
ChangeField.DELETED,
|
||||
ChangeField.DELTA);
|
||||
|
||||
|
||||
|
||||
private static Schema<ChangeData> release(Collection<FieldDef<ChangeData, ?>> fields) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.index;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
@ -47,6 +48,8 @@ public abstract class FieldDef<I, T> {
|
||||
extends FieldDef<I, Iterable<T>> {
|
||||
Repeatable(String name, FieldType<T> type, boolean stored) {
|
||||
super(name, type, stored);
|
||||
Preconditions.checkArgument(type != FieldType.INTEGER_RANGE,
|
||||
"Range queries against repeated fields are unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,10 @@ public class FieldType<T> {
|
||||
public static final FieldType<Integer> INTEGER =
|
||||
new FieldType<Integer>("INTEGER");
|
||||
|
||||
/** A single-integer-valued field matched using range queries. */
|
||||
public static final FieldType<Integer> INTEGER_RANGE =
|
||||
new FieldType<Integer>("INTEGER_RANGE");
|
||||
|
||||
/** A single integer-valued field. */
|
||||
public static final FieldType<Long> LONG =
|
||||
new FieldType<Long>("LONG");
|
||||
|
@ -0,0 +1,56 @@
|
||||
// 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.index;
|
||||
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.util.RangeUtil;
|
||||
import com.google.gerrit.server.util.RangeUtil.Range;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
public abstract class IntegerRangePredicate<T> extends IndexPredicate<T> {
|
||||
private final Range range;
|
||||
|
||||
protected IntegerRangePredicate(FieldDef<T, Integer> type,
|
||||
String value) throws QueryParseException {
|
||||
super(type, value);
|
||||
range = RangeUtil.getRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
if (range == null) {
|
||||
throw new QueryParseException("Invalid range predicate: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int getValueInt(T object) throws OrmException;
|
||||
|
||||
@Override
|
||||
public boolean match(T object) throws OrmException {
|
||||
int valueInt = getValueInt(object);
|
||||
return valueInt >= range.min && valueInt <= range.max;
|
||||
}
|
||||
|
||||
/** Return the minimum value of this predicate's range, inclusive. */
|
||||
public int getMinimumValue() {
|
||||
return range.min;
|
||||
}
|
||||
|
||||
/** Return the maximum value of this predicate's range, inclusive. */
|
||||
public int getMaximumValue() {
|
||||
return range.max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
|
||||
// TODO: Migrate this to IntegerRangePredicate
|
||||
public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
protected static FieldDef<ChangeData, Timestamp> updatedField(
|
||||
|
@ -0,0 +1,31 @@
|
||||
// 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 com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IntegerRangePredicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
public class AddedPredicate extends IntegerRangePredicate<ChangeData> {
|
||||
AddedPredicate(String value) throws QueryParseException {
|
||||
super(ChangeField.ADDED, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getValueInt(ChangeData changeData) throws OrmException {
|
||||
return changeData.changedLines().insertions;
|
||||
}
|
||||
}
|
@ -75,6 +75,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
// NOTE: As new search operations are added, please keep the
|
||||
// SearchSuggestOracle up to date.
|
||||
|
||||
public static final String FIELD_ADDED = "added";
|
||||
public static final String FIELD_AFTER = "after";
|
||||
public static final String FIELD_AGE = "age";
|
||||
public static final String FIELD_BEFORE = "before";
|
||||
@ -83,6 +84,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
public static final String FIELD_COMMENT = "comment";
|
||||
public static final String FIELD_COMMIT = "commit";
|
||||
public static final String FIELD_CONFLICTS = "conflicts";
|
||||
public static final String FIELD_DELETED = "deleted";
|
||||
public static final String FIELD_DELTA = "delta";
|
||||
public static final String FIELD_DRAFTBY = "draftby";
|
||||
public static final String FIELD_FILE = "file";
|
||||
public static final String FIELD_IS = "is";
|
||||
@ -676,6 +679,30 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
return sortkey_before(sortKey);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> added(String value)
|
||||
throws QueryParseException {
|
||||
return new AddedPredicate(value);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> deleted(String value)
|
||||
throws QueryParseException {
|
||||
return new DeletedPredicate(value);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> size(String value)
|
||||
throws QueryParseException {
|
||||
return delta(value);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> delta(String value)
|
||||
throws QueryParseException {
|
||||
return new DeltaPredicate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<ChangeData> defaultField(String query) {
|
||||
if (query.startsWith("refs/")) {
|
||||
|
@ -0,0 +1,31 @@
|
||||
// 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 com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IntegerRangePredicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
public class DeletedPredicate extends IntegerRangePredicate<ChangeData> {
|
||||
DeletedPredicate(String value) throws QueryParseException {
|
||||
super(ChangeField.DELETED, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getValueInt(ChangeData changeData) throws OrmException {
|
||||
return changeData.changedLines().deletions;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
// 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 com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IntegerRangePredicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
public class DeltaPredicate extends IntegerRangePredicate<ChangeData> {
|
||||
DeltaPredicate(String value) throws QueryParseException {
|
||||
super(ChangeField.DELTA, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getValueInt(ChangeData changeData) throws OrmException {
|
||||
ChangedLines changedLines = changeData.changedLines();
|
||||
return changedLines.insertions + changedLines.deletions;
|
||||
}
|
||||
}
|
@ -24,12 +24,12 @@ import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.query.OrPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.util.LabelVote;
|
||||
import com.google.gerrit.server.util.RangeUtil;
|
||||
import com.google.gerrit.server.util.RangeUtil.Range;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class LabelPredicate extends OrPredicate<ChangeData> {
|
||||
private static final int MAX_LABEL_VALUE = 4;
|
||||
@ -102,43 +102,28 @@ public class LabelPredicate extends OrPredicate<ChangeData> {
|
||||
// Try next format.
|
||||
}
|
||||
|
||||
Range range;
|
||||
if (parsed == null) {
|
||||
Matcher m = Pattern.compile("(>|>=|=|<|<=)([+-]?\\d+)$").matcher(v);
|
||||
if (m.find()) {
|
||||
parsed = new Parsed(v.substring(0, m.start()), m.group(1),
|
||||
value(m.group(2)));
|
||||
} else {
|
||||
parsed = new Parsed(v, "=", 1);
|
||||
range = RangeUtil.getRange(v, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
|
||||
if (range == null) {
|
||||
range = new Range(v, 1, 1);
|
||||
}
|
||||
} else {
|
||||
range = RangeUtil.getRange(
|
||||
parsed.label,
|
||||
parsed.test,
|
||||
parsed.expVal,
|
||||
-MAX_LABEL_VALUE,
|
||||
MAX_LABEL_VALUE);
|
||||
}
|
||||
String prefix = range.prefix;
|
||||
int min = range.min;
|
||||
int max = range.max;
|
||||
|
||||
int min, max;
|
||||
switch (parsed.test) {
|
||||
case "=":
|
||||
default:
|
||||
min = max = parsed.expVal;
|
||||
break;
|
||||
case ">":
|
||||
min = parsed.expVal + 1;
|
||||
max = MAX_LABEL_VALUE;
|
||||
break;
|
||||
case ">=":
|
||||
min = parsed.expVal;
|
||||
max = MAX_LABEL_VALUE;
|
||||
break;
|
||||
case "<":
|
||||
min = -MAX_LABEL_VALUE;
|
||||
max = parsed.expVal - 1;
|
||||
break;
|
||||
case "<=":
|
||||
min = -MAX_LABEL_VALUE;
|
||||
max = parsed.expVal;
|
||||
break;
|
||||
}
|
||||
List<Predicate<ChangeData>> r =
|
||||
Lists.newArrayListWithCapacity(max - min + 1);
|
||||
for (int i = min; i <= max; i++) {
|
||||
r.add(onePredicate(args, parsed.label, i));
|
||||
r.add(onePredicate(args, prefix, i));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@ -152,13 +137,6 @@ public class LabelPredicate extends OrPredicate<ChangeData> {
|
||||
}
|
||||
}
|
||||
|
||||
private static int value(String value) {
|
||||
if (value.startsWith("+")) {
|
||||
value = value.substring(1);
|
||||
}
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
private static Predicate<ChangeData> noLabelQuery(Args args, String label) {
|
||||
List<Predicate<ChangeData>> r =
|
||||
Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
|
||||
|
@ -0,0 +1,119 @@
|
||||
// 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.util;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class RangeUtil {
|
||||
private static final Pattern RANGE_PATTERN =
|
||||
Pattern.compile("(>|>=|=|<|<=|)([+-]?\\d+)$");
|
||||
|
||||
private RangeUtil() {}
|
||||
|
||||
public static class Range {
|
||||
/** The prefix of the query, before the range component. */
|
||||
public final String prefix;
|
||||
|
||||
/** The minimum value specified in the query, inclusive. */
|
||||
public final int min;
|
||||
|
||||
/** The maximum value specified in the query, inclusive. */
|
||||
public final int max;
|
||||
|
||||
public Range(String prefix, int min, int max) {
|
||||
this.prefix = prefix;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the range of values being requested in the given query.
|
||||
*
|
||||
* @param rangeQuery the raw query, e.g. "added:>12345"
|
||||
* @param minValue the minimum possible value for the field, inclusive
|
||||
* @param maxValue the maximum possible value for the field, inclusive
|
||||
* @return the calculated {@link Range}, or null if the query is invalid
|
||||
*/
|
||||
@Nullable
|
||||
public static Range getRange(String rangeQuery, int minValue, int maxValue) {
|
||||
Matcher m = RANGE_PATTERN.matcher(rangeQuery);
|
||||
String prefix;
|
||||
String test;
|
||||
Integer queryInt;
|
||||
if (m.find()) {
|
||||
prefix = rangeQuery.substring(0, m.start());
|
||||
test = m.group(1);
|
||||
queryInt = value(m.group(2));
|
||||
if (queryInt == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRange(prefix, test, queryInt, minValue, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the range of values being requested in the given query.
|
||||
*
|
||||
* @param prefix a prefix string which is copied into the range
|
||||
* @param test the test operator, one of >, >=, =, <, or <=
|
||||
* @param queryInt the integer being queried
|
||||
* @param minValue the minimum possible value for the field, inclusive
|
||||
* @param maxValue the maximum possible value for the field, inclusive
|
||||
* @return the calculated {@link Range}
|
||||
*/
|
||||
public static Range getRange(
|
||||
String prefix, String test, int queryInt, int minValue, int maxValue) {
|
||||
int min, max;
|
||||
switch (test) {
|
||||
case "=":
|
||||
default:
|
||||
min = max = queryInt;
|
||||
break;
|
||||
case ">":
|
||||
min = Ints.saturatedCast(queryInt + 1L);
|
||||
max = maxValue;
|
||||
break;
|
||||
case ">=":
|
||||
min = queryInt;
|
||||
max = maxValue;
|
||||
break;
|
||||
case "<":
|
||||
min = minValue;
|
||||
max = Ints.saturatedCast(queryInt - 1L);
|
||||
break;
|
||||
case "<=":
|
||||
min = minValue;
|
||||
max = queryInt;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Range(prefix, min, max);
|
||||
}
|
||||
|
||||
private static Integer value(String value) {
|
||||
if (value.startsWith("+")) {
|
||||
value = value.substring(1);
|
||||
}
|
||||
return Ints.tryParse(value);
|
||||
}
|
||||
}
|
@ -813,6 +813,44 @@ public abstract class AbstractQueryChangesTest {
|
||||
assertResultEquals(change1, results.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bySize() throws Exception {
|
||||
TestRepository<InMemoryRepository> repo = createProject("repo");
|
||||
|
||||
// added = 3, deleted = 0, delta = 3
|
||||
RevCommit commit1 = repo.parseBody(
|
||||
repo.commit().add("file1", "foo\n\foo\nfoo").create());
|
||||
// added = 0, deleted = 2, delta = 2
|
||||
RevCommit commit2 = repo.parseBody(
|
||||
repo.commit().parent(commit1).add("file1", "foo").create());
|
||||
|
||||
Change change1 = newChange(repo, commit1, null, null, null).insert();
|
||||
Change change2 = newChange(repo, commit2, null, null, null).insert();
|
||||
|
||||
assertTrue(query("added:>4").isEmpty());
|
||||
assertResultEquals(change1, queryOne("added:3"));
|
||||
assertResultEquals(change1, queryOne("added:>2"));
|
||||
assertResultEquals(change1, queryOne("added:>=3"));
|
||||
assertResultEquals(change2, queryOne("added:<1"));
|
||||
assertResultEquals(change2, queryOne("added:<=0"));
|
||||
|
||||
assertTrue(query("deleted:>3").isEmpty());
|
||||
assertResultEquals(change2, queryOne("deleted:2"));
|
||||
assertResultEquals(change2, queryOne("deleted:>1"));
|
||||
assertResultEquals(change2, queryOne("deleted:>=2"));
|
||||
assertResultEquals(change1, queryOne("deleted:<1"));
|
||||
assertResultEquals(change1, queryOne("deleted:<=0"));
|
||||
|
||||
for (String str : Lists.newArrayList("delta", "size")) {
|
||||
assertTrue(query(str + ":<2").isEmpty());
|
||||
assertResultEquals(change1, queryOne(str + ":3"));
|
||||
assertResultEquals(change1, queryOne(str + ":>2"));
|
||||
assertResultEquals(change1, queryOne(str + ":>=3"));
|
||||
assertResultEquals(change2, queryOne(str + ":<3"));
|
||||
assertResultEquals(change2, queryOne(str + ":<=2"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byDefault() throws Exception {
|
||||
TestRepository<InMemoryRepository> repo = createProject("repo");
|
||||
|
@ -54,6 +54,11 @@ public class LuceneQueryChangesV7Test extends AbstractQueryChangesTest {
|
||||
@Override
|
||||
@Test
|
||||
public void byDefault() {}
|
||||
|
||||
@Ignore
|
||||
@Override
|
||||
@Test
|
||||
public void bySize() {}
|
||||
// End tests for features not supported in V7.
|
||||
|
||||
@Test
|
||||
|
@ -1,11 +1,11 @@
|
||||
include_defs('//lib/maven.defs')
|
||||
|
||||
VERSION = '4.7.0'
|
||||
VERSION = '4.8.1'
|
||||
|
||||
maven_jar(
|
||||
name = 'core',
|
||||
id = 'org.apache.lucene:lucene-core:' + VERSION,
|
||||
sha1 = '12d2b92d15158ac0d7b2864f537403acb4d7f69e',
|
||||
sha1 = 'a549eef6316a2c38d4cda932be809107deeaf8a7',
|
||||
license = 'Apache2.0',
|
||||
exclude = [
|
||||
'META-INF/LICENSE.txt',
|
||||
@ -16,7 +16,7 @@ maven_jar(
|
||||
maven_jar(
|
||||
name = 'analyzers-common',
|
||||
id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
|
||||
sha1 = '399fa6b0d750c8e5c9e4ae73e6407c8b3ed4e8c1',
|
||||
sha1 = '6e3731524351c83cd21022a23bee5e87f0575555',
|
||||
license = 'Apache2.0',
|
||||
exclude = [
|
||||
'META-INF/LICENSE.txt',
|
||||
@ -27,6 +27,6 @@ maven_jar(
|
||||
maven_jar(
|
||||
name = 'query-parser',
|
||||
id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
|
||||
sha1 = 'f78a804de1582c511224d214c2d9c82ce48379e7',
|
||||
sha1 = 'f3e105d74137906fdeb2c7bc4dd68c08564778f9',
|
||||
license = 'Apache2.0',
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user