Implement full query operators

All of the change list pages have been rewritten in terms of the
new query operator implementation.  If the query exactly matches
a particular navigation call, we use the same underlying function
to scan the database, which gives us about the same performance.

Rather than showing custom page titles, all of the change list pages
update the search bar and show the underlying query.  This helps
users to learn what search operators Gerrit supports internally.

If the query has additional filter restrictions, we try to pick a
reasonable scan function to read from the database, but then use
filters inside of the JVM to whack out rows that do not match.
The scan function selection is based on a hand-coded cost based
query rewriter, which is really more of a rule based optimizer than
it is a cost based optimizer.  Its rather horrible at picking a good
"query plan", but its good enough given the limitations of the gwtorm
access API that we can still do something reasonable for the user.

Because the query graph is stored in memory as a proper graph,
we may be able to later perform some optimization work where we
transform the graph into a proper SQL SELECT statement and hand
it off to the database server for execution.  For most SQL systems
this would outperform what we do now.

Because the query rewriter edits the query, it may produce an invalid
result sometimes.  Any old hand-coded search is correct, because the
rewriter just transforms to the same underlying function that we used
to have.  More complex filters may still uncover bugs in the rewiter.

Some queries are simply not executable.  For example, "is:reviewed"
won't execute, because we don't have a way to scan the database
along that dimension.  The access method doesn't exist simply
because the result set would be too large to reasonably process.

The set of operators is still not complete.  We should support
things like 'age:3d' to mean changes that were last modified more
than 3 days ago, but we don't yet.  Doing so efficiently probably
requires converting the age specification into a sortKey and using
an existing scan operation along that column.

Queries once parsed into a predicate tree can be tested against a
single ChangeData object, to consider just that one change.  This
will later be useful for more powerful project watch specifications,
or access control rules.

Bug: issue 259
Bug: issue 287
Bug: issue 239
Bug: issue 504
Change-Id: Ifdf7306bb362484c58df5785054fd9e43363aaa9
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2010-07-16 18:34:31 -07:00
parent d4c502ffc6
commit 51d008d693
74 changed files with 4775 additions and 1274 deletions

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.server.query;
import com.google.gwtorm.client.OrmException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -21,44 +23,60 @@ import java.util.List;
/**
* An abstract predicate tree for any form of query.
* <p>
* Implementations should be immutable, and therefore also be thread-safe. They
* also should ensure their immutable promise by defensively copying any
* structures which might be modified externally, but were passed into the
* object's constructor.
* Implementations should be immutable, such that the meaning of a predicate
* never changes once constructed. They should ensure their immutable promise by
* defensively copying any structures which might be modified externally, but
* was passed into the object's constructor.
* <p>
* However, implementations <i>may</i> retain non-thread-safe caches internally,
* to speed up evaluation operations within the context of one thread's
* evaluation of the predicate. As a result, callers should assume predicates
* are not thread-safe, but that two predicate graphs produce the same results
* given the same inputs if they are {@link #equals(Object)}.
* <p>
* Predicates should support deep inspection whenever possible, so that generic
* algorithms can be written to operate against them. Predicates which contain
* other predicates should override {@link #getChildren()} to return the list of
* children nested within the predicate.
*
* @type <T> type of object the predicate can evaluate in memory.
*/
public abstract class Predicate {
public abstract class Predicate<T> {
/** Combine the passed predicates into a single AND node. */
public static Predicate and(final Predicate... that) {
return new AndPredicate(that);
public static <T> Predicate<T> and(final Predicate<T>... that) {
return new AndPredicate<T>(that);
}
/** Combine the passed predicates into a single AND node. */
public static Predicate and(final Collection<Predicate> that) {
return new AndPredicate(that);
public static <T> Predicate<T> and(
final Collection<? extends Predicate<T>> that) {
return new AndPredicate<T>(that);
}
/** Combine the passed predicates into a single OR node. */
public static Predicate or(final Predicate... that) {
return new OrPredicate(that);
public static <T> Predicate<T> or(final Predicate<T>... that) {
return new OrPredicate<T>(that);
}
/** Combine the passed predicates into a single OR node. */
public static Predicate or(final Collection<Predicate> that) {
return new OrPredicate(that);
public static <T> Predicate<T> or(
final Collection<? extends Predicate<T>> that) {
return new OrPredicate<T>(that);
}
/** Invert the passed node; same as {@code that.not()}. */
public static Predicate not(final Predicate that) {
return that.not();
/** Invert the passed node. */
@SuppressWarnings("unchecked")
public static <T> Predicate<T> not(final Predicate<T> that) {
if (that instanceof NotPredicate) {
// Negate of a negate is the original predicate.
//
return that.getChild(0);
}
return new NotPredicate<T>(that);
}
/** Get the children of this predicate, if any. */
public List<Predicate> getChildren() {
public List<Predicate<T>> getChildren() {
return Collections.emptyList();
}
@@ -68,14 +86,22 @@ public abstract class Predicate {
}
/** Same as {@code getChildren().get(i)} */
public Predicate getChild(final int i) {
public Predicate<T> getChild(final int i) {
return getChildren().get(i);
}
/** Obtain the inverse of this predicate. */
public Predicate not() {
return new NotPredicate(this);
}
/** Create a copy of this predicate, with new children. */
public abstract Predicate<T> copy(Collection<? extends Predicate<T>> children);
/**
* Does this predicate match this object?
*
* @throws OrmException
*/
public abstract boolean match(T object) throws OrmException;
/** @return a cost estimate to run this predicate, higher figures cost more. */
public abstract int getCost();
@Override
public abstract int hashCode();