Reduce FieldDef boilerplate with a builder

Inspired by Han-Wen's entirely reasonable annotation of the "stored"
argument to the FieldDef constructor[1], I thought this smells like it
could use the builder pattern to improve readability. Passing a
functional interface to the build method also means we can use lambdas,
for an overall significant boilerplate reduction: many FieldDefs can now
be one-liners.

[1] https://gerrit-review.googlesource.com/c/98014/9/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java#606

Change-Id: I845a7d9a28dda7f3e0cc0c049b94372118eb4480
This commit is contained in:
Dave Borowitz 2017-02-21 13:44:46 -05:00
parent 5822cb3433
commit da0d9a3c5f
8 changed files with 388 additions and 634 deletions

View File

@ -14,6 +14,9 @@
package com.google.gerrit.elasticsearch;
import static com.google.gerrit.server.index.change.ChangeField.APPROVAL_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.PATCH_SET_CODEC;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -39,9 +42,6 @@ import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.QueryOptions;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeField.ChangeProtoField;
import com.google.gerrit.server.index.change.ChangeField.PatchSetApprovalProtoField;
import com.google.gerrit.server.index.change.ChangeField.PatchSetProtoField;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.project.SubmitRuleOptions;
@ -280,16 +280,15 @@ class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
ChangeData cd =
changeDataFactory.create(
db.get(), ChangeProtoField.CODEC.decode(Base64.decodeBase64(c.getAsString())));
db.get(), CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
// Patch sets.
cd.setPatchSets(
decodeProtos(source, ChangeField.PATCH_SET.getName(), PatchSetProtoField.CODEC));
cd.setPatchSets(decodeProtos(source, ChangeField.PATCH_SET.getName(), PATCH_SET_CODEC));
// Approvals.
if (source.get(ChangeField.APPROVAL.getName()) != null) {
cd.setCurrentApprovals(
decodeProtos(source, ChangeField.APPROVAL.getName(), PatchSetApprovalProtoField.CODEC));
decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
} else if (fields.contains(ChangeField.APPROVAL.getName())) {
cd.setCurrentApprovals(Collections.emptyList());
}

View File

