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:
Dave Borowitz
2017-08-08 09:53:39 -04:00
parent 94f077b5d2
commit e47be68709
156 changed files with 424 additions and 363 deletions

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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 + "\"";
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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>
* &#064;Operator
* public Predicate is(String value) {
* if (&quot;starred&quot;.equals(value)) {
* return new StarredPredicate();
* }
* if (&quot;unread&quot;.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());
}
}
}
}

View File

@@ -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();
}

View File

@@ -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 &gt;, &gt;=, =, &lt;, or &lt;=
* @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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}