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:
@@ -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();
|
||||
|
Reference in New Issue
Block a user