Move general index classes from gerrit-server to gerrit-index
These classes do not depend on any Gerrit server functionality, and could even be used to define an index without depending on the gerrit-server package. This allows for a clearer separation of BUILD rules; the QueryParser and antlr targets don't escape the gerrit-index package. The general layout thus far is to put index definition code in com.google.gerrit.index, and query-related code (predicates, etc.) in com.google.gerrit.index.query. The gerrit-index package is still of limited utility on its own, because QueryProcessor and InternalQuery still live in the server package, and untangling their dependencies will still be a bit more work. Change-Id: I3c4616d08ecf19d5ccd1b9b91b3fd0b1fcedd901
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// 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.index.query;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Requires all predicates to be true. */
|
||||
public class AndPredicate<T> extends Predicate<T> implements Matchable<T> {
|
||||
private final List<Predicate<T>> children;
|
||||
private final int cost;
|
||||
|
||||
@SafeVarargs
|
||||
protected AndPredicate(Predicate<T>... that) {
|
||||
this(Arrays.asList(that));
|
||||
}
|
||||
|
||||
protected AndPredicate(Collection<? extends Predicate<T>> that) {
|
||||
List<Predicate<T>> t = new ArrayList<>(that.size());
|
||||
int c = 0;
|
||||
for (Predicate<T> p : that) {
|
||||
if (getClass() == p.getClass()) {
|
||||
for (Predicate<T> gp : p.getChildren()) {
|
||||
t.add(gp);
|
||||
c += gp.estimateCost();
|
||||
}
|
||||
} else {
|
||||
t.add(p);
|
||||
c += p.estimateCost();
|
||||
}
|
||||
}
|
||||
children = t;
|
||||
cost = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Predicate<T>> getChildren() {
|
||||
return Collections.unmodifiableList(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getChildCount() {
|
||||
return children.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Predicate<T> getChild(int i) {
|
||||
return children.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
return new AndPredicate<>(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatchable() {
|
||||
for (Predicate<T> c : children) {
|
||||
if (!c.isMatchable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) throws OrmException {
|
||||
for (Predicate<T> c : children) {
|
||||
checkState(
|
||||
c.isMatchable(),
|
||||
"match invoked, but child predicate %s doesn't implement %s",
|
||||
c,
|
||||
Matchable.class.getName());
|
||||
if (!c.asMatchable().match(object)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getChild(0).hashCode() * 31 + getChild(1).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return getClass() == other.getClass()
|
||||
&& getChildren().equals(((Predicate<?>) other).getChildren());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder r = new StringBuilder();
|
||||
r.append("(");
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
if (i != 0) {
|
||||
r.append(" ");
|
||||
}
|
||||
r.append(getChild(i));
|
||||
}
|
||||
r.append(")");
|
||||
return r.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// 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 com.google.common.base.Throwables;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gwtorm.server.ListResultSet;
|
||||
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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AndSource<T> extends AndPredicate<T>
|
||||
implements DataSource<T>, Comparator<Predicate<T>> {
|
||||
protected final DataSource<T> source;
|
||||
|
||||
private final IsVisibleToPredicate<T> isVisibleToPredicate;
|
||||
private final int start;
|
||||
private final int cardinality;
|
||||
|
||||
public AndSource(Collection<? extends Predicate<T>> that) {
|
||||
this(that, null, 0);
|
||||
}
|
||||
|
||||
public AndSource(Predicate<T> that, IsVisibleToPredicate<T> isVisibleToPredicate) {
|
||||
this(that, isVisibleToPredicate, 0);
|
||||
}
|
||||
|
||||
public AndSource(Predicate<T> that, IsVisibleToPredicate<T> isVisibleToPredicate, int start) {
|
||||
this(ImmutableList.of(that), isVisibleToPredicate, start);
|
||||
}
|
||||
|
||||
public AndSource(
|
||||
Collection<? extends Predicate<T>> that,
|
||||
IsVisibleToPredicate<T> isVisibleToPredicate,
|
||||
int start) {
|
||||
super(that);
|
||||
checkArgument(start >= 0, "negative start: %s", start);
|
||||
this.isVisibleToPredicate = isVisibleToPredicate;
|
||||
this.start = start;
|
||||
|
||||
int c = Integer.MAX_VALUE;
|
||||
DataSource<T> s = null;
|
||||
int minCost = Integer.MAX_VALUE;
|
||||
for (Predicate<T> p : sort(getChildren())) {
|
||||
if (p instanceof DataSource) {
|
||||
c = Math.min(c, ((DataSource<?>) p).getCardinality());
|
||||
|
||||
int cost = p.estimateCost();
|
||||
if (cost < minCost) {
|
||||
s = toDataSource(p);
|
||||
minCost = cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.source = s;
|
||||
this.cardinality = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet<T> read() throws OrmException {
|
||||
try {
|
||||
return readImpl();
|
||||
} catch (OrmRuntimeException err) {
|
||||
if (err.getCause() != null) {
|
||||
Throwables.throwIfInstanceOf(err.getCause(), OrmException.class);
|
||||
}
|
||||
throw new OrmException(err);
|
||||
}
|
||||
}
|
||||
|
||||
private ResultSet<T> readImpl() throws OrmException {
|
||||
if (source == null) {
|
||||
throw new OrmException("No DataSource: " + this);
|
||||
}
|
||||
List<T> r = new ArrayList<>();
|
||||
T last = null;
|
||||
int nextStart = 0;
|
||||
boolean skipped = false;
|
||||
for (T data : buffer(source.read())) {
|
||||
if (!isMatchable() || match(data)) {
|
||||
r.add(data);
|
||||
} else {
|
||||
skipped = true;
|
||||
}
|
||||
last = data;
|
||||
nextStart++;
|
||||
}
|
||||
|
||||
if (skipped && last != null && source instanceof Paginated) {
|
||||
// If our source is a paginated source and we skipped at
|
||||
// least one of its results, we may not have filled the full
|
||||
// limit the caller wants. Restart the source and continue.
|
||||
//
|
||||
@SuppressWarnings("unchecked")
|
||||
Paginated<T> p = (Paginated<T>) source;
|
||||
while (skipped && r.size() < p.getOptions().limit() + start) {
|
||||
skipped = false;
|
||||
ResultSet<T> next = p.restart(nextStart);
|
||||
|
||||
for (T data : buffer(next)) {
|
||||
if (match(data)) {
|
||||
r.add(data);
|
||||
} else {
|
||||
skipped = true;
|
||||
}
|
||||
nextStart++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start >= r.size()) {
|
||||
r = ImmutableList.of();
|
||||
} else if (start > 0) {
|
||||
r = ImmutableList.copyOf(r.subList(start, r.size()));
|
||||
}
|
||||
return new ListResultSet<>(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatchable() {
|
||||
return isVisibleToPredicate != null || super.isMatchable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) throws OrmException {
|
||||
if (isVisibleToPredicate != null && !isVisibleToPredicate.match(object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (super.isMatchable() && !super.match(object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Iterable<T> buffer(ResultSet<T> scanner) {
|
||||
return FluentIterable.from(Iterables.partition(scanner, 50))
|
||||
.transformAndConcat(this::transformBuffer);
|
||||
}
|
||||
|
||||
protected List<T> transformBuffer(List<T> buffer) throws OrmRuntimeException {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCardinality() {
|
||||
return cardinality;
|
||||
}
|
||||
|
||||
private List<Predicate<T>> sort(Collection<? extends Predicate<T>> that) {
|
||||
List<Predicate<T>> r = new ArrayList<>(that);
|
||||
Collections.sort(r, this);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Predicate<T> a, Predicate<T> b) {
|
||||
int ai = a instanceof DataSource ? 0 : 1;
|
||||
int bi = b instanceof DataSource ? 0 : 1;
|
||||
int cmp = ai - bi;
|
||||
|
||||
if (cmp == 0) {
|
||||
cmp = a.estimateCost() - b.estimateCost();
|
||||
}
|
||||
|
||||
if (cmp == 0 && a instanceof DataSource && b instanceof DataSource) {
|
||||
DataSource<?> as = (DataSource<?>) a;
|
||||
DataSource<?> bs = (DataSource<?>) b;
|
||||
cmp = as.getCardinality() - bs.getCardinality();
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private DataSource<T> toDataSource(Predicate<T> pred) {
|
||||
return (DataSource<T>) pred;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2014 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 com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
|
||||
public interface DataSource<T> {
|
||||
/** @return an estimate of the number of results from {@link #read()}. */
|
||||
int getCardinality();
|
||||
|
||||
/** @return read from the database and return the results. */
|
||||
ResultSet<T> read() throws OrmException;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.index.query;
|
||||
|
||||
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> {
|
||||
private final FieldDef<I, ?> def;
|
||||
|
||||
protected IndexPredicate(FieldDef<I, ?> def, String value) {
|
||||
super(def.getName(), value);
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
protected IndexPredicate(FieldDef<I, ?> def, String name, String value) {
|
||||
super(name, value);
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
public FieldDef<I, ?> getField() {
|
||||
return def;
|
||||
}
|
||||
|
||||
public FieldType<?> getType() {
|
||||
return def.getType();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.index.query;
|
||||
|
||||
/** Predicate to filter a field by matching integer value. */
|
||||
public abstract class IntPredicate<T> extends OperatorPredicate<T> {
|
||||
private final int intValue;
|
||||
|
||||
public IntPredicate(String name, String value) {
|
||||
super(name, value);
|
||||
this.intValue = Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public IntPredicate(String name, int intValue) {
|
||||
super(name, String.valueOf(intValue));
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
public int intValue() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getOperator().hashCode() * 31 + intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() == other.getClass()) {
|
||||
final IntPredicate<?> p = (IntPredicate<?>) other;
|
||||
return getOperator().equals(p.getOperator()) && intValue() == p.intValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getOperator() + ":" + getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2014 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 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> {
|
||||
private final Range range;
|
||||
|
||||
protected IntegerRangePredicate(FieldDef<T, Integer> type, String value)
|
||||
throws QueryParseException {
|
||||
super(type, value);
|
||||
range = RangeUtil.getRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
if (range == null) {
|
||||
throw new QueryParseException("Invalid range predicate: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Integer getValueInt(T object) throws OrmException;
|
||||
|
||||
public boolean match(T object) throws OrmException {
|
||||
Integer valueInt = getValueInt(object);
|
||||
if (valueInt == null) {
|
||||
return false;
|
||||
}
|
||||
return valueInt >= range.min && valueInt <= range.max;
|
||||
}
|
||||
|
||||
/** Return the minimum value of this predicate's range, inclusive. */
|
||||
public int getMinimumValue() {
|
||||
return range.min;
|
||||
}
|
||||
|
||||
/** Return the maximum value of this predicate's range, inclusive. */
|
||||
public int getMaximumValue() {
|
||||
return range.max;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
|
||||
public abstract class IsVisibleToPredicate<T> extends OperatorPredicate<T> implements Matchable<T> {
|
||||
public IsVisibleToPredicate(String name, String value) {
|
||||
super(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2014 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;
|
||||
|
||||
public class LimitPredicate<T> extends IntPredicate<T> implements Matchable<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Integer getLimit(String fieldName, Predicate<?> p) {
|
||||
IntPredicate<?> ip = QueryBuilder.find(p, IntPredicate.class, fieldName);
|
||||
return ip != null ? ip.intValue() : null;
|
||||
}
|
||||
|
||||
public LimitPredicate(String fieldName, int limit) throws QueryParseException {
|
||||
super(fieldName, limit);
|
||||
if (limit <= 0) {
|
||||
throw new QueryParseException("limit must be positive: " + limit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 com.google.gwtorm.server.OrmException;
|
||||
|
||||
public interface Matchable<T> {
|
||||
/**
|
||||
* Does this predicate match this object?
|
||||
*
|
||||
* @throws OrmException
|
||||
*/
|
||||
boolean match(T object) throws OrmException;
|
||||
|
||||
/** @return a cost estimate to run this predicate, higher figures cost more. */
|
||||
int getCost();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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.index.query;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Negates the result of another predicate. */
|
||||
public class NotPredicate<T> extends Predicate<T> implements Matchable<T> {
|
||||
private final Predicate<T> that;
|
||||
|
||||
protected NotPredicate(Predicate<T> that) {
|
||||
if (that instanceof NotPredicate) {
|
||||
throw new IllegalArgumentException("Double negation unsupported");
|
||||
}
|
||||
this.that = that;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Predicate<T>> getChildren() {
|
||||
return Collections.singletonList(that);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getChildCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Predicate<T> getChild(int i) {
|
||||
if (i != 0) {
|
||||
throw new ArrayIndexOutOfBoundsException(i);
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
if (children.size() != 1) {
|
||||
throw new IllegalArgumentException("Expected exactly one child");
|
||||
}
|
||||
return new NotPredicate<>(children.iterator().next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatchable() {
|
||||
return that.isMatchable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) throws OrmException {
|
||||
checkState(
|
||||
that.isMatchable(),
|
||||
"match invoked, but child predicate %s doesn't implement %s",
|
||||
that,
|
||||
Matchable.class.getName());
|
||||
return !that.asMatchable().match(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return that.estimateCost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ~that.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return getClass() == other.getClass()
|
||||
&& getChildren().equals(((Predicate<?>) other).getChildren());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return "-" + that.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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.index.query;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/** Predicate to filter a field by matching value. */
|
||||
public abstract class OperatorPredicate<T> extends Predicate<T> {
|
||||
protected final String name;
|
||||
protected final String value;
|
||||
|
||||
public OperatorPredicate(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getOperator() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
if (!children.isEmpty()) {
|
||||
throw new IllegalArgumentException("Expected 0 children");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getOperator().hashCode() * 31 + getValue().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() == other.getClass()) {
|
||||
final OperatorPredicate<?> p = (OperatorPredicate<?>) other;
|
||||
return getOperator().equals(p.getOperator()) && getValue().equals(p.getValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String val = getValue();
|
||||
if (QueryParser.isSingleWord(val)) {
|
||||
return getOperator() + ":" + val;
|
||||
}
|
||||
return getOperator() + ":\"" + val + "\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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.index.query;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Requires one predicate to be true. */
|
||||
public class OrPredicate<T> extends Predicate<T> implements Matchable<T> {
|
||||
private final List<Predicate<T>> children;
|
||||
private final int cost;
|
||||
|
||||
@SafeVarargs
|
||||
protected OrPredicate(Predicate<T>... that) {
|
||||
this(Arrays.asList(that));
|
||||
}
|
||||
|
||||
protected OrPredicate(Collection<? extends Predicate<T>> that) {
|
||||
List<Predicate<T>> t = new ArrayList<>(that.size());
|
||||
int c = 0;
|
||||
for (Predicate<T> p : that) {
|
||||
if (getClass() == p.getClass()) {
|
||||
for (Predicate<T> gp : p.getChildren()) {
|
||||
t.add(gp);
|
||||
c += gp.estimateCost();
|
||||
}
|
||||
} else {
|
||||
t.add(p);
|
||||
c += p.estimateCost();
|
||||
}
|
||||
}
|
||||
children = t;
|
||||
cost = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<Predicate<T>> getChildren() {
|
||||
return Collections.unmodifiableList(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getChildCount() {
|
||||
return children.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Predicate<T> getChild(int i) {
|
||||
return children.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
return new OrPredicate<>(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatchable() {
|
||||
for (Predicate<T> c : children) {
|
||||
if (!c.isMatchable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) throws OrmException {
|
||||
for (Predicate<T> c : children) {
|
||||
checkState(
|
||||
c.isMatchable(),
|
||||
"match invoked, but child predicate %s doesn't implement %s",
|
||||
c,
|
||||
Matchable.class.getName());
|
||||
if (c.asMatchable().match(object)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getChild(0).hashCode() * 31 + getChild(1).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return getClass() == other.getClass()
|
||||
&& getChildren().equals(((Predicate<?>) other).getChildren());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder r = new StringBuilder();
|
||||
r.append("(");
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
if (i != 0) {
|
||||
r.append(" OR ");
|
||||
}
|
||||
r.append(getChild(i));
|
||||
}
|
||||
r.append(")");
|
||||
return r.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.index.query;
|
||||
|
||||
import com.google.gerrit.index.QueryOptions;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
|
||||
public interface Paginated<T> {
|
||||
QueryOptions getOptions();
|
||||
|
||||
ResultSet<T> restart(int start) throws OrmException;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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.index.query;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract predicate tree for any form of query.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* @param <T> type of object the predicate can evaluate in memory.
|
||||
*/
|
||||
public abstract class Predicate<T> {
|
||||
/** A predicate that matches any input, always, with no cost. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Predicate<T> any() {
|
||||
return (Predicate<T>) Any.INSTANCE;
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
@SafeVarargs
|
||||
public static <T> Predicate<T> and(Predicate<T>... that) {
|
||||
if (that.length == 1) {
|
||||
return that[0];
|
||||
}
|
||||
return new AndPredicate<>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
public static <T> Predicate<T> and(Collection<? extends Predicate<T>> that) {
|
||||
if (that.size() == 1) {
|
||||
return Iterables.getOnlyElement(that);
|
||||
}
|
||||
return new AndPredicate<>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
@SafeVarargs
|
||||
public static <T> Predicate<T> or(Predicate<T>... that) {
|
||||
if (that.length == 1) {
|
||||
return that[0];
|
||||
}
|
||||
return new OrPredicate<>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
public static <T> Predicate<T> or(Collection<? extends Predicate<T>> that) {
|
||||
if (that.size() == 1) {
|
||||
return Iterables.getOnlyElement(that);
|
||||
}
|
||||
return new OrPredicate<>(that);
|
||||
}
|
||||
|
||||
/** Invert the passed node. */
|
||||
public static <T> Predicate<T> not(Predicate<T> that) {
|
||||
if (that instanceof NotPredicate) {
|
||||
// Negate of a negate is the original predicate.
|
||||
//
|
||||
return that.getChild(0);
|
||||
}
|
||||
return new NotPredicate<>(that);
|
||||
}
|
||||
|
||||
/** Get the children of this predicate, if any. */
|
||||
public List<Predicate<T>> getChildren() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/** Same as {@code getChildren().size()} */
|
||||
public int getChildCount() {
|
||||
return getChildren().size();
|
||||
}
|
||||
|
||||
/** Same as {@code getChildren().get(i)} */
|
||||
public Predicate<T> getChild(int i) {
|
||||
return getChildren().get(i);
|
||||
}
|
||||
|
||||
/** Create a copy of this predicate, with new children. */
|
||||
public abstract Predicate<T> copy(Collection<? extends Predicate<T>> children);
|
||||
|
||||
public boolean isMatchable() {
|
||||
return this instanceof Matchable;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Matchable<T> asMatchable() {
|
||||
checkState(isMatchable(), "not matchable");
|
||||
return (Matchable<T>) this;
|
||||
}
|
||||
|
||||
/** @return a cost estimate to run this predicate, higher figures cost more. */
|
||||
public int estimateCost() {
|
||||
if (!isMatchable()) {
|
||||
return 1;
|
||||
}
|
||||
return asMatchable().getCost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public abstract boolean equals(Object other);
|
||||
|
||||
private static class Any<T> extends Predicate<T> implements Matchable<T> {
|
||||
private static final Any<Object> INSTANCE = new Any<>();
|
||||
|
||||
private Any() {}
|
||||
|
||||
@Override
|
||||
public Predicate<T> copy(Collection<? extends Predicate<T>> children) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(T object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other == this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// 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.index.query;
|
||||
|
||||
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;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
|
||||
/**
|
||||
* Base class to support writing parsers for query languages.
|
||||
*
|
||||
* <p>Subclasses may document their supported query operators by declaring public methods that
|
||||
* perform the query conversion into a {@link Predicate}. For example, to support "is:starred",
|
||||
* "is:unread", and nothing else, a subclass may write:
|
||||
*
|
||||
* <pre>
|
||||
* @Operator
|
||||
* public Predicate is(String value) {
|
||||
* if ("starred".equals(value)) {
|
||||
* return new StarredPredicate();
|
||||
* }
|
||||
* if ("unread".equals(value)) {
|
||||
* return new UnreadPredicate();
|
||||
* }
|
||||
* throw new IllegalArgumentException();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>The available operator methods are discovered at runtime via reflection. Method names (after
|
||||
* being converted to lowercase), correspond to operators in the query language, method string
|
||||
* values correspond to the operator argument. Methods must be declared {@code public}, returning
|
||||
* {@link Predicate}, accepting one {@link String}, and annotated with the {@link Operator}
|
||||
* annotation.
|
||||
*
|
||||
* <p>Subclasses may also declare a handler for values which appear without operator by overriding
|
||||
* {@link #defaultField(String)}.
|
||||
*
|
||||
* @param <T> type of object the predicates can evaluate in memory.
|
||||
*/
|
||||
public abstract class QueryBuilder<T> {
|
||||
/** Converts a value string passed to an operator into a {@link Predicate}. */
|
||||
public interface OperatorFactory<T, Q extends QueryBuilder<T>> {
|
||||
Predicate<T> create(Q builder, String value) throws QueryParseException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the operators known by a QueryBuilder.
|
||||
*
|
||||
* <p>This class is thread-safe and may be reused or cached.
|
||||
*
|
||||
* @param <T> type of object the predicates can evaluate in memory.
|
||||
* @param <Q> type of the query builder subclass.
|
||||
*/
|
||||
public static class Definition<T, Q extends QueryBuilder<T>> {
|
||||
private final Map<String, OperatorFactory<T, Q>> opFactories = new HashMap<>();
|
||||
|
||||
public Definition(Class<Q> clazz) {
|
||||
// Guess at the supported operators by scanning methods.
|
||||
//
|
||||
Class<?> c = clazz;
|
||||
while (c != QueryBuilder.class) {
|
||||
for (Method method : c.getDeclaredMethods()) {
|
||||
if (method.getAnnotation(Operator.class) != null
|
||||
&& Predicate.class.isAssignableFrom(method.getReturnType())
|
||||
&& method.getParameterTypes().length == 1
|
||||
&& method.getParameterTypes()[0] == String.class
|
||||
&& (method.getModifiers() & Modifier.ABSTRACT) == 0
|
||||
&& (method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
|
||||
final String name = method.getName().toLowerCase();
|
||||
if (!opFactories.containsKey(name)) {
|
||||
opFactories.put(name, new ReflectionFactory<T, Q>(name, method));
|
||||
}
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a predicate in the predicate tree.
|
||||
*
|
||||
* @param p the predicate to find.
|
||||
* @param clazz type of the predicate instance.
|
||||
* @return the predicate, null if not found.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, P extends Predicate<T>> P find(Predicate<T> p, Class<P> clazz) {
|
||||
if (clazz.isAssignableFrom(p.getClass())) {
|
||||
return (P) p;
|
||||
}
|
||||
|
||||
for (Predicate<T> c : p.getChildren()) {
|
||||
P r = find(c, clazz);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a predicate in the predicate tree.
|
||||
*
|
||||
* @param p the predicate to find.
|
||||
* @param clazz type of the predicate instance.
|
||||
* @param name name of the operator.
|
||||
* @return the first instance of a predicate having the given type, as found by a depth-first
|
||||
* search.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, P extends OperatorPredicate<T>> P find(
|
||||
Predicate<T> p, Class<P> clazz, String name) {
|
||||
if (p instanceof OperatorPredicate
|
||||
&& ((OperatorPredicate<?>) p).getOperator().equals(name)
|
||||
&& clazz.isAssignableFrom(p.getClass())) {
|
||||
return (P) p;
|
||||
}
|
||||
|
||||
for (Predicate<T> c : p.getChildren()) {
|
||||
P r = find(c, clazz, name);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final Definition<T, ? extends QueryBuilder<T>> builderDef;
|
||||
|
||||
protected final Map<String, OperatorFactory<?, ?>> opFactories;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected QueryBuilder(Definition<T, ? extends QueryBuilder<T>> def) {
|
||||
builderDef = def;
|
||||
opFactories = (Map) def.opFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a user-supplied query string into a predicate.
|
||||
*
|
||||
* @param query the query string.
|
||||
* @return predicate representing the user query.
|
||||
* @throws QueryParseException the query string is invalid and cannot be parsed by this parser.
|
||||
* This may be due to a syntax error, may be due to an operator not being supported, or due to
|
||||
* an invalid value being passed to a recognized operator.
|
||||
*/
|
||||
public Predicate<T> parse(String query) throws QueryParseException {
|
||||
if (Strings.isNullOrEmpty(query)) {
|
||||
throw new QueryParseException("query is empty");
|
||||
}
|
||||
return toPredicate(QueryParser.parse(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse multiple user-supplied query strings into a list of predicates.
|
||||
*
|
||||
* @param queries the query strings.
|
||||
* @return predicates representing the user query, in the same order as the input.
|
||||
* @throws QueryParseException one of the query strings is invalid and cannot be parsed by this
|
||||
* parser. This may be due to a syntax error, may be due to an operator not being supported,
|
||||
* or due to an invalid value being passed to a recognized operator.
|
||||
*/
|
||||
public List<Predicate<T>> parse(List<String> queries) throws QueryParseException {
|
||||
List<Predicate<T>> predicates = new ArrayList<>(queries.size());
|
||||
for (String query : queries) {
|
||||
predicates.add(parse(query));
|
||||
}
|
||||
return predicates;
|
||||
}
|
||||
|
||||
private Predicate<T> toPredicate(Tree r) throws QueryParseException, IllegalArgumentException {
|
||||
switch (r.getType()) {
|
||||
case AND:
|
||||
return and(children(r));
|
||||
case OR:
|
||||
return or(children(r));
|
||||
case NOT:
|
||||
return not(toPredicate(onlyChildOf(r)));
|
||||
|
||||
case DEFAULT_FIELD:
|
||||
return defaultField(onlyChildOf(r));
|
||||
|
||||
case FIELD_NAME:
|
||||
return operator(r.getText(), onlyChildOf(r));
|
||||
|
||||
default:
|
||||
throw error("Unsupported operator: " + r);
|
||||
}
|
||||
}
|
||||
|
||||
private Predicate<T> operator(String name, Tree val) throws QueryParseException {
|
||||
switch (val.getType()) {
|
||||
// Expand multiple values, "foo:(a b c)", as though they were written
|
||||
// out with the longer form, "foo:a foo:b foo:c".
|
||||
//
|
||||
case AND:
|
||||
case OR:
|
||||
{
|
||||
List<Predicate<T>> p = new ArrayList<>(val.getChildCount());
|
||||
for (int i = 0; i < val.getChildCount(); i++) {
|
||||
final Tree c = val.getChild(i);
|
||||
if (c.getType() != DEFAULT_FIELD) {
|
||||
throw error("Nested operator not expected: " + c);
|
||||
}
|
||||
p.add(operator(name, onlyChildOf(c)));
|
||||
}
|
||||
return val.getType() == AND ? and(p) : or(p);
|
||||
}
|
||||
|
||||
case SINGLE_WORD:
|
||||
case EXACT_PHRASE:
|
||||
if (val.getChildCount() != 0) {
|
||||
throw error("Expected no children under: " + val);
|
||||
}
|
||||
return operator(name, val.getText());
|
||||
|
||||
default:
|
||||
throw error("Unsupported node in operator " + name + ": " + val);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate<T> operator(String name, String value) throws QueryParseException {
|
||||
@SuppressWarnings("rawtypes")
|
||||
OperatorFactory f = opFactories.get(name);
|
||||
if (f == null) {
|
||||
throw error("Unsupported operator " + name + ":" + value);
|
||||
}
|
||||
return f.create(this, value);
|
||||
}
|
||||
|
||||
private Predicate<T> defaultField(Tree r) throws QueryParseException {
|
||||
switch (r.getType()) {
|
||||
case SINGLE_WORD:
|
||||
case EXACT_PHRASE:
|
||||
if (r.getChildCount() != 0) {
|
||||
throw error("Expected no children under: " + r);
|
||||
}
|
||||
return defaultField(r.getText());
|
||||
|
||||
default:
|
||||
throw error("Unsupported node: " + r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a value present outside of an operator.
|
||||
*
|
||||
* <p>This default implementation always throws an "Unsupported query: " message containing the
|
||||
* input text. Subclasses may override this method to perform do-what-i-mean guesses based on the
|
||||
* input string.
|
||||
*
|
||||
* @param value the value supplied by itself in the query.
|
||||
* @return predicate representing this value.
|
||||
* @throws QueryParseException the parser does not recognize this value.
|
||||
*/
|
||||
protected Predicate<T> defaultField(String value) throws QueryParseException {
|
||||
throw error("Unsupported query:" + value);
|
||||
}
|
||||
|
||||
private List<Predicate<T>> children(Tree r) throws QueryParseException, IllegalArgumentException {
|
||||
List<Predicate<T>> p = new ArrayList<>(r.getChildCount());
|
||||
for (int i = 0; i < r.getChildCount(); i++) {
|
||||
p.add(toPredicate(r.getChild(i)));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private Tree onlyChildOf(Tree r) throws QueryParseException {
|
||||
if (r.getChildCount() != 1) {
|
||||
throw error("Expected exactly one child: " + r);
|
||||
}
|
||||
return r.getChild(0);
|
||||
}
|
||||
|
||||
protected static QueryParseException error(String msg) {
|
||||
return new QueryParseException(msg);
|
||||
}
|
||||
|
||||
protected static QueryParseException error(String msg, Throwable why) {
|
||||
return new QueryParseException(msg, why);
|
||||
}
|
||||
|
||||
/** Denotes a method which is a query operator. */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
protected @interface Operator {}
|
||||
|
||||
private static class ReflectionFactory<T, Q extends QueryBuilder<T>>
|
||||
implements OperatorFactory<T, Q> {
|
||||
private final String name;
|
||||
private final Method method;
|
||||
|
||||
ReflectionFactory(String name, Method method) {
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Predicate<T> create(Q builder, String value) throws QueryParseException {
|
||||
try {
|
||||
return (Predicate<T>) method.invoke(builder, value);
|
||||
} catch (RuntimeException | IllegalAccessException e) {
|
||||
throw error("Error in operator " + name + ":" + value, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof QueryParseException) {
|
||||
throw (QueryParseException) e.getCause();
|
||||
}
|
||||
throw error("Error in operator " + name + ":" + value, e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2014 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 com.google.auto.value.AutoValue;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/** Results of a query over entities. */
|
||||
@AutoValue
|
||||
public abstract class QueryResult<T> {
|
||||
public static <T> QueryResult<T> create(
|
||||
@Nullable String query, Predicate<T> predicate, int limit, List<T> entites) {
|
||||
boolean more;
|
||||
if (entites.size() > limit) {
|
||||
more = true;
|
||||
entites = entites.subList(0, limit);
|
||||
} else {
|
||||
more = false;
|
||||
}
|
||||
return new AutoValue_QueryResult<>(query, predicate, entites, more);
|
||||
}
|
||||
|
||||
/** @return the original query string, or null if the query was created programmatically. */
|
||||
@Nullable
|
||||
public abstract String query();
|
||||
|
||||
/** @return the predicate after all rewriting and other modification by the query subsystem. */
|
||||
public abstract Predicate<T> predicate();
|
||||
|
||||
/** @return the query results. */
|
||||
public abstract List<T> entities();
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (C) 2014 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 com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class RangeUtil {
|
||||
private static final Pattern RANGE_PATTERN = Pattern.compile("(>|>=|=|<|<=|)([+-]?\\d+)$");
|
||||
|
||||
private RangeUtil() {}
|
||||
|
||||
public static class Range {
|
||||
/** The prefix of the query, before the range component. */
|
||||
public final String prefix;
|
||||
|
||||
/** The minimum value specified in the query, inclusive. */
|
||||
public final int min;
|
||||
|
||||
/** The maximum value specified in the query, inclusive. */
|
||||
public final int max;
|
||||
|
||||
public Range(String prefix, int min, int max) {
|
||||
this.prefix = prefix;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the range of values being requested in the given query.
|
||||
*
|
||||
* @param rangeQuery the raw query, e.g. "{@code added:>12345}"
|
||||
* @param minValue the minimum possible value for the field, inclusive
|
||||
* @param maxValue the maximum possible value for the field, inclusive
|
||||
* @return the calculated {@link Range}, or null if the query is invalid
|
||||
*/
|
||||
@Nullable
|
||||
public static Range getRange(String rangeQuery, int minValue, int maxValue) {
|
||||
Matcher m = RANGE_PATTERN.matcher(rangeQuery);
|
||||
String prefix;
|
||||
String test;
|
||||
Integer queryInt;
|
||||
if (m.find()) {
|
||||
prefix = rangeQuery.substring(0, m.start());
|
||||
test = m.group(1);
|
||||
queryInt = value(m.group(2));
|
||||
if (queryInt == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRange(prefix, test, queryInt, minValue, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the range of values being requested in the given query.
|
||||
*
|
||||
* @param prefix a prefix string which is copied into the range
|
||||
* @param test the test operator, one of >, >=, =, <, or <=
|
||||
* @param queryInt the integer being queried
|
||||
* @param minValue the minimum possible value for the field, inclusive
|
||||
* @param maxValue the maximum possible value for the field, inclusive
|
||||
* @return the calculated {@link Range}
|
||||
*/
|
||||
public static Range getRange(
|
||||
String prefix, String test, int queryInt, int minValue, int maxValue) {
|
||||
int min;
|
||||
int max;
|
||||
switch (test) {
|
||||
case "=":
|
||||
default:
|
||||
min = max = queryInt;
|
||||
break;
|
||||
case ">":
|
||||
min = Ints.saturatedCast(queryInt + 1L);
|
||||
max = maxValue;
|
||||
break;
|
||||
case ">=":
|
||||
min = queryInt;
|
||||
max = maxValue;
|
||||
break;
|
||||
case "<":
|
||||
min = minValue;
|
||||
max = Ints.saturatedCast(queryInt - 1L);
|
||||
break;
|
||||
case "<=":
|
||||
min = minValue;
|
||||
max = queryInt;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Range(prefix, min, max);
|
||||
}
|
||||
|
||||
private static Integer value(String value) {
|
||||
if (value.startsWith("+")) {
|
||||
value = value.substring(1);
|
||||
}
|
||||
return Ints.tryParse(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.index.query;
|
||||
|
||||
import com.google.gerrit.index.FieldDef;
|
||||
|
||||
public abstract class RegexPredicate<I> extends IndexPredicate<I> {
|
||||
protected RegexPredicate(FieldDef<I, ?> def, String value) {
|
||||
super(def, value);
|
||||
}
|
||||
|
||||
protected RegexPredicate(FieldDef<I, ?> def, String name, String value) {
|
||||
super(def, name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.index.query;
|
||||
|
||||
import com.google.gerrit.index.FieldDef;
|
||||
import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
|
||||
// TODO: Migrate this to IntegerRangePredicate
|
||||
public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
|
||||
protected static Timestamp parse(String value) throws QueryParseException {
|
||||
try {
|
||||
return JavaSqlTimestampHelper.parseTimestamp(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// parseTimestamp's errors are specific and helpful, so preserve them.
|
||||
throw new QueryParseException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected TimestampRangePredicate(FieldDef<I, Timestamp> def, String name, String value) {
|
||||
super(def, name, value);
|
||||
}
|
||||
|
||||
public abstract Date getMinTimestamp();
|
||||
|
||||
public abstract Date getMaxTimestamp();
|
||||
}
|
||||
Reference in New Issue
Block a user