Merge changes from topics 'query-refactor', 'kill-sortkey'
* changes: QueryProcessor: Don't double-add visibleto predicate Add query tests for visibleto predicate Terminate sortkey with prejudice Use secondary index for advertising extra haves during push OutputStreamQuery: Optimize formatter allocation Move stream-based QueryProcessor output to its own class Add QueryProcessor methods to search by Predicate Fix limit handling in QueryProcessor QueryChanges: Remove unused reverse field
This commit is contained in:
		@@ -116,7 +116,7 @@ Find the 2 most recent open changes in the tools/gerrit project:
 | 
			
		||||
====
 | 
			
		||||
  $ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
 | 
			
		||||
  {"project":"tools/gerrit", ...}
 | 
			
		||||
  {"project":"tools/gerrit", ..., sortKey:"000e6aee00003e26", ...}
 | 
			
		||||
  {"project":"tools/gerrit", ...}
 | 
			
		||||
  {"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
 | 
			
		||||
====
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,6 @@ was created.
 | 
			
		||||
lastUpdated:: Time in seconds since the UNIX epoch when this change
 | 
			
		||||
was last updated.
 | 
			
		||||
 | 
			
		||||
sortKey:: Internal key used to sort changes, based on lastUpdated.
 | 
			
		||||
 | 
			
		||||
open:: Boolean indicating if the change is still open for review.
 | 
			
		||||
 | 
			
		||||
status:: Current state of this change.
 | 
			
		||||
 
 | 
			
		||||
@@ -1123,7 +1123,6 @@ link:rest-api-changes.html#change-info[ChangeInfo] entities.
 | 
			
		||||
      "created": "2013-02-01 09:59:32.126000000",
 | 
			
		||||
      "updated": "2013-02-21 11:16:36.775000000",
 | 
			
		||||
      "mergeable": true,
 | 
			
		||||
      "_sortkey": "0023412400000f7d",
 | 
			
		||||
      "_number": 3965,
 | 
			
		||||
      "owner": {
 | 
			
		||||
        "name": "John Doe"
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@ the resulting change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 0,
 | 
			
		||||
    "deletions": 0,
 | 
			
		||||
    "_sortkey": "002cbc25000004e5",
 | 
			
		||||
    "_number": 4711,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -105,7 +104,6 @@ Query for open changes of watched projects:
 | 
			
		||||
      "mergeable": true,
 | 
			
		||||
      "insertions": 26,
 | 
			
		||||
      "deletions": 10,
 | 
			
		||||
      "_sortkey": "001e7057000006dc",
 | 
			
		||||
      "_number": 1756,
 | 
			
		||||
      "owner": {
 | 
			
		||||
        "name": "John Doe"
 | 
			
		||||
@@ -123,7 +121,6 @@ Query for open changes of watched projects:
 | 
			
		||||
      "mergeable": true,
 | 
			
		||||
      "insertions": 12,
 | 
			
		||||
      "deletions": 18,
 | 
			
		||||
      "_sortkey": "001e7056000006dd",
 | 
			
		||||
      "_number": 1757,
 | 
			
		||||
      "owner": {
 | 
			
		||||
        "name": "John Doe"
 | 
			
		||||
@@ -177,7 +174,6 @@ Query that retrieves changes for a user's dashboard:
 | 
			
		||||
        "mergeable": true,
 | 
			
		||||
        "insertions": 4,
 | 
			
		||||
        "deletions": 7,
 | 
			
		||||
        "_sortkey": "001e7057000006dc",
 | 
			
		||||
        "_number": 1756,
 | 
			
		||||
        "owner": {
 | 
			
		||||
          "name": "John Doe"
 | 
			
		||||
@@ -330,7 +326,6 @@ default. Optional fields are:
 | 
			
		||||
      "mergeable": true,
 | 
			
		||||
      "insertions": 16,
 | 
			
		||||
      "deletions": 7,
 | 
			
		||||
      "_sortkey": "001c9bf400000061",
 | 
			
		||||
      "_number": 97,
 | 
			
		||||
      "owner": {
 | 
			
		||||
        "name": "Shawn Pearce"
 | 
			
		||||
@@ -467,7 +462,6 @@ describes the change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 34,
 | 
			
		||||
    "deletions": 101,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -520,7 +514,6 @@ REJECTED > APPROVED > DISLIKED > RECOMMENDED.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 126,
 | 
			
		||||
    "deletions": 11,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "_account_id": 1000096,
 | 
			
		||||
@@ -764,7 +757,6 @@ describes the abandoned change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 3,
 | 
			
		||||
    "deletions": 310,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -823,7 +815,6 @@ describes the restored change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 2,
 | 
			
		||||
    "deletions": 13,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -880,7 +871,6 @@ is included.
 | 
			
		||||
    "mergeable": false,
 | 
			
		||||
    "insertions": 33,
 | 
			
		||||
    "deletions": 9,
 | 
			
		||||
    "_sortkey": "0024cf9a000012bf",
 | 
			
		||||
    "_number": 4799,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -973,7 +963,6 @@ describes the reverting change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 6,
 | 
			
		||||
    "deletions": 4,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -1035,7 +1024,6 @@ describes the submitted/merged change.
 | 
			
		||||
    "status": "MERGED",
 | 
			
		||||
    "created": "2013-02-01 09:59:32.126000000",
 | 
			
		||||
    "updated": "2013-02-21 11:16:36.775000000",
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -1179,7 +1167,6 @@ missing from the result. At least `id`, `project`, `branch`, and
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 34,
 | 
			
		||||
    "deletions": 101,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -1228,7 +1215,6 @@ Only the change owner, a project owner, or an administrator may fix changes.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 34,
 | 
			
		||||
    "deletions": 101,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -1863,7 +1849,6 @@ for the current patch set.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 34,
 | 
			
		||||
    "deletions": 45,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "_account_id": 1000096,
 | 
			
		||||
@@ -2128,7 +2113,6 @@ is included.
 | 
			
		||||
    "mergeable": false,
 | 
			
		||||
    "insertions": 21,
 | 
			
		||||
    "deletions": 21,
 | 
			
		||||
    "_sortkey": "0024cf9a000012bf",
 | 
			
		||||
    "_number": 4799,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -3096,7 +3080,6 @@ describes the resulting cherry picked change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 12,
 | 
			
		||||
    "deletions": 11,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -3150,7 +3133,6 @@ describes the change.
 | 
			
		||||
    "mergeable": true,
 | 
			
		||||
    "insertions": 261,
 | 
			
		||||
    "deletions": 101,
 | 
			
		||||
    "_sortkey": "0023412400000f7d",
 | 
			
		||||
    "_number": 3965,
 | 
			
		||||
    "owner": {
 | 
			
		||||
      "name": "John Doe"
 | 
			
		||||
@@ -3335,7 +3317,6 @@ Not set for merged changes, or if the change has not yet been tested.
 | 
			
		||||
Number of inserted lines.
 | 
			
		||||
|`deletions`          ||
 | 
			
		||||
Number of deleted lines.
 | 
			
		||||
|`_sortkey`           ||The sortkey of the change.
 | 
			
		||||
|`_number`            ||The legacy numeric ID of the change.
 | 
			
		||||
|`owner`              ||
 | 
			
		||||
The owner of the change as an link:rest-api-accounts.html#account-info[
 | 
			
		||||
@@ -3373,7 +3354,7 @@ Only set if link:#current-revision[the current revision] is requested
 | 
			
		||||
if link:#all-revisions[all revisions] are requested.
 | 
			
		||||
|`_more_changes`      |optional, not set if `false`|
 | 
			
		||||
Whether the query would deliver more results if not limited. +
 | 
			
		||||
Only set on either the last or the first change that is returned.
 | 
			
		||||
Only set on the last change that is returned.
 | 
			
		||||
|`problems`           |optional|
 | 
			
		||||
A list of link:#problem-info[ProblemInfo] entities describing potential
 | 
			
		||||
problems with this change. Only set if link:#check[CHECK] is set.
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ public class ChangeInfo {
 | 
			
		||||
  protected String topic;
 | 
			
		||||
  protected boolean starred;
 | 
			
		||||
  protected Timestamp lastUpdatedOn;
 | 
			
		||||
  protected String sortKey;
 | 
			
		||||
  protected PatchSet.Id patchSetId;
 | 
			
		||||
  protected boolean latest;
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +51,6 @@ public class ChangeInfo {
 | 
			
		||||
    branch = c.getDest().getShortName();
 | 
			
		||||
    topic = c.getTopic();
 | 
			
		||||
    lastUpdatedOn = c.getLastUpdatedOn();
 | 
			
		||||
    sortKey = c.getSortKey();
 | 
			
		||||
    patchSetId = patchId;
 | 
			
		||||
    latest = patchSetId == null || patchSetId.equals(c.currentPatchSetId());
 | 
			
		||||
  }
 | 
			
		||||
@@ -112,8 +110,4 @@ public class ChangeInfo {
 | 
			
		||||
  public java.sql.Timestamp getLastUpdatedOn() {
 | 
			
		||||
    return lastUpdatedOn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public String getSortKey() {
 | 
			
		||||
    return sortKey;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,6 @@ public class ChangeInfo {
 | 
			
		||||
  public Integer insertions;
 | 
			
		||||
  public Integer deletions;
 | 
			
		||||
 | 
			
		||||
  public String _sortkey;
 | 
			
		||||
  public String baseChange;
 | 
			
		||||
  public int _number;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,6 @@ public class ChangeInfo extends JavaScriptObject {
 | 
			
		||||
  private final native String updatedRaw() /*-{ return this.updated; }-*/;
 | 
			
		||||
  public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
 | 
			
		||||
  public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
 | 
			
		||||
  public final native String _sortkey() /*-{ return this._sortkey; }-*/;
 | 
			
		||||
  public final native NativeMap<LabelInfo> all_labels() /*-{ return this.labels; }-*/;
 | 
			
		||||
  public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
 | 
			
		||||
  public final native String current_revision() /*-{ return this.current_revision; }-*/;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.rpc.change;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryProcessor;
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
 | 
			
		||||
import com.google.gerrit.server.query.change.OutputStreamQuery;
 | 
			
		||||
import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -31,11 +31,11 @@ import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
@Singleton
 | 
			
		||||
public class DeprecatedChangeQueryServlet extends HttpServlet {
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
  private final Provider<QueryProcessor> processor;
 | 
			
		||||
  private final Provider<OutputStreamQuery> queryProvider;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  DeprecatedChangeQueryServlet(Provider<QueryProcessor> processor) {
 | 
			
		||||
    this.processor = processor;
 | 
			
		||||
  DeprecatedChangeQueryServlet(Provider<OutputStreamQuery> queryProvider) {
 | 
			
		||||
    this.queryProvider = queryProvider;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
@@ -44,7 +44,7 @@ public class DeprecatedChangeQueryServlet extends HttpServlet {
 | 
			
		||||
    rsp.setContentType("text/json");
 | 
			
		||||
    rsp.setCharacterEncoding("UTF-8");
 | 
			
		||||
 | 
			
		||||
    QueryProcessor p = processor.get();
 | 
			
		||||
    OutputStreamQuery p = queryProvider.get();
 | 
			
		||||
    OutputFormat format = OutputFormat.JSON;
 | 
			
		||||
    try {
 | 
			
		||||
      format = OutputFormat.valueOf(get(req, "format", format.toString()));
 | 
			
		||||
 
 | 
			
		||||
@@ -433,9 +433,7 @@ public final class Change {
 | 
			
		||||
  @Column(id = 5)
 | 
			
		||||
  protected Timestamp lastUpdatedOn;
 | 
			
		||||
 | 
			
		||||
  /** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */
 | 
			
		||||
  @Column(id = 6, length = 16)
 | 
			
		||||
  protected String sortKey;
 | 
			
		||||
  // DELETED: id = 6 (sortkey)
 | 
			
		||||
 | 
			
		||||
  @Column(id = 7, name = "owner_account_id")
 | 
			
		||||
  protected Account.Id owner;
 | 
			
		||||
@@ -489,7 +487,6 @@ public final class Change {
 | 
			
		||||
    rowVersion = other.rowVersion;
 | 
			
		||||
    createdOn = other.createdOn;
 | 
			
		||||
    lastUpdatedOn = other.lastUpdatedOn;
 | 
			
		||||
    sortKey = other.sortKey;
 | 
			
		||||
    owner = other.owner;
 | 
			
		||||
    dest = other.dest;
 | 
			
		||||
    open = other.open;
 | 
			
		||||
@@ -534,14 +531,6 @@ public final class Change {
 | 
			
		||||
    return rowVersion;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public String getSortKey() {
 | 
			
		||||
    return sortKey;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setSortKey(final String newSortKey) {
 | 
			
		||||
    sortKey = newSortKey;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Account.Id getOwner() {
 | 
			
		||||
    return owner;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -55,11 +55,6 @@ public interface ChangeAccess extends Access<Change, Change.Id> {
 | 
			
		||||
  @Query("WHERE open = true AND dest = ?")
 | 
			
		||||
  ResultSet<Change> byBranchOpenAll(Branch.NameKey p) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query("WHERE open = true AND dest.projectName = ? AND sortKey < ?"
 | 
			
		||||
      + " ORDER BY sortKey DESC LIMIT ?")
 | 
			
		||||
  ResultSet<Change> byProjectOpenNext(Project.NameKey p, String sortKey,
 | 
			
		||||
      int limit) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query
 | 
			
		||||
  ResultSet<Change> all() throws OrmException;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on);
 | 
			
		||||
 | 
			
		||||
--    covers:             byProjectOpenAll
 | 
			
		||||
CREATE INDEX changes_byProjectOpen
 | 
			
		||||
ON changes (open, dest_project_name, sort_key);
 | 
			
		||||
ON changes (open, dest_project_name, last_updated_on);
 | 
			
		||||
 | 
			
		||||
--    covers:             byProject
 | 
			
		||||
CREATE INDEX changes_byProject
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on)
 | 
			
		||||
 | 
			
		||||
--    covers:             byProjectOpenPrev, byProjectOpenNext
 | 
			
		||||
CREATE INDEX changes_byProjectOpen
 | 
			
		||||
ON changes (open, dest_project_name, sort_key)
 | 
			
		||||
ON changes (open, dest_project_name, last_updated_on);
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
--    covers:             byProject
 | 
			
		||||
 
 | 
			
		||||
@@ -124,7 +124,7 @@ WHERE status = 's';
 | 
			
		||||
 | 
			
		||||
--    covers:             byProjectOpenAll
 | 
			
		||||
CREATE INDEX changes_byProjectOpen
 | 
			
		||||
ON changes (dest_project_name, sort_key)
 | 
			
		||||
ON changes (dest_project_name, last_updated_on)
 | 
			
		||||
WHERE open = 'Y';
 | 
			
		||||
 | 
			
		||||
--    covers:             byProject
 | 
			
		||||
 
 | 
			
		||||
@@ -15,15 +15,10 @@
 | 
			
		||||
package com.google.gerrit.server;
 | 
			
		||||
 | 
			
		||||
import static com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy.RECEIVE_COMMITS;
 | 
			
		||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
 | 
			
		||||
import static java.util.concurrent.TimeUnit.MINUTES;
 | 
			
		||||
import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.common.base.Optional;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.primitives.Ints;
 | 
			
		||||
import com.google.gerrit.common.TimeUtil;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
@@ -85,15 +80,6 @@ import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class ChangeUtil {
 | 
			
		||||
  /**
 | 
			
		||||
   * Epoch for sort key calculations, Tue Sep 30 2008 17:00:00.
 | 
			
		||||
   * <p>
 | 
			
		||||
   * We overrun approximately 4,083 years later, so ~6092.
 | 
			
		||||
   */
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  private static final long SORT_KEY_EPOCH_MINS =
 | 
			
		||||
      MINUTES.convert(1222819200L, SECONDS);
 | 
			
		||||
 | 
			
		||||
  private static final Object uuidLock = new Object();
 | 
			
		||||
  private static final int SEED = 0x2418e6f9;
 | 
			
		||||
  private static int uuidPrefix;
 | 
			
		||||
@@ -150,7 +136,6 @@ public class ChangeUtil {
 | 
			
		||||
 | 
			
		||||
  public static void updated(Change c) {
 | 
			
		||||
    c.setLastUpdatedOn(TimeUtil.nowTs());
 | 
			
		||||
    computeSortKey(c);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
 | 
			
		||||
@@ -166,29 +151,6 @@ public class ChangeUtil {
 | 
			
		||||
    db.patchSetAncestors().insert(toInsert);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static String sortKey(long lastUpdatedMs, int id) {
 | 
			
		||||
    long lastUpdatedMins = MINUTES.convert(lastUpdatedMs, MILLISECONDS);
 | 
			
		||||
    long minsSinceEpoch = lastUpdatedMins - SORT_KEY_EPOCH_MINS;
 | 
			
		||||
    StringBuilder r = new StringBuilder(16);
 | 
			
		||||
    r.setLength(16);
 | 
			
		||||
    formatHexInt(r, 0, Ints.checkedCast(minsSinceEpoch));
 | 
			
		||||
    formatHexInt(r, 8, id);
 | 
			
		||||
    return r.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static long parseSortKey(String sortKey) {
 | 
			
		||||
    if ("z".equals(sortKey)) {
 | 
			
		||||
      return Long.MAX_VALUE;
 | 
			
		||||
    }
 | 
			
		||||
    return Long.parseLong(sortKey, 16);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void computeSortKey(Change c) {
 | 
			
		||||
    long lastUpdatedMs = c.getLastUpdatedOn().getTime();
 | 
			
		||||
    int id = c.getId().get();
 | 
			
		||||
    c.setSortKey(sortKey(lastUpdatedMs, id));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static PatchSet.Id nextPatchSetId(Map<String, Ref> allRefs,
 | 
			
		||||
      PatchSet.Id id) {
 | 
			
		||||
    PatchSet.Id next = nextPatchSetId(id);
 | 
			
		||||
@@ -585,19 +547,4 @@ public class ChangeUtil {
 | 
			
		||||
  public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
 | 
			
		||||
    return new PatchSet.Id(id.getParentKey(), id.get() + 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final char[] hexchar =
 | 
			
		||||
      {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
 | 
			
		||||
          'a', 'b', 'c', 'd', 'e', 'f'};
 | 
			
		||||
 | 
			
		||||
  private static void formatHexInt(final StringBuilder dst, final int p, int w) {
 | 
			
		||||
    int o = p + 7;
 | 
			
		||||
    while (o >= p && w != 0) {
 | 
			
		||||
      dst.setCharAt(o--, hexchar[w & 0xf]);
 | 
			
		||||
      w >>>= 4;
 | 
			
		||||
    }
 | 
			
		||||
    while (o >= p) {
 | 
			
		||||
      dst.setCharAt(o--, '0');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -136,7 +136,6 @@ public class ChangeInserter {
 | 
			
		||||
    patchSet.setRevision(new RevId(commit.name()));
 | 
			
		||||
    patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
 | 
			
		||||
    change.setCurrentPatchSet(patchSetInfo);
 | 
			
		||||
    ChangeUtil.computeSortKey(change);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Change getChange() {
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,7 @@ import com.google.gerrit.server.project.ChangeControl;
 | 
			
		||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryResult;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -252,10 +253,16 @@ public class ChangeJson {
 | 
			
		||||
    return format(cd, Optional.of(rsrc.getPatchSet().getId()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
 | 
			
		||||
  public List<List<ChangeInfo>> formatQueryResults(List<QueryResult> in)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
 | 
			
		||||
    Iterable<ChangeData> all = Iterables.concat(in);
 | 
			
		||||
    Iterable<ChangeData> all = FluentIterable.from(in)
 | 
			
		||||
        .transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
 | 
			
		||||
          @Override
 | 
			
		||||
          public List<ChangeData> apply(QueryResult in) {
 | 
			
		||||
            return in.changes();
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
    ChangeData.ensureChangeLoaded(all);
 | 
			
		||||
    if (has(ALL_REVISIONS)) {
 | 
			
		||||
      ChangeData.ensureAllPatchSetsLoaded(all);
 | 
			
		||||
@@ -270,8 +277,12 @@ public class ChangeJson {
 | 
			
		||||
 | 
			
		||||
    List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
 | 
			
		||||
    Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
 | 
			
		||||
    for (List<ChangeData> changes : in) {
 | 
			
		||||
      res.add(toChangeInfo(out, changes, reviewed));
 | 
			
		||||
    for (QueryResult r : in) {
 | 
			
		||||
      List<ChangeInfo> infos = toChangeInfo(out, r.changes(), reviewed);
 | 
			
		||||
      if (r.moreChanges()) {
 | 
			
		||||
        infos.get(infos.size() - 1)._moreChanges = true;
 | 
			
		||||
      }
 | 
			
		||||
      res.add(infos);
 | 
			
		||||
    }
 | 
			
		||||
    accountLoader.fill();
 | 
			
		||||
    return res;
 | 
			
		||||
@@ -368,7 +379,6 @@ public class ChangeJson {
 | 
			
		||||
    out.created = in.getCreatedOn();
 | 
			
		||||
    out.updated = in.getLastUpdatedOn();
 | 
			
		||||
    out._number = in.getId().get();
 | 
			
		||||
    out._sortkey = in.getSortKey();
 | 
			
		||||
    out.starred = userProvider.get().getStarredChanges().contains(in.getId())
 | 
			
		||||
        ? true
 | 
			
		||||
        : null;
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ import com.google.gerrit.reviewdb.client.RevId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.ApprovalsUtil;
 | 
			
		||||
import com.google.gerrit.server.ChangeMessagesUtil;
 | 
			
		||||
import com.google.gerrit.server.ChangeUtil;
 | 
			
		||||
import com.google.gerrit.server.GerritPersonIdent;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.ProjectUtil;
 | 
			
		||||
@@ -271,7 +270,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
 | 
			
		||||
            if (change.getStatus().isOpen()) {
 | 
			
		||||
              change.setStatus(Change.Status.SUBMITTED);
 | 
			
		||||
              change.setLastUpdatedOn(timestamp);
 | 
			
		||||
              ChangeUtil.computeSortKey(change);
 | 
			
		||||
              return change;
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ public class ChangeAttribute {
 | 
			
		||||
 | 
			
		||||
    public Long createdOn;
 | 
			
		||||
    public Long lastUpdated;
 | 
			
		||||
    public String sortKey;
 | 
			
		||||
    public Boolean open;
 | 
			
		||||
    public Change.Status status;
 | 
			
		||||
    public List<MessageAttribute> comments;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,5 +18,5 @@ public class QueryStatsAttribute {
 | 
			
		||||
  public final String type = "stats";
 | 
			
		||||
  public int rowCount;
 | 
			
		||||
  public long runTimeMilliseconds;
 | 
			
		||||
  public String resumeSortKey;
 | 
			
		||||
  public boolean moreChanges;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -164,7 +164,6 @@ public class EventFactory {
 | 
			
		||||
  public void extend(ChangeAttribute a, Change change) {
 | 
			
		||||
    a.createdOn = change.getCreatedOn().getTime() / 1000L;
 | 
			
		||||
    a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
 | 
			
		||||
    a.sortKey = change.getSortKey();
 | 
			
		||||
    a.open = change.getStatus().isOpen();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,7 @@ import com.google.gerrit.server.project.ProjectControl;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectState;
 | 
			
		||||
import com.google.gerrit.server.project.RefControl;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryProcessor;
 | 
			
		||||
import com.google.gerrit.server.ssh.SshInfo;
 | 
			
		||||
import com.google.gerrit.server.util.LabelVote;
 | 
			
		||||
import com.google.gerrit.server.util.MagicBranch;
 | 
			
		||||
@@ -336,6 +337,7 @@ public class ReceiveCommits {
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  ReceiveCommits(final ReviewDb db,
 | 
			
		||||
      final Provider<QueryProcessor> queryProcessor,
 | 
			
		||||
      final SchemaFactory<ReviewDb> schemaFactory,
 | 
			
		||||
      final ChangeData.Factory changeDataFactory,
 | 
			
		||||
      final ChangeUpdate.Factory updateFactory,
 | 
			
		||||
@@ -472,7 +474,7 @@ public class ReceiveCommits {
 | 
			
		||||
    });
 | 
			
		||||
    advHooks.add(rp.getAdvertiseRefsHook());
 | 
			
		||||
    advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
 | 
			
		||||
        db, projectControl.getProject().getNameKey()));
 | 
			
		||||
        db, queryProcessor, projectControl.getProject().getNameKey()));
 | 
			
		||||
    rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,13 +18,18 @@ import static org.eclipse.jgit.lib.RefDatabase.ALL;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.RefNames;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryProcessor;
 | 
			
		||||
import com.google.gerrit.server.util.MagicBranch;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.lib.Constants;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
@@ -39,6 +44,7 @@ import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
@@ -48,11 +54,14 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
 | 
			
		||||
      .getLogger(ReceiveCommitsAdvertiseRefsHook.class);
 | 
			
		||||
 | 
			
		||||
  private final ReviewDb db;
 | 
			
		||||
  private final Provider<QueryProcessor> queryProcessor;
 | 
			
		||||
  private final Project.NameKey projectName;
 | 
			
		||||
 | 
			
		||||
  public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
 | 
			
		||||
      Provider<QueryProcessor> queryProcessor,
 | 
			
		||||
      Project.NameKey projectName) {
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.queryProcessor = queryProcessor;
 | 
			
		||||
    this.projectName = projectName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -93,10 +102,11 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
 | 
			
		||||
    Set<ObjectId> toInclude = Sets.newHashSet();
 | 
			
		||||
 | 
			
		||||
    // Advertise some recent open changes, in case a commit is based one.
 | 
			
		||||
    final int limit = 32;
 | 
			
		||||
    try {
 | 
			
		||||
      Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(32);
 | 
			
		||||
      for (Change c : db.changes().byProjectOpenNext(projectName, "z", 32)) {
 | 
			
		||||
        PatchSet.Id id = c.currentPatchSetId();
 | 
			
		||||
      Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
 | 
			
		||||
      for (ChangeData cd : queryRecentChanges(limit)) {
 | 
			
		||||
        PatchSet.Id id = cd.change().currentPatchSetId();
 | 
			
		||||
        if (id != null) {
 | 
			
		||||
          toGet.add(id);
 | 
			
		||||
        }
 | 
			
		||||
@@ -164,6 +174,20 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
 | 
			
		||||
    return toInclude;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<ChangeData> queryRecentChanges(int limit)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    QueryProcessor qp = queryProcessor.get();
 | 
			
		||||
    qp.setLimit(limit);
 | 
			
		||||
    ChangeQueryBuilder qb = qp.getQueryBuilder();
 | 
			
		||||
    Predicate<ChangeData> p =
 | 
			
		||||
        Predicate.and(qb.project(projectName.get()), qb.status_open());
 | 
			
		||||
    try {
 | 
			
		||||
      return qp.queryChanges(p).changes();
 | 
			
		||||
    } catch (QueryParseException e) {
 | 
			
		||||
      throw new OrmException(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean skip(String name) {
 | 
			
		||||
    return name.startsWith(RefNames.REFS_CHANGES)
 | 
			
		||||
        || name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,8 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.index;
 | 
			
		||||
 | 
			
		||||
import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkArgument;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.MoreObjects;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
@@ -29,9 +28,9 @@ import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
import com.google.gerrit.server.query.change.AndSource;
 | 
			
		||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.change.LimitPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.change.OrSource;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
@@ -129,16 +128,14 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
 | 
			
		||||
      throws QueryParseException {
 | 
			
		||||
  public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start,
 | 
			
		||||
      int limit) throws QueryParseException {
 | 
			
		||||
    checkArgument(limit > 0, "limit must be positive: %s", limit);
 | 
			
		||||
    ChangeIndex index = indexes.getSearchIndex();
 | 
			
		||||
    in = basicRewrites.rewrite(in);
 | 
			
		||||
    int limit = MoreObjects.firstNonNull(
 | 
			
		||||
        ChangeQueryBuilder.getLimit(in), DEFAULT_MAX_QUERY_LIMIT);
 | 
			
		||||
    // Increase the limit rather than skipping, since we don't know how many
 | 
			
		||||
    // skipped results would have been filtered out by the enclosing AndSource.
 | 
			
		||||
    limit += start;
 | 
			
		||||
    limit = Math.max(limit, 1);
 | 
			
		||||
 | 
			
		||||
    Predicate<ChangeData> out = rewriteImpl(in, index, limit);
 | 
			
		||||
    if (in == out || out instanceof IndexPredicate) {
 | 
			
		||||
@@ -168,6 +165,9 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
 | 
			
		||||
      ChangeIndex index, int limit) throws QueryParseException {
 | 
			
		||||
    if (isIndexPredicate(in, index)) {
 | 
			
		||||
      return in;
 | 
			
		||||
    } else if (in instanceof LimitPredicate) {
 | 
			
		||||
      // Replace any limits with the limit provided by the caller.
 | 
			
		||||
      return new LimitPredicate(limit);
 | 
			
		||||
    } else if (!isRewritePossible(in)) {
 | 
			
		||||
      return null; // magic to indicate "in" cannot be rewritten
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,8 @@ public abstract class QueryBuilder<T> {
 | 
			
		||||
   * @param p the predicate to find.
 | 
			
		||||
   * @param clazz type of the predicate instance.
 | 
			
		||||
   * @param name name of the operator.
 | 
			
		||||
   * @return the predicate, null if not found.
 | 
			
		||||
   * @return the first instance of a predicate having the given type, as found
 | 
			
		||||
   *     by a depth-first search.
 | 
			
		||||
   */
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  public static <T, P extends OperatorPredicate<T>> P find(Predicate<T> p,
 | 
			
		||||
 
 | 
			
		||||
@@ -16,13 +16,11 @@ package com.google.gerrit.server.query.change;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.query.IntPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryRewriter;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.OutOfScopeException;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import com.google.inject.name.Named;
 | 
			
		||||
 | 
			
		||||
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
 | 
			
		||||
  private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
 | 
			
		||||
@@ -69,14 +67,6 @@ public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
 | 
			
		||||
        ChangeStatusPredicate.forStatus(Change.Status.MERGED));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @NoCostComputation
 | 
			
		||||
  @Rewrite("A=(limit:*) B=(limit:*)")
 | 
			
		||||
  public Predicate<ChangeData> r00_smallestLimit(
 | 
			
		||||
      @Named("A") IntPredicate<ChangeData> a,
 | 
			
		||||
      @Named("B") IntPredicate<ChangeData> b) {
 | 
			
		||||
    return a.intValue() <= b.intValue() ? a : b;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static final class InvalidProvider<T> implements Provider<T> {
 | 
			
		||||
    @Override
 | 
			
		||||
    public T get() {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,6 @@ import com.google.gerrit.server.patch.PatchListCache;
 | 
			
		||||
import com.google.gerrit.server.project.ChangeControl;
 | 
			
		||||
import com.google.gerrit.server.project.ListChildProjects;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectCache;
 | 
			
		||||
import com.google.gerrit.server.query.IntPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryBuilder;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
@@ -121,12 +120,6 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
 | 
			
		||||
  private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
 | 
			
		||||
      new QueryBuilder.Definition<>(ChangeQueryBuilder.class);
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  public static Integer getLimit(Predicate<ChangeData> p) {
 | 
			
		||||
    IntPredicate<?> ip = find(p, IntPredicate.class, FIELD_LIMIT);
 | 
			
		||||
    return ip != null ? ip.intValue() : null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  public static class Arguments {
 | 
			
		||||
    final Provider<ReviewDb> db;
 | 
			
		||||
@@ -633,28 +626,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Operator
 | 
			
		||||
  public Predicate<ChangeData> limit(String limit) {
 | 
			
		||||
    return limit(Integer.parseInt(limit));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class LimitPredicate extends IntPredicate<ChangeData> {
 | 
			
		||||
    LimitPredicate(int limit) {
 | 
			
		||||
      super(FIELD_LIMIT, limit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean match(ChangeData object) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getCost() {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Predicate<ChangeData> limit(int limit) {
 | 
			
		||||
    return new LimitPredicate(limit);
 | 
			
		||||
  public Predicate<ChangeData> limit(String limit) throws QueryParseException {
 | 
			
		||||
    return new LimitPredicate(Integer.parseInt(limit));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Operator
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,6 @@ import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
 | 
			
		||||
public interface ChangeQueryRewriter {
 | 
			
		||||
  Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
 | 
			
		||||
  Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start, int limit)
 | 
			
		||||
      throws QueryParseException;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
// 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 static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_LIMIT;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.query.IntPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryBuilder;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
 | 
			
		||||
public class LimitPredicate extends IntPredicate<ChangeData> {
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  public static Integer getLimit(Predicate<ChangeData> p) {
 | 
			
		||||
    IntPredicate<?> ip = QueryBuilder.find(p, IntPredicate.class, FIELD_LIMIT);
 | 
			
		||||
    return ip != null ? ip.intValue() : null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public LimitPredicate(int limit) throws QueryParseException {
 | 
			
		||||
    super(ChangeQueryBuilder.FIELD_LIMIT, limit);
 | 
			
		||||
    if (limit <= 0) {
 | 
			
		||||
      throw new QueryParseException("limit must be positive: " + limit);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean match(ChangeData object) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public int getCost() {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,405 @@
 | 
			
		||||
// 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.common.TimeUtil;
 | 
			
		||||
import com.google.gerrit.common.data.LabelTypes;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.CurrentUser;
 | 
			
		||||
import com.google.gerrit.server.config.TrackingFooters;
 | 
			
		||||
import com.google.gerrit.server.data.ChangeAttribute;
 | 
			
		||||
import com.google.gerrit.server.data.PatchSetAttribute;
 | 
			
		||||
import com.google.gerrit.server.data.QueryStatsAttribute;
 | 
			
		||||
import com.google.gerrit.server.events.EventFactory;
 | 
			
		||||
import com.google.gerrit.server.project.ChangeControl;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.util.io.DisabledOutputStream;
 | 
			
		||||
import org.joda.time.format.DateTimeFormat;
 | 
			
		||||
import org.joda.time.format.DateTimeFormatter;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedWriter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.io.OutputStreamWriter;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change query implementation that outputs to a stream in the style of an SSH
 | 
			
		||||
 * command.
 | 
			
		||||
 */
 | 
			
		||||
public class OutputStreamQuery {
 | 
			
		||||
  private static final Logger log =
 | 
			
		||||
      LoggerFactory.getLogger(OutputStreamQuery.class);
 | 
			
		||||
 | 
			
		||||
  private static final DateTimeFormatter dtf =
 | 
			
		||||
      DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
 | 
			
		||||
 | 
			
		||||
  public static enum OutputFormat {
 | 
			
		||||
    TEXT, JSON
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final QueryProcessor queryProcessor;
 | 
			
		||||
  private final EventFactory eventFactory;
 | 
			
		||||
  private final TrackingFooters trackingFooters;
 | 
			
		||||
  private final CurrentUser user;
 | 
			
		||||
 | 
			
		||||
  private OutputFormat outputFormat = OutputFormat.TEXT;
 | 
			
		||||
  private boolean includePatchSets;
 | 
			
		||||
  private boolean includeCurrentPatchSet;
 | 
			
		||||
  private boolean includeApprovals;
 | 
			
		||||
  private boolean includeComments;
 | 
			
		||||
  private boolean includeFiles;
 | 
			
		||||
  private boolean includeCommitMessage;
 | 
			
		||||
  private boolean includeDependencies;
 | 
			
		||||
  private boolean includeSubmitRecords;
 | 
			
		||||
  private boolean includeAllReviewers;
 | 
			
		||||
 | 
			
		||||
  private OutputStream outputStream = DisabledOutputStream.INSTANCE;
 | 
			
		||||
  private PrintWriter out;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  OutputStreamQuery(QueryProcessor queryProcessor,
 | 
			
		||||
      EventFactory eventFactory,
 | 
			
		||||
      TrackingFooters trackingFooters,
 | 
			
		||||
      CurrentUser user) {
 | 
			
		||||
    this.queryProcessor = queryProcessor;
 | 
			
		||||
    this.eventFactory = eventFactory;
 | 
			
		||||
    this.trackingFooters = trackingFooters;
 | 
			
		||||
    this.user = user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setLimit(int n) {
 | 
			
		||||
    queryProcessor.setLimit(n);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setStart(int n) {
 | 
			
		||||
    queryProcessor.setStart(n);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludePatchSets(boolean on) {
 | 
			
		||||
    includePatchSets = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludePatchSets() {
 | 
			
		||||
    return includePatchSets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeCurrentPatchSet(boolean on) {
 | 
			
		||||
    includeCurrentPatchSet = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeCurrentPatchSet() {
 | 
			
		||||
    return includeCurrentPatchSet;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeApprovals(boolean on) {
 | 
			
		||||
    includeApprovals = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeComments(boolean on) {
 | 
			
		||||
    includeComments = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeFiles(boolean on) {
 | 
			
		||||
    includeFiles = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeFiles() {
 | 
			
		||||
    return includeFiles;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeDependencies(boolean on) {
 | 
			
		||||
    includeDependencies = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeDependencies() {
 | 
			
		||||
    return includeDependencies;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeCommitMessage(boolean on) {
 | 
			
		||||
    includeCommitMessage = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeSubmitRecords(boolean on) {
 | 
			
		||||
    includeSubmitRecords = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeAllReviewers(boolean on) {
 | 
			
		||||
    includeAllReviewers = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setOutput(OutputStream out, OutputFormat fmt) {
 | 
			
		||||
    this.outputStream = out;
 | 
			
		||||
    this.outputFormat = fmt;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void query(String queryString) throws IOException {
 | 
			
		||||
    out = new PrintWriter( //
 | 
			
		||||
        new BufferedWriter( //
 | 
			
		||||
            new OutputStreamWriter(outputStream, "UTF-8")));
 | 
			
		||||
    try {
 | 
			
		||||
      if (queryProcessor.isDisabled()) {
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "query disabled";
 | 
			
		||||
        show(m);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        final QueryStatsAttribute stats = new QueryStatsAttribute();
 | 
			
		||||
        stats.runTimeMilliseconds = TimeUtil.nowMs();
 | 
			
		||||
 | 
			
		||||
        QueryResult results = queryProcessor.queryByString(queryString);
 | 
			
		||||
        ChangeAttribute c = null;
 | 
			
		||||
        for (ChangeData d : results.changes()) {
 | 
			
		||||
          ChangeControl cc = d.changeControl().forUser(user);
 | 
			
		||||
 | 
			
		||||
          LabelTypes labelTypes = cc.getLabelTypes();
 | 
			
		||||
          c = eventFactory.asChangeAttribute(d.change());
 | 
			
		||||
          eventFactory.extend(c, d.change());
 | 
			
		||||
 | 
			
		||||
          if (!trackingFooters.isEmpty()) {
 | 
			
		||||
            eventFactory.addTrackingIds(c,
 | 
			
		||||
                trackingFooters.extract(d.commitFooters()));
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeAllReviewers) {
 | 
			
		||||
            eventFactory.addAllReviewers(c, d.notes());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeSubmitRecords) {
 | 
			
		||||
            eventFactory.addSubmitRecords(c, new SubmitRuleEvaluator(d)
 | 
			
		||||
                .setAllowClosed(true)
 | 
			
		||||
                .setAllowDraft(true)
 | 
			
		||||
                .canSubmit());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeCommitMessage) {
 | 
			
		||||
            eventFactory.addCommitMessage(c, d.commitMessage());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includePatchSets) {
 | 
			
		||||
            if (includeFiles) {
 | 
			
		||||
              eventFactory.addPatchSets(c, d.patches(),
 | 
			
		||||
                includeApprovals ? d.approvals().asMap() : null,
 | 
			
		||||
                includeFiles, d.change(), labelTypes);
 | 
			
		||||
            } else {
 | 
			
		||||
              eventFactory.addPatchSets(c, d.patches(),
 | 
			
		||||
                  includeApprovals ? d.approvals().asMap() : null,
 | 
			
		||||
                  labelTypes);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeCurrentPatchSet) {
 | 
			
		||||
            PatchSet current = d.currentPatchSet();
 | 
			
		||||
            if (current != null) {
 | 
			
		||||
              c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
 | 
			
		||||
              eventFactory.addApprovals(c.currentPatchSet,
 | 
			
		||||
                  d.currentApprovals(), labelTypes);
 | 
			
		||||
 | 
			
		||||
              if (includeFiles) {
 | 
			
		||||
                eventFactory.addPatchSetFileNames(c.currentPatchSet,
 | 
			
		||||
                    d.change(), d.currentPatchSet());
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeComments) {
 | 
			
		||||
            eventFactory.addComments(c, d.messages());
 | 
			
		||||
            if (includePatchSets) {
 | 
			
		||||
              for (PatchSetAttribute attribute : c.patchSets) {
 | 
			
		||||
                eventFactory.addPatchSetComments(attribute,  d.publishedComments());
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeDependencies) {
 | 
			
		||||
            eventFactory.addDependencies(c, d.change());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          show(c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stats.rowCount = results.changes().size();
 | 
			
		||||
        stats.moreChanges = results.moreChanges();
 | 
			
		||||
        stats.runTimeMilliseconds =
 | 
			
		||||
            TimeUtil.nowMs() - stats.runTimeMilliseconds;
 | 
			
		||||
        show(stats);
 | 
			
		||||
      } catch (OrmException err) {
 | 
			
		||||
        log.error("Cannot execute query: " + queryString, err);
 | 
			
		||||
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "cannot query database";
 | 
			
		||||
        show(m);
 | 
			
		||||
 | 
			
		||||
      } catch (QueryParseException e) {
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = e.getMessage();
 | 
			
		||||
        show(m);
 | 
			
		||||
      } catch (NoSuchChangeException e) {
 | 
			
		||||
        log.error("Missing change: " + e.getMessage(), e);
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "missing change " + e.getMessage();
 | 
			
		||||
        show(m);
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      try {
 | 
			
		||||
        out.flush();
 | 
			
		||||
      } finally {
 | 
			
		||||
        out = null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void show(Object data) {
 | 
			
		||||
    switch (outputFormat) {
 | 
			
		||||
      default:
 | 
			
		||||
      case TEXT:
 | 
			
		||||
        if (data instanceof ChangeAttribute) {
 | 
			
		||||
          out.print("change ");
 | 
			
		||||
          out.print(((ChangeAttribute) data).id);
 | 
			
		||||
          out.print("\n");
 | 
			
		||||
          showText(data, 1);
 | 
			
		||||
        } else {
 | 
			
		||||
          showText(data, 0);
 | 
			
		||||
        }
 | 
			
		||||
        out.print('\n');
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case JSON:
 | 
			
		||||
        out.print(new Gson().toJson(data));
 | 
			
		||||
        out.print('\n');
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void showText(Object data, int depth) {
 | 
			
		||||
    for (Field f : fieldsOf(data.getClass())) {
 | 
			
		||||
      Object val;
 | 
			
		||||
      try {
 | 
			
		||||
        val = f.get(data);
 | 
			
		||||
      } catch (IllegalArgumentException err) {
 | 
			
		||||
        continue;
 | 
			
		||||
      } catch (IllegalAccessException err) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (val == null) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      showField(f.getName(), val, depth);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private String indent(int spaces) {
 | 
			
		||||
    if (spaces == 0) {
 | 
			
		||||
      return "";
 | 
			
		||||
    } else {
 | 
			
		||||
      return String.format("%" + spaces + "s", " ");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void showField(String field, Object value, int depth) {
 | 
			
		||||
    final int spacesDepthRatio = 2;
 | 
			
		||||
    String indent = indent(depth * spacesDepthRatio);
 | 
			
		||||
    out.print(indent);
 | 
			
		||||
    out.print(field);
 | 
			
		||||
    out.print(':');
 | 
			
		||||
    if (value instanceof String && ((String) value).contains("\n")) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      // Idention for multi-line text is
 | 
			
		||||
      // current depth indetion + length of field + length of ": "
 | 
			
		||||
      indent = indent(indent.length() + field.length() + spacesDepthRatio);
 | 
			
		||||
      out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (value instanceof Long && isDateField(field)) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      out.print(dtf.print(((Long) value) * 1000L));
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (isPrimitive(value)) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      out.print(value);
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (value instanceof Collection) {
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
      boolean firstElement = true;
 | 
			
		||||
      for (Object thing : ((Collection<?>) value)) {
 | 
			
		||||
        // The name of the collection was initially printed at the beginning
 | 
			
		||||
        // of this routine.  Beginning at the second sub-element, reprint
 | 
			
		||||
        // the collection name so humans can separate individual elements
 | 
			
		||||
        // with less strain and error.
 | 
			
		||||
        //
 | 
			
		||||
        if (firstElement) {
 | 
			
		||||
          firstElement = false;
 | 
			
		||||
        } else {
 | 
			
		||||
          out.print(indent);
 | 
			
		||||
          out.print(field);
 | 
			
		||||
          out.print(":\n");
 | 
			
		||||
        }
 | 
			
		||||
        if (isPrimitive(thing)) {
 | 
			
		||||
          out.print(' ');
 | 
			
		||||
          out.print(value);
 | 
			
		||||
          out.print('\n');
 | 
			
		||||
        } else {
 | 
			
		||||
          showText(thing, depth + 1);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
      showText(value, depth + 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean isPrimitive(Object value) {
 | 
			
		||||
    return value instanceof String //
 | 
			
		||||
        || value instanceof Number //
 | 
			
		||||
        || value instanceof Boolean //
 | 
			
		||||
        || value instanceof Enum;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean isDateField(String name) {
 | 
			
		||||
    return "lastUpdated".equals(name) //
 | 
			
		||||
        || "grantedOn".equals(name) //
 | 
			
		||||
        || "timestamp".equals(name) //
 | 
			
		||||
        || "createdOn".equals(name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<Field> fieldsOf(Class<?> type) {
 | 
			
		||||
    List<Field> r = new ArrayList<>();
 | 
			
		||||
    if (type.getSuperclass() != null) {
 | 
			
		||||
      r.addAll(fieldsOf(type.getSuperclass()));
 | 
			
		||||
    }
 | 
			
		||||
    r.addAll(Arrays.asList(type.getDeclaredFields()));
 | 
			
		||||
    return r;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class ErrorMessage {
 | 
			
		||||
    public final String type = "error";
 | 
			
		||||
    public String message;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,6 @@ import com.google.inject.Provider;
 | 
			
		||||
 | 
			
		||||
import org.kohsuke.args4j.Option;
 | 
			
		||||
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.EnumSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
@@ -42,7 +41,6 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
 | 
			
		||||
  private final ChangeJson json;
 | 
			
		||||
  private final QueryProcessor imp;
 | 
			
		||||
  private final Provider<CurrentUser> user;
 | 
			
		||||
  private boolean reverse;
 | 
			
		||||
  private EnumSet<ListChangesOption> options;
 | 
			
		||||
 | 
			
		||||
  @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
 | 
			
		||||
@@ -137,30 +135,13 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
 | 
			
		||||
  private List<List<ChangeInfo>> query0() throws OrmException,
 | 
			
		||||
      QueryParseException {
 | 
			
		||||
    int cnt = queries.size();
 | 
			
		||||
    BitSet more = new BitSet(cnt);
 | 
			
		||||
    List<List<ChangeData>> data = imp.queryChanges(queries);
 | 
			
		||||
    for (int n = 0; n < cnt; n++) {
 | 
			
		||||
      List<ChangeData> changes = data.get(n);
 | 
			
		||||
      if (imp.getLimit() > 0 && changes.size() > imp.getLimit()) {
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
          changes = changes.subList(1, changes.size());
 | 
			
		||||
        } else {
 | 
			
		||||
          changes = changes.subList(0, imp.getLimit());
 | 
			
		||||
        }
 | 
			
		||||
        data.set(n, changes);
 | 
			
		||||
        more.set(n, true);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    List<List<ChangeInfo>> res = json.addOptions(options).formatList2(data);
 | 
			
		||||
    List<QueryResult> results = imp.queryByStrings(queries);
 | 
			
		||||
    List<List<ChangeInfo>> res = json.addOptions(options)
 | 
			
		||||
        .formatQueryResults(results);
 | 
			
		||||
    for (int n = 0; n < cnt; n++) {
 | 
			
		||||
      List<ChangeInfo> info = res.get(n);
 | 
			
		||||
      if (more.get(n) && !info.isEmpty()) {
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
          info.get(0)._moreChanges = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          info.get(info.size() - 1)._moreChanges = true;
 | 
			
		||||
        }
 | 
			
		||||
      if (results.get(n).moreChanges()) {
 | 
			
		||||
        info.get(info.size() - 1)._moreChanges = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,494 +14,178 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.query.change;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.MoreObjects;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.gerrit.common.TimeUtil;
 | 
			
		||||
import com.google.common.collect.Ordering;
 | 
			
		||||
import com.google.gerrit.common.data.GlobalCapability;
 | 
			
		||||
import com.google.gerrit.common.data.LabelTypes;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.CurrentUser;
 | 
			
		||||
import com.google.gerrit.server.config.TrackingFooters;
 | 
			
		||||
import com.google.gerrit.server.data.ChangeAttribute;
 | 
			
		||||
import com.google.gerrit.server.data.PatchSetAttribute;
 | 
			
		||||
import com.google.gerrit.server.data.QueryStatsAttribute;
 | 
			
		||||
import com.google.gerrit.server.events.EventFactory;
 | 
			
		||||
import com.google.gerrit.server.project.ChangeControl;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
 | 
			
		||||
import com.google.gerrit.server.index.IndexPredicate;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
import com.google.gerrit.server.query.QueryParseException;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.gwtorm.server.ResultSet;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.util.io.DisabledOutputStream;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedWriter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.io.OutputStreamWriter;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
import java.text.SimpleDateFormat;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class QueryProcessor {
 | 
			
		||||
  private static final Logger log =
 | 
			
		||||
      LoggerFactory.getLogger(QueryProcessor.class);
 | 
			
		||||
 | 
			
		||||
  public static enum OutputFormat {
 | 
			
		||||
    TEXT, JSON
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final Gson gson = new Gson();
 | 
			
		||||
  private final SimpleDateFormat sdf =
 | 
			
		||||
      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
 | 
			
		||||
 | 
			
		||||
  private final EventFactory eventFactory;
 | 
			
		||||
  private final ChangeQueryBuilder queryBuilder;
 | 
			
		||||
  private final ChangeQueryRewriter queryRewriter;
 | 
			
		||||
  private final TrackingFooters trackingFooters;
 | 
			
		||||
  private final CurrentUser user;
 | 
			
		||||
  private final int maxLimit;
 | 
			
		||||
  private final int permittedLimit;
 | 
			
		||||
 | 
			
		||||
  private OutputFormat outputFormat = OutputFormat.TEXT;
 | 
			
		||||
  private int limit;
 | 
			
		||||
  private int limitFromCaller;
 | 
			
		||||
  private int start;
 | 
			
		||||
  private boolean includePatchSets;
 | 
			
		||||
  private boolean includeCurrentPatchSet;
 | 
			
		||||
  private boolean includeApprovals;
 | 
			
		||||
  private boolean includeComments;
 | 
			
		||||
  private boolean includeFiles;
 | 
			
		||||
  private boolean includeCommitMessage;
 | 
			
		||||
  private boolean includeDependencies;
 | 
			
		||||
  private boolean includeSubmitRecords;
 | 
			
		||||
  private boolean includeAllReviewers;
 | 
			
		||||
 | 
			
		||||
  private OutputStream outputStream = DisabledOutputStream.INSTANCE;
 | 
			
		||||
  private PrintWriter out;
 | 
			
		||||
  private boolean moreResults;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  QueryProcessor(EventFactory eventFactory,
 | 
			
		||||
      ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
 | 
			
		||||
      ChangeQueryRewriter queryRewriter,
 | 
			
		||||
      TrackingFooters trackingFooters) {
 | 
			
		||||
    this.eventFactory = eventFactory;
 | 
			
		||||
  QueryProcessor(ChangeQueryBuilder.Factory queryBuilder,
 | 
			
		||||
      CurrentUser currentUser,
 | 
			
		||||
      ChangeQueryRewriter queryRewriter) {
 | 
			
		||||
    this.queryBuilder = queryBuilder.create(currentUser);
 | 
			
		||||
    this.queryRewriter = queryRewriter;
 | 
			
		||||
    this.trackingFooters = trackingFooters;
 | 
			
		||||
    this.user = currentUser;
 | 
			
		||||
    this.maxLimit = currentUser.getCapabilities()
 | 
			
		||||
    this.permittedLimit = currentUser.getCapabilities()
 | 
			
		||||
      .getRange(GlobalCapability.QUERY_LIMIT)
 | 
			
		||||
      .getMax();
 | 
			
		||||
    this.moreResults = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int getLimit() {
 | 
			
		||||
    return limit;
 | 
			
		||||
  public ChangeQueryBuilder getQueryBuilder() {
 | 
			
		||||
    return queryBuilder;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setLimit(int n) {
 | 
			
		||||
    limit = n;
 | 
			
		||||
  public void setLimit(int n) {
 | 
			
		||||
    limitFromCaller = n;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setStart(int n) {
 | 
			
		||||
    start = n;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludePatchSets(boolean on) {
 | 
			
		||||
    includePatchSets = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludePatchSets() {
 | 
			
		||||
    return includePatchSets;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeCurrentPatchSet(boolean on) {
 | 
			
		||||
    includeCurrentPatchSet = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeCurrentPatchSet() {
 | 
			
		||||
    return includeCurrentPatchSet;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeApprovals(boolean on) {
 | 
			
		||||
    includeApprovals = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeComments(boolean on) {
 | 
			
		||||
    includeComments = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeFiles(boolean on) {
 | 
			
		||||
    includeFiles = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeFiles() {
 | 
			
		||||
    return includeFiles;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeDependencies(boolean on) {
 | 
			
		||||
    includeDependencies = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public boolean getIncludeDependencies() {
 | 
			
		||||
    return includeDependencies;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeCommitMessage(boolean on) {
 | 
			
		||||
    includeCommitMessage = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeSubmitRecords(boolean on) {
 | 
			
		||||
    includeSubmitRecords = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setIncludeAllReviewers(boolean on) {
 | 
			
		||||
    includeAllReviewers = on;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setOutput(OutputStream out, OutputFormat fmt) {
 | 
			
		||||
    this.outputStream = out;
 | 
			
		||||
    this.outputFormat = fmt;
 | 
			
		||||
  /**
 | 
			
		||||
   * Query for changes that match the query string.
 | 
			
		||||
   *
 | 
			
		||||
   * @see #queryChanges(List)
 | 
			
		||||
   * @param queryString the query string to parse.
 | 
			
		||||
   * @return results of the query.
 | 
			
		||||
   */
 | 
			
		||||
  public QueryResult queryByString(String queryString)
 | 
			
		||||
      throws OrmException, QueryParseException {
 | 
			
		||||
    return queryByStrings(ImmutableList.of(queryString)).get(0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Query for changes that match the query string.
 | 
			
		||||
   * Perform multiple queries over a list of query strings.
 | 
			
		||||
   *
 | 
			
		||||
   * @see #queryChanges(List)
 | 
			
		||||
   * @param queryStrings the query strings to parse.
 | 
			
		||||
   * @return results of the queries, one list per input query.
 | 
			
		||||
   */
 | 
			
		||||
  public List<QueryResult> queryByStrings(List<String> queryStrings)
 | 
			
		||||
      throws OrmException, QueryParseException {
 | 
			
		||||
    List<Predicate<ChangeData>> queries = new ArrayList<>(queryStrings.size());
 | 
			
		||||
    for (String qs : queryStrings) {
 | 
			
		||||
      queries.add(queryBuilder.parse(qs));
 | 
			
		||||
    }
 | 
			
		||||
    return queryChanges(queries);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Query for changes that match a structured query.
 | 
			
		||||
   *
 | 
			
		||||
   * @see #queryChanges(List)
 | 
			
		||||
   * @param query the query.
 | 
			
		||||
   * @return results of the query.
 | 
			
		||||
   */
 | 
			
		||||
  public QueryResult queryChanges(Predicate<ChangeData> query)
 | 
			
		||||
      throws OrmException, QueryParseException {
 | 
			
		||||
    return queryChanges(ImmutableList.of(query)).get(0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Perform multiple queries over a list of query strings.
 | 
			
		||||
   * <p>
 | 
			
		||||
   * If a limit was specified using {@link #setLimit(int)} this method may
 | 
			
		||||
   * return up to {@code limit + 1} results, allowing the caller to determine if
 | 
			
		||||
   * there are more than {@code limit} matches and suggest to its own caller
 | 
			
		||||
   * that the query could be retried with {@link #setStart(int)}.
 | 
			
		||||
   *
 | 
			
		||||
   * @param queries the queries.
 | 
			
		||||
   * @return results of the queries, one list per input query.
 | 
			
		||||
   */
 | 
			
		||||
  public List<ChangeData> queryChanges(String queryString)
 | 
			
		||||
  public List<QueryResult> queryChanges(List<Predicate<ChangeData>> queries)
 | 
			
		||||
      throws OrmException, QueryParseException {
 | 
			
		||||
    return queryChanges(ImmutableList.of(queryString)).get(0);
 | 
			
		||||
    return queryChanges(null, queries);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Query for changes that match the query string.
 | 
			
		||||
   * <p>
 | 
			
		||||
   * If a limit was specified using {@link #setLimit(int)} this method may
 | 
			
		||||
   * return up to {@code limit + 1} results, allowing the caller to determine if
 | 
			
		||||
   * there are more than {@code limit} matches and suggest to its own caller
 | 
			
		||||
   * that the query could be retried with {@link #setStart(int)}.
 | 
			
		||||
   */
 | 
			
		||||
  public List<List<ChangeData>> queryChanges(List<String> queries)
 | 
			
		||||
  static {
 | 
			
		||||
    // In addition to this assumption, this queryChanges assumes the basic
 | 
			
		||||
    // rewrites do not touch visibleto predicates either.
 | 
			
		||||
    checkState(
 | 
			
		||||
        !IsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
 | 
			
		||||
        "QueryProcessor assumes visibleto is not used by the index rewriter.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<QueryResult> queryChanges(List<String> queryStrings,
 | 
			
		||||
      List<Predicate<ChangeData>> queries)
 | 
			
		||||
      throws OrmException, QueryParseException {
 | 
			
		||||
    final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
 | 
			
		||||
    Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
 | 
			
		||||
    int cnt = queries.size();
 | 
			
		||||
 | 
			
		||||
    // Parse and rewrite all queries.
 | 
			
		||||
    List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
 | 
			
		||||
    List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
 | 
			
		||||
    for (String query : queries) {
 | 
			
		||||
      Predicate<ChangeData> q = parseQuery(query, visibleToMe);
 | 
			
		||||
      Predicate<ChangeData> s = queryRewriter.rewrite(q, start);
 | 
			
		||||
    List<Integer> limits = new ArrayList<>(cnt);
 | 
			
		||||
    List<Predicate<ChangeData>> predicates = new ArrayList<>(cnt);
 | 
			
		||||
    List<ChangeDataSource> sources = new ArrayList<>(cnt);
 | 
			
		||||
    for (Predicate<ChangeData> q : queries) {
 | 
			
		||||
      int limit = getEffectiveLimit(q);
 | 
			
		||||
      limits.add(limit);
 | 
			
		||||
 | 
			
		||||
      // Always bump limit by 1, even if this results in exceeding the permitted
 | 
			
		||||
      // max for this user. The only way to see if there are more changes is to
 | 
			
		||||
      // ask for one more result from the query.
 | 
			
		||||
      Predicate<ChangeData> s = queryRewriter.rewrite(q, start, limit + 1);
 | 
			
		||||
      if (!(s instanceof ChangeDataSource)) {
 | 
			
		||||
        q = Predicate.and(queryBuilder.status_open(), q);
 | 
			
		||||
        s = queryRewriter.rewrite(q, start);
 | 
			
		||||
        s = queryRewriter.rewrite(q, start, limit);
 | 
			
		||||
      }
 | 
			
		||||
      if (!(s instanceof ChangeDataSource)) {
 | 
			
		||||
        throw new QueryParseException("invalid query: " + s);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Don't trust QueryRewriter to have left the visible predicate.
 | 
			
		||||
      AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start);
 | 
			
		||||
      limits.add(limit(q));
 | 
			
		||||
      predicates.add(a);
 | 
			
		||||
      sources.add(a);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Run each query asynchronously, if supported.
 | 
			
		||||
    List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
 | 
			
		||||
    List<ResultSet<ChangeData>> matches = new ArrayList<>(cnt);
 | 
			
		||||
    for (ChangeDataSource s : sources) {
 | 
			
		||||
      matches.add(s.read());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
 | 
			
		||||
    List<QueryResult> out = new ArrayList<>(cnt);
 | 
			
		||||
    for (int i = 0; i < cnt; i++) {
 | 
			
		||||
      List<ChangeData> results = matches.get(i).toList();
 | 
			
		||||
      if (results.size() > maxLimit) {
 | 
			
		||||
        moreResults = true;
 | 
			
		||||
      }
 | 
			
		||||
      int limit = limits.get(i);
 | 
			
		||||
      if (limit < results.size()) {
 | 
			
		||||
        results = results.subList(0, limit);
 | 
			
		||||
      }
 | 
			
		||||
      out.add(results);
 | 
			
		||||
      out.add(QueryResult.create(
 | 
			
		||||
          queryStrings != null ? queryStrings.get(i) : null,
 | 
			
		||||
          predicates.get(i),
 | 
			
		||||
          limits.get(i),
 | 
			
		||||
          matches.get(i).toList()));
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void query(String queryString) throws IOException {
 | 
			
		||||
    out = new PrintWriter( //
 | 
			
		||||
        new BufferedWriter( //
 | 
			
		||||
            new OutputStreamWriter(outputStream, "UTF-8")));
 | 
			
		||||
    try {
 | 
			
		||||
      if (isDisabled()) {
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "query disabled";
 | 
			
		||||
        show(m);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        final QueryStatsAttribute stats = new QueryStatsAttribute();
 | 
			
		||||
        stats.runTimeMilliseconds = TimeUtil.nowMs();
 | 
			
		||||
 | 
			
		||||
        List<ChangeData> results = queryChanges(queryString);
 | 
			
		||||
        ChangeAttribute c = null;
 | 
			
		||||
        for (ChangeData d : results) {
 | 
			
		||||
          ChangeControl cc = d.changeControl().forUser(user);
 | 
			
		||||
 | 
			
		||||
          LabelTypes labelTypes = cc.getLabelTypes();
 | 
			
		||||
          c = eventFactory.asChangeAttribute(d.change());
 | 
			
		||||
          eventFactory.extend(c, d.change());
 | 
			
		||||
 | 
			
		||||
          if (!trackingFooters.isEmpty()) {
 | 
			
		||||
            eventFactory.addTrackingIds(c,
 | 
			
		||||
                trackingFooters.extract(d.commitFooters()));
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeAllReviewers) {
 | 
			
		||||
            eventFactory.addAllReviewers(c, d.notes());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeSubmitRecords) {
 | 
			
		||||
            eventFactory.addSubmitRecords(c, new SubmitRuleEvaluator(d)
 | 
			
		||||
                .setAllowClosed(true)
 | 
			
		||||
                .setAllowDraft(true)
 | 
			
		||||
                .canSubmit());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeCommitMessage) {
 | 
			
		||||
            eventFactory.addCommitMessage(c, d.commitMessage());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includePatchSets) {
 | 
			
		||||
            if (includeFiles) {
 | 
			
		||||
              eventFactory.addPatchSets(c, d.patches(),
 | 
			
		||||
                includeApprovals ? d.approvals().asMap() : null,
 | 
			
		||||
                includeFiles, d.change(), labelTypes);
 | 
			
		||||
            } else {
 | 
			
		||||
              eventFactory.addPatchSets(c, d.patches(),
 | 
			
		||||
                  includeApprovals ? d.approvals().asMap() : null,
 | 
			
		||||
                  labelTypes);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeCurrentPatchSet) {
 | 
			
		||||
            PatchSet current = d.currentPatchSet();
 | 
			
		||||
            if (current != null) {
 | 
			
		||||
              c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
 | 
			
		||||
              eventFactory.addApprovals(c.currentPatchSet,
 | 
			
		||||
                  d.currentApprovals(), labelTypes);
 | 
			
		||||
 | 
			
		||||
              if (includeFiles) {
 | 
			
		||||
                eventFactory.addPatchSetFileNames(c.currentPatchSet,
 | 
			
		||||
                    d.change(), d.currentPatchSet());
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeComments) {
 | 
			
		||||
            eventFactory.addComments(c, d.messages());
 | 
			
		||||
            if (includePatchSets) {
 | 
			
		||||
              for (PatchSetAttribute attribute : c.patchSets) {
 | 
			
		||||
                eventFactory.addPatchSetComments(attribute,  d.publishedComments());
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (includeDependencies) {
 | 
			
		||||
            eventFactory.addDependencies(c, d.change());
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          show(c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stats.rowCount = results.size();
 | 
			
		||||
        if (moreResults) {
 | 
			
		||||
          stats.resumeSortKey = c.sortKey;
 | 
			
		||||
        }
 | 
			
		||||
        stats.runTimeMilliseconds =
 | 
			
		||||
            TimeUtil.nowMs() - stats.runTimeMilliseconds;
 | 
			
		||||
        show(stats);
 | 
			
		||||
      } catch (OrmException err) {
 | 
			
		||||
        log.error("Cannot execute query: " + queryString, err);
 | 
			
		||||
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "cannot query database";
 | 
			
		||||
        show(m);
 | 
			
		||||
 | 
			
		||||
      } catch (QueryParseException e) {
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = e.getMessage();
 | 
			
		||||
        show(m);
 | 
			
		||||
      } catch (NoSuchChangeException e) {
 | 
			
		||||
        log.error("Missing change: " + e.getMessage(), e);
 | 
			
		||||
        ErrorMessage m = new ErrorMessage();
 | 
			
		||||
        m.message = "missing change " + e.getMessage();
 | 
			
		||||
        show(m);
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      try {
 | 
			
		||||
        out.flush();
 | 
			
		||||
      } finally {
 | 
			
		||||
        out = null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  boolean isDisabled() {
 | 
			
		||||
    return maxLimit <= 0;
 | 
			
		||||
    return permittedLimit <= 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private int limit(Predicate<ChangeData> s) {
 | 
			
		||||
    int n = MoreObjects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit);
 | 
			
		||||
    return limit > 0 ? Math.min(n, limit) + 1 : n + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Predicate<ChangeData> parseQuery(String queryString,
 | 
			
		||||
      final Predicate<ChangeData> visibleToMe) throws QueryParseException {
 | 
			
		||||
    return Predicate.and(queryBuilder.parse(queryString),
 | 
			
		||||
        queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
 | 
			
		||||
        visibleToMe);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void show(Object data) {
 | 
			
		||||
    switch (outputFormat) {
 | 
			
		||||
      default:
 | 
			
		||||
      case TEXT:
 | 
			
		||||
        if (data instanceof ChangeAttribute) {
 | 
			
		||||
          out.print("change ");
 | 
			
		||||
          out.print(((ChangeAttribute) data).id);
 | 
			
		||||
          out.print("\n");
 | 
			
		||||
          showText(data, 1);
 | 
			
		||||
        } else {
 | 
			
		||||
          showText(data, 0);
 | 
			
		||||
        }
 | 
			
		||||
        out.print('\n');
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case JSON:
 | 
			
		||||
        out.print(gson.toJson(data));
 | 
			
		||||
        out.print('\n');
 | 
			
		||||
        break;
 | 
			
		||||
  private int getEffectiveLimit(Predicate<ChangeData> p) {
 | 
			
		||||
    List<Integer> possibleLimits = new ArrayList<>(3);
 | 
			
		||||
    possibleLimits.add(permittedLimit);
 | 
			
		||||
    if (limitFromCaller > 0) {
 | 
			
		||||
      possibleLimits.add(limitFromCaller);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void showText(Object data, int depth) {
 | 
			
		||||
    for (Field f : fieldsOf(data.getClass())) {
 | 
			
		||||
      Object val;
 | 
			
		||||
      try {
 | 
			
		||||
        val = f.get(data);
 | 
			
		||||
      } catch (IllegalArgumentException err) {
 | 
			
		||||
        continue;
 | 
			
		||||
      } catch (IllegalAccessException err) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (val == null) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      showField(f.getName(), val, depth);
 | 
			
		||||
    Integer limitFromPredicate = LimitPredicate.getLimit(p);
 | 
			
		||||
    if (limitFromPredicate != null) {
 | 
			
		||||
      possibleLimits.add(limitFromPredicate);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private String indent(int spaces) {
 | 
			
		||||
    if (spaces == 0) {
 | 
			
		||||
      return "";
 | 
			
		||||
    } else {
 | 
			
		||||
      return String.format("%" + spaces + "s", " ");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void showField(String field, Object value, int depth) {
 | 
			
		||||
    final int spacesDepthRatio = 2;
 | 
			
		||||
    String indent = indent(depth * spacesDepthRatio);
 | 
			
		||||
    out.print(indent);
 | 
			
		||||
    out.print(field);
 | 
			
		||||
    out.print(':');
 | 
			
		||||
    if (value instanceof String && ((String) value).contains("\n")) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      // Idention for multi-line text is
 | 
			
		||||
      // current depth indetion + length of field + length of ": "
 | 
			
		||||
      indent = indent(indent.length() + field.length() + spacesDepthRatio);
 | 
			
		||||
      out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (value instanceof Long && isDateField(field)) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      out.print(sdf.format(new Date(((Long) value) * 1000L)));
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (isPrimitive(value)) {
 | 
			
		||||
      out.print(' ');
 | 
			
		||||
      out.print(value);
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
    } else if (value instanceof Collection) {
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
      boolean firstElement = true;
 | 
			
		||||
      for (Object thing : ((Collection<?>) value)) {
 | 
			
		||||
        // The name of the collection was initially printed at the beginning
 | 
			
		||||
        // of this routine.  Beginning at the second sub-element, reprint
 | 
			
		||||
        // the collection name so humans can separate individual elements
 | 
			
		||||
        // with less strain and error.
 | 
			
		||||
        //
 | 
			
		||||
        if (firstElement) {
 | 
			
		||||
          firstElement = false;
 | 
			
		||||
        } else {
 | 
			
		||||
          out.print(indent);
 | 
			
		||||
          out.print(field);
 | 
			
		||||
          out.print(":\n");
 | 
			
		||||
        }
 | 
			
		||||
        if (isPrimitive(thing)) {
 | 
			
		||||
          out.print(' ');
 | 
			
		||||
          out.print(value);
 | 
			
		||||
          out.print('\n');
 | 
			
		||||
        } else {
 | 
			
		||||
          showText(thing, depth + 1);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      out.print('\n');
 | 
			
		||||
      showText(value, depth + 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean isPrimitive(Object value) {
 | 
			
		||||
    return value instanceof String //
 | 
			
		||||
        || value instanceof Number //
 | 
			
		||||
        || value instanceof Boolean //
 | 
			
		||||
        || value instanceof Enum;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean isDateField(String name) {
 | 
			
		||||
    return "lastUpdated".equals(name) //
 | 
			
		||||
        || "grantedOn".equals(name) //
 | 
			
		||||
        || "timestamp".equals(name) //
 | 
			
		||||
        || "createdOn".equals(name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<Field> fieldsOf(Class<?> type) {
 | 
			
		||||
    List<Field> r = new ArrayList<>();
 | 
			
		||||
    if (type.getSuperclass() != null) {
 | 
			
		||||
      r.addAll(fieldsOf(type.getSuperclass()));
 | 
			
		||||
    }
 | 
			
		||||
    r.addAll(Arrays.asList(type.getDeclaredFields()));
 | 
			
		||||
    return r;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class ErrorMessage {
 | 
			
		||||
    public final String type = "error";
 | 
			
		||||
    public String message;
 | 
			
		||||
    return Ordering.natural().min(possibleLimits);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
// 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.auto.value.AutoValue;
 | 
			
		||||
import com.google.gerrit.common.Nullable;
 | 
			
		||||
import com.google.gerrit.server.query.Predicate;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/** Results of a query over changes. */
 | 
			
		||||
@AutoValue
 | 
			
		||||
public abstract class QueryResult {
 | 
			
		||||
  static QueryResult create(@Nullable String query,
 | 
			
		||||
      Predicate<ChangeData> predicate, int limit, List<ChangeData> changes) {
 | 
			
		||||
    boolean moreChanges;
 | 
			
		||||
    if (changes.size() > limit) {
 | 
			
		||||
      moreChanges = true;
 | 
			
		||||
      changes = changes.subList(0, limit);
 | 
			
		||||
    } else {
 | 
			
		||||
      moreChanges = false;
 | 
			
		||||
    }
 | 
			
		||||
    return new AutoValue_QueryResult(query, predicate, changes, moreChanges);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return the original query string, or null if the query was created
 | 
			
		||||
   *     programmatically.
 | 
			
		||||
   */
 | 
			
		||||
  @Nullable public abstract String query();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return the predicate after all rewriting and other modification by the
 | 
			
		||||
   *     query subsystem.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract Predicate<ChangeData> predicate();
 | 
			
		||||
 | 
			
		||||
  /** @return the query results. */
 | 
			
		||||
  public abstract List<ChangeData> changes();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return whether the query could be retried with
 | 
			
		||||
   *     {@link QueryProcessor#setStart(int)} to produce more results. Never
 | 
			
		||||
   *     true if {@link #changes()} is empty.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract boolean moreChanges();
 | 
			
		||||
}
 | 
			
		||||
@@ -32,7 +32,7 @@ import java.util.List;
 | 
			
		||||
/** A version of the database schema. */
 | 
			
		||||
public abstract class SchemaVersion {
 | 
			
		||||
  /** The current schema version. */
 | 
			
		||||
  public static final Class<Schema_101> C = Schema_101.class;
 | 
			
		||||
  public static final Class<Schema_102> C = Schema_102.class;
 | 
			
		||||
 | 
			
		||||
  public static class Module extends AbstractModule {
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
// 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.schema;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gwtorm.jdbc.JdbcSchema;
 | 
			
		||||
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
 | 
			
		||||
import com.google.gwtorm.schema.sql.SqlDialect;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.sql.Statement;
 | 
			
		||||
 | 
			
		||||
public class Schema_102 extends SchemaVersion {
 | 
			
		||||
  @Inject
 | 
			
		||||
  Schema_102(Provider<Schema_100> prior) {
 | 
			
		||||
    super(prior);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void migrateData(ReviewDb db, UpdateUI ui)
 | 
			
		||||
      throws OrmException, SQLException {
 | 
			
		||||
    JdbcSchema schema = (JdbcSchema) db;
 | 
			
		||||
    SqlDialect dialect = schema.getDialect();
 | 
			
		||||
    try (Statement stmt = schema.getConnection().createStatement()) {
 | 
			
		||||
      stmt.executeUpdate("DROP INDEX changes_byProjectOpen");
 | 
			
		||||
      if (dialect instanceof DialectPostgreSQL) {
 | 
			
		||||
        stmt.executeUpdate("CREATE INDEX changes_byProjectOpen"
 | 
			
		||||
            + " ON changes (dest_project_name, last_updated_on)"
 | 
			
		||||
            + " WHERE open = 'Y'");
 | 
			
		||||
      } else {
 | 
			
		||||
        stmt.executeUpdate("CREATE INDEX changes_byProjectOpen"
 | 
			
		||||
            + " ON changes (open, dest_project_name, last_updated_on)");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -34,7 +34,6 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.ChangeUtil;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.account.AccountManager;
 | 
			
		||||
import com.google.gerrit.server.account.AuthRequest;
 | 
			
		||||
@@ -112,7 +111,6 @@ public class LabelNormalizerTest {
 | 
			
		||||
        new Change.Id(1), userId,
 | 
			
		||||
        new Branch.NameKey(allProjects, "refs/heads/master"),
 | 
			
		||||
        TimeUtil.nowTs());
 | 
			
		||||
    ChangeUtil.computeSortKey(change);
 | 
			
		||||
    PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
 | 
			
		||||
    ps.setSubject("Test change");
 | 
			
		||||
    change.setCurrentPatchSet(ps);
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ public class IndexRewriteTest {
 | 
			
		||||
        parse("-status:abandoned (status:open OR status:merged)");
 | 
			
		||||
    assertEquals(
 | 
			
		||||
        query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
 | 
			
		||||
        rewrite.rewrite(in, 0));
 | 
			
		||||
        rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -158,24 +158,26 @@ public class IndexRewriteTest {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void testLimit() throws Exception {
 | 
			
		||||
    Predicate<ChangeData> in = parse("file:a limit:3");
 | 
			
		||||
    Predicate<ChangeData> out = rewrite(in);
 | 
			
		||||
  public void testLimitArgumentOverridesAllLimitPredicates() throws Exception {
 | 
			
		||||
    Predicate<ChangeData> in = parse("limit:1 file:a limit:3");
 | 
			
		||||
    Predicate<ChangeData> out = rewrite(in, 5);
 | 
			
		||||
    assertSame(AndSource.class, out.getClass());
 | 
			
		||||
    assertEquals(ImmutableList.of(
 | 
			
		||||
          query(in.getChild(0), 3),
 | 
			
		||||
          in.getChild(1)),
 | 
			
		||||
          query(in.getChild(1), 5),
 | 
			
		||||
          parse("limit:5"),
 | 
			
		||||
          parse("limit:5")),
 | 
			
		||||
        out.getChildren());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void testStartIncreasesLimit() throws Exception {
 | 
			
		||||
    int n = 3;
 | 
			
		||||
    Predicate<ChangeData> f = parse("file:a");
 | 
			
		||||
    Predicate<ChangeData> l = parse("limit:3");
 | 
			
		||||
    Predicate<ChangeData> l = parse("limit:" + n);
 | 
			
		||||
    Predicate<ChangeData> in = and(f, l);
 | 
			
		||||
    assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0));
 | 
			
		||||
    assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1));
 | 
			
		||||
    assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2));
 | 
			
		||||
    assertEquals(and(query(f, 3), parse("limit:3")), rewrite.rewrite(in, 0, n));
 | 
			
		||||
    assertEquals(and(query(f, 4), parse("limit:4")), rewrite.rewrite(in, 1, n));
 | 
			
		||||
    assertEquals(and(query(f, 5), parse("limit:5")), rewrite.rewrite(in, 2, n));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -220,7 +222,12 @@ public class IndexRewriteTest {
 | 
			
		||||
 | 
			
		||||
  private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
 | 
			
		||||
      throws QueryParseException {
 | 
			
		||||
    return rewrite.rewrite(in, 0);
 | 
			
		||||
    return rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int limit)
 | 
			
		||||
      throws QueryParseException {
 | 
			
		||||
    return rewrite.rewrite(in, 0, limit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private IndexedChangeQuery query(Predicate<ChangeData> p)
 | 
			
		||||
 
 | 
			
		||||
@@ -136,18 +136,22 @@ public abstract class AbstractQueryChangesTest {
 | 
			
		||||
    userAccount.setPreferredEmail("user@example.com");
 | 
			
		||||
    db.accounts().update(ImmutableList.of(userAccount));
 | 
			
		||||
    user = userFactory.create(userId);
 | 
			
		||||
    requestContext.setContext(newRequestContext(userAccount.getId()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    requestContext.setContext(new RequestContext() {
 | 
			
		||||
  private RequestContext newRequestContext(Account.Id requestUserId) {
 | 
			
		||||
    final CurrentUser requestUser = userFactory.create(requestUserId);
 | 
			
		||||
    return new RequestContext() {
 | 
			
		||||
      @Override
 | 
			
		||||
      public CurrentUser getCurrentUser() {
 | 
			
		||||
        return user;
 | 
			
		||||
        return requestUser;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      @Override
 | 
			
		||||
      public Provider<ReviewDb> getReviewDbProvider() {
 | 
			
		||||
        return Providers.of(db);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @After
 | 
			
		||||
@@ -543,9 +547,22 @@ public abstract class AbstractQueryChangesTest {
 | 
			
		||||
 | 
			
		||||
    List<ChangeInfo> results;
 | 
			
		||||
    for (int i = 1; i <= n + 2; i++) {
 | 
			
		||||
      int expectedSize;
 | 
			
		||||
      Boolean expectedMoreChanges;
 | 
			
		||||
      if (i < n) {
 | 
			
		||||
        expectedSize = i;
 | 
			
		||||
        expectedMoreChanges = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        expectedSize = n;
 | 
			
		||||
        expectedMoreChanges = null;
 | 
			
		||||
      }
 | 
			
		||||
      results = query("status:new limit:" + i);
 | 
			
		||||
      assertThat(results).hasSize(Math.min(i, n));
 | 
			
		||||
      String msg = "i=" + i;
 | 
			
		||||
      assert_().withFailureMessage(msg).that(results).hasSize(expectedSize);
 | 
			
		||||
      assertResultEquals(last, results.get(0));
 | 
			
		||||
      assert_().withFailureMessage(msg)
 | 
			
		||||
          .that(results.get(results.size() - 1)._moreChanges)
 | 
			
		||||
          .isEqualTo(expectedMoreChanges);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -1031,6 +1048,49 @@ public abstract class AbstractQueryChangesTest {
 | 
			
		||||
    assertThat(query("repo")).hasSize(6);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void implicitVisibleTo() throws Exception {
 | 
			
		||||
    TestRepository<InMemoryRepository> repo = createProject("repo");
 | 
			
		||||
    Change change1 = newChange(repo, null, null, userId.get(), null).insert();
 | 
			
		||||
    ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
 | 
			
		||||
    Change change2 = ins2.getChange();
 | 
			
		||||
    change2.setStatus(Change.Status.DRAFT);
 | 
			
		||||
    ins2.insert();
 | 
			
		||||
 | 
			
		||||
    String q = "project:repo";
 | 
			
		||||
    List<ChangeInfo> results = query(q);
 | 
			
		||||
    assertThat(results).hasSize(2);
 | 
			
		||||
    assertResultEquals(change2, results.get(0));
 | 
			
		||||
    assertResultEquals(change1, results.get(1));
 | 
			
		||||
 | 
			
		||||
    // Second user cannot see first user's drafts.
 | 
			
		||||
    requestContext.setContext(newRequestContext(accountManager
 | 
			
		||||
        .authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
 | 
			
		||||
    assertResultEquals(change1, queryOne(q));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void explicitVisibleTo() throws Exception {
 | 
			
		||||
    TestRepository<InMemoryRepository> repo = createProject("repo");
 | 
			
		||||
    Change change1 = newChange(repo, null, null, userId.get(), null).insert();
 | 
			
		||||
    ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
 | 
			
		||||
    Change change2 = ins2.getChange();
 | 
			
		||||
    change2.setStatus(Change.Status.DRAFT);
 | 
			
		||||
    ins2.insert();
 | 
			
		||||
 | 
			
		||||
    String q = "project:repo";
 | 
			
		||||
    List<ChangeInfo> results = query(q);
 | 
			
		||||
    assertThat(results).hasSize(2);
 | 
			
		||||
    assertResultEquals(change2, results.get(0));
 | 
			
		||||
    assertResultEquals(change1, results.get(1));
 | 
			
		||||
 | 
			
		||||
    // Second user cannot see first user's drafts.
 | 
			
		||||
    Account.Id user2 = accountManager
 | 
			
		||||
        .authenticate(AuthRequest.forUser("anotheruser"))
 | 
			
		||||
        .getAccountId();
 | 
			
		||||
    assertResultEquals(change1, queryOne(q + " visibleto:" + user2.get()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected ChangeInserter newChange(
 | 
			
		||||
      TestRepository<InMemoryRepository> repo,
 | 
			
		||||
      @Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.RevId;
 | 
			
		||||
import com.google.gerrit.server.ChangeUtil;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersName;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersNameProvider;
 | 
			
		||||
@@ -114,6 +113,5 @@ public class TestChanges {
 | 
			
		||||
        change.getId(), curr != null ? curr.get() + 1 : 1));
 | 
			
		||||
    ps.setSubject("Change subject");
 | 
			
		||||
    change.setCurrentPatchSet(ps);
 | 
			
		||||
    ChangeUtil.computeSortKey(change);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,8 @@
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.sshd.commands;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.query.change.QueryProcessor;
 | 
			
		||||
import com.google.gerrit.server.query.change.OutputStreamQuery;
 | 
			
		||||
import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
 | 
			
		||||
import com.google.gerrit.sshd.CommandMetaData;
 | 
			
		||||
import com.google.gerrit.sshd.SshCommand;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
@@ -27,10 +28,10 @@ import java.util.List;
 | 
			
		||||
@CommandMetaData(name = "query", description = "Query the change database")
 | 
			
		||||
class Query extends SshCommand {
 | 
			
		||||
  @Inject
 | 
			
		||||
  private QueryProcessor processor;
 | 
			
		||||
  private OutputStreamQuery processor;
 | 
			
		||||
 | 
			
		||||
  @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
 | 
			
		||||
  void setFormat(QueryProcessor.OutputFormat format) {
 | 
			
		||||
  void setFormat(OutputFormat format) {
 | 
			
		||||
    processor.setOutput(out, format);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +98,7 @@ class Query extends SshCommand {
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void parseCommandLine() throws UnloggedFailure {
 | 
			
		||||
    processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
 | 
			
		||||
    processor.setOutput(out, OutputFormat.TEXT);
 | 
			
		||||
    super.parseCommandLine();
 | 
			
		||||
    if (processor.getIncludeFiles() &&
 | 
			
		||||
        !(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user