You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2015/02/10 22:11:05 UTC

[1/4] cassandra git commit: Fix multicolumn relations with indexes on some clustering cols

Repository: cassandra
Updated Branches:
  refs/heads/trunk 187624b11 -> fbc38cd3a


Fix multicolumn relations with indexes on some clustering cols

Patch by Benjamin Lerer; reviewed by Tyler Hobbs for CASSANDRA-8275


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/9649594c
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/9649594c
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/9649594c

Branch: refs/heads/trunk
Commit: 9649594c761dbb72e58ddd71a10f0794378337ca
Parents: 28c380c
Author: blerer <b_...@hotmail.com>
Authored: Tue Feb 10 15:07:02 2015 -0600
Committer: Tyler Hobbs <ty...@apache.org>
Committed: Tue Feb 10 15:07:02 2015 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |   2 +
 .../cql3/statements/SelectStatement.java        |  46 ++++--
 .../cassandra/cql3/MultiColumnRelationTest.java | 122 ++++++++++++++++
 .../cql3/SingleColumnRelationTest.java          | 145 +++++++++++++++++++
 4 files changed, 303 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/9649594c/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index fa9c77d..861730f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,6 @@
 2.0.13:
+ * Fix some multi-column relations with indexes on some clustering
+   columns (CASSANDRA-8275)
  * Fix IllegalArgumentException in dynamic snitch (CASSANDRA-8448)
  * Add support for UPDATE ... IF EXISTS (CASSANDRA-8610)
  * Fix reversal of list prepends (CASSANDRA-8733)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9649594c/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 19615b6..2fa57b9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -35,7 +35,6 @@ import org.apache.cassandra.cql3.CFDefinition.Name.Kind;
 import org.apache.cassandra.transport.messages.ResultMessage;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.context.CounterContext;
 import org.apache.cassandra.db.filter.*;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.dht.*;
@@ -83,8 +82,10 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
     /** Restrictions on non-primary key columns (i.e. secondary index restrictions) */
     private final Map<CFDefinition.Name, Restriction> metadataRestrictions = new HashMap<CFDefinition.Name, Restriction>();
 
-    // The name of all restricted names not covered by the key or index filter
-    private final Set<CFDefinition.Name> restrictedNames = new HashSet<CFDefinition.Name>();
+    // The map keys are the name of the columns that must be converted into IndexExpressions if a secondary index need
+    // to be used. The value specify if the column has an index that can be used to for the relation in which the column
+    // is specified.
+    private final Map<CFDefinition.Name, Boolean> restrictedNames = new HashMap<CFDefinition.Name, Boolean>();
     private Restriction.Slice sliceRestriction;
 
     private boolean isReversed;
@@ -1027,7 +1028,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             return Collections.emptyList();
 
         List<IndexExpression> expressions = new ArrayList<IndexExpression>();
-        for (CFDefinition.Name name : restrictedNames)
+        for (CFDefinition.Name name : restrictedNames.keySet())
         {
             Restriction restriction;
             switch (name.kind)
@@ -1068,12 +1069,21 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             }
             else
             {
-                List<ByteBuffer> values = restriction.values(variables);
+                ByteBuffer value;
+                if (restriction.isMultiColumn())
+                {
+                    List<ByteBuffer> values = restriction.values(variables);
+                    value = values.get(name.position);
+                }
+                else
+                {
+                    List<ByteBuffer> values = restriction.values(variables);
+                    if (values.size() != 1)
+                        throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
 
-                if (values.size() != 1)
-                    throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
+                    value = values.get(0);
+                }
 
-                ByteBuffer value = values.get(0);
                 validateIndexExpressionValue(value, name);
                 expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
             }
@@ -1496,7 +1506,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             // All (or none) of the partition key columns have been specified;
             // hence there is no need to turn these restrictions into index expressions.
             if (!stmt.usesSecondaryIndexing)
-                stmt.restrictedNames.removeAll(cfDef.partitionKeys());
+                stmt.restrictedNames.keySet().removeAll(cfDef.partitionKeys());
 
             if (stmt.selectsOnlyStaticColumns && stmt.hasClusteringColumnsRestriction())
                 throw new InvalidRequestException("Cannot restrict clustering columns when selecting only static columns");
@@ -1507,8 +1517,17 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             if (stmt.isKeyRange && hasQueriableClusteringColumnIndex)
                 stmt.usesSecondaryIndexing = true;
 
-            if (!stmt.usesSecondaryIndexing)
-                stmt.restrictedNames.removeAll(cfDef.clusteringColumns());
+            // The clustering columns that can be used to perform a slice filtering on the secondary index do not
+            // need to be converted into IndexExpressions. Therefore, if they are not indexed by an index that support
+            // the relation in which they have been specified, we can removes them from the restrictedNames map.
+            for (Name clusteringColumn : cfDef.clusteringColumns())
+            {
+                Boolean indexed = stmt.restrictedNames.get(clusteringColumn);
+                if (indexed == null)
+                    break;
+                if (!indexed)
+                    stmt.restrictedNames.remove(clusteringColumn);
+            }
 
             // Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
             // there is restrictions not covered by the PK.
@@ -1540,9 +1559,12 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             if (name == null)
                 handleUnrecognizedEntity(entity, relation);
 
-            stmt.restrictedNames.add(name);
             if (cfDef.cfm.getColumnDefinition(name.name.key).isIndexed() && relation.operator() == Relation.Type.EQ)
+            {
+                stmt.restrictedNames.put(name, Boolean.TRUE);
                 return new boolean[]{true, name.kind == CFDefinition.Name.Kind.COLUMN_ALIAS};
+            }
+            stmt.restrictedNames.put(name, Boolean.FALSE);
             return new boolean[]{false, false};
         }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9649594c/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
index ea4f1a6..30a9226 100644
--- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
@@ -76,6 +76,15 @@ public class MultiColumnRelationTest
                     "CREATE TABLE IF NOT EXISTS %s.multiple_clustering_reversed" + tableSuffix +
                         "(a int, b int, c int, d int, PRIMARY KEY (a, b, c, d)) WITH " + compactOption + " CLUSTERING ORDER BY (b DESC, c ASC, d DESC)");
         }
+
+        executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering_with_indices (a int, b int, c int, d int, e int, PRIMARY KEY (a, b, c, d))");
+        executeSchemaChange("CREATE INDEX ON %s.multiple_clustering_with_indices (b)");
+        executeSchemaChange("CREATE INDEX ON %s.multiple_clustering_with_indices (e)");
+
+        executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.partition_with_indices (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))");
+        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (c)");
+        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (f)");
+
         clientState = ClientState.forInternalCalls();
     }
 
@@ -1178,6 +1187,119 @@ public class MultiColumnRelationTest
         }
     }
 
+    @Test
+    public void testMultipleClusteringWithIndex() throws Throwable
+    {
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 0, 0, 0)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 1, 0, 1)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 1, 1, 2)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 0, 0, 0)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 1, 0, 1)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 1, 1, 2)");
+        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 2, 0, 0, 0)");
+
+        UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering_with_indices WHERE (b) = (1)");
+        assertEquals(3, results.size());
+        checkRow(0, results, 0, 1, 0, 0, 0);
+        checkRow(1, results, 0, 1, 1, 0, 1);
+        checkRow(2, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) = (1, 1) ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 1, 1, 0, 1);
+        checkRow(1, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) = (1, 1) AND e = 2 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) IN ((1)) AND e = 2 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) IN ((0), (1)) AND e = 2 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 2);
+        checkRow(1, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) IN ((0, 1)) AND e = 2 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) IN ((0, 1), (1, 1)) AND e = 2 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 2);
+        checkRow(1, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) >= (1) AND e = 2 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 1, 1, 1, 2);
+
+        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) >= (1, 1) AND e = 2 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 1, 1, 1, 2);
+    }
+
+    @Test
+    public void testPartitionWithIndex() throws Throwable
+    {
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 0, 0, 0)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 0, 1)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 1, 2)");
+
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 0, 0, 3)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 0, 4)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 1, 5)");
+
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 2, 0, 0, 5)");
+
+        UntypedResultSet results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) = (1) ALLOW FILTERING");
+        assertEquals(3, results.size());
+        checkRow(0, results, 0, 0, 1, 0, 0, 3);
+        checkRow(1, results, 0, 0, 1, 1, 0, 4);
+        checkRow(2, results, 0, 0, 1, 1, 1, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) = (1, 1) ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 0, 4);
+        checkRow(1, results, 0, 0, 1, 1, 1, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0  AND (c) IN ((1)) AND f = 5 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) IN ((1), (2)) AND f = 5 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+        checkRow(1, results, 0, 0, 2, 0, 0, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0  AND (c, d) IN ((1, 0)) AND f = 3 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 0, 1, 0, 0, 3);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) >= (1) AND f = 5 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+        checkRow(1, results, 0, 0, 2, 0, 0, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) >= (1, 1) AND f = 5 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+        checkRow(1, results, 0, 0, 2, 0, 0, 5);
+    }
+
+    @Test(expected=InvalidRequestException.class)
+    public void testMissingPartitionComponentWithInRestrictionOnIndexedColumn() throws Throwable
+    {
+        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) IN ((1, 1)) ALLOW FILTERING");
+    }
+
+    @Test(expected=InvalidRequestException.class)
+    public void testMissingPartitionComponentWithSliceRestrictionOnIndexedColumn() throws Throwable
+    {
+        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) >= (1, 1) ALLOW FILTERING");
+    }
+
     @Test(expected=InvalidRequestException.class)
     public void testPrepareLiteralInWithShortTuple() throws Throwable
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9649594c/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
new file mode 100644
index 0000000..34d3bf1
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.service.ClientState;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.cassandra.cql3.QueryProcessor.process;
+import static org.apache.cassandra.cql3.QueryProcessor.processInternal;
+import static org.junit.Assert.assertEquals;
+
+public class SingleColumnRelationTest
+{
+    static ClientState clientState;
+    static String keyspace = "single_column_relation_test";
+
+    @BeforeClass
+    public static void setUpClass() throws Throwable
+    {
+        SchemaLoader.loadSchema();
+        executeSchemaChange("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
+
+        executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.partition_with_indices (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))");
+        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (c)");
+        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (f)");
+
+        clientState = ClientState.forInternalCalls();
+    }
+
+    @AfterClass
+    public static void stopGossiper()
+    {
+        Gossiper.instance.stop();
+    }
+
+    private static void executeSchemaChange(String query) throws Throwable
+    {
+        try
+        {
+            process(String.format(query, keyspace), ConsistencyLevel.ONE);
+        } catch (RuntimeException exc)
+        {
+            throw exc.getCause();
+        }
+    }
+
+    private static UntypedResultSet execute(String query) throws Throwable
+    {
+        try
+        {
+            return processInternal(String.format(query, keyspace));
+        } catch (RuntimeException exc)
+        {
+            if (exc.getCause() != null)
+                throw exc.getCause();
+            throw exc;
+        }
+    }
+
+    @Test
+    public void testPartitionWithIndex() throws Throwable
+    {
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 0, 0, 0)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 0, 1)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 1, 2)");
+
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 0, 0, 3)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 0, 4)");
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 1, 5)");
+
+        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 2, 0, 0, 5)");
+
+        UntypedResultSet results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 ALLOW FILTERING");
+        assertEquals(3, results.size());
+        checkRow(0, results, 0, 0, 1, 0, 0, 3);
+        checkRow(1, results, 0, 0, 1, 1, 0, 4);
+        checkRow(2, results, 0, 0, 1, 1, 1, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 AND d = 1 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 0, 4);
+        checkRow(1, results, 0, 0, 1, 1, 1, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c >= 1 AND f = 5 ALLOW FILTERING");
+        assertEquals(2, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+        checkRow(1, results, 0, 0, 2, 0, 0, 5);
+
+        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 AND d >= 1 AND f = 5 ALLOW FILTERING");
+        assertEquals(1, results.size());
+        checkRow(0, results, 0, 0, 1, 1, 1, 5);
+    }
+
+    @Test(expected=InvalidRequestException.class)
+    public void testMissingPartitionComponentAndFileringOnTheSecondClusteringColumnWithoutAllowFiltering() throws Throwable
+    {
+        execute("SELECT * FROM %s.partition_with_indices WHERE d >= 1 AND f = 5");
+    }
+
+    @Test(expected=InvalidRequestException.class)
+    public void testMissingPartitionComponentWithSliceRestrictionOnIndexedColumn() throws Throwable
+    {
+        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c >= 1 ALLOW FILTERING");
+    }
+
+    private static void checkRow(int rowIndex, UntypedResultSet results, Integer... expectedValues)
+    {
+        List<UntypedResultSet.Row> rows = newArrayList(results.iterator());
+        UntypedResultSet.Row row = rows.get(rowIndex);
+        Iterator<ColumnSpecification> columns = row.getColumns().iterator();
+        for (Integer expected : expectedValues)
+        {
+            String columnName = columns.next().name.toString();
+            int actual = row.getInt(columnName);
+            assertEquals(String.format("Expected value %d for column %s in row %d, but got %s", actual, columnName, rowIndex, expected),
+                         (long) expected, actual);
+        }
+    }
+}


[3/4] cassandra git commit: Merge branch 'cassandra-2.1' into trunk

Posted by ty...@apache.org.
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<Restrictions> indexRestrictions = new ArrayList<>();
 +
 +    /**
 +     * <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 cfm the column family meta data
 +     * @return a new empty <code>StatementRestrictions</code>.
 +     */
 +    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<Relation> 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<IndexExpression> expressions, QueryOptions options) throws InvalidRequestException