@ -17,7 +17,10 @@ package com.google.gerrit.lucene;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.lucene.AbstractLuceneIndex.sortFieldName;
import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
import static com.google.gerrit.server.index.change.ChangeField.APPROVAL_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.LEGACY_ID;
import static com.google.gerrit.server.index.change.ChangeField.PATCH_SET_CODEC;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
@ -47,9 +50,6 @@ import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.QueryOptions;
import com.google.gerrit.server.index.Schema;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeField.ChangeProtoField;
import com.google.gerrit.server.index.change.ChangeField.PatchSetApprovalProtoField;
import com.google.gerrit.server.index.change.ChangeField.PatchSetProtoField;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.project.SubmitRuleOptions;
@ -421,7 +421,7 @@ public class LuceneChangeIndex implements ChangeIndex {
BytesRef proto = cb.binaryValue();
cd =
changeDataFactory.create(
db.get(), ChangeProtoField.CODEC.decode(proto.bytes, proto.offset, proto.length));
db.get(), CHANGE_CODEC.decode(proto.bytes, proto.offset, proto.length));
} else {
IndexableField f = Iterables.getFirst(doc.get(idFieldName), null);
Change.Id id = new Change.Id(f.numericValue().intValue());
@ -475,7 +475,7 @@ public class LuceneChangeIndex implements ChangeIndex {
}
private void decodePatchSets(ListMultimap<String, IndexableField> doc, ChangeData cd) {
List<PatchSet> patchSets = decodeProtos(doc, PATCH_SET_FIELD, PatchSetProtoField.CODEC);
List<PatchSet> patchSets = decodeProtos(doc, PATCH_SET_FIELD, PATCH_SET_CODEC);
if (!patchSets.isEmpty()) {
// Will be an empty list for schemas prior to when this field was stored;
// this cannot be valid since a change needs at least one patch set.
@ -484,7 +484,7 @@ public class LuceneChangeIndex implements ChangeIndex {
}
private void decodeApprovals(ListMultimap<String, IndexableField> doc, ChangeData cd) {
cd.setCurrentApprovals(decodeProtos(doc, APPROVAL_FIELD, PatchSetApprovalProtoField.CODEC));
cd.setCurrentApprovals(decodeProtos(doc, APPROVAL_FIELD, APPROVAL_CODEC));
}
private void decodeChangedLines(ListMultimap<String, IndexableField> doc, ChangeData cd) {

View File

@ -15,14 +15,16 @@
package com.google.gerrit.server.index;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.sql.Timestamp;
import org.eclipse.jgit.lib.Config;
/**
@ -32,31 +34,43 @@ import org.eclipse.jgit.lib.Config;
* @param <T> type that should be extracted from the input object when converting to an index
* document.
*/
public abstract class FieldDef<I, T> {
/** Definition of a single (non-repeatable) field. */
public abstract static class Single<I, T> extends FieldDef<I, T> {
protected Single(String name, FieldType<T> type, boolean stored) {
super(name, type, stored);
}
@Override
public final boolean isRepeatable() {
return false;
}
public final class FieldDef<I, T> {
public static FieldDef.Builder<String> exact(String name) {
return new FieldDef.Builder<>(FieldType.EXACT, name);
}
/** Definition of a repeatable field. */
public abstract static class Repeatable<I, T> extends FieldDef<I, Iterable<T>> {
protected 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");
}
public static FieldDef.Builder<String> fullText(String name) {
return new FieldDef.Builder<>(FieldType.FULL_TEXT, name);
}
@Override
public final boolean isRepeatable() {
return true;
}
public static FieldDef.Builder<Integer> intRange(String name) {
return new FieldDef.Builder<>(FieldType.INTEGER_RANGE, name).stored();
}
public static FieldDef.Builder<Integer> integer(String name) {
return new FieldDef.Builder<>(FieldType.INTEGER, name);
}
public static FieldDef.Builder<String> prefix(String name) {
return new FieldDef.Builder<>(FieldType.PREFIX, name);
}
public static FieldDef.Builder<byte[]> storedOnly(String name) {
return new FieldDef.Builder<>(FieldType.STORED_ONLY, name).stored();
}
public static FieldDef.Builder<Timestamp> timestamp(String name) {
return new FieldDef.Builder<>(FieldType.TIMESTAMP, name);
}
@FunctionalInterface
public interface Getter<I, T> {
T get(I input) throws OrmException, IOException;
}
@FunctionalInterface
public interface GetterWithArgs<I, T> {
T get(I input, FillArgs args) throws OrmException, IOException;
}
/** Arguments needed to fill in missing data in the input object. */
@ -74,34 +88,78 @@ public abstract class FieldDef<I, T> {
}
}
public static class Builder<T> {
private final FieldType<T> type;
private final String name;
private boolean stored;
public Builder(FieldType<T> type, String name) {
this.type = checkNotNull(type);
this.name = checkNotNull(name);
}
public Builder<T> stored() {
this.stored = true;
return this;
}
public <I> FieldDef<I, T> build(Getter<I, T> getter) {
return build((in, a) -> getter.get(in));
}
public <I> FieldDef<I, T> build(GetterWithArgs<I, T> getter) {
return new FieldDef<>(name, type, stored, false, getter);
}
public <I> FieldDef<I, Iterable<T>> buildRepeatable(Getter<I, Iterable<T>> getter) {
return buildRepeatable((in, a) -> getter.get(in));
}
public <I> FieldDef<I, Iterable<T>> buildRepeatable(GetterWithArgs<I, Iterable<T>> getter) {
return new FieldDef<>(name, type, stored, true, getter);
}
}
private final String name;
private final FieldType<?> type;
private final boolean stored;
private final boolean repeatable;
private final GetterWithArgs<I, T> getter;
private FieldDef(String name, FieldType<?> type, boolean stored) {
private FieldDef(
String name,
FieldType<?> type,
boolean stored,
boolean repeatable,
GetterWithArgs<I, T> getter) {
checkArgument(
!(repeatable && type == FieldType.INTEGER_RANGE),
"Range queries against repeated fields are unsupported");
this.name = checkName(name);
this.type = type;
this.type = checkNotNull(type);
this.stored = stored;
this.repeatable = repeatable;
this.getter = checkNotNull(getter);
}
private static String checkName(String name) {
CharMatcher m = CharMatcher.anyOf("abcdefghijklmnopqrstuvwxyz0123456789_");
checkArgument(m.matchesAllOf(name), "illegal field name: %s", name);
checkArgument(name != null && m.matchesAllOf(name), "illegal field name: %s", name);
return name;
}
/** @return name of the field. */
public final String getName() {
public String getName() {
return name;
}
/** @return type of the field; for repeatable fields, the inner type, not the iterable type. */
public final FieldType<?> getType() {
public FieldType<?> getType() {
return type;
}
/** @return whether the field should be stored in the index. */
public final boolean isStored() {
public boolean isStored() {
return stored;
}
@ -113,8 +171,16 @@ public abstract class FieldDef<I, T> {
* @return the field value(s) to index.
* @throws OrmException
*/
public abstract T get(I input, FillArgs args) throws OrmException;
public T get(I input, FillArgs args) throws OrmException {
try {
return getter.get(input, args);
} catch (IOException e) {
throw new OrmException(e);
}
}
/** @return whether the field is repeatable. */
public abstract boolean isRepeatable();
public boolean isRepeatable() {
return repeatable;
}
}

View File

@ -14,6 +14,11 @@
package com.google.gerrit.server.index.account;
import static com.google.gerrit.server.index.FieldDef.exact;
import static com.google.gerrit.server.index.FieldDef.integer;
import static com.google.gerrit.server.index.FieldDef.prefix;
import static com.google.gerrit.server.index.FieldDef.timestamp;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
@ -21,7 +26,6 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.ExternalId;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.SchemaUtil;
import java.sql.Timestamp;
import java.util.Collections;
@ -31,94 +35,60 @@ import java.util.Set;
/** Secondary index schemas for accounts. */
public class AccountField {
public static final FieldDef<AccountState, Integer> ID =
new FieldDef.Single<AccountState, Integer>("id", FieldType.INTEGER, true) {
@Override
public Integer get(AccountState input, FillArgs args) {
return input.getAccount().getId().get();
}
};
integer("id").stored().build(a -> a.getAccount().getId().get());
public static final FieldDef<AccountState, Iterable<String>> EXTERNAL_ID =
new FieldDef.Repeatable<AccountState, String>("external_id", FieldType.EXACT, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
return Iterables.transform(input.getExternalIds(), id -> id.key().get());
}
};
exact("external_id")
.buildRepeatable(a -> Iterables.transform(a.getExternalIds(), id -> id.key().get()));
/** Fuzzy prefix match on name and email parts. */
public static final FieldDef<AccountState, Iterable<String>> NAME_PART =
new FieldDef.Repeatable<AccountState, String>("name", FieldType.PREFIX, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
String fullName = input.getAccount().getFullName();
Set<String> parts =
SchemaUtil.getNameParts(
fullName, Iterables.transform(input.getExternalIds(), ExternalId::email));
prefix("name")
.buildRepeatable(
a -> {
String fullName = a.getAccount().getFullName();
Set<String> parts =
SchemaUtil.getNameParts(
fullName, Iterables.transform(a.getExternalIds(), ExternalId::email));
// Additional values not currently added by getPersonParts.
// TODO(dborowitz): Move to getPersonParts and remove this hack.
if (fullName != null) {
parts.add(fullName.toLowerCase(Locale.US));
}
return parts;
}
};
// Additional values not currently added by getPersonParts.
// TODO(dborowitz): Move to getPersonParts and remove this hack.
if (fullName != null) {
parts.add(fullName.toLowerCase(Locale.US));
}
return parts;
});
public static final FieldDef<AccountState, String> FULL_NAME =
new FieldDef.Single<AccountState, String>("full_name", FieldType.EXACT, false) {
@Override
public String get(AccountState input, FillArgs args) {
return input.getAccount().getFullName();
}
};
exact("full_name").build(a -> a.getAccount().getFullName());
public static final FieldDef<AccountState, String> ACTIVE =
new FieldDef.Single<AccountState, String>("inactive", FieldType.EXACT, false) {
@Override
public String get(AccountState input, FillArgs args) {
return input.getAccount().isActive() ? "1" : "0";
}
};
exact("inactive").build(a -> a.getAccount().isActive() ? "1" : "0");
public static final FieldDef<AccountState, Iterable<String>> EMAIL =
new FieldDef.Repeatable<AccountState, String>("email", FieldType.PREFIX, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
return FluentIterable.from(input.getExternalIds())
.transform(ExternalId::email)
.append(Collections.singleton(input.getAccount().getPreferredEmail()))
.filter(Predicates.notNull())
.transform(String::toLowerCase)
.toSet();
}
};
prefix("email")
.buildRepeatable(
a ->
FluentIterable.from(a.getExternalIds())
.transform(ExternalId::email)
.append(Collections.singleton(a.getAccount().getPreferredEmail()))
.filter(Predicates.notNull())
.transform(String::toLowerCase)
.toSet());
public static final FieldDef<AccountState, Timestamp> REGISTERED =
new FieldDef.Single<AccountState, Timestamp>("registered", FieldType.TIMESTAMP, false) {
@Override
public Timestamp get(AccountState input, FillArgs args) {
return input.getAccount().getRegisteredOn();
}
};
timestamp("registered").build(a -> a.getAccount().getRegisteredOn());
public static final FieldDef<AccountState, String> USERNAME =
new FieldDef.Single<AccountState, String>("username", FieldType.EXACT, false) {
@Override
public String get(AccountState input, FillArgs args) {
return Strings.nullToEmpty(input.getUserName()).toLowerCase();
}
};
exact("username").build(a -> Strings.nullToEmpty(a.getUserName()).toLowerCase());
public static final FieldDef<AccountState, Iterable<String>> WATCHED_PROJECT =
new FieldDef.Repeatable<AccountState, String>("watchedproject", FieldType.EXACT, false) {
@Override
public Iterable<String> get(AccountState input, FillArgs args) {
return FluentIterable.from(input.getProjectWatches().keySet())
.transform(k -> k.project().get())
.toSet();
}
};
exact("watchedproject")
.buildRepeatable(
a ->
FluentIterable.from(a.getProjectWatches().keySet())
.transform(k -> k.project().get())
.toSet());
private AccountField() {}
}

View File

@ -16,6 +16,13 @@ package com.google.gerrit.server.index.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.index.FieldDef.exact;
import static com.google.gerrit.server.index.FieldDef.fullText;
import static com.google.gerrit.server.index.FieldDef.intRange;
import static com.google.gerrit.server.index.FieldDef.integer;
import static com.google.gerrit.server.index.FieldDef.prefix;
import static com.google.gerrit.server.index.FieldDef.storedOnly;
import static com.google.gerrit.server.index.FieldDef.timestamp;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@ -42,7 +49,7 @@ import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gerrit.server.index.SchemaUtil;
import com.google.gerrit.server.index.change.StalenessChecker.RefState;
import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
@ -66,8 +73,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.eclipse.jgit.revwalk.FooterLine;
/**
@ -87,138 +94,52 @@ public class ChangeField {
/** Legacy change ID. */
public static final FieldDef<ChangeData, Integer> LEGACY_ID =
new FieldDef.Single<ChangeData, Integer>("legacy_id", FieldType.INTEGER, true) {
@Override
public Integer get(ChangeData input, FillArgs args) {
return input.getId().get();
}
};
integer("legacy_id").stored().build(cd -> cd.getId().get());
/** Newer style Change-Id key. */
public static final FieldDef<ChangeData, String> ID =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_CHANGE_ID, FieldType.PREFIX, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getKey().get();
}
};
prefix(ChangeQueryBuilder.FIELD_CHANGE_ID).build(changeGetter(c -> c.getKey().get()));
/** Change status string, in the same format as {@code status:}. */
public static final FieldDef<ChangeData, String> STATUS =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_STATUS, FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return ChangeStatusPredicate.canonicalize(c.getStatus());
}
};
exact(ChangeQueryBuilder.FIELD_STATUS)
.build(changeGetter(c -> ChangeStatusPredicate.canonicalize(c.getStatus())));
/** Project containing the change. */
public static final FieldDef<ChangeData, String> PROJECT =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_PROJECT, FieldType.EXACT, true) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getProject().get();
}
};
exact(ChangeQueryBuilder.FIELD_PROJECT)
.stored()
.build(changeGetter(c -> c.getProject().get()));
/** Project containing the change, as a prefix field. */
public static final FieldDef<ChangeData, String> PROJECTS =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_PROJECTS, FieldType.PREFIX, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getProject().get();
}
};
prefix(ChangeQueryBuilder.FIELD_PROJECTS).build(changeGetter(c -> c.getProject().get()));
/** Reference (aka branch) the change will submit onto. */
public static final FieldDef<ChangeData, String> REF =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_REF, FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getDest().get();
}
};
exact(ChangeQueryBuilder.FIELD_REF).build(changeGetter(c -> c.getDest().get()));
/** Topic, a short annotation on the branch. */
public static final FieldDef<ChangeData, String> EXACT_TOPIC =
new FieldDef.Single<ChangeData, String>("topic4", FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
return getTopic(input);
}
};
exact("topic4").build(ChangeField::getTopic);
/** Topic, a short annotation on the branch. */
public static final FieldDef<ChangeData, String> FUZZY_TOPIC =
new FieldDef.Single<ChangeData, String>("topic5", FieldType.FULL_TEXT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
return getTopic(input);
}
};
fullText("topic5").build(ChangeField::getTopic);
/** Submission id assigned by MergeOp. */
public static final FieldDef<ChangeData, String> SUBMISSIONID =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_SUBMISSIONID, FieldType.EXACT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getSubmissionId();
}
};
exact(ChangeQueryBuilder.FIELD_SUBMISSIONID).build(changeGetter(Change::getSubmissionId));
/** Last update time since January 1, 1970. */
public static final FieldDef<ChangeData, Timestamp> UPDATED =
new FieldDef.Single<ChangeData, Timestamp>("updated2", FieldType.TIMESTAMP, true) {
@Override
public Timestamp get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getLastUpdatedOn();
}
};
timestamp("updated2").stored().build(changeGetter(Change::getLastUpdatedOn));
/** List of full file paths modified in the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> PATH =
new FieldDef.Repeatable<ChangeData, String>(
// Named for backwards compatibility.
ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return firstNonNull(input.currentFilePaths(), ImmutableList.<String>of());
}
};
// Named for backwards compatibility.
exact(ChangeQueryBuilder.FIELD_FILE)
.buildRepeatable(cd -> firstNonNull(cd.currentFilePaths(), ImmutableList.of()));
public static Set<String> getFileParts(ChangeData cd) throws OrmException {
List<String> paths = cd.currentFilePaths();
@ -237,66 +158,31 @@ public class ChangeField {
/** Hashtags tied to a change */
public static final FieldDef<ChangeData, Iterable<String>> HASHTAG =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_HASHTAG, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return input.hashtags().stream().map(String::toLowerCase).collect(toSet());
}
};
exact(ChangeQueryBuilder.FIELD_HASHTAG)
.buildRepeatable(cd -> cd.hashtags().stream().map(String::toLowerCase).collect(toSet()));
/** Hashtags with original case. */
public static final FieldDef<ChangeData, Iterable<byte[]>> HASHTAG_CASE_AWARE =
new FieldDef.Repeatable<ChangeData, byte[]>("_hashtag", FieldType.STORED_ONLY, true) {
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
return input.hashtags().stream().map(t -> t.getBytes(UTF_8)).collect(toSet());
}
};
storedOnly("_hashtag")
.buildRepeatable(
cd -> cd.hashtags().stream().map(t -> t.getBytes(UTF_8)).collect(toSet()));
/** Components of each file path modified in the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> FILE_PART =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_FILEPART, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getFileParts(input);
}
};
exact(ChangeQueryBuilder.FIELD_FILEPART).buildRepeatable(ChangeField::getFileParts);
/** Owner/creator of the change. */
public static final FieldDef<ChangeData, Integer> OWNER =
new FieldDef.Single<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_OWNER, FieldType.INTEGER, false) {
@Override
public Integer get(ChangeData input, FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return c.getOwner().get();
}
};
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
/** The user assigned to the change. */
public static final FieldDef<ChangeData, Integer> ASSIGNEE =
new FieldDef.Single<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_ASSIGNEE, FieldType.INTEGER, false) {
@Override
public Integer get(ChangeData input, FillArgs args) throws OrmException {
Account.Id id = input.change().getAssignee();
return id != null ? id.get() : NO_ASSIGNEE;
}
};
integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
.build(changeGetter(c -> c.getAssignee() != null ? c.getAssignee().get() : NO_ASSIGNEE));
/** Reviewer(s) associated with the change. */
public static final FieldDef<ChangeData, Iterable<String>> REVIEWER =
new FieldDef.Repeatable<ChangeData, String>("reviewer2", FieldType.EXACT, true) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getReviewerFieldValues(input.reviewers());
}
};
exact("reviewer2").stored().buildRepeatable(cd -> getReviewerFieldValues(cd.reviewers()));
@VisibleForTesting
static List<String> getReviewerFieldValues(ReviewerSet reviewers) {
@ -336,23 +222,11 @@ public class ChangeField {
/** Commit ID of any patch set on the change, using prefix match. */
public static final FieldDef<ChangeData, Iterable<String>> COMMIT =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_COMMIT, FieldType.PREFIX, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getRevisions(input);
}
};
prefix(ChangeQueryBuilder.FIELD_COMMIT).buildRepeatable(ChangeField::getRevisions);
/** Commit ID of any patch set on the change, using exact match. */
public static final FieldDef<ChangeData, Iterable<String>> EXACT_COMMIT =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_EXACTCOMMIT, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getRevisions(input);
}
};
exact(ChangeQueryBuilder.FIELD_EXACTCOMMIT).buildRepeatable(ChangeField::getRevisions);
private static Set<String> getRevisions(ChangeData cd) throws OrmException {
Set<String> revisions = new HashSet<>();
@ -366,49 +240,32 @@ public class ChangeField {
/** Tracking id extracted from a footer. */
public static final FieldDef<ChangeData, Iterable<String>> TR =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_TR, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
try {
List<FooterLine> footers = input.commitFooters();
if (footers == null) {
return ImmutableSet.of();
}
return Sets.newHashSet(args.trackingFooters.extract(footers).values());
} catch (IOException e) {
throw new OrmException(e);
}
}
};
exact(ChangeQueryBuilder.FIELD_TR)
.buildRepeatable(
(ChangeData cd, FillArgs a) -> {
List<FooterLine> footers = cd.commitFooters();
if (footers == null) {
return ImmutableSet.of();
}
return Sets.newHashSet(a.trackingFooters.extract(footers).values());
});
/** List of labels on the current patch set. */
@Deprecated
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 {
return getLabels(input, false);
}
};
exact(ChangeQueryBuilder.FIELD_LABEL).buildRepeatable(cd -> getLabels(cd, false));
/** List of labels on the current patch set including change owner votes. */
public static final FieldDef<ChangeData, Iterable<String>> LABEL2 =
new FieldDef.Repeatable<ChangeData, String>("label2", FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getLabels(input, true);
}
};
exact("label2").buildRepeatable(cd -> getLabels(cd, true));
private static Iterable<String> getLabels(ChangeData input, boolean owners) throws OrmException {
private static Iterable<String> getLabels(ChangeData cd, boolean owners) throws OrmException {
Set<String> allApprovals = new HashSet<>();
Set<String> distinctApprovals = new HashSet<>();
for (PatchSetApproval a : input.currentApprovals()) {
for (PatchSetApproval a : cd.currentApprovals()) {
if (a.getValue() != 0 && !a.isLegacySubmit()) {
allApprovals.add(formatLabel(a.getLabel(), a.getValue(), a.getAccountId()));
if (owners && input.change().getOwner().equals(a.getAccountId())) {
if (owners && cd.change().getOwner().equals(a.getAccountId())) {
allApprovals.add(
formatLabel(a.getLabel(), a.getValue(), ChangeQueryBuilder.OWNER_ACCOUNT_ID));
}
@ -419,20 +276,12 @@ public class ChangeField {
return allApprovals;
}
public static Set<String> getAuthorParts(ChangeData cd) throws OrmException {
try {
return SchemaUtil.getPersonParts(cd.getAuthor());
} catch (IOException e) {
throw new OrmException(e);
}
public static Set<String> getAuthorParts(ChangeData cd) throws OrmException, IOException {
return SchemaUtil.getPersonParts(cd.getAuthor());
}
public static Set<String> getCommitterParts(ChangeData cd) throws OrmException {
try {
return SchemaUtil.getPersonParts(cd.getCommitter());
} catch (IOException e) {
throw new OrmException(e);
}
public static Set<String> getCommitterParts(ChangeData cd) throws OrmException, IOException {
return SchemaUtil.getPersonParts(cd.getCommitter());
}
/**
@ -440,63 +289,28 @@ public class ChangeField {
* set.
*/
public static final FieldDef<ChangeData, Iterable<String>> AUTHOR =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_AUTHOR, FieldType.FULL_TEXT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getAuthorParts(input);
}
};
fullText(ChangeQueryBuilder.FIELD_AUTHOR).buildRepeatable(ChangeField::getAuthorParts);
/**
* The exact email address, or any part of the committer name or email address, in the current
* patch set.
*/
public static final FieldDef<ChangeData, Iterable<String>> COMMITTER =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_COMMITTER, FieldType.FULL_TEXT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return getCommitterParts(input);
}
};
fullText(ChangeQueryBuilder.FIELD_COMMITTER).buildRepeatable(ChangeField::getCommitterParts);
public static class ChangeProtoField extends FieldDef.Single<ChangeData, byte[]> {
public static final ProtobufCodec<Change> CODEC = CodecFactory.encoder(Change.class);
private ChangeProtoField() {
super("_change", FieldType.STORED_ONLY, true);
}
@Override
public byte[] get(ChangeData input, FieldDef.FillArgs args) throws OrmException {
Change c = input.change();
if (c == null) {
return null;
}
return CODEC.encodeToByteArray(c);
}
}
public static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
/** Serialized change object, used for pre-populating results. */
public static final ChangeProtoField CHANGE = new ChangeProtoField();
public static final FieldDef<ChangeData, byte[]> CHANGE =
storedOnly("_change").build(changeGetter(CHANGE_CODEC::encodeToByteArray));
public static class PatchSetApprovalProtoField extends FieldDef.Repeatable<ChangeData, byte[]> {
public static final ProtobufCodec<PatchSetApproval> CODEC =
CodecFactory.encoder(PatchSetApproval.class);
private PatchSetApprovalProtoField() {
super("_approval", FieldType.STORED_ONLY, true);
}
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
return toProtos(CODEC, input.currentApprovals());
}
}
public static final ProtobufCodec<PatchSetApproval> APPROVAL_CODEC =
CodecFactory.encoder(PatchSetApproval.class);
/** Serialized approvals for the current patch set, used for pre-populating results. */
public static final PatchSetApprovalProtoField APPROVAL = new PatchSetApprovalProtoField();
public static final FieldDef<ChangeData, Iterable<byte[]>> APPROVAL =
storedOnly("_approval")
.buildRepeatable(cd -> toProtos(APPROVAL_CODEC, cd.currentApprovals()));
public static String formatLabel(String label, int value) {
return formatLabel(label, value, null);
@ -518,181 +332,123 @@ public class ChangeField {
/** Commit message of the current patch set. */
public static final FieldDef<ChangeData, String> COMMIT_MESSAGE =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_MESSAGE, FieldType.FULL_TEXT, false) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
try {
return input.commitMessage();
} catch (IOException e) {
throw new OrmException(e);
}
}
};
fullText(ChangeQueryBuilder.FIELD_MESSAGE).build(ChangeData::commitMessage);
/** Summary or inline comment. */
public static final FieldDef<ChangeData, Iterable<String>> COMMENT =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_COMMENT, FieldType.FULL_TEXT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
Set<String> r = new HashSet<>();
for (Comment c : input.publishedComments()) {
r.add(c.message);
}
for (ChangeMessage m : input.messages()) {
r.add(m.getMessage());
}
return r;
}
};
fullText(ChangeQueryBuilder.FIELD_COMMENT)
.buildRepeatable(
cd -> {
Set<String> r = new HashSet<>();
for (Comment c : cd.publishedComments()) {
r.add(c.message);
}
for (ChangeMessage m : cd.messages()) {
r.add(m.getMessage());
}
return r;
});
/** Number of unresolved comments of the change. */
public static final FieldDef<ChangeData, Integer> UNRESOLVED_COMMENT_COUNT =
new FieldDef.Single<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT, FieldType.INTEGER_RANGE, true) {
@Override
public Integer get(ChangeData input, FillArgs args) throws OrmException {
return input.unresolvedCommentCount();
}
};
intRange(ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT)
.stored()
.build(ChangeData::unresolvedCommentCount);
/** Whether the change is mergeable. */
public static final FieldDef<ChangeData, String> MERGEABLE =
new FieldDef.Single<ChangeData, String>(
ChangeQueryBuilder.FIELD_MERGEABLE, FieldType.EXACT, true) {
@Override
public String get(ChangeData input, FillArgs args) throws OrmException {
Boolean m = input.isMergeable();
if (m == null) {
return null;
}
return m ? "1" : "0";
}
};
exact(ChangeQueryBuilder.FIELD_MERGEABLE)
.stored()
.build(
cd -> {
Boolean m = cd.isMergeable();
if (m == null) {
return null;
}
return m ? "1" : "0";
});
/** 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().isPresent() ? input.changedLines().get().insertions : null;
}
};
intRange(ChangeQueryBuilder.FIELD_ADDED)
.stored()
.build(cd -> cd.changedLines().isPresent() ? cd.changedLines().get().insertions : null);
/** 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().isPresent() ? input.changedLines().get().deletions : null;
}
};
intRange(ChangeQueryBuilder.FIELD_DELETED)
.stored()
.build(cd -> cd.changedLines().isPresent() ? cd.changedLines().get().deletions : null);
/** 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 {
return input.changedLines().map(c -> c.insertions + c.deletions).orElse(null);
}
};
intRange(ChangeQueryBuilder.FIELD_DELTA)
.build(cd -> cd.changedLines().map(c -> c.insertions + c.deletions).orElse(null));
/** Users who have commented on this change. */
public static final FieldDef<ChangeData, Iterable<Integer>> COMMENTBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_COMMENTBY, FieldType.INTEGER, false) {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException {
Set<Integer> r = new HashSet<>();
for (ChangeMessage m : input.messages()) {
if (m.getAuthor() != null) {
r.add(m.getAuthor().get());
}
}
for (Comment c : input.publishedComments()) {
r.add(c.author.getId().get());
}
return r;
}
};
integer(ChangeQueryBuilder.FIELD_COMMENTBY)
.buildRepeatable(
cd -> {
Set<Integer> r = new HashSet<>();
for (ChangeMessage m : cd.messages()) {
if (m.getAuthor() != null) {
r.add(m.getAuthor().get());
}
}
for (Comment c : cd.publishedComments()) {
r.add(c.author.getId().get());
}
return r;
});
/** Star labels on this change in the format: &lt;account-id&gt;:&lt;label&gt; */
public static final FieldDef<ChangeData, Iterable<String>> STAR =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_STAR, FieldType.EXACT, true) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return Iterables.transform(
input.stars().entries(),
(Map.Entry<Account.Id, String> e) -> {
return StarredChangesUtil.StarField.create(e.getKey(), e.getValue()).toString();
});
}
};
exact(ChangeQueryBuilder.FIELD_STAR)
.stored()
.buildRepeatable(
cd ->
Iterables.transform(
cd.stars().entries(),
e ->
StarredChangesUtil.StarField.create(e.getKey(), e.getValue())
.toString()));
/** Users that have starred the change with any label. */
public static final FieldDef<ChangeData, Iterable<Integer>> STARBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_STARBY, FieldType.INTEGER, false) {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException {
return Iterables.transform(input.stars().keySet(), Account.Id::get);
}
};
integer(ChangeQueryBuilder.FIELD_STARBY)
.buildRepeatable(cd -> Iterables.transform(cd.stars().keySet(), Account.Id::get));
/** Opaque group identifiers for this change's patch sets. */
public static final FieldDef<ChangeData, Iterable<String>> GROUP =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_GROUP, FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
Set<String> r = Sets.newHashSetWithExpectedSize(1);
for (PatchSet ps : input.patchSets()) {
r.addAll(ps.getGroups());
}
return r;
}
};
exact(ChangeQueryBuilder.FIELD_GROUP)
.buildRepeatable(
cd -> {
Set<String> r = Sets.newHashSetWithExpectedSize(1);
for (PatchSet ps : cd.patchSets()) {
r.addAll(ps.getGroups());
}
return r;
});
public static class PatchSetProtoField extends FieldDef.Repeatable<ChangeData, byte[]> {
public static final ProtobufCodec<PatchSet> CODEC = CodecFactory.encoder(PatchSet.class);
private PatchSetProtoField() {
super("_patch_set", FieldType.STORED_ONLY, true);
}
@Override
public Iterable<byte[]> get(ChangeData input, FieldDef.FillArgs args) throws OrmException {
return toProtos(CODEC, input.patchSets());
}
}
public static final ProtobufCodec<PatchSet> PATCH_SET_CODEC =
CodecFactory.encoder(PatchSet.class);
/** Serialized patch set object, used for pre-populating results. */
public static final PatchSetProtoField PATCH_SET = new PatchSetProtoField();
public static final FieldDef<ChangeData, Iterable<byte[]>> PATCH_SET =
storedOnly("_patch_set").buildRepeatable(cd -> toProtos(PATCH_SET_CODEC, cd.patchSets()));
/** Users who have edits on this change. */
public static final FieldDef<ChangeData, Iterable<Integer>> EDITBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_EDITBY, FieldType.INTEGER, false) {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException {
return input.editsByUser().stream().map(Account.Id::get).collect(toSet());
}
};
integer(ChangeQueryBuilder.FIELD_EDITBY)
.buildRepeatable(cd -> cd.editsByUser().stream().map(Account.Id::get).collect(toSet()));
/** Users who have draft comments on this change. */
public static final FieldDef<ChangeData, Iterable<Integer>> DRAFTBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_DRAFTBY, FieldType.INTEGER, false) {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException {
return input.draftsByUser().stream().map(Account.Id::get).collect(toSet());
}
};
integer(ChangeQueryBuilder.FIELD_DRAFTBY)
.buildRepeatable(cd -> cd.draftsByUser().stream().map(Account.Id::get).collect(toSet()));
public static final Integer NOT_REVIEWED = -1;
/**
* Users the change was reviewed by since the last author update.
@ -705,21 +461,20 @@ public class ChangeField {
* emitted.
*/
public static final FieldDef<ChangeData, Iterable<Integer>> REVIEWEDBY =
new FieldDef.Repeatable<ChangeData, Integer>(
ChangeQueryBuilder.FIELD_REVIEWEDBY, FieldType.INTEGER, true) {
@Override
public Iterable<Integer> get(ChangeData input, FillArgs args) throws OrmException {
Set<Account.Id> reviewedBy = input.reviewedBy();
if (reviewedBy.isEmpty()) {
return ImmutableSet.of(NOT_REVIEWED);
}
List<Integer> result = new ArrayList<>(reviewedBy.size());
for (Account.Id id : reviewedBy) {
result.add(id.get());
}
return result;
}
};
integer(ChangeQueryBuilder.FIELD_REVIEWEDBY)
.stored()
.buildRepeatable(
cd -> {
Set<Account.Id> reviewedBy = cd.reviewedBy();
if (reviewedBy.isEmpty()) {
return ImmutableSet.of(NOT_REVIEWED);
}
List<Integer> result = new ArrayList<>(reviewedBy.size());
for (Account.Id id : reviewedBy) {
result.add(id.get());
}
return result;
});
// Submit rule options in this class should never use fastEvalLabels. This
// slows down indexing slightly but produces correct search results.
@ -780,30 +535,15 @@ public class ChangeField {
}
public static final FieldDef<ChangeData, Iterable<String>> SUBMIT_RECORD =
new FieldDef.Repeatable<ChangeData, String>("submit_record", FieldType.EXACT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args) throws OrmException {
return formatSubmitRecordValues(input);
}
};
exact("submit_record").buildRepeatable(cd -> formatSubmitRecordValues(cd));
public static final FieldDef<ChangeData, Iterable<byte[]>> STORED_SUBMIT_RECORD_STRICT =
new FieldDef.Repeatable<ChangeData, byte[]>(
"full_submit_record_strict", FieldType.STORED_ONLY, true) {
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
return storedSubmitRecords(input, SUBMIT_RULE_OPTIONS_STRICT);
}
};
storedOnly("full_submit_record_strict")
.buildRepeatable(cd -> storedSubmitRecords(cd, SUBMIT_RULE_OPTIONS_STRICT));
public static final FieldDef<ChangeData, Iterable<byte[]>> STORED_SUBMIT_RECORD_LENIENT =
new FieldDef.Repeatable<ChangeData, byte[]>(
"full_submit_record_lenient", FieldType.STORED_ONLY, true) {
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
return storedSubmitRecords(input, SUBMIT_RULE_OPTIONS_LENIENT);
}
};
storedOnly("full_submit_record_lenient")
.buildRepeatable(cd -> storedSubmitRecords(cd, SUBMIT_RULE_OPTIONS_LENIENT));
public static void parseSubmitRecords(
Collection<String> values, SubmitRuleOptions opts, ChangeData out) {
@ -873,35 +613,35 @@ public class ChangeField {
* <p>Emitted as UTF-8 encoded strings of the form {@code project:ref/name:[hex sha]}.
*/
public static final FieldDef<ChangeData, Iterable<byte[]>> REF_STATE =
new FieldDef.Repeatable<ChangeData, byte[]>("ref_state", FieldType.STORED_ONLY, true) {
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
List<byte[]> result = new ArrayList<>();
Project.NameKey project = input.change().getProject();
storedOnly("ref_state")
.buildRepeatable(
(cd, a) -> {
List<byte[]> result = new ArrayList<>();
Project.NameKey project = cd.change().getProject();
input.editRefs().values().forEach(r -> result.add(RefState.of(r).toByteArray(project)));
input
.starRefs()
.values()
.forEach(r -> result.add(RefState.of(r.ref()).toByteArray(args.allUsers)));
cd.editRefs()
.values()
.forEach(r -> result.add(RefState.of(r).toByteArray(project)));
cd.starRefs()
.values()
.forEach(r -> result.add(RefState.of(r.ref()).toByteArray(a.allUsers)));
if (PrimaryStorage.of(input.change()) == PrimaryStorage.NOTE_DB) {
ChangeNotes notes = input.notes();
result.add(RefState.create(notes.getRefName(), notes.getMetaId()).toByteArray(project));
notes.getRobotComments(); // Force loading robot comments.
RobotCommentNotes robotNotes = notes.getRobotCommentNotes();
result.add(
RefState.create(robotNotes.getRefName(), robotNotes.getMetaId())
.toByteArray(project));
input
.draftRefs()
.values()
.forEach(r -> result.add(RefState.of(r).toByteArray(args.allUsers)));
}
if (PrimaryStorage.of(cd.change()) == PrimaryStorage.NOTE_DB) {
ChangeNotes notes = cd.notes();
result.add(
RefState.create(notes.getRefName(), notes.getMetaId()).toByteArray(project));
notes.getRobotComments(); // Force loading robot comments.
RobotCommentNotes robotNotes = notes.getRobotCommentNotes();
result.add(
RefState.create(robotNotes.getRefName(), robotNotes.getMetaId())
.toByteArray(project));
cd.draftRefs()
.values()
.forEach(r -> result.add(RefState.of(r).toByteArray(a.allUsers)));
}
return result;
}
};
return result;
});
/**
* All ref wildcard patterns that were used in the course of indexing this document.
@ -910,32 +650,29 @@ public class ChangeField {
* RefStatePattern} for the pattern format.
*/
public static final FieldDef<ChangeData, Iterable<byte[]>> REF_STATE_PATTERN =
new FieldDef.Repeatable<ChangeData, byte[]>(
"ref_state_pattern", FieldType.STORED_ONLY, true) {
@Override
public Iterable<byte[]> get(ChangeData input, FillArgs args) throws OrmException {
Change.Id id = input.getId();
Project.NameKey project = input.change().getProject();
List<byte[]> result = new ArrayList<>(3);
result.add(
RefStatePattern.create(RefNames.REFS_USERS + "*/" + RefNames.EDIT_PREFIX + id + "/*")
.toByteArray(project));
result.add(
RefStatePattern.create(RefNames.refsStarredChangesPrefix(id) + "*")
.toByteArray(args.allUsers));
if (PrimaryStorage.of(input.change()) == PrimaryStorage.NOTE_DB) {
result.add(
RefStatePattern.create(RefNames.refsDraftCommentsPrefix(id) + "*")
.toByteArray(args.allUsers));
}
return result;
}
};
storedOnly("ref_state_pattern")
.buildRepeatable(
(cd, a) -> {
Change.Id id = cd.getId();
Project.NameKey project = cd.change().getProject();
List<byte[]> result = new ArrayList<>(3);
result.add(
RefStatePattern.create(
RefNames.REFS_USERS + "*/" + RefNames.EDIT_PREFIX + id + "/*")
.toByteArray(project));
result.add(
RefStatePattern.create(RefNames.refsStarredChangesPrefix(id) + "*")
.toByteArray(a.allUsers));
if (PrimaryStorage.of(cd.change()) == PrimaryStorage.NOTE_DB) {
result.add(
RefStatePattern.create(RefNames.refsDraftCommentsPrefix(id) + "*")
.toByteArray(a.allUsers));
}
return result;
});
public static final Integer NOT_REVIEWED = -1;
private static String getTopic(ChangeData input) throws OrmException {
Change c = input.change();
private static String getTopic(ChangeData cd) throws OrmException {
Change c = cd.change();
if (c == null) {
return null;
}
@ -959,4 +696,8 @@ public class ChangeField {
}
return result;
}
private static <T> FieldDef.Getter<ChangeData, T> changeGetter(Function<Change, T> func) {
return in -> in.change() != null ? func.apply(in.change()) : null;
}
}

View File

@ -14,74 +14,42 @@
package com.google.gerrit.server.index.group;
import static com.google.gerrit.server.index.FieldDef.exact;
import static com.google.gerrit.server.index.FieldDef.fullText;
import static com.google.gerrit.server.index.FieldDef.integer;
import static com.google.gerrit.server.index.FieldDef.prefix;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.index.FieldDef;
import com.google.gerrit.server.index.FieldType;
import com.google.gerrit.server.index.SchemaUtil;
import com.google.gwtorm.server.OrmException;
/** Secondary index schemas for groups. */
public class GroupField {
/** Legacy group ID. */
public static final FieldDef<AccountGroup, Integer> ID =
new FieldDef.Single<AccountGroup, Integer>("id", FieldType.INTEGER, false) {
@Override
public Integer get(AccountGroup input, FillArgs args) {
return input.getId().get();
}
};
integer("id").build(g -> g.getId().get());
/** Group UUID. */
public static final FieldDef<AccountGroup, String> UUID =
new FieldDef.Single<AccountGroup, String>("uuid", FieldType.EXACT, true) {
@Override
public String get(AccountGroup input, FillArgs args) {
return input.getGroupUUID().get();
}
};
exact("uuid").stored().build(g -> g.getGroupUUID().get());
/** Group owner UUID. */
public static final FieldDef<AccountGroup, String> OWNER_UUID =
new FieldDef.Single<AccountGroup, String>("owner_uuid", FieldType.EXACT, false) {
@Override
public String get(AccountGroup input, FillArgs args) {
return input.getOwnerGroupUUID().get();
}
};
exact("owner_uuid").build(g -> g.getOwnerGroupUUID().get());
/** Group name. */
public static final FieldDef<AccountGroup, String> NAME =
new FieldDef.Single<AccountGroup, String>("name", FieldType.EXACT, false) {
@Override
public String get(AccountGroup input, FillArgs args) {
return input.getName();
}
};
exact("name").build(AccountGroup::getName);
/** Prefix match on group name parts. */
public static final FieldDef<AccountGroup, Iterable<String>> NAME_PART =
new FieldDef.Repeatable<AccountGroup, String>("name_part", FieldType.PREFIX, false) {
@Override
public Iterable<String> get(AccountGroup input, FillArgs args) {
return SchemaUtil.getNameParts(input.getName());
}
};
prefix("name_part").buildRepeatable(g -> SchemaUtil.getNameParts(g.getName()));
/** Group description. */
public static final FieldDef<AccountGroup, String> DESCRIPTION =
new FieldDef.Single<AccountGroup, String>("description", FieldType.FULL_TEXT, false) {
@Override
public String get(AccountGroup input, FillArgs args) {
return input.getDescription();
}
};
fullText("description").build(AccountGroup::getDescription);
/** Whether the group is visible to all users. */
public static final FieldDef<AccountGroup, String> IS_VISIBLE_TO_ALL =
new FieldDef.Single<AccountGroup, String>("is_visible_to_all", FieldType.EXACT, false) {
@Override
public String get(AccountGroup input, FillArgs args) throws OrmException {
return input.isVisibleToAll() ? "1" : "0";
}
};
exact("is_visible_to_all").build(g -> g.isVisibleToAll() ? "1" : "0");
}

View File

@ -19,6 +19,7 @@ import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_AUT
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
import java.io.IOException;
public class AuthorPredicate extends ChangeIndexPredicate {
AuthorPredicate(String value) {
@ -27,7 +28,11 @@ public class AuthorPredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData object) throws OrmException {
return ChangeField.getAuthorParts(object).contains(getValue().toLowerCase());
try {
return ChangeField.getAuthorParts(object).contains(getValue().toLowerCase());
} catch (IOException e) {
throw new OrmException(e);
}
}
@Override

View File

@ -19,6 +19,7 @@ import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_COM
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
import java.io.IOException;
public class CommitterPredicate extends ChangeIndexPredicate {
CommitterPredicate(String value) {
@ -27,7 +28,11 @@ public class CommitterPredicate extends ChangeIndexPredicate {
@Override
public boolean match(ChangeData object) throws OrmException {
return ChangeField.getCommitterParts(object).contains(getValue().toLowerCase());
try {
return ChangeField.getCommitterParts(object).contains(getValue().toLowerCase());
} catch (IOException e) {
throw new OrmException(e);
}
}
@Override