package org.radargun.service;
import java.util.Comparator;
import com.hazelcast.query.PagingPredicate;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.Predicates;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.traits.Query;
import org.radargun.traits.Queryable;
/**
* Hazelcast has a limited support for aggregations - only a single aggregation can be executed per query, without any grouping,
* and because of an existing bug, also without filtering:
* http://stackoverflow.com/questions/29481508/hazelcast-aggregations-api-results-in-classcastexception-with-predicates
* Additionaly, indexes are not used in aggregations.
*
* @author Radim Vansa <rvansa@redhat.com>
* @author Jakub Markos <jmarkos@redhat.com>
*/
public class HazelcastQueryable implements Queryable {
private static final Log log = LogFactory.getLog(HazelcastQueryable.class);
protected final Hazelcast36Service service;
public HazelcastQueryable(Hazelcast36Service service) {
this.service = service;
}
@Override
public Query.Builder getBuilder(String mapName, Class<?> clazz) {
return new HazelcastQueryBuilder(clazz);
}
@Override
public Query.Context createContext(String containerName) {
return new HazelcastQuery.Context(service.getMap(containerName));
}
@Override
public void reindex(String containerName) {
// noop
}
private static class HazelcastQueryBuilder implements Query.Builder {
private final Class<?> clazz;
private Predicate predicate;
private Comparator comparator;
private int limit = -1;
private int offset = 0;
private Query.SelectExpression[] projection;
private HazelcastQueryBuilder(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public Query.Builder subquery() {
return new HazelcastQueryBuilder(clazz);
}
private void implicitAnd(Predicate p) {
if (predicate == null) {
predicate = p;
} else {
predicate = Predicates.and(predicate, p);
}
}
@Override
public Query.Builder eq(Query.SelectExpression selectExpression, Object value) {
implicitAnd(Predicates.equal(selectExpression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder lt(Query.SelectExpression selectExpression, Object value) {
implicitAnd(Predicates.lessThan(selectExpression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder le(Query.SelectExpression selectExpression, Object value) {
implicitAnd(Predicates.lessEqual(selectExpression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder gt(Query.SelectExpression selectExpression, Object value) {
implicitAnd(Predicates.greaterThan(selectExpression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder ge(Query.SelectExpression selectExpression, Object value) {
implicitAnd(Predicates.greaterEqual(selectExpression.attribute, (Comparable) value));
return this;
}
@Override
public Query.Builder between(Query.SelectExpression selectExpression, Object lowerBound, boolean lowerInclusive, Object upperBound, boolean upperInclusive) {
if (!lowerInclusive || !upperInclusive)
throw new IllegalArgumentException("Hazelcast supports only inclusive bounds");
implicitAnd(Predicates.between(selectExpression.attribute, (Comparable) lowerBound, (Comparable) upperBound));
return this;
}
@Override
public Query.Builder isNull(Query.SelectExpression selectExpression) {
implicitAnd(Predicates.equal(selectExpression.attribute, null));
return this;
}
@Override
public Query.Builder like(Query.SelectExpression selectExpression, String pattern) {
implicitAnd(Predicates.like(selectExpression.attribute, pattern));
return this;
}
@Override
public Query.Builder contains(Query.SelectExpression selectExpression, Object value) {
throw new UnsupportedOperationException();
}
@Override
public Query.Builder not(Query.Builder subquery) {
implicitAnd(Predicates.not(((HazelcastQueryBuilder) subquery).predicate));
return this;
}
@Override
public Query.Builder any(Query.Builder... subqueries) {
Predicate p = null;
for (Query.Builder subquery : subqueries) {
if (p == null) {
p = ((HazelcastQueryBuilder) subquery).predicate;
} else {
p = Predicates.or(p, ((HazelcastQueryBuilder) subquery).predicate);
}
}
implicitAnd(p);
return this;
}
@Override
public Query.Builder orderBy(Query.SelectExpression selectExpression) {
if (!selectExpression.asc) {
comparator = new HazelcastQuery.InverseComparator(selectExpression.attribute);
} else {
comparator = new HazelcastQuery.RegularComparator(selectExpression.attribute);
}
return this;
}
@Override
public Query.Builder projection(Query.SelectExpression... selectExpressions) {
log.warn("Projection is emulated; no native support for projection.");
for (Query.SelectExpression selectExpression : selectExpressions) {
if (selectExpression.function != Query.AggregationFunction.NONE && selectExpressions.length != 1)
throw new RuntimeException("Hazelcast only supports a single aggregation per query!");
}
this.projection = selectExpressions;
return this;
}
@Override
public Query.Builder groupBy(String[] attribute) {
throw new UnsupportedOperationException();
}
@Override
public Query.Builder offset(long offset) {
log.warn("Offset is emulated; first records will be loaded anyway.");
this.offset = (int) offset;
return this;
}
@Override
public Query.Builder limit(long limit) {
this.limit = (int) limit;
return this;
}
@Override
public Query build() {
if (projection != null && projection.length == 1 && projection[0].function != Query.AggregationFunction.NONE) {
return new HazelcastAggregationQuery(clazz, predicate, projection[0]);
}
Predicate finalPredicate;
if (comparator == null) {
if (limit < 0) finalPredicate = predicate;
else finalPredicate = new PagingPredicate(predicate, limit);
} else {
if (limit < 0) finalPredicate = new PagingPredicate(predicate, comparator, Integer.MAX_VALUE);
else finalPredicate = new PagingPredicate(predicate, comparator, limit);
}
String[] stringProjection = new String[projection.length];
for (int i = 0; i < projection.length; i++) {
stringProjection[i] = projection[i].attribute;
}
return new HazelcastQuery(finalPredicate, offset, stringProjection);
}
}
}