-                 {
-                     List<IndexExpression> 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 <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) 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 <code>true</code> if the partition key has some unrestricted components, <code>false</code> 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<ColumnIdentifier> getPartitionKeyUnrestrictedComponents()
 +    {
 +        List<ColumnDefinition> list = new ArrayList<>(cfm.partitionKeyColumns());
 +        list.removeAll(partitionKeyRestrictions.getColumnDefs());
 +        return ColumnDefinition.toIdentifiers(list);
 +    }
 +
 +    /**
 +     * Processes the clustering column restrictions.
 +     *
 +     * @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise
 +     * @param selectACollection <code>true</code> 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<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))
 +                {
 +                    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<IndexExpression> getIndexExpressions(QueryOptions options) throws InvalidRequestException
++    public List<IndexExpression> getIndexExpressions(SecondaryIndexManager indexManager,
++                                                     QueryOptions options) throws InvalidRequestException
 +    {
 +        if (!usesSecondaryIndexing || indexRestrictions.isEmpty())
 +            return Collections.emptyList();
 +
 +        List<IndexExpression> 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<ByteBuffer> 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<RowPosition> getPartitionKeyBounds(QueryOptions options) throws InvalidRequestException
 +    {
 +        IPartitioner p = StorageService.getPartitioner();
 +
 +        if (partitionKeyRestrictions.isOnToken())
 +        {
 +            return getPartitionKeyBoundsForTokenRestrictions(p, options);
 +        }
 +
 +        return getPartitionKeyBounds(p, options);
 +    }
 +
 +    private AbstractBounds<RowPosition> 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<RowPosition> 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 <code>true</code> if the query does not contains any restriction on the clustering columns,
 +     * <code>false</code> 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 <code>Composite</code>s.
 +     *
 +     * @param options the query options
 +     * @return the requested clustering columns as <code>Composite</code>s
 +     * @throws InvalidRequestException if the query is not valid
 +     */
 +    public List<Composite> getClusteringColumnsAsComposites(QueryOptions options) throws InvalidRequestException
 +    {
 +        return clusteringColumnsRestrictions.valuesAsComposites(options);
 +    }
 +
 +    /**
 +     * Returns the bounds (start or end) of the clustering columns as <code>Composites</code>.
 +     *
 +     * @param b the bound type
 +     * @param options the query options
 +     * @return the bounds (start or end) of the clustering columns as <code>Composites</code>
 +     * @throws InvalidRequestException if the request is not valid
 +     */
 +    public List<Composite> 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<ByteBuffer> 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 <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()
 +    {
 +        // 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 <code>true</code> if the query need to use filtering, <code>false</code> 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 <code>true</code> if the query has some restrictions on the clustering columns,
 +     * <code>false</code> 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;
 +
 +/**
 + * <code>Restriction</code> using the token function.
 + */
 +public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
 +{
 +    /**
 +     * The definition of the columns to which apply the token restriction.
 +     */
 +    protected final List<ColumnDefinition> columnDefs;
 +
 +    /**
 +     * Creates a new <code>TokenRestriction</code> 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<ColumnDefinition> columnDefs)
 +    {
 +        super(ctype);
 +        this.columnDefs = columnDefs;
 +    }
 +
 +    @Override
 +    public  boolean isOnToken()
 +    {
 +        return true;
 +    }
 +
 +    @Override
 +    public Collection<ColumnDefinition> getColumnDefs()
 +    {
 +        return columnDefs;
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager)
 +    {
 +        return false;
 +    }
 +
 +    @Override
-     public void addIndexExpressionTo(List<IndexExpression> expressions, QueryOptions options)
++    public final void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
++                                     QueryOptions options)
 +    {
 +        throw new UnsupportedOperationException("Index expression cannot be created for token restriction");
 +    }
 +
 +    @Override
 +    public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    @Override
 +    public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        throw new UnsupportedOperationException();
 +    }
 +
 +    /**
 +     * Returns the column names as a comma separated <code>String</code>.
 +     *
 +     * @return the column names as a comma separated <code>String</code>.
 +     */
 +    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 <code>TokenRestriction</code>.
 +     * @param otherRestriction the <code>TokenRestriction</code> to merge with.
 +     */
 +    protected abstract PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Converts the specified restriction into a <code>PrimaryKeyRestrictions</code>.
 +     *
 +     * @param restriction the restriction to convert
 +     * @return a <code>PrimaryKeyRestrictions</code>
 +     * @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<ColumnDefinition> 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<ByteBuffer> 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<ColumnDefinition> 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<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            return slice.hasBound(b);
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> 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<ColumnDefinition> 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<Composite> buildBound(Bound bound,
 -                                              List<ColumnDefinition> 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<ColumnDefinition> 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<ByteBuffer> 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<Composite> 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<Composite> buildMultiColumnSliceBound(Bound bound,
 -                                                              List<ColumnDefinition> defs,
 -                                                              MultiColumnRestriction.Slice slice,
 -                                                              boolean isReversed,
 -                                                              CBuilder builder,
 -                                                              QueryOptions options) throws InvalidRequestException
 -    {
 -        Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
 -
 -        Iterator<ColumnDefinition> 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<ByteBuffer> 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<Composite> buildMultiColumnInBound(Bound bound,
 -                                                           List<ColumnDefinition> defs,
 -                                                           MultiColumnRestriction.IN restriction,
 -                                                           boolean isReversed,
 -                                                           CBuilder builder,
 -                                                           CType type,
 -                                                           QueryOptions options) throws InvalidRequestException
 -    {
 -        List<List<ByteBuffer>> 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<Composite> inValues = new TreeSet<>(isReversed ? type.reverseComparator() : type);
 -        for (List<ByteBuffer> 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<Composite> buildMultiColumnEQBound(Bound bound,
 -                                                           List<ColumnDefinition> defs,
 -                                                           MultiColumnRestriction.EQ restriction,
 -                                                           boolean isReversed,
 -                                                           CBuilder builder,
 -                                                           QueryOptions options) throws InvalidRequestException
 -    {
 -        Bound eocBound = isReversed ? Bound.reverse(bound) : bound;
 -        List<ByteBuffer> 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<Composite> getRequestedBound(Bound b, QueryOptions options) throws InvalidRequestException
 -    {
 -        assert isColumnRange();
 -        return buildBound(b, cfm.clusteringColumns(), columnRestrictions, isReversed, cfm.comparator, options);
 -    }
 -
      public List<IndexExpression> getValidatedIndexExpressions(QueryOptions options) throws InvalidRequestException
      {
 -        if (!usesSecondaryIndexing || restrictedColumns.isEmpty())
 +        if (!restrictions.usesSecondaryIndexing())
              return Collections.emptyList();
  
-         List<IndexExpression> expressions = restrictions.getIndexExpressions(options);
- 
 -        List<IndexExpression> expressions = new ArrayList<IndexExpression>();
 -        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<ByteBuffer> values = restriction.values(options);
 -                    value = values.get(def.position());
 -                }
 -                else
 -                {
 -                    List<ByteBuffer> values = restriction.values(options);
 -                    if (values.size() != 1)
 -                        throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
++        List<IndexExpression> 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);
+     }
  }


[4/4] cassandra git commit: Merge branch 'cassandra-2.1' into trunk

Posted by ty...@apache.org.
Merge branch 'cassandra-2.1' into trunk


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/fbc38cd3
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/fbc38cd3
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/fbc38cd3

Branch: refs/heads/trunk
Commit: fbc38cd3a2dbda77aeca4a84765550fc571031ad
Parents: 187624b 07ffe1b
Author: Tyler Hobbs <ty...@apache.org>
Authored: Tue Feb 10 15:10:49 2015 -0600
Committer: Tyler Hobbs <ty...@apache.org>
Committed: Tue Feb 10 15:10:49 2015 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |   3 +
 .../ForwardingPrimaryKeyRestrictions.java       |   3 +-
 .../restrictions/MultiColumnRestriction.java    |  63 ++++++------
 .../cql3/restrictions/Restriction.java          |   2 +
 .../cql3/restrictions/Restrictions.java         |   2 +
 .../SingleColumnPrimaryKeyRestrictions.java     |  26 ++++-
 .../restrictions/SingleColumnRestriction.java   |   6 ++
 .../restrictions/SingleColumnRestrictions.java  |   3 +-
 .../restrictions/StatementRestrictions.java     |  33 +-----
 .../cql3/restrictions/TokenRestriction.java     |   4 +-
 .../cql3/statements/SelectStatement.java        |   5 +-
 .../cassandra/cql3/MultiColumnRelationTest.java | 100 ++++++++++++++-----
 .../cql3/SingleColumnRelationTest.java          |  67 +++++++++++++
 13 files changed, 226 insertions(+), 91 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/CHANGES.txt
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
index 8a57292,0000000..5492c2b
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java
@@@ -1,159 -1,0 +1,160 @@@
 +/*
 + * 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.List;
 +
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A <code>PrimaryKeyRestrictions</code> which forwards all its method calls to another 
 + * <code>PrimaryKeyRestrictions</code>. Subclasses should override one or more methods to modify the behavior 
 + * of the backing <code>PrimaryKeyRestrictions</code> as desired per the decorator pattern. 
 + */
 +abstract class ForwardingPrimaryKeyRestrictions implements PrimaryKeyRestrictions
 +{
 +    /**
 +     * Returns the backing delegate instance that methods are forwarded to.
 +     * @return the backing delegate instance that methods are forwarded to.
 +     */
 +    protected abstract PrimaryKeyRestrictions getDelegate();
 +
 +    @Override
 +    public boolean usesFunction(String ksName, String functionName)
 +    {
 +        return getDelegate().usesFunction(ksName, functionName);
 +    }
 +
 +    @Override
 +    public Collection<ColumnDefinition> getColumnDefs()
 +    {
 +        return getDelegate().getColumnDefs();
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        return getDelegate().mergeWith(restriction);
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager)
 +    {
 +        return getDelegate().hasSupportingIndex(secondaryIndexManager);
 +    }
 +
 +    @Override
 +    public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +    {
 +        return getDelegate().values(options);
 +    }
 +
 +    @Override
 +    public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +    {
 +        return getDelegate().valuesAsComposites(options);
 +    }
 +
 +    @Override
 +    public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        return getDelegate().bounds(bound, options);
 +    }
 +
 +    @Override
 +    public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        return getDelegate().boundsAsComposites(bound, options);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound bound)
 +    {
 +        return getDelegate().isInclusive(bound.reverse());
 +    }
 +
 +    @Override
 +    public boolean isEmpty()
 +    {
 +        return getDelegate().isEmpty();
 +    }
 +
 +    @Override
 +    public int size()
 +    {
 +        return getDelegate().size();
 +    }
 +
 +    @Override
 +    public boolean isOnToken()
 +    {
 +        return getDelegate().isOnToken();
 +    }
 +
 +    @Override
 +    public boolean isSlice()
 +    {
 +        return getDelegate().isSlice();
 +    }
 +
 +    @Override
 +    public boolean isEQ()
 +    {
 +        return getDelegate().isEQ();
 +    }
 +
 +    @Override
 +    public boolean isIN()
 +    {
 +        return getDelegate().isIN();
 +    }
 +
 +    @Override
 +    public boolean isContains()
 +    {
 +        return getDelegate().isContains();
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return getDelegate().isMultiColumn();
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        return getDelegate().hasBound(b);
 +    }
 +
 +    @Override
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options) throws InvalidRequestException
 +    {
-         getDelegate().addIndexExpressionTo(expressions, options);
++        getDelegate().addIndexExpressionTo(expressions, indexManager, options);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
index 2d6deeb,0000000..9f6ab4c
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
@@@ -1,518 -1,0 +1,519 @@@
 +/*
 + * 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 org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.AbstractMarker;
 +import org.apache.cassandra.cql3.Operator;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.Term;
 +import org.apache.cassandra.cql3.Tuples;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CBuilder;
 +import org.apache.cassandra.db.composites.CType;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.composites.Composites;
 +import org.apache.cassandra.db.index.SecondaryIndex;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +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;
 +
 +public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestrictions
 +{
 +    /**
 +     * The columns to which the restriction apply.
 +     */
 +    protected final List<ColumnDefinition> columnDefs;
 +
 +    public MultiColumnRestriction(CType ctype, List<ColumnDefinition> columnDefs)
 +    {
 +        super(ctype);
 +        this.columnDefs = columnDefs;
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return true;
 +    }
 +
 +    @Override
 +    public Collection<ColumnDefinition> getColumnDefs()
 +    {
 +        return columnDefs;
 +    }
 +
 +    @Override
 +    public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +    {
 +        return Composites.toByteBuffers(valuesAsComposites(options));
 +    }
 +
 +    @Override
 +    public final PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +    {
 +            checkTrue(otherRestriction.isMultiColumn(),
 +                      "Mixing single column relations and multi column relations on clustering columns is not allowed");
 +            return doMergeWith((PrimaryKeyRestrictions) otherRestriction);
 +    }
 +
 +    protected abstract PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Returns the names of the columns that are specified within this <code>Restrictions</code> and the other one
 +     * as a comma separated <code>String</code>.
 +     *
 +     * @param otherRestrictions the other restrictions
 +     * @return the names of the columns that are specified within this <code>Restrictions</code> and the other one
 +     * as a comma separated <code>String</code>.
 +     */
 +    protected final String getColumnsInCommons(Restrictions otherRestrictions)
 +    {
 +        Set<ColumnDefinition> commons = new HashSet<>(getColumnDefs());
 +        commons.retainAll(otherRestrictions.getColumnDefs());
 +        StringBuilder builder = new StringBuilder();
 +        for (ColumnDefinition columnDefinition : commons)
 +        {
 +            if (builder.length() != 0)
 +                builder.append(" ,");
 +            builder.append(columnDefinition.name);
 +        }
 +        return builder.toString();
 +    }
 +
 +    @Override
 +    public final boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        for (ColumnDefinition columnDef : columnDefs)
 +        {
 +            SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes);
 +            if (index != null && isSupportedBy(index))
 +                return true;
 +        }
 +        return false;
 +    }
 +
++    @Override
++    public final void addIndexExpressionTo(List<IndexExpression> expressions,
++                                           SecondaryIndexManager indexManager,
++                                           QueryOptions options) throws InvalidRequestException
++    {
++        for (ColumnDefinition columnDef : columnDefs)
++        {
++            SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes);
++            if (index != null && isSupportedBy(index))
++                expressions.add(getIndexExpression(columnDef, options));
++        }
++    }
++
++    /**
++     * Returns the <code>IndexExpression</code> for the specified column.
++     *
++     * @param columnDef the column definition
++     * @param options the query options
++     * @return the <code>IndexExpression</code> for the specified column
++     */
++    protected IndexExpression getIndexExpression(ColumnDefinition columnDef,
++                                                 QueryOptions options) throws InvalidRequestException
++    {
++        // Except for EQ this operation is not supported
++        throw new UnsupportedOperationException();
++    }
++
 +    /**
-      * Check if this type of restriction is supported for the specified column by the specified index.
++     * Check if this type of restriction is supported for by the specified index.
 +     * @param index the Secondary index
 +     *
 +     * @return <code>true</code> this type of restriction is supported by the specified index,
 +     * <code>false</code> otherwise.
 +     */
 +    protected abstract boolean isSupportedBy(SecondaryIndex index);
 +
 +    public static class EQ  extends MultiColumnRestriction
 +    {
 +        protected final Term value;
 +
 +        public EQ(CType ctype, List<ColumnDefinition> columnDefs, Term value)
 +        {
 +            super(ctype, columnDefs);
 +            this.value = value;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return usesFunction(value, ksName, functionName);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("EQ(%s)", value);
 +        }
 +
 +        @Override
 +        public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal",
 +                                 getColumnsInCommons(otherRestriction));
 +        }
 +
 +        @Override
 +        public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +        {
 +            return Collections.singletonList(compositeValue(options));
 +        }
 +
 +        @Override
 +        public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +        {
 +            Composite prefix = compositeValue(options);
 +            return Collections.singletonList(ctype.size() > prefix.size() && bound.isEnd()
 +                                             ? prefix.end()
 +                                             : prefix);
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.EQ);
 +        }
 +
 +        private Composite compositeValue(QueryOptions options) throws InvalidRequestException
 +        {
 +            CBuilder builder = ctype.builder();
 +            Tuples.Value t = ((Tuples.Value) value.bind(options));
 +            List<ByteBuffer> values = t.getElements();
 +            for (int i = 0; i < values.size(); i++)
 +            {
 +                ByteBuffer component = checkNotNull(values.get(i),
 +                                                    "Invalid null value in condition for column %s",
 +                                                    columnDefs.get(i).name);
 +                builder.add(component);
 +            }
 +
 +            return builder.build();
 +        }
 +
 +        @Override
