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/01/07 21:06:39 UTC

cassandra git commit: Allow mixing token and partition key restrictions

Repository: cassandra
Updated Branches:
  refs/heads/trunk 0a80fe4b5 -> 493859bf6


Allow mixing token and partition key restrictions

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


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

Branch: refs/heads/trunk
Commit: 493859bf617ac80f560d02ad6d471aefd6a0ef91
Parents: 0a80fe4
Author: blerer <b_...@hotmail.com>
Authored: Wed Jan 7 14:05:38 2015 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Wed Jan 7 14:06:28 2015 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../apache/cassandra/cql3/TokenRelation.java    |   4 +-
 .../AbstractPrimaryKeyRestrictions.java         |  12 +
 .../restrictions/MultiColumnRestriction.java    |   4 +-
 .../SingleColumnPrimaryKeyRestrictions.java     |  16 +-
 .../restrictions/StatementRestrictions.java     |   5 +-
 .../cql3/restrictions/TokenFilter.java          | 237 +++++++++++++++++++
 .../cql3/restrictions/TokenRestriction.java     |  57 +++--
 .../cql3/SelectWithTokenFunctionTest.java       | 139 ++++++++++-
 9 files changed, 439 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 8ccc014..9f946a3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0
+ * Allow mixing token and partition key restrictions (CASSANDRA-7016)
  * Support index key/value entries on map collections (CASSANDRA-8473)
  * Modernize schema tables (CASSANDRA-8261)
  * Support for user-defined aggregation functions (CASSANDRA-8053)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/TokenRelation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/TokenRelation.java b/src/java/org/apache/cassandra/cql3/TokenRelation.java
index d1bd265..5896fae 100644
--- a/src/java/org/apache/cassandra/cql3/TokenRelation.java
+++ b/src/java/org/apache/cassandra/cql3/TokenRelation.java
@@ -69,7 +69,7 @@ public final class TokenRelation extends Relation
     {
         List<ColumnDefinition> columnDefs = getColumnDefinitions(cfm);
         Term term = toTerm(toReceivers(cfm, columnDefs), value, cfm.ksName, boundNames);
-        return new TokenRestriction.EQ(columnDefs, term);
+        return new TokenRestriction.EQ(cfm.getKeyValidatorAsCType(), columnDefs, term);
     }
 
     @Override
