Merge "Create separate bazel test target for each directory's template test"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 71474f5..1dc43dd 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3286,8 +3286,8 @@
 === Section noteDb
 
 NoteDb is the next generation of Gerrit storage backend, currently powering
-`googlesource.com`. It is not (yet) recommended for general use, but if you want
-to learn more, see the link:dev-note-db.html[developer documentation].
+`googlesource.com`. For more information, including how to migrate your data,
+see the link:note-db.html[documentation].
 
 [[notedb.accounts.sequenceBatchSize]]notedb.accounts.sequenceBatchSize::
 +
@@ -3302,6 +3302,26 @@
 +
 By default, 1.
 
+[[noteDb.retryMaxWait]]noteDb.retryMaxWait::
++
+Maximum time to wait between attempts to retry update operations when one
+attempt fails due to contention (aka lock failure) on the underlying ref
+storage. Operations are retried with exponential backoff, plus some random
+jitter, until the interval reaches this limit. After that, retries continue to
+occur after a fixed timeout (plus jitter), up to
+link:#noteDb.retryTimeout[`noteDb.retryTimeout`].
++
+Defaults to 5 seconds; unit suffixes are supported, and assumes milliseconds if
+not specified.
+
+[[noteDb.retryTimeout]]noteDb.retryTimeout::
++
+Total timeout for retrying update operations when one attempt fails due to
+contention (aka lock failure) on the underlying ref storage.
++
+Defaults to 20 seconds; unit suffixes are supported, and assumes milliseconds if
+not specified.
+
 [[oauth]]
 === Section oauth
 
diff --git a/Documentation/dev-note-db.txt b/Documentation/dev-note-db.txt
deleted file mode 100644
index bacbeda..0000000
--- a/Documentation/dev-note-db.txt
+++ /dev/null
@@ -1,179 +0,0 @@
-= Gerrit Code Review - NoteDb Backend
-
-NoteDb is the next generation of Gerrit storage backend, which replaces the
-traditional SQL backend for change and account metadata with storing data in the
-same repository as code changes.
-
-.Advantages
-- *Simplicity*: All data is stored in one location in the site directory, rather
-  than being split between the site directory and a possibly external database
-  server.
-- *Consistency*: Replication and backups can use a snapshot of the Git
-  repository refs, which will include both the branch and patch set refs, and
-  the change metadata that points to them.
-- *Auditability*: Rather than storing mutable rows in a database, modifications
-  to changes are stored as a sequence of Git commits, automatically preserving
-  history of the metadata. +
-  There are no strict guarantees, and meta refs may be rewritten, but the
-  default assumption is that all operations are logged.
-- *Extensibility*: Plugin developers can add new fields to metadata without the
-  core database schema having to know about them.
-- *New features*: Enables simple federation between Gerrit servers, as well as
-  offline code review and interoperation with other tools.
-
-== Current Status
-
-- Storing change metadata is fully implemented in master, and is live on the
-  servers behind `googlesource.com`. In other words, if you use
-  link:https://gerrit-review.googlesource.com/[gerrit-review], you're already
-  using NoteDb. +
-- Storing some account data, e.g. user preferences, is implemented in releases
-  back to 2.13.
-- Storing the rest of account data is a work in progress.
-- Storing group data is a work in progress.
-
-To use a configuration similar to the current state of `googlesource.com`, paste
-the following config snippet in your `gerrit.config`:
-
-----
-[noteDb "changes"]
-  write = true
-  read = true
-  primaryStorage = NOTE_DB
-  disableReviewDb = true
-----
-
-This is the configuration that will be produced if you enable experimental
-NoteDb support on a new site with `init`.
-
-`googlesource.com` additionally uses `fuseUpdates = true`, because its repo
-backend supports atomic multi-ref transactions. Native JGit doesn't, so setting
-this option on a dev server would fail.
-
-For an example NoteDb change, poke around at this one:
-----
-  git fetch https://gerrit.googlesource.com/gerrit refs/changes/70/98070/meta \
-      && git log -p FETCH_HEAD
-----
-
-== Configuration
-
-Account and group data is migrated to NoteDb automatically using the normal
-schema upgrade process during updates. The remainder of this section details the
-configuration options that control migration of the change data, which is mostly
-but not fully implemented.
-
-Change migration state is configured in `gerrit.config` with options like
-`noteDb.changes.*`. These options are undocumented outside of this file, and the
-general approach has been to add one new option for each phase of the migration.
-Assume that each config option in the following list requires all of the
-previous options, unless otherwise noted.
-
-- `noteDb.changes.write=true`: During a ReviewDb write, the state of the change
-  in NoteDb is written to the `note_db_state` field in the `Change` entity.
-  After the ReviewDb write, this state is written into NoteDb, resulting in
-  effectively double the time for write operations. NoteDb write errors are
-  dropped on the floor, and no attempt is made to read from ReviewDb or correct
-  errors (without additional configuration, below). +
-  This state allows for a rolling update in a multi-master setting, where some
-  servers can start reading from NoteDb, but older servers are still reading
-  only from ReviewDb.
-- `noteDb.changes.read=true`: Change data is written
-  to and read from NoteDb, but ReviewDb is still the source of truth. During
-  reads, first read the change from ReviewDb, and compare its `note_db_state`
-  with what is in NoteDb. If it doesn't match, immediately "auto-rebuild" the
-  change, copying data from ReviewDb to NoteDb and returning the result.
-- `noteDb.changes.primaryStorage=NOTE_DB`: New changes are written only to
-  NoteDb, but changes whose primary storage is ReviewDb are still supported.
-  Continues to read from ReviewDb first as in the previous stage, but if the
-  change is not in ReviewDb, falls back to reading from NoteDb. +
-  Migration of existing changes is described in the link:#migration[Migration]
-  section below. +
-  Due to an implementation detail, writes to Changes or related tables still
-  result in write calls to the database layer, but they are inside a transaction
-  that is always rolled back.
-- `noteDb.changes.disableReviewDb=true`: All access to Changes or related tables
-  is disabled; reads return no results, and writes are no-ops. Assumes the state
-  of all changes in NoteDb is accurate, and so is only safe once all changes are
-  NoteDb primary. Otherwise, reading changes only from NoteDb might result in
-  inaccurate results, and writing to NoteDb would compound the problem. +
-  Thus it is up to an admin of a previously-ReviewDb site to ensure
-  MigratePrimaryStorage has been run for all changes. Note that the current
-  implementation of the `migrate-to-note-db` program does not do this. +
-  In this phase, it would be possible to delete the Changes tables out from
-  under a running server with no effect.
-- `noteDb.changes.fuseUpdates=true`: Code and meta updates within a single
-  repository are fused into a single atomic `BatchRefUpdate`, so they either
-  all succeed or all fail. This mode is only possible on a backend that
-  supports atomic ref updates, which notably excludes the default file repository
-  backend.
-
-[[migration]]
-== Migration
-
-Once configuration options are set, migration to NoteDb is primarily
-accomplished by running the `migrate-to-note-db` program. Currently, this program
-bulk copies ReviewDb data into NoteDb, but leaves primary storage of these
-changes in ReviewDb, so the site is runnable with
-`noteDb.changes.{write,read}=true`, but ReviewDb is still required.
-
-Eventually, `migrate-to-note-db` will set primary storage to NoteDb for all
-changes by default, so a site will be able to stop using ReviewDb for changes
-immediately after a successful run.
-
-There is code in `PrimaryStorageMigrator.java` to migrate individual changes
-from NoteDb primary to ReviewDb primary. This code is not intended to be used
-except in the event of a critical bug in NoteDb primary changes in production.
-It will likely never be used by `migrate-to-note-db`, and in fact it's not
-recommended to run `migrate-to-note-db` until the code is stable enough that the
-reverse migration won't be necessary.
-
-=== Zero-Downtime Multi-Master Migration
-
-Single-master Gerrit sites can use `migrate-to-note-db` on an offline site to
-rebuild NoteDb, but this doesn't work in a zero-downtime environment like
-googlesource.com.
-
-Here, the migration process looks like:
-
-- Turn on `noteDb.changes.write=true` to start writing to NoteDb.
-- Run a parallel link:https://research.google.com/pubs/pub35650.html[FlumeJava]
-  pipeline to write NoteDb data for all changes, and update all `note_db_state`
-  fields. (Sorry, this implementation is entirely closed-source.)
-- Turn on `noteDb.changes.read=true` to start reading from NoteDb.
-- Turn on `noteDb.changes.primaryStorage=NOTE_DB` to start writing new changes
-  to NoteDb only.
-- Run a Flume to migrate all existing changes to NoteDb primary. (Also
-  closed-source, but basically just a wrapper around `PrimaryStorageMigrator`.)
-- Turn off access to ReviewDb changes tables.
-
-== Configuration
-
-This section describes general configuration for the NoteDb backend that is not
-directly related to the migration process. These options will continue to be
-supported after the migration is complete and the migration-related options are
-obsolete.
-
-[[noteDb.retryMaxWait]]noteDb.retryMaxWait::
-+
-Maximum time to wait between attempts to retry update operations when one
-attempt fails due to contention (aka lock failure) on the underlying ref
-storage. Operations are retried with exponential backoff, plus some random
-jitter, until the interval reaches this limit. After that, retries continue to
-occur after a fixed timeout (plus jitter), up to
-link:#noteDb.retryTimeout[`noteDb.retryTimeout`].
-+
-Only applicable when `noteDb.changes.fuseUpdates=true`.
-+
-Defaults to 5 seconds; unit suffixes are supported, and assumes milliseconds if
-not specified.
-
-[[noteDb.retryTimeout]]noteDb.retryTimeout::
-+
-Total timeout for retrying update operations when one attempt fails due to
-contention (aka lock failure) on the underlying ref storage.
-+
-Only applicable when `noteDb.changes.fuseUpdates=true`.
-+
-Defaults to 20 seconds; unit suffixes are supported, and assumes milliseconds if
-not specified.
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 0f51926..da211b6 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -12,7 +12,7 @@
 == Guides
 . link:intro-user.html[User Guide]
 . link:intro-project-owner.html[Project Owner Guide]
-. link:http://source.android.com/submit-patches/workflow[Default Android Workflow] (external)
+. link:https://source.android.com/source/developing[Default Android Workflow] (external)
 
 == Tutorials
 . Web
@@ -66,6 +66,7 @@
 . link:config-reverseproxy.html[Reverse Proxy]
 . link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
 . link:pgm-index.html[Server Side Administrative Tools]
+. link:note-db.html[NoteDb]
 
 == Developer
 . Getting Started
@@ -82,7 +83,6 @@
 .. link:dev-stars.html[Starring Changes]
 . link:dev-design.html[System Design]
 . link:i18n-readme.html[i18n Support]
-. link:dev-note-db.html[NoteDb]
 
 == Maintainer
 . link:dev-release.html[Making a Gerrit Release]
diff --git a/Documentation/note-db.txt b/Documentation/note-db.txt
new file mode 100644
index 0000000..b926bba
--- /dev/null
+++ b/Documentation/note-db.txt
@@ -0,0 +1,173 @@
+= Gerrit Code Review - NoteDb Backend
+
+NoteDb is the next generation of Gerrit storage backend, which replaces the
+traditional SQL backend for change and account metadata with storing data in the
+same repository as code changes.
+
+.Advantages
+- *Simplicity*: All data is stored in one location in the site directory, rather
+  than being split between the site directory and a possibly external database
+  server.
+- *Consistency*: Replication and backups can use a snapshot of the Git
+  repository refs, which will include both the branch and patch set refs, and
+  the change metadata that points to them.
+- *Auditability*: Rather than storing mutable rows in a database, modifications
+  to changes are stored as a sequence of Git commits, automatically preserving
+  history of the metadata. +
+  There are no strict guarantees, and meta refs may be rewritten, but the
+  default assumption is that all operations are logged.
+- *Extensibility*: Plugin developers can add new fields to metadata without the
+  core database schema having to know about them.
+- *New features*: Enables simple federation between Gerrit servers, as well as
+  offline code review and interoperation with other tools.
+
+== Current Status
+
+- Storing change metadata is fully implemented in the 2.15 release. Admins may
+  use an link:#offline-migration[offline] or link:#online-migration[online] tool
+  to migrate change data from ReviewDb.
+- Storing account data is fully implemented in the 2.15 release. Account data is
+  migrated automatically during the upgrade process by running `gerrit.war
+  init`.
+- Account and change metadata on the servers behind `googlesource.com` is fully
+  migrated to NoteDb. In other words, if you use
+  link:https://gerrit-review.googlesource.com/[gerrit-review], you're already
+  using NoteDb.
+
+For an example NoteDb change, poke around at this one:
+----
+  git fetch https://gerrit.googlesource.com/gerrit refs/changes/70/98070/meta \
+      && git log -p FETCH_HEAD
+----
+
+== Future Work ("Gerrit 3.0")
+
+- Storing group data is a work in progress. Like account data, it will be
+  migrated automatically.
+- NoteDb will be the only database format supported by Gerrit 3.0. The offline
+  change data migration tool will be included in Gerrit 3.0, but online
+  migration will only be available in the 2.x line.
+
+[[migration]]
+== Migration
+
+Migrating change metadata can take a long time for large sites, so
+administrators choose whether to do the migration offline or online, depending
+on their available resources and tolerance for downtime.
+
+Only change metadata requires manual steps to migrate it from ReviewDb; account
+and group data is migrated automatically by `gerrit.war init`.
+
+[[online-migration]]
+=== Online
+
+To start the online migration, set the `noteDb.changes.autoMigrate` option in
+`gerrit.config` and restart Gerrit:
+----
+[noteDb "changes"]
+  autoMigrate = true
+----
+
+Alternatively, pass the `--migrate-to-note-db` flag to
+`gerrit.war daemon`:
+----
+  java -jar gerrit.war daemon -d /path/to/site --migrate-to-note-db
+----
+
+Both ways of starting the online migration are equivalent. Once started, it is
+safe to restart the server at any time; the migration will pick up where it left
+off. Migration progress will be reported to the Gerrit logs.
+
+*Advantages*
+
+* No downtime required.
+
+*Disadvantages*
+
+* Only available in 2.x; will not be available in Gerrit 3.0.
+* Much slower than offline; uses only a single thread, to leave resources
+  available for serving traffic.
+* Performance may be degraded, particularly of updates; data needs to be written
+  to both ReviewDb and NoteDb while the migration is in progress.
+
+[[offline-migration]]
+=== Offline
+
+To run the offline migration, run the `migrate-to-note-db` program:
+----
+  java -jar gerrit.war migrate-to-note-db /path/to/site
+----
+
+Once started, it is safe to cancel and restart the migration process, or to
+switch to the online process.
+
+*Advantages*
+
+* Much faster than online; can use all available CPUs, since no live traffic
+  needs to be served.
+* No degraded performance of live servers due to writing data to 2 locations.
+* Available in both Gerrit 2.x and 3.0.
+
+*Disadvantages*
+
+* May require substantial downtime; takes about twice as long as an
+  link:#pgm-reindex[offline reindex]. (In fact, one of the migration steps is a
+  full reindex, so it can't possibly take less time.)
+
+[[trial-migration]]
+==== Trial mode
+
+The offline migration tool also supports "trial mode", where changes are
+migrated to NoteDb and read from NoteDb at runtime, but their primary storage
+location is still ReviewDb, and data is kept in sync between the two locations.
+
+To run the offline migration in trial mode, add `--trial` to
+`migrate-to-note-db`:
+----
+  java -jar gerrit.war migrate-to-note-db --trial /path/to/site
+----
+
+There are several use cases for trial mode:
+
+* Help test early releases of the migration tool for bugs with lower risk.
+* Try out new NoteDb-only features like
+  link:rest-api-changes.txt#get-hashtags[hashtags] without running the full
+  migration.
+
+To continue with the full migration after running the trial migration, use
+either the online or offline migration steps as normal. To revert to
+ReviewDb-only, remove `noteDb.changes.read` and `noteDb.changes.write` from
+`gerrit.config` and restart Gerrit.
+
+== Configuration
+
+The migration process works by setting a configuration option in `gerrit.config`
+for each step in the process, then performing the corresponding data migration.
+In general, users should not set these options manually; this section serves
+primarily as a reference.
+
+- `noteDb.changes.write=true`: During a ReviewDb write, the state of the change
+  in NoteDb is written to the `note_db_state` field in the `Change` entity.
+  After the ReviewDb write, this state is written into NoteDb, resulting in
+  effectively double the time for write operations. NoteDb write errors are
+  dropped on the floor, and no attempt is made to read from ReviewDb or correct
+  errors (without additional configuration, below).
+- `noteDb.changes.read=true`: Change data is written
+  to and read from NoteDb, but ReviewDb is still the source of truth. During
+  reads, first read the change from ReviewDb, and compare its `note_db_state`
+  with what is in NoteDb. If it doesn't match, immediately "auto-rebuild" the
+  change, copying data from ReviewDb to NoteDb and returning the result.
+- `noteDb.changes.primaryStorage=NOTE_DB`: New changes are written only to
+  NoteDb, but changes whose primary storage is ReviewDb are still supported.
+  Continues to read from ReviewDb first as in the previous stage, but if the
+  change is not in ReviewDb, falls back to reading from NoteDb. +
+  Migration of existing changes is described in the link:#migration[Migration]
+  section above. +
+  Due to an implementation detail, writes to Changes or related tables still
+  result in write calls to the database layer, but they are inside a transaction
+  that is always rolled back.
+- `noteDb.changes.disableReviewDb=true`: All access to Changes or related tables
+  is disabled; reads return no results, and writes are no-ops. Assumes the state
+  of all changes in NoteDb is accurate, and so is only safe once all changes are
+  NoteDb primary. Otherwise, reading changes only from NoteDb might result in
+  inaccurate results, and writing to NoteDb would compound the problem. +
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index 465dcc6..5a7f3b7 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -7,9 +7,11 @@
     "//gerrit-common:server",
     "//gerrit-extension-api:api",
     "//gerrit-httpd:httpd",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-pgm:init",
     "//gerrit-reviewdb:server",