-         public final void addIndexExpressionTo(List<IndexExpression> expressions,
-                                                QueryOptions options) throws InvalidRequestException
++        protected final IndexExpression getIndexExpression(ColumnDefinition columnDef,
++                                                           QueryOptions options) throws InvalidRequestException
 +        {
 +            Tuples.Value t = ((Tuples.Value) value.bind(options));
 +            List<ByteBuffer> values = t.getElements();
-             for (int i = 0; i < values.size(); i++)
-             {
-                 ColumnDefinition columnDef = columnDefs.get(i);
-                 ByteBuffer component = validateIndexedValue(columnDef, values.get(i));
-                 expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component));
-             }
++            ByteBuffer component = validateIndexedValue(columnDef, values.get(columnDef.position()));
++            return new IndexExpression(columnDef.name.bytes, Operator.EQ, component);
 +        }
 +    }
 +
 +    public abstract static class IN extends MultiColumnRestriction
 +    {
 +        @Override
 +        public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +        {
 +            CBuilder builder = ctype.builder();
 +            List<List<ByteBuffer>> splitInValues = splitValues(options);
 +            // 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<Composite> inValues = new TreeSet<>(ctype);
 +            for (List<ByteBuffer> components : splitInValues)
 +            {
 +                for (int i = 0; i < components.size(); i++)
 +                    checkNotNull(components.get(i), "Invalid null value in condition for column " + columnDefs.get(i).name);
 +
 +                inValues.add(builder.buildWith(components));
 +            }
 +            return new ArrayList<>(inValues);
 +        }
 +
 +        @Override
 +        public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +        {
 +            CBuilder builder = ctype.builder();
 +            List<List<ByteBuffer>> splitInValues = splitValues(options);
 +            // 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<Composite> inValues = new TreeSet<>(ctype);
 +            for (List<ByteBuffer> components : splitInValues)
 +            {
 +                for (int i = 0; i < components.size(); i++)
 +                    checkNotNull(components.get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name);
 +
 +                Composite prefix = builder.buildWith(components);
 +                inValues.add(bound.isEnd() && builder.remainingCount() - components.size() > 0
 +                             ? prefix.end()
 +                             : prefix);
 +            }
 +            return new ArrayList<>(inValues);
 +        }
 +
-         @Override
-         public void addIndexExpressionTo(List<IndexExpression> expressions,
-                                          QueryOptions options) throws InvalidRequestException
-         {
-             List<List<ByteBuffer>> splitInValues = splitValues(options);
-             checkTrue(splitInValues.size() == 1, "IN restrictions are not supported on indexed columns");
- 
-             List<ByteBuffer> values = splitInValues.get(0);
-             checkTrue(values.size() == 1, "IN restrictions are not supported on indexed columns");
- 
-             ColumnDefinition columnDef = columnDefs.get(0);
-             ByteBuffer component = validateIndexedValue(columnDef, values.get(0));
-             expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component));
-         }
- 
 +        public IN(CType ctype, List<ColumnDefinition> columnDefs)
 +        {
 +            super(ctype, columnDefs);
 +        }
 +
 +        @Override
 +        public boolean isIN()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestrictions) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes a IN",
 +                                 getColumnsInCommons(otherRestrictions));
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.IN);
 +        }
 +
 +        protected abstract List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException;
 +    }
 +
 +    /**
 +     * An IN restriction that has a set of terms for in values.
 +     * For example: "SELECT ... WHERE (a, b, c) IN ((1, 2, 3), (4, 5, 6))" or "WHERE (a, b, c) IN (?, ?)"
 +     */
 +    public static class InWithValues extends MultiColumnRestriction.IN
 +    {
 +        protected final List<Term> values;
 +
 +        public InWithValues(CType ctype, List<ColumnDefinition> columnDefs, List<Term> values)
 +        {
 +            super(ctype, columnDefs);
 +            this.values = values;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return usesFunction(values, ksName, functionName);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("IN(%s)", values);
 +        }
 +
 +        @Override
 +        protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<List<ByteBuffer>> buffers = new ArrayList<>(values.size());
 +            for (Term value : values)
 +            {
 +                Term.MultiItemTerminal term = (Term.MultiItemTerminal) value.bind(options);
 +                buffers.add(term.getElements());
 +            }
 +            return buffers;
 +        }
 +    }
 +
 +    /**
 +     * An IN restriction that uses a single marker for a set of IN values that are tuples.
 +     * For example: "SELECT ... WHERE (a, b, c) IN ?"
 +     */
 +    public static class InWithMarker extends MultiColumnRestriction.IN
 +    {
 +        protected final AbstractMarker marker;
 +
 +        public InWithMarker(CType ctype, List<ColumnDefinition> columnDefs, AbstractMarker marker)
 +        {
 +            super(ctype, columnDefs);
 +            this.marker = marker;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return false;
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "IN ?";
 +        }
 +
 +        @Override
 +        protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException
 +        {
 +            Tuples.InMarker inMarker = (Tuples.InMarker) marker;
 +            Tuples.InValue inValue = inMarker.bind(options);
 +            checkNotNull(inValue, "Invalid null value for IN restriction");
 +            return inValue.getSplitValues();
 +        }
 +    }
 +
 +    public static class Slice extends MultiColumnRestriction
 +    {
 +        private final TermSlice slice;
 +
 +        public Slice(CType ctype, List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term)
 +        {
 +            this(ctype, columnDefs, TermSlice.newInstance(bound, inclusive, term));
 +        }
 +
 +        private Slice(CType ctype, List<ColumnDefinition> columnDefs, TermSlice slice)
 +        {
 +            super(ctype, columnDefs);
 +            this.slice = slice;
 +        }
 +
 +        @Override
 +        public boolean isSlice()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            return Composites.toByteBuffers(boundsAsComposites(b, options));
 +        }
 +
 +        @Override
 +        public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +        {
 +            CBuilder builder = ctype.builder();
 +            Iterator<ColumnDefinition> iter = columnDefs.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 = !firstName.isReversedType() ? bound : bound.reverse();
 +
 +            if (!hasBound(firstComponentBound))
 +            {
 +                Composite prefix = builder.build();
 +                return Collections.singletonList(builder.remainingCount() > 0 && bound.isEnd()
 +                        ? prefix.end()
 +                        : prefix);
 +            }
 +
 +            List<ByteBuffer> vals = componentBounds(firstComponentBound, options);
 +
 +            ByteBuffer v = checkNotNull(vals.get(firstName.position()), "Invalid null value in condition for column %s", firstName.name);
 +            builder.add(v);
 +
 +            while (iter.hasNext())
 +            {
 +                ColumnDefinition def = iter.next();
 +                if (def.position() >= vals.size())
 +                    break;
 +
 +                v = checkNotNull(vals.get(def.position()), "Invalid null value in condition for column %s", def.name);
 +                builder.add(v);
 +            }
 +            Composite.EOC eoc =  eocFor(this, bound, firstComponentBound);
 +            return Collections.singletonList(builder.build().withEOC(eoc));
 +        }
 +
 +        @Override
