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
|
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
|
||||||
{"project":"tools/gerrit", ...}
|
{"project":"tools/gerrit", ...}
|
||||||
{"project":"tools/gerrit", ..., sortKey:"000e6aee00003e26", ...}
|
{"project":"tools/gerrit", ...}
|
||||||
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
|
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ was created.
|
|||||||
lastUpdated:: Time in seconds since the UNIX epoch when this change
|
lastUpdated:: Time in seconds since the UNIX epoch when this change
|
||||||
was last updated.
|
was last updated.
|
||||||
|
|
||||||
sortKey:: Internal key used to sort changes, based on lastUpdated.
|
|
||||||
|
|
||||||
open:: Boolean indicating if the change is still open for review.
|
open:: Boolean indicating if the change is still open for review.
|
||||||
|
|
||||||
status:: Current state of this change.
|
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",
|
"created": "2013-02-01 09:59:32.126000000",
|
||||||
"updated": "2013-02-21 11:16:36.775000000",
|
"updated": "2013-02-21 11:16:36.775000000",
|
||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ the resulting change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 0,
|
"insertions": 0,
|
||||||
"deletions": 0,
|
"deletions": 0,
|
||||||
"_sortkey": "002cbc25000004e5",
|
|
||||||
"_number": 4711,
|
"_number": 4711,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -105,7 +104,6 @@ Query for open changes of watched projects:
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 26,
|
"insertions": 26,
|
||||||
"deletions": 10,
|
"deletions": 10,
|
||||||
"_sortkey": "001e7057000006dc",
|
|
||||||
"_number": 1756,
|
"_number": 1756,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -123,7 +121,6 @@ Query for open changes of watched projects:
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 12,
|
"insertions": 12,
|
||||||
"deletions": 18,
|
"deletions": 18,
|
||||||
"_sortkey": "001e7056000006dd",
|
|
||||||
"_number": 1757,
|
"_number": 1757,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -177,7 +174,6 @@ Query that retrieves changes for a user's dashboard:
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 4,
|
"insertions": 4,
|
||||||
"deletions": 7,
|
"deletions": 7,
|
||||||
"_sortkey": "001e7057000006dc",
|
|
||||||
"_number": 1756,
|
"_number": 1756,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -330,7 +326,6 @@ default. Optional fields are:
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 16,
|
"insertions": 16,
|
||||||
"deletions": 7,
|
"deletions": 7,
|
||||||
"_sortkey": "001c9bf400000061",
|
|
||||||
"_number": 97,
|
"_number": 97,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "Shawn Pearce"
|
"name": "Shawn Pearce"
|
||||||
@@ -467,7 +462,6 @@ describes the change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 34,
|
"insertions": 34,
|
||||||
"deletions": 101,
|
"deletions": 101,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -520,7 +514,6 @@ REJECTED > APPROVED > DISLIKED > RECOMMENDED.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 126,
|
"insertions": 126,
|
||||||
"deletions": 11,
|
"deletions": 11,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"_account_id": 1000096,
|
"_account_id": 1000096,
|
||||||
@@ -764,7 +757,6 @@ describes the abandoned change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 3,
|
"insertions": 3,
|
||||||
"deletions": 310,
|
"deletions": 310,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -823,7 +815,6 @@ describes the restored change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 2,
|
"insertions": 2,
|
||||||
"deletions": 13,
|
"deletions": 13,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -880,7 +871,6 @@ is included.
|
|||||||
"mergeable": false,
|
"mergeable": false,
|
||||||
"insertions": 33,
|
"insertions": 33,
|
||||||
"deletions": 9,
|
"deletions": 9,
|
||||||
"_sortkey": "0024cf9a000012bf",
|
|
||||||
"_number": 4799,
|
"_number": 4799,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -973,7 +963,6 @@ describes the reverting change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 6,
|
"insertions": 6,
|
||||||
"deletions": 4,
|
"deletions": 4,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -1035,7 +1024,6 @@ describes the submitted/merged change.
|
|||||||
"status": "MERGED",
|
"status": "MERGED",
|
||||||
"created": "2013-02-01 09:59:32.126000000",
|
"created": "2013-02-01 09:59:32.126000000",
|
||||||
"updated": "2013-02-21 11:16:36.775000000",
|
"updated": "2013-02-21 11:16:36.775000000",
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -1179,7 +1167,6 @@ missing from the result. At least `id`, `project`, `branch`, and
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 34,
|
"insertions": 34,
|
||||||
"deletions": 101,
|
"deletions": 101,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -1228,7 +1215,6 @@ Only the change owner, a project owner, or an administrator may fix changes.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 34,
|
"insertions": 34,
|
||||||
"deletions": 101,
|
"deletions": 101,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -1863,7 +1849,6 @@ for the current patch set.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 34,
|
"insertions": 34,
|
||||||
"deletions": 45,
|
"deletions": 45,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"_account_id": 1000096,
|
"_account_id": 1000096,
|
||||||
@@ -2128,7 +2113,6 @@ is included.
|
|||||||
"mergeable": false,
|
"mergeable": false,
|
||||||
"insertions": 21,
|
"insertions": 21,
|
||||||
"deletions": 21,
|
"deletions": 21,
|
||||||
"_sortkey": "0024cf9a000012bf",
|
|
||||||
"_number": 4799,
|
"_number": 4799,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -3096,7 +3080,6 @@ describes the resulting cherry picked change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 12,
|
"insertions": 12,
|
||||||
"deletions": 11,
|
"deletions": 11,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"name": "John Doe"
|
||||||
@@ -3150,7 +3133,6 @@ describes the change.
|
|||||||
"mergeable": true,
|
"mergeable": true,
|
||||||
"insertions": 261,
|
"insertions": 261,
|
||||||
"deletions": 101,
|
"deletions": 101,
|
||||||
"_sortkey": "0023412400000f7d",
|
|
||||||
"_number": 3965,
|
"_number": 3965,
|
||||||
"owner": {
|
"owner": {
|
||||||
"name": "John Doe"
|
"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.
|
Number of inserted lines.
|
||||||
|`deletions` ||
|
|`deletions` ||
|
||||||
Number of deleted lines.
|
Number of deleted lines.
|
||||||
|`_sortkey` ||The sortkey of the change.
|
|
||||||
|`_number` ||The legacy numeric ID of the change.
|
|`_number` ||The legacy numeric ID of the change.
|
||||||
|`owner` ||
|
|`owner` ||
|
||||||
The owner of the change as an link:rest-api-accounts.html#account-info[
|
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.
|
if link:#all-revisions[all revisions] are requested.
|
||||||
|`_more_changes` |optional, not set if `false`|
|
|`_more_changes` |optional, not set if `false`|
|
||||||
Whether the query would deliver more results if not limited. +
|
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|
|
|`problems` |optional|
|
||||||
A list of link:#problem-info[ProblemInfo] entities describing potential
|
A list of link:#problem-info[ProblemInfo] entities describing potential
|
||||||
problems with this change. Only set if link:#check[CHECK] is set.
|
problems with this change. Only set if link:#check[CHECK] is set.
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public class ChangeInfo {
|
|||||||
protected String topic;
|
protected String topic;
|
||||||
protected boolean starred;
|
protected boolean starred;
|
||||||
protected Timestamp lastUpdatedOn;
|
protected Timestamp lastUpdatedOn;
|
||||||
protected String sortKey;
|
|
||||||
protected PatchSet.Id patchSetId;
|
protected PatchSet.Id patchSetId;
|
||||||
protected boolean latest;
|
protected boolean latest;
|
||||||
|
|
||||||
@@ -52,7 +51,6 @@ public class ChangeInfo {
|
|||||||
branch = c.getDest().getShortName();
|
branch = c.getDest().getShortName();
|
||||||
topic = c.getTopic();
|
topic = c.getTopic();
|
||||||
lastUpdatedOn = c.getLastUpdatedOn();
|
lastUpdatedOn = c.getLastUpdatedOn();
|
||||||
sortKey = c.getSortKey();
|
|
||||||
patchSetId = patchId;
|
patchSetId = patchId;
|
||||||
latest = patchSetId == null || patchSetId.equals(c.currentPatchSetId());
|
latest = patchSetId == null || patchSetId.equals(c.currentPatchSetId());
|
||||||
}
|
}
|
||||||
@@ -112,8 +110,4 @@ public class ChangeInfo {
|
|||||||
public java.sql.Timestamp getLastUpdatedOn() {
|
public java.sql.Timestamp getLastUpdatedOn() {
|
||||||
return lastUpdatedOn;
|
return lastUpdatedOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSortKey() {
|
|
||||||
return sortKey;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ public class ChangeInfo {
|
|||||||
public Integer insertions;
|
public Integer insertions;
|
||||||
public Integer deletions;
|
public Integer deletions;
|
||||||
|
|
||||||
public String _sortkey;
|
|
||||||
public String baseChange;
|
public String baseChange;
|
||||||
public int _number;
|
public int _number;
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ public class ChangeInfo extends JavaScriptObject {
|
|||||||
private final native String updatedRaw() /*-{ return this.updated; }-*/;
|
private final native String updatedRaw() /*-{ return this.updated; }-*/;
|
||||||
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
|
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
|
||||||
public final native boolean reviewed() /*-{ return this.reviewed ? 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 NativeMap<LabelInfo> all_labels() /*-{ return this.labels; }-*/;
|
||||||
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
|
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
|
||||||
public final native String current_revision() /*-{ return this.current_revision; }-*/;
|
public final native String current_revision() /*-{ return this.current_revision; }-*/;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd.rpc.change;
|
package com.google.gerrit.httpd.rpc.change;
|
||||||
|
|
||||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
import com.google.gerrit.server.query.change.OutputStreamQuery;
|
||||||
import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
|
import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -31,11 +31,11 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class DeprecatedChangeQueryServlet extends HttpServlet {
|
public class DeprecatedChangeQueryServlet extends HttpServlet {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private final Provider<QueryProcessor> processor;
|
private final Provider<OutputStreamQuery> queryProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DeprecatedChangeQueryServlet(Provider<QueryProcessor> processor) {
|
DeprecatedChangeQueryServlet(Provider<OutputStreamQuery> queryProvider) {
|
||||||
this.processor = processor;
|
this.queryProvider = queryProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,7 +44,7 @@ public class DeprecatedChangeQueryServlet extends HttpServlet {
|
|||||||
rsp.setContentType("text/json");
|
rsp.setContentType("text/json");
|
||||||
rsp.setCharacterEncoding("UTF-8");
|
rsp.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
QueryProcessor p = processor.get();
|
OutputStreamQuery p = queryProvider.get();
|
||||||
OutputFormat format = OutputFormat.JSON;
|
OutputFormat format = OutputFormat.JSON;
|
||||||
try {
|
try {
|
||||||
format = OutputFormat.valueOf(get(req, "format", format.toString()));
|
format = OutputFormat.valueOf(get(req, "format", format.toString()));
|
||||||
|
|||||||
@@ -433,9 +433,7 @@ public final class Change {
|
|||||||
@Column(id = 5)
|
@Column(id = 5)
|
||||||
protected Timestamp lastUpdatedOn;
|
protected Timestamp lastUpdatedOn;
|
||||||
|
|
||||||
/** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */
|
// DELETED: id = 6 (sortkey)
|
||||||
@Column(id = 6, length = 16)
|
|
||||||
protected String sortKey;
|
|
||||||
|
|
||||||
@Column(id = 7, name = "owner_account_id")
|
@Column(id = 7, name = "owner_account_id")
|
||||||
protected Account.Id owner;
|
protected Account.Id owner;
|
||||||
@@ -489,7 +487,6 @@ public final class Change {
|
|||||||
rowVersion = other.rowVersion;
|
rowVersion = other.rowVersion;
|
||||||
createdOn = other.createdOn;
|
createdOn = other.createdOn;
|
||||||
lastUpdatedOn = other.lastUpdatedOn;
|
lastUpdatedOn = other.lastUpdatedOn;
|
||||||
sortKey = other.sortKey;
|
|
||||||
owner = other.owner;
|
owner = other.owner;
|
||||||
dest = other.dest;
|
dest = other.dest;
|
||||||
open = other.open;
|
open = other.open;
|
||||||
@@ -534,14 +531,6 @@ public final class Change {
|
|||||||
return rowVersion;
|
return rowVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSortKey() {
|
|
||||||
return sortKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSortKey(final String newSortKey) {
|
|
||||||
sortKey = newSortKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account.Id getOwner() {
|
public Account.Id getOwner() {
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,11 +55,6 @@ public interface ChangeAccess extends Access<Change, Change.Id> {
|
|||||||
@Query("WHERE open = true AND dest = ?")
|
@Query("WHERE open = true AND dest = ?")
|
||||||
ResultSet<Change> byBranchOpenAll(Branch.NameKey p) throws OrmException;
|
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
|
@Query
|
||||||
ResultSet<Change> all() throws OrmException;
|
ResultSet<Change> all() throws OrmException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on);
|
|||||||
|
|
||||||
-- covers: byProjectOpenAll
|
-- covers: byProjectOpenAll
|
||||||
CREATE INDEX changes_byProjectOpen
|
CREATE INDEX changes_byProjectOpen
|
||||||
ON changes (open, dest_project_name, sort_key);
|
ON changes (open, dest_project_name, last_updated_on);
|
||||||
|
|
||||||
-- covers: byProject
|
-- covers: byProject
|
||||||
CREATE INDEX changes_byProject
|
CREATE INDEX changes_byProject
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on)
|
|||||||
|
|
||||||
-- covers: byProjectOpenPrev, byProjectOpenNext
|
-- covers: byProjectOpenPrev, byProjectOpenNext
|
||||||
CREATE INDEX changes_byProjectOpen
|
CREATE INDEX changes_byProjectOpen
|
||||||
ON changes (open, dest_project_name, sort_key)
|
ON changes (open, dest_project_name, last_updated_on);
|
||||||
#
|
#
|
||||||
|
|
||||||
-- covers: byProject
|
-- covers: byProject
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ WHERE status = 's';
|
|||||||
|
|
||||||
-- covers: byProjectOpenAll
|
-- covers: byProjectOpenAll
|
||||||
CREATE INDEX changes_byProjectOpen
|
CREATE INDEX changes_byProjectOpen
|
||||||
ON changes (dest_project_name, sort_key)
|
ON changes (dest_project_name, last_updated_on)
|
||||||
WHERE open = 'Y';
|
WHERE open = 'Y';
|
||||||
|
|
||||||
-- covers: byProject
|
-- covers: byProject
|
||||||
|
|||||||
@@ -15,15 +15,10 @@
|
|||||||
package com.google.gerrit.server;
|
package com.google.gerrit.server;
|
||||||
|
|
||||||
import static com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy.RECEIVE_COMMITS;
|
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.Optional;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import com.google.gerrit.common.TimeUtil;
|
import com.google.gerrit.common.TimeUtil;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
@@ -85,15 +80,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ChangeUtil {
|
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 Object uuidLock = new Object();
|
||||||
private static final int SEED = 0x2418e6f9;
|
private static final int SEED = 0x2418e6f9;
|
||||||
private static int uuidPrefix;
|
private static int uuidPrefix;
|
||||||
@@ -150,7 +136,6 @@ public class ChangeUtil {
|
|||||||
|
|
||||||
public static void updated(Change c) {
|
public static void updated(Change c) {
|
||||||
c.setLastUpdatedOn(TimeUtil.nowTs());
|
c.setLastUpdatedOn(TimeUtil.nowTs());
|
||||||
computeSortKey(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
|
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
|
||||||
@@ -166,29 +151,6 @@ public class ChangeUtil {
|
|||||||
db.patchSetAncestors().insert(toInsert);
|
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,
|
public static PatchSet.Id nextPatchSetId(Map<String, Ref> allRefs,
|
||||||
PatchSet.Id id) {
|
PatchSet.Id id) {
|
||||||
PatchSet.Id next = nextPatchSetId(id);
|
PatchSet.Id next = nextPatchSetId(id);
|
||||||
@@ -585,19 +547,4 @@ public class ChangeUtil {
|
|||||||
public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
|
public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
|
||||||
return new PatchSet.Id(id.getParentKey(), id.get() + 1);
|
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()));
|
patchSet.setRevision(new RevId(commit.name()));
|
||||||
patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
|
patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
|
||||||
change.setCurrentPatchSet(patchSetInfo);
|
change.setCurrentPatchSet(patchSetInfo);
|
||||||
ChangeUtil.computeSortKey(change);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Change getChange() {
|
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.project.SubmitRuleEvaluator;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
|
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.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -252,10 +253,16 @@ public class ChangeJson {
|
|||||||
return format(cd, Optional.of(rsrc.getPatchSet().getId()));
|
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 {
|
throws OrmException {
|
||||||
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
|
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);
|
ChangeData.ensureChangeLoaded(all);
|
||||||
if (has(ALL_REVISIONS)) {
|
if (has(ALL_REVISIONS)) {
|
||||||
ChangeData.ensureAllPatchSetsLoaded(all);
|
ChangeData.ensureAllPatchSetsLoaded(all);
|
||||||
@@ -270,8 +277,12 @@ public class ChangeJson {
|
|||||||
|
|
||||||
List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
|
List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
|
||||||
Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
|
Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
|
||||||
for (List<ChangeData> changes : in) {
|
for (QueryResult r : in) {
|
||||||
res.add(toChangeInfo(out, changes, reviewed));
|
List<ChangeInfo> infos = toChangeInfo(out, r.changes(), reviewed);
|
||||||
|
if (r.moreChanges()) {
|
||||||
|
infos.get(infos.size() - 1)._moreChanges = true;
|
||||||
|
}
|
||||||
|
res.add(infos);
|
||||||
}
|
}
|
||||||
accountLoader.fill();
|
accountLoader.fill();
|
||||||
return res;
|
return res;
|
||||||
@@ -368,7 +379,6 @@ public class ChangeJson {
|
|||||||
out.created = in.getCreatedOn();
|
out.created = in.getCreatedOn();
|
||||||
out.updated = in.getLastUpdatedOn();
|
out.updated = in.getLastUpdatedOn();
|
||||||
out._number = in.getId().get();
|
out._number = in.getId().get();
|
||||||
out._sortkey = in.getSortKey();
|
|
||||||
out.starred = userProvider.get().getStarredChanges().contains(in.getId())
|
out.starred = userProvider.get().getStarredChanges().contains(in.getId())
|
||||||
? true
|
? true
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import com.google.gerrit.reviewdb.client.RevId;
|
|||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.ProjectUtil;
|
import com.google.gerrit.server.ProjectUtil;
|
||||||
@@ -271,7 +270,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
if (change.getStatus().isOpen()) {
|
if (change.getStatus().isOpen()) {
|
||||||
change.setStatus(Change.Status.SUBMITTED);
|
change.setStatus(Change.Status.SUBMITTED);
|
||||||
change.setLastUpdatedOn(timestamp);
|
change.setLastUpdatedOn(timestamp);
|
||||||
ChangeUtil.computeSortKey(change);
|
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public class ChangeAttribute {
|
|||||||
|
|
||||||
public Long createdOn;
|
public Long createdOn;
|
||||||
public Long lastUpdated;
|
public Long lastUpdated;
|
||||||
public String sortKey;
|
|
||||||
public Boolean open;
|
public Boolean open;
|
||||||
public Change.Status status;
|
public Change.Status status;
|
||||||
public List<MessageAttribute> comments;
|
public List<MessageAttribute> comments;
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ public class QueryStatsAttribute {
|
|||||||
public final String type = "stats";
|
public final String type = "stats";
|
||||||
public int rowCount;
|
public int rowCount;
|
||||||
public long runTimeMilliseconds;
|
public long runTimeMilliseconds;
|
||||||
public String resumeSortKey;
|
public boolean moreChanges;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ public class EventFactory {
|
|||||||
public void extend(ChangeAttribute a, Change change) {
|
public void extend(ChangeAttribute a, Change change) {
|
||||||
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
||||||
a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
|
a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
|
||||||
a.sortKey = change.getSortKey();
|
|
||||||
a.open = change.getStatus().isOpen();
|
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.ProjectState;
|
||||||
import com.google.gerrit.server.project.RefControl;
|
import com.google.gerrit.server.project.RefControl;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
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.ssh.SshInfo;
|
||||||
import com.google.gerrit.server.util.LabelVote;
|
import com.google.gerrit.server.util.LabelVote;
|
||||||
import com.google.gerrit.server.util.MagicBranch;
|
import com.google.gerrit.server.util.MagicBranch;
|
||||||
@@ -336,6 +337,7 @@ public class ReceiveCommits {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReceiveCommits(final ReviewDb db,
|
ReceiveCommits(final ReviewDb db,
|
||||||
|
final Provider<QueryProcessor> queryProcessor,
|
||||||
final SchemaFactory<ReviewDb> schemaFactory,
|
final SchemaFactory<ReviewDb> schemaFactory,
|
||||||
final ChangeData.Factory changeDataFactory,
|
final ChangeData.Factory changeDataFactory,
|
||||||
final ChangeUpdate.Factory updateFactory,
|
final ChangeUpdate.Factory updateFactory,
|
||||||
@@ -472,7 +474,7 @@ public class ReceiveCommits {
|
|||||||
});
|
});
|
||||||
advHooks.add(rp.getAdvertiseRefsHook());
|
advHooks.add(rp.getAdvertiseRefsHook());
|
||||||
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
|
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
|
||||||
db, projectControl.getProject().getNameKey()));
|
db, queryProcessor, projectControl.getProject().getNameKey()));
|
||||||
rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
|
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.Maps;
|
||||||
import com.google.common.collect.Sets;
|
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.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
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.gerrit.server.util.MagicBranch;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
@@ -39,6 +44,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -48,11 +54,14 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
|||||||
.getLogger(ReceiveCommitsAdvertiseRefsHook.class);
|
.getLogger(ReceiveCommitsAdvertiseRefsHook.class);
|
||||||
|
|
||||||
private final ReviewDb db;
|
private final ReviewDb db;
|
||||||
|
private final Provider<QueryProcessor> queryProcessor;
|
||||||
private final Project.NameKey projectName;
|
private final Project.NameKey projectName;
|
||||||
|
|
||||||
public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
|
public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
|
||||||
|
Provider<QueryProcessor> queryProcessor,
|
||||||
Project.NameKey projectName) {
|
Project.NameKey projectName) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.queryProcessor = queryProcessor;
|
||||||
this.projectName = projectName;
|
this.projectName = projectName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,10 +102,11 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
|||||||
Set<ObjectId> toInclude = Sets.newHashSet();
|
Set<ObjectId> toInclude = Sets.newHashSet();
|
||||||
|
|
||||||
// Advertise some recent open changes, in case a commit is based one.
|
// Advertise some recent open changes, in case a commit is based one.
|
||||||
|
final int limit = 32;
|
||||||
try {
|
try {
|
||||||
Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(32);
|
Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
|
||||||
for (Change c : db.changes().byProjectOpenNext(projectName, "z", 32)) {
|
for (ChangeData cd : queryRecentChanges(limit)) {
|
||||||
PatchSet.Id id = c.currentPatchSetId();
|
PatchSet.Id id = cd.change().currentPatchSetId();
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
toGet.add(id);
|
toGet.add(id);
|
||||||
}
|
}
|
||||||
@@ -164,6 +174,20 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
|||||||
return toInclude;
|
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) {
|
private static boolean skip(String name) {
|
||||||
return name.startsWith(RefNames.REFS_CHANGES)
|
return name.startsWith(RefNames.REFS_CHANGES)
|
||||||
|| name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
|
|| name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.index;
|
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.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
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.AndSource;
|
||||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
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.ChangeQueryRewriter;
|
||||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
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.gerrit.server.query.change.OrSource;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
@@ -129,16 +128,14 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
|
public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start,
|
||||||
throws QueryParseException {
|
int limit) throws QueryParseException {
|
||||||
|
checkArgument(limit > 0, "limit must be positive: %s", limit);
|
||||||
ChangeIndex index = indexes.getSearchIndex();
|
ChangeIndex index = indexes.getSearchIndex();
|
||||||
in = basicRewrites.rewrite(in);
|
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
|
// Increase the limit rather than skipping, since we don't know how many
|
||||||
// skipped results would have been filtered out by the enclosing AndSource.
|
// skipped results would have been filtered out by the enclosing AndSource.
|
||||||
limit += start;
|
limit += start;
|
||||||
limit = Math.max(limit, 1);
|
|
||||||
|
|
||||||
Predicate<ChangeData> out = rewriteImpl(in, index, limit);
|
Predicate<ChangeData> out = rewriteImpl(in, index, limit);
|
||||||
if (in == out || out instanceof IndexPredicate) {
|
if (in == out || out instanceof IndexPredicate) {
|
||||||
@@ -168,6 +165,9 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
|||||||
ChangeIndex index, int limit) throws QueryParseException {
|
ChangeIndex index, int limit) throws QueryParseException {
|
||||||
if (isIndexPredicate(in, index)) {
|
if (isIndexPredicate(in, index)) {
|
||||||
return in;
|
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)) {
|
} else if (!isRewritePossible(in)) {
|
||||||
return null; // magic to indicate "in" cannot be rewritten
|
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 p the predicate to find.
|
||||||
* @param clazz type of the predicate instance.
|
* @param clazz type of the predicate instance.
|
||||||
* @param name name of the operator.
|
* @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")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T, P extends OperatorPredicate<T>> P find(Predicate<T> p,
|
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.client.Change;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
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.Predicate;
|
||||||
import com.google.gerrit.server.query.QueryRewriter;
|
import com.google.gerrit.server.query.QueryRewriter;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.OutOfScopeException;
|
import com.google.inject.OutOfScopeException;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.name.Named;
|
|
||||||
|
|
||||||
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||||
private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
|
private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
|
||||||
@@ -69,14 +67,6 @@ public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
|||||||
ChangeStatusPredicate.forStatus(Change.Status.MERGED));
|
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> {
|
private static final class InvalidProvider<T> implements Provider<T> {
|
||||||
@Override
|
@Override
|
||||||
public T get() {
|
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.ChangeControl;
|
||||||
import com.google.gerrit.server.project.ListChildProjects;
|
import com.google.gerrit.server.project.ListChildProjects;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
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.Predicate;
|
||||||
import com.google.gerrit.server.query.QueryBuilder;
|
import com.google.gerrit.server.query.QueryBuilder;
|
||||||
import com.google.gerrit.server.query.QueryParseException;
|
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 =
|
private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
|
||||||
new QueryBuilder.Definition<>(ChangeQueryBuilder.class);
|
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
|
@VisibleForTesting
|
||||||
public static class Arguments {
|
public static class Arguments {
|
||||||
final Provider<ReviewDb> db;
|
final Provider<ReviewDb> db;
|
||||||
@@ -633,28 +626,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operator
|
@Operator
|
||||||
public Predicate<ChangeData> limit(String limit) {
|
public Predicate<ChangeData> limit(String limit) throws QueryParseException {
|
||||||
return limit(Integer.parseInt(limit));
|
return new LimitPredicate(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operator
|
@Operator
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ import com.google.gerrit.server.query.Predicate;
|
|||||||
import com.google.gerrit.server.query.QueryParseException;
|
import com.google.gerrit.server.query.QueryParseException;
|
||||||
|
|
||||||
public interface ChangeQueryRewriter {
|
public interface ChangeQueryRewriter {
|
||||||
Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
|
Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start, int limit)
|
||||||
throws QueryParseException;
|
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 org.kohsuke.args4j.Option;
|
||||||
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -42,7 +41,6 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
|
|||||||
private final ChangeJson json;
|
private final ChangeJson json;
|
||||||
private final QueryProcessor imp;
|
private final QueryProcessor imp;
|
||||||
private final Provider<CurrentUser> user;
|
private final Provider<CurrentUser> user;
|
||||||
private boolean reverse;
|
|
||||||
private EnumSet<ListChangesOption> options;
|
private EnumSet<ListChangesOption> options;
|
||||||
|
|
||||||
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
|
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
|
||||||
@@ -137,32 +135,15 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
|
|||||||
private List<List<ChangeInfo>> query0() throws OrmException,
|
private List<List<ChangeInfo>> query0() throws OrmException,
|
||||||
QueryParseException {
|
QueryParseException {
|
||||||
int cnt = queries.size();
|
int cnt = queries.size();
|
||||||
BitSet more = new BitSet(cnt);
|
List<QueryResult> results = imp.queryByStrings(queries);
|
||||||
List<List<ChangeData>> data = imp.queryChanges(queries);
|
List<List<ChangeInfo>> res = json.addOptions(options)
|
||||||
for (int n = 0; n < cnt; n++) {
|
.formatQueryResults(results);
|
||||||
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);
|
|
||||||
for (int n = 0; n < cnt; n++) {
|
for (int n = 0; n < cnt; n++) {
|
||||||
List<ChangeInfo> info = res.get(n);
|
List<ChangeInfo> info = res.get(n);
|
||||||
if (more.get(n) && !info.isEmpty()) {
|
if (results.get(n).moreChanges()) {
|
||||||
if (reverse) {
|
|
||||||
info.get(0)._moreChanges = true;
|
|
||||||
} else {
|
|
||||||
info.get(info.size() - 1)._moreChanges = true;
|
info.get(info.size() - 1)._moreChanges = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,494 +14,178 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.query.change;
|
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.ImmutableList;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.gerrit.common.TimeUtil;
|
|
||||||
import com.google.gerrit.common.data.GlobalCapability;
|
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.CurrentUser;
|
||||||
import com.google.gerrit.server.config.TrackingFooters;
|
import com.google.gerrit.server.index.IndexPredicate;
|
||||||
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.Predicate;
|
import com.google.gerrit.server.query.Predicate;
|
||||||
import com.google.gerrit.server.query.QueryParseException;
|
import com.google.gerrit.server.query.QueryParseException;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.gwtorm.server.ResultSet;
|
import com.google.gwtorm.server.ResultSet;
|
||||||
import com.google.inject.Inject;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class QueryProcessor {
|
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 ChangeQueryBuilder queryBuilder;
|
||||||
private final ChangeQueryRewriter queryRewriter;
|
private final ChangeQueryRewriter queryRewriter;
|
||||||
private final TrackingFooters trackingFooters;
|
private final int permittedLimit;
|
||||||
private final CurrentUser user;
|
|
||||||
private final int maxLimit;
|
|
||||||
|
|
||||||
private OutputFormat outputFormat = OutputFormat.TEXT;
|
private int limitFromCaller;
|
||||||
private int limit;
|
|
||||||
private int start;
|
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
|
@Inject
|
||||||
QueryProcessor(EventFactory eventFactory,
|
QueryProcessor(ChangeQueryBuilder.Factory queryBuilder,
|
||||||
ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
|
CurrentUser currentUser,
|
||||||
ChangeQueryRewriter queryRewriter,
|
ChangeQueryRewriter queryRewriter) {
|
||||||
TrackingFooters trackingFooters) {
|
|
||||||
this.eventFactory = eventFactory;
|
|
||||||
this.queryBuilder = queryBuilder.create(currentUser);
|
this.queryBuilder = queryBuilder.create(currentUser);
|
||||||
this.queryRewriter = queryRewriter;
|
this.queryRewriter = queryRewriter;
|
||||||
this.trackingFooters = trackingFooters;
|
this.permittedLimit = currentUser.getCapabilities()
|
||||||
this.user = currentUser;
|
|
||||||
this.maxLimit = currentUser.getCapabilities()
|
|
||||||
.getRange(GlobalCapability.QUERY_LIMIT)
|
.getRange(GlobalCapability.QUERY_LIMIT)
|
||||||
.getMax();
|
.getMax();
|
||||||
this.moreResults = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getLimit() {
|
public ChangeQueryBuilder getQueryBuilder() {
|
||||||
return limit;
|
return queryBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLimit(int n) {
|
public void setLimit(int n) {
|
||||||
limit = n;
|
limitFromCaller = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStart(int n) {
|
public void setStart(int n) {
|
||||||
start = n;
|
start = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIncludePatchSets(boolean on) {
|
/**
|
||||||
includePatchSets = on;
|
* Query for changes that match the query string.
|
||||||
}
|
*
|
||||||
|
* @see #queryChanges(List)
|
||||||
public boolean getIncludePatchSets() {
|
* @param queryString the query string to parse.
|
||||||
return includePatchSets;
|
* @return results of the query.
|
||||||
}
|
*/
|
||||||
|
public QueryResult queryByString(String queryString)
|
||||||
public void setIncludeCurrentPatchSet(boolean on) {
|
throws OrmException, QueryParseException {
|
||||||
includeCurrentPatchSet = on;
|
return queryByStrings(ImmutableList.of(queryString)).get(0);
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
* 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>
|
* <p>
|
||||||
* If a limit was specified using {@link #setLimit(int)} this method may
|
* 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
|
* 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
|
* there are more than {@code limit} matches and suggest to its own caller
|
||||||
* that the query could be retried with {@link #setStart(int)}.
|
* 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 {
|
throws OrmException, QueryParseException {
|
||||||
return queryChanges(ImmutableList.of(queryString)).get(0);
|
return queryChanges(null, queries);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static {
|
||||||
* Query for changes that match the query string.
|
// In addition to this assumption, this queryChanges assumes the basic
|
||||||
* <p>
|
// rewrites do not touch visibleto predicates either.
|
||||||
* If a limit was specified using {@link #setLimit(int)} this method may
|
checkState(
|
||||||
* return up to {@code limit + 1} results, allowing the caller to determine if
|
!IsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
|
||||||
* there are more than {@code limit} matches and suggest to its own caller
|
"QueryProcessor assumes visibleto is not used by the index rewriter.");
|
||||||
* that the query could be retried with {@link #setStart(int)}.
|
}
|
||||||
*/
|
|
||||||
public List<List<ChangeData>> queryChanges(List<String> queries)
|
private List<QueryResult> queryChanges(List<String> queryStrings,
|
||||||
|
List<Predicate<ChangeData>> queries)
|
||||||
throws OrmException, QueryParseException {
|
throws OrmException, QueryParseException {
|
||||||
final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
|
Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
|
||||||
int cnt = queries.size();
|
int cnt = queries.size();
|
||||||
|
|
||||||
// Parse and rewrite all queries.
|
// Parse and rewrite all queries.
|
||||||
List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
|
List<Integer> limits = new ArrayList<>(cnt);
|
||||||
List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
|
List<Predicate<ChangeData>> predicates = new ArrayList<>(cnt);
|
||||||
for (String query : queries) {
|
List<ChangeDataSource> sources = new ArrayList<>(cnt);
|
||||||
Predicate<ChangeData> q = parseQuery(query, visibleToMe);
|
for (Predicate<ChangeData> q : queries) {
|
||||||
Predicate<ChangeData> s = queryRewriter.rewrite(q, start);
|
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)) {
|
if (!(s instanceof ChangeDataSource)) {
|
||||||
q = Predicate.and(queryBuilder.status_open(), q);
|
q = Predicate.and(queryBuilder.status_open(), q);
|
||||||
s = queryRewriter.rewrite(q, start);
|
s = queryRewriter.rewrite(q, start, limit);
|
||||||
}
|
}
|
||||||
if (!(s instanceof ChangeDataSource)) {
|
if (!(s instanceof ChangeDataSource)) {
|
||||||
throw new QueryParseException("invalid query: " + s);
|
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);
|
AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start);
|
||||||
limits.add(limit(q));
|
predicates.add(a);
|
||||||
sources.add(a);
|
sources.add(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run each query asynchronously, if supported.
|
// Run each query asynchronously, if supported.
|
||||||
List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
|
List<ResultSet<ChangeData>> matches = new ArrayList<>(cnt);
|
||||||
for (ChangeDataSource s : sources) {
|
for (ChangeDataSource s : sources) {
|
||||||
matches.add(s.read());
|
matches.add(s.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
|
List<QueryResult> out = new ArrayList<>(cnt);
|
||||||
for (int i = 0; i < cnt; i++) {
|
for (int i = 0; i < cnt; i++) {
|
||||||
List<ChangeData> results = matches.get(i).toList();
|
out.add(QueryResult.create(
|
||||||
if (results.size() > maxLimit) {
|
queryStrings != null ? queryStrings.get(i) : null,
|
||||||
moreResults = true;
|
predicates.get(i),
|
||||||
}
|
limits.get(i),
|
||||||
int limit = limits.get(i);
|
matches.get(i).toList()));
|
||||||
if (limit < results.size()) {
|
|
||||||
results = results.subList(0, limit);
|
|
||||||
}
|
|
||||||
out.add(results);
|
|
||||||
}
|
}
|
||||||
return out;
|
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() {
|
boolean isDisabled() {
|
||||||
return maxLimit <= 0;
|
return permittedLimit <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int limit(Predicate<ChangeData> s) {
|
private int getEffectiveLimit(Predicate<ChangeData> p) {
|
||||||
int n = MoreObjects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit);
|
List<Integer> possibleLimits = new ArrayList<>(3);
|
||||||
return limit > 0 ? Math.min(n, limit) + 1 : n + 1;
|
possibleLimits.add(permittedLimit);
|
||||||
|
if (limitFromCaller > 0) {
|
||||||
|
possibleLimits.add(limitFromCaller);
|
||||||
}
|
}
|
||||||
|
Integer limitFromPredicate = LimitPredicate.getLimit(p);
|
||||||
private Predicate<ChangeData> parseQuery(String queryString,
|
if (limitFromPredicate != null) {
|
||||||
final Predicate<ChangeData> visibleToMe) throws QueryParseException {
|
possibleLimits.add(limitFromPredicate);
|
||||||
return Predicate.and(queryBuilder.parse(queryString),
|
|
||||||
queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
|
|
||||||
visibleToMe);
|
|
||||||
}
|
}
|
||||||
|
return Ordering.natural().min(possibleLimits);
|
||||||
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 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(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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** 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 {
|
public static class Module extends AbstractModule {
|
||||||
@Override
|
@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.PatchSetApproval.LabelId;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.AccountManager;
|
import com.google.gerrit.server.account.AccountManager;
|
||||||
import com.google.gerrit.server.account.AuthRequest;
|
import com.google.gerrit.server.account.AuthRequest;
|
||||||
@@ -112,7 +111,6 @@ public class LabelNormalizerTest {
|
|||||||
new Change.Id(1), userId,
|
new Change.Id(1), userId,
|
||||||
new Branch.NameKey(allProjects, "refs/heads/master"),
|
new Branch.NameKey(allProjects, "refs/heads/master"),
|
||||||
TimeUtil.nowTs());
|
TimeUtil.nowTs());
|
||||||
ChangeUtil.computeSortKey(change);
|
|
||||||
PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
|
PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
|
||||||
ps.setSubject("Test change");
|
ps.setSubject("Test change");
|
||||||
change.setCurrentPatchSet(ps);
|
change.setCurrentPatchSet(ps);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class IndexRewriteTest {
|
|||||||
parse("-status:abandoned (status:open OR status:merged)");
|
parse("-status:abandoned (status:open OR status:merged)");
|
||||||
assertEquals(
|
assertEquals(
|
||||||
query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
|
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
|
@Test
|
||||||
@@ -158,24 +158,26 @@ public class IndexRewriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLimit() throws Exception {
|
public void testLimitArgumentOverridesAllLimitPredicates() throws Exception {
|
||||||
Predicate<ChangeData> in = parse("file:a limit:3");
|
Predicate<ChangeData> in = parse("limit:1 file:a limit:3");
|
||||||
Predicate<ChangeData> out = rewrite(in);
|
Predicate<ChangeData> out = rewrite(in, 5);
|
||||||
assertSame(AndSource.class, out.getClass());
|
assertSame(AndSource.class, out.getClass());
|
||||||
assertEquals(ImmutableList.of(
|
assertEquals(ImmutableList.of(
|
||||||
query(in.getChild(0), 3),
|
query(in.getChild(1), 5),
|
||||||
in.getChild(1)),
|
parse("limit:5"),
|
||||||
|
parse("limit:5")),
|
||||||
out.getChildren());
|
out.getChildren());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartIncreasesLimit() throws Exception {
|
public void testStartIncreasesLimit() throws Exception {
|
||||||
|
int n = 3;
|
||||||
Predicate<ChangeData> f = parse("file:a");
|
Predicate<ChangeData> f = parse("file:a");
|
||||||
Predicate<ChangeData> l = parse("limit:3");
|
Predicate<ChangeData> l = parse("limit:" + n);
|
||||||
Predicate<ChangeData> in = and(f, l);
|
Predicate<ChangeData> in = and(f, l);
|
||||||
assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0));
|
assertEquals(and(query(f, 3), parse("limit:3")), rewrite.rewrite(in, 0, n));
|
||||||
assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1));
|
assertEquals(and(query(f, 4), parse("limit:4")), rewrite.rewrite(in, 1, n));
|
||||||
assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2));
|
assertEquals(and(query(f, 5), parse("limit:5")), rewrite.rewrite(in, 2, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -220,7 +222,12 @@ public class IndexRewriteTest {
|
|||||||
|
|
||||||
private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
|
private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
|
||||||
throws QueryParseException {
|
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)
|
private IndexedChangeQuery query(Predicate<ChangeData> p)
|
||||||
|
|||||||
@@ -136,18 +136,22 @@ public abstract class AbstractQueryChangesTest {
|
|||||||
userAccount.setPreferredEmail("user@example.com");
|
userAccount.setPreferredEmail("user@example.com");
|
||||||
db.accounts().update(ImmutableList.of(userAccount));
|
db.accounts().update(ImmutableList.of(userAccount));
|
||||||
user = userFactory.create(userId);
|
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
|
@Override
|
||||||
public CurrentUser getCurrentUser() {
|
public CurrentUser getCurrentUser() {
|
||||||
return user;
|
return requestUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Provider<ReviewDb> getReviewDbProvider() {
|
public Provider<ReviewDb> getReviewDbProvider() {
|
||||||
return Providers.of(db);
|
return Providers.of(db);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -543,9 +547,22 @@ public abstract class AbstractQueryChangesTest {
|
|||||||
|
|
||||||
List<ChangeInfo> results;
|
List<ChangeInfo> results;
|
||||||
for (int i = 1; i <= n + 2; i++) {
|
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);
|
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));
|
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);
|
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(
|
protected ChangeInserter newChange(
|
||||||
TestRepository<InMemoryRepository> repo,
|
TestRepository<InMemoryRepository> repo,
|
||||||
@Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
|
@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.PatchSetInfo;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.config.AllUsersNameProvider;
|
import com.google.gerrit.server.config.AllUsersNameProvider;
|
||||||
@@ -114,6 +113,5 @@ public class TestChanges {
|
|||||||
change.getId(), curr != null ? curr.get() + 1 : 1));
|
change.getId(), curr != null ? curr.get() + 1 : 1));
|
||||||
ps.setSubject("Change subject");
|
ps.setSubject("Change subject");
|
||||||
change.setCurrentPatchSet(ps);
|
change.setCurrentPatchSet(ps);
|
||||||
ChangeUtil.computeSortKey(change);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.sshd.commands;
|
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.CommandMetaData;
|
||||||
import com.google.gerrit.sshd.SshCommand;
|
import com.google.gerrit.sshd.SshCommand;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -27,10 +28,10 @@ import java.util.List;
|
|||||||
@CommandMetaData(name = "query", description = "Query the change database")
|
@CommandMetaData(name = "query", description = "Query the change database")
|
||||||
class Query extends SshCommand {
|
class Query extends SshCommand {
|
||||||
@Inject
|
@Inject
|
||||||
private QueryProcessor processor;
|
private OutputStreamQuery processor;
|
||||||
|
|
||||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||||
void setFormat(QueryProcessor.OutputFormat format) {
|
void setFormat(OutputFormat format) {
|
||||||
processor.setOutput(out, format);
|
processor.setOutput(out, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ class Query extends SshCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void parseCommandLine() throws UnloggedFailure {
|
protected void parseCommandLine() throws UnloggedFailure {
|
||||||
processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
|
processor.setOutput(out, OutputFormat.TEXT);
|
||||||
super.parseCommandLine();
|
super.parseCommandLine();
|
||||||
if (processor.getIncludeFiles() &&
|
if (processor.getIncludeFiles() &&
|
||||||
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
|
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
|
||||||
|
|||||||
Reference in New Issue
Block a user