/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cassandra.cql3.restrictions; import java.nio.ByteBuffer; import java.util.*; import com.google.common.base.Joiner; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.cql3.statements.StatementType; import org.apache.cassandra.db.*; import org.apache.cassandra.db.filter.RowFilter; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.dht.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.index.Index; import org.apache.cassandra.index.SecondaryIndexManager; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.btree.BTreeSet; import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; /** * The restrictions corresponding to the relations specified on the where-clause of CQL query. */ public final class StatementRestrictions { public static final String REQUIRES_ALLOW_FILTERING_MESSAGE = "Cannot execute this query as it might involve data filtering and " + "thus may have unpredictable performance. If you want to execute " + "this query despite the performance unpredictability, use ALLOW FILTERING"; /** * The type of statement */ private final StatementType type; /** * The Column Family meta data */ public final TableMetadata table; /** * Restrictions on partitioning columns */ private PartitionKeyRestrictions partitionKeyRestrictions; /** * Restrictions on clustering columns */ private ClusteringColumnRestrictions clusteringColumnsRestrictions; /** * Restriction on non-primary key columns (i.e. secondary index restrictions) */ private RestrictionSet nonPrimaryKeyRestrictions; private Set<ColumnMetadata> notNullColumns; /** * The restrictions used to build the row filter */ private final IndexRestrictions filterRestrictions = new IndexRestrictions(); /** * <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise */ private boolean usesSecondaryIndexing; /** * Specify if the query will return a range of partition keys. */ private boolean isKeyRange; /** * <code>true</code> if nonPrimaryKeyRestrictions contains restriction on a regular column, * <code>false</code> otherwise. */ private boolean hasRegularColumnsRestrictions; /** * Creates a new empty <code>StatementRestrictions</code>. * * @param type the type of statement * @param table the column family meta data * @return a new empty <code>StatementRestrictions</code>. */ public static StatementRestrictions empty(StatementType type, TableMetadata table) { return new StatementRestrictions(type, table, false); } private StatementRestrictions(StatementType type, TableMetadata table, boolean allowFiltering) { this.type = type; this.table = table; this.partitionKeyRestrictions = new PartitionKeySingleRestrictionSet(table.partitionKeyAsClusteringComparator()); this.clusteringColumnsRestrictions = new ClusteringColumnRestrictions(table, allowFiltering); this.nonPrimaryKeyRestrictions = new RestrictionSet(); this.notNullColumns = new HashSet<>(); } public StatementRestrictions(StatementType type, TableMetadata table, WhereClause whereClause, VariableSpecifications boundNames, boolean selectsOnlyStaticColumns, boolean allowFiltering, boolean forView) { this(type, table, allowFiltering); ColumnFamilyStore cfs; SecondaryIndexManager secondaryIndexManager = null; if (type.allowUseOfSecondaryIndices()) { cfs = Keyspace.open(table.keyspace).getColumnFamilyStore(table.name); secondaryIndexManager = cfs.indexManager; } /* * WHERE clause. For a given entity, rules are: * - EQ relation conflicts with anything else (including a 2nd EQ) * - Can't have more than one LT(E) relation (resp. GT(E) relation) * - IN relation are restricted to row keys (for now) and conflicts with anything else (we could * allow two IN for the same entity but that doesn't seem very useful) * - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value * in CQL so far) */ for (Relation relation : whereClause.relations) { if (relation.operator() == Operator.IS_NOT) { if (!forView) throw new InvalidRequestException("Unsupported restriction: " + relation); for (ColumnMetadata def : relation.toRestriction(table, boundNames).getColumnDefs()) this.notNullColumns.add(def); } else if (relation.isLIKE()) { Restriction restriction = relation.toRestriction(table, boundNames); if (!type.allowUseOfSecondaryIndices() || !restriction.hasSupportingIndex(secondaryIndexManager)) throw new InvalidRequestException(String.format("LIKE restriction is only supported on properly " + "indexed columns. %s is not valid.", relation.toString())); addRestriction(restriction); } else { addRestriction(relation.toRestriction(table, boundNames)); } } hasRegularColumnsRestrictions = nonPrimaryKeyRestrictions.hasRestrictionFor(ColumnMetadata.Kind.REGULAR); boolean hasQueriableClusteringColumnIndex = false; boolean hasQueriableIndex = false; if (type.allowUseOfSecondaryIndices()) { if (whereClause.containsCustomExpressions()) processCustomIndexExpressions(whereClause.expressions, boundNames, secondaryIndexManager); hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager); hasQueriableIndex = !filterRestrictions.getCustomIndexExpressions().isEmpty() || hasQueriableClusteringColumnIndex || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager) || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager); } // At this point, the select statement if fully constructed, but we still have a few things to validate processPartitionKeyRestrictions(hasQueriableIndex, allowFiltering, forView); // Some but not all of the partition key columns have been specified; // hence we need turn these restrictions into a row filter. if (usesSecondaryIndexing || partitionKeyRestrictions.needFiltering(table)) filterRestrictions.add(partitionKeyRestrictions); if (selectsOnlyStaticColumns && hasClusteringColumnsRestrictions()) { // If the only updated/deleted columns are static, then we don't need clustering columns. // And in fact, unless it is an INSERT, we reject if clustering colums are provided as that // suggest something unintended. For instance, given: // CREATE TABLE t (k int, v int, s int static, PRIMARY KEY (k, v)) // it can make sense to do: // INSERT INTO t(k, v, s) VALUES (0, 1, 2) // but both // UPDATE t SET s = 3 WHERE k = 0 AND v = 1 // DELETE v FROM t WHERE k = 0 AND v = 1 // sounds like you don't really understand what your are doing. if (type.isDelete() || type.isUpdate()) throw invalidRequest("Invalid restrictions on clustering columns since the %s statement modifies only static columns", type); if (type.isSelect()) throw invalidRequest("Cannot restrict clustering columns when selecting only static columns"); } processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, forView, allowFiltering); // Covers indexes on the first clustering column (among others). if (isKeyRange && hasQueriableClusteringColumnIndex) usesSecondaryIndexing = true; if (usesSecondaryIndexing || clusteringColumnsRestrictions.needFiltering()) filterRestrictions.add(clusteringColumnsRestrictions); // Even if usesSecondaryIndexing is false at this point, we'll still have to use one if // there is restrictions not covered by the PK. if (!nonPrimaryKeyRestrictions.isEmpty()) { if (!type.allowNonPrimaryKeyInWhereClause()) { Collection<ColumnIdentifier> nonPrimaryKeyColumns = ColumnMetadata.toIdentifiers(nonPrimaryKeyRestrictions.getColumnDefs()); throw invalidRequest("Non PRIMARY KEY columns found in where clause: %s ", Joiner.on(", ").join(nonPrimaryKeyColumns)); } if (hasQueriableIndex) usesSecondaryIndexing = true; else if (!allowFiltering) throw invalidRequest(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE); filterRestrictions.add(nonPrimaryKeyRestrictions); } if (usesSecondaryIndexing) validateSecondaryIndexSelections(selectsOnlyStaticColumns); } private void addRestriction(Restriction restriction) { ColumnMetadata def = restriction.getFirstColumn(); if (def.isPartitionKey()) partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction); else if (def.isClusteringColumn()) clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction); else nonPrimaryKeyRestrictions = nonPrimaryKeyRestrictions.addRestriction((SingleRestriction) restriction); } public void addFunctionsTo(List<Function> functions) { partitionKeyRestrictions.addFunctionsTo(functions); clusteringColumnsRestrictions.addFunctionsTo(functions); nonPrimaryKeyRestrictions.addFunctionsTo(functions); } // may be used by QueryHandler implementations public IndexRestrictions getIndexRestrictions() { return filterRestrictions; } /** * Returns the non-PK column that are restricted. If includeNotNullRestrictions is true, columns that are restricted * by an IS NOT NULL restriction will be included, otherwise they will not be included (unless another restriction * applies to them). */ public Set<ColumnMetadata> nonPKRestrictedColumns(boolean includeNotNullRestrictions) { Set<ColumnMetadata> columns = new HashSet<>(); for (Restrictions r : filterRestrictions.getRestrictions()) { for (ColumnMetadata def : r.getColumnDefs()) if (!def.isPrimaryKeyColumn()) columns.add(def); } if (includeNotNullRestrictions) { for (ColumnMetadata def : notNullColumns) { if (!def.isPrimaryKeyColumn()) columns.add(def); } } return columns; } /** * @return the set of columns that have an IS NOT NULL restriction on them */ public Set<ColumnMetadata> notNullColumns() { return notNullColumns; } /** * @return true if column is restricted by some restriction, false otherwise */ public boolean isRestricted(ColumnMetadata column) { if (notNullColumns.contains(column)) return true; return getRestrictions(column.kind).getColumnDefs().contains(column); } /** * Checks if the restrictions on the partition key has IN restrictions. * * @return <code>true</code> the restrictions on the partition key has an IN restriction, <code>false</code> * otherwise. */ public boolean keyIsInRelation() { return partitionKeyRestrictions.hasIN(); } /** * Checks if the query request a range of partition keys. * * @return <code>true</code> if the query request a range of partition keys, <code>false</code> otherwise. */ public boolean isKeyRange() { return this.isKeyRange; } /** * Checks if the specified column is restricted by an EQ restriction. * * @param columnDef the column definition * @return <code>true</code> if the specified column is restricted by an EQ restiction, <code>false</code> * otherwise. */ public boolean isColumnRestrictedByEq(ColumnMetadata columnDef) { Set<Restriction> restrictions = getRestrictions(columnDef.kind).getRestrictions(columnDef); return restrictions.stream() .filter(SingleRestriction.class::isInstance) .anyMatch(p -> ((SingleRestriction) p).isEQ()); } /** * Returns the <code>Restrictions</code> for the specified type of columns. * * @param kind the column type * @return the <code>Restrictions</code> for the specified type of columns */ private Restrictions getRestrictions(ColumnMetadata.Kind kind) { switch (kind) { case PARTITION_KEY: return partitionKeyRestrictions; case CLUSTERING: return clusteringColumnsRestrictions; default: return nonPrimaryKeyRestrictions; } } /** * Checks if the secondary index need to be queried. * * @return <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise. */ public boolean usesSecondaryIndexing() { return this.usesSecondaryIndexing; } private void processPartitionKeyRestrictions(boolean hasQueriableIndex, boolean allowFiltering, boolean forView) { if (!type.allowPartitionKeyRanges()) { checkFalse(partitionKeyRestrictions.isOnToken(), "The token function cannot be used in WHERE clauses for %s statements", type); if (partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table)) throw invalidRequest("Some partition key parts are missing: %s", Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents())); // slice query checkFalse(partitionKeyRestrictions.hasSlice(), "Only EQ and IN relation are supported on the partition key (unless you use the token() function)" + " for %s statements", type); } else { // If there are no partition restrictions or there's only token restriction, we have to set a key range if (partitionKeyRestrictions.isOnToken()) isKeyRange = true; if (partitionKeyRestrictions.isEmpty() && partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table)) { isKeyRange = true; usesSecondaryIndexing = hasQueriableIndex; } // If there is a queriable index, no special condition is required on the other restrictions. // But we still need to know 2 things: // - If we don't have a queriable index, is the query ok // - Is it queriable without 2ndary index, which is always more efficient // If a component of the partition key is restricted by a relation, all preceding // components must have a EQ. Only the last partition key component can be in IN relation. if (partitionKeyRestrictions.needFiltering(table)) { if (!allowFiltering && !forView && !hasQueriableIndex) throw new InvalidRequestException(REQUIRES_ALLOW_FILTERING_MESSAGE); if (partitionKeyRestrictions.hasIN()) throw new InvalidRequestException("IN restrictions are not supported when the query involves filtering"); isKeyRange = true; usesSecondaryIndexing = hasQueriableIndex; } } } public boolean hasPartitionKeyRestrictions() { return !partitionKeyRestrictions.isEmpty(); } /** * Checks if the restrictions contain any non-primary key restrictions * @return <code>true</code> if the restrictions contain any non-primary key restrictions, <code>false</code> otherwise. */ public boolean hasNonPrimaryKeyRestrictions() { return !nonPrimaryKeyRestrictions.isEmpty(); } /** * Returns the partition key components that are not restricted. * @return the partition key components that are not restricted. */ private Collection<ColumnIdentifier> getPartitionKeyUnrestrictedComponents() { List<ColumnMetadata> list = new ArrayList<>(table.partitionKeyColumns()); list.removeAll(partitionKeyRestrictions.getColumnDefs()); return ColumnMetadata.toIdentifiers(list); } /** * Checks if the restrictions on the partition key are token restrictions. * * @return <code>true</code> if the restrictions on the partition key are token restrictions, * <code>false</code> otherwise. */ public boolean isPartitionKeyRestrictionsOnToken() { return partitionKeyRestrictions.isOnToken(); } /** * Checks if restrictions on the clustering key have IN restrictions. * * @return <code>true</code> if the restrictions on the clustering key have IN restrictions, * <code>false</code> otherwise. */ public boolean clusteringKeyRestrictionsHasIN() { return clusteringColumnsRestrictions.hasIN(); } /** * Processes the clustering column restrictions. * * @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise * @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics, * <code>false</code> otherwise. */ private void processClusteringColumnsRestrictions(boolean hasQueriableIndex, boolean selectsOnlyStaticColumns, boolean forView, boolean allowFiltering) { checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.hasSlice(), "Slice restrictions are not supported on the clustering columns in %s statements", type); if (!type.allowClusteringColumnSlices() && (!table.isCompactTable() || (table.isCompactTable() && !hasClusteringColumnsRestrictions()))) { if (!selectsOnlyStaticColumns && hasUnrestrictedClusteringColumns()) throw invalidRequest("Some clustering keys are missing: %s", Joiner.on(", ").join(getUnrestrictedClusteringColumns())); } else { checkFalse(clusteringColumnsRestrictions.hasContains() && !hasQueriableIndex && !allowFiltering, "Clustering columns can only be restricted with CONTAINS with a secondary index or filtering"); if (hasClusteringColumnsRestrictions() && clusteringColumnsRestrictions.needFiltering()) { if (hasQueriableIndex || forView) { usesSecondaryIndexing = true; } else if (!allowFiltering) { List<ColumnMetadata> clusteringColumns = table.clusteringColumns(); List<ColumnMetadata> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()); for (int i = 0, m = restrictedColumns.size(); i < m; i++) { ColumnMetadata clusteringColumn = clusteringColumns.get(i); ColumnMetadata restrictedColumn = restrictedColumns.get(i); if (!clusteringColumn.equals(restrictedColumn)) { throw invalidRequest("PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted", restrictedColumn.name, clusteringColumn.name); } } } } } } /** * Returns the clustering columns that are not restricted. * @return the clustering columns that are not restricted. */ private Collection<ColumnIdentifier> getUnrestrictedClusteringColumns() { List<ColumnMetadata> missingClusteringColumns = new ArrayList<>(table.clusteringColumns()); missingClusteringColumns.removeAll(new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs())); return ColumnMetadata.toIdentifiers(missingClusteringColumns); } /** * Checks if some clustering columns are not restricted. * @return <code>true</code> if some clustering columns are not restricted, <code>false</code> otherwise. */ private boolean hasUnrestrictedClusteringColumns() { return table.clusteringColumns().size() != clusteringColumnsRestrictions.size(); } private void processCustomIndexExpressions(List<CustomIndexExpression> expressions, VariableSpecifications boundNames, SecondaryIndexManager indexManager) { if (expressions.size() > 1) throw new InvalidRequestException(IndexRestrictions.MULTIPLE_EXPRESSIONS); CustomIndexExpression expression = expressions.get(0); CFName cfName = expression.targetIndex.getCfName(); if (cfName.hasKeyspace() && !expression.targetIndex.getKeyspace().equals(table.keyspace)) throw IndexRestrictions.invalidIndex(expression.targetIndex, table); if (cfName.getColumnFamily() != null && !cfName.getColumnFamily().equals(table.name)) throw IndexRestrictions.invalidIndex(expression.targetIndex, table); if (!table.indexes.has(expression.targetIndex.getIdx())) throw IndexRestrictions.indexNotFound(expression.targetIndex, table); Index index = indexManager.getIndex(table.indexes.get(expression.targetIndex.getIdx()).get()); if (!index.getIndexMetadata().isCustom()) throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex); AbstractType<?> expressionType = index.customExpressionValueType(); if (expressionType == null) throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex); expression.prepareValue(table, expressionType, boundNames); filterRestrictions.add(expression); } public RowFilter getRowFilter(SecondaryIndexManager indexManager, QueryOptions options) { if (filterRestrictions.isEmpty()) return RowFilter.NONE; RowFilter filter = RowFilter.create(); for (Restrictions restrictions : filterRestrictions.getRestrictions()) restrictions.addRowFilterTo(filter, indexManager, options); for (CustomIndexExpression expression : filterRestrictions.getCustomIndexExpressions()) expression.addToRowFilter(filter, table, options); return filter; } /** * Returns the partition keys for which the data is requested. * * @param options the query options * @return the partition keys for which the data is requested. */ public List<ByteBuffer> getPartitionKeys(final QueryOptions options) { return partitionKeyRestrictions.values(options); } /** * Returns the specified bound of the partition key. * * @param b the boundary type * @param options the query options * @return the specified bound of the partition key */ private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options) { // We deal with IN queries for keys in other places, so we know buildBound will return only one result return partitionKeyRestrictions.bounds(b, options).get(0); } /** * Returns the partition key bounds. * * @param options the query options * @return the partition key bounds */ public AbstractBounds<PartitionPosition> getPartitionKeyBounds(QueryOptions options) { IPartitioner p = table.partitioner; if (partitionKeyRestrictions.isOnToken()) { return getPartitionKeyBoundsForTokenRestrictions(p, options); } return getPartitionKeyBounds(p, options); } private AbstractBounds<PartitionPosition> getPartitionKeyBounds(IPartitioner p, QueryOptions options) { // Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the // first component of a composite partition key) queries that filter on the partition key. if (partitionKeyRestrictions.needFiltering(table)) return new Range<>(p.getMinimumToken().minKeyBound(), p.getMinimumToken().maxKeyBound()); ByteBuffer startKeyBytes = getPartitionKeyBound(Bound.START, options); ByteBuffer finishKeyBytes = getPartitionKeyBound(Bound.END, options); PartitionPosition startKey = PartitionPosition.ForKey.get(startKeyBytes, p); PartitionPosition finishKey = PartitionPosition.ForKey.get(finishKeyBytes, p); if (startKey.compareTo(finishKey) > 0 && !finishKey.isMinimum()) return null; if (partitionKeyRestrictions.isInclusive(Bound.START)) { return partitionKeyRestrictions.isInclusive(Bound.END) ? new Bounds<>(startKey, finishKey) : new IncludingExcludingBounds<>(startKey, finishKey); } return partitionKeyRestrictions.isInclusive(Bound.END) ? new Range<>(startKey, finishKey) : new ExcludingBounds<>(startKey, finishKey); } private AbstractBounds<PartitionPosition> getPartitionKeyBoundsForTokenRestrictions(IPartitioner p, QueryOptions options) { Token startToken = getTokenBound(Bound.START, options, p); Token endToken = getTokenBound(Bound.END, options, p); boolean includeStart = partitionKeyRestrictions.isInclusive(Bound.START); boolean includeEnd = partitionKeyRestrictions.isInclusive(Bound.END); /* * If we ask SP.getRangeSlice() for (token(200), token(200)], it will happily return the whole ring. * However, wrapping range doesn't really make sense for CQL, and we want to return an empty result in that * case (CASSANDRA-5573). So special case to create a range that is guaranteed to be empty. * * In practice, we want to return an empty result set if either startToken > endToken, or both are equal but * one of the bound is excluded (since [a, a] can contains something, but not (a, a], [a, a) or (a, a)). * Note though that in the case where startToken or endToken is the minimum token, then this special case * rule should not apply. */ int cmp = startToken.compareTo(endToken); if (!startToken.isMinimum() && !endToken.isMinimum() && (cmp > 0 || (cmp == 0 && (!includeStart || !includeEnd)))) return null; PartitionPosition start = includeStart ? startToken.minKeyBound() : startToken.maxKeyBound(); PartitionPosition end = includeEnd ? endToken.maxKeyBound() : endToken.minKeyBound(); return new Range<>(start, end); } private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p) { if (!partitionKeyRestrictions.hasBound(b)) return p.getMinimumToken(); ByteBuffer value = partitionKeyRestrictions.bounds(b, options).get(0); checkNotNull(value, "Invalid null token value"); return p.getTokenFactory().fromByteArray(value); } /** * Checks if the query has some restrictions on the clustering columns. * * @return <code>true</code> if the query has some restrictions on the clustering columns, * <code>false</code> otherwise. */ public boolean hasClusteringColumnsRestrictions() { return !clusteringColumnsRestrictions.isEmpty(); } /** * Returns the requested clustering columns. * * @param options the query options * @return the requested clustering columns */ public NavigableSet<Clustering> getClusteringColumns(QueryOptions options) { // If this is a names command and the table is a static compact one, then as far as CQL is concerned we have // only a single row which internally correspond to the static parts. In which case we want to return an empty // set (since that's what ClusteringIndexNamesFilter expects). if (table.isStaticCompactTable()) return BTreeSet.empty(table.comparator); return clusteringColumnsRestrictions.valuesAsClustering(options); } /** * Returns the bounds (start or end) of the clustering columns. * * @param b the bound type * @param options the query options * @return the bounds (start or end) of the clustering columns */ public NavigableSet<ClusteringBound> getClusteringColumnsBounds(Bound b, QueryOptions options) { return clusteringColumnsRestrictions.boundsAsClustering(b, options); } /** * Checks if the query returns a range of columns. * * @return <code>true</code> if the query returns a range of columns, <code>false</code> otherwise. */ public boolean isColumnRange() { // For static compact tables we want to ignore the fake clustering column (note that if we weren't special casing, // this would mean a 'SELECT *' on a static compact table would query whole partitions, even though we'll only return // the static part as far as CQL is concerned. This is thus mostly an optimization to use the query-by-name path). int numberOfClusteringColumns = table.isStaticCompactTable() ? 0 : table.clusteringColumns().size(); // it is a range query if it has at least one the column alias for which no relation is defined or is not EQ or IN. return clusteringColumnsRestrictions.size() < numberOfClusteringColumns || !clusteringColumnsRestrictions.hasOnlyEqualityRestrictions(); } /** * Checks if the query need to use filtering. * @return <code>true</code> if the query need to use filtering, <code>false</code> otherwise. */ public boolean needFiltering() { int numberOfRestrictions = filterRestrictions.getCustomIndexExpressions().size(); for (Restrictions restrictions : filterRestrictions.getRestrictions()) numberOfRestrictions += restrictions.size(); return numberOfRestrictions > 1 || (numberOfRestrictions == 0 && !clusteringColumnsRestrictions.isEmpty()) || (numberOfRestrictions != 0 && nonPrimaryKeyRestrictions.hasMultipleContains()); } private void validateSecondaryIndexSelections(boolean selectsOnlyStaticColumns) { checkFalse(keyIsInRelation(), "Select on indexed columns and with IN clause for the PRIMARY KEY are not supported"); // When the user only select static columns, the intent is that we don't query the whole partition but just // the static parts. But 1) we don't have an easy way to do that with 2i and 2) since we don't support index on // static columns // so far, 2i means that you've restricted a non static column, so the query is somewhat non-sensical. checkFalse(selectsOnlyStaticColumns, "Queries using 2ndary indexes don't support selecting only static columns"); } /** * Checks that all the primary key columns (partition key and clustering columns) are restricted by an equality * relation ('=' or 'IN'). * * @return <code>true</code> if all the primary key columns are restricted by an equality relation. */ public boolean hasAllPKColumnsRestrictedByEqualities() { return !isPartitionKeyRestrictionsOnToken() && !partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table) && (partitionKeyRestrictions.hasOnlyEqualityRestrictions()) && !hasUnrestrictedClusteringColumns() && (clusteringColumnsRestrictions.hasOnlyEqualityRestrictions()); } /** * Checks if one of the restrictions applies to a regular column. * @return {@code true} if one of the restrictions applies to a regular column, {@code false} otherwise. */ public boolean hasRegularColumnsRestrictions() { return hasRegularColumnsRestrictions; } }