-         public void addIndexExpressionTo(List<IndexExpression> expressions,
-                                          QueryOptions options) throws InvalidRequestException
-         {
-             throw invalidRequest("Slice restrictions are not supported on indexed columns which are part of a multi column relation");
-         }
- 
-         @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return slice.isSupportedBy(index);
 +        }
 +
 +        private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound)
 +        {
 +            if (eocBound.isStart())
 +                return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END;
 +
 +            return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START;
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            return slice.hasBound(b);
 +        }
 +
 +        @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
 +        public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isSlice(),
 +                      "Column \"%s\" cannot be restricted by both an equality and an inequality relation",
 +                      getColumnsInCommons(otherRestriction));
 +
 +            Slice otherSlice = (Slice) otherRestriction;
 +
 +            checkFalse(hasBound(Bound.START) && otherSlice.hasBound(Bound.START),
 +                       "More than one restriction was found for the start bound on %s",
 +                       getColumnsInCommons(otherRestriction));
 +            checkFalse(hasBound(Bound.END) && otherSlice.hasBound(Bound.END),
 +                       "More than one restriction was found for the end bound on %s",
 +                       getColumnsInCommons(otherRestriction));
 +
 +            List<ColumnDefinition> newColumnDefs = size() >= otherSlice.size() ?  columnDefs : otherSlice.columnDefs;
 +            return new Slice(ctype,  newColumnDefs, slice.merge(otherSlice.slice));
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "SLICE" + slice;
 +        }
 +
 +        /**
 +         * Similar to bounds(), but returns one ByteBuffer per-component in the bound instead of a single
 +         * ByteBuffer to represent the entire bound.
 +         * @param b the bound type
 +         * @param options the query options
 +         * @return one ByteBuffer per-component in the bound
 +         * @throws InvalidRequestException if the components cannot be retrieved
 +         */
 +        private List<ByteBuffer> componentBounds(Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            Tuples.Value value = (Tuples.Value) slice.bound(b).bind(options);
 +            return value.getElements();
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
index d0ed193,0000000..f6d0c73
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java
@@@ -1,97 -1,0 +1,99 @@@
 +/*
 + * 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.List;
 +
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * A restriction/clause on a column.
 + * The goal of this class being to group all conditions for a column in a SELECT.
 + */
 +public interface Restriction
 +{
 +    public boolean isOnToken();
 +    public boolean isSlice();
 +    public boolean isEQ();
 +    public boolean isIN();
 +    public boolean isContains();
 +    public boolean isMultiColumn();
 +
 +    public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException;
 +
 +    /**
 +     * Returns <code>true</code> if one of the restrictions use the specified function.
 +     *
 +     * @param ksName the keyspace name
 +     * @param functionName the function name
 +     * @return <code>true</code> if one of the restrictions use the specified function, <code>false</code> otherwise.
 +     */
 +    public boolean usesFunction(String ksName, String functionName);
 +
 +    /**
 +     * Checks if the specified bound is set or not.
 +     * @param b the bound type
 +     * @return <code>true</code> if the specified bound is set, <code>false</code> otherwise
 +     */
 +    public boolean hasBound(Bound b);
 +
 +    public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException;
 +
 +    /**
 +     * Checks if the specified bound is inclusive or not.
 +     * @param b the bound type
 +     * @return <code>true</code> if the specified bound is inclusive, <code>false</code> otherwise
 +     */
 +    public boolean isInclusive(Bound b);
 +
 +    /**
 +     * Merges this restriction with the specified one.
 +     *
 +     * @param otherRestriction the restriction to merge into this one
 +     * @return the restriction resulting of the merge
 +     * @throws InvalidRequestException if the restrictions cannot be merged
 +     */
 +    public Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException;
 +
 +    /**
 +     * Check if the restriction is on indexed columns.
 +     *
 +     * @param indexManager the index manager
 +     * @return <code>true</code> if the restriction is on indexed columns, <code>false</code>
 +     */
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager);
 +
 +    /**
 +     * Adds to the specified list the <code>IndexExpression</code>s corresponding to this <code>Restriction</code>.
 +     *
 +     * @param expressions the list to add the <code>IndexExpression</code>s to
++     * @param indexManager the secondary index manager
 +     * @param options the query options
 +     * @throws InvalidRequestException if this <code>Restriction</code> cannot be converted into 
 +     * <code>IndexExpression</code>s
 +     */
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options)
 +                                     throws InvalidRequestException;
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java
index cf2555e,0000000..3cfe4ab
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java
@@@ -1,82 -1,0 +1,84 @@@
 +/*
 + * 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.util.Collection;
 +import java.util.List;
 +
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * Sets of restrictions
 + */
 +interface Restrictions
 +{
 +    /**
 +     * Returns the column definitions in position order.
 +     * @return the column definitions in position order.
 +     */
 +    public Collection<ColumnDefinition> getColumnDefs();
 +
 +    /**
 +     * Returns <code>true</code> if one of the restrictions use the specified function.
 +     *
 +     * @param ksName the keyspace name
 +     * @param functionName the function name
 +     * @return <code>true</code> if one of the restrictions use the specified function, <code>false</code> otherwise.
 +     */
 +    public boolean usesFunction(String ksName, String functionName);
 +
 +    /**
 +     * Check if the restriction is on indexed columns.
 +     *
 +     * @param indexManager the index manager
 +     * @return <code>true</code> if the restriction is on indexed columns, <code>false</code>
 +     */
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager);
 +
 +    /**
 +     * Adds to the specified list the <code>IndexExpression</code>s corresponding to this <code>Restriction</code>.
 +     *
 +     * @param expressions the list to add the <code>IndexExpression</code>s to
++     * @param indexManager the secondary index manager
 +     * @param options the query options
 +     * @throws InvalidRequestException if this <code>Restriction</code> cannot be converted into
 +     * <code>IndexExpression</code>s
 +     */
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options)
 +                                     throws InvalidRequestException;
 +
 +    /**
 +     * Checks if this <code>SingleColumnPrimaryKeyRestrictions</code> is empty or not.
 +     *
 +     * @return <code>true</code> if this <code>SingleColumnPrimaryKeyRestrictions</code> is empty, <code>false</code> otherwise.
 +     */
 +    boolean isEmpty();
 +
 +    /**
 +     * Returns the number of columns that have a restriction.
 +     *
 +     * @return the number of columns that have a restriction.
 +     */
 +    public int size();
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
index e109036,0000000..945479a
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
@@@ -1,305 -1,0 +1,327 @@@
 +/*
 + * 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 org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.statements.Bound;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.composites.CBuilder;
 +import org.apache.cassandra.db.composites.CType;
 +import org.apache.cassandra.db.composites.Composite;
 +import org.apache.cassandra.db.composites.Composite.EOC;
 +import org.apache.cassandra.db.composites.Composites;
 +import org.apache.cassandra.db.composites.CompositesBuilder;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +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;
 +
 +/**
 + * A set of single column restrictions on a primary key part (partition key or clustering key).
 + */
 +final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestrictions
 +{
 +    /**
 +     * The restrictions.
 +     */
 +    private final SingleColumnRestrictions restrictions;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to an EQ, <code>false</code> otherwise.
 +     */
 +    private boolean eq;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to an IN, <code>false</code> otherwise.
 +     */
 +    private boolean in;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to a Slice, <code>false</code> otherwise.
 +     */
 +    private boolean slice;
 +
 +    /**
 +     * <code>true</code> if the restrictions are corresponding to a Contains, <code>false</code> otherwise.
 +     */
 +    private boolean contains;
 +
 +    public SingleColumnPrimaryKeyRestrictions(CType ctype)
 +    {
 +        super(ctype);
 +        this.restrictions = new SingleColumnRestrictions();
 +        this.eq = true;
 +    }
 +
 +    private SingleColumnPrimaryKeyRestrictions(SingleColumnPrimaryKeyRestrictions primaryKeyRestrictions,
 +                                               SingleColumnRestriction restriction) throws InvalidRequestException
 +    {
 +        super(primaryKeyRestrictions.ctype);
 +        this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction);
 +
 +        if (!primaryKeyRestrictions.isEmpty())
 +        {
 +            ColumnDefinition lastColumn = primaryKeyRestrictions.restrictions.lastColumn();
 +            ColumnDefinition newColumn = restriction.getColumnDef();
 +
 +            checkFalse(primaryKeyRestrictions.isSlice() && newColumn.position() > lastColumn.position(),
 +                       "Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)",
 +                       newColumn.name,
 +                       lastColumn.name);
 +
 +            if (newColumn.position() < lastColumn.position())
 +                checkFalse(restriction.isSlice(),
 +                           "PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)",
 +                           restrictions.nextColumn(newColumn).name,
 +                           newColumn.name);
 +        }
 +
 +        if (restriction.isSlice() || primaryKeyRestrictions.isSlice())
 +            this.slice = true;
 +        else if (restriction.isContains() || primaryKeyRestrictions.isContains())
 +            this.contains = true;
 +        else if (restriction.isIN())
 +            this.in = true;
 +        else
 +            this.eq = true;
 +    }
 +
 +    @Override
 +    public boolean isSlice()
 +    {
 +        return slice;
 +    }
 +
 +    @Override
 +    public boolean isEQ()
 +    {
 +        return eq;
 +    }
 +
 +    @Override
 +    public boolean isIN()
 +    {
 +        return in;
 +    }
 +
 +    @Override
 +    public boolean isOnToken()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean isContains()
 +    {
 +        return contains;
 +    }
 +
 +    @Override
 +    public boolean isMultiColumn()
 +    {
 +        return false;
 +    }
 +
 +    @Override
 +    public boolean usesFunction(String ksName, String functionName)
 +    {
 +        return restrictions.usesFunction(ksName, functionName);
 +    }
 +
 +    @Override
 +    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
 +    {
 +        if (restriction.isMultiColumn())
 +        {
 +            checkTrue(isEmpty(),
 +                      "Mixing single column relations and multi column relations on clustering columns is not allowed");
 +            return (PrimaryKeyRestrictions) restriction;
 +        }
 +
 +        if (restriction.isOnToken())
 +        {
 +            if (isEmpty())
 +                return (PrimaryKeyRestrictions) restriction;
 +
 +            return new TokenFilter(this, (TokenRestriction) restriction);
 +        }
 +
 +        return new SingleColumnPrimaryKeyRestrictions(this, (SingleColumnRestriction) restriction);
 +    }
 +
 +    @Override
 +    public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
 +    {
 +        CompositesBuilder builder = new CompositesBuilder(ctype.builder(), ctype);
 +        for (ColumnDefinition def : restrictions.getColumnDefs())
 +        {
 +            Restriction r = restrictions.getRestriction(def);
 +            assert !r.isSlice();
 +
 +            List<ByteBuffer> values = r.values(options);
 +
 +            if (values.isEmpty())
 +                return Collections.emptyList();
 +
 +            builder.addEachElementToAll(values);
 +            checkFalse(builder.containsNull(), "Invalid null value for column %s", def.name);
 +        }
 +
 +        return builder.build();
 +    }
 +
 +    @Override
 +    public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
 +    {
 +        CBuilder builder = ctype.builder();
 +        List<ColumnDefinition> defs = new ArrayList<>(restrictions.getColumnDefs());
 +
 +        CompositesBuilder compositeBuilder = new CompositesBuilder(builder, ctype);
 +        // 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
 +        int keyPosition = 0;
 +        for (ColumnDefinition def : defs)
 +        {
 +            // 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 = !def.isReversedType() ? bound : bound.reverse();
 +            Restriction r = restrictions.getRestriction(def);
 +            if (keyPosition != def.position() || r.isContains())
 +                return compositeBuilder.buildWithEOC(bound.isEnd() ? EOC.END : EOC.START);
 +
 +            if (r.isSlice())
 +            {
 +                if (!r.hasBound(b))
 +                {
 +                    // 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.
 +                    return compositeBuilder.buildWithEOC(bound.isEnd() ? EOC.END : EOC.START);
 +                }
 +
 +                ByteBuffer value = checkNotNull(r.bounds(b, options).get(0), "Invalid null clustering key part %s", r);
 +                compositeBuilder.addElementToAll(value);
 +                Composite.EOC eoc = eocFor(r, bound, b);
 +                return compositeBuilder.buildWithEOC(eoc);
 +            }
 +
 +            List<ByteBuffer> values = r.values(options);
 +
 +            if (values.isEmpty())
 +                return Collections.emptyList();
 +
 +            compositeBuilder.addEachElementToAll(values);
 +
 +            checkFalse(compositeBuilder.containsNull(), "Invalid null clustering key part %s", def.name);
 +            keyPosition++;
 +        }
 +        // 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).
 +        EOC eoc = !compositeBuilder.hasRemaining() ? EOC.NONE : (bound.isEnd() ? EOC.END : EOC.START);
 +        return compositeBuilder.buildWithEOC(eoc);
 +    }
 +
 +    @Override
 +    public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +    {
 +        return Composites.toByteBuffers(valuesAsComposites(options));
 +    }
 +
 +    @Override
 +    public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
 +    {
 +        return Composites.toByteBuffers(boundsAsComposites(b, options));
 +    }
 +
 +    private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound)
 +    {
 +        if (eocBound.isStart())
 +            return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END;
 +
 +        return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START;
 +    }
 +
 +    @Override
 +    public boolean hasBound(Bound b)
 +    {
 +        if (isEmpty())
 +            return false;
 +        return restrictions.lastRestriction().hasBound(b);
 +    }
 +
 +    @Override
 +    public boolean isInclusive(Bound b)
 +    {
 +        if (isEmpty())
 +            return false;
 +        return restrictions.lastRestriction().isInclusive(b);
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        return restrictions.hasSupportingIndex(indexManager);
 +    }
 +
 +    @Override
