| /* |
| * 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.config.CFMetaData; |
| import org.apache.cassandra.config.ColumnDefinition; |
| 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.net.MessagingService; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| 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 CFMetaData cfm; |
| |
| /** |
| * Restrictions on partitioning columns |
| */ |
| private PrimaryKeyRestrictions partitionKeyRestrictions; |
| |
| /** |
| * Restrictions on clustering columns |
| */ |
| private PrimaryKeyRestrictions clusteringColumnsRestrictions; |
| |
| /** |
| * Restriction on non-primary key columns (i.e. secondary index restrictions) |
| */ |
| private RestrictionSet nonPrimaryKeyRestrictions; |
| |
| private Set<ColumnDefinition> notNullColumns; |
| |
| /** |
| * The restrictions used to build the row filter |
| */ |
| private final IndexRestrictions indexRestrictions = 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; |
| |
| /** |
| * Creates a new empty <code>StatementRestrictions</code>. |
| * |
| * @param type the type of statement |
| * @param cfm the column family meta data |
| * @return a new empty <code>StatementRestrictions</code>. |
| */ |
| public static StatementRestrictions empty(StatementType type, CFMetaData cfm) |
| { |
| return new StatementRestrictions(type, cfm); |
| } |
| |
| private StatementRestrictions(StatementType type, CFMetaData cfm) |
| { |
| this.type = type; |
| this.cfm = cfm; |
| this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsClusteringComparator(), true); |
| this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, false); |
| this.nonPrimaryKeyRestrictions = new RestrictionSet(); |
| this.notNullColumns = new HashSet<>(); |
| } |
| |
| public StatementRestrictions(StatementType type, |
| CFMetaData cfm, |
| WhereClause whereClause, |
| VariableSpecifications boundNames, |
| boolean selectsOnlyStaticColumns, |
| boolean selectACollection, |
| boolean allowFiltering, |
| boolean forView) throws InvalidRequestException |
| { |
| this.type = type; |
| this.cfm = cfm; |
| this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsClusteringComparator(), true); |
| this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, false); |
| this.nonPrimaryKeyRestrictions = new RestrictionSet(); |
| this.notNullColumns = new HashSet<>(); |
| |
| /* |
| * 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 (ColumnDefinition def : relation.toRestriction(cfm, boundNames).getColumnDefs()) |
| this.notNullColumns.add(def); |
| } |
| else |
| { |
| addRestriction(relation.toRestriction(cfm, boundNames)); |
| } |
| } |
| |
| boolean hasQueriableClusteringColumnIndex = false; |
| boolean hasQueriableIndex = false; |
| |
| if (type.allowUseOfSecondaryIndices()) |
| { |
| ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName); |
| SecondaryIndexManager secondaryIndexManager = cfs.indexManager; |
| |
| if (whereClause.containsCustomExpressions()) |
| processCustomIndexExpressions(whereClause.expressions, boundNames, secondaryIndexManager); |
| |
| hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager); |
| hasQueriableIndex = !indexRestrictions.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); |
| |
| // Some but not all of the partition key columns have been specified; |
| // hence we need turn these restrictions into a row filter. |
| if (usesSecondaryIndexing) |
| indexRestrictions.add(partitionKeyRestrictions); |
| |
| if (selectsOnlyStaticColumns && hasClusteringColumnsRestriction()) |
| { |
| // 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, selectACollection, forView); |
| |
| // Covers indexes on the first clustering column (among others). |
| if (isKeyRange && hasQueriableClusteringColumnIndex) |
| usesSecondaryIndexing = true; |
| |
| usesSecondaryIndexing = usesSecondaryIndexing || clusteringColumnsRestrictions.isContains(); |
| |
| if (usesSecondaryIndexing) |
| indexRestrictions.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 = |
| ColumnDefinition.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); |
| |
| indexRestrictions.add(nonPrimaryKeyRestrictions); |
| } |
| |
| if (usesSecondaryIndexing) |
| validateSecondaryIndexSelections(selectsOnlyStaticColumns); |
| } |
| |
| private void addRestriction(Restriction restriction) |
| { |
| if (restriction.isMultiColumn()) |
| clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction); |
| else if (restriction.isOnToken()) |
| partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction); |
| else |
| addSingleColumnRestriction((SingleColumnRestriction) restriction); |
| } |
| |
| public void addFunctionsTo(List<Function> functions) |
| { |
| partitionKeyRestrictions.addFunctionsTo(functions); |
| clusteringColumnsRestrictions.addFunctionsTo(functions); |
| nonPrimaryKeyRestrictions.addFunctionsTo(functions); |
| } |
| |
| private void addSingleColumnRestriction(SingleColumnRestriction restriction) |
| { |
| ColumnDefinition def = restriction.columnDef; |
| if (def.isPartitionKey()) |
| partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction); |
| else if (def.isClusteringColumn()) |
| clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction); |
| else |
| nonPrimaryKeyRestrictions = nonPrimaryKeyRestrictions.addRestriction(restriction); |
| } |
| |
| /** |
| * 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<ColumnDefinition> nonPKRestrictedColumns(boolean includeNotNullRestrictions) |
| { |
| Set<ColumnDefinition> columns = new HashSet<>(); |
| for (Restrictions r : indexRestrictions.getRestrictions()) |
| { |
| for (ColumnDefinition def : r.getColumnDefs()) |
| if (!def.isPrimaryKeyColumn()) |
| columns.add(def); |
| } |
| |
| if (includeNotNullRestrictions) |
| { |
| for (ColumnDefinition 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<ColumnDefinition> notNullColumns() |
| { |
| return notNullColumns; |
| } |
| |
| /** |
| * @return true if column is restricted by some restriction, false otherwise |
| */ |
| public boolean isRestricted(ColumnDefinition column) |
| { |
| if (notNullColumns.contains(column)) |
| return true; |
| else if (column.isPartitionKey()) |
| return partitionKeyRestrictions.getColumnDefs().contains(column); |
| else if (column.isClusteringColumn()) |
| return clusteringColumnsRestrictions.getColumnDefs().contains(column); |
| else |
| return nonPrimaryKeyRestrictions.getColumnDefs().contains(column); |
| } |
| |
| /** |
| * Checks if the restrictions on the partition key is an IN restriction. |
| * |
| * @return <code>true</code> the restrictions on the partition key is an IN restriction, <code>false</code> |
| * otherwise. |
| */ |
| public boolean keyIsInRelation() |
| { |
| return partitionKeyRestrictions.isIN(); |
| } |
| |
| /** |
| * 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 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) |
| { |
| if (!type.allowPartitionKeyRanges()) |
| { |
| checkFalse(partitionKeyRestrictions.isOnToken(), |
| "The token function cannot be used in WHERE clauses for %s statements", type); |
| |
| if (hasUnrestrictedPartitionKeyComponents()) |
| throw invalidRequest("Some partition key parts are missing: %s", |
| Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents())); |
| } |
| else |
| { |
| // If there is a queriable index, no special condition are 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.isOnToken()) |
| isKeyRange = true; |
| |
| if (hasUnrestrictedPartitionKeyComponents()) |
| { |
| if (!partitionKeyRestrictions.isEmpty()) |
| { |
| if (!hasQueriableIndex) |
| throw invalidRequest("Partition key parts: %s must be restricted as other parts are", |
| Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents())); |
| } |
| |
| isKeyRange = true; |
| usesSecondaryIndexing = hasQueriableIndex; |
| } |
| } |
| } |
| |
| /** |
| * Checks if the partition key has some unrestricted components. |
| * @return <code>true</code> if the partition key has some unrestricted components, <code>false</code> otherwise. |
| */ |
| private boolean hasUnrestrictedPartitionKeyComponents() |
| { |
| return partitionKeyRestrictions.size() < cfm.partitionKeyColumns().size(); |
| } |
| |
| 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<ColumnDefinition> list = new ArrayList<>(cfm.partitionKeyColumns()); |
| list.removeAll(partitionKeyRestrictions.getColumnDefs()); |
| return ColumnDefinition.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(); |
| } |
| |
| /** |
| * 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. |
| * @param selectACollection <code>true</code> if the query should return a collection column |
| */ |
| private void processClusteringColumnsRestrictions(boolean hasQueriableIndex, |
| boolean selectsOnlyStaticColumns, |
| boolean selectACollection, |
| boolean forView) throws InvalidRequestException |
| { |
| validateClusteringRestrictions(hasQueriableIndex); |
| |
| checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(), |
| "Slice restrictions are not supported on the clustering columns in %s statements", type); |
| |
| if (!type.allowClusteringColumnSlices() |
| && (!cfm.isCompactTable() || (cfm.isCompactTable() && !hasClusteringColumnsRestriction()))) |
| { |
| if (!selectsOnlyStaticColumns && hasUnrestrictedClusteringColumns()) |
| throw invalidRequest("Some clustering keys are missing: %s", |
| Joiner.on(", ").join(getUnrestrictedClusteringColumns())); |
| } |
| else |
| { |
| checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection, |
| "Cannot restrict clustering columns by IN relations when a collection is selected by the query"); |
| checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex, |
| "Cannot restrict clustering columns by a CONTAINS relation without a secondary index"); |
| |
| if (hasClusteringColumnsRestriction() && clusteringRestrictionsNeedFiltering()) |
| { |
| if (hasQueriableIndex || forView) |
| { |
| usesSecondaryIndexing = true; |
| return; |
| } |
| |
| List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns(); |
| List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()); |
| |
| for (int i = 0, m = restrictedColumns.size(); i < m; i++) |
| { |
| ColumnDefinition clusteringColumn = clusteringColumns.get(i); |
| ColumnDefinition 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); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Validates whether or not restrictions are allowed for execution when secondary index is not used. |
| */ |
| public final void validateClusteringRestrictions(boolean hasQueriableIndex) |
| { |
| assert clusteringColumnsRestrictions instanceof PrimaryKeyRestrictionSet; |
| |
| // If there's a queriable index, filtering will take care of clustering restrictions |
| if (hasQueriableIndex) |
| return; |
| |
| Iterator<Restriction> iter = ((PrimaryKeyRestrictionSet) clusteringColumnsRestrictions).iterator(); |
| Restriction previousRestriction = null; |
| while (iter.hasNext()) |
| { |
| Restriction restriction = iter.next(); |
| |
| if (previousRestriction != null) |
| { |
| ColumnDefinition lastRestrictionStart = previousRestriction.getFirstColumn(); |
| ColumnDefinition newRestrictionStart = restriction.getFirstColumn(); |
| |
| if (previousRestriction.isSlice() && newRestrictionStart.position() > lastRestrictionStart.position()) |
| throw invalidRequest("Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", |
| newRestrictionStart.name, |
| lastRestrictionStart.name); |
| } |
| previousRestriction = restriction; |
| } |
| } |
| |
| public final boolean clusteringRestrictionsNeedFiltering() |
| { |
| assert clusteringColumnsRestrictions instanceof PrimaryKeyRestrictionSet; |
| return ((PrimaryKeyRestrictionSet) clusteringColumnsRestrictions).needsFiltering(); |
| } |
| |
| /** |
| * Returns the clustering columns that are not restricted. |
| * @return the clustering columns that are not restricted. |
| */ |
| private Collection<ColumnIdentifier> getUnrestrictedClusteringColumns() |
| { |
| List<ColumnDefinition> missingClusteringColumns = new ArrayList<>(cfm.clusteringColumns()); |
| missingClusteringColumns.removeAll(new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs())); |
| return ColumnDefinition.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 cfm.clusteringColumns().size() != clusteringColumnsRestrictions.size(); |
| } |
| |
| private void processCustomIndexExpressions(List<CustomIndexExpression> expressions, |
| VariableSpecifications boundNames, |
| SecondaryIndexManager indexManager) |
| { |
| if (!MessagingService.instance().areAllNodesAtLeast30()) |
| throw new InvalidRequestException("Please upgrade all nodes to at least 3.0 before using custom index expressions"); |
| |
| 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(cfm.ksName)) |
| throw IndexRestrictions.invalidIndex(expression.targetIndex, cfm); |
| |
| if (cfName.getColumnFamily() != null && !cfName.getColumnFamily().equals(cfm.cfName)) |
| throw IndexRestrictions.invalidIndex(expression.targetIndex, cfm); |
| |
| if (!cfm.getIndexes().has(expression.targetIndex.getIdx())) |
| throw IndexRestrictions.indexNotFound(expression.targetIndex, cfm); |
| |
| Index index = indexManager.getIndex(cfm.getIndexes().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(cfm, expressionType, boundNames); |
| |
| indexRestrictions.add(expression); |
| } |
| |
| public RowFilter getRowFilter(SecondaryIndexManager indexManager, QueryOptions options) |
| { |
| if (indexRestrictions.isEmpty()) |
| return RowFilter.NONE; |
| |
| RowFilter filter = RowFilter.create(); |
| for (Restrictions restrictions : indexRestrictions.getRestrictions()) |
| restrictions.addRowFilterTo(filter, indexManager, options); |
| |
| for (CustomIndexExpression expression : indexRestrictions.getCustomIndexExpressions()) |
| expression.addToRowFilter(filter, cfm, 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) |
| { |
| // Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the |
| // first component of a composite partition key). |
| if (hasUnrestrictedPartitionKeyComponents()) |
| return ByteBufferUtil.EMPTY_BYTE_BUFFER; |
| |
| // 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 = cfm.partitioner; |
| |
| if (partitionKeyRestrictions.isOnToken()) |
| { |
| return getPartitionKeyBoundsForTokenRestrictions(p, options); |
| } |
| |
| return getPartitionKeyBounds(p, options); |
| } |
| |
| private AbstractBounds<PartitionPosition> getPartitionKeyBounds(IPartitioner p, |
| QueryOptions options) |
| { |
| 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 hasClusteringColumnsRestriction() |
| { |
| 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 (cfm.isStaticCompactTable()) |
| return BTreeSet.empty(cfm.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<Slice.Bound> getClusteringColumnsBounds(Bound b, QueryOptions options) |
| { |
| return clusteringColumnsRestrictions.boundsAsClustering(b, options); |
| } |
| |
| /** |
| * Checks if the bounds (start or end) of the clustering columns are inclusive. |
| * |
| * @param bound the bound type |
| * @return <code>true</code> if the bounds (start or end) of the clustering columns are inclusive, |
| * <code>false</code> otherwise |
| */ |
| public boolean areRequestedBoundsInclusive(Bound bound) |
| { |
| return clusteringColumnsRestrictions.isInclusive(bound); |
| } |
| |
| /** |
| * 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 = cfm.isStaticCompactTable() ? 0 : cfm.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. |
| return clusteringColumnsRestrictions.size() < numberOfClusteringColumns |
| || (!clusteringColumnsRestrictions.isEQ() && !clusteringColumnsRestrictions.isIN()); |
| } |
| |
| /** |
| * 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 = indexRestrictions.getCustomIndexExpressions().size(); |
| for (Restrictions restrictions : indexRestrictions.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() |
| && !hasUnrestrictedPartitionKeyComponents() |
| && (partitionKeyRestrictions.isEQ() || partitionKeyRestrictions.isIN()) |
| && !hasUnrestrictedClusteringColumns() |
| && (clusteringColumnsRestrictions.isEQ() || clusteringColumnsRestrictions.isIN()); |
| } |
| |
| } |