Kill @Rewrite support in queries
These had been significantly pared down since almost all queries of consequence are sent as-is to the secondary index subsystem. The remaining queries just existed to simplify some arbitrary subtrees into slightly less complicated subtrees. This required almost a thousand lines of magical supporting code for something with no remaining performance benefit. Let it die a graceful death. Change-Id: I99ee92549b6619e7aa7529875149073f55c85c69
This commit is contained in:
parent
78279ba1c7
commit
e23ea2c7b9
@ -116,7 +116,6 @@ conditionAnd2
|
||||
conditionNot
|
||||
: '-' conditionBase -> ^(NOT conditionBase)
|
||||
| NOT^ conditionBase
|
||||
| VARIABLE_ASSIGN^ conditionOr ')'!
|
||||
| conditionBase
|
||||
;
|
||||
conditionBase
|
||||
@ -143,13 +142,6 @@ FIELD_NAME
|
||||
: ('a'..'z' | '_')+
|
||||
;
|
||||
|
||||
VARIABLE_ASSIGN
|
||||
: ('A'..'Z') ('A'..'Z' | 'a'..'Z')* '=' '(' {
|
||||
String s = $text;
|
||||
setText(s.substring(0, s.length() - 2));
|
||||
}
|
||||
;
|
||||
|
||||
EXACT_PHRASE
|
||||
: '"' ( ~('"') )* '"' {
|
||||
String s = $text;
|
||||
|
@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.gerrit.lifecycle.LifecycleModule;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.git.WorkQueue;
|
||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
@ -69,7 +68,6 @@ public class IndexModule extends LifecycleModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(ChangeQueryRewriter.class).to(IndexRewriteImpl.class);
|
||||
bind(BasicChangeRewrites.class);
|
||||
bind(IndexCollection.class);
|
||||
listener().to(IndexCollection.class);
|
||||
factory(ChangeIndexer.Factory.class);
|
||||
|
@ -26,7 +26,6 @@ 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.AndSource;
|
||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
|
||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
||||
@ -118,13 +117,10 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
||||
}
|
||||
|
||||
private final IndexCollection indexes;
|
||||
private final BasicChangeRewrites basicRewrites;
|
||||
|
||||
@Inject
|
||||
IndexRewriteImpl(IndexCollection indexes,
|
||||
BasicChangeRewrites basicRewrites) {
|
||||
IndexRewriteImpl(IndexCollection indexes) {
|
||||
this.indexes = indexes;
|
||||
this.basicRewrites = basicRewrites;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -132,7 +128,6 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
||||
int limit) throws QueryParseException {
|
||||
checkArgument(limit > 0, "limit must be positive: %s", limit);
|
||||
ChangeIndex index = indexes.getSearchIndex();
|
||||
in = basicRewrites.rewrite(in);
|
||||
// Increase the limit rather than skipping, since we don't know how many
|
||||
// skipped results would have been filtered out by the enclosing AndSource.
|
||||
limit += start;
|
||||
|
@ -24,7 +24,6 @@ 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.server.query.QueryParser.VARIABLE_ASSIGN;
|
||||
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
|
||||
@ -222,20 +221,6 @@ public abstract class QueryBuilder<T> {
|
||||
case FIELD_NAME:
|
||||
return operator(r.getText(), onlyChildOf(r));
|
||||
|
||||
case VARIABLE_ASSIGN: {
|
||||
final String var = r.getText();
|
||||
final Tree opTree = onlyChildOf(r);
|
||||
if (opTree.getType() == FIELD_NAME) {
|
||||
final Tree val = onlyChildOf(opTree);
|
||||
if (val.getType() == SINGLE_WORD && "*".equals(val.getText())) {
|
||||
final String op = opTree.getText();
|
||||
final WildPatternPredicate<T> pat = new WildPatternPredicate<>(op);
|
||||
return new VariablePredicate<>(var, pat);
|
||||
}
|
||||
}
|
||||
return new VariablePredicate<>(var, toPredicate(opTree));
|
||||
}
|
||||
|
||||
default:
|
||||
throw error("Unsupported operator: " + r);
|
||||
}
|
||||
|
@ -1,505 +0,0 @@
|
||||
// Copyright (C) 2009 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.collect.Lists;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Rewrites a Predicate tree by applying rewrite rules.
|
||||
* <p>
|
||||
* Subclasses may document their rewrite rules by declaring public methods with
|
||||
* {@link Rewrite} annotations, such as:
|
||||
*
|
||||
* <pre>
|
||||
* @Rewrite("A=(owner:*) B=(status:*)")
|
||||
* public Predicate r1_ownerStatus(@Named("A") OperatorPredicate owner,
|
||||
* @Named("B") OperatorPredicate status) {
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Rewrite methods are applied in order by declared name, so naming methods with
|
||||
* a numeric prefix to ensure a specific ordering (if required) is suggested.
|
||||
*
|
||||
* @param <T> type of object the predicate can evaluate in memory.
|
||||
*/
|
||||
public abstract class QueryRewriter<T> {
|
||||
/**
|
||||
* Defines the rewrite rules known by a QueryRewriter.
|
||||
*
|
||||
* This class is thread-safe and may be reused or cached.
|
||||
*
|
||||
* @param <T> type of object the predicates can evaluate in memory.
|
||||
* @param <R> type of the rewriter subclass.
|
||||
*/
|
||||
public static class Definition<T, R extends QueryRewriter<T>> {
|
||||
private final List<RewriteRule<T>> rewriteRules;
|
||||
|
||||
public Definition(Class<R> clazz, QueryBuilder<T> qb) {
|
||||
rewriteRules = Lists.newArrayList();
|
||||
|
||||
Class<?> c = clazz;
|
||||
while (c != QueryRewriter.class) {
|
||||
Method[] declared = c.getDeclaredMethods();
|
||||
for (Method m : declared) {
|
||||
Rewrite rp = m.getAnnotation(Rewrite.class);
|
||||
if ((m.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT
|
||||
&& (m.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC
|
||||
&& rp != null) {
|
||||
rewriteRules.add(new MethodRewrite<>(qb, rp.value(), m));
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
Collections.sort(rewriteRules);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<RewriteRule<T>> rewriteRules;
|
||||
|
||||
protected QueryRewriter(final Definition<T, ? extends QueryRewriter<T>> def) {
|
||||
this.rewriteRules = def.rewriteRules;
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
public Predicate<T> and(Collection<? extends Predicate<T>> that) {
|
||||
return Predicate.and(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
public Predicate<T> and(@SuppressWarnings("unchecked") Predicate<T>... that) {
|
||||
return and(Arrays.asList(that));
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
public Predicate<T> or(Collection<? extends Predicate<T>> that) {
|
||||
return Predicate.or(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
public Predicate<T> or(@SuppressWarnings("unchecked") Predicate<T>... that) {
|
||||
return or(Arrays.asList(that));
|
||||
}
|
||||
|
||||
/** Invert the passed node. */
|
||||
public Predicate<T> not(Predicate<T> that) {
|
||||
return Predicate.not(that);
|
||||
}
|
||||
|
||||
protected Predicate<T> preRewrite(Predicate<T> in) {
|
||||
return in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply rewrites to a graph until it stops changing.
|
||||
*
|
||||
* @param in the graph to rewrite.
|
||||
* @return the rewritten graph.
|
||||
*/
|
||||
public final Predicate<T> rewrite(Predicate<T> in) {
|
||||
in = preRewrite(in);
|
||||
return rewriteImpl(in);
|
||||
}
|
||||
|
||||
private Predicate<T> rewriteImpl(Predicate<T> in) {
|
||||
Predicate<T> old;
|
||||
do {
|
||||
old = in;
|
||||
in = rewriteOne(in);
|
||||
|
||||
if (old.equals(in) && in.getChildCount() > 0) {
|
||||
List<Predicate<T>> n = new ArrayList<>(in.getChildCount());
|
||||
for (Predicate<T> p : in.getChildren()) {
|
||||
n.add(rewriteImpl(p));
|
||||
}
|
||||
n = removeDuplicates(n);
|
||||
if (n.size() == 1 && (isAND(in) || isOR(in))) {
|
||||
in = n.get(0);
|
||||
} else {
|
||||
in = in.copy(n);
|
||||
}
|
||||
}
|
||||
|
||||
} while (!old.equals(in));
|
||||
return replaceGenericNodes(in);
|
||||
}
|
||||
|
||||
protected Predicate<T> replaceGenericNodes(final Predicate<T> in) {
|
||||
if (in instanceof NotPredicate) {
|
||||
return not(replaceGenericNodes(in.getChild(0)));
|
||||
|
||||
} else if (in instanceof AndPredicate) {
|
||||
List<Predicate<T>> n = new ArrayList<>(in.getChildCount());
|
||||
for (Predicate<T> c : in.getChildren()) {
|
||||
n.add(replaceGenericNodes(c));
|
||||
}
|
||||
return and(n);
|
||||
|
||||
} else if (in instanceof OrPredicate) {
|
||||
List<Predicate<T>> n = new ArrayList<>(in.getChildCount());
|
||||
for (Predicate<T> c : in.getChildren()) {
|
||||
n.add(replaceGenericNodes(c));
|
||||
}
|
||||
return or(n);
|
||||
|
||||
} else {
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
private Predicate<T> rewriteOne(Predicate<T> input) {
|
||||
Predicate<T> best = null;
|
||||
for (RewriteRule<T> r : rewriteRules) {
|
||||
Predicate<T> n = r.rewrite(this, input);
|
||||
if (n == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r.useBestCost()) {
|
||||
return n;
|
||||
}
|
||||
|
||||
if (best == null || n.getCost() < best.getCost()) {
|
||||
best = n;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return best != null ? best : input;
|
||||
}
|
||||
|
||||
private static class MatchResult<T> {
|
||||
private static final MatchResult<?> FAIL = new MatchResult<>(null);
|
||||
private static final MatchResult<?> OK = new MatchResult<>(null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> MatchResult<T> fail() {
|
||||
return (MatchResult<T>) FAIL;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> MatchResult<T> ok() {
|
||||
return (MatchResult<T>) OK;
|
||||
}
|
||||
|
||||
final Predicate<T> extra;
|
||||
|
||||
MatchResult(Predicate<T> extra) {
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
boolean success() {
|
||||
return this != FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
private MatchResult<T> match(final Map<String, Predicate<T>> outVars,
|
||||
final Predicate<T> pattern, final Predicate<T> actual) {
|
||||
if (pattern instanceof VariablePredicate) {
|
||||
final VariablePredicate<T> v = (VariablePredicate<T>) pattern;
|
||||
final MatchResult<T> r = match(outVars, v.getChild(0), actual);
|
||||
if (r.success()) {
|
||||
Predicate<T> old = outVars.get(v.getName());
|
||||
if (old == null) {
|
||||
outVars.put(v.getName(), actual);
|
||||
return r;
|
||||
} else if (old.equals(actual)) {
|
||||
return r;
|
||||
} else {
|
||||
return MatchResult.fail();
|
||||
}
|
||||
} else {
|
||||
return MatchResult.fail();
|
||||
}
|
||||
}
|
||||
|
||||
if ((isAND(pattern) && isAND(actual)) //
|
||||
|| (isOR(pattern) && isOR(actual)) //
|
||||
|| (isNOT(pattern) && isNOT(actual)) //
|
||||
) {
|
||||
// Order doesn't actually matter here. That does make our logic quite
|
||||
// a bit more complex as we need to consult each child at most once,
|
||||
// but in any order.
|
||||
//
|
||||
final LinkedList<Predicate<T>> have = dup(actual);
|
||||
final LinkedList<Predicate<T>> extra = new LinkedList<>();
|
||||
for (final Predicate<T> pat : pattern.getChildren()) {
|
||||
boolean found = false;
|
||||
for (final Iterator<Predicate<T>> i = have.iterator(); i.hasNext();) {
|
||||
final MatchResult<T> r = match(outVars, pat, i.next());
|
||||
if (r.success()) {
|
||||
found = true;
|
||||
i.remove();
|
||||
if (r.extra != null) {
|
||||
extra.add(r.extra);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return MatchResult.fail();
|
||||
}
|
||||
}
|
||||
have.addAll(extra);
|
||||
switch (have.size()) {
|
||||
case 0:
|
||||
return MatchResult.ok();
|
||||
case 1:
|
||||
if (isNOT(actual)) {
|
||||
return new MatchResult<>(actual.copy(have));
|
||||
}
|
||||
return new MatchResult<>(have.get(0));
|
||||
default:
|
||||
return new MatchResult<>(actual.copy(have));
|
||||
}
|
||||
|
||||
} else if (pattern.equals(actual)) {
|
||||
return MatchResult.ok();
|
||||
|
||||
} else if (pattern instanceof WildPatternPredicate
|
||||
&& actual instanceof OperatorPredicate
|
||||
&& ((OperatorPredicate<T>) pattern).getOperator().equals(
|
||||
((OperatorPredicate<T>) actual).getOperator())) {
|
||||
return MatchResult.ok();
|
||||
|
||||
} else {
|
||||
return MatchResult.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> LinkedList<Predicate<T>> dup(final Predicate<T> actual) {
|
||||
return new LinkedList<>(actual.getChildren());
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes a method which wants to replace a predicate expression.
|
||||
* <p>
|
||||
* This annotation must be applied to a public method which returns
|
||||
* {@link Predicate}. The arguments of the method should {@link Predicate}, or
|
||||
* any subclass of it. The annotation value is a query language string which
|
||||
* describes the subtree this rewrite applies to. Method arguments should be
|
||||
* named with a {@link Named} annotation, and the same names should be used in
|
||||
* the query.
|
||||
* <p>
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* @Rewrite("A=(owner:*) B=(status:*)")
|
||||
* public Predicate ownerStatus(@Named("A") OperatorPredicate owner,
|
||||
* @Named("B") OperatorPredicate status) {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* matches an AND Predicate with at least two children, one being an operator
|
||||
* predicate called "owner" and the other being an operator predicate called
|
||||
* "status". The variables in the query are matched by name against the
|
||||
* parameters.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
protected @interface Rewrite {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
protected @interface NoCostComputation {
|
||||
}
|
||||
|
||||
/** Applies a rewrite rule to a Predicate. */
|
||||
protected interface RewriteRule<T> extends Comparable<RewriteRule<T>> {
|
||||
/**
|
||||
* Apply a rewrite rule to the Predicate.
|
||||
*
|
||||
* @param input the input predicate to be tested, and possibly rewritten.
|
||||
* @return a rewritten form of the predicate if this rule matches with the
|
||||
* tree {@code input} and has a rewrite for it; {@code null} if this
|
||||
* rule does not want this predicate.
|
||||
*/
|
||||
Predicate<T> rewrite(QueryRewriter<T> rewriter, Predicate<T> input);
|
||||
|
||||
/** @return true if the best cost should be selected. */
|
||||
boolean useBestCost();
|
||||
}
|
||||
|
||||
/** Implements the magic behind {@link Rewrite} annotations. */
|
||||
private static class MethodRewrite<T> implements RewriteRule<T> {
|
||||
private final Method method;
|
||||
private final Predicate<T> pattern;
|
||||
private final String[] argNames;
|
||||
private final Class<? extends Predicate<T>>[] argTypes;
|
||||
private final boolean useBestCost;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MethodRewrite(QueryBuilder<T> queryBuilder, String patternText, Method m) {
|
||||
method = m;
|
||||
useBestCost = m.getAnnotation(NoCostComputation.class) == null;
|
||||
|
||||
Predicate<T> p;
|
||||
try {
|
||||
p = queryBuilder.parse(patternText);
|
||||
} catch (QueryParseException e) {
|
||||
throw new RuntimeException("Bad @Rewrite(\"" + patternText + "\")"
|
||||
+ " on " + m.toGenericString() + " in " + m.getDeclaringClass()
|
||||
+ ": " + e.getMessage(), e);
|
||||
}
|
||||
if (!Predicate.class.isAssignableFrom(m.getReturnType())) {
|
||||
throw new RuntimeException(m.toGenericString() + " in "
|
||||
+ m.getDeclaringClass() + " must return " + Predicate.class);
|
||||
}
|
||||
|
||||
pattern = p;
|
||||
argNames = new String[method.getParameterTypes().length];
|
||||
argTypes = new Class[argNames.length];
|
||||
for (int i = 0; i < argNames.length; i++) {
|
||||
Named name = null;
|
||||
for (Annotation a : method.getParameterAnnotations()[i]) {
|
||||
if (a instanceof Named) {
|
||||
name = (Named) a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
throw new RuntimeException("Argument " + (i + 1) + " of "
|
||||
+ m.toGenericString() + " in " + m.getDeclaringClass()
|
||||
+ " has no @Named annotation");
|
||||
}
|
||||
if (!Predicate.class.isAssignableFrom(method.getParameterTypes()[i])) {
|
||||
throw new RuntimeException("Argument " + (i + 1) + " of "
|
||||
+ m.toGenericString() + " in " + m.getDeclaringClass()
|
||||
+ " must be of type " + Predicate.class);
|
||||
}
|
||||
argNames[i] = name.value();
|
||||
argTypes[i] = (Class<Predicate<T>>) method.getParameterTypes()[i];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useBestCost() {
|
||||
return useBestCost;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Predicate<T> rewrite(QueryRewriter<T> rewriter,
|
||||
final Predicate<T> input) {
|
||||
final HashMap<String, Predicate<T>> args = new HashMap<>();
|
||||
final MatchResult<T> res = rewriter.match(args, pattern, input);
|
||||
if (!res.success()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Predicate<T>[] argList = new Predicate[argNames.length];
|
||||
for (int i = 0; i < argList.length; i++) {
|
||||
argList[i] = args.get(argNames[i]);
|
||||
if (argList[i] == null) {
|
||||
final String a = "@Named(\"" + argNames[i] + "\")";
|
||||
throw error(new IllegalStateException("No value bound for " + a));
|
||||
}
|
||||
if (!argTypes[i].isInstance(argList[i])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Predicate<T> rep;
|
||||
try {
|
||||
rep = (Predicate<T>) method.invoke(rewriter, (Object[]) argList);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw error(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw error(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw error(e.getCause());
|
||||
}
|
||||
|
||||
if (rep instanceof RewritePredicate) {
|
||||
((RewritePredicate<T>) rep).init(method.getName(), argList);
|
||||
}
|
||||
|
||||
if (res.extra == null) {
|
||||
return rep;
|
||||
}
|
||||
|
||||
Predicate<T> extra = removeDuplicates(res.extra);
|
||||
Predicate<T>[] newArgs = new Predicate[] {extra, rep};
|
||||
return input.copy(Arrays.asList(newArgs));
|
||||
}
|
||||
|
||||
private IllegalArgumentException error(Throwable e) {
|
||||
final String msg = "Cannot apply " + method.getName();
|
||||
return new IllegalArgumentException(msg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RewriteRule<T> in) {
|
||||
if (in instanceof MethodRewrite) {
|
||||
return method.getName().compareTo(
|
||||
((MethodRewrite<T>) in).method.getName());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Predicate<T> removeDuplicates(Predicate<T> in) {
|
||||
if (in.getChildCount() > 0) {
|
||||
List<Predicate<T>> n = removeDuplicates(in.getChildren());
|
||||
if (n.size() == 1 && (isAND(in) || isOR(in))) {
|
||||
in = n.get(0);
|
||||
} else {
|
||||
in = in.copy(n);
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
private static <T> List<Predicate<T>> removeDuplicates(List<Predicate<T>> n) {
|
||||
List<Predicate<T>> r = new ArrayList<>();
|
||||
for (Predicate<T> p : n) {
|
||||
if (!r.contains(p)) {
|
||||
r.add(p);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static <T> boolean isAND(final Predicate<T> p) {
|
||||
return p instanceof AndPredicate;
|
||||
}
|
||||
|
||||
private static <T> boolean isOR(final Predicate<T> p) {
|
||||
return p instanceof OrPredicate;
|
||||
}
|
||||
|
||||
private static <T> boolean isNOT(final Predicate<T> p) {
|
||||
return p instanceof NotPredicate;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
// Copyright (C) 2010 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.collect.ImmutableList;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class RewritePredicate<T> extends Predicate<T> {
|
||||
private boolean init;
|
||||
private String name = getClass().getSimpleName();
|
||||
private List<Predicate<T>> children = Collections.emptyList();
|
||||
|
||||
protected void init(String name, @SuppressWarnings("unchecked") Predicate<T>... args) {
|
||||
this.init = true;
|
||||
this.name = name;
|
||||
this.children = ImmutableList.copyOf(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof RewritePredicate) {
|
||||
RewritePredicate<T> that = (RewritePredicate<T>) other;
|
||||
if (this.init && that.init) {
|
||||
return this.getClass() == that.getClass()
|
||||
&& this.name.equals(that.name)
|
||||
&& this.children.equals(that.children);
|
||||
}
|
||||
}
|
||||
return this == other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = getClass().hashCode();
|
||||
if (!children.isEmpty()) {
|
||||
h *= 31;
|
||||
h += children.get(0).hashCode();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder r = new StringBuilder();
|
||||
r.append(name);
|
||||
if (!children.isEmpty()) {
|
||||
r.append("(");
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
if (i != 0) {
|
||||
r.append(" ");
|
||||
}
|
||||
r.append(children.get(i));
|
||||
}
|
||||
r.append(")");
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
// Copyright (C) 2009 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.gwtorm.server.OrmException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds another predicate in a named variable.
|
||||
*
|
||||
* @see QueryRewriter
|
||||
*/
|
||||
public class VariablePredicate<T> extends Predicate<T> {
|
||||
private final String name;
|
||||
private final Predicate<T> that;
|
||||
|
||||
protected VariablePredicate(final String name, final Predicate<T> that) {
|
||||
this.name = name;
|
||||
this.that = that;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Predicate<T>> getChildren() {
|
||||
return Collections.singletonList(that);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getChildCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Predicate<T> getChild(final int i) {
|
||||
if (i != 0) {
|
||||
throw new ArrayIndexOutOfBoundsException(i);
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(final Collection<? extends Predicate<T>> children) {
|
||||
if (children.size() != 1) {
|
||||
throw new IllegalArgumentException("Expected exactly one child");
|
||||
}
|
||||
return new VariablePredicate<>(getName(), children.iterator().next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(final T object) throws OrmException {
|
||||
return that.match(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return that.getCost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode() * 31 + that.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() == other.getClass()) {
|
||||
final VariablePredicate<?> v = (VariablePredicate<?>) other;
|
||||
return getName().equals(v.getName())
|
||||
&& getChildren().equals(v.getChildren());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return getName() + "=(" + that.toString() + ")";
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
// Copyright (C) 2009 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;
|
||||
|
||||
/**
|
||||
* Predicate only for use in rewrite rule patterns.
|
||||
* <p>
|
||||
* May <b>only</b> be used when nested immediately within a
|
||||
* {@link VariablePredicate}. Within the QueryRewriter this predicate matches
|
||||
* any other operator whose name matches this predicate's operator name.
|
||||
*
|
||||
* @see QueryRewriter
|
||||
*/
|
||||
public final class WildPatternPredicate<T> extends OperatorPredicate<T> {
|
||||
public WildPatternPredicate(final String name) {
|
||||
super(name, "*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(final T object) {
|
||||
throw new UnsupportedOperationException("Cannot match " + toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getOperator().hashCode() * 31;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() == other.getClass()) {
|
||||
final WildPatternPredicate<?> p = (WildPatternPredicate<?>) other;
|
||||
return getOperator().equals(p.getOperator());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getOperator() + ":" + getValue();
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryRewriter;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||
private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
|
||||
new ChangeQueryBuilder.Arguments(
|
||||
new InvalidProvider<ReviewDb>(),
|
||||
new InvalidProvider<InternalChangeQuery>(),
|
||||
new InvalidProvider<ChangeQueryRewriter>(),
|
||||
null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null));
|
||||
|
||||
private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
|
||||
new QueryRewriter.Definition<>(BasicChangeRewrites.class, BUILDER);
|
||||
|
||||
@Inject
|
||||
public BasicChangeRewrites() {
|
||||
super(mydef);
|
||||
}
|
||||
|
||||
@Rewrite("-status:open")
|
||||
@NoCostComputation
|
||||
public Predicate<ChangeData> r00_notOpen() {
|
||||
return ChangeStatusPredicate.closed();
|
||||
}
|
||||
|
||||
@Rewrite("-status:closed")
|
||||
@NoCostComputation
|
||||
public Predicate<ChangeData> r00_notClosed() {
|
||||
return ChangeStatusPredicate.open();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NoCostComputation
|
||||
@Rewrite("-status:merged")
|
||||
public Predicate<ChangeData> r00_notMerged() {
|
||||
return or(ChangeStatusPredicate.open(),
|
||||
new ChangeStatusPredicate(Change.Status.ABANDONED));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NoCostComputation
|
||||
@Rewrite("-status:abandoned")
|
||||
public Predicate<ChangeData> r00_notAbandoned() {
|
||||
return or(ChangeStatusPredicate.open(),
|
||||
new ChangeStatusPredicate(Change.Status.MERGED));
|
||||
}
|
||||
|
||||
private static final class InvalidProvider<T> implements Provider<T> {
|
||||
@Override
|
||||
public T get() {
|
||||
throw new OutOfScopeException("Not available at init");
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,6 @@ import com.google.gerrit.server.query.AndPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.AndSource;
|
||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gerrit.server.query.change.OrSource;
|
||||
@ -54,7 +53,7 @@ public class IndexRewriteTest {
|
||||
indexes = new IndexCollection();
|
||||
indexes.setSearchIndex(index);
|
||||
queryBuilder = new FakeQueryBuilder(indexes);
|
||||
rewrite = new IndexRewriteImpl(indexes, new BasicChangeRewrites());
|
||||
rewrite = new IndexRewriteImpl(indexes);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -94,9 +93,9 @@ public class IndexRewriteTest {
|
||||
@Test
|
||||
public void testThreeLevelTreeWithAllIndexPredicates() throws Exception {
|
||||
Predicate<ChangeData> in =
|
||||
parse("-status:abandoned (status:open OR status:merged)");
|
||||
parse("-status:abandoned (file:a OR file:b)");
|
||||
assertEquals(
|
||||
query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
|
||||
query(in),
|
||||
rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user