-     public void addIndexExpressionTo(List<IndexExpression> expressions, QueryOptions options) throws InvalidRequestException
++    public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
++                                     QueryOptions options) throws InvalidRequestException
 +    {
-         restrictions.addIndexExpressionTo(expressions, options);
++        Boolean clusteringColumns = null;
++        int position = 0;
++
++        for (ColumnDefinition columnDef : restrictions.getColumnDefs())
++        {
++            // SingleColumnPrimaryKeyRestrictions contains only one kind of column, either partition key or clustering columns.
++            // Therefore we only need to check the column kind once. All the other columns will be of the same kind.
++            if (clusteringColumns == null)
++                clusteringColumns = columnDef.isClusteringColumn() ? Boolean.TRUE : Boolean.FALSE;
++
++            Restriction restriction = restrictions.getRestriction(columnDef);
++
++            // We ignore all the clustering columns that can be handled by slices.
++            if (clusteringColumns && !restriction.isContains()&& position == columnDef.position())
++            {
++                position++;
++                if (!restriction.hasSupportingIndex(indexManager))
++                    continue;
++            }
++            restriction.addIndexExpressionTo(expressions, indexManager, options);
++        }
 +    }
 +
 +    @Override
 +    public Collection<ColumnDefinition> getColumnDefs()
 +    {
 +        return restrictions.getColumnDefs();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index 4acd34b,0000000..9fbe462
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@@ -1,515 -1,0 +1,521 @@@
 +/*
 + * 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.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.AbstractMarker;
 +import org.apache.cassandra.cql3.Operator;
 +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.index.SecondaryIndex;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.db.marshal.CompositeType;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
 +
 +public abstract class SingleColumnRestriction extends AbstractRestriction
 +{
 +    /**
 +     * The definition of the column to which apply the restriction.
 +     */
 +    protected final ColumnDefinition columnDef;
 +
 +    public SingleColumnRestriction(ColumnDefinition columnDef)
 +    {
 +        this.columnDef = columnDef;
 +    }
 +
 +    /**
 +     * Returns the definition of the column to which is associated this restriction.
 +     * @return the definition of the column to which is associated this restriction
 +     */
 +    public ColumnDefinition getColumnDef()
 +    {
 +        return columnDef;
 +    }
 +
 +    @Override
 +    public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                     SecondaryIndexManager indexManager,
 +                                     QueryOptions options) throws InvalidRequestException
 +    {
 +        List<ByteBuffer> values = values(options);
 +        checkTrue(values.size() == 1, "IN restrictions are not supported on indexed columns");
 +
 +        ByteBuffer value = validateIndexedValue(columnDef, values.get(0));
 +        expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, value));
 +    }
 +
 +    @Override
 +    public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes);
 +        return index != null && isSupportedBy(index);
 +    }
 +
 +    /**
 +     * Check if this type of restriction is supported by the specified index.
 +     *
 +     * @param index the Secondary index
 +     * @return <code>true</code> this type of restriction is supported by the specified index,
 +     * <code>false</code> otherwise.
 +     */
 +    protected abstract boolean isSupportedBy(SecondaryIndex index);
 +
 +    public static final class EQ extends SingleColumnRestriction
 +    {
 +        private final Term value;
 +
 +        public EQ(ColumnDefinition columnDef, Term value)
 +        {
 +            super(columnDef);
 +            this.value = value;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return usesFunction(value, ksName, functionName);
 +        }
 +
++        @Override
 +        public boolean isEQ()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            return Collections.singletonList(value.bindAndGet(options));
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("EQ(%s)", value);
 +        }
 +
 +        @Override
 +        public Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal", columnDef.name);
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.EQ);
 +        }
 +    }
 +
 +    public static abstract class IN extends SingleColumnRestriction
 +    {
 +        public IN(ColumnDefinition columnDef)
 +        {
 +            super(columnDef);
 +        }
 +
 +        @Override
 +        public final boolean isIN()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public final Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            throw invalidRequest("%s cannot be restricted by more than one relation if it includes a IN", columnDef.name);
 +        }
 +
 +        @Override
 +        protected final boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return index.supportsOperator(Operator.IN);
 +        }
 +    }
 +
 +    public static class InWithValues extends IN
 +    {
 +        protected final List<Term> values;
 +
 +        public InWithValues(ColumnDefinition columnDef, List<Term> values)
 +        {
 +            super(columnDef);
 +            this.values = values;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return usesFunction(values, ksName, functionName);
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> buffers = new ArrayList<>(values.size());
 +            for (Term value : values)
 +                buffers.add(value.bindAndGet(options));
 +            return buffers;
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("IN(%s)", values);
 +        }
 +    }
 +
 +    public static class InWithMarker extends IN
 +    {
 +        protected final AbstractMarker marker;
 +
 +        public InWithMarker(ColumnDefinition columnDef, AbstractMarker marker)
 +        {
 +            super(columnDef);
 +            this.marker = marker;
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return false;
 +        }
 +
++        @Override
 +        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            Term.MultiItemTerminal lval = (Term.MultiItemTerminal) marker.bind(options);
 +            if (lval == null)
 +                throw new InvalidRequestException("Invalid null value for IN restriction");
 +            return lval.getElements();
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return "IN ?";
 +        }
 +    }
 +
 +    public static class Slice extends SingleColumnRestriction
 +    {
 +        private final TermSlice slice;
 +
 +        public Slice(ColumnDefinition columnDef, Bound bound, boolean inclusive, Term term)
 +        {
 +            super(columnDef);
 +            slice = TermSlice.newInstance(bound, inclusive, term);
 +        }
 +
 +        @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 isSlice()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            return slice.hasBound(b);
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            return Collections.singletonList(slice.bound(b).bindAndGet(options));
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound b)
 +        {
 +            return slice.isInclusive(b);
 +        }
 +
 +        @Override
 +        public Restriction mergeWith(Restriction otherRestriction)
 +        throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isSlice(),
 +                      "Column \"%s\" cannot be restricted by both an equality and an inequality relation",
 +                      columnDef.name);
 +
 +            SingleColumnRestriction.Slice otherSlice = (SingleColumnRestriction.Slice) otherRestriction;
 +
 +            checkFalse(hasBound(Bound.START) && otherSlice.hasBound(Bound.START),
 +                       "More than one restriction was found for the start bound on %s", columnDef.name);
 +
 +            checkFalse(hasBound(Bound.END) && otherSlice.hasBound(Bound.END),
 +                       "More than one restriction was found for the end bound on %s", columnDef.name);
 +
 +            return new Slice(columnDef,  slice.merge(otherSlice.slice));
 +        }
 +
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options) throws InvalidRequestException
 +        {
 +            for (Bound b : Bound.values())
 +            {
 +                if (hasBound(b))
 +                {
 +                    ByteBuffer value = validateIndexedValue(columnDef, slice.bound(b).bindAndGet(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.
 +                    op = columnDef.isReversedType() ? op.reverse() : op;
 +                    expressions.add(new IndexExpression(columnDef.name.bytes, op, value));
 +                }
 +            }
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            return slice.isSupportedBy(index);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("SLICE%s", slice);
 +        }
 +
 +        private Slice(ColumnDefinition columnDef, TermSlice slice)
 +        {
 +            super(columnDef);
 +            this.slice = slice;
 +        }
 +    }
 +
 +    // This holds CONTAINS, CONTAINS_KEY, and map[key] = value restrictions because we might want to have any combination of them.
 +    public static final class Contains extends SingleColumnRestriction
 +    {
 +        private List<Term> values = new ArrayList<>(); // for CONTAINS
 +        private List<Term> keys = new ArrayList<>(); // for CONTAINS_KEY
 +        private List<Term> entryKeys = new ArrayList<>(); // for map[key] = value
 +        private List<Term> entryValues = new ArrayList<>(); // for map[key] = value
 +
 +        public Contains(ColumnDefinition columnDef, Term t, boolean isKey)
 +        {
 +            super(columnDef);
 +            if (isKey)
 +                keys.add(t);
 +            else
 +                values.add(t);
 +        }
 +
 +        public Contains(ColumnDefinition columnDef, Term mapKey, Term mapValue)
 +        {
 +            super(columnDef);
 +            entryKeys.add(mapKey);
 +            entryValues.add(mapValue);
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
 +        {
 +            return bindAndGet(values, options);
 +        }
 +
 +        @Override
 +        public boolean isContains()
 +        {
 +            return true;
 +        }
 +
 +        @Override
 +        public Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException
 +        {
 +            checkTrue(otherRestriction.isContains(),
 +                      "Collection column %s can only be restricted by CONTAINS, CONTAINS KEY, or map-entry equality",
 +                      getColumnDef().name);
 +
 +            SingleColumnRestriction.Contains newContains = new Contains(getColumnDef());
 +
 +            copyKeysAndValues(this, newContains);
 +            copyKeysAndValues((Contains) otherRestriction, newContains);
 +
 +            return newContains;
 +        }
 +
 +        @Override
 +        public void addIndexExpressionTo(List<IndexExpression> expressions,
++                                         SecondaryIndexManager indexManager,
 +                                         QueryOptions options)
 +                                         throws InvalidRequestException
 +        {
 +            addExpressionsFor(expressions, values(options), Operator.CONTAINS);
 +            addExpressionsFor(expressions, keys(options), Operator.CONTAINS_KEY);
 +            addExpressionsFor(expressions, entries(options), Operator.EQ);
 +        }
 +
 +        private void addExpressionsFor(List<IndexExpression> target, List<ByteBuffer> values,
 +                                       Operator op) throws InvalidRequestException
 +        {
 +            for (ByteBuffer value : values)
 +            {
 +                validateIndexedValue(columnDef, value);
 +                target.add(new IndexExpression(columnDef.name.bytes, op, value));
 +            }
 +        }
 +
 +        @Override
 +        protected boolean isSupportedBy(SecondaryIndex index)
 +        {
 +            boolean supported = false;
 +
 +            if (numberOfValues() > 0)
 +                supported |= index.supportsOperator(Operator.CONTAINS);
 +
 +            if (numberOfKeys() > 0)
 +                supported |= index.supportsOperator(Operator.CONTAINS_KEY);
 +
 +            if (numberOfEntries() > 0)
 +                supported |= index.supportsOperator(Operator.EQ);
 +
 +            return supported;
 +        }
 +
 +        public int numberOfValues()
 +        {
 +            return values.size();
 +        }
 +
 +        public int numberOfKeys()
 +        {
 +            return keys.size();
 +        }
 +
 +        public int numberOfEntries()
 +        {
 +            return entryKeys.size();
 +        }
 +
 +        @Override
 +        public boolean usesFunction(String ksName, String functionName)
 +        {
 +            return usesFunction(values, ksName, functionName) || usesFunction(keys, ksName, functionName) ||
 +                   usesFunction(entryKeys, ksName, functionName) || usesFunction(entryValues, ksName, functionName);
 +        }
 +
 +        @Override
 +        public String toString()
 +        {
 +            return String.format("CONTAINS(values=%s, keys=%s, entryKeys=%s, entryValues=%s)", values, keys, entryKeys, entryValues);
 +        }
 +
 +        @Override
 +        public boolean hasBound(Bound b)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        @Override
 +        public boolean isInclusive(Bound b)
 +        {
 +            throw new UnsupportedOperationException();
 +        }
 +
 +        private List<ByteBuffer> keys(QueryOptions options) throws InvalidRequestException
 +        {
 +            return bindAndGet(keys, options);
 +        }
 +
 +        private List<ByteBuffer> entries(QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> entryBuffers = new ArrayList<>(entryKeys.size());
 +            List<ByteBuffer> keyBuffers = bindAndGet(entryKeys, options);
 +            List<ByteBuffer> valueBuffers = bindAndGet(entryValues, options);
 +            for (int i = 0; i < entryKeys.size(); i++)
 +            {
 +                if (valueBuffers.get(i) == null)
 +                    throw new InvalidRequestException("Unsupported null value for map-entry equality");
 +                entryBuffers.add(CompositeType.build(keyBuffers.get(i), valueBuffers.get(i)));
 +            }
 +            return entryBuffers;
 +        }
 +
 +        /**
 +         * Binds the query options to the specified terms and returns the resulting values.
 +         *
 +         * @param terms the terms
 +         * @param options the query options
 +         * @return the value resulting from binding the query options to the specified terms
 +         * @throws InvalidRequestException if a problem occurs while binding the query options
 +         */
 +        private static List<ByteBuffer> bindAndGet(List<Term> terms, QueryOptions options) throws InvalidRequestException
 +        {
 +            List<ByteBuffer> buffers = new ArrayList<>(terms.size());
 +            for (Term value : terms)
 +                buffers.add(value.bindAndGet(options));
 +            return buffers;
 +        }
 +
 +        /**
 +         * Copies the keys and value from the first <code>Contains</code> to the second one.
 +         *
 +         * @param from the <code>Contains</code> to copy from
 +         * @param to the <code>Contains</code> to copy to
 +         */
 +        private static void copyKeysAndValues(Contains from, Contains to)
 +        {
 +            to.values.addAll(from.values);
 +            to.keys.addAll(from.keys);
 +            to.entryKeys.addAll(from.entryKeys);
 +            to.entryValues.addAll(from.entryValues);
 +        }
 +
 +        private Contains(ColumnDefinition columnDef)
 +        {
 +            super(columnDef);
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fbc38cd3/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestrictions.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestrictions.java
index b9ffc68,0000000..b85e932
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestrictions.java
@@@ -1,209 -1,0 +1,210 @@@
 +/*
 + * 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.util.*;
 +
 +import org.apache.cassandra.config.ColumnDefinition;
 +import org.apache.cassandra.cql3.QueryOptions;
 +import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction.Contains;
 +import org.apache.cassandra.db.IndexExpression;
 +import org.apache.cassandra.db.index.SecondaryIndexManager;
 +import org.apache.cassandra.exceptions.InvalidRequestException;
 +
 +/**
 + * Sets of single column restrictions.
 + */
 +final class SingleColumnRestrictions implements Restrictions
 +{
 +    /**
 +     * The comparator used to sort the <code>Restriction</code>s.
 +     */
 +    private static final Comparator<ColumnDefinition> COLUMN_DEFINITION_COMPARATOR = new Comparator<ColumnDefinition>()
 +    {
 +        @Override
 +        public int compare(ColumnDefinition column, ColumnDefinition otherColumn)
 +        {
 +            int value = Integer.compare(column.position(), otherColumn.position());
 +            return value != 0 ? value : column.name.bytes.compareTo(otherColumn.name.bytes);
 +        }
 +    };
 +
 +    /**
 +     * The restrictions per column.
 +     */
 +    protected final TreeMap<ColumnDefinition, Restriction> restrictions;
 +
 +    public SingleColumnRestrictions()
 +    {
 +        this(new TreeMap<ColumnDefinition, Restriction>(COLUMN_DEFINITION_COMPARATOR));
 +    }
 +
 +    protected SingleColumnRestrictions(TreeMap<ColumnDefinition, Restriction> restrictions)
 +    {
 +        this.restrictions = restrictions;
 +    }
 +
 +    @Override
 +    public final void addIndexExpressionTo(List<IndexExpression> expressions,
++                                           SecondaryIndexManager indexManager,
 +                                           QueryOptions options) throws InvalidRequestException
 +    {
 +        for (Restriction restriction : restrictions.values())
-             restriction.addIndexExpressionTo(expressions, options);
++            restriction.addIndexExpressionTo(expressions, indexManager, options);
 +    }
 +
 +    @Override
 +    public final Set<ColumnDefinition> getColumnDefs()
 +    {
 +        return restrictions.keySet();
 +    }
 +
 +    /**
 +     * Returns the restriction associated to the specified column.
 +     *
 +     * @param columnDef the column definition
 +     * @return the restriction associated to the specified column
 +     */
 +    public Restriction getRestriction(ColumnDefinition columnDef)
 +    {
 +        return restrictions.get(columnDef);
 +    }
 +
 +    @Override
 +    public boolean usesFunction(String ksName, String functionName)
 +    {
 +        for (Restriction restriction : restrictions.values())
 +            if (restriction.usesFunction(ksName, functionName))
 +                return true;
 +
 +        return false;
 +    }
 +
 +    @Override
 +    public final boolean isEmpty()
 +    {
 +        return getColumnDefs().isEmpty();
 +    }
 +
 +    @Override
 +    public final int size()
 +    {
 +        return getColumnDefs().size();
 +    }
 +
 +    /**
 +     * Adds the specified restriction to this set of restrictions.
 +     *
 +     * @param restriction the restriction to add
 +     * @return the new set of restrictions
 +     * @throws InvalidRequestException if the new restriction cannot be added
 +     */
 +    public SingleColumnRestrictions addRestriction(SingleColumnRestriction restriction) throws InvalidRequestException
 +    {
 +        TreeMap<ColumnDefinition, Restriction> newRestrictions = new TreeMap<>(this.restrictions);
 +        return new SingleColumnRestrictions(mergeRestrictions(newRestrictions, restriction));
 +    }
 +
 +    private static TreeMap<ColumnDefinition, Restriction> mergeRestrictions(TreeMap<ColumnDefinition, Restriction> restrictions,
 +                                                                            Restriction restriction)
 +                                                                            throws InvalidRequestException
 +    {
 +        ColumnDefinition def = ((SingleColumnRestriction) restriction).getColumnDef();
 +        Restriction existing = restrictions.get(def);
 +        Restriction newRestriction = mergeRestrictions(existing, restriction);
 +        restrictions.put(def, newRestriction);
 +        return restrictions;
 +    }
 +
 +    @Override
 +    public final boolean hasSupportingIndex(SecondaryIndexManager indexManager)
 +    {
 +        for (Restriction restriction : restrictions.values())
 +        {
 +            if (restriction.hasSupportingIndex(indexManager))
 +                return true;
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * Returns the column after the specified one.
 +     *
 +     * @param columnDef the column for which the next one need to be found
 +     * @return the column after the specified one.
 +     */
 +    ColumnDefinition nextColumn(ColumnDefinition columnDef)
 +    {
 +        return restrictions.tailMap(columnDef, false).firstKey();
 +    }
 +
 +    /**
 +     * Returns the definition of the last column.
 +     *
 +     * @return the definition of the last column.
 +     */
 +    ColumnDefinition lastColumn()
 +    {
 +        return isEmpty() ? null : this.restrictions.lastKey();
 +    }
 +
 +    /**
 +     * Returns the last restriction.
 +     *
 +     * @return the last restriction.
 +     */
 +    Restriction lastRestriction()
 +    {
 +        return isEmpty() ? null : this.restrictions.lastEntry().getValue();
 +    }
 +
 +    /**
 +     * Merges the two specified restrictions.
 +     *
 +     * @param restriction the first restriction
 +     * @param otherRestriction the second restriction
 +     * @return the merged restriction
 +     * @throws InvalidRequestException if the two restrictions cannot be merged
 +     */
 +    private static Restriction mergeRestrictions(Restriction restriction,
 +                                                 Restriction otherRestriction) throws InvalidRequestException
 +    {
 +        return restriction == null ? otherRestriction
 +                                   : restriction.mergeWith(otherRestriction);
 +    }
 +
 +    /**
 +     * Checks if the restrictions contains multiple contains, contains key, or map[key] = value.
 +     *
 +     * @return <code>true</code> if the restrictions contains multiple contains, contains key, or ,
 +     * map[key] = value; <code>false</code> otherwise
 +     */
 +    public final boolean hasMultipleContains()
 +    {
 +        int numberOfContains = 0;
 +        for (Restriction restriction : restrictions.values())
 +        {
 +            if (restriction.isContains())
 +            {
 +                Contains contains = (Contains) restriction;
 +                numberOfContains += (contains.numberOfValues() + contains.numberOfKeys() + contains.numberOfEntries());
 +            }
 +        }
 +        return numberOfContains > 1;
 +    }
 +}


[2/4] cassandra git commit: Merge branch 'cassandra-2.0' into cassandra-2.1

Posted by ty...@apache.org.
Merge branch 'cassandra-2.0' into cassandra-2.1


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/07ffe1b1
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/07ffe1b1
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/07ffe1b1

Branch: refs/heads/trunk
Commit: 07ffe1b12eb68cd51fdfc8715ffa7df14381df3a
Parents: ad91d41 9649594
Author: Tyler Hobbs <ty...@apache.org>
Authored: Tue Feb 10 15:09:39 2015 -0600
Committer: Tyler Hobbs <ty...@apache.org>
Committed: Tue Feb 10 15:09:39 2015 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |  3 +
 .../cql3/statements/SelectStatement.java        | 58 ++++++------
 .../cassandra/cql3/MultiColumnRelationTest.java | 94 ++++++++++++++++++++
 .../cql3/SingleColumnRelationTest.java          | 40 +++++++++
 4 files changed, 170 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/07ffe1b1/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index 92ee5d1,861730f..2113349
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,96 -1,6 +1,99 @@@
 -2.0.13:
 +2.1.4
 + * Write partition size estimates into a system table (CASSANDRA-7688)
 + * cqlsh: Fix keys() and full() collection indexes in DESCRIBE output
 +   (CASSANDRA-8154)
++Merged from 2.0:
+  * Fix some multi-column relations with indexes on some clustering
+    columns (CASSANDRA-8275)
 +
 +
 +2.1.3
 + * Upgrade libthrift to 0.9.2 (CASSANDRA-8685)
 + * Don't use the shared ref in sstableloader (CASSANDRA-8704)
 + * Purge internal prepared statements if related tables or
 +   keyspaces are dropped (CASSANDRA-8693)
 + * (cqlsh) Handle unicode BOM at start of files (CASSANDRA-8638)
 + * Stop compactions before exiting offline tools (CASSANDRA-8623)
 + * Update tools/stress/README.txt to match current behaviour (CASSANDRA-7933)
 + * Fix schema from Thrift conversion with empty metadata (CASSANDRA-8695)
 + * Safer Resource Management (CASSANDRA-7705)
 + * Make sure we compact highly overlapping cold sstables with
 +   STCS (CASSANDRA-8635)
 + * rpc_interface and listen_interface generate NPE on startup when specified
 +   interface doesn't exist (CASSANDRA-8677)
 + * Fix ArrayIndexOutOfBoundsException in nodetool cfhistograms (CASSANDRA-8514)
 + * Switch from yammer metrics for nodetool cf/proxy histograms (CASSANDRA-8662)
 + * Make sure we don't add tmplink files to the compaction
 +   strategy (CASSANDRA-8580)
 + * (cqlsh) Handle maps with blob keys (CASSANDRA-8372)
 + * (cqlsh) Handle DynamicCompositeType schemas correctly (CASSANDRA-8563)
 + * Duplicate rows returned when in clause has repeated values (CASSANDRA-6706)
 + * Add tooling to detect hot partitions (CASSANDRA-7974)
 + * Fix cassandra-stress user-mode truncation of partition generation (CASSANDRA-8608)
 + * Only stream from unrepaired sstables during inc repair (CASSANDRA-8267)
 + * Don't allow starting multiple inc repairs on the same sstables (CASSANDRA-8316)
 + * Invalidate prepared BATCH statements when related tables
 +   or keyspaces are dropped (CASSANDRA-8652)
 + * Fix missing results in secondary index queries on collections
 +   with ALLOW FILTERING (CASSANDRA-8421)
 + * Expose EstimatedHistogram metrics for range slices (CASSANDRA-8627)
 + * (cqlsh) Escape clqshrc passwords properly (CASSANDRA-8618)
 + * Fix NPE when passing wrong argument in ALTER TABLE statement (CASSANDRA-8355)
 + * Pig: Refactor and deprecate CqlStorage (CASSANDRA-8599)
 + * Don't reuse the same cleanup strategy for all sstables (CASSANDRA-8537)
 + * Fix case-sensitivity of index name on CREATE and DROP INDEX
 +   statements (CASSANDRA-8365)
 + * Better detection/logging for corruption in compressed sstables (CASSANDRA-8192)
 + * Use the correct repairedAt value when closing writer (CASSANDRA-8570)
 + * (cqlsh) Handle a schema mismatch being detected on startup (CASSANDRA-8512)
 + * Properly calculate expected write size during compaction (CASSANDRA-8532)
 + * Invalidate affected prepared statements when a table's columns
 +   are altered (CASSANDRA-7910)
 + * Stress - user defined writes should populate sequentally (CASSANDRA-8524)
 + * Fix regression in SSTableRewriter causing some rows to become unreadable 
 +   during compaction (CASSANDRA-8429)
 + * Run major compactions for repaired/unrepaired in parallel (CASSANDRA-8510)
 + * (cqlsh) Fix compression options in DESCRIBE TABLE output when compression
 +   is disabled (CASSANDRA-8288)
 + * (cqlsh) Fix DESCRIBE output after keyspaces are altered (CASSANDRA-7623)
 + * Make sure we set lastCompactedKey correctly (CASSANDRA-8463)
 + * (cqlsh) Fix output of CONSISTENCY command (CASSANDRA-8507)
 + * (cqlsh) Fixed the handling of LIST statements (CASSANDRA-8370)
 + * Make sstablescrub check leveled manifest again (CASSANDRA-8432)
 + * Check first/last keys in sstable when giving out positions (CASSANDRA-8458)
 + * Disable mmap on Windows (CASSANDRA-6993)
 + * Add missing ConsistencyLevels to cassandra-stress (CASSANDRA-8253)
 + * Add auth support to cassandra-stress (CASSANDRA-7985)
 + * Fix ArrayIndexOutOfBoundsException when generating error message
 +   for some CQL syntax errors (CASSANDRA-8455)
 + * Scale memtable slab allocation logarithmically (CASSANDRA-7882)
 + * cassandra-stress simultaneous inserts over same seed (CASSANDRA-7964)
 + * Reduce cassandra-stress sampling memory requirements (CASSANDRA-7926)
 + * Ensure memtable flush cannot expire commit log entries from its future (CASSANDRA-8383)
 + * Make read "defrag" async to reclaim memtables (CASSANDRA-8459)
 + * Remove tmplink files for offline compactions (CASSANDRA-8321)
 + * Reduce maxHintsInProgress (CASSANDRA-8415)
 + * BTree updates may call provided update function twice (CASSANDRA-8018)
 + * Release sstable references after anticompaction (CASSANDRA-8386)
 + * Handle abort() in SSTableRewriter properly (CASSANDRA-8320)
 + * Fix high size calculations for prepared statements (CASSANDRA-8231)
 + * Centralize shared executors (CASSANDRA-8055)
 + * Fix filtering for CONTAINS (KEY) relations on frozen collection
 +   clustering columns when the query is restricted to a single
 +   partition (CASSANDRA-8203)
 + * Do more aggressive entire-sstable TTL expiry checks (CASSANDRA-8243)
 + * Add more log info if readMeter is null (CASSANDRA-8238)
 + * add check of the system wall clock time at startup (CASSANDRA-8305)
 + * Support for frozen collections (CASSANDRA-7859)
 + * Fix overflow on histogram computation (CASSANDRA-8028)
 + * Have paxos reuse the timestamp generation of normal queries (CASSANDRA-7801)
 + * Fix incremental repair not remove parent session on remote (CASSANDRA-8291)
 + * Improve JBOD disk utilization (CASSANDRA-7386)
 + * Log failed host when preparing incremental repair (CASSANDRA-8228)
 + * Force config client mode in CQLSSTableWriter (CASSANDRA-8281)
 + * Fix sstableupgrade throws exception (CASSANDRA-8688)
 + * Fix hang when repairing empty keyspace (CASSANDRA-8694)
 +Merged from 2.0:
   * Fix IllegalArgumentException in dynamic snitch (CASSANDRA-8448)
   * Add support for UPDATE ... IF EXISTS (CASSANDRA-8610)
   * Fix reversal of list prepends (CASSANDRA-8733)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/07ffe1b1/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 633d43c,2fa57b9..08777c7
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@@ -85,10 -80,12 +85,12 @@@ public class SelectStatement implement
      private final Restriction[] columnRestrictions;
  
      /** Restrictions on non-primary key columns (i.e. secondary index restrictions) */
 -    private final Map<CFDefinition.Name, Restriction> metadataRestrictions = new HashMap<CFDefinition.Name, Restriction>();
 +    private final Map<ColumnIdentifier, Restriction> metadataRestrictions = new HashMap<ColumnIdentifier, Restriction>();
  
-     // All restricted columns not covered by the key or index filter
-     private final Set<ColumnDefinition> restrictedColumns = new HashSet<ColumnDefinition>();
+     // The map keys are the name of the columns that must be converted into IndexExpressions if a secondary index need
+     // to be used. The value specify if the column has an index that can be used to for the relation in which the column
+     // is specified.
 -    private final Map<CFDefinition.Name, Boolean> restrictedNames = new HashMap<CFDefinition.Name, Boolean>();
++    private final Map<ColumnDefinition, Boolean> restrictedColumns = new HashMap<ColumnDefinition, Boolean>();
      private Restriction.Slice sliceRestriction;
  
      private boolean isReversed;
@@@ -1059,23 -1028,23 +1061,23 @@@
              return Collections.emptyList();
  
          List<IndexExpression> expressions = new ArrayList<IndexExpression>();
-         for (ColumnDefinition def : restrictedColumns)
 -        for (CFDefinition.Name name : restrictedNames.keySet())
++        for (ColumnDefinition def : restrictedColumns.keySet())
          {
              Restriction restriction;
 -            switch (name.kind)
 +            switch (def.kind)
              {
 -                case KEY_ALIAS:
 -                    restriction = keyRestrictions[name.position];
 +                case PARTITION_KEY:
 +                    restriction = keyRestrictions[def.position()];
                      break;
 -                case COLUMN_ALIAS:
 -                    restriction = columnRestrictions[name.position];
 +                case CLUSTERING_COLUMN:
 +                    restriction = columnRestrictions[def.position()];
                      break;
 -                case COLUMN_METADATA:
 +                case REGULAR:
                  case STATIC:
 -                    restriction = metadataRestrictions.get(name);
 +                    restriction = metadataRestrictions.get(def.name);
                      break;
                  default:
 -                    // We don't allow restricting a VALUE_ALIAS for now in prepare.
 +                    // We don't allow restricting a COMPACT_VALUE for now in prepare.
                      throw new AssertionError();
              }
  
@@@ -1097,39 -1067,27 +1099,49 @@@
                      }
                  }
              }
 +            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
              {
-                 List<ByteBuffer> values = restriction.values(options);
+                 ByteBuffer value;
+                 if (restriction.isMultiColumn())
+                 {
 -                    List<ByteBuffer> values = restriction.values(variables);
 -                    value = values.get(name.position);
++                    List<ByteBuffer> values = restriction.values(options);
++                    value = values.get(def.position());
+                 }
+                 else
+                 {
 -                    List<ByteBuffer> values = restriction.values(variables);
++                    List<ByteBuffer> values = restriction.values(options);
+                     if (values.size() != 1)
+                         throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
  
-                 if (values.size() != 1)
-                     throw new InvalidRequestException("IN restrictions are not supported on indexed columns");
+                     value = values.get(0);
+                 }
  
-                 ByteBuffer value = validateIndexedValue(def, values.get(0));
 -                validateIndexExpressionValue(value, name);
 -                expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
++                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;
      }
  
@@@ -1503,7 -1506,7 +1515,7 @@@
              // All (or none) of the partition key columns have been specified;
              // hence there is no need to turn these restrictions into index expressions.
              if (!stmt.usesSecondaryIndexing)
-                 stmt.restrictedColumns.removeAll(cfm.partitionKeyColumns());
 -                stmt.restrictedNames.keySet().removeAll(cfDef.partitionKeys());
++                stmt.restrictedColumns.keySet().removeAll(cfm.partitionKeyColumns());
  
              if (stmt.selectsOnlyStaticColumns && stmt.hasClusteringColumnsRestriction())
                  throw new InvalidRequestException("Cannot restrict clustering columns when selecting only static columns");
@@@ -1514,21 -1517,16 +1526,15 @@@
              if (stmt.isKeyRange && hasQueriableClusteringColumnIndex)
                  stmt.usesSecondaryIndexing = true;
  
-             if (!stmt.usesSecondaryIndexing)
 -            // The clustering columns that can be used to perform a slice filtering on the secondary index do not
 -            // need to be converted into IndexExpressions. Therefore, if they are not indexed by an index that support
 -            // the relation in which they have been specified, we can removes them from the restrictedNames map.
 -            for (Name clusteringColumn : cfDef.clusteringColumns())
++            for (ColumnDefinition def : cfm.clusteringColumns())
              {
-                 for (ColumnDefinition def : cfm.clusteringColumns())
-                 {
-                     // Remove clustering column restrictions that can be handled by slices; the remainder will be
-                     // handled by filters (which may require a secondary index).
-                     Restriction restriction = stmt.columnRestrictions[def.position()];
-                     if (restriction != null)
-                     {
-                         if (restriction.canEvaluateWithSlices())
-                             stmt.restrictedColumns.remove(def);
-                         else
-                             stmt.usesSecondaryIndexing = true;
-                     }
-                 }
 -                Boolean indexed = stmt.restrictedNames.get(clusteringColumn);
++                // Remove clustering column restrictions that can be handled by slices; the remainder will be
++                // handled by filters (which may require a secondary index).
++                Boolean indexed = stmt.restrictedColumns.get(def);
+                 if (indexed == null)
+                     break;
 -                if (!indexed)
 -                    stmt.restrictedNames.remove(clusteringColumn);
++                if (!indexed && stmt.columnRestrictions[def.position()].canEvaluateWithSlices())
++                    stmt.restrictedColumns.remove(def);
              }
  
              // Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
@@@ -1551,21 -1553,18 +1557,23 @@@
          }
  
          /** Returns a pair of (hasQueriableIndex, hasQueriableClusteringColumnIndex) */
 -        private boolean[] processRelationEntity(SelectStatement stmt, Relation relation, ColumnIdentifier entity, CFDefinition cfDef) throws InvalidRequestException
 +        private boolean[] processRelationEntity(SelectStatement stmt,
 +                                                SecondaryIndexManager indexManager,
 +                                                Relation relation,
 +                                                ColumnIdentifier entity,
 +                                                ColumnDefinition def) throws InvalidRequestException
          {
 -            CFDefinition.Name name = cfDef.get(entity);
 -            if (name == null)
 +            if (def == null)
                  handleUnrecognizedEntity(entity, relation);
  
-             stmt.restrictedColumns.add(def);
- 
 -            if (cfDef.cfm.getColumnDefinition(name.name.key).isIndexed() && relation.operator() == Relation.Type.EQ)
 +            SecondaryIndex index = indexManager.getIndexForColumn(def.name.bytes);
 +            if (index != null && index.supportsOperator(relation.operator()))
+             {
 -                stmt.restrictedNames.put(name, Boolean.TRUE);
 -                return new boolean[]{true, name.kind == CFDefinition.Name.Kind.COLUMN_ALIAS};
++                stmt.restrictedColumns.put(def, Boolean.TRUE);
 +                return new boolean[]{true, def.kind == ColumnDefinition.Kind.CLUSTERING_COLUMN};
+             }
 -            stmt.restrictedNames.put(name, Boolean.FALSE);
 +
++            stmt.restrictedColumns.put(def, Boolean.FALSE);
              return new boolean[]{false, false};
          }
  
@@@ -2134,38 -2126,7 +2142,38 @@@
              }
          }
  
 -        private SingleColumnRelation findInclusiveClusteringRelationForCompact(CFDefinition cfDef)
 +        /**
 +         * Checks if the specified statement will need to filter the data.
 +         *
 +         * @param stmt the statement to test.
 +         * @return <code>true</code> if the specified statement will need to filter the data, <code>false</code>
 +         * otherwise.
 +         */
 +        private static boolean needFiltering(SelectStatement stmt)
 +        {
 +            return stmt.restrictedColumns.size() > 1
 +                    || (stmt.restrictedColumns.isEmpty() && !stmt.columnFilterIsIdentity())
 +                    || (!stmt.restrictedColumns.isEmpty()
-                             && stmt.isRestrictedByMultipleContains(Iterables.getOnlyElement(stmt.restrictedColumns)));
++                            && stmt.isRestrictedByMultipleContains(Iterables.getOnlyElement(stmt.restrictedColumns.keySet())));
 +        }
 +
 +        private int indexOf(ColumnDefinition def, Selection selection)
 +        {
 +            return indexOf(def, selection.getColumns().iterator());
 +        }
 +
 +        private int indexOf(final ColumnDefinition def, Iterator<ColumnDefinition> defs)
 +        {
 +            return Iterators.indexOf(defs, new Predicate<ColumnDefinition>()
 +                                           {
 +                                               public boolean apply(ColumnDefinition n)
 +                                               {
 +                                                   return def.name.equals(n.name);
 +                                               }
 +                                           });
 +        }
 +
 +        private SingleColumnRelation findInclusiveClusteringRelationForCompact(CFMetaData cfm)
          {
              for (Relation r : whereClause)
              {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/07ffe1b1/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
index 4c3ba2a,30a9226..25df030
--- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
@@@ -469,90 -1140,233 +469,184 @@@ public class MultiColumnRelationTest ex
      }
  
      @Test
 -    public void testPrepareInOneMarker() throws Throwable
 +    public void testMultipleClusteringReversedComponents() throws Throwable
      {
 -        for (String tableSuffix : new String[]{"", "_compact"})
 +        for (String compactOption : new String[]{"", " COMPACT STORAGE AND"})
          {
 -            execute("INSERT INTO %s.multiple_clustering" + tableSuffix + " (a, b, c, d) VALUES (0, 0, 0, 0)");
 -            execute("INSERT INTO %s.multiple_clustering" + tableSuffix + " (a, b, c, d) VALUES (0, 0, 1, 0)");
 -            execute("INSERT INTO %s.multiple_clustering" + tableSuffix + " (a, b, c, d) VALUES (0, 0, 1, 1)");
 +            createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d)) WITH" + compactOption + " CLUSTERING ORDER BY (b DESC, c ASC, d DESC)");
  
 -            UntypedResultSet results = executePrepared(prepare(
 -                            "SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 AND (b, c, d) IN ?"),
 -                    options(list(tuple(0, 1, 0), tuple(0, 1, 1))));
 -            assertEquals(2, results.size());
 -            checkRow(0, results, 0, 0, 1, 0);
 -            checkRow(1, results, 0, 0, 1, 1);
 +            // b and d are reversed in the clustering order
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 0, 0);
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 1);
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 0);
 +
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 0, 0);
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 1);
 +            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 0);
 +
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?)", 0, 0),
 +                    row(0, 1, 0, 0),
 +                    row(0, 1, 1, 1),
 +                    row(0, 1, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) >= (?)", 0, 0),
 +                    row(0, 1, 0, 0),
 +                    row(0, 1, 1, 1),
 +                    row(0, 1, 1, 0),
 +                    row(0, 0, 0, 0),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) < (?)", 0, 1),
 +                    row(0, 0, 0, 0),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) <= (?)", 0, 1),
 +                    row(0, 1, 0, 0),
 +                    row(0, 1, 1, 1),
 +                    row(0, 1, 1, 0),
 +                    row(0, 0, 0, 0),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a=? AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))", 0, 1, 1, 1, 0, 1, 1),
 +                    row(0, 1, 1, 1),
 +                    row(0, 0, 1, 1)
 +            );
  
              // same query, but reversed order for the IN values
 -            results = executePrepared(prepare(
 -                            "SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 AND (b, c, d) IN ?"),
 -                    options(list(tuple(0, 1, 1), tuple(0, 1, 0))));
 -            assertEquals(2, results.size());
 -            checkRow(0, results, 0, 0, 1, 0);
 -            checkRow(1, results, 0, 0, 1, 1);
 -
 -            results = executePrepared(prepare(
 -                            "SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 AND (b, c, d) IN ?"),
 -                    options(list()));
 -            assertTrue(results.isEmpty());
 -
 -            results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 and (b, c) IN ?"),
 -                    options(list(tuple(0, 1))));
 -            assertEquals(2, results.size());
 -            checkRow(0, results, 0, 0, 1, 0);
 -            checkRow(1, results, 0, 0, 1, 1);
 -
 -            results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 and (b) IN ?"),
 -                    options(list(tuple(0))));
 -            assertEquals(3, results.size());
 -            checkRow(0, results, 0, 0, 0, 0);
 -            checkRow(1, results, 0, 0, 1, 0);
 -            checkRow(2, results, 0, 0, 1, 1);
 -
 -            results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering" + tableSuffix + " WHERE a=0 and (b) IN ?"),
 -                    options(list()));
 -            assertTrue(results.isEmpty());
 +            assertRows(execute("SELECT * FROM %s WHERE a=? AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))", 0, 0, 1, 1, 1, 1, 1),
 +                    row(0, 1, 1, 1),
 +                    row(0, 0, 1, 1)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) IN (?, ?, ?, ?, ?, ?)",
 +                            0, tuple(1, 0, 0), tuple(1, 1, 1), tuple(1, 1, 0), tuple(0, 0, 0), tuple(0, 1, 1), tuple(0, 1, 0)),
 +                    row(0, 1, 0, 0),
 +                    row(0, 1, 1, 1),
 +                    row(0, 1, 1, 0),
 +                    row(0, 0, 0, 0),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) IN (?)", 0, tuple(0, 1)),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) IN (?)", 0, tuple(0, 0)),
 +                    row(0, 0, 0, 0)
 +            );
 +
 +            assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) IN ((?))", 0, 0),
 +                    row(0, 0, 0, 0),
 +                    row(0, 0, 1, 1),
 +                    row(0, 0, 1, 0)
 +            );
 +
 +            // preserve pre-6875 behavior (even though the query result is technically incorrect)
 +            assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b, c) > (?, ?)", 0, 1, 0));
          }
      }
