Return-Path: X-Original-To: apmail-cassandra-commits-archive@www.apache.org Delivered-To: apmail-cassandra-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 7B78017643 for ; Tue, 10 Feb 2015 21:11:06 +0000 (UTC) Received: (qmail 98382 invoked by uid 500); 10 Feb 2015 21:11:06 -0000 Delivered-To: apmail-cassandra-commits-archive@cassandra.apache.org Received: (qmail 98270 invoked by uid 500); 10 Feb 2015 21:11:06 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 97886 invoked by uid 99); 10 Feb 2015 21:11:06 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 10 Feb 2015 21:11:06 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id EF485E05D2; Tue, 10 Feb 2015 21:11:05 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tylerhobbs@apache.org To: commits@cassandra.apache.org Date: Tue, 10 Feb 2015 21:11:07 -0000 Message-Id: <28c0b36f26264a9cae725f0e86af0c8a@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [3/4] cassandra git commit: Merge branch 'cassandra-2.1' into trunk http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java index 598478c,0000000..403bf6d mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java @@@ -1,599 -1,0 +1,576 @@@ +/* + * 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.ColumnIdentifier; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Relation; +import org.apache.cassandra.cql3.VariableSpecifications; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.IndexExpression; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.RowPosition; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.dht.*; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.ByteBufferUtil; + +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.checkTrue; +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 +{ + /** + * 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 SingleColumnRestrictions nonPrimaryKeyRestrictions; + + /** + * The restrictions used to build the index expressions + */ + private final List indexRestrictions = new ArrayList<>(); + + /** + * true if the secondary index need to be queried, false otherwise + */ + private boolean usesSecondaryIndexing; + + /** + * Specify if the query will return a range of partition keys. + */ + private boolean isKeyRange; + + /** + * Creates a new empty StatementRestrictions. + * + * @param cfm the column family meta data + * @return a new empty StatementRestrictions. + */ + public static StatementRestrictions empty(CFMetaData cfm) + { + return new StatementRestrictions(cfm); + } + + private StatementRestrictions(CFMetaData cfm) + { + this.cfm = cfm; + this.partitionKeyRestrictions = new SingleColumnPrimaryKeyRestrictions(cfm.getKeyValidatorAsCType()); + this.clusteringColumnsRestrictions = new SingleColumnPrimaryKeyRestrictions(cfm.comparator); + this.nonPrimaryKeyRestrictions = new SingleColumnRestrictions(); + } + + public StatementRestrictions(CFMetaData cfm, + List whereClause, + VariableSpecifications boundNames, + boolean selectsOnlyStaticColumns, + boolean selectACollection) throws InvalidRequestException + { + this.cfm = cfm; + this.partitionKeyRestrictions = new SingleColumnPrimaryKeyRestrictions(cfm.getKeyValidatorAsCType()); + this.clusteringColumnsRestrictions = new SingleColumnPrimaryKeyRestrictions(cfm.comparator); + this.nonPrimaryKeyRestrictions = new SingleColumnRestrictions(); + + /* + * 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) + addRestriction(relation.toRestriction(cfm, boundNames)); + + ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName); + SecondaryIndexManager secondaryIndexManager = cfs.indexManager; + + boolean hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager); + boolean hasQueriableIndex = 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 index expressions. + if (usesSecondaryIndexing) + indexRestrictions.add(partitionKeyRestrictions); + + checkFalse(selectsOnlyStaticColumns && hasClusteringColumnsRestriction(), + "Cannot restrict clustering columns when selecting only static columns"); + + processClusteringColumnsRestrictions(hasQueriableIndex, selectACollection); + + // Covers indexes on the first clustering column (among others). + if (isKeyRange && hasQueriableClusteringColumnIndex) + usesSecondaryIndexing = true; + ++ usesSecondaryIndexing = usesSecondaryIndexing || clusteringColumnsRestrictions.isContains(); ++ + if (usesSecondaryIndexing) - { + indexRestrictions.add(clusteringColumnsRestrictions); - } - else if (clusteringColumnsRestrictions.isContains()) - { - indexRestrictions.add(new ForwardingPrimaryKeyRestrictions() { + - @Override - protected PrimaryKeyRestrictions getDelegate() - { - return clusteringColumnsRestrictions; - } - - @Override - public void addIndexExpressionTo(List expressions, QueryOptions options) throws InvalidRequestException - { - List list = new ArrayList<>(); - super.addIndexExpressionTo(list, options); - - for (IndexExpression expression : list) - { - if (expression.isContains() || expression.isContainsKey()) - expressions.add(expression); - } - } - }); - usesSecondaryIndexing = true; - } + // 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()) + { + usesSecondaryIndexing = true; + indexRestrictions.add(nonPrimaryKeyRestrictions); + } + + if (usesSecondaryIndexing) + validateSecondaryIndexSelections(selectsOnlyStaticColumns); + } + + private void addRestriction(Restriction restriction) throws InvalidRequestException + { + if (restriction.isMultiColumn()) + clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction); + else if (restriction.isOnToken()) + partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction); + else + addSingleColumnRestriction((SingleColumnRestriction) restriction); + } + + public boolean usesFunction(String ksName, String functionName) + { + return partitionKeyRestrictions.usesFunction(ksName, functionName) + || clusteringColumnsRestrictions.usesFunction(ksName, functionName) + || nonPrimaryKeyRestrictions.usesFunction(ksName, functionName); + } + + private void addSingleColumnRestriction(SingleColumnRestriction restriction) throws InvalidRequestException + { + ColumnDefinition def = restriction.getColumnDef(); + if (def.isPartitionKey()) + partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction); + else if (def.isClusteringColumn()) + clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction); + else + nonPrimaryKeyRestrictions = nonPrimaryKeyRestrictions.addRestriction(restriction); + } + + /** + * Checks if the restrictions on the partition key is an IN restriction. + * + * @return true the restrictions on the partition key is an IN restriction, false + * otherwise. + */ + public boolean keyIsInRelation() + { + return partitionKeyRestrictions.isIN(); + } + + /** + * Checks if the query request a range of partition keys. + * + * @return true if the query request a range of partition keys, false otherwise. + */ + public boolean isKeyRange() + { + return this.isKeyRange; + } + + /** + * Checks if the secondary index need to be queried. + * + * @return true if the secondary index need to be queried, false otherwise. + */ + public boolean usesSecondaryIndexing() + { + return this.usesSecondaryIndexing; + } + + private void processPartitionKeyRestrictions(boolean hasQueriableIndex) throws InvalidRequestException + { + // 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 (hasPartitionKeyUnrestrictedComponents()) + { + 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 true if the partition key has some unrestricted components, false otherwise. + */ + private boolean hasPartitionKeyUnrestrictedComponents() + { + return partitionKeyRestrictions.size() < cfm.partitionKeyColumns().size(); + } + + /** + * Returns the partition key components that are not restricted. + * @return the partition key components that are not restricted. + */ + private List getPartitionKeyUnrestrictedComponents() + { + List list = new ArrayList<>(cfm.partitionKeyColumns()); + list.removeAll(partitionKeyRestrictions.getColumnDefs()); + return ColumnDefinition.toIdentifiers(list); + } + + /** + * Processes the clustering column restrictions. + * + * @param hasQueriableIndex true if some of the queried data are indexed, false otherwise + * @param selectACollection true if the query should return a collection column + * @throws InvalidRequestException if the request is invalid + */ + private void processClusteringColumnsRestrictions(boolean hasQueriableIndex, + boolean selectACollection) throws InvalidRequestException + { + 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()) + { + List clusteringColumns = cfm.clusteringColumns(); + List 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)) + { + checkTrue(hasQueriableIndex, + "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted", + restrictedColumn.name, + clusteringColumn.name); + + usesSecondaryIndexing = true; // handle gaps and non-keyrange cases. + break; + } + } + } + + if (clusteringColumnsRestrictions.isContains()) + usesSecondaryIndexing = true; + } + - public List getIndexExpressions(QueryOptions options) throws InvalidRequestException ++ public List getIndexExpressions(SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException + { + if (!usesSecondaryIndexing || indexRestrictions.isEmpty()) + return Collections.emptyList(); + + List expressions = new ArrayList<>(); + for (Restrictions restrictions : indexRestrictions) - restrictions.addIndexExpressionTo(expressions, options); ++ restrictions.addIndexExpressionTo(expressions, indexManager, options); + + return expressions; + } + + /** + * 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. + * @throws InvalidRequestException if the partition keys cannot be retrieved + */ + public Collection getPartitionKeys(final QueryOptions options) throws InvalidRequestException + { + 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 + * @throws InvalidRequestException if the boundary cannot be retrieved + */ + private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options) throws InvalidRequestException + { + // 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 (hasPartitionKeyUnrestrictedComponents()) + 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 + * @throws InvalidRequestException if the query is invalid + */ + public AbstractBounds getPartitionKeyBounds(QueryOptions options) throws InvalidRequestException + { + IPartitioner p = StorageService.getPartitioner(); + + if (partitionKeyRestrictions.isOnToken()) + { + return getPartitionKeyBoundsForTokenRestrictions(p, options); + } + + return getPartitionKeyBounds(p, options); + } + + private AbstractBounds getPartitionKeyBounds(IPartitioner p, + QueryOptions options) throws InvalidRequestException + { + ByteBuffer startKeyBytes = getPartitionKeyBound(Bound.START, options); + ByteBuffer finishKeyBytes = getPartitionKeyBound(Bound.END, options); + + RowPosition startKey = RowPosition.ForKey.get(startKeyBytes, p); + RowPosition finishKey = RowPosition.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 getPartitionKeyBoundsForTokenRestrictions(IPartitioner p, + QueryOptions options) + throws InvalidRequestException + { + 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; + + RowPosition start = includeStart ? startToken.minKeyBound() : startToken.maxKeyBound(); + RowPosition end = includeEnd ? endToken.maxKeyBound() : endToken.minKeyBound(); + + return new Range<>(start, end); + } + + private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p) throws InvalidRequestException + { + 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 does not contains any restriction on the clustering columns. + * + * @return true if the query does not contains any restriction on the clustering columns, + * false otherwise. + */ + public boolean hasNoClusteringColumnsRestriction() + { + return clusteringColumnsRestrictions.isEmpty(); + } + + // For non-composite slices, we don't support internally the difference between exclusive and + // inclusive bounds, so we deal with it manually. + public boolean isNonCompositeSliceWithExclusiveBounds() + { + return !cfm.comparator.isCompound() + && clusteringColumnsRestrictions.isSlice() + && (!clusteringColumnsRestrictions.isInclusive(Bound.START) || !clusteringColumnsRestrictions.isInclusive(Bound.END)); + } + + /** + * Returns the requested clustering columns as Composites. + * + * @param options the query options + * @return the requested clustering columns as Composites + * @throws InvalidRequestException if the query is not valid + */ + public List getClusteringColumnsAsComposites(QueryOptions options) throws InvalidRequestException + { + return clusteringColumnsRestrictions.valuesAsComposites(options); + } + + /** + * Returns the bounds (start or end) of the clustering columns as Composites. + * + * @param b the bound type + * @param options the query options + * @return the bounds (start or end) of the clustering columns as Composites + * @throws InvalidRequestException if the request is not valid + */ + public List getClusteringColumnsBoundsAsComposites(Bound b, + QueryOptions options) throws InvalidRequestException + { + return clusteringColumnsRestrictions.boundsAsComposites(b, 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 + * @throws InvalidRequestException if the request is not valid + */ + public List getClusteringColumnsBounds(Bound b, QueryOptions options) throws InvalidRequestException + { + return clusteringColumnsRestrictions.bounds(b, options); + } + + /** + * Checks if the bounds (start or end) of the clustering columns are inclusive. + * + * @param bound the bound type + * @return true if the bounds (start or end) of the clustering columns are inclusive, + * false otherwise + */ + public boolean areRequestedBoundsInclusive(Bound bound) + { + return clusteringColumnsRestrictions.isInclusive(bound); + } + + /** + * Checks if the query returns a range of columns. + * + * @return true if the query returns a range of columns, false otherwise. + */ + public boolean isColumnRange() + { + // Due to CASSANDRA-5762, we always do a slice for CQL3 tables (not dense, composite). + // Static CF (non dense but non composite) never entails a column slice however + if (!cfm.comparator.isDense()) + return cfm.comparator.isCompound(); + + // Otherwise (i.e. for compact table where we don't have a row marker anyway and thus don't care about + // CASSANDRA-5762), + // 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() < cfm.clusteringColumns().size() || clusteringColumnsRestrictions.isSlice(); + } + + /** + * Checks if the query need to use filtering. + * @return true if the query need to use filtering, false otherwise. + */ + public boolean needFiltering() + { + int numberOfRestrictedColumns = 0; + for (Restrictions restrictions : indexRestrictions) + numberOfRestrictedColumns += restrictions.size(); + + return numberOfRestrictedColumns > 1 + || (numberOfRestrictedColumns == 0 && !clusteringColumnsRestrictions.isEmpty()) + || (numberOfRestrictedColumns != 0 + && nonPrimaryKeyRestrictions.hasMultipleContains()); + } + + private void validateSecondaryIndexSelections(boolean selectsOnlyStaticColumns) throws InvalidRequestException + { + 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 if the query has some restrictions on the clustering columns. + * + * @return true if the query has some restrictions on the clustering columns, + * false otherwise. + */ + private boolean hasClusteringColumnsRestriction() + { + return !clusteringColumnsRestrictions.isEmpty(); + } + + public void reverse() + { + clusteringColumnsRestrictions = new ReversedPrimaryKeyRestrictions(clusteringColumnsRestrictions); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java index 8d63fea,0000000..cbcec07 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java @@@ -1,253 -1,0 +1,255 @@@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.List; + +import com.google.common.base.Joiner; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.IndexExpression; +import org.apache.cassandra.db.composites.CType; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.exceptions.InvalidRequestException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + +/** + * Restriction using the token function. + */ +public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions +{ + /** + * The definition of the columns to which apply the token restriction. + */ + protected final List columnDefs; + + /** + * Creates a new TokenRestriction that apply to the specified columns. + * + * @param ctype the composite type + * @param columnDefs the definition of the columns to which apply the token restriction + */ + public TokenRestriction(CType ctype, List columnDefs) + { + super(ctype); + this.columnDefs = columnDefs; + } + + @Override + public boolean isOnToken() + { + return true; + } + + @Override + public Collection getColumnDefs() + { + return columnDefs; + } + + @Override + public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager) + { + return false; + } + + @Override - public void addIndexExpressionTo(List expressions, QueryOptions options) ++ public final void addIndexExpressionTo(List expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) + { + throw new UnsupportedOperationException("Index expression cannot be created for token restriction"); + } + + @Override + public List valuesAsComposites(QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException(); + } + + @Override + public List boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException(); + } + + /** + * Returns the column names as a comma separated String. + * + * @return the column names as a comma separated String. + */ + protected final String getColumnNamesAsString() + { + return Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs)); + } + + @Override + public final PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) throws InvalidRequestException + { + if (!otherRestriction.isOnToken()) + return new TokenFilter(toPrimaryKeyRestriction(otherRestriction), this); + + return doMergeWith((TokenRestriction) otherRestriction); + } + + /** + * Merges this restriction with the specified TokenRestriction. + * @param otherRestriction the TokenRestriction to merge with. + */ + protected abstract PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException; + + /** + * Converts the specified restriction into a PrimaryKeyRestrictions. + * + * @param restriction the restriction to convert + * @return a PrimaryKeyRestrictions + * @throws InvalidRequestException if a problem occurs while converting the restriction + */ + private PrimaryKeyRestrictions toPrimaryKeyRestriction(Restriction restriction) throws InvalidRequestException + { + if (restriction instanceof PrimaryKeyRestrictions) + return (PrimaryKeyRestrictions) restriction; + + return new SingleColumnPrimaryKeyRestrictions(ctype).mergeWith(restriction); + } + + public static final class EQ extends TokenRestriction + { + private final Term value; + + public EQ(CType ctype, List columnDefs, Term value) + { + super(ctype, columnDefs); + this.value = value; + } + + @Override + public boolean isEQ() + { + return true; + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return usesFunction(value, ksName, functionName); + } + + @Override + protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException + { + throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal", + Joiner.on(", ").join(ColumnDefinition.toIdentifiers(columnDefs))); + } + + @Override + public List values(QueryOptions options) throws InvalidRequestException + { + return Collections.singletonList(value.bindAndGet(options)); + } + } + + public static class Slice extends TokenRestriction + { + private final TermSlice slice; + + public Slice(CType ctype, List columnDefs, Bound bound, boolean inclusive, Term term) + { + super(ctype, columnDefs); + slice = TermSlice.newInstance(bound, inclusive, term); + } + + @Override + public boolean isSlice() + { + return true; + } + + @Override + public List values(QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasBound(Bound b) + { + return slice.hasBound(b); + } + + @Override + public List bounds(Bound b, QueryOptions options) throws InvalidRequestException + { + return Collections.singletonList(slice.bound(b).bindAndGet(options)); + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return (slice.hasBound(Bound.START) && usesFunction(slice.bound(Bound.START), ksName, functionName)) + || (slice.hasBound(Bound.END) && usesFunction(slice.bound(Bound.END), ksName, functionName)); + } + + @Override + public boolean isInclusive(Bound b) + { + return slice.isInclusive(b); + } + + @Override + protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) + throws InvalidRequestException + { + if (!otherRestriction.isSlice()) + throw invalidRequest("Columns \"%s\" cannot be restricted by both an equality and an inequality relation", + getColumnNamesAsString()); + + TokenRestriction.Slice otherSlice = (TokenRestriction.Slice) otherRestriction; + + if (hasBound(Bound.START) && otherSlice.hasBound(Bound.START)) + throw invalidRequest("More than one restriction was found for the start bound on %s", + getColumnNamesAsString()); + + if (hasBound(Bound.END) && otherSlice.hasBound(Bound.END)) + throw invalidRequest("More than one restriction was found for the end bound on %s", + getColumnNamesAsString()); + + return new Slice(ctype, columnDefs, slice.merge(otherSlice.slice)); + } + + @Override + public String toString() + { + return String.format("SLICE%s", slice); + } + + private Slice(CType ctype, List columnDefs, TermSlice slice) + { + super(ctype, columnDefs); + this.slice = slice; + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index de8e004,08777c7..7094b6c --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@@ -531,17 -812,336 +531,18 @@@ public class SelectStatement implement } } - /** Returns true if a non-frozen collection is selected, false otherwise. */ - private boolean selectACollection() - { - if (!cfm.comparator.hasCollections()) - return false; - - for (ColumnDefinition def : selection.getColumns()) - { - if (def.type.isCollection() && def.type.isMultiCell()) - return true; - } - - return false; - } - - private static List buildBound(Bound bound, - List defs, - Restriction[] restrictions, - boolean isReversed, - CType type, - QueryOptions options) throws InvalidRequestException - { - CBuilder builder = type.builder(); - - // check the first restriction to see if we're dealing with a multi-column restriction - if (!defs.isEmpty()) - { - Restriction firstRestriction = restrictions[0]; - if (firstRestriction != null && firstRestriction.isMultiColumn()) - { - if (firstRestriction.isSlice()) - return buildMultiColumnSliceBound(bound, defs, (MultiColumnRestriction.Slice) firstRestriction, isReversed, builder, options); - else if (firstRestriction.isIN()) - return buildMultiColumnInBound(bound, defs, (MultiColumnRestriction.IN) firstRestriction, isReversed, builder, type, options); - else - return buildMultiColumnEQBound(bound, defs, (MultiColumnRestriction.EQ) firstRestriction, isReversed, builder, options); - } - } - - // The end-of-component of composite doesn't depend on whether the - // component type is reversed or not (i.e. the ReversedType is applied - // to the component comparator but not to the end-of-component itself), - // it only depends on whether the slice is reversed - Bound eocBound = isReversed ? Bound.reverse(bound) : bound; - for (Iterator iter = defs.iterator(); iter.hasNext();) - { - ColumnDefinition def = iter.next(); - - // In a restriction, we always have Bound.START < Bound.END for the "base" comparator. - // So if we're doing a reverse slice, we must inverse the bounds when giving them as start and end of the slice filter. - // But if the actual comparator itself is reversed, we must inversed the bounds too. - Bound b = isReversed == isReversedType(def) ? bound : Bound.reverse(bound); - Restriction r = restrictions[def.position()]; - if (isNullRestriction(r, b) || !r.canEvaluateWithSlices()) - { - // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix. - // For composites, if there was preceding component and we're computing the end, we must change the last component - // End-Of-Component, otherwise we would be selecting only one record. - Composite prefix = builder.build(); - return Collections.singletonList(eocBound == Bound.END ? prefix.end() : prefix.start()); - } - if (r.isSlice()) - { - builder.add(getSliceValue(r, b, options)); - Operator relType = ((Restriction.Slice)r).getRelation(eocBound, b); - return Collections.singletonList(builder.build().withEOC(eocForRelation(relType))); - } - else - { - // IN or EQ - List values = r.values(options); - if (values.size() != 1) - { - // IN query, we only support it on the clustering columns - assert def.position() == defs.size() - 1; - // The IN query might not have listed the values in comparator order, so we need to re-sort - // the bounds lists to make sure the slices works correctly (also, to avoid duplicates). - TreeSet s = new TreeSet<>(isReversed ? type.reverseComparator() : type); - for (ByteBuffer val : values) - { - if (val == null) - throw new InvalidRequestException(String.format("Invalid null clustering key part %s", def.name)); - Composite prefix = builder.buildWith(val); - // See below for why this - s.add(builder.remainingCount() == 0 ? prefix : (eocBound == Bound.END ? prefix.end() : prefix.start())); - } - return new ArrayList<>(s); - } - - ByteBuffer val = values.get(0); - if (val == null) - throw new InvalidRequestException(String.format("Invalid null clustering key part %s", def.name)); - builder.add(val); - } - } - // Means no relation at all or everything was an equal - // Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection, - // it would be harmless to do it. However, we use this method got the partition key too. And when a query - // with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that - // case using the eoc would be bad, since for the random partitioner we have no guarantee that - // prefix.end() will sort after prefix (see #5240). - Composite prefix = builder.build(); - return Collections.singletonList(builder.remainingCount() == 0 ? prefix : (eocBound == Bound.END ? prefix.end() : prefix.start())); - } - - private static Composite.EOC eocForRelation(Operator op) - { - switch (op) - { - case LT: - // < X => using startOf(X) as finish bound - return Composite.EOC.START; - case GT: - case LTE: - // > X => using endOf(X) as start bound - // <= X => using endOf(X) as finish bound - return Composite.EOC.END; - default: - // >= X => using X as start bound (could use START_OF too) - // = X => using X - return Composite.EOC.NONE; - } - } - - private static List buildMultiColumnSliceBound(Bound bound, - List defs, - MultiColumnRestriction.Slice slice, - boolean isReversed, - CBuilder builder, - QueryOptions options) throws InvalidRequestException - { - Bound eocBound = isReversed ? Bound.reverse(bound) : bound; - - Iterator iter = defs.iterator(); - ColumnDefinition firstName = iter.next(); - // A hack to preserve pre-6875 behavior for tuple-notation slices where the comparator mixes ASCENDING - // and DESCENDING orders. This stores the bound for the first component; we will re-use it for all following - // components, even if they don't match the first component's reversal/non-reversal. Note that this does *not* - // guarantee correct query results, it just preserves the previous behavior. - Bound firstComponentBound = isReversed == isReversedType(firstName) ? bound : Bound.reverse(bound); - - if (!slice.hasBound(firstComponentBound)) - { - Composite prefix = builder.build(); - return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END - ? prefix.end() - : prefix); - } - - List vals = slice.componentBounds(firstComponentBound, options); - - ByteBuffer v = vals.get(firstName.position()); - if (v == null) - throw new InvalidRequestException("Invalid null value in condition for column " + firstName.name); - builder.add(v); - - while (iter.hasNext()) - { - ColumnDefinition def = iter.next(); - if (def.position() >= vals.size()) - break; - - v = vals.get(def.position()); - if (v == null) - throw new InvalidRequestException("Invalid null value in condition for column " + def.name); - builder.add(v); - } - Operator relType = slice.getRelation(eocBound, firstComponentBound); - return Collections.singletonList(builder.build().withEOC(eocForRelation(relType))); - } - - private static List buildMultiColumnInBound(Bound bound, - List defs, - MultiColumnRestriction.IN restriction, - boolean isReversed, - CBuilder builder, - CType type, - QueryOptions options) throws InvalidRequestException - { - List> splitInValues = restriction.splitValues(options); - Bound eocBound = isReversed ? Bound.reverse(bound) : bound; - - // The IN query might not have listed the values in comparator order, so we need to re-sort - // the bounds lists to make sure the slices works correctly (also, to avoid duplicates). - TreeSet inValues = new TreeSet<>(isReversed ? type.reverseComparator() : type); - for (List components : splitInValues) - { - for (int i = 0; i < components.size(); i++) - if (components.get(i) == null) - throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i)); - - Composite prefix = builder.buildWith(components); - inValues.add(eocBound == Bound.END && builder.remainingCount() - components.size() > 0 - ? prefix.end() - : prefix); - } - return new ArrayList<>(inValues); - } - - private static List buildMultiColumnEQBound(Bound bound, - List defs, - MultiColumnRestriction.EQ restriction, - boolean isReversed, - CBuilder builder, - QueryOptions options) throws InvalidRequestException - { - Bound eocBound = isReversed ? Bound.reverse(bound) : bound; - List values = restriction.values(options); - for (int i = 0; i < values.size(); i++) - { - ByteBuffer component = values.get(i); - if (component == null) - throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i)); - builder.add(component); - } - - Composite prefix = builder.build(); - return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END - ? prefix.end() - : prefix); - } - - private static boolean isNullRestriction(Restriction r, Bound b) - { - return r == null || (r.isSlice() && !((Restriction.Slice)r).hasBound(b)); - } - - private static ByteBuffer getSliceValue(Restriction r, Bound b, QueryOptions options) throws InvalidRequestException - { - Restriction.Slice slice = (Restriction.Slice)r; - assert slice.hasBound(b); - ByteBuffer val = slice.bound(b, options); - if (val == null) - throw new InvalidRequestException(String.format("Invalid null clustering key part %s", r)); - return val; - } - - private List getRequestedBound(Bound b, QueryOptions options) throws InvalidRequestException - { - assert isColumnRange(); - return buildBound(b, cfm.clusteringColumns(), columnRestrictions, isReversed, cfm.comparator, options); - } - public List getValidatedIndexExpressions(QueryOptions options) throws InvalidRequestException { - if (!usesSecondaryIndexing || restrictedColumns.isEmpty()) + if (!restrictions.usesSecondaryIndexing()) return Collections.emptyList(); - List expressions = restrictions.getIndexExpressions(options); - - List expressions = new ArrayList(); - for (ColumnDefinition def : restrictedColumns.keySet()) - { - Restriction restriction; - switch (def.kind) - { - case PARTITION_KEY: - restriction = keyRestrictions[def.position()]; - break; - case CLUSTERING_COLUMN: - restriction = columnRestrictions[def.position()]; - break; - case REGULAR: - case STATIC: - restriction = metadataRestrictions.get(def.name); - break; - default: - // We don't allow restricting a COMPACT_VALUE for now in prepare. - throw new AssertionError(); - } + ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily()); + SecondaryIndexManager secondaryIndexManager = cfs.indexManager; + - if (restriction.isSlice()) - { - Restriction.Slice slice = (Restriction.Slice)restriction; - for (Bound b : Bound.values()) - { - if (slice.hasBound(b)) - { - ByteBuffer value = validateIndexedValue(def, slice.bound(b, options)); - Operator op = slice.getIndexOperator(b); - // If the underlying comparator for name is reversed, we need to reverse the IndexOperator: user operation - // always refer to the "forward" sorting even if the clustering order is reversed, but the 2ndary code does - // use the underlying comparator as is. - if (def.type instanceof ReversedType) - op = reverse(op); - expressions.add(new IndexExpression(def.name.bytes, op, value)); - } - } - } - else if (restriction.isContains()) - { - SingleColumnRestriction.Contains contains = (SingleColumnRestriction.Contains)restriction; - for (ByteBuffer value : contains.values(options)) - { - validateIndexedValue(def, value); - expressions.add(new IndexExpression(def.name.bytes, Operator.CONTAINS, value)); - } - for (ByteBuffer key : contains.keys(options)) - { - validateIndexedValue(def, key); - expressions.add(new IndexExpression(def.name.bytes, Operator.CONTAINS_KEY, key)); - } - } - else - { - ByteBuffer value; - if (restriction.isMultiColumn()) - { - List values = restriction.values(options); - value = values.get(def.position()); - } - else - { - List values = restriction.values(options); - if (values.size() != 1) - throw new InvalidRequestException("IN restrictions are not supported on indexed columns"); ++ List expressions = restrictions.getIndexExpressions(secondaryIndexManager, options); + - value = values.get(0); - } + secondaryIndexManager.validateIndexSearchersForQuery(expressions); - validateIndexedValue(def, value); - expressions.add(new IndexExpression(def.name.bytes, Operator.EQ, value)); - } - } - - if (usesSecondaryIndexing) - { - ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily()); - SecondaryIndexManager secondaryIndexManager = cfs.indexManager; - secondaryIndexManager.validateIndexSearchersForQuery(expressions); - } - return expressions; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java index 3e6e45b,25df030..51625da --- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java +++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java @@@ -605,74 -573,80 +601,128 @@@ public class MultiColumnRelationTest ex row(0, 1, 0, 0, 0), row(0, 1, 1, 0, 1), row(0, 1, 1, 1, 2)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b, c) = (?, ?)", 1, 1); assertRows(execute("SELECT * FROM %s WHERE (b, c) = (?, ?) ALLOW FILTERING", 1, 1), row(0, 1, 1, 0, 1), row(0, 1, 1, 1, 2)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b, c) = (?, ?) AND e = ?", 1, 1, 2); assertRows(execute("SELECT * FROM %s WHERE (b, c) = (?, ?) AND e = ? ALLOW FILTERING", 1, 1, 2), row(0, 1, 1, 1, 2)); - assertRows(execute("SELECT * FROM %s WHERE (b) IN ((?)) AND e = ?", 1, 2), + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b) IN ((?)) AND e = ?", 1, 2); + assertRows(execute("SELECT * FROM %s WHERE (b) IN ((?)) AND e = ? ALLOW FILTERING", 1, 2), row(0, 1, 1, 1, 2)); - assertInvalidMessage("IN restrictions are not supported on indexed columns", - "SELECT * FROM %s WHERE (b) IN ((?), (?)) AND e = ? ALLOW FILTERING", 0, 1, 2); - assertRows(execute("SELECT * FROM %s WHERE (b) IN ((?), (?)) AND e = ?", 0, 1, 2), ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b) IN ((?), (?)) AND e = ?", 0, 1, 2); ++ assertRows(execute("SELECT * FROM %s WHERE (b) IN ((?), (?)) AND e = ? ALLOW FILTERING", 0, 1, 2), + row(0, 0, 1, 1, 2), + row(0, 1, 1, 1, 2)); - assertInvalidMessage("IN restrictions are not supported on indexed columns", - "SELECT * FROM %s WHERE (b, c) IN ((?, ?)) AND e = ? ALLOW FILTERING", 0, 1, 2); - assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?)) AND e = ?", 0, 1, 2), ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b, c) IN ((?, ?)) AND e = ?", 0, 1, 2); ++ assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?)) AND e = ? ALLOW FILTERING", 0, 1, 2), + row(0, 0, 1, 1, 2)); - assertInvalidMessage("IN restrictions are not supported on indexed columns", - "SELECT * FROM %s WHERE (b, c) IN ((?, ?), (?, ?)) AND e = ? ALLOW FILTERING", 0, 1, 1, 1, 2); - assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?), (?, ?)) AND e = ?", 0, 1, 1, 1, 2), ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b, c) IN ((?, ?), (?, ?)) AND e = ?", 0, 1, 1, 1, 2); ++ assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?), (?, ?)) AND e = ? ALLOW FILTERING", 0, 1, 1, 1, 2), + row(0, 0, 1, 1, 2), + row(0, 1, 1, 1, 2)); - assertInvalidMessage("Slice restrictions are not supported on indexed columns which are part of a multi column relation", - "SELECT * FROM %s WHERE (b) >= (?) AND e = ? ALLOW FILTERING", 1, 2); - assertRows(execute("SELECT * FROM %s WHERE (b) >= (?) AND e = ?", 1, 2), ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b) >= (?) AND e = ?", 1, 2); ++ assertRows(execute("SELECT * FROM %s WHERE (b) >= (?) AND e = ? ALLOW FILTERING", 1, 2), + row(0, 1, 1, 1, 2)); + - assertRows(execute("SELECT * FROM %s WHERE (b, c) >= (?, ?) AND e = ?", 1, 1, 2), ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE (b, c) >= (?, ?) AND e = ?", 1, 1, 2); ++ assertRows(execute("SELECT * FROM %s WHERE (b, c) >= (?, ?) AND e = ? ALLOW FILTERING", 1, 1, 2), + row(0, 1, 1, 1, 2)); } @Test public void testMultiplePartitionKeyAndMultiClusteringWithIndex() throws Throwable { - createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, PRIMARY KEY ((a, b), c, d, e))"); + createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))"); createIndex("CREATE INDEX ON %s (c)"); + createIndex("CREATE INDEX ON %s (f)"); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 0, 0, 0); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 0, 1, 0); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 0, 1, 1); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 0, 0, 0); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 1, 0, 1); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 1, 1, 2); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 1, 0, 0); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 1, 1, 0); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 1, 1, 1); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 0, 0, 3); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 1, 0, 4); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 1, 1, 5); - execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 2, 0, 0); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 2, 0, 0, 5); ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c) = (?)"); assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c) = (?) ALLOW FILTERING", 0, 1), - row(0, 0, 1, 0, 0), - row(0, 0, 1, 1, 0), - row(0, 0, 1, 1, 1)); + row(0, 0, 1, 0, 0, 3), + row(0, 0, 1, 1, 0, 4), + row(0, 0, 1, 1, 1, 5)); ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c, d) = (?, ?)", 0, 1, 1); assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) = (?, ?) ALLOW FILTERING", 0, 1, 1), - row(0, 0, 1, 1, 0), - row(0, 0, 1, 1, 1)); + row(0, 0, 1, 1, 0, 4), + row(0, 0, 1, 1, 1, 5)); - assertInvalidMessage("Partition key part b must be restricted since preceding part is", + assertInvalidMessage("Partition key parts: b must be restricted as other parts are", "SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?)) ALLOW FILTERING", 0, 1, 1); - assertInvalidMessage("Partition key part b must be restricted since preceding part is", + assertInvalidMessage("Partition key parts: b must be restricted as other parts are", "SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c) IN ((?)) AND f = ?", 0, 1, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c) IN ((?)) AND f = ? ALLOW FILTERING", 0, 1, 5), + row(0, 0, 1, 1, 1, 5)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c) IN ((?), (?)) AND f = ?", 0, 1, 2, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c) IN ((?), (?)) AND f = ? ALLOW FILTERING", 0, 1, 2, 5), + row(0, 0, 1, 1, 1, 5), + row(0, 0, 2, 0, 0, 5)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?)) AND f = ?", 0, 1, 0, 3); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?)) AND f = ? ALLOW FILTERING", 0, 1, 0, 3), + row(0, 0, 1, 0, 0, 3)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c) >= (?) AND f = ?", 0, 1, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c) >= (?) AND f = ? ALLOW FILTERING", 0, 1, 5), + row(0, 0, 1, 1, 1, 5), + row(0, 0, 2, 0, 0, 5)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) AND f = ?", 0, 1, 1, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) AND f = ? ALLOW FILTERING", 0, 1, 1, 5), + row(0, 0, 1, 1, 1, 5), + row(0, 0, 2, 0, 0, 5)); } + + @Test + public void testINWithDuplicateValue() throws Throwable + { + for (String compactOption : new String[] { "", " WITH COMPACT STORAGE" }) + { + createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY (k1, k2))" + compactOption); + execute("INSERT INTO %s (k1, k2, v) VALUES (?, ?, ?)", 1, 1, 1); + + assertRows(execute("SELECT * FROM %s WHERE k1 IN (?, ?) AND (k2) IN ((?), (?))", 1, 1, 1, 2), + row(1, 1, 1)); + assertRows(execute("SELECT * FROM %s WHERE k1 = ? AND (k2) IN ((?), (?))", 1, 1, 1), + row(1, 1, 1)); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java index 4e4cc50,604ec60..4bbec81 --- a/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java +++ b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java @@@ -436,6 -61,47 +436,73 @@@ public class SingleColumnRelationTest e for (int i = 0; i < 10000; i++) inValues.add(i); assertRows(execute("SELECT * FROM %s WHERE k=? AND c IN ?", 0, inValues), - row(0, 0, 0) - ); + row(0, 0, 0)); } + + @Test + public void testMultiplePartitionKeyWithIndex() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))"); + createIndex("CREATE INDEX ON %s (c)"); + createIndex("CREATE INDEX ON %s (f)"); + + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 0, 0, 0); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 1, 0, 1); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 0, 1, 1, 2); + + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 0, 0, 3); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 1, 0, 4); + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 1, 1, 1, 5); + + execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 2, 0, 0, 5); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c = ?", 0, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND c = ? ALLOW FILTERING", 0, 1), + row(0, 0, 1, 0, 0, 3), + row(0, 0, 1, 1, 0, 4), + row(0, 0, 1, 1, 1, 5)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c = ? AND d = ?", 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND c = ? AND d = ? ALLOW FILTERING", 0, 1, 1), + row(0, 0, 1, 1, 0, 4), + row(0, 0, 1, 1, 1, 5)); + - assertInvalidMessage("Partition key part b must be restricted since preceding part is", ++ assertInvalidMessage("Partition key parts: b must be restricted as other parts are", ++ "SELECT * FROM %s WHERE a = ? AND c IN (?) AND d IN (?) ALLOW FILTERING", 0, 1, 1); ++ ++ assertInvalidMessage("Partition key parts: b must be restricted as other parts are", ++ "SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1); ++ ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c IN (?) AND f = ?", 0, 1, 5); ++ assertRows(execute("SELECT * FROM %s WHERE a = ? AND c IN (?) AND f = ? ALLOW FILTERING", 0, 1, 5), ++ row(0, 0, 1, 1, 1, 5)); ++ ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c IN (?, ?) AND f = ?", 0, 1, 2, 5); ++ assertRows(execute("SELECT * FROM %s WHERE a = ? AND c IN (?, ?) AND f = ? ALLOW FILTERING", 0, 1, 2, 5), ++ row(0, 0, 1, 1, 1, 5), ++ row(0, 0, 2, 0, 0, 5)); ++ ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c IN (?) AND d IN (?) AND f = ?", 0, 1, 0, 3); ++ assertRows(execute("SELECT * FROM %s WHERE a = ? AND c IN (?) AND d IN (?) AND f = ? ALLOW FILTERING", 0, 1, 0, 3), ++ row(0, 0, 1, 0, 0, 3)); ++ ++ assertInvalidMessage("Partition key parts: b must be restricted as other parts are", + "SELECT * FROM %s WHERE a = ? AND c >= ? ALLOW FILTERING", 0, 1); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c >= ? AND f = ?", 0, 1, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND c >= ? AND f = ? ALLOW FILTERING", 0, 1, 5), + row(0, 0, 1, 1, 1, 5), + row(0, 0, 2, 0, 0, 5)); + ++ assertInvalidMessage("Cannot execute this query as it might involve data filtering", ++ "SELECT * FROM %s WHERE a = ? AND c = ? AND d >= ? AND f = ?", 0, 1, 1, 5); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND c = ? AND d >= ? AND f = ? ALLOW FILTERING", 0, 1, 1, 5), + row(0, 0, 1, 1, 1, 5)); - - assertInvalidMessage("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", - "SELECT * FROM %s WHERE a = ? AND d >= ? AND f = ?", 0, 1, 5); + } }