163 lines
6.3 KiB
Java
163 lines
6.3 KiB
Java
// 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.elasticsearch;
|
|
|
|
import com.google.gerrit.elasticsearch.builders.BoolQueryBuilder;
|
|
import com.google.gerrit.elasticsearch.builders.QueryBuilder;
|
|
import com.google.gerrit.elasticsearch.builders.QueryBuilders;
|
|
import com.google.gerrit.server.index.FieldDef;
|
|
import com.google.gerrit.server.index.FieldType;
|
|
import com.google.gerrit.server.index.IndexPredicate;
|
|
import com.google.gerrit.server.index.IntegerRangePredicate;
|
|
import com.google.gerrit.server.index.RegexPredicate;
|
|
import com.google.gerrit.server.index.TimestampRangePredicate;
|
|
import com.google.gerrit.server.query.AndPredicate;
|
|
import com.google.gerrit.server.query.NotPredicate;
|
|
import com.google.gerrit.server.query.OrPredicate;
|
|
import com.google.gerrit.server.query.PostFilterPredicate;
|
|
import com.google.gerrit.server.query.Predicate;
|
|
import com.google.gerrit.server.query.QueryParseException;
|
|
import com.google.gerrit.server.query.change.AfterPredicate;
|
|
import java.time.Instant;
|
|
|
|
public class ElasticQueryBuilder {
|
|
|
|
protected <T> QueryBuilder toQueryBuilder(Predicate<T> p) throws QueryParseException {
|
|
if (p instanceof AndPredicate) {
|
|
return and(p);
|
|
} else if (p instanceof OrPredicate) {
|
|
return or(p);
|
|
} else if (p instanceof NotPredicate) {
|
|
return not(p);
|
|
} else if (p instanceof IndexPredicate) {
|
|
return fieldQuery((IndexPredicate<T>) p);
|
|
} else if (p instanceof PostFilterPredicate) {
|
|
return QueryBuilders.matchAllQuery();
|
|
} else {
|
|
throw new QueryParseException("cannot create query for index: " + p);
|
|
}
|
|
}
|
|
|
|
private <T> BoolQueryBuilder and(Predicate<T> p) throws QueryParseException {
|
|
BoolQueryBuilder b = QueryBuilders.boolQuery();
|
|
for (Predicate<T> c : p.getChildren()) {
|
|
b.must(toQueryBuilder(c));
|
|
}
|
|
return b;
|
|
}
|
|
|
|
private <T> BoolQueryBuilder or(Predicate<T> p) throws QueryParseException {
|
|
BoolQueryBuilder q = QueryBuilders.boolQuery();
|
|
for (Predicate<T> c : p.getChildren()) {
|
|
q.should(toQueryBuilder(c));
|
|
}
|
|
return q;
|
|
}
|
|
|
|
private <T> QueryBuilder not(Predicate<T> p) throws QueryParseException {
|
|
Predicate<T> n = p.getChild(0);
|
|
if (n instanceof TimestampRangePredicate) {
|
|
return notTimestamp((TimestampRangePredicate<T>) n);
|
|
}
|
|
|
|
// Lucene does not support negation, start with all and subtract.
|
|
BoolQueryBuilder q = QueryBuilders.boolQuery();
|
|
q.must(QueryBuilders.matchAllQuery());
|
|
q.mustNot(toQueryBuilder(n));
|
|
return q;
|
|
}
|
|
|
|
private <T> QueryBuilder fieldQuery(IndexPredicate<T> p) throws QueryParseException {
|
|
FieldType<?> type = p.getType();
|
|
FieldDef<?, ?> field = p.getField();
|
|
String name = field.getName();
|
|
String value = p.getValue();
|
|
|
|
if (type == FieldType.INTEGER) {
|
|
// QueryBuilder encodes integer fields as prefix coded bits,
|
|
// which elasticsearch's queryString can't handle.
|
|
// Create integer terms with string representations instead.
|
|
return QueryBuilders.termQuery(name, value);
|
|
} else if (type == FieldType.INTEGER_RANGE) {
|
|
return intRangeQuery(p);
|
|
} else if (type == FieldType.TIMESTAMP) {
|
|
return timestampQuery(p);
|
|
} else if (type == FieldType.EXACT) {
|
|
return exactQuery(p);
|
|
} else if (type == FieldType.PREFIX) {
|
|
return QueryBuilders.matchPhrasePrefixQuery(name, value);
|
|
} else if (type == FieldType.FULL_TEXT) {
|
|
return QueryBuilders.matchPhraseQuery(name, value);
|
|
} else {
|
|
throw FieldType.badFieldType(p.getType());
|
|
}
|
|
}
|
|
|
|
private <T> QueryBuilder intRangeQuery(IndexPredicate<T> p) throws QueryParseException {
|
|
if (p instanceof IntegerRangePredicate) {
|
|
IntegerRangePredicate<T> r = (IntegerRangePredicate<T>) p;
|
|
int minimum = r.getMinimumValue();
|
|
int maximum = r.getMaximumValue();
|
|
if (minimum == maximum) {
|
|
// Just fall back to a standard integer query.
|
|
return QueryBuilders.termQuery(p.getField().getName(), minimum);
|
|
}
|
|
return QueryBuilders.rangeQuery(p.getField().getName()).gte(minimum).lte(maximum);
|
|
}
|
|
throw new QueryParseException("not an integer range: " + p);
|
|
}
|
|
|
|
private <T> QueryBuilder notTimestamp(TimestampRangePredicate<T> r) throws QueryParseException {
|
|
if (r.getMinTimestamp().getTime() == 0) {
|
|
return QueryBuilders.rangeQuery(r.getField().getName())
|
|
.gt(Instant.ofEpochMilli(r.getMaxTimestamp().getTime()));
|
|
}
|
|
throw new QueryParseException("cannot negate: " + r);
|
|
}
|
|
|
|
private <T> QueryBuilder timestampQuery(IndexPredicate<T> p) throws QueryParseException {
|
|
if (p instanceof TimestampRangePredicate) {
|
|
TimestampRangePredicate<T> r = (TimestampRangePredicate<T>) p;
|
|
if (p instanceof AfterPredicate) {
|
|
return QueryBuilders.rangeQuery(r.getField().getName())
|
|
.gte(Instant.ofEpochMilli(r.getMinTimestamp().getTime()));
|
|
}
|
|
return QueryBuilders.rangeQuery(r.getField().getName())
|
|
.gte(Instant.ofEpochMilli(r.getMinTimestamp().getTime()))
|
|
.lte(Instant.ofEpochMilli(r.getMaxTimestamp().getTime()));
|
|
}
|
|
throw new QueryParseException("not a timestamp: " + p);
|
|
}
|
|
|
|
private <T> QueryBuilder exactQuery(IndexPredicate<T> p) {
|
|
String name = p.getField().getName();
|
|
String value = p.getValue();
|
|
|
|
if (value.isEmpty()) {
|
|
return new BoolQueryBuilder().mustNot(QueryBuilders.existsQuery(name));
|
|
} else if (p instanceof RegexPredicate) {
|
|
if (value.startsWith("^")) {
|
|
value = value.substring(1);
|
|
}
|
|
if (value.endsWith("$") && !value.endsWith("\\$") && !value.endsWith("\\\\$")) {
|
|
value = value.substring(0, value.length() - 1);
|
|
}
|
|
return QueryBuilders.regexpQuery(name + ".key", value);
|
|
} else {
|
|
return QueryBuilders.termQuery(name + ".key", value);
|
|
}
|
|
}
|
|
}
|