+    "//gerrit-server:metrics",
     "//gerrit-server:receive",
     "//gerrit-server:server",
     "//lib:gson",
@@ -32,8 +34,8 @@
     testonly = 1,
     srcs = SRCS,
     exported_deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-gpg:gpg",
+        "//gerrit-index:query_exception",
         "//gerrit-launcher:launcher",
         "//gerrit-openid:openid",
         "//gerrit-pgm:daemon",
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
index 6b054c8..7809ae0 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.acceptance;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.io.IOException;
 
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index 91b90e3..ebc7c9b 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -19,6 +19,7 @@
         "//gerrit-pgm:pgm",
         "//gerrit-pgm:util",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:prolog-common",
         "//gerrit-server:receive",
         "//gerrit-server:server",
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 6c1fb39..48aa0bb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -152,7 +152,7 @@
 
   @Inject private Sequences seq;
 
-  @Inject private InternalAccountQuery accountQuery;
+  @Inject private Provider<InternalAccountQuery> accountQueryProvider;
 
   @Inject protected Emails emails;
 
@@ -1033,7 +1033,7 @@
     }
 
     assertThat(accountCache.getOrNull(admin.id)).isNull();
-    assertThat(accountQuery.byDefault(admin.id.toString())).isEmpty();
+    assertThat(accountQueryProvider.get().byDefault(admin.id.toString())).isEmpty();
   }
 
   @Test
@@ -1257,7 +1257,7 @@
   @Test
   public void internalQueryFindActiveAndInactiveAccounts() throws Exception {
     String name = name("foo");
-    assertThat(accountQuery.byDefault(name)).isEmpty();
+    assertThat(accountQueryProvider.get().byDefault(name)).isEmpty();
 
     TestAccount foo1 = accountCreator.create(name + "-1");
     assertThat(gApi.accounts().id(foo1.username).getActive()).isTrue();
@@ -1266,7 +1266,7 @@
     gApi.accounts().id(foo2.username).setActive(false);
     assertThat(gApi.accounts().id(foo2.username).getActive()).isFalse();
 
-    assertThat(accountQuery.byDefault(name)).hasSize(2);
+    assertThat(accountQueryProvider.get().byDefault(name)).hasSize(2);
   }
 
   private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
index 281e6cd..0517675 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
@@ -76,7 +76,7 @@
     assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
     setUpOneChange();
 
