net.e6tech.elements.cassandra.query.BaseQuery Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2015-2020 Futeh Kao
*
* 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 net.e6tech.elements.cassandra.query;
import net.e6tech.elements.cassandra.Sibyl;
import net.e6tech.elements.cassandra.etl.Inspector;
import net.e6tech.elements.cassandra.generator.KeyColumn;
import net.e6tech.elements.cassandra.generator.TableGenerator;
import net.e6tech.elements.common.interceptor.CallFrame;
import net.e6tech.elements.common.interceptor.Interceptor;
import net.e6tech.elements.common.interceptor.InterceptorHandler;
import net.e6tech.elements.common.reflection.Primitives;
import net.e6tech.elements.common.reflection.Reflection;
import net.e6tech.elements.common.util.datastructure.Triplet;
import java.beans.PropertyDescriptor;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@SuppressWarnings("unchecked")
public abstract class BaseQuery> {
protected static final String AND = " and ";
protected Sibyl sibyl;
protected T partitionTemplate;
protected T clusteringTemplate;
protected T orderByTemplate;
protected RelationHandler partitionHandler;
protected RelationHandler clusteringHandler;
protected OrderByHandler orderByHandler;
protected List partitionRelations = new ArrayList<>();
protected List orderBy = new ArrayList<>();
protected List clusteringRelations = new ArrayList<>();
protected int limit = -1;
protected Class entityClass;
protected TableGenerator table;
protected Inspector inspector;
public BaseQuery(Sibyl sibyl, Class entityClass) {
this.sibyl = sibyl;
this.entityClass = entityClass;
this.partitionHandler = new RelationHandler(true);
this.clusteringHandler = new RelationHandler(false);
this.orderByHandler = new OrderByHandler();
partitionTemplate = Interceptor.getInstance().newInstance(entityClass, partitionHandler );
clusteringTemplate = Interceptor.getInstance().newInstance(entityClass, clusteringHandler);
orderByTemplate = Interceptor.getInstance().newInstance(entityClass, orderByHandler);
table = sibyl.getGenerator().getTable(null, entityClass);
inspector = sibyl.getInspector(entityClass);
}
public Q limit(int limit) {
this.limit = limit;
return (Q) this;
}
public int limit() {
return limit;
}
protected Q newRelation(BiConsumer consumer, R value, Comparison comparison, List list, boolean partition) {
T template = partition ? partitionTemplate : clusteringTemplate;
RelationHandler relationHandler = partition ? partitionHandler : clusteringHandler;
consumer.accept(template, value);
Relation relation = new Relation(relationHandler.keyColumn, comparison, value);
list.removeIf(r -> r.comparison == comparison && r.keyColumn.getPosition() == relation.keyColumn.getPosition());
list.add(relation);
return (Q) this;
}
public Q partition(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.EQUAL, partitionRelations, true);
}
public Q equalTo(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.EQUAL, clusteringRelations, false);
}
protected Q newOrderBy(Consumer consumer, Comparison comparison) {
orderByHandler.keyColumnValues.clear();
consumer.accept(orderByTemplate);
for (KeyColumnValue keyColumnValue : orderByHandler.keyColumnValues) {
KeyColumn keyColumn = keyColumnValue.keyColumn;
Relation relation = new Relation(keyColumn, comparison, null);
orderBy.removeIf(o -> o.keyColumn.getPosition() == relation.keyColumn.getPosition());
orderBy.add(relation);
}
orderByHandler.keyColumnValues.clear();
orderBy.forEach(o -> o.comparison = comparison);
return (Q) this;
}
protected Q lessThan(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.LESS_THAN, clusteringRelations, false);
}
protected Q lessThanOrEqualTo(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.LESS_THAN_OR_EQUAL, clusteringRelations, false);
}
protected Q greaterThan(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.GREATER_THAN, clusteringRelations, false);
}
protected Q greaterThanOrEqualTo(BiConsumer consumer, R value) {
return newRelation(consumer, value, Comparison.GREATER_THAN_OR_EQUAL, clusteringRelations, false);
}
public > Q ascending(BiConsumer consumer, R from, R to) {
if (from.compareTo(to) < 0) {
greaterThan(consumer, from);
lessThanOrEqualTo(consumer, to);
} else {
greaterThan(consumer, to);
lessThanOrEqualTo(consumer, from);
}
return newOrderBy(t -> consumer.accept(t, from), Comparison.LESS_THAN);
}
public > Q descending(BiConsumer consumer, R from, R to) {
if (from.compareTo(to) < 0) {
lessThan(consumer, to);
greaterThanOrEqualTo(consumer, from);
} else {
lessThan(consumer, from);
greaterThanOrEqualTo(consumer, to);
}
return newOrderBy(t -> consumer.accept(t, from), Comparison.GREATER_THAN);
}
public Q ascending(Consumer consumer) {
return newOrderBy(consumer, Comparison.LESS_THAN);
}
public Q descending(Consumer consumer) {
return newOrderBy(consumer, Comparison.GREATER_THAN);
}
protected Q clearOrderBy() {
return (Q) this;
}
protected void buildRelation(StringBuilder builder, Map map, Relation relation) {
builder.append(relation.keyColumn.getName());
switch (relation.comparison) {
case EQUAL:
builder.append(" = ");
break;
case LESS_THAN:
builder.append(" < ");
break;
case LESS_THAN_OR_EQUAL:
builder.append(" <= ");
break;
case GREATER_THAN:
builder.append(" > ");
break;
case GREATER_THAN_OR_EQUAL:
builder.append(" >= ");
break;
}
if (relation.value == null) {
throw new IllegalArgumentException("comparision value for " + relation.keyColumn.getName() + " cannot be null");
} else {
String argument = relation.keyColumn.getName() + "_" + (map.size() + 1);
builder.append(":").append(argument);
map.put(argument, relation.value);
}
}
protected void buildPartitionKeys(StringBuilder builder, Map map) {
boolean first = true;
for (Relation relation : partitionRelations) {
if (first) {
first = false;
} else {
builder.append(AND);
}
buildRelation(builder, map, relation);
}
}
protected void buildClusteringKeys(StringBuilder builder, Map map) {
boolean first = true;
for (Relation relation : clusteringRelations) {
if (first) {
first = false;
} else {
builder.append(AND);
}
buildRelation(builder, map, relation);
}
}
protected void buildOrderBy(StringBuilder builder) {
boolean first = true;
for (Relation relation : orderBy) {
if (first) {
first = false;
builder.append(" order by ");
} else {
builder.append(", ");
}
builder.append(relation.keyColumn.getName());
if (relation.comparison == Comparison.LESS_THAN) {
builder.append(" asc ");
} else if (relation.comparison == Comparison.GREATER_THAN) {
builder.append(" desc ");
} else {
throw new IllegalStateException();
}
}
}
protected void validate() {
partitionRelations.sort(Comparator.comparingInt(c -> c.keyColumn.getPosition()));
clusteringRelations.sort(Comparator.comparingInt(c -> c.keyColumn.getPosition()));
orderBy.sort(Comparator.comparingInt(c -> c.keyColumn.getPosition()));
validatePartitionKeys();
validClusteringKeys();
}
protected List select() {
Map map = new HashMap<>();
StringBuilder query = buildQuery(map);
return sibyl.all(entityClass, query.toString(), map);
}
protected StringBuilder buildQuery(Map map) {
StringBuilder query = new StringBuilder("select * from ");
query.append(table.getTableName())
.append(" where ");
buildPartitionKeys(query, map);
if (!partitionRelations.isEmpty() && !clusteringRelations.isEmpty())
query.append(BaseQuery.AND);
buildClusteringKeys(query, map);
buildOrderBy(query);
if (limit > 0) {
query.append(" ").append("limit ").append(limit);
}
return query;
}
// make sure all partition keys are specified.
protected void validatePartitionKeys() {
for (KeyColumn key : table.getPartitionKeys()) {
boolean found = false;
for (Relation r : partitionRelations) {
if (r.keyColumn.getName().equals(key.getName())) {
found = true;
break;
}
}
if (!found)
throw new IllegalArgumentException("Partition key " + key.getName() + " not specified.");
}
}
// make sure clustering keys are specified based on some rules
protected void validClusteringKeys() {
Relation prev = null;
for (Relation relation : clusteringRelations) {
if (prev != null && prev.keyColumn.getPosition() != relation.keyColumn.getPosition() && prev.comparison != Comparison.EQUAL) {
throw new IllegalArgumentException("Clustering key column " + prev.keyColumn.getName() + " comparison must be '='");
}
if (prev != null && prev.keyColumn.getPosition() != relation.keyColumn.getPosition() && relation.keyColumn.getPosition() - prev.keyColumn.getPosition() != 1) {
throw new IllegalArgumentException("Missing relation for clustering key column " + (relation.keyColumn.getPosition() - 1));
}
prev = relation;
}
}
protected enum Comparison {
EQUAL,
LESS_THAN,
LESS_THAN_OR_EQUAL,
GREATER_THAN,
GREATER_THAN_OR_EQUAL;
}
protected class Relation {
Comparison comparison;
KeyColumn keyColumn;
Object value;
Inspector.ColumnAccessor accessor;
public Relation(KeyColumn keyColumn, Comparison comparison, Object value) {
this.keyColumn = keyColumn;
this.comparison = comparison;
this.value = value;
this.accessor = inspector.getColumn(keyColumn.getName());
}
public boolean isRelated(T t, T t2) {
Object v1 = accessor.get(t);
Object v2 = accessor.get(t2);
return Objects.equals(v1, v2);
}
}
protected class KeyColumnValue {
KeyColumn keyColumn;
Object value;
public KeyColumnValue(KeyColumn keyColumn, Object value) {
this.keyColumn = keyColumn;
this.value = value;
}
}
private Triplet