+ 
+     @Test
+     public void testMultipleClusteringWithIndex() throws Throwable
+     {
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 0, 0, 0)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 1, 0, 1)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 0, 1, 1, 2)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 0, 0, 0)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 1, 0, 1)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 1, 1, 1, 2)");
 -        execute("INSERT INTO %s.multiple_clustering_with_indices (a, b, c, d, e) VALUES (0, 2, 0, 0, 0)");
 -
 -        UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering_with_indices WHERE (b) = (1)");
 -        assertEquals(3, results.size());
 -        checkRow(0, results, 0, 1, 0, 0, 0);
 -        checkRow(1, results, 0, 1, 1, 0, 1);
 -        checkRow(2, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) = (1, 1) ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 1, 1, 0, 1);
 -        checkRow(1, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) = (1, 1) AND e = 2 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) IN ((1)) AND e = 2 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) IN ((0), (1)) AND e = 2 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 2);
 -        checkRow(1, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) IN ((0, 1)) AND e = 2 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) IN ((0, 1), (1, 1)) AND e = 2 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 2);
 -        checkRow(1, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b) >= (1) AND e = 2 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 1, 1, 1, 2);
 -
 -        results = execute("SELECT * FROM %s.multiple_clustering_with_indices  WHERE (b, c) >= (1, 1) AND e = 2 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 1, 1, 1, 2);
++        createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, PRIMARY KEY (a, b, c, d))");
++        createIndex("CREATE INDEX ON %s (b)");
++        createIndex("CREATE INDEX ON %s (e)");
++        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, 1, 0, 1);
++        execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 0, 1, 1, 2);
++        execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 1, 0, 0, 0);
++        execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 1, 1, 0, 1);
++        execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 1, 1, 1, 2);
++        execute("INSERT INTO %s (a, b, c, d, e) VALUES (?, ?, ?, ?, ?)", 0, 2, 0, 0, 0);
++        assertRows(execute("SELECT * FROM %s WHERE (b) = (?)", 1),
++                   row(0, 1, 0, 0, 0),
++                   row(0, 1, 1, 0, 1),
++                   row(0, 1, 1, 1, 2));
++        assertRows(execute("SELECT * FROM %s WHERE (b, c) = (?, ?) ALLOW FILTERING", 1, 1),
++                   row(0, 1, 1, 0, 1),
++                   row(0, 1, 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),
++                   row(0, 1, 1, 1, 2));
++
++        assertRows(execute("SELECT * FROM %s WHERE (b) IN ((?), (?)) AND e = ?", 0, 1, 2),
++                   row(0, 0, 1, 1, 2),
++                   row(0, 1, 1, 1, 2));
++
++        assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?)) AND e = ?", 0, 1, 2),
++                   row(0, 0, 1, 1, 2));
++
++        assertRows(execute("SELECT * FROM %s WHERE (b, c) IN ((?, ?), (?, ?)) AND e = ?", 0, 1, 1, 1, 2),
++                   row(0, 0, 1, 1, 2),
++                   row(0, 1, 1, 1, 2));
++
++        assertRows(execute("SELECT * FROM %s WHERE (b) >= (?) AND e = ?", 1, 2),
++                   row(0, 1, 1, 1, 2));
++
++        assertRows(execute("SELECT * FROM %s WHERE (b, c) >= (?, ?) AND e = ?", 1, 1, 2),
++                   row(0, 1, 1, 1, 2));
+     }
+ 
+     @Test
 -    public void testPartitionWithIndex() throws Throwable
