ChangeStatusPredicate: Support querying by prefix

Change-Id: Ideca3171fb215c7a37c0af5f69492d42e5fa4d3d
This commit is contained in:
Dave Borowitz
2014-09-07 19:01:17 -07:00
parent 7ecea26d58
commit 3eda8807a0
3 changed files with 57 additions and 14 deletions

View File

@@ -79,7 +79,7 @@ public class ChangeField {
@Override
public String get(ChangeData input, FillArgs args)
throws OrmException {
return ChangeStatusPredicate.VALUES.get(
return ChangeStatusPredicate.canonicalize(
input.change().getStatus());
}
};

View File

@@ -14,9 +14,6 @@
package com.google.gerrit.server.query.change;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableBiMap;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.server.index.ChangeField;
@@ -26,6 +23,9 @@ import com.google.gwtorm.server.OrmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
/**
* Predicate for a {@link Status}.
@@ -33,17 +33,21 @@ import java.util.List;
* The actual name of this operator can differ, it usually comes as {@code
* status:} but may also be {@code is:} to help do-what-i-meanery for end-users
* searching for changes. Either operator name has the same meaning.
* <p>
* Status names are looked up by prefix case-insensitively.
*/
public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
public static final ImmutableBiMap<Change.Status, String> VALUES;
private static final TreeMap<String, Change.Status> VALUES;
static {
ImmutableBiMap.Builder<Change.Status, String> values =
ImmutableBiMap.builder();
VALUES = new TreeMap<>();
for (Change.Status s : Change.Status.values()) {
values.put(s, s.name().toLowerCase());
VALUES.put(canonicalize(s), s);
}
VALUES = values.build();
}
public static String canonicalize(Change.Status status) {
return status.name().toLowerCase();
}
public static Predicate<ChangeData> parse(String value) {
@@ -51,11 +55,17 @@ public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
return open();
} else if ("closed".equalsIgnoreCase(value)) {
return closed();
} else {
Change.Status status = VALUES.inverse().get(value.toLowerCase());
checkArgument(status != null, "invalid change status: %s", value);
return new ChangeStatusPredicate(status);
}
String lower = value.toLowerCase();
NavigableMap<String, Change.Status> head = VALUES.tailMap(lower, true);
if (!head.isEmpty()) {
// Assume no statuses share a common prefix so we can only walk one entry.
Map.Entry<String, Change.Status> e = head.entrySet().iterator().next();
if (e.getKey().startsWith(lower)) {
return new ChangeStatusPredicate(e.getValue());
}
}
throw new IllegalArgumentException("invalid change status: " + value);
}
public static Predicate<ChangeData> open() {
@@ -81,7 +91,7 @@ public final class ChangeStatusPredicate extends IndexPredicate<ChangeData> {
private final Change.Status status;
ChangeStatusPredicate(Change.Status status) {
super(ChangeField.STATUS, VALUES.get(status));
super(ChangeField.STATUS, canonicalize(status));
this.status = status;
}

View File

@@ -20,6 +20,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
@@ -29,6 +30,7 @@ import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
@@ -262,6 +264,28 @@ public abstract class AbstractQueryChangesTest {
assertResultEquals(change1, results.get(1));
}
@Test
public void byStatusPrefix() throws Exception {
TestRepository<InMemoryRepository> repo = createProject("repo");
ChangeInserter ins1 = newChange(repo, null, null, null, null);
Change change1 = ins1.getChange();
change1.setStatus(Change.Status.NEW);
ins1.insert();
ChangeInserter ins2 = newChange(repo, null, null, null, null);
Change change2 = ins2.getChange();
change2.setStatus(Change.Status.MERGED);
ins2.insert();
assertResultEquals(change1, queryOne("status:n"));
assertResultEquals(change1, queryOne("status:ne"));
assertResultEquals(change1, queryOne("status:new"));
assertResultEquals(change1, queryOne("status:N"));
assertResultEquals(change1, queryOne("status:nE"));
assertResultEquals(change1, queryOne("status:neW"));
assertBadQuery("status:nx");
assertBadQuery("status:newx");
}
@Test
public void byCommit() throws Exception {
TestRepository<InMemoryRepository> repo = createProject("repo");
@@ -944,6 +968,15 @@ public abstract class AbstractQueryChangesTest {
assertEquals(message, expected.getId().get(), actual._number);
}
protected void assertBadQuery(Object query) throws Exception {
try {
query(query);
fail("expected BadRequestException for query: " + query);
} catch (BadRequestException e) {
// Expected.
}
}
protected TestRepository<InMemoryRepository> createProject(String name)
throws Exception {
CreateProject create = projectFactory.create(name);