All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.index.change.ChangeField Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// 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.index.change;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.gerrit.index.FieldDef.exact;
import static com.google.gerrit.index.FieldDef.fullText;
import static com.google.gerrit.index.FieldDef.intRange;
import static com.google.gerrit.index.FieldDef.integer;
import static com.google.gerrit.index.FieldDef.prefix;
import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Enums;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Longs;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.SchemaUtil;
import com.google.gerrit.mail.Address;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.notedb.RobotCommentNotes;
import com.google.gerrit.server.project.SubmitRuleOptions;
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.gson.Gson;
import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
import com.google.protobuf.CodedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.eclipse.jgit.lib.PersonIdent;

/**
 * Fields indexed on change documents.
 *
 * 

Each field corresponds to both a field name supported by {@link ChangeQueryBuilder} for * querying that field, and a method on {@link ChangeData} used for populating the corresponding * document fields in the secondary index. * *

Field names are all lowercase alphanumeric plus underscore; index implementations may create * unambiguous derived field names containing other characters. */ public class ChangeField { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); public static final int NO_ASSIGNEE = -1; private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson(); /** Legacy change ID. */ public static final FieldDef LEGACY_ID = integer("legacy_id").stored().build(cd -> cd.getId().get()); /** Newer style Change-Id key. */ public static final FieldDef ID = 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 STATUS = exact(ChangeQueryBuilder.FIELD_STATUS) .build(changeGetter(c -> ChangeStatusPredicate.canonicalize(c.getStatus()))); /** Project containing the change. */ public static final FieldDef PROJECT = exact(ChangeQueryBuilder.FIELD_PROJECT) .stored() .build(changeGetter(c -> c.getProject().get())); /** Project containing the change, as a prefix field. */ public static final FieldDef PROJECTS = prefix(ChangeQueryBuilder.FIELD_PROJECTS).build(changeGetter(c -> c.getProject().get())); /** Reference (aka branch) the change will submit onto. */ public static final FieldDef REF = exact(ChangeQueryBuilder.FIELD_REF).build(changeGetter(c -> c.getDest().get())); /** Topic, a short annotation on the branch. */ public static final FieldDef EXACT_TOPIC = exact("topic4").build(ChangeField::getTopic); /** Topic, a short annotation on the branch. */ public static final FieldDef FUZZY_TOPIC = fullText("topic5").build(ChangeField::getTopic); /** Submission id assigned by MergeOp. */ public static final FieldDef SUBMISSIONID = exact(ChangeQueryBuilder.FIELD_SUBMISSIONID).build(changeGetter(Change::getSubmissionId)); /** Last update time since January 1, 1970. */ public static final FieldDef UPDATED = timestamp("updated2").stored().build(changeGetter(Change::getLastUpdatedOn)); /** List of full file paths modified in the current patch set. */ public static final FieldDef> PATH = // Named for backwards compatibility. exact(ChangeQueryBuilder.FIELD_FILE) .buildRepeatable(cd -> firstNonNull(cd.currentFilePaths(), ImmutableList.of())); public static Set getFileParts(ChangeData cd) throws OrmException { List paths; try { paths = cd.currentFilePaths(); } catch (IOException e) { throw new OrmException(e); } Splitter s = Splitter.on('/').omitEmptyStrings(); Set r = new HashSet<>(); for (String path : paths) { for (String part : s.split(path)) { r.add(part); } } return r; } /** Hashtags tied to a change */ public static final FieldDef> HASHTAG = exact(ChangeQueryBuilder.FIELD_HASHTAG) .buildRepeatable(cd -> cd.hashtags().stream().map(String::toLowerCase).collect(toSet())); /** Hashtags with original case. */ public static final FieldDef> HASHTAG_CASE_AWARE = 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> FILE_PART = exact(ChangeQueryBuilder.FIELD_FILEPART).buildRepeatable(ChangeField::getFileParts); /** Owner/creator of the change. */ public static final FieldDef OWNER = integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get())); /** The user assigned to the change. */ public static final FieldDef 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> REVIEWER = exact("reviewer2").stored().buildRepeatable(cd -> getReviewerFieldValues(cd.reviewers())); /** Reviewer(s) associated with the change that do not have a gerrit account. */ public static final FieldDef> REVIEWER_BY_EMAIL = exact("reviewer_by_email") .stored() .buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.reviewersByEmail())); /** Reviewer(s) modified during change's current WIP phase. */ public static final FieldDef> PENDING_REVIEWER = exact(ChangeQueryBuilder.FIELD_PENDING_REVIEWER) .stored() .buildRepeatable(cd -> getReviewerFieldValues(cd.pendingReviewers())); /** Reviewer(s) by email modified during change's current WIP phase. */ public static final FieldDef> PENDING_REVIEWER_BY_EMAIL = exact(ChangeQueryBuilder.FIELD_PENDING_REVIEWER_BY_EMAIL) .stored() .buildRepeatable(cd -> getReviewerByEmailFieldValues(cd.pendingReviewersByEmail())); /** References a change that this change reverts. */ public static final FieldDef REVERT_OF = integer(ChangeQueryBuilder.FIELD_REVERTOF) .build(cd -> cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null); @VisibleForTesting static List getReviewerFieldValues(ReviewerSet reviewers) { List r = new ArrayList<>(reviewers.asTable().size() * 2); for (Table.Cell c : reviewers.asTable().cellSet()) { String v = getReviewerFieldValue(c.getRowKey(), c.getColumnKey()); r.add(v); r.add(v + ',' + c.getValue().getTime()); } return r; } public static String getReviewerFieldValue(ReviewerStateInternal state, Account.Id id) { return state.toString() + ',' + id; } @VisibleForTesting static List getReviewerByEmailFieldValues(ReviewerByEmailSet reviewersByEmail) { List r = new ArrayList<>(reviewersByEmail.asTable().size() * 2); for (Table.Cell c : reviewersByEmail.asTable().cellSet()) { String v = getReviewerByEmailFieldValue(c.getRowKey(), c.getColumnKey()); r.add(v); if (c.getColumnKey().getName() != null) { // Add another entry without the name to provide search functionality on the email Address emailOnly = new Address(c.getColumnKey().getEmail()); r.add(getReviewerByEmailFieldValue(c.getRowKey(), emailOnly)); } r.add(v + ',' + c.getValue().getTime()); } return r; } public static String getReviewerByEmailFieldValue(ReviewerStateInternal state, Address adr) { return state.toString() + ',' + adr; } public static ReviewerSet parseReviewerFieldValues(Change.Id changeId, Iterable values) { ImmutableTable.Builder b = ImmutableTable.builder(); for (String v : values) { int i = v.indexOf(','); if (i < 0) { logger.atWarning().log( "Invalid value for reviewer field from change %s: %s", changeId.get(), v); continue; } int i2 = v.lastIndexOf(','); if (i2 == i) { // Don't log a warning here. // For each reviewer we store 2 values in the reviewer field, one value with the format // "," and one value with the format // ",," (see #getReviewerFieldValues(ReviewerSet)). // For parsing we are only interested in the ",," // value and the "," value is ignored here. continue; } com.google.common.base.Optional reviewerState = Enums.getIfPresent(ReviewerStateInternal.class, v.substring(0, i)); if (!reviewerState.isPresent()) { logger.atWarning().log( "Failed to parse reviewer state of reviewer field from change %s: %s", changeId.get(), v); continue; } Optional accountId = Account.Id.tryParse(v.substring(i + 1, i2)); if (!accountId.isPresent()) { logger.atWarning().log( "Failed to parse account ID of reviewer field from change %s: %s", changeId.get(), v); continue; } Long l = Longs.tryParse(v.substring(i2 + 1, v.length())); if (l == null) { logger.atWarning().log( "Failed to parse timestamp of reviewer field from change %s: %s", changeId.get(), v); continue; } Timestamp timestamp = new Timestamp(l); b.put(reviewerState.get(), accountId.get(), timestamp); } return ReviewerSet.fromTable(b.build()); } public static ReviewerByEmailSet parseReviewerByEmailFieldValues( Change.Id changeId, Iterable values) { ImmutableTable.Builder b = ImmutableTable.builder(); for (String v : values) { int i = v.indexOf(','); if (i < 0) { logger.atWarning().log( "Invalid value for reviewer by email field from change %s: %s", changeId.get(), v); continue; } int i2 = v.lastIndexOf(','); if (i2 == i) { // Don't log a warning here. // For each reviewer we store 2 values in the reviewer field, one value with the format // "," and one value with the format // ",," (see // #getReviewerByEmailFieldValues(ReviewerByEmailSet)). // For parsing we are only interested in the ",," value // and the "," value is ignored here. continue; } com.google.common.base.Optional reviewerState = Enums.getIfPresent(ReviewerStateInternal.class, v.substring(0, i)); if (!reviewerState.isPresent()) { logger.atWarning().log( "Failed to parse reviewer state of reviewer by email field from change %s: %s", changeId.get(), v); continue; } Address address = Address.tryParse(v.substring(i + 1, i2)); if (address == null) { logger.atWarning().log( "Failed to parse address of reviewer by email field from change %s: %s", changeId.get(), v); continue; } Long l = Longs.tryParse(v.substring(i2 + 1, v.length())); if (l == null) { logger.atWarning().log( "Failed to parse timestamp of reviewer by email field from change %s: %s", changeId.get(), v); continue; } Timestamp timestamp = new Timestamp(l); b.put(reviewerState.get(), address, timestamp); } return ReviewerByEmailSet.fromTable(b.build()); } /** Commit ID of any patch set on the change, using prefix match. */ public static final FieldDef> COMMIT = prefix(ChangeQueryBuilder.FIELD_COMMIT).buildRepeatable(ChangeField::getRevisions); /** Commit ID of any patch set on the change, using exact match. */ public static final FieldDef> EXACT_COMMIT = exact(ChangeQueryBuilder.FIELD_EXACTCOMMIT).buildRepeatable(ChangeField::getRevisions); private static Set getRevisions(ChangeData cd) throws OrmException { Set revisions = new HashSet<>(); for (PatchSet ps : cd.patchSets()) { if (ps.getRevision() != null) { revisions.add(ps.getRevision().get()); } } return revisions; } /** Tracking id extracted from a footer. */ public static final FieldDef> TR = exact(ChangeQueryBuilder.FIELD_TR) .buildRepeatable(cd -> ImmutableSet.copyOf(cd.trackingFooters().values())); /** List of labels on the current patch set including change owner votes. */ public static final FieldDef> LABEL = exact("label2").buildRepeatable(cd -> getLabels(cd, true)); private static Iterable getLabels(ChangeData cd, boolean owners) throws OrmException { Set allApprovals = new HashSet<>(); Set distinctApprovals = new HashSet<>(); for (PatchSetApproval a : cd.currentApprovals()) { if (a.getValue() != 0 && !a.isLegacySubmit()) { allApprovals.add(formatLabel(a.getLabel(), a.getValue(), a.getAccountId())); if (owners && cd.change().getOwner().equals(a.getAccountId())) { allApprovals.add( formatLabel(a.getLabel(), a.getValue(), ChangeQueryBuilder.OWNER_ACCOUNT_ID)); } distinctApprovals.add(formatLabel(a.getLabel(), a.getValue())); } } allApprovals.addAll(distinctApprovals); return allApprovals; } public static Set getAuthorParts(ChangeData cd) throws OrmException, IOException { return SchemaUtil.getPersonParts(cd.getAuthor()); } public static Set getAuthorNameAndEmail(ChangeData cd) throws OrmException, IOException { return getNameAndEmail(cd.getAuthor()); } public static Set getCommitterParts(ChangeData cd) throws OrmException, IOException { return SchemaUtil.getPersonParts(cd.getCommitter()); } public static Set getCommitterNameAndEmail(ChangeData cd) throws OrmException, IOException { return getNameAndEmail(cd.getCommitter()); } private static Set getNameAndEmail(PersonIdent person) { if (person == null) { return ImmutableSet.of(); } String name = person.getName().toLowerCase(Locale.US); String email = person.getEmailAddress().toLowerCase(Locale.US); StringBuilder nameEmailBuilder = new StringBuilder(); PersonIdent.appendSanitized(nameEmailBuilder, name); nameEmailBuilder.append(" <"); PersonIdent.appendSanitized(nameEmailBuilder, email); nameEmailBuilder.append('>'); return ImmutableSet.of(name, email, nameEmailBuilder.toString()); } /** * The exact email address, or any part of the author name or email address, in the current patch * set. */ public static final FieldDef> AUTHOR = fullText(ChangeQueryBuilder.FIELD_AUTHOR).buildRepeatable(ChangeField::getAuthorParts); /** The exact name, email address and NameEmail of the author. */ public static final FieldDef> EXACT_AUTHOR = exact(ChangeQueryBuilder.FIELD_EXACTAUTHOR) .buildRepeatable(ChangeField::getAuthorNameAndEmail); /** * The exact email address, or any part of the committer name or email address, in the current * patch set. */ public static final FieldDef> COMMITTER = fullText(ChangeQueryBuilder.FIELD_COMMITTER).buildRepeatable(ChangeField::getCommitterParts); /** The exact name, email address, and NameEmail of the committer. */ public static final FieldDef> EXACT_COMMITTER = exact(ChangeQueryBuilder.FIELD_EXACTCOMMITTER) .buildRepeatable(ChangeField::getCommitterNameAndEmail); /** Serialized change object, used for pre-populating results. */ public static final FieldDef CHANGE = storedOnly("_change").build(changeGetter(CHANGE_CODEC::encodeToByteArray)); /** Serialized approvals for the current patch set, used for pre-populating results. */ public static final FieldDef> APPROVAL = storedOnly("_approval") .buildRepeatable(cd -> toProtos(APPROVAL_CODEC, cd.currentApprovals())); public static String formatLabel(String label, int value) { return formatLabel(label, value, null); } public static String formatLabel(String label, int value, Account.Id accountId) { return label.toLowerCase() + (value >= 0 ? "+" : "") + value + (accountId != null ? "," + formatAccount(accountId) : ""); } private static String formatAccount(Account.Id accountId) { if (ChangeQueryBuilder.OWNER_ACCOUNT_ID.equals(accountId)) { return ChangeQueryBuilder.ARG_ID_OWNER; } return Integer.toString(accountId.get()); } /** Commit message of the current patch set. */ public static final FieldDef COMMIT_MESSAGE = fullText(ChangeQueryBuilder.FIELD_MESSAGE).build(ChangeData::commitMessage); /** Summary or inline comment. */ public static final FieldDef> COMMENT = fullText(ChangeQueryBuilder.FIELD_COMMENT) .buildRepeatable( cd -> Stream.concat( cd.publishedComments().stream().map(c -> c.message), cd.messages().stream().map(ChangeMessage::getMessage)) .collect(toSet())); /** Number of unresolved comments of the change. */ public static final FieldDef UNRESOLVED_COMMENT_COUNT = intRange(ChangeQueryBuilder.FIELD_UNRESOLVED_COMMENT_COUNT) .build(ChangeData::unresolvedCommentCount); /** Whether the change is mergeable. */ public static final FieldDef MERGEABLE = 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 ADDED = intRange(ChangeQueryBuilder.FIELD_ADDED) .build(cd -> cd.changedLines().isPresent() ? cd.changedLines().get().insertions : null); /** The number of deleted lines in this change. */ public static final FieldDef DELETED = intRange(ChangeQueryBuilder.FIELD_DELETED) .build(cd -> cd.changedLines().isPresent() ? cd.changedLines().get().deletions : null); /** The total number of modified lines in this change. */ public static final FieldDef DELTA = intRange(ChangeQueryBuilder.FIELD_DELTA) .build(cd -> cd.changedLines().map(c -> c.insertions + c.deletions).orElse(null)); /** Determines if this change is private. */ public static final FieldDef PRIVATE = exact(ChangeQueryBuilder.FIELD_PRIVATE).build(cd -> cd.change().isPrivate() ? "1" : "0"); /** Determines if this change is work in progress. */ public static final FieldDef WIP = exact(ChangeQueryBuilder.FIELD_WIP).build(cd -> cd.change().isWorkInProgress() ? "1" : "0"); /** Determines if this change has started review. */ public static final FieldDef STARTED = exact(ChangeQueryBuilder.FIELD_STARTED) .build(cd -> cd.change().hasReviewStarted() ? "1" : "0"); /** Users who have commented on this change. */ public static final FieldDef> COMMENTBY = integer(ChangeQueryBuilder.FIELD_COMMENTBY) .buildRepeatable( cd -> Stream.concat( cd.messages().stream().map(ChangeMessage::getAuthor), cd.publishedComments().stream().map(c -> c.author.getId())) .filter(Objects::nonNull) .map(Account.Id::get) .collect(toSet())); /** Star labels on this change in the format: <account-id>:<label> */ public static final FieldDef> STAR = 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> STARBY = 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> GROUP = exact(ChangeQueryBuilder.FIELD_GROUP) .buildRepeatable( cd -> cd.patchSets().stream().flatMap(ps -> ps.getGroups().stream()).collect(toSet())); /** Serialized patch set object, used for pre-populating results. */ public static final FieldDef> PATCH_SET = storedOnly("_patch_set").buildRepeatable(cd -> toProtos(PATCH_SET_CODEC, cd.patchSets())); /** Users who have edits on this change. */ public static final FieldDef> EDITBY = 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> DRAFTBY = 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. * *