++    public void testMultiplePartitionKeyAndMultiClusteringWithIndex() throws Throwable
+     {
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 0, 0, 0)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 0, 1)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 1, 2)");
 -
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 0, 0, 3)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 0, 4)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 1, 5)");
 -
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 2, 0, 0, 5)");
 -
 -        UntypedResultSet results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) = (1) ALLOW FILTERING");
 -        assertEquals(3, results.size());
 -        checkRow(0, results, 0, 0, 1, 0, 0, 3);
 -        checkRow(1, results, 0, 0, 1, 1, 0, 4);
 -        checkRow(2, results, 0, 0, 1, 1, 1, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) = (1, 1) ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 0, 4);
 -        checkRow(1, results, 0, 0, 1, 1, 1, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0  AND (c) IN ((1)) AND f = 5 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) IN ((1), (2)) AND f = 5 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -        checkRow(1, results, 0, 0, 2, 0, 0, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0  AND (c, d) IN ((1, 0)) AND f = 3 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 0, 1, 0, 0, 3);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c) >= (1) AND f = 5 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -        checkRow(1, results, 0, 0, 2, 0, 0, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) >= (1, 1) AND f = 5 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -        checkRow(1, results, 0, 0, 2, 0, 0, 5);
 -    }
++        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)");
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testMissingPartitionComponentWithInRestrictionOnIndexedColumn() throws Throwable
 -    {
 -        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) IN ((1, 1)) ALLOW FILTERING");
 -    }