@@ -86,7 +86,7 @@ public final class TokenRelation extends Relation
     {
         List<ColumnDefinition> columnDefs = getColumnDefinitions(cfm);
         Term term = toTerm(toReceivers(cfm, columnDefs), value, cfm.ksName, boundNames);
-        return new TokenRestriction.Slice(columnDefs, bound, inclusive, term);
+        return new TokenRestriction.Slice(cfm.getKeyValidatorAsCType(), columnDefs, bound, inclusive, term);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
index f137a77..0107603 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java
@@ -17,11 +17,23 @@
  */
 package org.apache.cassandra.cql3.restrictions;
 
+import org.apache.cassandra.db.composites.CType;
+
 /**
  * Base class for <code>PrimaryKeyRestrictions</code>.
  */
 abstract class AbstractPrimaryKeyRestrictions extends AbstractRestriction implements PrimaryKeyRestrictions
 {
+    /**
+     * The composite type.
+     */
+    protected final CType ctype;
+
+    public AbstractPrimaryKeyRestrictions(CType ctype)
+    {
+        this.ctype = ctype;
+    }
+
     @Override
     public final boolean isEmpty()
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
index e3b3c4c..2d6deeb 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java
@@ -43,8 +43,6 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq
 
 public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestrictions
 {
-    protected final CType ctype;
-
     /**
      * The columns to which the restriction apply.
      */
@@ -52,7 +50,7 @@ public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestricti
 
     public MultiColumnRestriction(CType ctype, List<ColumnDefinition> columnDefs)
     {
-        this.ctype = ctype;
+        super(ctype);
         this.columnDefs = columnDefs;
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
index 5c8386e..d2a3885 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnPrimaryKeyRestrictions.java
@@ -43,11 +43,6 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestrictions
 {
     /**
-     * The composite type.
-     */
-    private final CType ctype;
-
-    /**
      * The restrictions.
      */
     private final SingleColumnRestrictions restrictions;
@@ -74,7 +69,7 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric
 
     public SingleColumnPrimaryKeyRestrictions(CType ctype)
     {
-        this.ctype = ctype;
+        super(ctype);
         this.restrictions = new SingleColumnRestrictions();
         this.eq = true;
     }
@@ -82,8 +77,8 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric
     private SingleColumnPrimaryKeyRestrictions(SingleColumnPrimaryKeyRestrictions primaryKeyRestrictions,
                                                SingleColumnRestriction restriction) throws InvalidRequestException
     {
+        super(primaryKeyRestrictions.ctype);
         this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction);
-        this.ctype = primaryKeyRestrictions.ctype;
 
         if (!primaryKeyRestrictions.isEmpty())
         {
@@ -166,9 +161,10 @@ final class SingleColumnPrimaryKeyRestrictions extends AbstractPrimaryKeyRestric
 
         if (restriction.isOnToken())
         {
-            checkTrue(isEmpty(), "Columns \"%s\" cannot be restricted by both a normal relation and a token relation",
-                      ((TokenRestriction) restriction).getColumnNamesAsString());
-            return (PrimaryKeyRestrictions) restriction;
+            if (isEmpty())
+                return (PrimaryKeyRestrictions) restriction;
+
+            return new TokenFilter(this, (TokenRestriction) restriction);
         }
 
         return new SingleColumnPrimaryKeyRestrictions(this, (SingleColumnRestriction) restriction);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index 60c7465..598478c 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -259,10 +259,9 @@ public final class StatementRestrictions
         // 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;
-        }
-        else if (hasPartitionKeyUnrestrictedComponents())
+
+        if (hasPartitionKeyUnrestrictedComponents())
         {
             if (!partitionKeyRestrictions.isEmpty())
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java b/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
new file mode 100644
index 0000000..4b5383b
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenFilter.java
@@ -0,0 +1,237 @@
+/*
+ * 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.List;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.ImmutableRangeSet;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
+
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.statements.Bound;
+import org.apache.cassandra.db.composites.Composite;
+import org.apache.cassandra.dht.IPartitioner;
+import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.service.StorageService;
+
+import static org.apache.cassandra.cql3.statements.Bound.END;
+import static org.apache.cassandra.cql3.statements.Bound.START;
+
+/**
+ * <code>Restriction</code> decorator used to merge non-token restriction and token restriction on partition keys.
+ */
+final class TokenFilter extends ForwardingPrimaryKeyRestrictions
+{
+    /**
+     * The decorated restriction
+     */
+    private PrimaryKeyRestrictions restrictions;
+
+    /**
+     * The restriction on the token
+     */
+    private TokenRestriction tokenRestriction;
+
+    /**
+     * The partitioner
+     */
+    private static final IPartitioner partitioner = StorageService.getPartitioner();
+
+    @Override
+    protected PrimaryKeyRestrictions getDelegate()
+    {
+        return restrictions;
+    }
+
+    @Override
+    public boolean isOnToken()
+    {
+        // if all partition key columns have non-token restrictions, we can simply use the token range to filter
+        // those restrictions and then ignore the token range
+        return restrictions.size() < tokenRestriction.size();
+    }
+
+    public TokenFilter(PrimaryKeyRestrictions restrictions, TokenRestriction tokenRestriction)
+    {
+        this.restrictions = restrictions;
+        this.tokenRestriction = tokenRestriction;
+    }
+
+    @Override
+    public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
+    {
+        return filter(restrictions.values(options), options);
+    }
+
+    @Override
+    public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
+    {
+        if (restriction.isOnToken())
+            return new TokenFilter(restrictions, (TokenRestriction) tokenRestriction.mergeWith(restriction));
+
+        return new TokenFilter(super.mergeWith(restriction), tokenRestriction);
+    }
+
+    @Override
+    public boolean isInclusive(Bound bound)
+    {
+        return tokenRestriction.isInclusive(bound);
+    }
+
+    @Override
+    public boolean hasBound(Bound b)
+    {
+        return tokenRestriction.hasBound(b);
+    }
+
+    @Override
+    public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException
+    {
+        return tokenRestriction.bounds(bound, options);
+    }
+
+    @Override
+    public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException
+    {
+        return tokenRestriction.boundsAsComposites(bound, options);
+    }
+
+    /**
+     * Filter the values returned by the restriction.
+     *
+     * @param values the values returned by the decorated restriction
+     * @param options the query options
+     * @return the values matching the token restriction
+     * @throws InvalidRequestException if the request is invalid
+     */
+    private List<ByteBuffer> filter(List<ByteBuffer> values, QueryOptions options) throws InvalidRequestException
+    {
+        RangeSet<Token> rangeSet = tokenRestriction.isSlice() ? toRangeSet(tokenRestriction, options)
+                                                              : toRangeSet(tokenRestriction.values(options));
+
+        return filterWithRangeSet(rangeSet, values);
+    }
+
+    /**
+     * Filter out the values for which the tokens are not included within the specified range.
+     *
+     * @param tokens the tokens range
+     * @param values the restricted values
+     * @return the values for which the tokens are not included within the specified range.
+     */
+    private static List<ByteBuffer> filterWithRangeSet(RangeSet<Token> tokens, List<ByteBuffer> values)
+    {
+        List<ByteBuffer> remaining = new ArrayList<>();
+
+        for (ByteBuffer value : values)
+        {
+            Token token = partitioner.getToken(value);
+
+            if (!tokens.contains(token))
+                continue;
+
+            remaining.add(value);
+        }
+        return remaining;
+    }
+
+    /**
+     * Converts the specified list into a range set.
+     *
+     * @param buffers the token restriction values
+     * @return the range set corresponding to the specified list
+     */
+    private static RangeSet<Token> toRangeSet(List<ByteBuffer> buffers)
+    {
+        ImmutableRangeSet.Builder<Token> builder = ImmutableRangeSet.builder();
+
+        for (ByteBuffer buffer : buffers)
+            builder.add(Range.singleton(deserializeToken(buffer)));
+
+        return builder.build();
+    }
+
+    /**
+     * Converts the specified slice into a range set.
+     *
+     * @param slice the slice to convert
+     * @param options the query option
+     * @return the range set corresponding to the specified slice
+     * @throws InvalidRequestException if the request is invalid
+     */
+    private static RangeSet<Token> toRangeSet(TokenRestriction slice, QueryOptions options) throws InvalidRequestException
+    {
+        if (slice.hasBound(START))
+        {
+            Token start = deserializeToken(slice.bounds(START, options).get(0));
+
+            BoundType startBoundType = toBoundType(slice.isInclusive(START));
+            BoundType endBoundType = toBoundType(slice.isInclusive(END));
+
+            if (slice.hasBound(END))
+            {
+                Token end = deserializeToken(slice.bounds(END, options).get(0));
+
+                if (start.equals(end) && (BoundType.OPEN == startBoundType || BoundType.OPEN == endBoundType))
+                    return ImmutableRangeSet.of();
+
+                if (start.compareTo(end) <= 0)
+                    return ImmutableRangeSet.of(Range.range(start,
+                                                            startBoundType,
+                                                            end,
+                                                            endBoundType));
+
+                return ImmutableRangeSet.<Token> builder()
+                                        .add(Range.upTo(end, endBoundType))
+                                        .add(Range.downTo(start, startBoundType))
+                                        .build();
+            }
+            return ImmutableRangeSet.of(Range.downTo(start,
+                                                     startBoundType));
+        }
+        Token end = deserializeToken(slice.bounds(END, options).get(0));
+        return ImmutableRangeSet.of(Range.upTo(end, toBoundType(slice.isInclusive(END))));
+    }
+
+    /**
+     * Deserializes the token corresponding to the specified buffer.
+     *
+     * @param buffer the buffer
+     * @return the token corresponding to the specified buffer
+     */
+    private static Token deserializeToken(ByteBuffer buffer)
+    {
+        return partitioner.getTokenFactory().fromByteArray(buffer);
+    }
+
+    private static BoundType toBoundType(boolean inclusive)
+    {
+        return inclusive ? BoundType.CLOSED : BoundType.OPEN;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
index 85d614e..8d63fea 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java
@@ -29,6 +29,7 @@ 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;
@@ -48,10 +49,12 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
     /**
      * 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(List<ColumnDefinition> columnDefs)
+    public TokenRestriction(CType ctype, List<ColumnDefinition> columnDefs)
     {
+        super(ctype);
         this.columnDefs = columnDefs;
     }
 
@@ -101,13 +104,43 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
         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(List<ColumnDefinition> columnDefs, Term value)
+        public EQ(CType ctype, List<ColumnDefinition> columnDefs, Term value)
         {
-            super(columnDefs);
+            super(ctype, columnDefs);
             this.value = value;
         }
 
@@ -124,7 +157,7 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
         }
 
         @Override
-        public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
+        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)));
@@ -141,9 +174,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
     {
         private final TermSlice slice;
 
-        public Slice(List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term)
+        public Slice(CType ctype, List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term)
         {
-            super(columnDefs);
+            super(ctype, columnDefs);
             slice = TermSlice.newInstance(bound, inclusive, term);
         }
 
@@ -185,13 +218,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
         }
 
         @Override
-        public PrimaryKeyRestrictions mergeWith(Restriction otherRestriction)
+        protected PrimaryKeyRestrictions doMergeWith(TokenRestriction otherRestriction)
         throws InvalidRequestException
         {
-            if (!otherRestriction.isOnToken())
-                throw invalidRequest("Columns \"%s\" cannot be restricted by both a normal relation and a token relation",
-                                     getColumnNamesAsString());
-
             if (!otherRestriction.isSlice())
                 throw invalidRequest("Columns \"%s\" cannot be restricted by both an equality and an inequality relation",
                                      getColumnNamesAsString());
@@ -206,7 +235,7 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
                 throw invalidRequest("More than one restriction was found for the end bound on %s",
                                      getColumnNamesAsString());
 
-            return new Slice(columnDefs,  slice.merge(otherSlice.slice));
+            return new Slice(ctype, columnDefs,  slice.merge(otherSlice.slice));
         }
 
         @Override
@@ -215,9 +244,9 @@ public abstract class TokenRestriction extends AbstractPrimaryKeyRestrictions
             return String.format("SLICE%s", slice);
         }
 
-        private Slice(List<ColumnDefinition> columnDefs, TermSlice slice)
+        private Slice(CType ctype, List<ColumnDefinition> columnDefs, TermSlice slice)
         {
-            super(columnDefs);
+            super(ctype, columnDefs);
             this.slice = slice;
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/493859bf/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java b/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java
index 39b62e3..a365c09 100644
--- a/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java
+++ b/test/unit/org/apache/cassandra/cql3/SelectWithTokenFunctionTest.java
@@ -17,6 +17,8 @@
  */
 package org.apache.cassandra.cql3;
 
+import java.util.Arrays;
+
 import org.junit.Test;
 
 public class SelectWithTokenFunctionTest extends CQLTester
@@ -30,10 +32,6 @@ public class SelectWithTokenFunctionTest extends CQLTester
         assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?)", 0), row(0, "a"));
         assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?) and token(a) < token(?)", 0, 1), row(0, "a"));
         assertInvalid("SELECT * FROM %s WHERE token(a) > token(?)", "a");
-        assertInvalidMessage("Columns \"a\" cannot be restricted by both a normal relation and a token relation",
-                             "SELECT * FROM %s WHERE token(a) > token(?) AND a = ?", 1, 1);
-        assertInvalidMessage("Columns \"a\" cannot be restricted by both a normal relation and a token relation",
-                             "SELECT * FROM %s WHERE a = ? and token(a) > token(?)", 1, 1);
         assertInvalidMessage("The token() function must contains only partition key components",
                              "SELECT * FROM %s WHERE token(a, b) >= token(?, ?)", "b", 0);
         assertInvalidMessage("More than one restriction was found for the start bound on a",
@@ -88,4 +86,137 @@ public class SelectWithTokenFunctionTest extends CQLTester
         assertInvalidMessage("The token() function must be applied to all partition key components or none of them",
                              "SELECT * FROM %s WHERE token(a) > token(?, ?) and token(b) > token(?, ?)", 0, "a", 0, "a");
     }
+
+    @Test
+    public void testSingleColumnPartitionKeyWithTokenNonTokenRestrictionsMix() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int primary key, b int)");
+
+        execute("INSERT INTO %s (a, b) VALUES (0, 0);");
+        execute("INSERT INTO %s (a, b) VALUES (1, 1);");
+        execute("INSERT INTO %s (a, b) VALUES (2, 2);");
+        execute("INSERT INTO %s (a, b) VALUES (3, 3);");
+        execute("INSERT INTO %s (a, b) VALUES (4, 4);");
+        assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?);", 1, 3),
+                   row(1, 1),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a)> token(?) and token(a) <= token(?);", 1, 3),
+                   row(2, 2),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a)= token(2);"),
+                   row(2, 2));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a IN (?, ?);",
+                           1, 3, 1, 3),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) < token(?) AND token(a) >= token(?) AND a IN (?, ?);",
+                           1, 3, 1, 3),
+                   row(3, 3));
+        assertInvalidMessage("Only EQ and IN relation are supported on the partition key (unless you use the token() function)",
+                             "SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a > ?;", 1, 3, 1);
+
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a IN ?;",
+                           1, 3, Arrays.asList(1, 3)),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a = ?;", 1, 3),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE a = ? AND token(a) > token(?);", 3, 1),
+                   row(3, 3));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a = ?;", 3, 1));
+        assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND token(a) > token(?);", 1, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a IN (?, ?);", 2, 1, 3),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) < token(?) AND a IN (?, ?) ;", 2, 5, 1, 3),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) AND token(a) > token(?) AND token(a) < token(?);", 1, 3, 2, 5),
+                   row(3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) > token(?) AND a IN (?, ?) AND token(a) < token(?);", 2, 1, 3, 5),
+                   row(3, 3));
+        assertEmpty(execute("SELECT * FROM %s WHERE a IN (?, ?) AND token(a) > token(?);", 1, 3, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) <= token(?) AND a = ?;", 2, 2),
+                   row(2, 2));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) <= token(?) AND a = ?;", 2, 3));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) = token(?) AND a = ?;", 2, 3));
+        assertRows(execute("SELECT * FROM %s WHERE token(a) >= token(?) AND token(a) <= token(?) AND a = ?;", 2, 2, 2),
+                   row(2, 2));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) >= token(?) AND token(a) < token(?) AND a = ?;", 2, 2, 2));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) <= token(?) AND a = ?;", 2, 2, 2));
+        assertEmpty(execute("SELECT * FROM %s WHERE token(a) > token(?) AND token(a) < token(?) AND a = ?;", 2, 2, 2));
+    }
+
+    @Test
+    public void testMultiColumnPartitionKeyWithTokenNonTokenRestrictionsMix() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int, b int, c int, primary key((a, b)))");
+
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 0, 0);");
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 1, 1);");
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 2, 2);");
+        execute("INSERT INTO %s (a, b, c) VALUES (1, 0, 3);");
+        execute("INSERT INTO %s (a, b, c) VALUES (1, 1, 4);");
+
+        assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?);", 0, 0),
+                   row(0, 1, 1),
+                   row(0, 2, 2),
+                   row(1, 0, 3),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND a = ? AND b IN (?, ?);",
+                           0, 0, 1, 0, 1),
+                   row(1, 0, 3),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE a = ? AND token(a, b) > token(?, ?) AND b IN (?, ?);",
+                           1, 0, 0, 0, 1),
+                   row(1, 0, 3),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND a = ?;",
+                           0, 1, 0, 0, 1),
+                   row(1, 0, 3),
+                   row(1, 1, 4));
+
+        assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND token(a, b) < token(?, ?) AND a = ?;",
+                            0, 1, 0, 0, 0, 0, 1));
+
+        assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) > token(?, ?) AND token(a, b) <= token(?, ?) AND a = ?;",
+                            0, 1, 0, 0, 0, 0, 1));
+
+        assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) >= token(?, ?) AND token(a, b) < token(?, ?) AND a = ?;",
+                            0, 1, 0, 0, 0, 0, 1));
+
+        assertEmpty(execute("SELECT * FROM %s WHERE b IN (?, ?) AND token(a, b) = token(?, ?) AND a = ?;",
+                            0, 1, 0, 0, 1));
+
+        assertInvalidMessage("Partition key parts: b must be restricted as other parts are",
+                             "SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND a = ?;", 0, 0, 1);
+    }
+
+    @Test
+    public void testMultiColumnPartitionKeyWithIndexAndTokenNonTokenRestrictionsMix() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int, b int, c int, primary key((a, b)))");
+        createIndex("CREATE INDEX ON %s(b)");
+        createIndex("CREATE INDEX ON %s(c)");
+
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 0, 0);");
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 1, 1);");
+        execute("INSERT INTO %s (a, b, c) VALUES (0, 2, 2);");
+        execute("INSERT INTO %s (a, b, c) VALUES (1, 0, 3);");
+        execute("INSERT INTO %s (a, b, c) VALUES (1, 1, 4);");
+
+        assertRows(execute("SELECT * FROM %s WHERE b = ?;", 1),
+                   row(0, 1, 1),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE token(a, b) > token(?, ?) AND b = ?;", 0, 0, 1),
+                   row(0, 1, 1),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE b = ? AND token(a, b) > token(?, ?);", 1, 0, 0),
+                   row(0, 1, 1),
+                   row(1, 1, 4));
+
+        assertRows(execute("SELECT * FROM %s WHERE b = ? AND token(a, b) > token(?, ?) and c = ? ALLOW FILTERING;", 1, 0, 0, 4),
+                   row(1, 1, 4));
+    }
 }