-    migrate();
+    migrate("--trial");
     assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
 
     try (ServerContext ctx = startServer()) {
@@ -104,7 +104,7 @@
     assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
     setUpOneChange();
 
-    migrate("--trial", "false");
+    migrate();
     assertNotesMigrationState(NotesMigrationState.NOTE_DB);
 
     try (ServerContext ctx = startServer()) {
@@ -142,7 +142,7 @@
     status.save();
     assertServerStartupFails();
 
-    migrate("--trial", "false");
+    migrate();
     assertNotesMigrationState(NotesMigrationState.NOTE_DB);
 
     status = new GerritIndexStatus(sitePaths);
diff --git a/gerrit-antlr/BUILD b/gerrit-antlr/BUILD
deleted file mode 100644
index f4ce4c7..0000000
--- a/gerrit-antlr/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-load("//tools/bzl:genrule2.bzl", "genrule2")
-
-java_library(
-    name = "query_exception",
-    srcs = ["src/main/java/com/google/gerrit/server/query/QueryParseException.java"],
-    visibility = ["//visibility:public"],
-)
-
-genrule2(
-    name = "query_antlr",
-    srcs = ["src/main/antlr3/com/google/gerrit/server/query/Query.g"],
-    outs = ["query_antlr.srcjar"],
-    cmd = " && ".join([
-        "$(location //lib/antlr:antlr-tool) -o $$TMP $<",
-        "cd $$TMP",
-        "$$ROOT/$(location @bazel_tools//tools/zip:zipper) cC $$ROOT/$@ $$(find *)",
-    ]),
-    tools = [
-        "//lib/antlr:antlr-tool",
-        "@bazel_tools//tools/zip:zipper",
-    ],
-)
-
-java_library(
-    name = "query_parser",
-    srcs = [":query_antlr"],
-    visibility = ["//visibility:public"],
-    deps = [
-        ":query_exception",
-        "//lib/antlr:java_runtime",
-    ],
-)
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
index ccaee55..fb86aaf 100644
--- a/gerrit-elasticsearch/BUILD
+++ b/gerrit-elasticsearch/BUILD
@@ -3,8 +3,9 @@
     srcs = glob(["src/main/java/**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:gson",
@@ -35,6 +36,7 @@
     deps = [
         ":elasticsearch",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:gson",
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 3462eeb..8f0ea8f 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -23,12 +23,12 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonArray;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 29d787a..18eb660 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -20,19 +20,19 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 53b16b1..b99f296 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -30,6 +30,10 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Id;
@@ -40,14 +44,10 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-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.ChangeDataSource;
 import com.google.gson.JsonArray;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 51c2a67..38c4e23 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -18,18 +18,18 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.group.GroupField;
 import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index a690136..7868443 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -16,9 +16,9 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.SingleVersionModule;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
index af74daf..9e1c729 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.elasticsearch;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Schema;
 import java.util.Map;
 
 class ElasticMapping {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
index d493497..a470696 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
@@ -14,17 +14,17 @@
 
 package com.google.gerrit.elasticsearch;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.RegexPredicate;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import com.google.gerrit.server.query.change.AfterPredicate;
 import java.time.Instant;
 import org.apache.lucene.search.BooleanQuery;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
index 609c4d9..b2b241f 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
@@ -17,13 +17,13 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.GerritIndexStatus;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index ece9edd..694348f 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -26,10 +26,10 @@
 import com.google.gerrit.elasticsearch.ElasticAccountIndex.AccountMapping;
 import com.google.gerrit.elasticsearch.ElasticChangeIndex.ChangeMapping;
 import com.google.gerrit.elasticsearch.ElasticGroupIndex.GroupMapping;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/BooleanCondition.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/BooleanCondition.java
new file mode 100644
index 0000000..950365a
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/BooleanCondition.java
@@ -0,0 +1,217 @@
+// Copyright (C) 2017 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.extensions.conditions;
+
+import com.google.common.collect.Iterables;
+import java.util.Collections;
+
+/** Delayed evaluation of a boolean condition. */
+public abstract class BooleanCondition {
+  public static final BooleanCondition TRUE = new Value(true);
+  public static final BooleanCondition FALSE = new Value(false);
+
+  public static BooleanCondition valueOf(boolean a) {
+    return a ? TRUE : FALSE;
+  }
+
+  public static BooleanCondition and(BooleanCondition a, BooleanCondition b) {
+    return a == FALSE || b == FALSE ? FALSE : new And(a, b);
+  }
+
+  public static BooleanCondition and(boolean a, BooleanCondition b) {
+    return and(valueOf(a), b);
+  }
+
+  public static BooleanCondition or(BooleanCondition a, BooleanCondition b) {
+    return a == TRUE || b == TRUE ? TRUE : new Or(a, b);
+  }
+
+  public static BooleanCondition or(boolean a, BooleanCondition b) {
+    return or(valueOf(a), b);
+  }
+
+  public static BooleanCondition not(BooleanCondition bc) {
+    return bc == TRUE ? FALSE : bc == FALSE ? TRUE : new Not(bc);
+  }
+
+  BooleanCondition() {}
+
+  /** @return evaluate the condition and return its value. */
+  public abstract boolean value();
+
+  /**
+   * Recursively collect all children of type {@code type}.
+   *
+   * @param type implementation type of the conditions to collect and return.
+   * @return non-null, unmodifiable iteration of children of type {@code type}.
+   */
+  public abstract <T> Iterable<T> children(Class<T> type);
+
+  private static final class And extends BooleanCondition {
+    private final BooleanCondition a;
+    private final BooleanCondition b;
+
+    And(BooleanCondition a, BooleanCondition b) {
+      this.a = a;
+      this.b = b;
+    }
+
+    @Override
+    public boolean value() {
+      return a.value() && b.value();
+    }
+
+    @Override
+    public <T> Iterable<T> children(Class<T> type) {
+      return Iterables.concat(a.children(type), b.children(type));
+    }
+
+    @Override
+    public int hashCode() {
+      return a.hashCode() * 31 + b.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other instanceof And) {
+        And o = (And) other;
+        return a.equals(o.a) && b.equals(o.b);
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return "(" + maybeTrim(a, getClass()) + " && " + maybeTrim(a, getClass()) + ")";
+    }
+  }
+
+  private static final class Or extends BooleanCondition {
+    private final BooleanCondition a;
+    private final BooleanCondition b;
+
+    Or(BooleanCondition a, BooleanCondition b) {
+      this.a = a;
+      this.b = b;
+    }
+
+    @Override
+    public boolean value() {
+      return a.value() || b.value();
+    }
+
+    @Override
+    public <T> Iterable<T> children(Class<T> type) {
+      return Iterables.concat(a.children(type), b.children(type));
+    }
+
+    @Override
+    public int hashCode() {
+      return a.hashCode() * 31 + b.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other instanceof Or) {
+        Or o = (Or) other;
+        return a.equals(o.a) && b.equals(o.b);
+      }
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return "(" + maybeTrim(a, getClass()) + " || " + maybeTrim(a, getClass()) + ")";
+    }
+  }
+
+  private static final class Not extends BooleanCondition {
+    private final BooleanCondition cond;
+
+    Not(BooleanCondition bc) {
+      cond = bc;
+    }
+
+    @Override
+    public boolean value() {
+      return !cond.value();
+    }
+
+    @Override
+    public <T> Iterable<T> children(Class<T> type) {
+      return cond.children(type);
+    }
+
+    @Override
+    public int hashCode() {
+      return cond.hashCode() * 31;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      return other instanceof Not ? cond.equals(((Not) other).cond) : false;
+    }
+
+    @Override
+    public String toString() {
+      return "!" + cond;
+    }
+  }
+
+  private static final class Value extends BooleanCondition {
+    private final boolean value;
+
+    Value(boolean v) {
+      value = v;
+    }
+
+    @Override
+    public boolean value() {
+      return value;
+    }
+
+    @Override
+    public <T> Iterable<T> children(Class<T> type) {
+      return Collections.emptyList();
+    }
+
+    @Override
+    public int hashCode() {
+      return value ? 1 : 0;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      return other instanceof Value ? value == ((Value) other).value : false;
+    }
+
+    @Override
+    public String toString() {
+      return Boolean.toString(value);
+    }
+  }
+
+  /** Remove leading '(' and trailing ')' if the type is the same as the parent. */
+  static String maybeTrim(BooleanCondition cond, Class<? extends BooleanCondition> type) {
+    String s = cond.toString();
+    if (cond.getClass() == type
+        && s.length() > 2
+        && s.charAt(0) == '('
+        && s.charAt(s.length() - 1) == ')') {
+      s = s.substring(1, s.length() - 1);
+    }
+    return s;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/PrivateInternals_BooleanCondition.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/PrivateInternals_BooleanCondition.java
new file mode 100644
index 0000000..4fa932a
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/conditions/PrivateInternals_BooleanCondition.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 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.extensions.conditions;
+
+import java.util.Collections;
+
+/** <b>DO NOT USE</b> */
+public final class PrivateInternals_BooleanCondition {
+  private PrivateInternals_BooleanCondition() {}
+
+  public abstract static class SubclassOnlyInCoreServer extends BooleanCondition {
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> Iterable<T> children(Class<T> type) {
+      if (type.isAssignableFrom(getClass())) {
+        return Collections.singleton((T) this);
+      }
+      return Collections.emptyList();
+    }
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
index 62c074e..5f6dec3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/UiAction.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.webui;
 
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
 
@@ -35,8 +36,8 @@
     private String id;
     private String label;
     private String title;
-    private boolean visible = true;
-    private boolean enabled = true;
+    private BooleanCondition visible = BooleanCondition.TRUE;
+    private BooleanCondition enabled = BooleanCondition.TRUE;
 
     public String getMethod() {
       return method;
@@ -77,6 +78,10 @@
     }
 
     public boolean isVisible() {
+      return getVisibleCondition().value();
+    }
+
+    public BooleanCondition getVisibleCondition() {
       return visible;
     }
 
@@ -85,16 +90,33 @@
      * action description may not be sent to the client.
      */
     public Description setVisible(boolean visible) {
+      return setVisible(BooleanCondition.valueOf(visible));
+    }
+
+    /**
+     * Set if the action's button is visible on screen for the current client. If not visible the
+     * action description may not be sent to the client.
+     */
+    public Description setVisible(BooleanCondition visible) {
       this.visible = visible;
       return this;
     }
 
     public boolean isEnabled() {
-      return enabled && isVisible();
+      return getEnabledCondition().value();
+    }
+
+    public BooleanCondition getEnabledCondition() {
+      return BooleanCondition.and(enabled, visible);
     }
 
     /** Set if the button should be invokable (true), or greyed out (false). */
     public Description setEnabled(boolean enabled) {
+      return setEnabled(BooleanCondition.valueOf(enabled));
+    }
+
+    /** Set if the button should be invokable (true), or greyed out (false). */
+    public Description setEnabled(BooleanCondition enabled) {
       this.enabled = enabled;
       return this;
     }
diff --git a/gerrit-httpd/BUILD b/gerrit-httpd/BUILD
index 57ecd26..dbca10c 100644
--- a/gerrit-httpd/BUILD
+++ b/gerrit-httpd/BUILD
@@ -16,16 +16,17 @@
     resources = RESOURCES,
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
         "//gerrit-gwtexpui:linker_server",
         "//gerrit-gwtexpui:server",
+        "//gerrit-index:query_exception",
         "//gerrit-launcher:launcher",
         "//gerrit-patch-jgit:server",
         "//gerrit-prettify:server",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:receive",
         "//gerrit-server:server",
         "//gerrit-util-cli:cli",
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 365789c..4f7a5ba 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -38,6 +38,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -63,7 +64,7 @@
   private final AccountCache accountCache;
   private final AccountManager accountManager;
   private final SiteHeaderFooter headers;
-  private final InternalAccountQuery accountQuery;
+  private final Provider<InternalAccountQuery> queryProvider;
 
   @Inject
   BecomeAnyAccountLoginServlet(
@@ -73,14 +74,14 @@
       AccountCache ac,
       AccountManager am,
       SiteHeaderFooter shf,
-      InternalAccountQuery aq) {
+      Provider<InternalAccountQuery> qp) {
     webSession = ws;
     schema = sf;
     accounts = a;
     accountCache = ac;
     accountManager = am;
     headers = shf;
-    accountQuery = aq;
+    queryProvider = qp;
   }
 
   @Override
@@ -198,7 +199,8 @@
 
   private AuthResult byUserName(String userName) {
     try {
-      List<AccountState> accountStates = accountQuery.byExternalId(SCHEME_USERNAME, userName);
+      List<AccountState> accountStates =
+          queryProvider.get().byExternalId(SCHEME_USERNAME, userName);
       if (accountStates.isEmpty()) {
         getServletContext().log("No accounts with username " + userName + " found");
         return null;
@@ -217,7 +219,8 @@
   private AuthResult byPreferredEmail(String email) {
     try (ReviewDb db = schema.open()) {
       Optional<Account> match =
-          accountQuery
+          queryProvider
+              .get()
               .byPreferredEmail(email)
               .stream()
               // the index query also matches prefixes, filter those out
diff --git a/gerrit-index/BUILD b/gerrit-index/BUILD
new file mode 100644
index 0000000..41eed60
--- /dev/null
+++ b/gerrit-index/BUILD
@@ -0,0 +1,74 @@
+load("//tools/bzl:genrule2.bzl", "genrule2")
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+QUERY_PARSE_EXCEPTION_SRCS = ["src/main/java/com/google/gerrit/index/query/QueryParseException.java"]
+
+java_library(
+    name = "query_exception",
+    srcs = QUERY_PARSE_EXCEPTION_SRCS,
+    visibility = ["//visibility:public"],
+)
+
+genrule2(
+    name = "query_antlr",
+    srcs = ["src/main/antlr3/com/google/gerrit/index/query/Query.g"],
+    outs = ["query_antlr.srcjar"],
+    cmd = " && ".join([
+        "$(location //lib/antlr:antlr-tool) -o $$TMP $<",
+        "cd $$TMP",
+        "$$ROOT/$(location @bazel_tools//tools/zip:zipper) cC $$ROOT/$@ $$(find *)",
+    ]),
+    tools = [
+        "//lib/antlr:antlr-tool",
+        "@bazel_tools//tools/zip:zipper",
+    ],
+)
+
+java_library(
+    name = "query_parser",
+    srcs = [":query_antlr"],
+    visibility = ["//gerrit-plugin-api:__pkg__"],
+    deps = [
+        ":query_exception",
+        "//lib/antlr:java_runtime",
+    ],
+)
+
+java_library(
+    name = "index",
+    srcs = glob(
+        ["src/main/java/**/*.java"],
+        exclude = QUERY_PARSE_EXCEPTION_SRCS,
+    ),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":query_exception",
+        ":query_parser",
+        "//gerrit-common:annotations",
+        "//gerrit-extension-api:api",
+        "//gerrit-server:metrics",
+        "//lib:guava",
+        "//lib:gwtjsonrpc",
+        "//lib:gwtorm",
+        "//lib/antlr:java_runtime",
+        "//lib/auto:auto-value",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/log:api",
+    ],
+)
+
+junit_tests(
+    name = "index_tests",
+    size = "small",
+    srcs = glob(["src/test/java/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":index",
+        ":query_exception",
+        ":query_parser",
+        "//lib:junit",
+        "//lib:truth",
+        "//lib/antlr:java_runtime",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g b/gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
similarity index 96%
rename from gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
rename to gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
index d0b5875..953a473 100644
--- a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
+++ b/gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
@@ -26,7 +26,7 @@
 }
 
 @header {
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 }
 @members {
   static class QueryParseInternalException extends RuntimeException {
@@ -55,7 +55,7 @@
     }
   }
 
-  static boolean isSingleWord(final String value) {
+  public static boolean isSingleWord(final String value) {
     try {
       final QueryLexer lexer = new QueryLexer(new ANTLRStringStream(value));
       lexer.mSINGLE_WORD();
@@ -77,7 +77,7 @@
 }
 
 @lexer::header {
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 }
 @lexer::members {
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.java
index 5e226e2..b1ffac1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-index/src/main/java/com/google/gerrit/index/FieldType.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/FieldType.java
index 820b62a..0db0284 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/FieldType.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import java.sql.Timestamp;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java b/gerrit-index/src/main/java/com/google/gerrit/index/Index.java
similarity index 91%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/Index.java
index 3be201a..739c358 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/Index.java
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.List;
@@ -26,8 +27,8 @@
  * Secondary index implementation for arbitrary documents.
  *
  * <p>Documents are inserted into the index and are queried by converting special {@link
- * com.google.gerrit.server.query.Predicate} instances into index-aware predicates that use the
- * index search results as a source.
+ * com.google.gerrit.index.query.Predicate} instances into index-aware predicates that use the index
+ * search results as a source.
  *
  * <p>Implementations must be thread-safe and should batch inserts/updates where appropriate.
  */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.java
index a887852..77cabd1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.events.LifecycleListener;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
index 5c3cdf2..b53b59b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.java
index 0d42ee5..f283bf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.gerrit.common.Nullable;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
similarity index 82%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
index 5c1d838..4d6a35b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 
 public interface IndexRewriter<T> {
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
index b8f21f5..050b4a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Paginated;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Paginated;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java b/gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.java
index a26b0ac..b57fb5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-index/src/main/java/com/google/gerrit/index/Schema.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/Schema.java
index d14a37f..3070951 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/Schema.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.java
index 261734d..f9c690c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.java
index ea33190..c59f251 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
index 0d84be7..4ad0827 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
index 7d99052..7fba05f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
index dcd8a66..16620b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
index 8a1718d..77dcca2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
similarity index 90%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
index ff9ff03..7811a32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
 
 /** Index-aware predicate that includes a field type annotation. */
 public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
index 42dcff8..16e59e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 /** Predicate to filter a field by matching integer value. */
 public abstract class IntPredicate<T> extends OperatorPredicate<T> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
similarity index 88%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
index 5832694..66351a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.util.RangeUtil;
-import com.google.gerrit.server.util.RangeUtil.Range;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.RangeUtil.Range;
 import com.google.gwtorm.server.OrmException;
 
 public abstract class IntegerRangePredicate<T> extends IndexPredicate<T> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
similarity index 88%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
index 87772d2..0f8948b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
 import com.google.gwtorm.server.OrmException;
 import java.util.List;
 import java.util.Set;
@@ -30,6 +30,9 @@
  * <p>By default, visibility of returned entities is not enforced (unlike in {@link
  * QueryProcessor}). The methods in this class are not typically used by user-facing paths, but
  * rather by internal callers that need to process all matching results.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
  */
 public class InternalQuery<T> {
   private final QueryProcessor<T> queryProcessor;
@@ -47,7 +50,7 @@
   }
 
   public InternalQuery<T> setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
     return this;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
similarity index 68%
copy from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
copy to gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
index 5c1d838..9cc6f50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
@@ -12,12 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-
-public interface IndexRewriter<T> {
-
-  Predicate<T> rewrite(Predicate<T> in, QueryOptions opts) throws QueryParseException;
+public abstract class IsVisibleToPredicate<T> extends OperatorPredicate<T> implements Matchable<T> {
+  public IsVisibleToPredicate(String name, String value) {
+    super(name, value);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
index 7c38e5a8..23e0f6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 public class LimitPredicate<T> extends IntPredicate<T> implements Matchable<T> {
   @SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
index b37e112..3d07943 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
index 306b4cb..750759d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
index 254aa99..368ee24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import java.util.Collection;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
index f357344..8c3ed1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
similarity index 89%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
index a51555e..20f65dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.index.QueryOptions;
+import com.google.gerrit.index.QueryOptions;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
index c5b2b96..ca74a52 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
index c1fecba..c6c39c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
-import static com.google.gerrit.server.query.Predicate.or;
-import static com.google.gerrit.server.query.QueryParser.AND;
-import static com.google.gerrit.server.query.QueryParser.DEFAULT_FIELD;
-import static com.google.gerrit.server.query.QueryParser.EXACT_PHRASE;
-import static com.google.gerrit.server.query.QueryParser.FIELD_NAME;
-import static com.google.gerrit.server.query.QueryParser.NOT;
-import static com.google.gerrit.server.query.QueryParser.OR;
-import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.or;
+import static com.google.gerrit.index.query.QueryParser.AND;
+import static com.google.gerrit.index.query.QueryParser.DEFAULT_FIELD;
+import static com.google.gerrit.index.query.QueryParser.EXACT_PHRASE;
+import static com.google.gerrit.index.query.QueryParser.FIELD_NAME;
+import static com.google.gerrit.index.query.QueryParser.NOT;
+import static com.google.gerrit.index.query.QueryParser.OR;
+import static com.google.gerrit.index.query.QueryParser.SINGLE_WORD;
 
 import com.google.common.base.Strings;
 import java.lang.annotation.ElementType;
diff --git a/gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
similarity index 95%
rename from gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
index 9495d19..6a62b9e 100644
--- a/gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 /**
  * Exception thrown when a search query is invalid.
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java
new file mode 100644
index 0000000..b318199
--- /dev/null
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -0,0 +1,335 @@
+// Copyright (C) 2016 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.index.query;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.SchemaDefinitions;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer1;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
+import com.google.gwtorm.server.ResultSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.IntSupplier;
+import java.util.stream.IntStream;
+
+/**
+ * Lower-level implementation for executing a single query over a secondary index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
+public abstract class QueryProcessor<T> {
+  protected static class Metrics {
+    final Timer1<String> executionTime;
+
+    Metrics(MetricMaker metricMaker) {
+      Field<String> index = Field.ofString("index", "index name");
+      executionTime =
+          metricMaker.newTimer(
+              "query/query_latency",
+              new Description("Successful query latency, accumulated over the life of the process")
+                  .setCumulative()
+                  .setUnit(Description.Units.MILLISECONDS),
+              index);
+    }
+  }
+
+  private final Metrics metrics;
+  private final SchemaDefinitions<T> schemaDef;
+  private final IndexConfig indexConfig;
+  private final IndexCollection<?, T, ? extends Index<?, T>> indexes;
+  private final IndexRewriter<T> rewriter;
+  private final String limitField;
+  private final IntSupplier permittedLimit;
+
+  // This class is not generally thread-safe, but programmer error may result in it being shared
+  // across threads. At least ensure the bit for checking if it's been used is threadsafe.
+  private final AtomicBoolean used;
+
+  protected int start;
+
+  private boolean enforceVisibility = true;
+  private int userProvidedLimit;
+  private Set<String> requestedFields;
+
+  protected QueryProcessor(
+      MetricMaker metricMaker,
+      SchemaDefinitions<T> schemaDef,
+      IndexConfig indexConfig,
+      IndexCollection<?, T, ? extends Index<?, T>> indexes,
+      IndexRewriter<T> rewriter,
+      String limitField,
+      IntSupplier permittedLimit) {
+    this.metrics = new Metrics(metricMaker);
+    this.schemaDef = schemaDef;
+    this.indexConfig = indexConfig;
+    this.indexes = indexes;
+    this.rewriter = rewriter;
+    this.limitField = limitField;
+    this.permittedLimit = permittedLimit;
+    this.used = new AtomicBoolean(false);
+  }
+
+  public QueryProcessor<T> setStart(int n) {
+    start = n;
+    return this;
+  }
+
+  /**
+   * Specify whether to enforce visibility by filtering out results that are not visible to the
+   * user.
+   *
+   * <p>Enforcing visibility may have performance consequences, as the index system may need to
+   * post-filter a large number of results to fill even a modest limit.
+   *
+   * <p>If visibility is enforced, the user's {@code queryLimit} global capability is also used to
+   * bound the total number of results. If this capability is non-positive, this results in the
+   * entire query processor being {@link #isDisabled() disabled}.
+   *
+   * @param enforce whether to enforce visibility.
+   * @return this.
+   */
+  public QueryProcessor<T> enforceVisibility(boolean enforce) {
+    enforceVisibility = enforce;
+    return this;
+  }
+
+  /**
+   * Set an end-user-provided limit on the number of results returned.
+   *
+   * <p>Since this limit is provided by an end user, it may exceed the limit that they are
+   * authorized to use. This is allowed; the processor will take multiple possible limits into
+   * account and choose the one that makes the most sense.
+   *
+   * @param n limit; zero or negative means no limit.
+   * @return this.
+   */
+  public QueryProcessor<T> setUserProvidedLimit(int n) {
+    userProvidedLimit = n;
+    return this;
+  }
+
+  public QueryProcessor<T> setRequestedFields(Set<String> fields) {
+    requestedFields = fields;
+    return this;
+  }
+
+  /**
+   * Query for entities that match a structured query.
+   *
+   * @see #query(List)
+   * @param query the query.
+   * @return results of the query.
+   */
+  public QueryResult<T> query(Predicate<T> query) throws OrmException, QueryParseException {
+    return query(ImmutableList.of(query)).get(0);
+  }
+
+  /**
+   * Perform multiple queries in parallel.
+   *
+   * <p>If querying is disabled, short-circuits the index and returns empty results. Callers that
+   * wish to distinguish this case from a query returning no results from the index may call {@link
+   * #isDisabled()} themselves.
+   *
+   * @param queries list of queries.
+   * @return results of the queries, one QueryResult per input query, in the same order as the
+   *     input.
+   */
+  public List<QueryResult<T>> query(List<Predicate<T>> queries)
+      throws OrmException, QueryParseException {
+    try {
+      return query(null, queries);
+    } catch (OrmRuntimeException e) {
+      throw new OrmException(e.getMessage(), e);
+    } catch (OrmException e) {
+      if (e.getCause() != null) {
+        Throwables.throwIfInstanceOf(e.getCause(), QueryParseException.class);
+      }
+      throw e;
+    }
+  }
+
+  private List<QueryResult<T>> query(
+      @Nullable List<String> queryStrings, List<Predicate<T>> queries)
+      throws OrmException, QueryParseException {
+    long startNanos = System.nanoTime();
+    checkState(!used.getAndSet(true), "%s has already been used", getClass().getSimpleName());
+    int cnt = queries.size();
+    if (queryStrings != null) {
+      int qs = queryStrings.size();
+      checkArgument(qs == cnt, "got %s query strings but %s predicates", qs, cnt);
+    }
+    if (cnt == 0) {
+      return ImmutableList.of();
+    }
+    if (isDisabled()) {
+      return disabledResults(queryStrings, queries);
+    }
+
+    // Parse and rewrite all queries.
+    List<Integer> limits = new ArrayList<>(cnt);
+    List<Predicate<T>> predicates = new ArrayList<>(cnt);
+    List<DataSource<T>> sources = new ArrayList<>(cnt);
+    for (Predicate<T> q : queries) {
+      int limit = getEffectiveLimit(q);
+      limits.add(limit);
+
+      if (limit == getBackendSupportedLimit()) {
+        limit--;
+      }
+
+      int page = (start / limit) + 1;
+      if (page > indexConfig.maxPages()) {
+        throw new QueryParseException(
+            "Cannot go beyond page " + indexConfig.maxPages() + " of results");
+      }
+
+      // 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 entities is to
+      // ask for one more result from the query.
+      QueryOptions opts = createOptions(indexConfig, start, limit + 1, getRequestedFields());
+      Predicate<T> pred = rewriter.rewrite(q, opts);
+      if (enforceVisibility) {
+        pred = enforceVisibility(pred);
+      }
+      predicates.add(pred);
+
+      @SuppressWarnings("unchecked")
+      DataSource<T> s = (DataSource<T>) pred;
+      sources.add(s);
+    }
+
+    // Run each query asynchronously, if supported.
+    List<ResultSet<T>> matches = new ArrayList<>(cnt);
+    for (DataSource<T> s : sources) {
+      matches.add(s.read());
+    }
+
+    List<QueryResult<T>> out = new ArrayList<>(cnt);
+    for (int i = 0; i < cnt; i++) {
+      out.add(
+          QueryResult.create(
+              queryStrings != null ? queryStrings.get(i) : null,
+              predicates.get(i),
+              limits.get(i),
+              matches.get(i).toList()));
+    }
+
+    // Only measure successful queries that actually touched the index.
+    metrics.executionTime.record(
+        schemaDef.getName(), System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
+    return out;
+  }
+
+  private static <T> ImmutableList<QueryResult<T>> disabledResults(
+      List<String> queryStrings, List<Predicate<T>> queries) {
+    return IntStream.range(0, queries.size())
+        .mapToObj(
+            i ->
+                QueryResult.create(
+                    queryStrings != null ? queryStrings.get(i) : null,
+                    queries.get(i),
+                    0,
+                    ImmutableList.of()))
+        .collect(toImmutableList());
+  }
+
+  protected QueryOptions createOptions(
+      IndexConfig indexConfig, int start, int limit, Set<String> requestedFields) {
+    return QueryOptions.create(indexConfig, start, limit, requestedFields);
+  }
+
+  /**
+   * Invoked after the query was rewritten. Subclasses must overwrite this method to filter out
+   * results that are not visible to the calling user.
+   *
+   * @param pred the query
+   * @return the modified query
+   */
+  protected abstract Predicate<T> enforceVisibility(Predicate<T> pred);
+
+  private Set<String> getRequestedFields() {
+    if (requestedFields != null) {
+      return requestedFields;
+    }
+    Index<?, T> index = indexes.getSearchIndex();
+    return index != null ? index.getSchema().getStoredFields().keySet() : ImmutableSet.<String>of();
+  }
+
+  /**
+   * Check whether querying should be disabled.
+   *
+   * <p>Currently, the only condition that can disable the whole query processor is if both {@link
+   * #enforceVisibility(boolean) visibility is enforced} and the user has a non-positive maximum
+   * value for the {@code queryLimit} capability.
+   *
+   * <p>If querying is disabled, all calls to {@link #query(Predicate)} and {@link #query(List)}
+   * will return empty results. This method can be used if callers wish to distinguish this case
+   * from a query returning no results from the index.
+   *
+   * @return true if querying should be disabled.
+   */
+  public boolean isDisabled() {
+    return enforceVisibility && getPermittedLimit() <= 0;
+  }
+
+  private int getPermittedLimit() {
+    return enforceVisibility ? permittedLimit.getAsInt() : Integer.MAX_VALUE;
+  }
+
+  private int getBackendSupportedLimit() {
+    return indexConfig.maxLimit();
+  }
+
+  private int getEffectiveLimit(Predicate<T> p) {
+    List<Integer> possibleLimits = new ArrayList<>(4);
+    possibleLimits.add(getBackendSupportedLimit());
+    possibleLimits.add(getPermittedLimit());
+    if (userProvidedLimit > 0) {
+      possibleLimits.add(userProvidedLimit);
+    }
+    if (limitField != null) {
+      Integer limitFromPredicate = LimitPredicate.getLimit(limitField, p);
+      if (limitFromPredicate != null) {
+        possibleLimits.add(limitFromPredicate);
+      }
+    }
+    int result = Ordering.natural().min(possibleLimits);
+    // Should have short-circuited from #query or thrown some other exception before getting here.
+    checkState(result > 0, "effective limit should be positive");
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
similarity index 85%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
index f86eb707..341e2b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.auto.value.AutoValue;
 import com.google.gerrit.common.Nullable;
@@ -21,7 +21,7 @@
 /** Results of a query over entities. */
 @AutoValue
 public abstract class QueryResult<T> {
-  static <T> QueryResult<T> create(
+  public static <T> QueryResult<T> create(
       @Nullable String query, Predicate<T> predicate, int limit, List<T> entites) {
     boolean more;
     if (entites.size() > limit) {
@@ -44,8 +44,8 @@
   public abstract List<T> entities();
 
   /**
-   * @return whether the query could be retried with {@link QueryProcessor#setStart(int)} to produce
-   *     more results. Never true if {@link #entities()} is empty.
+   * @return whether the query could be retried with a higher start/limit to produce more results.
+   *     Never true if {@link #entities()} is empty.
    */
   public abstract boolean more();
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
index f7f2cff..1f22f36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.util;
+package com.google.gerrit.index.query;
 
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.Nullable;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
similarity index 91%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
index b73674d..60a2a9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index.query;
+
+import com.google.gerrit.index.FieldDef;
 
 public abstract class RegexPredicate<I> extends IndexPredicate<I> {
   protected RegexPredicate(FieldDef<I, ?> def, String value) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
index 7e194b7..edc2120 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
 import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
 import java.sql.Timestamp;
 import java.util.Date;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
similarity index 87%
rename from gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
index 1dc6a469..3c0bbe0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
@@ -12,19 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.index.SchemaUtil.getNameParts;
-import static com.google.gerrit.server.index.SchemaUtil.getPersonParts;
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.getNameParts;
+import static com.google.gerrit.index.SchemaUtil.getPersonParts;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
-import com.google.gerrit.testutil.GerritBaseTests;
 import java.util.Map;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
-public class SchemaUtilTest extends GerritBaseTests {
+public class SchemaUtilTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
   static class TestSchemas {
     static final Schema<String> V1 = schema();
     static final Schema<String> V2 = schema();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
index cc59081..21098b3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.collect.ImmutableList.of;
-import static com.google.gerrit.server.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.and;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
index 6a72fce..8fe90fc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
similarity index 95%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
index f70b8fc..88d8349 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
index 7d97a0d..255a3f8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.collect.ImmutableList.of;
-import static com.google.gerrit.server.query.Predicate.or;
+import static com.google.gerrit.index.query.Predicate.or;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
similarity index 81%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
index 2d13876..6979d82 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
@@ -12,13 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.testutil.GerritBaseTests;
 import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
 
 @Ignore
-public abstract class PredicateTest extends GerritBaseTests {
+public abstract class PredicateTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
   protected static final class TestPredicate extends OperatorPredicate<String> {
     protected TestPredicate(String name, String value) {
       super(name, value);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
similarity index 96%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
index 3a38a50..448f292 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/gerrit-lucene/BUILD b/gerrit-lucene/BUILD
index 5f87b4e..aae5000 100644
--- a/gerrit-lucene/BUILD
+++ b/gerrit-lucene/BUILD
@@ -7,7 +7,8 @@
     srcs = QUERY_BUILDER,
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:guava",
@@ -25,10 +26,11 @@
     visibility = ["//visibility:public"],
     deps = [
         ":query_builder",
-        "//gerrit-antlr:query_exception",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:guava",
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index ad72f70..9d474dd 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -25,13 +25,13 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.Set;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
index 58117b8..126c79f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
@@ -19,17 +19,17 @@
 import static com.google.gerrit.lucene.LuceneChangeIndex.UPDATED_SORT_FIELD;
 import static com.google.gerrit.server.index.change.ChangeSchemaDefinitions.NAME;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.io.IOException;
 import java.nio.file.Path;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index b08301f..7a4cd40 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -16,18 +16,18 @@
 
 import static com.google.gerrit.server.index.account.AccountField.ID;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 5bb811e..80078dc 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -36,6 +36,10 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -46,14 +50,10 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-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.ChangeDataSource;
 import com.google.gwtorm.protobuf.ProtobufCodec;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index 21371e2..f08b3df 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -16,17 +16,17 @@
 
 import static com.google.gerrit.server.index.group.GroupField.UUID;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index b5531d5..d738540 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -17,9 +17,9 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.SingleVersionModule;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index 9e8007c..c7c802f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -16,13 +16,13 @@
 
 import com.google.common.primitives.Ints;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.GerritIndexStatus;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index 35033be..2f2a1cd 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -21,17 +21,17 @@
 import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
 
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.RegexPredicate;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import java.util.Date;
 import java.util.List;
 import org.apache.lucene.analysis.Analyzer;
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index 24a19d4..1fd3165 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -29,6 +29,7 @@
 
 DEPS = BASE_JETTY_DEPS + [
     "//gerrit-reviewdb:server",
+    "//gerrit-server:metrics",
     "//gerrit-server:module",
     "//gerrit-server:receive",
     "//lib:gwtorm",
@@ -50,6 +51,7 @@
     deps = DEPS + [
         ":init-api",
         ":util",
+        "//gerrit-index:index",
         "//gerrit-elasticsearch:elasticsearch",
         "//gerrit-launcher:launcher",  # We want this dep to be provided_deps
         "//gerrit-lucene:lucene",
@@ -116,6 +118,7 @@
     "//gerrit-cache-h2:cache-h2",
     "//gerrit-elasticsearch:elasticsearch",
     "//gerrit-gpg:gpg",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-oauth:oauth",
     "//gerrit-openid:openid",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 72920d0..19475ec 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -170,7 +170,7 @@
 
   @Option(
     name = "--migrate-to-note-db",
-    usage = "(EXPERIMENTAL) Automatically migrate changes to NoteDb",
+    usage = "Automatically migrate changes to NoteDb",
     handler = ExplicitBooleanOptionHandler.class
   )
   private boolean migrateToNoteDb;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index eeebddd..02e779b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -74,10 +74,9 @@
     name = "--trial",
     usage =
         "trial mode: migrate changes and turn on reading from NoteDb, but leave ReviewDb as"
-            + " the source of truth",
-    handler = ExplicitBooleanOptionHandler.class
+            + " the source of truth"
   )
-  private boolean trial = true; // TODO(dborowitz): Default to false in 3.0.
+  private boolean trial;
 
   @Option(
     name = "--sequence-gap",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 232d71b..bee9928 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -22,6 +22,9 @@
 import com.google.gerrit.common.Die;
 import com.google.gerrit.elasticsearch.ElasticIndexModule;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.util.BatchProgramModule;
@@ -29,11 +32,8 @@
 import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
index c83f4e4..825cfc0 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
@@ -58,10 +58,9 @@
 
   private void initNoteDb() {
     ui.message(
-        "Use experimental NoteDb for change metadata?\n"
-            + "  NoteDb is not recommended for production servers."
-            + "  Please familiarize yourself with the documentation:\n"
-            + "  https://gerrit-review.googlesource.com/Documentation/dev-note-db.html\n");
+        "Use NoteDb for change metadata?\n"
+            + "  See documentation:\n"
+            + "  https://gerrit-review.googlesource.com/Documentation/note-db.html\n");
     if (!ui.yesno(false, "Enable")) {
       return;
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index 0d9a822..6ad0a6b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.InitStep;
@@ -24,7 +25,6 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
index 5273dfb..6b1ee26 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.pgm.init.index;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.index.IndexDefinition;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 import java.util.Collection;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
index 0358f13..b417d05 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
@@ -19,10 +19,10 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.gerrit.server.index.SingleVersionModule;
 import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index d8d6838..51b1486 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -12,12 +12,14 @@
 ]
 
 EXPORTS = [
-    "//gerrit-antlr:query_exception",
-    "//gerrit-antlr:query_parser",
+    "//gerrit-index:index",
+    "//gerrit-index:query_exception",
+    "//gerrit-index:query_parser",
     "//gerrit-common:annotations",
     "//gerrit-common:server",
     "//gerrit-extension-api:api",
     "//gerrit-gwtexpui:server",
+    "//gerrit-server:metrics",
     "//gerrit-reviewdb:server",
     "//gerrit-server:prolog-common",
     "//lib/commons:dbcp",
@@ -81,12 +83,12 @@
     main_class = "Dummy",
     visibility = ["//visibility:public"],
     runtime_deps = [
-        "//gerrit-antlr:libquery_exception-src.jar",
-        "//gerrit-antlr:libquery_parser-src.jar",
         "//gerrit-common:libannotations-src.jar",
         "//gerrit-extension-api:libapi-src.jar",
         "//gerrit-gwtexpui:libserver-src.jar",
         "//gerrit-httpd:libhttpd-src.jar",
+        "//gerrit-index:libquery_exception-src.jar",
+        "//gerrit-index:libquery_parser-src.jar",
         "//gerrit-pgm:libinit-api-src.jar",
         "//gerrit-reviewdb:libserver-src.jar",
         "//gerrit-server:libserver-src.jar",
@@ -99,8 +101,8 @@
 java_doc(
     name = "plugin-api-javadoc",
     libs = PLUGIN_API + [
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
+        "//gerrit-index:query_exception",
+        "//gerrit-index:query_parser",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index 567ec6c..e124e89 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -9,11 +9,14 @@
     "src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java",
 ]
 
+# Non-recursive glob; dropwizard implementation is in a subpackage.
+METRICS_SRCS = glob(["src/main/java/com/google/gerrit/metrics/*.java"])
+
 RECEIVE_SRCS = glob(["src/main/java/com/google/gerrit/server/git/receive/**/*.java"])
 
 SRCS = glob(
     ["src/main/java/**/*.java"],
-    exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + RECEIVE_SRCS,
+    exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + METRICS_SRCS + RECEIVE_SRCS,
 )
 
 RESOURCES = glob(["src/main/resources/**/*"])
@@ -44,11 +47,12 @@
     visibility = ["//visibility:public"],
     deps = [
         ":constants",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
+        ":metrics",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-patch-commonsnet:commons-net",
         "//gerrit-patch-jgit:server",
         "//gerrit-prettify:server",
@@ -73,7 +77,6 @@
         "//lib:soy",
         "//lib:tukaani-xz",
         "//lib:velocity",
-        "//lib/antlr:java_runtime",
         "//lib/auto:auto-value",
         "//lib/bouncycastle:bcpkix-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
@@ -147,7 +150,20 @@
     ],
 )
 
+# TODO(dborowitz): Move to a different top-level directory to avoid inbound
+# dependencies on gerrit-server.
+java_library(
+    name = "metrics",
+    srcs = METRICS_SRCS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//gerrit-extension-api:api",
+        "//lib:guava",
+    ],
+)
+
 TESTUTIL_DEPS = [
+    ":metrics",
     ":module",
     ":server",
     "//gerrit-common:annotations",
@@ -155,6 +171,7 @@
     "//gerrit-cache-h2:cache-h2",
     "//gerrit-extension-api:api",
     "//gerrit-gpg:gpg",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-reviewdb:server",
     "//lib:gwtorm",
@@ -266,9 +283,6 @@
     deps = TESTUTIL_DEPS + [
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
-        "//lib/antlr:java_runtime",
     ],
 )
 
@@ -280,9 +294,6 @@
     deps = TESTUTIL_DEPS + [
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
-        "//lib/antlr:java_runtime",
     ],
 )
 
@@ -299,7 +310,7 @@
         ":custom-truth-subjects",
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
+        "//gerrit-index:query_exception",
         "//gerrit-patch-jgit:server",
         "//gerrit-test-util:test_util",
         "//lib:args4j",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
index 731d156..2e0fe2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
@@ -19,12 +19,12 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.change.ChangeTriplet;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
index 2fad708..7374833 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
@@ -23,6 +23,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -36,8 +38,6 @@
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectControl;
-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.InternalChangeQuery;
@@ -80,7 +80,7 @@
   private final ChangeQueryBuilder changeQueryBuilder;
   private final Config config;
   private final DynamicMap<ReviewerSuggestion> reviewerSuggestionPluginMap;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final WorkQueue workQueue;
   private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
@@ -89,7 +89,7 @@
   ReviewerRecommender(
       ChangeQueryBuilder changeQueryBuilder,
       DynamicMap<ReviewerSuggestion> reviewerSuggestionPluginMap,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       WorkQueue workQueue,
       Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
@@ -98,7 +98,7 @@
     fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     this.changeQueryBuilder = changeQueryBuilder;
     this.config = config;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.reviewerSuggestionPluginMap = reviewerSuggestionPluginMap;
     this.workQueue = workQueue;
     this.dbProvider = dbProvider;
@@ -202,7 +202,8 @@
     // Get the user's last 25 changes, check approvals
     try {
       List<ChangeData> result =
-          internalChangeQuery
+          queryProvider
+              .get()
               .setLimit(25)
               .setRequestedFields(ImmutableSet.of(ChangeField.REVIEWER.getName()))
               .query(changeQueryBuilder.owner("self"));
@@ -268,7 +269,7 @@
     }
 
     List<List<ChangeData>> result =
-        internalChangeQuery.setLimit(25).setRequestedFields(ImmutableSet.of()).query(predicates);
+        queryProvider.get().setLimit(25).setRequestedFields(ImmutableSet.of()).query(predicates);
 
     Iterator<List<ChangeData>> queryResultIterator = result.iterator();
     Iterator<Account.Id> reviewersIterator = reviewers.keySet().iterator();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index 57a81c8..0c1f871 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -25,6 +25,8 @@
 import com.google.gerrit.extensions.common.GroupBaseInfo;
 import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Description.Units;
 import com.google.gerrit.metrics.MetricMaker;
@@ -41,8 +43,6 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.account.AccountPredicates;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gerrit.server.query.account.AccountQueryProcessor;
@@ -109,7 +109,7 @@
 
   private final AccountLoader accountLoader;
   private final AccountQueryBuilder accountQueryBuilder;
-  private final AccountQueryProcessor accountQueryProcessor;
+  private final Provider<AccountQueryProcessor> queryProvider;
   private final GroupBackend groupBackend;
   private final GroupMembers.Factory groupMembersFactory;
   private final Provider<CurrentUser> currentUser;
@@ -120,7 +120,7 @@
   ReviewersUtil(
       AccountLoader.Factory accountLoaderFactory,
       AccountQueryBuilder accountQueryBuilder,
-      AccountQueryProcessor accountQueryProcessor,
+      Provider<AccountQueryProcessor> queryProvider,
       GroupBackend groupBackend,
       GroupMembers.Factory groupMembersFactory,
       Provider<CurrentUser> currentUser,
@@ -130,7 +130,7 @@
     fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     this.accountLoader = accountLoaderFactory.create(fillOptions);
     this.accountQueryBuilder = accountQueryBuilder;
-    this.accountQueryProcessor = accountQueryProcessor;
+    this.queryProvider = queryProvider;
     this.currentUser = currentUser;
     this.groupBackend = groupBackend;
     this.groupMembersFactory = groupMembersFactory;
@@ -199,8 +199,9 @@
     try (Timer0.Context ctx = metrics.queryAccountsLatency.start()) {
       try {
         QueryResult<AccountState> result =
-            accountQueryProcessor
-                .setLimit(suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER)
+            queryProvider
+                .get()
+                .setUserProvidedLimit(suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER)
                 .query(
                     AccountPredicates.andActive(
                         accountQueryBuilder.defaultQuery(suggestReviewers.getQuery())));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
index e8e6d9e..4d1d1b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.index.query.QueryProcessor;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.group.SystemGroupBackend;
@@ -89,6 +90,15 @@
     return QueueProvider.QueueType.INTERACTIVE;
   }
 
+  /**
+   * Get the limit on a {@link QueryProcessor} for a given user.
+   *
+   * @return limit according to {@link GlobalCapability#QUERY_LIMIT}.
+   */
+  public int getQueryLimit() {
+    return getRange(GlobalCapability.QUERY_LIMIT).getMax();
+  }
+
   /** @return true if the user has a permission rule specifying the range. */
   public boolean hasExplicitRange(String permission) {
     return GlobalCapability.hasRange(permission) && !getRules(permission).isEmpty();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
index 15f4509..db707a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.List;
@@ -32,12 +33,12 @@
 @Singleton
 public class Emails {
   private final ExternalIds externalIds;
-  private final InternalAccountQuery accountQuery;
+  private final Provider<InternalAccountQuery> queryProvider;
 
   @Inject
-  public Emails(ExternalIds externalIds, InternalAccountQuery accountQuery) {
+  public Emails(ExternalIds externalIds, Provider<InternalAccountQuery> queryProvider) {
     this.externalIds = externalIds;
-    this.accountQuery = accountQuery;
+    this.queryProvider = queryProvider;
   }
 
   /**
@@ -60,7 +61,7 @@
    * @see #getAccountsFor(String...)
    */
   public ImmutableSet<Account.Id> getAccountFor(String email) throws IOException, OrmException {
-    List<AccountState> byPreferredEmail = accountQuery.byPreferredEmail(email);
+    List<AccountState> byPreferredEmail = queryProvider.get().byPreferredEmail(email);
     return Streams.concat(
             externalIds.byEmail(email).stream().map(e -> e.accountId()),
             byPreferredEmail
@@ -85,7 +86,8 @@
         .entries()
         .stream()
         .forEach(e -> builder.put(e.getKey(), e.getValue().accountId()));
-    accountQuery
+    queryProvider
+        .get()
         .byPreferredEmail(emails)
         .entries()
         .stream()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
index f270b3c..cbfb7f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
@@ -22,13 +22,13 @@
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountDirectory.FillOptions;
 import com.google.gerrit.server.api.accounts.AccountInfoComparator;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.account.AccountPredicates;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gerrit.server.query.account.AccountQueryProcessor;
@@ -71,7 +71,7 @@
     usage = "maximum number of users to return"
   )
   public void setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
 
     if (n < 0) {
       suggestLimit = 10;
@@ -177,7 +177,7 @@
       Predicate<AccountState> queryPred;
       if (suggest) {
         queryPred = queryBuilder.defaultQuery(query);
-        queryProcessor.setLimit(suggestLimit);
+        queryProcessor.setUserProvidedLimit(suggestLimit);
       } else {
         queryPred = queryBuilder.parse(query);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 5073e4a..14aa108 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.TimeUtil;
@@ -191,8 +193,8 @@
         .setLabel("Abandon")
         .setTitle("Abandon the change")
         .setVisible(
-            change.getStatus().isOpen()
-                && change.getStatus() != Change.Status.DRAFT
-                && rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.ABANDON));
+            and(
+                change.getStatus().isOpen() && change.getStatus() != Change.Status.DRAFT,
+                rsrc.permissions().database(dbProvider).testCond(ChangePermission.ABANDON)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
index 02ad66e..58a908d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -16,17 +16,18 @@
 
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.InternalUser;
 import com.google.gerrit.server.config.ChangeCleanupConfig;
 import com.google.gerrit.server.project.ChangeControl;
-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.ChangeQueryProcessor;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,7 +41,7 @@
   private static final Logger log = LoggerFactory.getLogger(AbandonUtil.class);
 
   private final ChangeCleanupConfig cfg;
-  private final ChangeQueryProcessor queryProcessor;
+  private final Provider<ChangeQueryProcessor> queryProvider;
   private final ChangeQueryBuilder queryBuilder;
   private final Abandon abandon;
   private final InternalUser internalUser;
@@ -49,11 +50,11 @@
   AbandonUtil(
       ChangeCleanupConfig cfg,
       InternalUser.Factory internalUserFactory,
-      ChangeQueryProcessor queryProcessor,
+      Provider<ChangeQueryProcessor> queryProvider,
       ChangeQueryBuilder queryBuilder,
       Abandon abandon) {
     this.cfg = cfg;
-    this.queryProcessor = queryProcessor;
+    this.queryProvider = queryProvider;
     this.queryBuilder = queryBuilder;
     this.abandon = abandon;
     internalUser = internalUserFactory.create();
@@ -72,7 +73,7 @@
       }
 
       List<ChangeData> changesToAbandon =
-          queryProcessor.enforceVisibility(false).query(queryBuilder.parse(query)).entities();
+          queryProvider.get().enforceVisibility(false).query(queryBuilder.parse(query)).entities();
       ImmutableListMultimap.Builder<Project.NameKey, ChangeControl> builder =
           ImmutableListMultimap.builder();
       for (ChangeData cd : changesToAbandon) {
@@ -110,7 +111,11 @@
     for (ChangeControl cc : changeControls) {
       String newQuery = query + " change:" + cc.getId();
       List<ChangeData> changesToAbandon =
-          queryProcessor.enforceVisibility(false).query(queryBuilder.parse(newQuery)).entities();
+          queryProvider
+              .get()
+              .enforceVisibility(false)
+              .query(queryBuilder.parse(newQuery))
+              .entities();
       if (!changesToAbandon.isEmpty()) {
         validChanges.add(cc);
       } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index 19fdcfb..a1deb89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -36,6 +35,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -162,7 +162,7 @@
       return out;
     }
 
-    FluentIterable<UiAction.Description> descs =
+    Iterable<UiAction.Description> descs =
         uiActions.from(changeViews, changeResourceFactory.create(ctl));
 
     // The followup action is a client-side only operation that does not
@@ -174,7 +174,7 @@
       PrivateInternals_UiActionDescription.setMethod(descr, "POST");
       descr.setTitle("Create follow-up change");
       descr.setLabel("Follow-Up");
-      descs = descs.append(descr);
+      descs = Iterables.concat(descs, Collections.singleton(descr));
     }
 
     ACTION:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 6c66e50..4e6bb5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -81,6 +81,7 @@
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -117,7 +118,6 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gerrit.server.query.change.PluginDefinedAttributesFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index 993148e..930cb8b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -106,10 +108,11 @@
         .setLabel("Cherry Pick")
         .setTitle("Cherry pick change to a different branch")
         .setVisible(
-            rsrc.isCurrent()
-                && permissionBackend
+            and(
+                rsrc.isCurrent(),
+                permissionBackend
                     .user(user)
                     .project(rsrc.getProject())
-                    .testOrFalse(ProjectPermission.CREATE_CHANGE));
+                    .testCond(ProjectPermission.CREATE_CHANGE)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
index b9b05e8..bc53a7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -100,7 +102,7 @@
     return new UiAction.Description()
         .setLabel("Delete")
         .setTitle("Delete change " + rsrc.getId())
-        .setVisible(couldDeleteWhenIn(status) && perm.testOrFalse(ChangePermission.DELETE));
+        .setVisible(and(couldDeleteWhenIn(status), perm.testCond(ChangePermission.DELETE)));
   }
 
   private boolean couldDeleteWhenIn(Change.Status status) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
index 71c940b..001ef89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
@@ -14,7 +14,10 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
+
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -56,7 +59,7 @@
   protected Response<String> applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
       throws RestApiException, UpdateException {
-    if (!canDeletePrivate(rsrc)) {
+    if (!canDeletePrivate(rsrc).value()) {
       throw new AuthException("not allowed to unmark private");
     }
 
@@ -78,9 +81,10 @@
     return Response.none();
   }
 
-  protected boolean canDeletePrivate(ChangeResource rsrc) {
+  protected BooleanCondition canDeletePrivate(ChangeResource rsrc) {
     PermissionBackend.WithUser user = permissionBackend.user(rsrc.getUser());
-    return user.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
-        || (rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED);
+    return or(
+        rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED,
+        user.testCond(GlobalPermission.ADMINISTRATE_SERVER));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
index 9cf85d1..2de57eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeMessagesUtil;
@@ -39,6 +41,6 @@
     return new UiAction.Description()
         .setLabel("Unmark private")
         .setTitle("Unmark change as private")
-        .setVisible(rsrc.getChange().isPrivate() && canDeletePrivate(rsrc));
+        .setVisible(and(rsrc.getChange().isPrivate(), canDeletePrivate(rsrc)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
index 5f690ba..10c30b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.server.permissions.ChangePermission.ABANDON;
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
@@ -215,11 +216,13 @@
         .setLabel("Move Change")
         .setTitle("Move change to a different branch")
         .setVisible(
-            change.getStatus().isOpen()
-                && permissionBackend
-                    .user(rsrc.getUser())
-                    .ref(change.getDest())
-                    .testOrFalse(CREATE_CHANGE)
-                && rsrc.permissions().database(dbProvider).testOrFalse(ABANDON));
+            and(
+                change.getStatus().isOpen(),
+                and(
+                    permissionBackend
+                        .user(rsrc.getUser())
+                        .ref(change.getDest())
+                        .testCond(CREATE_CHANGE),
+                    rsrc.permissions().database(dbProvider).testCond(ABANDON))));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
index 0c8f010..7ae0abe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
@@ -67,6 +67,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Hashtags")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_HASHTAGS));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_HASHTAGS));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
index 771e669..ad8e72c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostPrivate.java
@@ -14,7 +14,11 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
+
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -57,7 +61,7 @@
   public Response<String> applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
       throws RestApiException, UpdateException {
-    if (!canSetPrivate(rsrc)) {
+    if (!canSetPrivate(rsrc).value()) {
       throw new AuthException("not allowed to mark private");
     }
 
@@ -85,12 +89,13 @@
     return new UiAction.Description()
         .setLabel("Mark private")
         .setTitle("Mark change as private")
-        .setVisible(!change.isPrivate() && canSetPrivate(rsrc));
+        .setVisible(and(!change.isPrivate(), canSetPrivate(rsrc)));
   }
 
-  private boolean canSetPrivate(ChangeResource rsrc) {
+  private BooleanCondition canSetPrivate(ChangeResource rsrc) {
     PermissionBackend.WithUser user = permissionBackend.user(rsrc.getUser());
-    return user.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
-        || (rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED);
+    return or(
+        rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED,
+        user.testCond(GlobalPermission.ADMINISTRATE_SERVER));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
index a0862d6..8b1f4e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
@@ -123,6 +123,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Assignee")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_ASSIGNEE));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_ASSIGNEE));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
index f2614c3..62742ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -131,6 +131,6 @@
   public UiAction.Description getDescription(RevisionResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Description")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_DESCRIPTION));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_DESCRIPTION));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 5d9f7b9..1829199 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -128,6 +128,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Topic")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_TOPIC_NAME));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_TOPIC_NAME));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 6f74ddd..0b8fdfe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.RestoreInput;
@@ -156,7 +158,8 @@
         .setLabel("Restore")
         .setTitle("Restore the change")
         .setVisible(
-            rsrc.getChange().getStatus() == Status.ABANDONED
-                && rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.RESTORE));
+            and(
+                rsrc.getChange().getStatus() == Status.ABANDONED,
+                rsrc.permissions().database(dbProvider).testCond(ChangePermission.RESTORE)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 53e09d4..b126efd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 
 import com.google.common.base.Strings;
@@ -240,8 +241,9 @@
         .setLabel("Revert")
         .setTitle("Revert the change")
         .setVisible(
-            change.getStatus() == Change.Status.MERGED
-                && permissionBackend.user(user).ref(change.getDest()).testOrFalse(CREATE_CHANGE));
+            and(
+                change.getStatus() == Change.Status.MERGED,
+                permissionBackend.user(user).ref(change.getDest()).testCond(CREATE_CHANGE)));
   }
 
   private class NotifyOp implements BatchUpdateOp {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index ae15cfd..79a5d4c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -14,23 +14,32 @@
 
 package com.google.gerrit.server.extensions.webui;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Streams;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
 import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.extensions.webui.UiAction.Description;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendCondition;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import org.slf4j.Logger;
@@ -53,16 +62,35 @@
     this.userProvider = userProvider;
   }
 
-  public <R extends RestResource> FluentIterable<UiAction.Description> from(
+  public <R extends RestResource> Iterable<UiAction.Description> from(
       RestCollection<?, R> collection, R resource) {
     return from(collection.views(), resource);
   }
 
-  public <R extends RestResource> FluentIterable<UiAction.Description> from(
+  public <R extends RestResource> Iterable<UiAction.Description> from(
       DynamicMap<RestView<R>> views, R resource) {
-    return FluentIterable.from(views)
-        .transform((e) -> describe(e, resource))
-        .filter(Objects::nonNull);
+    List<UiAction.Description> descs =
+        Streams.stream(views)
+            .map(e -> describe(e, resource))
+            .filter(Objects::nonNull)
+            .collect(toList());
+
+    List<PermissionBackendCondition> conds =
+        Streams.concat(
+                descs.stream().flatMap(u -> Streams.stream(visibleCondition(u))),
+                descs.stream().flatMap(u -> Streams.stream(enabledCondition(u))))
+            .collect(toList());
+    permissionBackend.bulkEvaluateTest(conds);
+
+    return descs.stream().filter(u -> u.isVisible()).collect(toList());
+  }
+
+  private static Iterable<PermissionBackendCondition> visibleCondition(Description u) {
+    return u.getVisibleCondition().children(PermissionBackendCondition.class);
+  }
+
+  private static Iterable<PermissionBackendCondition> enabledCondition(Description u) {
+    return u.getEnabledCondition().children(PermissionBackendCondition.class);
   }
 
   @Nullable
@@ -86,22 +114,27 @@
       return null;
     }
 
+    UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
+    if (dsc == null) {
+      return null;
+    }
+
+    Set<GlobalOrPluginPermission> globalRequired;
     try {
-      Set<GlobalOrPluginPermission> need =
-          GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
-      if (!need.isEmpty() && permissionBackend.user(userProvider).test(need).isEmpty()) {
-        // A permission is required, but test returned no candidates.
-        return null;
-      }
+      globalRequired = GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
     } catch (PermissionBackendException err) {
       log.error(
           String.format("exception testing view %s.%s", e.getPluginName(), e.getExportName()), err);
       return null;
     }
-
-    UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
-    if (dsc == null || !dsc.isVisible()) {
-      return null;
+    if (!globalRequired.isEmpty()) {
+      PermissionBackend.WithUser withUser = permissionBackend.user(userProvider);
+      Iterator<GlobalOrPluginPermission> i = globalRequired.iterator();
+      BooleanCondition p = withUser.testCond(i.next());
+      while (i.hasNext()) {
+        p = or(p, withUser.testCond(i.next()));
+      }
+      dsc.setVisible(and(p, dsc.getVisibleCondition()));
     }
 
     String name = e.getExportName().substring(d + 1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index d3ddbd9..8823e6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -228,7 +228,7 @@
   private final InternalUser.Factory internalUserFactory;
   private final MergeSuperSet mergeSuperSet;
   private final MergeValidators.Factory mergeValidatorsFactory;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final SubmoduleOp.Factory subOpFactory;
   private final Provider<MergeOpRepoManager> ormProvider;
@@ -255,7 +255,7 @@
       InternalUser.Factory internalUserFactory,
       MergeSuperSet mergeSuperSet,
       MergeValidators.Factory mergeValidatorsFactory,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       SubmitStrategyFactory submitStrategyFactory,
       SubmoduleOp.Factory subOpFactory,
       Provider<MergeOpRepoManager> ormProvider,
@@ -267,7 +267,7 @@
     this.internalUserFactory = internalUserFactory;
     this.mergeSuperSet = mergeSuperSet;
     this.mergeValidatorsFactory = mergeValidatorsFactory;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.submitStrategyFactory = submitStrategyFactory;
     this.subOpFactory = subOpFactory;
     this.ormProvider = ormProvider;
@@ -860,7 +860,7 @@
 
   private void abandonAllOpenChangeForDeletedProject(Project.NameKey destProject) {
     try {
-      for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
+      for (ChangeData cd : queryProvider.get().byProjectOpen(destProject)) {
         try (BatchUpdate bu =
             batchUpdateFactory.create(db, destProject, internalUserFactory.create(), ts)) {
           bu.setRequestId(submissionId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index d596987..6fc5eaa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -41,7 +42,7 @@
   private final RevFlag canMergeFlag;
   private final RevCommit initialTip;
   private final Set<RevCommit> alreadyAccepted;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final Set<CodeReviewCommit> incoming;
 
   public RebaseSorter(
@@ -49,13 +50,13 @@
       RevCommit initialTip,
       Set<RevCommit> alreadyAccepted,
       RevFlag canMergeFlag,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       Set<CodeReviewCommit> incoming) {
     this.rw = rw;
     this.canMergeFlag = canMergeFlag;
     this.initialTip = initialTip;
     this.alreadyAccepted = alreadyAccepted;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.incoming = incoming;
   }
 
@@ -116,7 +117,7 @@
       }
 
       // check if the commit associated change is merged in the same branch
-      List<ChangeData> changes = internalChangeQuery.byCommit(commit);
+      List<ChangeData> changes = queryProvider.get().byCommit(commit);
       for (ChangeData change : changes) {
         if (change.change().getStatus() == Status.MERGED
             && change.change().getDest().equals(dest)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index 9892b6e..2a22c1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -58,6 +58,7 @@
 import com.google.gerrit.server.util.RequestId;
 import com.google.inject.Inject;
 import com.google.inject.Module;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -119,7 +120,7 @@
     final RebaseChangeOp.Factory rebaseFactory;
     final OnSubmitValidators.Factory onSubmitValidatorsFactory;
     final TagCache tagCache;
-    final InternalChangeQuery internalChangeQuery;
+    final Provider<InternalChangeQuery> queryProvider;
 
     final Branch.NameKey destBranch;
     final CodeReviewRevWalk rw;
@@ -159,7 +160,7 @@
         RebaseChangeOp.Factory rebaseFactory,
         OnSubmitValidators.Factory onSubmitValidatorsFactory,
         TagCache tagCache,
-        InternalChangeQuery internalChangeQuery,
+        Provider<InternalChangeQuery> queryProvider,
         @Assisted Branch.NameKey destBranch,
         @Assisted CommitStatus commitStatus,
         @Assisted CodeReviewRevWalk rw,
@@ -188,7 +189,7 @@
       this.projectCache = projectCache;
       this.rebaseFactory = rebaseFactory;
       this.tagCache = tagCache;
-      this.internalChangeQuery = internalChangeQuery;
+      this.queryProvider = queryProvider;
 
       this.serverIdent = serverIdent;
       this.destBranch = destBranch;
@@ -214,12 +215,7 @@
       this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag, incoming);
       this.rebaseSorter =
           new RebaseSorter(
-              rw,
-              mergeTip.getInitialTip(),
-              alreadyAccepted,
-              canMergeFlag,
-              internalChangeQuery,
-              incoming);
+              rw, mergeTip.getInitialTip(), alreadyAccepted, canMergeFlag, queryProvider, incoming);
       this.mergeUtil = mergeUtilFactory.create(project);
       this.onSubmitValidatorsFactory = onSubmitValidatorsFactory;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
index abf464e..32ea4e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
@@ -23,11 +23,11 @@
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.group.GroupQueryBuilder;
 import com.google.gerrit.server.query.group.GroupQueryProcessor;
 import com.google.gwtorm.server.OrmException;
@@ -119,7 +119,7 @@
     }
 
     if (limit != 0) {
-      queryProcessor.setLimit(limit);
+      queryProcessor.setUserProvidedLimit(limit);
     }
 
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
index 1706761..51ef634 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.index.account.AccountIndex;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 636cce6..6854a87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -23,6 +23,8 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.WorkQueue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
index 7000e04..ea9900b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
@@ -21,9 +21,12 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.group.GroupField;
+import com.google.gerrit.server.query.change.SingleGroupUser;
 import java.io.IOException;
 import java.util.Set;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -81,6 +84,16 @@
         : Sets.union(fs, ImmutableSet.of(GroupField.UUID.getName()));
   }
 
+  public static String describe(CurrentUser user) {
+    if (user.isIdentifiedUser()) {
+      return user.getAccountId().toString();
+    }
+    if (user instanceof SingleGroupUser) {
+      return "group:" + user.getEffectiveGroups().getKnownGroups().iterator().next().toString();
+    }
+    return user.toString();
+  }
+
   private IndexUtils() {
     // hide default constructor
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
index 8d14931..bb6b427 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
@@ -18,6 +18,10 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SiteIndexer;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
index bf28d7d..e3f9d7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
@@ -16,6 +16,9 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
index 697c9c2..5284117 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
@@ -22,8 +22,12 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.IndexDefinition.IndexFactory;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.IndexDefinition.IndexFactory;
 import com.google.inject.ProvisionException;
 import java.io.IOException;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
index 9258913..b7c5e77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
@@ -14,19 +14,19 @@
 
 package com.google.gerrit.server.index.account;
 
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.timestamp;
 
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import java.sql.Timestamp;
 import java.util.Collections;
 import java.util.Locale;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
index ffa94ec..5c1b3dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.account;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.account.AccountPredicates;
 
 public interface AccountIndex extends Index<Account.Id, AccountState> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
index 2eb8235..67b507d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.server.index.account;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
index 25bf541..af23b52 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.server.index.account;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.inject.Inject;
 
 public class AccountIndexDefinition
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
index c6b2b45..bc0970e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
@@ -16,11 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
index 8796360..6ec1260 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
@@ -18,10 +18,10 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Index;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
index b89256d..8f9b443 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.account;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
 
 public class AccountSchemaDefinitions extends SchemaDefinitions<AccountState> {
   @Deprecated
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
index a8cc8aa..b6a95b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
@@ -20,12 +20,12 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
index 8c29266..e8b1861 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
@@ -14,14 +14,14 @@
 
 package com.google.gerrit.server.index.account;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 
 public class IndexedAccountQuery extends IndexedQuery<Account.Id, AccountState>
     implements DataSource<AccountState> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 35953b0..02fd609 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -28,6 +28,7 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -36,7 +37,6 @@
 import com.google.gerrit.server.git.MultiProgressMonitor;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index c780d19..cc8f9be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -16,13 +16,13 @@
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.fullText;
-import static com.google.gerrit.server.index.FieldDef.intRange;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.storedOnly;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.fullText;
+import static com.google.gerrit.index.FieldDef.intRange;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.storedOnly;
+import static com.google.gerrit.index.FieldDef.timestamp;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
@@ -36,6 +36,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Table;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -48,8 +50,6 @@
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import com.google.gerrit.server.index.change.StalenessChecker.RefState;
 import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
 import com.google.gerrit.server.mail.Address;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
index 27b0c26..855bfe9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.LegacyChangeIdPredicate;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
index f8acb74..5ce361f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
index 8b63a1d..7945429 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index 11c9d83..28843c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -18,20 +18,20 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.AndChangeSource;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index e4919c8..f62b662 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -24,12 +24,12 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 2f03779..129c8ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.query.change.ChangeData;
 
 public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
index 6cbc1cb..f6cee6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index f99f3b4..66f8df2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -21,15 +21,15 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Matchable;
-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.ChangeDataSource;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
index 63d5f9a..df92379 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -30,11 +30,11 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
index 0f03435..7c4074a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
@@ -21,12 +21,12 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.group.Groups;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
index 70bdb3f..3d4c92f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
@@ -14,15 +14,15 @@
 
 package com.google.gerrit.server.index.group;
 
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.fullText;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.fullText;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.timestamp;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import java.sql.Timestamp;
 
 /** Secondary index schemas for groups. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
index 48480f8..1e56837 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.group;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.group.GroupPredicates;
 
 public interface GroupIndex extends Index<AccountGroup.UUID, AccountGroup> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
index 2f0d8e0..5c49ee5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.group;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
index 8e15b5e..61c3445 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.group;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.inject.Inject;
 
 public class GroupIndexDefinition
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
index 82f55ed..9ef4ba1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
@@ -16,11 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index b137fb3..8c2eec9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -18,9 +18,9 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.index.Index;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
index cebde7e..ecd4168 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.group;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
 
 public class GroupSchemaDefinitions extends SchemaDefinitions<AccountGroup> {
   @Deprecated
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
index 1ea4478..5f31dd7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.server.index.group;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 
 public class IndexedGroupQuery extends IndexedQuery<AccountGroup.UUID, AccountGroup>
     implements DataSource<AccountGroup> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index 6363afa..278cd86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -19,6 +19,8 @@
 import com.google.gerrit.common.data.GroupDescriptions;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -31,8 +33,6 @@
 import com.google.gerrit.server.git.NotifyConfig;
 import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.project.ProjectState;
-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.SingleGroupUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 522eccb..5c0cf44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
@@ -78,7 +79,7 @@
  *   public UiAction.Description getDescription(ChangeResource rsrc) {
  *     return new UiAction.Description()
  *       .setLabel("Submit")
- *       .setVisible(rsrc.permissions().testOrFalse(ChangePermission.SUBMIT));
+ *       .setVisible(rsrc.permissions().testCond(ChangePermission.SUBMIT));
  * }
  * </pre>
  */
@@ -94,6 +95,24 @@
     return user(checkNotNull(user, "Provider<CurrentUser>").get());
   }
 
+  /**
+   * Bulk evaluate a collection of {@link PermissionBackendCondition} for view handling.
+   *
+   * <p>Overridden implementations should call {@link PermissionBackendCondition#set(boolean)} to
+   * cache the result of {@code testOrFalse} in the condition for later evaluation. Caching the
+   * result will bypass the usual invocation of {@code testOrFalse}.
+   *
+   * <p>{@code conds} may contain duplicate entries (such as same user, resource, permission
+   * triplet). When duplicates exist, implementations should set a result into all instances to
+   * ensure {@code testOrFalse} does not get invoked during evaluation of the containing condition.
+   *
+   * @param conds conditions to consider.
+   */
+  public void bulkEvaluateTest(Collection<PermissionBackendCondition> conds) {
+    // Do nothing by default. The default implementation of PermissionBackendCondition
+    // delegates to the appropriate testOrFalse method in PermissionBackend.
+  }
+
   /** PermissionBackend with an optional per-request ReviewDb handle. */
   public abstract static class AcceptsReviewDb<T> {
     protected Provider<ReviewDb> db;
@@ -198,6 +217,10 @@
       }
     }
 
+    public BooleanCondition testCond(GlobalOrPluginPermission perm) {
+      return new PermissionBackendCondition.WithUser(this, perm);
+    }
+
     /**
      * Filter a set of projects using {@code check(perm)}.
      *
@@ -265,6 +288,10 @@
         return false;
       }
     }
+
+    public BooleanCondition testCond(ProjectPermission perm) {
+      return new PermissionBackendCondition.ForProject(this, perm);
+    }
   }
 
   /** PermissionBackend scoped to a user, project and reference. */
@@ -313,6 +340,10 @@
         return false;
       }
     }
+
+    public BooleanCondition testCond(RefPermission perm) {
+      return new PermissionBackendCondition.ForRef(this, perm);
+    }
   }
 
   /** PermissionBackend scoped to a user, project, reference and change. */
@@ -354,6 +385,10 @@
       }
     }
 
+    public BooleanCondition testCond(ChangePermissionOrLabel perm) {
+      return new PermissionBackendCondition.ForChange(this, perm);
+    }
+
     /**
      * Test which values of a label the user may be able to set.
      *
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java
new file mode 100644
index 0000000..8d66e50
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java
@@ -0,0 +1,152 @@
+// Copyright (C) 2017 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.permissions;
+
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
+import com.google.gerrit.extensions.conditions.PrivateInternals_BooleanCondition;
+
+/** {@link BooleanCondition} to evaluate a permission. */
+public abstract class PermissionBackendCondition
+    extends PrivateInternals_BooleanCondition.SubclassOnlyInCoreServer {
+  Boolean value;
+
+  /**
+   * Assign a specific {@code testOrFalse} result to this condition.
+   *
+   * <p>By setting the condition to a specific value the condition will bypass calling {@link
+   * PermissionBackend} during {@code value()}, and immediately return the set value instead.
+   *
+   * @param val value to return from {@code value()}.
+   */
+  public void set(boolean val) {
+    value = val;
+  }
+
+  @Override
+  public abstract String toString();
+
+  public static class WithUser extends PermissionBackendCondition {
+    private final PermissionBackend.WithUser impl;
+    private final GlobalOrPluginPermission perm;
+
+    WithUser(PermissionBackend.WithUser impl, GlobalOrPluginPermission perm) {
+      this.impl = impl;
+      this.perm = perm;
+    }
+
+    public PermissionBackend.WithUser withUser() {
+      return impl;
+    }
+
+    public GlobalOrPluginPermission permission() {
+      return perm;
+    }
+
+    @Override
+    public boolean value() {
+      return value != null ? value : impl.testOrFalse(perm);
+    }
+
+    @Override
+    public String toString() {
+      return "PermissionBackendCondition.WithUser(" + perm + ")";
+    }
+  }
+
+  public static class ForProject extends PermissionBackendCondition {
+    private final PermissionBackend.ForProject impl;
+    private final ProjectPermission perm;
+
+    ForProject(PermissionBackend.ForProject impl, ProjectPermission perm) {
+      this.impl = impl;
+      this.perm = perm;
+    }
+
+    public PermissionBackend.ForProject project() {
+      return impl;
+    }
+
+    public ProjectPermission permission() {
+      return perm;
+    }
+
+    @Override
+    public boolean value() {
+      return value != null ? value : impl.testOrFalse(perm);
+    }
+
+    @Override
+    public String toString() {
+      return "PermissionBackendCondition.ForProject(" + perm + ")";
+    }
+  }
+
+  public static class ForRef extends PermissionBackendCondition {
+    private final PermissionBackend.ForRef impl;
+    private final RefPermission perm;
+
+    ForRef(PermissionBackend.ForRef impl, RefPermission perm) {
+      this.impl = impl;
+      this.perm = perm;
+    }
+
+    public PermissionBackend.ForRef ref() {
+      return impl;
+    }
+
+    public RefPermission permission() {
+      return perm;
+    }
+
+    @Override
+    public boolean value() {
+      return value != null ? value : impl.testOrFalse(perm);
+    }
+
+    @Override
+    public String toString() {
+      return "PermissionBackendCondition.ForRef(" + perm + ")";
+    }
+  }
+
+  public static class ForChange extends PermissionBackendCondition {
+    private final PermissionBackend.ForChange impl;
+    private final ChangePermissionOrLabel perm;
+
+    ForChange(PermissionBackend.ForChange impl, ChangePermissionOrLabel perm) {
+      this.impl = impl;
+      this.perm = perm;
+    }
+
+    public PermissionBackend.ForChange change() {
+      return impl;
+    }
+
+    public ChangePermissionOrLabel permission() {
+      return perm;
+    }
+
+    @Override
+    public boolean value() {
+      return value != null ? value : impl.testOrFalse(perm);
+    }
+
+    @Override
+    public String toString() {
+      return "PermissionBackendCondition.ForChange(" + perm + ")";
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java
deleted file mode 100644
index 9295eb9..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2016 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;
-
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.query.change.SingleGroupUser;
-
-public abstract class IsVisibleToPredicate<T> extends OperatorPredicate<T> implements Matchable<T> {
-  public IsVisibleToPredicate(String name, String value) {
-    super(name, value);
-  }
-
-  protected static String describe(CurrentUser user) {
-    if (user.isIdentifiedUser()) {
-      return user.getAccountId().toString();
-    }
-    if (user instanceof SingleGroupUser) {
-      return "group:" + user.getEffectiveGroups().getKnownGroups().iterator().next().toString();
-    }
-    return user.toString();
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
deleted file mode 100644
index 7176d80..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright (C) 2016 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;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.metrics.Description;
-import com.google.gerrit.metrics.Field;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountLimits;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.SchemaDefinitions;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-public abstract class QueryProcessor<T> {
-  @Singleton
-  protected static class Metrics {
-    final Timer1<String> executionTime;
-
-    @Inject
-    Metrics(MetricMaker metricMaker) {
-      Field<String> index = Field.ofString("index", "index name");
-      executionTime =
-          metricMaker.newTimer(
-              "query/query_latency",
-              new Description("Successful query latency, accumulated over the life of the process")
-                  .setCumulative()
-                  .setUnit(Description.Units.MILLISECONDS),
-              index);
-    }
-  }
-
-  protected final Provider<CurrentUser> userProvider;
-
-  private final AccountLimits.Factory limitsFactory;
-  private final Metrics metrics;
-  private final SchemaDefinitions<T> schemaDef;
-  private final IndexConfig indexConfig;
-  private final IndexCollection<?, T, ? extends Index<?, T>> indexes;
-  private final IndexRewriter<T> rewriter;
-  private final String limitField;
-
-  protected int start;
-
-  private boolean enforceVisibility = true;
-  private int limitFromCaller;
-  private Set<String> requestedFields;
-
-  protected QueryProcessor(
-      Provider<CurrentUser> userProvider,
-      AccountLimits.Factory limitsFactory,
-      Metrics metrics,
-      SchemaDefinitions<T> schemaDef,
-      IndexConfig indexConfig,
-      IndexCollection<?, T, ? extends Index<?, T>> indexes,
-      IndexRewriter<T> rewriter,
-      String limitField) {
-    this.userProvider = userProvider;
-    this.limitsFactory = limitsFactory;
-    this.metrics = metrics;
-    this.schemaDef = schemaDef;
-    this.indexConfig = indexConfig;
-    this.indexes = indexes;
-    this.rewriter = rewriter;
-    this.limitField = limitField;
-  }
-
-  public QueryProcessor<T> setStart(int n) {
-    start = n;
-    return this;
-  }
-
-  public QueryProcessor<T> enforceVisibility(boolean enforce) {
-    enforceVisibility = enforce;
-    return this;
-  }
-
-  public QueryProcessor<T> setLimit(int n) {
-    limitFromCaller = n;
-    return this;
-  }
-
-  public QueryProcessor<T> setRequestedFields(Set<String> fields) {
-    requestedFields = fields;
-    return this;
-  }
-
-  /**
-   * Query for entities that match a structured query.
-   *
-   * @see #query(List)
-   * @param query the query.
-   * @return results of the query.
-   */
-  public QueryResult<T> query(Predicate<T> query) throws OrmException, QueryParseException {
-    return query(ImmutableList.of(query)).get(0);
-  }
-
-  /**
-   * Perform multiple queries in parallel.
-   *
-   * @param queries list of queries.
-   * @return results of the queries, one QueryResult per input query, in the same order as the
-   *     input.
-   */
-  public List<QueryResult<T>> query(List<Predicate<T>> queries)
-      throws OrmException, QueryParseException {
-    try {
-      return query(null, queries);
-    } catch (OrmRuntimeException e) {
-      throw new OrmException(e.getMessage(), e);
-    } catch (OrmException e) {
-      if (e.getCause() != null) {
-        Throwables.throwIfInstanceOf(e.getCause(), QueryParseException.class);
-      }
-      throw e;
-    }
-  }
-
-  private List<QueryResult<T>> query(List<String> queryStrings, List<Predicate<T>> queries)
-      throws OrmException, QueryParseException {
-    long startNanos = System.nanoTime();
-
-    int cnt = queries.size();
-    // Parse and rewrite all queries.
-    List<Integer> limits = new ArrayList<>(cnt);
-    List<Predicate<T>> predicates = new ArrayList<>(cnt);
-    List<DataSource<T>> sources = new ArrayList<>(cnt);
-    for (Predicate<T> q : queries) {
-      int limit = getEffectiveLimit(q);
-      limits.add(limit);
-
-      if (limit == getBackendSupportedLimit()) {
-        limit--;
-      }
-
-      int page = (start / limit) + 1;
-      if (page > indexConfig.maxPages()) {
-        throw new QueryParseException(
-            "Cannot go beyond page " + indexConfig.maxPages() + " of results");
-      }
-
-      // 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 entities is to
-      // ask for one more result from the query.
-      QueryOptions opts = createOptions(indexConfig, start, limit + 1, getRequestedFields());
-      Predicate<T> pred = rewriter.rewrite(q, opts);
-      if (enforceVisibility) {
-        pred = enforceVisibility(pred);
-      }
-      predicates.add(pred);
-
-      @SuppressWarnings("unchecked")
-      DataSource<T> s = (DataSource<T>) pred;
-      sources.add(s);
-    }
-
-    // Run each query asynchronously, if supported.
-    List<ResultSet<T>> matches = new ArrayList<>(cnt);
-    for (DataSource<T> s : sources) {
-      matches.add(s.read());
-    }
-
-    List<QueryResult<T>> out = new ArrayList<>(cnt);
-    for (int i = 0; i < cnt; i++) {
-      out.add(
-          QueryResult.create(
-              queryStrings != null ? queryStrings.get(i) : null,
-              predicates.get(i),
-              limits.get(i),
-              matches.get(i).toList()));
-    }
-
-    // only measure successful queries
-    metrics.executionTime.record(
-        schemaDef.getName(), System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
-    return out;
-  }
-
-  protected QueryOptions createOptions(
-      IndexConfig indexConfig, int start, int limit, Set<String> requestedFields) {
-    return QueryOptions.create(indexConfig, start, limit, requestedFields);
-  }
-
-  /**
-   * Invoked after the query was rewritten. Subclasses must overwrite this method to filter out
-   * results that are not visible to the calling user.
-   *
-   * @param pred the query
-   * @return the modified query
-   */
-  protected abstract Predicate<T> enforceVisibility(Predicate<T> pred);
-
-  private Set<String> getRequestedFields() {
-    if (requestedFields != null) {
-      return requestedFields;
-    }
-    Index<?, T> index = indexes.getSearchIndex();
-    return index != null ? index.getSchema().getStoredFields().keySet() : ImmutableSet.<String>of();
-  }
-
-  public boolean isDisabled() {
-    return getPermittedLimit() <= 0;
-  }
-
-  private int getPermittedLimit() {
-    if (enforceVisibility) {
-      return limitsFactory
-          .create(userProvider.get())
-          .getRange(GlobalCapability.QUERY_LIMIT)
-          .getMax();
-    }
-    return Integer.MAX_VALUE;
-  }
-
-  private int getBackendSupportedLimit() {
-    return indexConfig.maxLimit();
-  }
-
-  private int getEffectiveLimit(Predicate<T> p) {
-    List<Integer> possibleLimits = new ArrayList<>(4);
-    possibleLimits.add(getBackendSupportedLimit());
-    possibleLimits.add(getPermittedLimit());
-    if (limitFromCaller > 0) {
-      possibleLimits.add(limitFromCaller);
-    }
-    if (limitField != null) {
-      Integer limitFromPredicate = LimitPredicate.getLimit(limitField, p);
-      if (limitFromPredicate != null) {
-        possibleLimits.add(limitFromPredicate);
-      }
-    }
-    return Ordering.natural().min(possibleLimits);
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
index e5ed44d..cc9fc0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
@@ -14,16 +14,17 @@
 
 package com.google.gerrit.server.query.account;
 
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.server.account.AccountControl;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gwtorm.server.OrmException;
 
 public class AccountIsVisibleToPredicate extends IsVisibleToPredicate<AccountState> {
   protected final AccountControl accountControl;
 
   public AccountIsVisibleToPredicate(AccountControl accountControl) {
-    super(AccountQueryBuilder.FIELD_VISIBLETO, describe(accountControl.getUser()));
+    super(AccountQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(accountControl.getUser()));
     this.accountControl = accountControl;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
index e13bd0f..d6552e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -16,14 +16,14 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.account.AccountField;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
 import java.util.List;
 
 public class AccountPredicates {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 6122277..9358a7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -18,14 +18,14 @@
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.errors.NotSignedInException;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index 75213e7..a33118d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -17,21 +17,28 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.query.account.AccountQueryBuilder.FIELD_LIMIT;
 
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountControl;
 import com.google.gerrit.server.account.AccountLimits;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.index.account.AccountIndexRewriter;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+/**
+ * Query processor for the account index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class AccountQueryProcessor extends QueryProcessor<AccountState> {
   private final AccountControl.Factory accountControlFactory;
 
@@ -46,20 +53,19 @@
   protected AccountQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       AccountIndexCollection indexes,
       AccountIndexRewriter rewriter,
       AccountControl.Factory accountControlFactory) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         AccountSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
     this.accountControlFactory = accountControlFactory;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 8fa29753..4821e6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -20,12 +20,12 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.InternalQuery;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
-import com.google.gerrit.server.query.InternalQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.util.Arrays;
@@ -34,6 +34,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Query wrapper for the account index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class InternalAccountQuery extends InternalQuery<AccountState> {
   private static final Logger log = LoggerFactory.getLogger(InternalAccountQuery.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
index 06cccf4..099e841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class AddedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
index b9c4694..de57b3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.Date;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
index b0fcfd1..ff1ab23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
-import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IsVisibleToPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
index bc57f15..4d6ed69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.Date;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
index 50d9c90..5930b74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.index.FieldDef;
 import com.google.gwtorm.server.OrmException;
 
 public class BooleanPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
index c32ff0d..34579a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.DataSource;
+import com.google.gerrit.index.query.DataSource;
 
 public interface ChangeDataSource extends DataSource<ChangeData> {
   /** @return true if all returned ChangeData.hasChange() will be true. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
index 0362c85..db3b94c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 
 public abstract class ChangeIndexPredicate extends IndexPredicate<ChangeData>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index e262881..7bbb27b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -14,16 +14,17 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
@@ -40,7 +41,7 @@
       ChangeControl.GenericFactory changeControlFactory,
       CurrentUser user,
       PermissionBackend permissionBackend) {
-    super(ChangeQueryBuilder.FIELD_VISIBLETO, describe(user));
+    super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
     this.db = db;
     this.notesFactory = notesFactory;
     this.changeControlFactory = changeControlFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
index 242592e..8b08536 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.OperatorPredicate;
 
 public abstract class ChangeOperatorPredicate extends OperatorPredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index e997a82..5cd2a01 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -29,6 +29,13 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.errors.NotSignedInException;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaUtil;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -52,9 +59,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.strategy.SubmitDryRun;
 import com.google.gerrit.server.group.ListMembers;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaUtil;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -68,10 +72,6 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index eeb6d01..eb6cf77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -19,12 +19,15 @@
 
 import com.google.gerrit.extensions.common.PluginDefinedInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountLimits;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.QueryOptions;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
@@ -32,14 +35,18 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Query processor for the change index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
     implements PluginDefinedAttributesFactory {
   /**
@@ -53,6 +60,7 @@
   }
 
   private final Provider<ReviewDb> db;
+  private final Provider<CurrentUser> userProvider;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final ChangeNotes.Factory notesFactory;
   private final DynamicMap<ChangeAttributeFactory> attributeFactories;
@@ -69,7 +77,7 @@
   ChangeQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       ChangeIndexCollection indexes,
       ChangeIndexRewriter rewriter,
@@ -79,15 +87,15 @@
       DynamicMap<ChangeAttributeFactory> attributeFactories,
       PermissionBackend permissionBackend) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         ChangeSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
     this.db = db;
+    this.userProvider = userProvider;
     this.changeControlFactory = changeControlFactory;
     this.notesFactory = notesFactory;
     this.attributeFactories = attributeFactories;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
index f421985..24b8b7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.query.Matchable;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.RegexPredicate;
 
 public abstract class ChangeRegexPredicate extends RegexPredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index e9564bd..d2065cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
index 85efe90..5a6d186 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class CommentPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index d2537ca..d1ae529 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -18,8 +18,8 @@
 import static com.google.gerrit.server.index.change.ChangeField.EXACT_COMMIT;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 
+import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.index.FieldDef;
 import com.google.gwtorm.server.OrmException;
 
 public class CommitPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 8212d64..7890edd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.CodeReviewCommit;
@@ -24,8 +26,6 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
index 24c37e3..6232fc5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class DeletedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
index dac3730..aae0a20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class DeltaPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
index 66958695..b5a2d05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
index c6908dc..545b668 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
@@ -17,11 +17,11 @@
 import static com.google.gerrit.server.index.change.ChangeField.FUZZY_TOPIC;
 
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class FuzzyTopicPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
index d4f5620..312c04e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.QueryParseException;
 
 public abstract class IntegerRangeChangePredicate extends IntegerRangePredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 42e60a4..2316bd0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -15,25 +15,25 @@
 package com.google.gerrit.server.query.change;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
-import static com.google.gerrit.server.query.Predicate.or;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.or;
 import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.InternalQuery;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.query.InternalQuery;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -46,6 +46,12 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 
+/**
+ * Query wrapper for the change index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class InternalChangeQuery extends InternalQuery<ChangeData> {
   private static Predicate<ChangeData> ref(Branch.NameKey branch) {
     return new RefPredicate(branch.get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 8b6c8e6..7ff5a28 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -16,9 +16,9 @@
 
 import static com.google.gerrit.server.index.change.ChangeField.REVIEWEDBY;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
index c319546..225dc454 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class IsUnresolvedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index a1a5070..90eb8e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index bd342d7..c9ddfb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -15,6 +15,10 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.RangeUtil;
+import com.google.gerrit.index.query.RangeUtil.Range;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -22,11 +26,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.util.LabelVote;
-import com.google.gerrit.server.util.RangeUtil;
-import com.google.gerrit.server.util.RangeUtil.Range;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 92d1ed3..0cfcedb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 /** Predicate to match changes that contains specified text in commit messages body. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
index 90c2fb3..a703852 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index a69727d7..879d34b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -19,6 +19,8 @@
 
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -34,8 +36,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -59,7 +59,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Change query implementation that outputs to a stream in the style of an SSH command. */
+/**
+ * Change query implementation that outputs to a stream in the style of an SSH command.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class OutputStreamQuery {
   private static final Logger log = LoggerFactory.getLogger(OutputStreamQuery.class);
 
@@ -120,7 +125,7 @@
   }
 
   void setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
   }
 
   public void setStart(int n) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 3b00c0a..f5e8d69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -22,8 +24,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
index 1fbc1aa..ad7a57d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.QueryParseException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index f0ef40d..c1fc0ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -25,10 +25,10 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.util.ArrayList;
@@ -60,7 +60,7 @@
     usage = "Maximum number of results to return"
   )
   public void setLimit(int limit) {
-    imp.setLimit(limit);
+    imp.setUserProvidedLimit(limit);
   }
 
   @Option(name = "-o", usage = "Output options per change")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
index a040e18..16feed9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
@@ -16,10 +16,10 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index f3a8619..7bea4a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -17,10 +17,10 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.stream.Collectors.toList;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import java.util.stream.Stream;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
index 81d64e0e..17034df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
@@ -17,9 +17,9 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import java.util.Set;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
index f0ac127..abbd0c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.Matchable;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import java.sql.Timestamp;
 
 public abstract class TimestampRangeChangePredicate extends TimestampRangePredicate<ChangeData>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
index 3ac9c39..63138cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
@@ -15,10 +15,11 @@
 package com.google.gerrit.server.query.group;
 
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gwtorm.server.OrmException;
 
@@ -28,7 +29,7 @@
 
   public GroupIsVisibleToPredicate(
       GroupControl.GenericFactory groupControlFactory, CurrentUser user) {
-    super(AccountQueryBuilder.FIELD_VISIBLETO, describe(user));
+    super(AccountQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
     this.groupControlFactory = groupControlFactory;
     this.user = user;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
index 650024c..6d3d9ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.group;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.group.GroupField;
-import com.google.gerrit.server.query.Predicate;
 import java.util.Locale;
 
 public class GroupPredicates {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
index 3197ab7..1cba96c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
@@ -18,14 +18,14 @@
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import java.util.List;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
index e096656..a1f4a31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
@@ -17,22 +17,30 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.query.group.GroupQueryBuilder.FIELD_LIMIT;
 
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountLimits;
 import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
 import com.google.gerrit.server.index.group.GroupIndexRewriter;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+/**
+ * Query processor for the group index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class GroupQueryProcessor extends QueryProcessor<AccountGroup> {
+  private final Provider<CurrentUser> userProvider;
   private final GroupControl.GenericFactory groupControlFactory;
 
   static {
@@ -46,20 +54,20 @@
   protected GroupQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       GroupIndexCollection indexes,
       GroupIndexRewriter rewriter,
       GroupControl.GenericFactory groupControlFactory) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         GroupSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
+    this.userProvider = userProvider;
     this.groupControlFactory = groupControlFactory;
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
index b80e31e..b589289 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
@@ -16,21 +16,21 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.or;
 import static com.google.gerrit.reviewdb.client.Change.Status.ABANDONED;
 import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT;
 import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
 import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
 import static com.google.gerrit.server.index.change.IndexedChangeQuery.convertOptions;
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.or;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.AndChangeSource;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
index 8189c81..74e1c09 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-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.ChangeDataSource;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index edc221c..a194336 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.index.change;
 
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.index.query.OperatorPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import org.junit.Ignore;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 8323051..c5eea0f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -104,7 +104,7 @@
 
   @Inject protected OneOffRequestContext oneOffRequestContext;
 
-  @Inject protected InternalAccountQuery internalAccountQuery;
+  @Inject protected Provider<InternalAccountQuery> queryProvider;
 
   @Inject protected AllProjectsName allProjects;
 
@@ -294,19 +294,19 @@
     AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
     AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
 
-    assertThat(internalAccountQuery.byWatchedProject(p)).isEmpty();
+    assertThat(queryProvider.get().byWatchedProject(p)).isEmpty();
 
     watch(user1, p, null);
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1);
 
     watch(user2, p, "keyword");
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1, user2);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
 
     watch(user3, p2, "keyword");
     watch(user3, allProjects, "keyword");
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1, user2);
-    assertAccounts(internalAccountQuery.byWatchedProject(p2), user3);
-    assertAccounts(internalAccountQuery.byWatchedProject(allProjects), user3);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
+    assertAccounts(queryProvider.get().byWatchedProject(p2), user3);
+    assertAccounts(queryProvider.get().byWatchedProject(allProjects), user3);
   }
 
   @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 044dbbe..9ab4db0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -57,6 +57,10 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -84,10 +88,6 @@
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexer;
@@ -167,13 +167,13 @@
   @Inject protected ChangeIndexer indexer;
   @Inject protected IndexConfig indexConfig;
   @Inject protected InMemoryRepositoryManager repoManager;
-  @Inject protected InternalChangeQuery internalChangeQuery;
+  @Inject protected Provider<InternalChangeQuery> queryProvider;
   @Inject protected ChangeNotes.Factory notesFactory;
   @Inject protected OneOffRequestContext oneOffRequestContext;
   @Inject protected PatchSetInserter.Factory patchSetFactory;
   @Inject protected PatchSetUtil psUtil;
   @Inject protected ChangeControl.GenericFactory changeControlFactory;
-  @Inject protected ChangeQueryProcessor queryProcessor;
+  @Inject protected Provider<ChangeQueryProcessor> queryProcessorProvider;
   @Inject protected SchemaCreator schemaCreator;
   @Inject protected SchemaFactory<ReviewDb> schemaFactory;
   @Inject protected Sequences seq;
@@ -2011,7 +2011,7 @@
 
     for (int i = 1; i <= 11; i++) {
       Iterable<ChangeData> cds =
-          internalChangeQuery.byCommitsOnBranchNotMerged(repo.getRepository(), db, dest, shas, i);
+          queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), db, dest, shas, i);
       Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
       String name = "limit " + i;
       assertThat(ids).named(name).hasSize(n);
@@ -2029,7 +2029,10 @@
     requestContext.setContext(newRequestContext(userId));
     // Use QueryProcessor directly instead of API so we get ChangeDatas back.
     List<ChangeData> cds =
-        queryProcessor.query(queryBuilder.parse(change.getId().toString())).entities();
+        queryProcessorProvider
+            .get()
+            .query(queryBuilder.parse(change.getId().toString()))
+            .entities();
     assertThat(cds).hasSize(1);
 
     ChangeData cd = cds.get(0);
@@ -2059,7 +2062,8 @@
     requestContext.setContext(newRequestContext(userId));
     // Use QueryProcessor directly instead of API so we get ChangeDatas back.
     List<ChangeData> cds =
-        queryProcessor
+        queryProcessorProvider
+            .get()
             .setRequestedFields(
                 ImmutableSet.of(ChangeField.PATCH_SET.getName(), ChangeField.CHANGE.getName()))
             .query(queryBuilder.parse(change.getId().toString()))
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 96f573f..db89eda 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -40,7 +40,6 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.group.GroupsUpdate;
 import com.google.gerrit.server.group.ServerInitiated;
-import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
@@ -94,8 +93,6 @@
 
   @Inject protected OneOffRequestContext oneOffRequestContext;
 
-  @Inject protected InternalAccountQuery internalAccountQuery;
-
   @Inject protected AllProjectsName allProjects;
 
   @Inject protected GroupCache groupCache;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 0db22d3..41647db 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.gpg.GpgModule;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -50,7 +51,6 @@
 import com.google.gerrit.server.git.SearchingChangeCacheImpl;
 import com.google.gerrit.server.git.SendEmailExecutor;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
 import com.google.gerrit.server.index.account.AllAccountsIndexer;
 import com.google.gerrit.server.index.change.AllChangesIndexer;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
index 825cd7b..c2ba740 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
@@ -22,8 +22,8 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
diff --git a/gerrit-sshd/BUILD b/gerrit-sshd/BUILD
index 1ae0376..6dd0d5f 100644
--- a/gerrit-sshd/BUILD
+++ b/gerrit-sshd/BUILD
@@ -14,6 +14,7 @@
         "//gerrit-lucene:lucene",
         "//gerrit-patch-jgit:server",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:receive",
         "//gerrit-server:server",
         "//gerrit-util-cli:cli",
diff --git a/lib/antlr/BUILD b/lib/antlr/BUILD
index 6afe7b8..fff886fe 100644
--- a/lib/antlr/BUILD
+++ b/lib/antlr/BUILD
@@ -1,3 +1,5 @@
+package(default_visibility = ["//gerrit-index:__pkg__"])
+
 [java_library(
     name = n,
     data = ["//lib:LICENSE-antlr"],
@@ -17,7 +19,7 @@
 java_binary(
     name = "antlr-tool",
     main_class = "org.antlr.Tool",
-    visibility = ["//gerrit-antlr:__pkg__"],
+    visibility = ["//gerrit-index:__pkg__"],
     runtime_deps = [":tool"],
 )
 
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 54030a4..50d04dd 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 54030a46ed17e097f09fdc586ebe1859569e1383
+Subproject commit 50d04dd09a747868cd2f2707d1aac3d9b2a2d630
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
index ee910b4..43c893c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
@@ -46,11 +46,11 @@
           </td>
           <td class="type">[[itemType(item.type)]]</td>
           <td class="member">
-            <a href$="[[_computeGroupUrl(item.member._account_id)]]">
-              [[_getName(item.member)]]
+            <a href$="[[_computeGroupUrl(item.member.group_id)]]">
+              [[_getNameForMember(item.member)]]
             </a>
           </td>
-          <td class="by-user">[[_getName(item.user)]]</td>
+          <td class="by-user">[[_getNameForUser(item.user)]]</td>
         </tr>
       </template>
     </table>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
index 7c4d84d..84f73ff 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
@@ -19,7 +19,7 @@
 
     properties: {
       groupId: Object,
-      _auditLog: Array,
+      _auditLog: Object,
       _loading: {
         type: Boolean,
         value: true,
@@ -39,16 +39,15 @@
     },
 
     _getAuditLogs() {
+      if (!this.groupId) {
+        return '';
+      }
       return this.$.restAPI.getGroupAuditLog(this.groupId).then(auditLog => {
         if (!auditLog) {
           this._auditLog = [];
           return;
         }
-        this._auditLog = Object.keys(auditLog).map(key => {
-          const audit = auditLog[key];
-          audit.name = key;
-          return audit;
-        });
+        this._auditLog = auditLog;
         this._loading = false;
       });
     },
@@ -58,6 +57,9 @@
     },
 
     _computeGroupUrl(id) {
+      if (!id) {
+        return '';
+      }
       return this.getBaseUrl() + '/admin/groups/' + id;
     },
 
@@ -78,11 +80,21 @@
       return item;
     },
 
-    _getName(account) {
-      if (account.username) {
-        return account.username + ' (' + account._account_id + ')';
-      } else if (account.name) {
-        return account.name + ' (' + account._account_id + ')';
+    _getNameForUser(account) {
+      const accountId = account._account_id ? ' (' +
+        account._account_id + ')' : '';
+      if (account && account.username) {
+        return account.username + accountId;
+      } else if (account && account.name) {
+        return account.name + accountId;
+      }
+    },
+
+    _getNameForMember(account) {
+      if (account && account.name) {
+        return account.name;
+      } else if (account && account.username) {
+        return account.username;
       }
     },
   });
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index e0cdad2..e437f7d 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -39,20 +39,64 @@
       element = fixture('basic');
     });
 
-    test('test _getName', () => {
-      let account;
-      account = {
-        username: 'test-user',
-        name: 'test-name',
-        _account_id: 12,
-      };
-      assert.deepEqual(element._getName(account), 'test-user (12)');
+    suite('members', () => {
+      test('test getNameForMember', () => {
+        let account = {
+          member: {
+            username: 'test-user',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForMember(account.member, false), 'test-user');
 
-      account = {
-        name: 'test-name',
-        _account_id: 12,
-      };
-      assert.deepEqual(element._getName(account), 'test-name (12)');
+        account = {
+          member: {
+            name: 'test-name',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForMember(account.member), 'test-name');
+      });
+    });
+
+    suite('users', () => {
+      test('test _getName', () => {
+        let account = {
+          user: {
+            username: 'test-user',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForUser(account.user), 'test-user (12)');
+
+        account = {
+          user: {
+            name: 'test-name',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForUser(account.user), 'test-name (12)');
+      });
+
+      test('test _account_id not present', () => {
+        let account = {
+          user: {
+            username: 'test-user',
+          },
+        };
+        assert.deepEqual(element._getNameForUser(account.user), 'test-user');
+
+        account = {
+          user: {
+            name: 'test-name',
+          },
+        };
+        assert.deepEqual(element._getNameForUser(account.user), 'test-name');
+      });
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 9181f5d..70275b3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -19,6 +19,7 @@
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-change-list-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
 <link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
@@ -72,7 +73,8 @@
       }
       a {
         color: var(--default-text-color);
-        display: block;
+        cursor: pointer;
+        display: inline-block;
         text-decoration: none;
       }
       a:hover {
@@ -141,8 +143,11 @@
     <td class="cell branch"
         hidden$="[[isColumnHidden('Branch', visibleChangeTableColumns)]]">
       <a href$="[[_computeProjectBranchURL(change)]]">
-        [[_computeBranchText(change)]]
+        [[change.branch]]
       </a>
+      <template is="dom-if" if="[[change.topic]]">
+        (<a href$="[[_computeTopicURL(change)]]">[[change.topic]]</a>)
+      </template>
     </td>
     <td class="cell updated"
         hidden$="[[isColumnHidden('Updated', visibleChangeTableColumns)]]">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index e9a0419..79f06fe 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -27,7 +27,7 @@
       change: Object,
       changeURL: {
         type: String,
-        computed: '_computeChangeURL(change._number)',
+        computed: '_computeChangeURL(change)',
       },
       showStar: {
         type: Boolean,
@@ -43,9 +43,8 @@
       Gerrit.URLEncodingBehavior,
     ],
 
-    _computeChangeURL(changeNum) {
-      if (!changeNum) { return ''; }
-      return this.getBaseUrl() + '/c/' + changeNum + '/';
+    _computeChangeURL(change) {
+      return Gerrit.Nav.getUrlForChange(change);
     },
 
     _computeLabelTitle(change, labelName) {
@@ -105,24 +104,16 @@
     },
 
     _computeProjectURL(project) {
-      return this.getBaseUrl() + '/q/status:open+project:' +
-          this.encodeURL(project, false);
+      return Gerrit.Nav.getUrlForProject(project, true);
     },
 
     _computeProjectBranchURL(change) {
-      // @see Issue 4255, Issue 6195.
-      let output = this._computeProjectURL(change.project);
-      output += '+branch:' + this.encodeURL(change.branch, false);
-      if (change.topic) {
-        output += '+topic:' + this.encodeURL(change.topic, false);
-      }
-      return output;
+      return Gerrit.Nav.getUrlForBranch(change.branch, change.project);
     },
 
-    _computeBranchText(change) {
-      let output = change.branch;
-      if (change.topic) { output += ` (${change.topic})`; }
-      return output;
+    _computeTopicURL(change) {
+      if (!change.topic) { return ''; }
+      return Gerrit.Nav.getUrlForTopic(change.topic);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 502a720..8b13aa7 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -112,23 +112,6 @@
           {labels: {Verified: {approved: true}}}, 'Verified'), '✓');
       assert.equal(element._computeLabelValue(
           {labels: {Verified: {rejected: true}}}, 'Verified'), '✕');
-
-      assert.equal(element._computeProjectURL('combustible/stuff'),
-          '/q/status:open+project:combustible%252Fstuff');
-
-      const change = {project: 'combustible-stuff', branch: 'le/mons'};
-      assert.equal(element._computeProjectBranchURL(change),
-          '/q/status:open+project:combustible-stuff+branch:le%252Fmons');
-
-      change.topic = 'test/test';
-      assert.equal(element._computeProjectBranchURL(change),
-          '/q/status:open+project:combustible-stuff+branch:le%252Fmons' +
-              '+topic:test%252Ftest');
-
-      element.change = {_number: 42};
-      assert.equal(element.changeURL, '/c/42/');
-      element.change = {_number: 43};
-      assert.equal(element.changeURL, '/c/43/');
     });
 
     test('no hidden columns', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
index ce97f47..d841fbc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
@@ -21,6 +21,7 @@
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-change-list-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-change-list-item/gr-change-list-item.html">
 <link rel="import" href="../../../styles/shared-styles.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index 02ff1ae..d302120 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -238,7 +238,7 @@
           this.modifierPressed(e)) { return; }
 
       e.preventDefault();
-      page.show(this._changeURLForIndex(this.selectedIndex));
+      Gerrit.Nav.navigateToChange(this._changeForIndex(this.selectedIndex));
     },
 
     _handleNKey(e) {
@@ -285,12 +285,12 @@
       this.$.restAPI.saveChangeStarred(change._number, newVal);
     },
 
-    _changeURLForIndex(index) {
+    _changeForIndex(index) {
       const changeEls = this._getListItems();
       if (index < changeEls.length && changeEls[index]) {
-        return changeEls[index].changeURL;
+        return changeEls[index].change;
       }
-      return '';
+      return null;
     },
 
     _getListItems() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index a570bd5..d758475 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -42,11 +42,15 @@
 <script>
   suite('gr-change-list basic tests', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       element = fixture('basic');
     });
 
+    teardown(() => { sandbox.restore(); });
+
     function stubRestAPI(preferences) {
       const loggedInPromise = Promise.resolve(preferences !== null);
       const preferencesPromise = Promise.resolve(preferences);
@@ -149,16 +153,16 @@
         assert.equal(element.selectedIndex, 1);
         MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
 
-        const showStub = sinon.stub(page, 'show');
+        const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
         assert.equal(element.selectedIndex, 2);
         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
-        assert(showStub.lastCall.calledWithExactly('/c/2/'),
+        assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
             'Should navigate to /c/2/');
 
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
         assert.equal(element.selectedIndex, 1);
         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
-        assert(showStub.lastCall.calledWithExactly('/c/1/'),
+        assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
             'Should navigate to /c/1/');
 
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
@@ -166,7 +170,6 @@
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
         assert.equal(element.selectedIndex, 0);
 
-        showStub.restore();
         done();
       });
     });
@@ -367,11 +370,15 @@
 
   suite('gr-change-list sections', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       element = fixture('basic');
     });
 
+    teardown(() => { sandbox.restore(); });
+
     test('keyboard shortcuts', () => {
       element.selectedIndex = 0;
       element.sections = [
@@ -411,16 +418,17 @@
       assert.equal(element.selectedIndex, 1);
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 
-      const showStub = sinon.stub(page, 'show');
+      const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
       assert.equal(element.selectedIndex, 2);
+
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/2/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
           'Should navigate to /c/2/');
 
       MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
       assert.equal(element.selectedIndex, 1);
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/1/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
           'Should navigate to /c/1/');
 
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
@@ -428,9 +436,8 @@
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
       assert.equal(element.selectedIndex, 4);
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/4/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
           'Should navigate to /c/4/');
-      showStub.restore();
     });
 
     test('assigned attribute set in each item', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 7e305bb..1658da8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -228,7 +228,7 @@
     });
 
     test('submit change', done => {
-      sandbox.stub(element.$.restAPI, '_getFromProjectLookup')
+      sandbox.stub(element.$.restAPI, 'getFromProjectLookup')
           .returns(Promise.resolve('test'));
       sandbox.stub(element, 'fetchIsLatestKnown',
           () => { return Promise.resolve(true); });
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index f80a1f2..bd0ef38 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -924,6 +924,30 @@
       return msg.replace(REVIEWERS_REGEX, '$1=\u200B');
     },
 
+    /**
+     * Utility function to make the necessary modifications to a change in the
+     * case an edit exists.
+     *
+     * @param {!Object} change
+     * @param {?Object} edit
+     */
+    _processEdit(change, edit) {
+      if (!edit) { return; }
+      change.revisions[edit.commit.commit] = {
+        _number: this.EDIT_NAME,
+        basePatchNum: edit.base_patch_set_number,
+        commit: edit.commit,
+        fetch: edit.fetch,
+      };
+      // If the edit is based on the most recent patchset, load it by
+      // default, unless another patch set to load was specified in the URL.
+      if (!this._patchRange.patchNum &&
+          change.current_revision === edit.base_revision) {
+        change.current_revision = edit.commit.commit;
+        this._patchRange.patchNum = this.EDIT_NAME;
+      }
+    },
+
     _getChangeDetail() {
       const detailCompletes = this.$.restAPI.getChangeDetail(
           this._changeNum, this._handleGetChangeDetailError.bind(this));
@@ -934,15 +958,7 @@
             if (!change) {
               return '';
             }
-            this._upgradeUrl(change, this.params);
-            if (edit) {
-              change.revisions[edit.commit.commit] = {
-                _number: this.EDIT_NAME,
-                basePatchNum: edit.base_patch_set_number,
-                commit: edit.commit,
-                fetch: edit.fetch,
-              };
-            }
+            this._processEdit(change, edit);
             // Issue 4190: Coalesce missing topics to null.
             if (!change.topic) { change.topic = null; }
             if (!change.reviewer_updates) {
@@ -978,13 +994,6 @@
           });
     },
 
-    _upgradeUrl(change, params) {
-      const project = change.project;
-      if (!params.project || project !== params.project) {
-        Gerrit.Nav.upgradeUrl(Object.assign({}, params, {project}));
-      }
-    },
-
     _getComments() {
       return this.$.restAPI.getDiffComments(this._changeNum).then(comments => {
         this._comments = comments;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index ddbbd5f..2a8cf63 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -835,7 +835,6 @@
 
     test('topic is coalesced to null', done => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -853,7 +852,6 @@
 
     test('commit sha is populated from getChangeDetail', done => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -871,7 +869,6 @@
 
     test('edit is added to change', () => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -886,6 +883,7 @@
           commit: {commit: 'bar'},
         });
       });
+      element._patchRange = {};
 
       return element._getChangeDetail().then(() => {
         const revs = element._change.revisions;
@@ -1296,30 +1294,41 @@
       assert.isTrue(callCompute({basePatchNum: 1, patchNum: 'edit'}));
     });
 
-    suite('_upgradeUrl calls', () => {
-      let upgradeStub;
-      const mockChange = {project: 'test'};
+    test('_processEdit', () => {
+      element._patchRange = {};
+      const change = {
+        current_revision: 'foo',
+        revisions: {foo: {commit: {}}},
+      };
+      let mockChange;
 
-      setup(() => {
-        upgradeStub = sandbox.stub(window.Gerrit.Nav, 'upgradeUrl');
-      });
+      // With no edit, mockChange should be unmodified.
+      element._processEdit(mockChange = _.cloneDeep(change), null);
+      assert.deepEqual(mockChange, change);
 
-      test('app.params.project undefined', () => {
-        element._upgradeUrl(mockChange, {});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
+      // When edit is not based on the latest PS, current_revision should be
+      // unmodified.
+      const edit = {
+        base_patch_set_number: 1,
+        commit: {commit: 'bar'},
+        fetch: true,
+      };
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.notDeepEqual(mockChange, change);
+      assert.equal(mockChange.revisions.bar._number, element.EDIT_NAME);
+      assert.equal(mockChange.current_revision, change.current_revision);
+      assert.deepEqual(mockChange.revisions.bar.commit, {commit: 'bar'});
 
-      test('app.params.project differs from change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'demo'});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
+      edit.base_revision = 'foo';
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.notDeepEqual(mockChange, change);
+      assert.equal(mockChange.current_revision, 'bar');
 
-      test('app.params.project === change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'test'});
-        assert.isFalse(upgradeStub.called);
-      });
+      // If _patchRange.patchNum is defined, do not load edit.
+      element._patchRange.patchNum = 'baz';
+      change.current_revision = 'baz';
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.equal(element._patchRange.patchNum, 'baz');
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index c61514c..3d57469 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -123,28 +123,31 @@
       },
 
       /**
-       * @param {string} project The name of the project.
+       * @param {!string} project The name of the project.
+       * @param {boolean=} opt_openOnly When true, only search open changes in
+       *     the project.
        * @return {string}
        */
-      getUrlForProject(project) {
+      getUrlForProject(project, opt_openOnly) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           project,
+          statuses: opt_openOnly ? ['open'] : [],
         });
       },
 
       /**
        * @param {string} branch The name of the branch.
        * @param {string} project The name of the project.
-       * @param {string} status The status to search.
+       * @param {string=} opt_status The status to search.
        * @return {string}
        */
-      getUrlForBranch(branch, project, status) {
+      getUrlForBranch(branch, project, opt_status) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           branch,
           project,
-          statuses: [status],
+          statuses: opt_status ? [opt_status] : undefined,
         });
       },
 
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index ede5b7a..6a0cf8d 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -67,6 +67,21 @@
 
     Gerrit.Nav.setup(url => { page.show(url); }, generateUrl, upgradeUrl);
 
+    /**
+     * Given a set of params without a project, gets the project from the rest
+     * API project lookup and then sets the app params.
+     *
+     * @param {?Object} params
+     */
+    const normalizeLegacyRouteParams = params => {
+      if (!params.changeNum) { return; }
+
+      restAPI.getFromProjectLookup(params.changeNum).then(project => {
+        params.project = project;
+        normalizePatchRangeParams(params);
+      });
+    };
+
     // Middleware
     page((ctx, next) => {
       document.body.scrollTop = 0;
@@ -224,7 +239,7 @@
     });
 
     // Matches /admin/groups/<group>
-    page(/^\/admin\/groups\/(.+)$/, loadUser, data => {
+    page(/^\/admin\/groups\/([^,]+)$/, loadUser, data => {
       restAPI.getLoggedIn().then(loggedIn => {
         if (loggedIn) {
           app.params = {
@@ -336,7 +351,7 @@
     });
 
     // Matches /admin/projects/<project>
-    page(/^\/admin\/projects\/(.+)$/, loadUser, data => {
+    page(/^\/admin\/projects\/([^,]+)$/, loadUser, data => {
       app.params = {
         view: Gerrit.Nav.View.ADMIN,
         project: data.params[0],
@@ -460,10 +475,10 @@
             patchNum: ctx.params[6],
             path: ctx.params[8],
             view: ctx.params[8] ? Gerrit.Nav.View.DIFF : Gerrit.Nav.View.CHANGE,
+            hash: ctx.hash,
           };
           normalizePatchRangeParams(params);
           app.params = params;
-          upgradeUrl(params);
           restAPI.setInProjectLookup(params.changeNum, params.project);
         });
 
@@ -477,8 +492,7 @@
         view: Gerrit.Nav.View.CHANGE,
       };
 
-      normalizePatchRangeParams(params);
-      app.params = params;
+      normalizeLegacyRouteParams(params);
     });
 
     // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
@@ -501,8 +515,7 @@
         view: Gerrit.Nav.View.DIFF,
       };
 
-      normalizePatchRangeParams(params);
-      app.params = params;
+      normalizeLegacyRouteParams(params);
     });
 
     page(/^\/settings\/(agreements|new-agreement)/, loadUser, data => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 5ec81e5..fbd5b1b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -179,17 +179,9 @@
     _getChangeDetail(changeNum) {
       return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
         this._change = change;
-        this._upgradeUrl(change, this.params);
       });
     },
 
-    _upgradeUrl(change, params) {
-      const project = change.project;
-      if (!params.project || project !== params.project) {
-        Gerrit.Nav.upgradeUrl(Object.assign({}, params, {project}));
-      }
-    },
-
     _getChangeEdit(changeNum) {
       return this.$.restAPI.getChangeEdit(this._changeNum);
     },
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 49e82d4..e9c5de8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -463,7 +463,6 @@
       stub('gr-rest-api-interface', {
         getDiffComments() { return Promise.resolve({}); },
       });
-      sandbox.stub(element, '_upgradeUrl');
       const saveReviewedStub = sandbox.stub(element, '_saveReviewedState',
           () => Promise.resolve());
       sandbox.stub(element.$.diff, 'reload');
@@ -509,7 +508,6 @@
       stub('gr-rest-api-interface', {
         getDiffComments() { return Promise.resolve({}); },
       });
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.diff, 'reload');
       sandbox.stub(element, '_loadHash');
 
@@ -732,32 +730,6 @@
       });
     });
 
-    suite('_upgradeUrl calls', () => {
-      let upgradeStub;
-      const mockChange = {project: 'test'};
-
-      setup(() => {
-        upgradeStub = sandbox.stub(window.Gerrit.Nav, 'upgradeUrl');
-      });
-
-      test('app.params.project undefined', () => {
-        element._upgradeUrl(mockChange, {});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
-
-      test('app.params.project differs from change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'demo'});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
-
-      test('app.params.project === change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'test'});
-        assert.isFalse(upgradeStub.called);
-      });
-    });
-
     test('_computeEditLoaded', () => {
       const callCompute = range => element._computeEditLoaded({base: range});
       assert.isFalse(callCompute({}));
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 956d0b3..fe3646b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1556,7 +1556,7 @@
       // stack every time _changeBaseURL is called without a project.
       const projectPromise = opt_project ?
           Promise.resolve(opt_project) :
-          this._getFromProjectLookup(changeNum);
+          this.getFromProjectLookup(changeNum);
       return projectPromise.then(project => {
         let url = `/changes/${encodeURIComponent(project)}~${changeNum}`;
         if (opt_patchNum) {
@@ -1732,7 +1732,7 @@
      * @param {string|number} changeNum
      * @return {!Promise<string|undefined>}
      */
-    _getFromProjectLookup(changeNum) {
+    getFromProjectLookup(changeNum) {
       const project = this._projectLookup[changeNum];
       if (project) { return Promise.resolve(project); }
       return this.getChange(changeNum).then(change => {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index eac758f..54ec90e 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -269,7 +269,7 @@
     });
 
     test('differing patch diff comments are properly grouped', done => {
-      sandbox.stub(element, '_getFromProjectLookup')
+      sandbox.stub(element, 'getFromProjectLookup')
           .returns(Promise.resolve('test'));
       sandbox.stub(element, 'fetchJSON', url => {
         if (url === '/changes/test~42/revisions/1') {
@@ -780,11 +780,11 @@
       assert.deepEqual(element._projectLookup, {test: 'project'});
     });
 
-    suite('_getFromProjectLookup', () => {
+    suite('getFromProjectLookup', () => {
       test('getChange fails', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve());
-        return element._getFromProjectLookup().then(val => {
+        return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
         });
@@ -793,7 +793,7 @@
       test('getChange succeeds, no project', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve({}));
-        return element._getFromProjectLookup().then(val => {
+        return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
         });
@@ -802,7 +802,7 @@
       test('getChange succeeds with project', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve({project: 'project'}));
-        return element._getFromProjectLookup('test').then(val => {
+        return element.getFromProjectLookup('test').then(val => {
           assert.equal(val, 'project');
           assert.deepEqual(element._projectLookup, {test: 'project'});
         });
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index 7d79bc2..303c6f3 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -11,6 +11,7 @@
     "//gerrit-gpg:gpg_tests",
     "//gerrit-gwtui:ui_tests",
     "//gerrit-httpd:httpd_tests",
+    "//gerrit-index:index_tests",
     "//gerrit-patch-jgit:jgit_patch_tests",
     "//gerrit-reviewdb:client_tests",
     "//gerrit-server:server_tests",