++        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);
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testMissingPartitionComponentWithSliceRestrictionOnIndexedColumn() throws Throwable
 -    {
 -        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND (c, d) >= (1, 1) ALLOW FILTERING");
 -    }
++        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);
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testPrepareLiteralInWithShortTuple() throws Throwable
 -    {
 -        prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?))");
 -    }
++        execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 2, 0, 0, 5);
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testPrepareLiteralInWithLongTuple() throws Throwable
 -    {
 -        prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?, ?, ?))");
 -    }
++        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));
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testPrepareLiteralInWithPartitionKey() throws Throwable
 -    {
 -        prepare("SELECT * FROM %s.multiple_clustering WHERE (a, b, c, d) IN ((?, ?, ?, ?))");
 -    }
++        assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c, d) = (?, ?) ALLOW FILTERING", 0, 1, 1),
++                   row(0, 0, 1, 1, 0, 4),
++                   row(0, 0, 1, 1, 1, 5));
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testPrepareLiteralInSkipsClusteringColumn() throws Throwable
 -    {
 -        prepare("SELECT * FROM %s.multiple_clustering WHERE (c, d) IN ((?, ?))");
 -    }
++        assertInvalidMessage("Partition key part b must be restricted since preceding part is",
++                             "SELECT * FROM %s WHERE a = ? AND (c, d) IN ((?, ?)) ALLOW FILTERING", 0, 1, 1);
+ 
 -    private static QueryOptions makeIntOptions(Integer... values)
 -    {
 -        List<ByteBuffer> buffers = new ArrayList<>(values.length);
 -        for (int value : values)
 -            buffers.add(ByteBufferUtil.bytes(value));
 -        return new QueryOptions(ConsistencyLevel.ONE, buffers);
 -    }
++        assertInvalidMessage("Partition key part b must be restricted since preceding part is",
++                             "SELECT * FROM %s WHERE a = ? AND (c, d) >= (?, ?) ALLOW FILTERING", 0, 1, 1);
+ 
 -    private static ByteBuffer tuple(Integer... values)
 -    {
 -        List<AbstractType<?>> types = new ArrayList<>(values.length);
 -        ByteBuffer[] buffers = new ByteBuffer[values.length];
 -        for (int i = 0; i < values.length; i++)
 -        {
 -            types.add(Int32Type.instance);
 -            buffers[i] = ByteBufferUtil.bytes(values[i]);
 -        }
++        assertRows(execute("SELECT * FROM %s WHERE a = ? AND (c) IN ((?)) AND f = ? ALLOW FILTERING", 0, 1, 5),
++                   row(0, 0, 1, 1, 1, 5));
+ 
 -        TupleType type = new TupleType(types);
 -        return type.buildValue(buffers);
 -    }
++        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));
+ 
 -    private static ByteBuffer list(ByteBuffer... values)
 -    {
 -        return CollectionType.pack(Arrays.asList(values), values.length);
 -    }
++        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));
+ 
 -    private static QueryOptions options(ByteBuffer... buffers)
 -    {
 -        return new QueryOptions(ConsistencyLevel.ONE, Arrays.asList(buffers));
 -    }
++        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));
+ 
 -    private static void checkRow(int rowIndex, UntypedResultSet results, Integer... expectedValues)
 -    {
 -        List<UntypedResultSet.Row> rows = newArrayList(results.iterator());
 -        UntypedResultSet.Row row = rows.get(rowIndex);
 -        Iterator<ColumnSpecification> columns = row.getColumns().iterator();
 -        for (Integer expected : expectedValues)
 -        {
 -            String columnName = columns.next().name.toString();
 -            int actual = row.getInt(columnName);
 -            assertEquals(String.format("Expected value %d for column %s in row %d, but got %s", actual, columnName, rowIndex, expected),
 -                         (long) expected, actual);
 -        }
++        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));
+     }
  }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/07ffe1b1/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
index 2ad4bda,34d3bf1..604ec60
--- a/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
@@@ -17,51 -17,129 +17,91 @@@
   */
  package org.apache.cassandra.cql3;
  
 -import java.util.Iterator;
 -import java.util.List;
 -
 -import org.junit.AfterClass;
 -import org.junit.BeforeClass;
  import org.junit.Test;
  
 -import org.apache.cassandra.SchemaLoader;
 -import org.apache.cassandra.db.ConsistencyLevel;
 -import org.apache.cassandra.exceptions.InvalidRequestException;
 -import org.apache.cassandra.gms.Gossiper;
 -import org.apache.cassandra.service.ClientState;
 -
 -import static com.google.common.collect.Lists.newArrayList;
 -import static org.apache.cassandra.cql3.QueryProcessor.process;
 -import static org.apache.cassandra.cql3.QueryProcessor.processInternal;
 -import static org.junit.Assert.assertEquals;
 +import java.util.ArrayList;
 +import java.util.List;
  
 -public class SingleColumnRelationTest
 +public class SingleColumnRelationTest extends CQLTester
  {
 -    static ClientState clientState;
 -    static String keyspace = "single_column_relation_test";
 -
 -    @BeforeClass
 -    public static void setUpClass() throws Throwable
 -    {
 -        SchemaLoader.loadSchema();
 -        executeSchemaChange("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
 -
 -        executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.partition_with_indices (a int, b int, c int, d int, e int, f int, PRIMARY KEY ((a, b), c, d, e))");
 -        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (c)");
 -        executeSchemaChange("CREATE INDEX ON %s.partition_with_indices (f)");
 -
 -        clientState = ClientState.forInternalCalls();
 -    }
 -
 -    @AfterClass
 -    public static void stopGossiper()
 +    @Test
 +    public void testInvalidCollectionEqualityRelation() throws Throwable
      {
 -        Gossiper.instance.stop();
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b set<int>, c list<int>, d map<int, int>)");
 +        createIndex("CREATE INDEX ON %s (b)");
 +        createIndex("CREATE INDEX ON %s (c)");
 +        createIndex("CREATE INDEX ON %s (d)");
 +
 +        assertInvalid("SELECT * FROM %s WHERE a = 0 AND b=?", set(0));
 +        assertInvalid("SELECT * FROM %s WHERE a = 0 AND c=?", list(0));
 +        assertInvalid("SELECT * FROM %s WHERE a = 0 AND d=?", map(0, 0));
      }
  
 -    private static void executeSchemaChange(String query) throws Throwable
 +    @Test
 +    public void testInvalidCollectionNonEQRelation() throws Throwable
      {
 -        try
 -        {
 -            process(String.format(query, keyspace), ConsistencyLevel.ONE);
 -        } catch (RuntimeException exc)
 -        {
 -            throw exc.getCause();
 -        }
 +        createTable("CREATE TABLE %s (a int PRIMARY KEY, b set<int>, c int)");
 +        createIndex("CREATE INDEX ON %s (c)");
 +        execute("INSERT INTO %s (a, b, c) VALUES (0, {0}, 0)");
 +
 +        // non-EQ operators
 +        assertInvalid("SELECT * FROM %s WHERE c = 0 AND b > ?", set(0));
 +        assertInvalid("SELECT * FROM %s WHERE c = 0 AND b >= ?", set(0));
 +        assertInvalid("SELECT * FROM %s WHERE c = 0 AND b < ?", set(0));
 +        assertInvalid("SELECT * FROM %s WHERE c = 0 AND b <= ?", set(0));
 +        assertInvalid("SELECT * FROM %s WHERE c = 0 AND b IN (?)", set(0));
      }
  
 -    private static UntypedResultSet execute(String query) throws Throwable
 +    @Test
 +    public void testLargeClusteringINValues() throws Throwable
      {
 -        try
 -        {
 -            return processInternal(String.format(query, keyspace));
 -        } catch (RuntimeException exc)
 -        {
 -            if (exc.getCause() != null)
 -                throw exc.getCause();
 -            throw exc;
 -        }
 +        createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY (k, c))");
 +        execute("INSERT INTO %s (k, c, v) VALUES (0, 0, 0)");
 +        List<Integer> inValues = new ArrayList<>(10000);
 +        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)
 +        );
      }
+ 
+     @Test
 -    public void testPartitionWithIndex() throws Throwable
++    public void testMultiplePartitionKeyWithIndex() throws Throwable
+     {
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 0, 0, 0)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 0, 1)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 0, 1, 1, 2)");
 -
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 0, 0, 3)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 0, 4)");
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 1, 1, 1, 5)");
 -
 -        execute("INSERT INTO %s.partition_with_indices (a, b, c, d, e, f) VALUES (0, 0, 2, 0, 0, 5)");
 -
 -        UntypedResultSet results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 ALLOW FILTERING");
 -        assertEquals(3, results.size());
 -        checkRow(0, results, 0, 0, 1, 0, 0, 3);
 -        checkRow(1, results, 0, 0, 1, 1, 0, 4);
 -        checkRow(2, results, 0, 0, 1, 1, 1, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 AND d = 1 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 0, 4);
 -        checkRow(1, results, 0, 0, 1, 1, 1, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c >= 1 AND f = 5 ALLOW FILTERING");
 -        assertEquals(2, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -        checkRow(1, results, 0, 0, 2, 0, 0, 5);
 -
 -        results = execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c = 1 AND d >= 1 AND f = 5 ALLOW FILTERING");
 -        assertEquals(1, results.size());
 -        checkRow(0, results, 0, 0, 1, 1, 1, 5);
 -    }
++        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)");
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testMissingPartitionComponentAndFileringOnTheSecondClusteringColumnWithoutAllowFiltering() throws Throwable
 -    {
 -        execute("SELECT * FROM %s.partition_with_indices WHERE d >= 1 AND f = 5");
 -    }
++        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);
+ 
 -    @Test(expected=InvalidRequestException.class)
 -    public void testMissingPartitionComponentWithSliceRestrictionOnIndexedColumn() throws Throwable
 -    {
 -        execute("SELECT * FROM %s.partition_with_indices WHERE a = 0 AND c >= 1 ALLOW FILTERING");
 -    }
++        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);
+ 
 -    private static void checkRow(int rowIndex, UntypedResultSet results, Integer... expectedValues)
 -    {
 -        List<UntypedResultSet.Row> rows = newArrayList(results.iterator());
 -        UntypedResultSet.Row row = rows.get(rowIndex);
 -        Iterator<ColumnSpecification> columns = row.getColumns().iterator();
 -        for (Integer expected : expectedValues)
 -        {
 -            String columnName = columns.next().name.toString();
 -            int actual = row.getInt(columnName);
 -            assertEquals(String.format("Expected value %d for column %s in row %d, but got %s", actual, columnName, rowIndex, expected),
 -                         (long) expected, actual);
 -        }
++        execute("INSERT INTO %s (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)", 0, 0, 2, 0, 0, 5);
++
++        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));
++
++        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",
++                             "SELECT * FROM %s WHERE a = ? AND c >= ? ALLOW FILTERING", 0, 1);
++
++        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));
++
++        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);
+     }
  }