A change is considered reviewed by a user if the latest update by that user is newer than * the latest update by the change author. Both top-level change messages and new patch sets are * considered to be updates. * *

If the latest update is by the change owner, then the special value {@link #NOT_REVIEWED} is * emitted. */ public static final FieldDef> REVIEWEDBY = integer(ChangeQueryBuilder.FIELD_REVIEWEDBY) .stored() .buildRepeatable( cd -> { Set reviewedBy = cd.reviewedBy(); if (reviewedBy.isEmpty()) { return ImmutableSet.of(NOT_REVIEWED); } return reviewedBy.stream().map(Account.Id::get).collect(toList()); }); public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_LENIENT = SubmitRuleOptions.builder().allowClosed(true).build(); public static final SubmitRuleOptions SUBMIT_RULE_OPTIONS_STRICT = SubmitRuleOptions.builder().build(); /** * JSON type for storing SubmitRecords. * *

Stored fields need to use a stable format over a long period; this type insulates the index * from implementation changes in SubmitRecord itself. */ public static class StoredSubmitRecord { static class StoredLabel { String label; SubmitRecord.Label.Status status; Integer appliedBy; } static class StoredRequirement { String fallbackText; String type; Map data; } SubmitRecord.Status status; List labels; List requirements; String errorMessage; public StoredSubmitRecord(SubmitRecord rec) { this.status = rec.status; this.errorMessage = rec.errorMessage; if (rec.labels != null) { this.labels = new ArrayList<>(rec.labels.size()); for (SubmitRecord.Label label : rec.labels) { StoredLabel sl = new StoredLabel(); sl.label = label.label; sl.status = label.status; sl.appliedBy = label.appliedBy != null ? label.appliedBy.get() : null; this.labels.add(sl); } } if (rec.requirements != null) { this.requirements = new ArrayList<>(rec.requirements.size()); for (SubmitRequirement requirement : rec.requirements) { StoredRequirement sr = new StoredRequirement(); sr.type = requirement.type(); sr.fallbackText = requirement.fallbackText(); sr.data = requirement.data(); this.requirements.add(sr); } } } public SubmitRecord toSubmitRecord() { SubmitRecord rec = new SubmitRecord(); rec.status = status; rec.errorMessage = errorMessage; if (labels != null) { rec.labels = new ArrayList<>(labels.size()); for (StoredLabel label : labels) { SubmitRecord.Label srl = new SubmitRecord.Label(); srl.label = label.label; srl.status = label.status; srl.appliedBy = label.appliedBy != null ? new Account.Id(label.appliedBy) : null; rec.labels.add(srl); } } if (requirements != null) { rec.requirements = new ArrayList<>(requirements.size()); for (StoredRequirement req : requirements) { SubmitRequirement sr = SubmitRequirement.builder() .setType(req.type) .setFallbackText(req.fallbackText) .setData(req.data) .build(); rec.requirements.add(sr); } } return rec; } } public static final FieldDef> SUBMIT_RECORD = exact("submit_record").buildRepeatable(ChangeField::formatSubmitRecordValues); public static final FieldDef> STORED_SUBMIT_RECORD_STRICT = storedOnly("full_submit_record_strict") .buildRepeatable(cd -> storedSubmitRecords(cd, SUBMIT_RULE_OPTIONS_STRICT)); public static final FieldDef> STORED_SUBMIT_RECORD_LENIENT = storedOnly("full_submit_record_lenient") .buildRepeatable(cd -> storedSubmitRecords(cd, SUBMIT_RULE_OPTIONS_LENIENT)); public static void parseSubmitRecords( Collection values, SubmitRuleOptions opts, ChangeData out) { List records = parseSubmitRecords(values); if (records.isEmpty()) { // Assume no values means the field is not in the index; // SubmitRuleEvaluator ensures the list is non-empty. return; } out.setSubmitRecords(opts, records); } @VisibleForTesting static List parseSubmitRecords(Collection values) { return values .stream() .map(v -> GSON.fromJson(v, StoredSubmitRecord.class).toSubmitRecord()) .collect(toList()); } @VisibleForTesting static List storedSubmitRecords(List records) { return Lists.transform(records, r -> GSON.toJson(new StoredSubmitRecord(r)).getBytes(UTF_8)); } private static Iterable storedSubmitRecords(ChangeData cd, SubmitRuleOptions opts) { return storedSubmitRecords(cd.submitRecords(opts)); } public static List formatSubmitRecordValues(ChangeData cd) throws OrmException { return formatSubmitRecordValues( cd.submitRecords(SUBMIT_RULE_OPTIONS_STRICT), cd.change().getOwner()); } @VisibleForTesting static List formatSubmitRecordValues(List records, Account.Id changeOwner) { List result = new ArrayList<>(); for (SubmitRecord rec : records) { result.add(rec.status.name()); if (rec.labels == null) { continue; } for (SubmitRecord.Label label : rec.labels) { String sl = label.status.toString() + ',' + label.label.toLowerCase(); result.add(sl); String slc = sl + ','; if (label.appliedBy != null) { result.add(slc + label.appliedBy.get()); if (label.appliedBy.equals(changeOwner)) { result.add(slc + ChangeQueryBuilder.OWNER_ACCOUNT_ID.get()); } } } } return result; } /** * All values of all refs that were used in the course of indexing this document. * *

Emitted as UTF-8 encoded strings of the form {@code project:ref/name:[hex sha]}. */ public static final FieldDef> REF_STATE = storedOnly("ref_state") .buildRepeatable( cd -> { List result = new ArrayList<>(); Project.NameKey project = cd.change().getProject(); cd.editRefs() .values() .forEach(r -> result.add(RefState.of(r).toByteArray(project))); cd.starRefs() .values() .forEach(r -> result.add(RefState.of(r.ref()).toByteArray(allUsers(cd)))); 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(allUsers(cd)))); } return result; }); /** * All ref wildcard patterns that were used in the course of indexing this document. * *

Emitted as UTF-8 encoded strings of the form {@code project:ref/name/*}. See {@link * RefStatePattern} for the pattern format. */ public static final FieldDef> REF_STATE_PATTERN = storedOnly("ref_state_pattern") .buildRepeatable( cd -> { Change.Id id = cd.getId(); Project.NameKey project = cd.change().getProject(); List 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(allUsers(cd))); if (PrimaryStorage.of(cd.change()) == PrimaryStorage.NOTE_DB) { result.add( RefStatePattern.create(RefNames.refsDraftCommentsPrefix(id) + "*") .toByteArray(allUsers(cd))); } return result; }); private static String getTopic(ChangeData cd) throws OrmException { Change c = cd.change(); if (c == null) { return null; } return firstNonNull(c.getTopic(), ""); } private static List toProtos(ProtobufCodec codec, Collection objs) throws OrmException { List result = Lists.newArrayListWithCapacity(objs.size()); ByteArrayOutputStream out = new ByteArrayOutputStream(256); try { for (T obj : objs) { out.reset(); CodedOutputStream cos = CodedOutputStream.newInstance(out); codec.encode(obj, cos); cos.flush(); result.add(out.toByteArray()); } } catch (IOException e) { throw new OrmException(e); } return result; } private static FieldDef.Getter changeGetter(Function func) { return in -> in.change() != null ? func.apply(in.change()) : null; } private static AllUsersName allUsers(ChangeData cd) { return cd.getAllUsersNameForIndexing(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy