Support pagination using offsets instead of sortkey
We cannot guarantee that secondary index implementations (particularly the one used by googlesource.com) can efficiently paginate based on the sortkey, and in particular can both sort and reverse sort on the same field. This particular performance issue notwithstanding, searching is now generally fast enough that it is feasible just to skip the first N results when doing pagination. Add an option S= to QueryChanges to support starting at a nonzero offset. Note that we still have to fetch n+S results from the index in order to do visibility filtering, since if we skipped at the index layer we wouldn't know how many of the skipped elements would have matched later filtering. Drop the sortkey token suffix from the legacy anchor parser; there is no reliable way to convert it to an offset, and it's unlikely that users have permalinks to specific sortkey values. On the server side, remove the sortkey field from the current index version, and use pagination by offset instead of sortkey in the new version only. Continue to support sortkey queries against old index versions, to support online reindexing while clients have an older JS version. Change-Id: I6a82965db02c4d534e2107ca6ec91217085124d6
This commit is contained in:
		| @@ -22,6 +22,7 @@ import static com.google.gerrit.common.PageLinks.ADMIN_PROJECTS; | ||||
| import static com.google.gerrit.common.PageLinks.DASHBOARDS; | ||||
| import static com.google.gerrit.common.PageLinks.MINE; | ||||
| import static com.google.gerrit.common.PageLinks.PROJECTS; | ||||
| import static com.google.gerrit.common.PageLinks.QUERY; | ||||
| import static com.google.gerrit.common.PageLinks.REGISTER; | ||||
| import static com.google.gerrit.common.PageLinks.SETTINGS; | ||||
| import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS; | ||||
| @@ -34,6 +35,7 @@ import static com.google.gerrit.common.PageLinks.SETTINGS_PROJECTS; | ||||
| import static com.google.gerrit.common.PageLinks.SETTINGS_SSHKEYS; | ||||
| import static com.google.gerrit.common.PageLinks.SETTINGS_WEBIDENT; | ||||
| import static com.google.gerrit.common.PageLinks.op; | ||||
| import static com.google.gerrit.common.PageLinks.toChangeQuery; | ||||
|  | ||||
| import com.google.gerrit.client.account.MyAgreementsScreen; | ||||
| import com.google.gerrit.client.account.MyContactInformationScreen; | ||||
| @@ -83,8 +85,8 @@ import com.google.gerrit.client.ui.Screen; | ||||
| import com.google.gerrit.common.PageLinks; | ||||
| import com.google.gerrit.common.data.PatchSetDetail; | ||||
| import com.google.gerrit.reviewdb.client.Account; | ||||
| import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView; | ||||
| import com.google.gerrit.reviewdb.client.AccountGeneralPreferences; | ||||
| import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView; | ||||
| import com.google.gerrit.reviewdb.client.AccountGroup; | ||||
| import com.google.gerrit.reviewdb.client.Change; | ||||
| import com.google.gerrit.reviewdb.client.Patch; | ||||
| @@ -218,7 +220,7 @@ public class Dispatcher { | ||||
|   } | ||||
|  | ||||
|   private static void select(final String token) { | ||||
|     if (matchPrefix("/q/", token)) { | ||||
|     if (matchPrefix(QUERY, token)) { | ||||
|       query(token); | ||||
|  | ||||
|     } else if (matchPrefix("/Documentation/", token)) { | ||||
| @@ -291,19 +293,19 @@ public class Dispatcher { | ||||
|     } | ||||
|  | ||||
|     if (matchExact("mine,starred", token)) { | ||||
|       return PageLinks.toChangeQuery("is:starred"); | ||||
|       return toChangeQuery("is:starred"); | ||||
|     } | ||||
|  | ||||
|     if (matchExact("mine,drafts", token)) { | ||||
|       return PageLinks.toChangeQuery("is:draft"); | ||||
|       return toChangeQuery("is:draft"); | ||||
|     } | ||||
|  | ||||
|     if (matchExact("mine,comments", token)) { | ||||
|       return PageLinks.toChangeQuery("has:draft"); | ||||
|       return toChangeQuery("has:draft"); | ||||
|     } | ||||
|  | ||||
|     if (matchPrefix("mine,watched,", token)) { | ||||
|       return PageLinks.toChangeQuery("is:watched status:open", skip(token)); | ||||
|       return toChangeQuery("is:watched status:open"); | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
| @@ -311,15 +313,15 @@ public class Dispatcher { | ||||
|  | ||||
|   private static String legacyAll(final String token) { | ||||
|     if (matchPrefix("all,abandoned,", token)) { | ||||
|       return PageLinks.toChangeQuery("status:abandoned", skip(token)); | ||||
|       return toChangeQuery("status:abandoned"); | ||||
|     } | ||||
|  | ||||
|     if (matchPrefix("all,merged,", token)) { | ||||
|       return PageLinks.toChangeQuery("status:merged", skip(token)); | ||||
|       return toChangeQuery("status:merged"); | ||||
|     } | ||||
|  | ||||
|     if (matchPrefix("all,open,", token)) { | ||||
|       return PageLinks.toChangeQuery("status:open", skip(token)); | ||||
|       return toChangeQuery("status:open"); | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
| @@ -330,27 +332,21 @@ public class Dispatcher { | ||||
|       final String s = skip(token); | ||||
|       final int c = s.indexOf(','); | ||||
|       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); | ||||
|       return PageLinks.toChangeQuery( // | ||||
|           "status:open " + op("project", proj.get()), // | ||||
|           s.substring(c + 1)); | ||||
|       return toChangeQuery("status:open " + op("project", proj.get())); | ||||
|     } | ||||
|  | ||||
|     if (matchPrefix("project,merged,", token)) { | ||||
|       final String s = skip(token); | ||||
|       final int c = s.indexOf(','); | ||||
|       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); | ||||
|       return PageLinks.toChangeQuery( // | ||||
|           "status:merged " + op("project", proj.get()), // | ||||
|           s.substring(c + 1)); | ||||
|       return toChangeQuery("status:merged " + op("project", proj.get())); | ||||
|     } | ||||
|  | ||||
|     if (matchPrefix("project,abandoned,", token)) { | ||||
|       final String s = skip(token); | ||||
|       final int c = s.indexOf(','); | ||||
|       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c)); | ||||
|       return PageLinks.toChangeQuery( // | ||||
|           "status:abandoned " + op("project", proj.get()), // | ||||
|           s.substring(c + 1)); | ||||
|       return toChangeQuery("status:abandoned " + op("project", proj.get())); | ||||
|     } | ||||
|  | ||||
|     return null; | ||||
| @@ -408,10 +404,22 @@ public class Dispatcher { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   private static void query(final String token) { | ||||
|     final String s = skip(token); | ||||
|     final int c = s.indexOf(','); | ||||
|     Gerrit.display(token, new QueryScreen(s.substring(0, c), s.substring(c + 1))); | ||||
|   private static void query(String token) { | ||||
|     String s = skip(token); | ||||
|     int c = s.indexOf(','); | ||||
|     Screen screen; | ||||
|     if (c >= 0) { | ||||
|       String prefix = s.substring(0, c); | ||||
|       if (s.substring(c).equals(",n,z")) { | ||||
|         // Respect legacy token with max sortkey. | ||||
|         screen = new QueryScreen(prefix, 0); | ||||
|       } else { | ||||
|         screen = new QueryScreen(prefix, Integer.parseInt(s.substring(c + 1))); | ||||
|       } | ||||
|     } else { | ||||
|       screen = new QueryScreen(s, 0); | ||||
|     } | ||||
|     Gerrit.display(token, screen); | ||||
|   } | ||||
|  | ||||
|   private static Screen mine(final String token) { | ||||
| @@ -462,7 +470,7 @@ public class Dispatcher { | ||||
|           @Override | ||||
|           public void onFailure(Throwable caught) { | ||||
|             if ("default".equals(dashboardId) && RestApi.isNotFound(caught)) { | ||||
|               Gerrit.display(PageLinks.toChangeQuery( | ||||
|               Gerrit.display(toChangeQuery( | ||||
|                   PageLinks.projectQuery(new Project.NameKey(project)))); | ||||
|             } else { | ||||
|               super.onFailure(caught); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dave Borowitz
					Dave Borowitz