/*
* 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.facebook.presto.cassandra;
import com.facebook.presto.cassandra.util.CassandraCqlUtils;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.predicate.Domain;
import com.facebook.presto.spi.predicate.Range;
import com.facebook.presto.spi.predicate.TupleDomain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.toCQLCompatibleString;
import static com.google.common.collect.Sets.cartesianProduct;
import static java.util.Objects.requireNonNull;
public class CassandraClusteringPredicatesExtractor
{
private final List<CassandraColumnHandle> clusteringColumns;
private final TupleDomain<ColumnHandle> predicates;
private final ClusteringPushDownResult clusteringPushDownResult;
public CassandraClusteringPredicatesExtractor(List<CassandraColumnHandle> clusteringColumns, TupleDomain<ColumnHandle> predicates)
{
this.clusteringColumns = ImmutableList.copyOf(requireNonNull(clusteringColumns, "clusteringColumns is null"));
this.predicates = requireNonNull(predicates, "predicates is null");
this.clusteringPushDownResult = getClusteringKeysSet(clusteringColumns, predicates);
}
public List<String> getClusteringKeyPredicates()
{
Set<List<Object>> pushedDownDomainValues = clusteringPushDownResult.getDomainValues();
if (pushedDownDomainValues.isEmpty()) {
return ImmutableList.of();
}
ImmutableList.Builder<String> clusteringPredicates = ImmutableList.builder();
for (List<Object> clusteringKeys : pushedDownDomainValues) {
if (clusteringKeys.isEmpty()) {
continue;
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < clusteringKeys.size(); i++) {
if (i > 0) {
stringBuilder.append(" AND ");
}
stringBuilder.append(CassandraCqlUtils.validColumnName(clusteringColumns.get(i).getName()));
stringBuilder.append(" = ");
stringBuilder.append(CassandraCqlUtils.cqlValue(toCQLCompatibleString(clusteringKeys.get(i)), clusteringColumns.get(i).getCassandraType()));
}
clusteringPredicates.add(stringBuilder.toString());
}
return clusteringPredicates.build();
}
public TupleDomain<ColumnHandle> getUnenforcedConstraints()
{
Map<ColumnHandle, Domain> pushedDown = clusteringPushDownResult.getDomains();
Map<ColumnHandle, Domain> notPushedDown = new HashMap<>(predicates.getDomains().get());
if (!notPushedDown.isEmpty() && !pushedDown.isEmpty()) {
notPushedDown.entrySet().removeAll(pushedDown.entrySet());
}
return TupleDomain.withColumnDomains(notPushedDown);
}
private static ClusteringPushDownResult getClusteringKeysSet(List<CassandraColumnHandle> clusteringColumns, TupleDomain<ColumnHandle> predicates)
{
ImmutableMap.Builder<ColumnHandle, Domain> domainsBuilder = ImmutableMap.builder();
ImmutableList.Builder<Set<Object>> clusteringColumnValues = ImmutableList.builder();
for (CassandraColumnHandle columnHandle : clusteringColumns) {
Domain domain = predicates.getDomains().get().get(columnHandle);
if (domain == null) {
break;
}
if (domain.isNullAllowed()) {
return new ClusteringPushDownResult(domainsBuilder.build(), ImmutableSet.of());
}
Set<Object> values = domain.getValues().getValuesProcessor().transform(
ranges -> {
ImmutableSet.Builder<Object> columnValues = ImmutableSet.builder();
for (Range range : ranges.getOrderedRanges()) {
if (!range.isSingleValue()) {
return ImmutableSet.of();
}
/* TODO add code to handle a range of values for the last column
* Prior to Cassandra 2.2, only the last clustering column can have a range of values
* Take a look at how this is done in PreparedStatementBuilder.java
*/
Object value = range.getSingleValue();
CassandraType valueType = columnHandle.getCassandraType();
columnValues.add(valueType.validateClusteringKey(value));
}
return columnValues.build();
},
discreteValues -> {
if (discreteValues.isWhiteList()) {
return ImmutableSet.copyOf(discreteValues.getValues());
}
return ImmutableSet.of();
},
allOrNone -> ImmutableSet.of());
if (!values.isEmpty()) {
clusteringColumnValues.add(values);
domainsBuilder.put(columnHandle, domain);
}
}
return new ClusteringPushDownResult(domainsBuilder.build(), cartesianProduct(clusteringColumnValues.build()));
}
private static class ClusteringPushDownResult
{
private final Map<ColumnHandle, Domain> domains;
private final Set<List<Object>> domainValues;
public ClusteringPushDownResult(Map<ColumnHandle, Domain> domains, Set<List<Object>> domainValues)
{
this.domains = requireNonNull(ImmutableMap.copyOf(domains));
this.domainValues = requireNonNull(ImmutableSet.copyOf(domainValues));
}
public Map<ColumnHandle, Domain> getDomains()
{
return domains;
}
public Set<List<Object>> getDomainValues()
{
return domainValues;
}
}
}