Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.1 6ce045642 -> 3d5f3a659


Support non-equals conditions for LWT

Patch by Tyler Hobbs; review by Sylvain Lebresne for CASSANDRA-6839


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

Branch: refs/heads/cassandra-2.1
Commit: 3d5f3a659695212b4a6dbde6cb5a07a2bea1aca0
Parents: 6ce0456
Author: Tyler Hobbs <[email protected]>
Authored: Tue Aug 26 12:53:13 2014 -0500
Committer: Tyler Hobbs <[email protected]>
Committed: Tue Aug 26 12:53:13 2014 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../apache/cassandra/cql3/AbstractMarker.java   |   3 -
 .../apache/cassandra/cql3/ColumnCondition.java  | 609 +++++++++++++++----
 src/java/org/apache/cassandra/cql3/Cql.g        |  17 +-
 src/java/org/apache/cassandra/cql3/Lists.java   |   7 +-
 .../org/apache/cassandra/cql3/Relation.java     |   4 +-
 .../cql3/statements/CQL3CasRequest.java         |  12 +-
 .../cql3/statements/SelectStatement.java        |   6 +
 .../cassandra/serializers/ListSerializer.java   |  12 +-
 .../cassandra/cql3/ColumnConditionTest.java     | 577 ++++++++++++++++++
 .../cassandra/cql3/MultiColumnRelationTest.java |   7 +
 11 files changed, 1120 insertions(+), 135 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index bf030c8..b5c3a32 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.1
+ * Support non-equals conditions in lightweight transactions (CASSANDRA-6839)
  * Add IF [NOT] EXISTS to create/drop triggers (CASSANDRA-7606)
  * (cqlsh) Display the current logged-in user (CASSANDRA-7785)
  * (cqlsh) Don't ignore CTRL-C during COPY FROM execution (CASSANDRA-7815)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/AbstractMarker.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java 
b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
index 0b59ed4..10a4dff 100644
--- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java
+++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
@@ -105,9 +105,6 @@ public abstract class AbstractMarker extends 
Term.NonTerminal
         @Override
         public AbstractMarker prepare(String keyspace, ColumnSpecification 
receiver) throws InvalidRequestException
         {
-            if (receiver.type instanceof CollectionType)
-                throw new InvalidRequestException("Collection columns do not 
support IN relations");
-
             return new Lists.Marker(bindIndex, makeInReceiver(receiver));
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/ColumnCondition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java 
b/src/java/org/apache/cassandra/cql3/ColumnCondition.java
index ed2b6b4..703ac5b 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java
@@ -23,43 +23,77 @@ import java.util.*;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterators;
+import static com.google.common.collect.Lists.newArrayList;
 
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.composites.CellName;
+import org.apache.cassandra.db.composites.CellNameType;
 import org.apache.cassandra.db.composites.Composite;
 import org.apache.cassandra.db.filter.ColumnSlice;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.utils.ByteBufferUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A CQL3 condition.
  */
 public class ColumnCondition
 {
+    private static final Logger logger = 
LoggerFactory.getLogger(ColumnCondition.class);
+
     public final ColumnDefinition column;
 
     // For collection, when testing the equality of a specific element, null 
otherwise.
     private final Term collectionElement;
 
-    private final Term value;
+    private final Term value;  // a single value or a marker for a list of IN 
values
+    private final List<Term> inValues;
+
+    public final Relation.Type operator;
 
-    private ColumnCondition(ColumnDefinition column, Term collectionElement, 
Term value)
+    private ColumnCondition(ColumnDefinition column, Term collectionElement, 
Term value, List<Term> inValues, Relation.Type op)
     {
         this.column = column;
         this.collectionElement = collectionElement;
         this.value = value;
+        this.inValues = inValues;
+        this.operator = op;
+
+        if (!operator.equals(Relation.Type.IN))
+            assert this.inValues == null;
+    }
+
+    public static ColumnCondition condition(ColumnDefinition column, Term 
value, Relation.Type op)
+    {
+        return new ColumnCondition(column, null, value, null, op);
+    }
+
+    public static ColumnCondition condition(ColumnDefinition column, Term 
collectionElement, Term value, Relation.Type op)
+    {
+        return new ColumnCondition(column, collectionElement, value, null, op);
+    }
+
+    public static ColumnCondition inCondition(ColumnDefinition column, 
List<Term> inValues)
+    {
+        return new ColumnCondition(column, null, null, inValues, 
Relation.Type.IN);
     }
 
-    public static ColumnCondition equal(ColumnDefinition column, Term value)
+    public static ColumnCondition inCondition(ColumnDefinition column, Term 
collectionElement, List<Term> inValues)
     {
-        return new ColumnCondition(column, null, value);
+        return new ColumnCondition(column, collectionElement, null, inValues, 
Relation.Type.IN);
     }
 
-    public static ColumnCondition equal(ColumnDefinition column, Term 
collectionElement, Term value)
+    public static ColumnCondition inCondition(ColumnDefinition column, Term 
inMarker)
     {
-        return new ColumnCondition(column, collectionElement, value);
+        return new ColumnCondition(column, null, inMarker, null, 
Relation.Type.IN);
+    }
+
+    public static ColumnCondition inCondition(ColumnDefinition column, Term 
collectionElement, Term inMarker)
+    {
+        return new ColumnCondition(column, collectionElement, inMarker, null, 
Relation.Type.IN);
     }
 
     /**
@@ -72,23 +106,40 @@ public class ColumnCondition
     {
         if (collectionElement != null)
             collectionElement.collectMarkerSpecification(boundNames);
-        value.collectMarkerSpecification(boundNames);
+
+        if (operator.equals(Relation.Type.IN) && inValues != null)
+        {
+            for (Term value : inValues)
+                value.collectMarkerSpecification(boundNames);
+        }
+        else
+        {
+            value.collectMarkerSpecification(boundNames);
+        }
     }
 
     public ColumnCondition.Bound bind(QueryOptions options) throws 
InvalidRequestException
     {
-        return column.type instanceof CollectionType
-             ? (collectionElement == null ? new CollectionBound(this, options) 
: new ElementAccessBound(this, options))
-             : new SimpleBound(this, options);
+        boolean isInCondition = operator.equals(Relation.Type.IN);
+        if (column.type instanceof CollectionType)
+        {
+            if (collectionElement == null)
+                return isInCondition ? new CollectionInBound(this, options) : 
new CollectionBound(this, options);
+            else
+                return isInCondition ? new ElementAccessInBound(this, options) 
: new ElementAccessBound(this, options);
+        }
+        return isInCondition ? new SimpleInBound(this, options) : new 
SimpleBound(this, options);
     }
 
     public static abstract class Bound
     {
         public final ColumnDefinition column;
+        public final Relation.Type operator;
 
-        protected Bound(ColumnDefinition column)
+        protected Bound(ColumnDefinition column, Relation.Type operator)
         {
             this.column = column;
+            this.operator = operator;
         }
 
         /**
@@ -101,11 +152,51 @@ public class ColumnCondition
             return null;
         }
 
-        protected boolean equalsValue(ByteBuffer value, Cell c, 
AbstractType<?> type, long now)
+        protected boolean isSatisfiedByValue(ByteBuffer value, Cell c, 
AbstractType<?> type, Relation.Type operator, long now) throws 
InvalidRequestException
+        {
+            ByteBuffer columnValue = (c == null || !c.isLive(now)) ? null : 
c.value();
+            return compareWithOperator(operator, type, value, columnValue);
+        }
+
+        /** Returns true if the operator is satisfied (i.e. "value operator 
otherValue == true"), false otherwise. */
+        protected boolean compareWithOperator(Relation.Type operator, 
AbstractType<?> type, ByteBuffer value, ByteBuffer otherValue) throws 
InvalidRequestException
         {
-            return value == null
-                 ? c == null || !c.isLive(now)
-                 : c != null && c.isLive(now) && type.compare(c.value(), 
value) == 0;
+            if (value == null)
+            {
+                switch (operator)
+                {
+                    case EQ:
+                        return otherValue == null;
+                    case NEQ:
+                        return otherValue != null;
+                    default:
+                        throw new 
InvalidRequestException(String.format("Invalid comparison with null for 
operator \"%s\"", operator));
+                }
+            }
+            else if (otherValue == null)
+            {
+                // the condition value is not null, so only NEQ can return true
+                return operator.equals(Relation.Type.NEQ);
+            }
+            int comparison = type.compare(otherValue, value);
+            switch (operator)
+            {
+                case EQ:
+                    return comparison == 0;
+                case LT:
+                    return comparison < 0;
+                case LTE:
+                    return comparison <= 0;
+                case GT:
+                    return comparison > 0;
+                case GTE:
+                    return comparison >= 0;
+                case NEQ:
+                    return comparison != 0;
+                default:
+                    // we shouldn't get IN, CONTAINS, or CONTAINS KEY here
+                    throw new AssertionError();
+            }
         }
 
         protected Iterator<Cell> collectionColumns(CellName collection, 
ColumnFamily cf, final long now)
@@ -124,54 +215,85 @@ public class ColumnCondition
         }
     }
 
-    private static class SimpleBound extends Bound
+    /**
+     * A condition on a single non-collection column. This does not support IN 
operators (see SimpleInBound).
+     */
+    static class SimpleBound extends Bound
     {
         public final ByteBuffer value;
 
         private SimpleBound(ColumnCondition condition, QueryOptions options) 
throws InvalidRequestException
         {
-            super(condition.column);
+            super(condition.column, condition.operator);
             assert !(column.type instanceof CollectionType) && 
condition.collectionElement == null;
+            assert !condition.operator.equals(Relation.Type.IN);
             this.value = condition.value.bindAndGet(options);
         }
 
         public boolean appliesTo(Composite rowPrefix, ColumnFamily current, 
long now) throws InvalidRequestException
         {
             CellName name = current.metadata().comparator.create(rowPrefix, 
column);
-            return equalsValue(value, current.getColumn(name), column.type, 
now);
+            return isSatisfiedByValue(value, current.getColumn(name), 
column.type, operator, now);
         }
 
         @Override
-        public boolean equals(Object o)
+        public int hashCode()
         {
-            if (!(o instanceof SimpleBound))
-                return false;
+            return Objects.hashCode(column, value, operator);
+        }
+    }
 
-            SimpleBound that = (SimpleBound)o;
-            if (!column.equals(that.column))
-                return false;
+    /**
+     * An IN condition on a single non-collection column.
+     */
+    static class SimpleInBound extends Bound
+    {
+        public final List<ByteBuffer> inValues;
 
-            return value == null || that.value == null
-                 ? value == null && that.value == null
-                 : column.type.compare(value, that.value) == 0;
+        private SimpleInBound(ColumnCondition condition, QueryOptions options) 
throws InvalidRequestException
+        {
+            super(condition.column, condition.operator);
+            assert !(column.type instanceof CollectionType) && 
condition.collectionElement == null;
+            assert condition.operator.equals(Relation.Type.IN);
+            if (condition.inValues == null)
+                this.inValues = ((Lists.Marker) 
condition.value).bind(options).getElements();
+            else
+            {
+                this.inValues = new ArrayList<>(condition.inValues.size());
+                for (Term value : condition.inValues)
+                    this.inValues.add(value.bindAndGet(options));
+            }
+        }
+
+        public boolean appliesTo(Composite rowPrefix, ColumnFamily current, 
long now) throws InvalidRequestException
+        {
+            CellName name = current.metadata().comparator.create(rowPrefix, 
column);
+            for (ByteBuffer value : inValues)
+            {
+                if (isSatisfiedByValue(value, current.getColumn(name), 
column.type, Relation.Type.EQ, now))
+                    return true;
+            }
+            return false;
         }
 
         @Override
         public int hashCode()
         {
-            return Objects.hashCode(column, value);
+            return Objects.hashCode(column, inValues, operator);
         }
     }
 
-    private static class ElementAccessBound extends Bound
+    /** A condition on an element of a collection column. IN operators are not 
supported here, see ElementAccessInBound. */
+    static class ElementAccessBound extends Bound
     {
         public final ByteBuffer collectionElement;
         public final ByteBuffer value;
 
         private ElementAccessBound(ColumnCondition condition, QueryOptions 
options) throws InvalidRequestException
         {
-            super(condition.column);
+            super(condition.column, condition.operator);
             assert column.type instanceof CollectionType && 
condition.collectionElement != null;
+            assert !condition.operator.equals(Relation.Type.IN);
             this.collectionElement = 
condition.collectionElement.bindAndGet(options);
             this.value = condition.value.bindAndGet(options);
         }
@@ -182,24 +304,34 @@ public class ColumnCondition
                 throw new InvalidRequestException("Invalid null value for " + 
(column.type instanceof MapType ? "map" : "list") + " element access");
 
             if (column.type instanceof MapType)
-                return equalsValue(value, 
current.getColumn(current.metadata().comparator.create(rowPrefix, column, 
collectionElement)), ((MapType)column.type).values, now);
+            {
+                Cell cell = 
current.getColumn(current.metadata().comparator.create(rowPrefix, column, 
collectionElement));
+                return isSatisfiedByValue(value, cell, ((MapType) 
column.type).values, operator, now);
+            }
 
+            // sets don't have element access, so it's a list
             assert column.type instanceof ListType;
+            ByteBuffer columnValue = getListItem(
+                    
collectionColumns(current.metadata().comparator.create(rowPrefix, column), 
current, now),
+                    getListIndex(collectionElement));
+            return compareWithOperator(operator, 
((ListType)column.type).elements, value, columnValue);
+        }
+
+        static int getListIndex(ByteBuffer collectionElement) throws 
InvalidRequestException
+        {
             int idx = ByteBufferUtil.toInt(collectionElement);
             if (idx < 0)
                 throw new InvalidRequestException(String.format("Invalid 
negative list index %d", idx));
+            return idx;
+        }
 
-            Iterator<Cell> iter = 
collectionColumns(current.metadata().comparator.create(rowPrefix, column), 
current, now);
-            int adv = Iterators.advance(iter, idx);
-            if (adv != idx || !iter.hasNext())
-                throw new InvalidRequestException(String.format("List index %d 
out of bound, list has size %d", idx, adv));
-
-            // We don't support null values inside collections, so a condition 
like 'IF l[3] = null' can only
-            // be false. We do special case though, as the compare below might 
mind getting a null.
-            if (value == null)
-                return false;
-
-            return 
((ListType)column.type).elements.compare(iter.next().value(), value) == 0;
+        static ByteBuffer getListItem(Iterator<Cell> iter, int index) throws 
InvalidRequestException
+        {
+            int adv = Iterators.advance(iter, index);
+            if (adv == index && iter.hasNext())
+                return iter.next().value();
+            else
+                return null;
         }
 
         public ByteBuffer getCollectionElementValue()
@@ -208,47 +340,83 @@ public class ColumnCondition
         }
 
         @Override
-        public boolean equals(Object o)
+        public int hashCode()
         {
-            if (!(o instanceof ElementAccessBound))
-                return false;
+            return Objects.hashCode(column, collectionElement, value, 
operator);
+        }
+    }
 
-            ElementAccessBound that = (ElementAccessBound)o;
-            if (!column.equals(that.column))
-                return false;
+    static class ElementAccessInBound extends Bound
+    {
+        public final ByteBuffer collectionElement;
+        public final List<ByteBuffer> inValues;
 
-            if ((collectionElement == null) != (that.collectionElement == 
null))
-                return false;
+        private ElementAccessInBound(ColumnCondition condition, QueryOptions 
options) throws InvalidRequestException
+        {
+            super(condition.column, condition.operator);
+            assert column.type instanceof CollectionType && 
condition.collectionElement != null;
+            this.collectionElement = 
condition.collectionElement.bindAndGet(options);
 
-            if (collectionElement != null)
+            if (condition.inValues == null)
+                this.inValues = ((Lists.Marker) 
condition.value).bind(options).getElements();
+            else
             {
-                assert column.type instanceof ListType || column.type 
instanceof MapType;
-                AbstractType<?> comparator = column.type instanceof ListType
-                                           ? Int32Type.instance
-                                           : ((MapType)column.type).keys;
+                this.inValues = new ArrayList<>(condition.inValues.size());
+                for (Term value : condition.inValues)
+                    this.inValues.add(value.bindAndGet(options));
+            }
+        }
 
-                if (comparator.compare(collectionElement, 
that.collectionElement) != 0)
-                    return false;
+        public boolean appliesTo(Composite rowPrefix, ColumnFamily current, 
final long now) throws InvalidRequestException
+        {
+            if (collectionElement == null)
+                throw new InvalidRequestException("Invalid null value for " + 
(column.type instanceof MapType ? "map" : "list") + " element access");
+
+            CellNameType nameType = current.metadata().comparator;
+            if (column.type instanceof MapType)
+            {
+                CellName name = nameType.create(rowPrefix, column, 
collectionElement);
+                Cell item = current.getColumn(name);
+                AbstractType<?> valueType = ((MapType) column.type).values;
+                for (ByteBuffer value : inValues)
+                {
+                    if (isSatisfiedByValue(value, item, valueType, 
Relation.Type.EQ, now))
+                        return true;
+                }
+                return false;
             }
 
-            return column.type.compare(value, that.value) == 0;
+            assert column.type instanceof ListType;
+            ByteBuffer columnValue = ElementAccessBound.getListItem(
+                    collectionColumns(nameType.create(rowPrefix, column), 
current, now),
+                    ElementAccessBound.getListIndex(collectionElement));
+
+            AbstractType<?> valueType = ((ListType) column.type).elements;
+            for (ByteBuffer value : inValues)
+            {
+                if (compareWithOperator(Relation.Type.EQ, valueType, value, 
columnValue))
+                    return true;
+            }
+            return false;
         }
 
         @Override
         public int hashCode()
         {
-            return Objects.hashCode(column, collectionElement, value);
+            return Objects.hashCode(column, collectionElement, inValues, 
operator);
         }
     }
 
-    private static class CollectionBound extends Bound
+    /** A condition on an entire collection column. IN operators are not 
supported here, see CollectionInBound. */
+    static class CollectionBound extends Bound
     {
         public final Term.Terminal value;
 
         private CollectionBound(ColumnCondition condition, QueryOptions 
options) throws InvalidRequestException
         {
-            super(condition.column);
+            super(condition.column, condition.operator);
             assert column.type instanceof CollectionType && 
condition.collectionElement == null;
+            assert !condition.operator.equals(Relation.Type.IN);
             this.value = condition.value.bind(options);
         }
 
@@ -258,78 +426,113 @@ public class ColumnCondition
 
             Iterator<Cell> iter = 
collectionColumns(current.metadata().comparator.create(rowPrefix, column), 
current, now);
             if (value == null)
+            {
+                if (operator.equals(Relation.Type.EQ))
+                    return !iter.hasNext();
+                else if (operator.equals(Relation.Type.NEQ))
+                    return iter.hasNext();
+                else
+                    throw new InvalidRequestException(String.format("Invalid 
comparison with null for operator \"%s\"", operator));
+            }
+
+            return valueAppliesTo(type, iter, value, operator);
+        }
+
+        static boolean valueAppliesTo(CollectionType type, Iterator<Cell> 
iter, Term.Terminal value, Relation.Type operator)
+        {
+            if (value == null)
                 return !iter.hasNext();
 
             switch (type.kind)
             {
-                case LIST: return listAppliesTo((ListType)type, iter, 
((Lists.Value)value).elements);
-                case SET: return setAppliesTo((SetType)type, iter, 
((Sets.Value)value).elements);
-                case MAP: return mapAppliesTo((MapType)type, iter, 
((Maps.Value)value).map);
+                case LIST: return listAppliesTo((ListType)type, iter, 
((Lists.Value)value).elements, operator);
+                case SET: return setAppliesTo((SetType)type, iter, 
((Sets.Value)value).elements, operator);
+                case MAP: return mapAppliesTo((MapType)type, iter, 
((Maps.Value)value).map, operator);
             }
             throw new AssertionError();
         }
 
-        private boolean listAppliesTo(ListType type, Iterator<Cell> iter, 
List<ByteBuffer> elements)
+        private static boolean setOrListAppliesTo(AbstractType<?> type, 
Iterator<Cell> iter, Iterator<ByteBuffer> conditionIter, Relation.Type 
operator, boolean isSet)
         {
-            for (ByteBuffer e : elements)
-                if (!iter.hasNext() || 
type.elements.compare(iter.next().value(), e) != 0)
-                    return false;
-            // We must not have more elements than expected
-            return !iter.hasNext();
+            while(iter.hasNext())
+            {
+                if (!conditionIter.hasNext())
+                    return operator.equals(Relation.Type.GT) || 
operator.equals(Relation.Type.GTE) || operator.equals(Relation.Type.NEQ);
+
+                // for lists we use the cell value; for sets we use the cell 
name
+                ByteBuffer cellValue = isSet? 
iter.next().name().collectionElement() : iter.next().value();
+                int comparison = type.compare(cellValue, conditionIter.next());
+                if (comparison != 0)
+                    return evaluateComparisonWithOperator(comparison, 
operator);
+            }
+
+            if (conditionIter.hasNext())
+                return operator.equals(Relation.Type.LT) || 
operator.equals(Relation.Type.LTE) || operator.equals(Relation.Type.NEQ);
+
+            // they're equal
+            return operator == Relation.Type.EQ || operator == 
Relation.Type.LTE || operator == Relation.Type.GTE;
         }
 
-        private boolean setAppliesTo(SetType type, Iterator<Cell> iter, 
Set<ByteBuffer> elements)
+        private static boolean evaluateComparisonWithOperator(int comparison, 
Relation.Type operator)
         {
-            Set<ByteBuffer> remaining = new TreeSet<>(type.elements);
-            remaining.addAll(elements);
-            while (iter.hasNext())
+            // called when comparison != 0
+            switch (operator)
             {
-                if (remaining.isEmpty())
-                    return false;
-
-                if (!remaining.remove(iter.next().name().collectionElement()))
+                case EQ:
                     return false;
+                case LT:
+                case LTE:
+                    return comparison < 0;
+                case GT:
+                case GTE:
+                    return comparison > 0;
+                case NEQ:
+                    return true;
+                default:
+                    throw new AssertionError();
             }
-            return remaining.isEmpty();
         }
 
-        private boolean mapAppliesTo(MapType type, Iterator<Cell> iter, 
Map<ByteBuffer, ByteBuffer> elements)
+        static boolean listAppliesTo(ListType type, Iterator<Cell> iter, 
List<ByteBuffer> elements, Relation.Type operator)
         {
-            Map<ByteBuffer, ByteBuffer> remaining = new TreeMap<>(type.keys);
-            remaining.putAll(elements);
-            while (iter.hasNext())
-            {
-                if (remaining.isEmpty())
-                    return false;
+            return setOrListAppliesTo(type.elements, iter, 
elements.iterator(), operator, false);
+        }
 
-                Cell c = iter.next();
-                ByteBuffer previous = 
remaining.remove(c.name().collectionElement());
-                if (previous == null || type.values.compare(previous, 
c.value()) != 0)
-                    return false;
-            }
-            return remaining.isEmpty();
+        static boolean setAppliesTo(SetType type, Iterator<Cell> iter, 
Set<ByteBuffer> elements, Relation.Type operator)
+        {
+            ArrayList<ByteBuffer> sortedElements = new 
ArrayList<>(elements.size());
+            sortedElements.addAll(elements);
+            Collections.sort(sortedElements, type.elements);
+            return setOrListAppliesTo(type.elements, iter, 
sortedElements.iterator(), operator, true);
         }
 
-        @Override
-        public boolean equals(Object o)
+        static boolean mapAppliesTo(MapType type, Iterator<Cell> iter, 
Map<ByteBuffer, ByteBuffer> elements, Relation.Type operator)
         {
-            if (!(o instanceof CollectionBound))
-                return false;
+            Iterator<Map.Entry<ByteBuffer, ByteBuffer>> conditionIter = 
elements.entrySet().iterator();
+            while(iter.hasNext())
+            {
+                if (!conditionIter.hasNext())
+                    return operator.equals(Relation.Type.GT) || 
operator.equals(Relation.Type.GTE) || operator.equals(Relation.Type.NEQ);
 
-            CollectionBound that = (CollectionBound)o;
-            if (!column.equals(that.column))
-                return false;
+                Map.Entry<ByteBuffer, ByteBuffer> conditionEntry = 
conditionIter.next();
+                Cell c = iter.next();
 
-            if (value == null || that.value == null)
-                return value == null && that.value == null;
+                // compare the keys
+                int comparison = 
type.keys.compare(c.name().collectionElement(), conditionEntry.getKey());
+                if (comparison != 0)
+                    return evaluateComparisonWithOperator(comparison, 
operator);
 
-            switch (((CollectionType)column.type).kind)
-            {
-                case LIST: return 
((Lists.Value)value).equals((ListType)column.type, (Lists.Value)that.value);
-                case SET: return 
((Sets.Value)value).equals((SetType)column.type, (Sets.Value)that.value);
-                case MAP: return 
((Maps.Value)value).equals((MapType)column.type, (Maps.Value)that.value);
+                // compare the values
+                comparison = type.values.compare(c.value(), 
conditionEntry.getValue());
+                if (comparison != 0)
+                    return evaluateComparisonWithOperator(comparison, 
operator);
             }
-            throw new AssertionError();
+
+            if (conditionIter.hasNext())
+                return operator.equals(Relation.Type.LT) || 
operator.equals(Relation.Type.LTE) || operator.equals(Relation.Type.NEQ);
+
+            // they're equal
+            return operator == Relation.Type.EQ || operator == 
Relation.Type.LTE || operator == Relation.Type.GTE;
         }
 
         @Override
@@ -355,50 +558,218 @@ public class ColumnCondition
         }
     }
 
+    public static class CollectionInBound extends Bound
+    {
+        public final List<Term.Terminal> inValues;
+
+        private CollectionInBound(ColumnCondition condition, QueryOptions 
options) throws InvalidRequestException
+        {
+            super(condition.column, condition.operator);
+            assert column.type instanceof CollectionType && 
condition.collectionElement == null;
+            assert condition.operator.equals(Relation.Type.IN);
+            inValues = new ArrayList<>();
+            if (condition.inValues == null)
+            {
+                // We have a list of serialized collections that need to be 
deserialized for later comparisons
+                CollectionType collectionType = (CollectionType) column.type;
+                Lists.Marker inValuesMarker = (Lists.Marker) condition.value;
+                if (column.type instanceof ListType)
+                {
+                    ListType deserializer = 
ListType.getInstance(collectionType.valueComparator());
+                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    {
+                        if (buffer == null)
+                            this.inValues.add(null);
+                        else
+                            
this.inValues.add(Lists.Value.fromSerialized(buffer, deserializer, 
options.getProtocolVersion()));
+                    }
+                }
+                else if (column.type instanceof MapType)
+                {
+                    MapType deserializer = 
MapType.getInstance(collectionType.nameComparator(), 
collectionType.valueComparator());
+                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    {
+                        if (buffer == null)
+                            this.inValues.add(null);
+                        else
+                            
this.inValues.add(Maps.Value.fromSerialized(buffer, deserializer, 
options.getProtocolVersion()));
+                    }
+                }
+                else if (column.type instanceof SetType)
+                {
+                    SetType deserializer = 
SetType.getInstance(collectionType.valueComparator());
+                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    {
+                        if (buffer == null)
+                            this.inValues.add(null);
+                        else
+                            
this.inValues.add(Sets.Value.fromSerialized(buffer, deserializer, 
options.getProtocolVersion()));
+                    }
+                }
+            }
+            else
+            {
+                for (Term value : condition.inValues)
+                    this.inValues.add(value.bind(options));
+            }
+        }
+
+        public boolean appliesTo(Composite rowPrefix, ColumnFamily current, 
final long now) throws InvalidRequestException
+        {
+            CollectionType type = (CollectionType)column.type;
+            CellName name = current.metadata().comparator.create(rowPrefix, 
column);
+
+            // copy iterator contents so that we can properly reuse them for 
each comparison with an IN value
+            List<Cell> cells = newArrayList(collectionColumns(name, current, 
now));
+            for (Term.Terminal value : inValues)
+            {
+                if (CollectionBound.valueAppliesTo(type, cells.iterator(), 
value, Relation.Type.EQ))
+                    return true;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            List<Collection<ByteBuffer>> inValueBuffers = new 
ArrayList<>(inValues.size());
+            switch (((CollectionType)column.type).kind)
+            {
+                case LIST:
+                    for (Term.Terminal term : inValues)
+                        inValueBuffers.add(term == null ? null : 
((Lists.Value)term).elements);
+                    break;
+                case SET:
+                    for (Term.Terminal term : inValues)
+                        inValueBuffers.add(term == null ? null : 
((Sets.Value)term).elements);
+                    break;
+                case MAP:
+                    for (Term.Terminal term : inValues)
+                    {
+                        if (term != null)
+                        {
+                            
inValueBuffers.add(((Maps.Value)term).map.keySet());
+                            
inValueBuffers.add(((Maps.Value)term).map.values());
+                        }
+                        else
+                            inValueBuffers.add(null);
+                    }
+                    break;
+            }
+            return Objects.hashCode(column, inValueBuffers, operator);
+        }
+    }
+
     public static class Raw
     {
         private final Term.Raw value;
+        private final List<Term.Raw> inValues;
+        private final AbstractMarker.INRaw inMarker;
 
         // Can be null, only used with the syntax "IF m[e] = ..." (in which 
case it's 'e')
         private final Term.Raw collectionElement;
 
-        private Raw(Term.Raw value, Term.Raw collectionElement)
+        private final Relation.Type operator;
+
+        private Raw(Term.Raw value, List<Term.Raw> inValues, 
AbstractMarker.INRaw inMarker, Term.Raw collectionElement, Relation.Type op)
         {
             this.value = value;
+            this.inValues = inValues;
+            this.inMarker = inMarker;
             this.collectionElement = collectionElement;
+            this.operator = op;
         }
 
-        public static Raw simpleEqual(Term.Raw value)
+        /** A condition on a column. For example: "IF col = 'foo'" */
+        public static Raw simpleCondition(Term.Raw value, Relation.Type op)
         {
-            return new Raw(value, null);
+            return new Raw(value, null, null, null, op);
         }
 
-        public static Raw collectionEqual(Term.Raw value, Term.Raw 
collectionElement)
+        /** An IN condition on a column. For example: "IF col IN ('foo', 
'bar', ...)" */
+        public static Raw simpleInCondition(List<Term.Raw> inValues)
         {
-            return new Raw(value, collectionElement);
+            return new Raw(null, inValues, null, null, Relation.Type.IN);
+        }
+
+        /** An IN condition on a column with a single marker. For example: "IF 
col IN ?" */
+        public static Raw simpleInCondition(AbstractMarker.INRaw inMarker)
+        {
+            return new Raw(null, null, inMarker, null, Relation.Type.IN);
+        }
+
+        /** A condition on a collection element. For example: "IF col['key'] = 
'foo'" */
+        public static Raw collectionCondition(Term.Raw value, Term.Raw 
collectionElement, Relation.Type op)
+        {
+            return new Raw(value, null, null, collectionElement, op);
+        }
+
+        /** An IN condition on a collection element. For example: "IF 
col['key'] IN ('foo', 'bar', ...)" */
+        public static Raw collectionInCondition(Term.Raw collectionElement, 
List<Term.Raw> inValues)
+        {
+            return new Raw(null, inValues, null, collectionElement, 
Relation.Type.IN);
+        }
+
+        /** An IN condition on a collection element with a single marker. For 
example: "IF col['key'] IN ?" */
+        public static Raw collectionInCondition(Term.Raw collectionElement, 
AbstractMarker.INRaw inMarker)
+        {
+            return new Raw(null, null, inMarker, collectionElement, 
Relation.Type.IN);
         }
 
         public ColumnCondition prepare(String keyspace, ColumnDefinition 
receiver) throws InvalidRequestException
         {
             if (receiver.type instanceof CounterColumnType)
-                throw new InvalidRequestException("Condtions on counters are 
not supported");
+                throw new InvalidRequestException("Conditions on counters are 
not supported");
 
             if (collectionElement == null)
-                return ColumnCondition.equal(receiver, value.prepare(keyspace, 
receiver));
+            {
+                if (operator.equals(Relation.Type.IN))
+                {
+                    if (inValues == null)
+                        return ColumnCondition.inCondition(receiver, 
inMarker.prepare(keyspace, receiver));
+                    List<Term> terms = new ArrayList<>(inValues.size());
+                    for (Term.Raw value : inValues)
+                        terms.add(value.prepare(keyspace, receiver));
+                    return ColumnCondition.inCondition(receiver, terms);
+                }
+                else
+                {
+                    return ColumnCondition.condition(receiver, 
value.prepare(keyspace, receiver), operator);
+                }
+            }
 
             if (!(receiver.type.isCollection()))
                 throw new InvalidRequestException(String.format("Invalid 
element access syntax for non-collection column %s", receiver.name));
 
-            switch (((CollectionType)receiver.type).kind)
+            ColumnSpecification elementSpec, valueSpec;
+            switch ((((CollectionType)receiver.type).kind))
             {
                 case LIST:
-                    return ColumnCondition.equal(receiver, 
collectionElement.prepare(keyspace, Lists.indexSpecOf(receiver)), 
value.prepare(keyspace, Lists.valueSpecOf(receiver)));
+                    elementSpec = Lists.indexSpecOf(receiver);
+                    valueSpec = Lists.valueSpecOf(receiver);
+                    break;
+                case MAP:
+                    elementSpec = Maps.keySpecOf(receiver);
+                    valueSpec = Maps.valueSpecOf(receiver);
+                    break;
                 case SET:
                     throw new InvalidRequestException(String.format("Invalid 
element access syntax for set column %s", receiver.name));
-                case MAP:
-                    return ColumnCondition.equal(receiver, 
collectionElement.prepare(keyspace, Maps.keySpecOf(receiver)), 
value.prepare(keyspace, Maps.valueSpecOf(receiver)));
+                default:
+                    throw new AssertionError();
+            }
+            if (operator.equals(Relation.Type.IN))
+            {
+                if (inValues == null)
+                    return ColumnCondition.inCondition(receiver, 
collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, 
valueSpec));
+                List<Term> terms = new ArrayList<>(inValues.size());
+                for (Term.Raw value : inValues)
+                    terms.add(value.prepare(keyspace, valueSpec));
+                return ColumnCondition.inCondition(receiver, 
collectionElement.prepare(keyspace, elementSpec), terms);
+            }
+            else
+            {
+                return ColumnCondition.condition(receiver, 
collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, 
valueSpec), operator);
             }
-            throw new AssertionError();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g 
b/src/java/org/apache/cassandra/cql3/Cql.g
index ec24284..93df1b4 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -971,8 +971,20 @@ columnOperation[List<Pair<ColumnIdentifier, 
Operation.RawUpdate>> operations]
 
 columnCondition[List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions]
     // Note: we'll reject duplicates later
-    : key=cident '=' t=term { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleEqual(t))); }
-    | key=cident '[' element=term ']' '=' t=term { 
conditions.add(Pair.create(key, ColumnCondition.Raw.collectionEqual(t, 
element))); } 
+    : key=cident
+        ( op=relationType t=term { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleCondition(t, op))); }
+        | K_IN
+            ( values=singleColumnInValues { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleInCondition(values))); }
+            | marker=inMarker { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleInCondition(marker))); }
+            )
+        | '[' element=term ']'
+            ( op=relationType t=term { conditions.add(Pair.create(key, 
ColumnCondition.Raw.collectionCondition(t, element, op))); }
+            | K_IN
+                ( values=singleColumnInValues { 
conditions.add(Pair.create(key, 
ColumnCondition.Raw.collectionInCondition(element, values))); }
+                | marker=inMarker { conditions.add(Pair.create(key, 
ColumnCondition.Raw.collectionInCondition(element, marker))); }
+                )
+            )
+        )
     ;
 
 properties[PropertyDefinitions props]
@@ -995,6 +1007,7 @@ relationType returns [Relation.Type op]
     | '<=' { $op = Relation.Type.LTE; }
     | '>'  { $op = Relation.Type.GT; }
     | '>=' { $op = Relation.Type.GTE; }
+    | '!=' { $op = Relation.Type.NEQ; }
     ;
 
 relation[List<Relation> clauses]

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Lists.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Lists.java 
b/src/java/org/apache/cassandra/cql3/Lists.java
index 224af50..ea10b48 100644
--- a/src/java/org/apache/cassandra/cql3/Lists.java
+++ b/src/java/org/apache/cassandra/cql3/Lists.java
@@ -37,12 +37,16 @@ import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.UUIDGen;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Static helper methods and classes for lists.
  */
 public abstract class Lists
 {
+    private static final Logger logger = LoggerFactory.getLogger(Lists.class);
+
     private Lists() {}
 
     public static ColumnSpecification indexSpecOf(ColumnSpecification column)
@@ -138,7 +142,8 @@ public abstract class Lists
                 List<?> l = 
(List<?>)type.getSerializer().deserializeForNativeProtocol(value, version);
                 List<ByteBuffer> elements = new 
ArrayList<ByteBuffer>(l.size());
                 for (Object element : l)
-                    elements.add(type.elements.decompose(element));
+                    // elements can be null in lists that represent a set of 
IN values
+                    elements.add(element == null ? null : 
type.elements.decompose(element));
                 return new Value(elements);
             }
             catch (MarshalException e)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/Relation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java 
b/src/java/org/apache/cassandra/cql3/Relation.java
index 5318907..42373c3 100644
--- a/src/java/org/apache/cassandra/cql3/Relation.java
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -24,7 +24,7 @@ public abstract class Relation {
 
     public static enum Type
     {
-        EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY;
+        EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY, NEQ;
 
         public boolean allowsIndexQuery()
         {
@@ -54,6 +54,8 @@ public abstract class Relation {
                     return ">";
                 case GTE:
                     return ">=";
+                case NEQ:
+                    return "!=";
                 default:
                     return this.name();
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java 
b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
index a85c1e5..eb29012 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
@@ -20,6 +20,8 @@ package org.apache.cassandra.cql3.statements;
 import java.nio.ByteBuffer;
 import java.util.*;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import org.apache.cassandra.cql3.*;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.db.*;
@@ -111,6 +113,7 @@ public class CQL3CasRequest implements CASRequest
             slices[i++] = prefix.slice();
 
         int toGroup = cfm.comparator.isDense() ? -1 : 
cfm.clusteringColumns().size();
+        slices = ColumnSlice.deoverlapSlices(slices, cfm.comparator);
         assert ColumnSlice.validateSlices(slices, cfm.comparator, false);
         return new SliceQueryFilter(slices, false, slices.length, toGroup);
     }
@@ -233,7 +236,7 @@ public class CQL3CasRequest implements CASRequest
 
     private static class ColumnsConditions extends RowCondition
     {
-        private final Map<Pair<ColumnIdentifier, ByteBuffer>, 
ColumnCondition.Bound> conditions = new HashMap<>();
+        private final Multimap<Pair<ColumnIdentifier, ByteBuffer>, 
ColumnCondition.Bound> conditions = HashMultimap.create();
 
         private ColumnsConditions(Composite rowPrefix, long now)
         {
@@ -244,13 +247,8 @@ public class CQL3CasRequest implements CASRequest
         {
             for (ColumnCondition condition : conds)
             {
-                // We will need the variables in appliesTo but with protocol 
batches, each condition in this object can have a
-                // different list of variables.
                 ColumnCondition.Bound current = condition.bind(options);
-                ColumnCondition.Bound previous = 
conditions.put(Pair.create(condition.column.name, 
current.getCollectionElementValue()), current);
-                // If 2 conditions are actually equal, let it slide
-                if (previous != null && !previous.equals(current))
-                    throw new InvalidRequestException("Duplicate and 
incompatible conditions for column " + condition.column.name);
+                conditions.put(Pair.create(condition.column.name, 
current.getCollectionElementValue()), current);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/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 fdab978..a2e6624 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -1587,7 +1587,10 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
                         restriction.setBound(def.name, relation.operator(), t);
                         stmt.columnRestrictions[def.position()] = restriction;
                     }
+                    break;
                 }
+                case NEQ:
+                    throw new 
InvalidRequestException(String.format("Unsupported \"!=\" relation: %s", 
relation));
             }
         }
 
@@ -1687,6 +1690,8 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
                         existingRestriction = new 
SingleColumnRestriction.InWithValues(inValues);
                     }
                     break;
+                case NEQ:
+                    throw new 
InvalidRequestException(String.format("Unsupported \"!=\" relation on column 
\"%s\"", def.name));
                 case GT:
                 case GTE:
                 case LT:
@@ -1727,6 +1732,7 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
                     Term t = newRel.getValue().prepare(keyspace(), receiver);
                     t.collectMarkerSpecification(boundNames);
                     
((SingleColumnRestriction.Contains)existingRestriction).add(t, isKey);
+                    break;
                 }
             }
             return existingRestriction;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/src/java/org/apache/cassandra/serializers/ListSerializer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java 
b/src/java/org/apache/cassandra/serializers/ListSerializer.java
index b64c012..9c642bc 100644
--- a/src/java/org/apache/cassandra/serializers/ListSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java
@@ -84,9 +84,17 @@ public class ListSerializer<T> extends 
CollectionSerializer<List<T>>
             List<T> l = new ArrayList<T>(n);
             for (int i = 0; i < n; i++)
             {
+                // We can have nulls in lists that are used for IN values
                 ByteBuffer databb = readValue(input, version);
-                elements.validate(databb);
-                l.add(elements.deserialize(databb));
+                if (databb != null)
+                {
+                    elements.validate(databb);
+                    l.add(elements.deserialize(databb));
+                }
+                else
+                {
+                    l.add(null);
+                }
             }
             return l;
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java 
b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
new file mode 100644
index 0000000..2071a33
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
@@ -0,0 +1,577 @@
+/*
+ * 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 org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.db.BufferCell;
+import org.apache.cassandra.db.Cell;
+import org.apache.cassandra.db.composites.*;
+import org.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.serializers.Int32Serializer;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+public class ColumnConditionTest
+{
+    public static final ByteBuffer ZERO = Int32Type.instance.fromString("0");
+    public static final ByteBuffer ONE = Int32Type.instance.fromString("1");
+    public static final ByteBuffer TWO = Int32Type.instance.fromString("2");
+
+    public static final ByteBuffer A = AsciiType.instance.fromString("a");
+    public static final ByteBuffer B = AsciiType.instance.fromString("b");
+
+    private static boolean isSatisfiedBy(ColumnCondition.Bound bound, 
ByteBuffer conditionValue, ByteBuffer columnValue) throws 
InvalidRequestException
+    {
+        Cell cell = null;
+        if (columnValue != null)
+        {
+            CompoundSparseCellNameType nameType = new 
CompoundSparseCellNameType(Collections.EMPTY_LIST);
+            ColumnDefinition definition = new ColumnDefinition("ks", "cf", new 
ColumnIdentifier("c", true), Int32Type.instance, null, null, null, null, null);
+            cell = new BufferCell(nameType.create(Composites.EMPTY, 
definition), columnValue);
+        }
+        return bound.isSatisfiedByValue(conditionValue, cell, 
Int32Type.instance, bound.operator, 1234);
+    }
+
+    private static void assertThrowsIRE(ColumnCondition.Bound bound, 
ByteBuffer conditionValue, ByteBuffer columnValue)
+    {
+        try
+        {
+            isSatisfiedBy(bound, conditionValue, columnValue);
+            fail("Expected InvalidRequestException was not thrown");
+        } catch (InvalidRequestException e) { }
+    }
+
+    @Test
+    public void testSimpleBoundIsSatisfiedByValue() throws 
InvalidRequestException
+    {
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new 
ColumnIdentifier("c", true), Int32Type.instance, null, null, null, null, null);
+
+        // EQ
+        ColumnCondition condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.EQ);
+        ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
+        assertTrue(isSatisfiedBy(bound, ONE, ONE));
+        assertFalse(isSatisfiedBy(bound, ZERO, ONE));
+        assertFalse(isSatisfiedBy(bound, TWO, ONE));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertFalse(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertTrue(isSatisfiedBy(bound, null, null));
+        assertFalse(isSatisfiedBy(bound, ONE, null));
+        assertFalse(isSatisfiedBy(bound, null, ONE));
+
+        // NEQ
+        condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.NEQ);
+        bound = condition.bind(QueryOptions.DEFAULT);
+        assertFalse(isSatisfiedBy(bound, ONE, ONE));
+        assertTrue(isSatisfiedBy(bound, ZERO, ONE));
+        assertTrue(isSatisfiedBy(bound, TWO, ONE));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertTrue(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertFalse(isSatisfiedBy(bound, null, null));
+        assertTrue(isSatisfiedBy(bound, ONE, null));
+        assertTrue(isSatisfiedBy(bound, null, ONE));
+
+        // LT
+        condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.LT);
+        bound = condition.bind(QueryOptions.DEFAULT);
+        assertFalse(isSatisfiedBy(bound, ONE, ONE));
+        assertFalse(isSatisfiedBy(bound, ZERO, ONE));
+        assertTrue(isSatisfiedBy(bound, TWO, ONE));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertTrue(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertThrowsIRE(bound, null, ONE);
+        assertFalse(isSatisfiedBy(bound, ONE, null));
+
+        // LTE
+        condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.LTE);
+        bound = condition.bind(QueryOptions.DEFAULT);
+        assertTrue(isSatisfiedBy(bound, ONE, ONE));
+        assertFalse(isSatisfiedBy(bound, ZERO, ONE));
+        assertTrue(isSatisfiedBy(bound, TWO, ONE));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertTrue(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertThrowsIRE(bound, null, ONE);
+        assertFalse(isSatisfiedBy(bound, ONE, null));
+
+        // GT
+        condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.GT);
+        bound = condition.bind(QueryOptions.DEFAULT);
+        assertFalse(isSatisfiedBy(bound, ONE, ONE));
+        assertTrue(isSatisfiedBy(bound, ZERO, ONE));
+        assertFalse(isSatisfiedBy(bound, TWO, ONE));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertFalse(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertThrowsIRE(bound, null, ONE);
+        assertFalse(isSatisfiedBy(bound, ONE, null));
+
+        // GT
+        condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Relation.Type.GTE);
+        bound = condition.bind(QueryOptions.DEFAULT);
+        assertTrue(isSatisfiedBy(bound, ONE, ONE));
+        assertTrue(isSatisfiedBy(bound, ZERO, ONE));
+        assertFalse(isSatisfiedBy(bound, TWO, ONE));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE));
+        assertFalse(isSatisfiedBy(bound, ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertThrowsIRE(bound, null, ONE);
+        assertFalse(isSatisfiedBy(bound, ONE, null));
+    }
+
+    private static List<ByteBuffer> list(ByteBuffer... values)
+    {
+        return Arrays.asList(values);
+    }
+
+    private static boolean listAppliesTo(ColumnCondition.CollectionBound 
bound, List<ByteBuffer> conditionValues, List<ByteBuffer> columnValues)
+    {
+        CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY 
KEY, b int, c list<int>)", "ks");
+        Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
+        typeMap.put(ByteBufferUtil.bytes("c"), 
ListType.getInstance(Int32Type.instance));
+        CompoundSparseCellNameType.WithCollection nameType = new 
CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, 
ColumnToCollectionType.getInstance(typeMap));
+        ColumnDefinition definition = new ColumnDefinition(cfm, 
ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance), 0, 
ColumnDefinition.Kind.REGULAR);
+
+        List<Cell> cells = new ArrayList<>(columnValues.size());
+        if (columnValues != null)
+        {
+            for (int i = 0; i < columnValues.size(); i++)
+            {
+                ByteBuffer key = Int32Serializer.instance.serialize(i);
+                ByteBuffer value = columnValues.get(i);
+                cells.add(new BufferCell(nameType.create(Composites.EMPTY, 
definition, key), value));
+            };
+        }
+
+        return bound.listAppliesTo(ListType.getInstance(Int32Type.instance), 
cells == null ? null : cells.iterator(), conditionValues, bound.operator);
+    }
+
+    @Test
+    // sets use the same check as lists
+    public void testListCollectionBoundAppliesTo() throws 
InvalidRequestException
+    {
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new 
ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance), null, 
null, null, null, null);
+
+        // EQ
+        ColumnCondition condition = ColumnCondition.condition(definition, 
null, new Lists.Value(Arrays.asList(ONE)), Relation.Type.EQ);
+        ColumnCondition.CollectionBound bound = 
(ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list()));
+        assertFalse(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list()));
+
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // NEQ
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.NEQ);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list()));
+        assertTrue(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list()));
+
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LT
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.LT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list()));
+        assertFalse(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list()));
+
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LTE
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.LTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list()));
+        assertFalse(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), list()));
+
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GT
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.GT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(), list()));
+        assertTrue(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list()));
+
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GTE
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.GTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list()));
+        assertTrue(listAppliesTo(bound, list(ZERO), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list(ZERO)));
+        assertTrue(listAppliesTo(bound, list(ONE), list(ONE, ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE, ONE), list(ONE)));
+        assertTrue(listAppliesTo(bound, list(), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), list()));
+
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ONE)));
+        assertFalse(listAppliesTo(bound, list(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(listAppliesTo(bound, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+    }
+
+    private static Set<ByteBuffer> set(ByteBuffer... values)
+    {
+        Set results = new HashSet<ByteBuffer>(values.length);
+        results.addAll(Arrays.asList(values));
+        return results;
+    }
+
+    private static boolean setAppliesTo(ColumnCondition.CollectionBound bound, 
Set<ByteBuffer> conditionValues, List<ByteBuffer> columnValues)
+    {
+        CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY 
KEY, b int, c set<int>)", "ks");
+        Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
+        typeMap.put(ByteBufferUtil.bytes("c"), 
SetType.getInstance(Int32Type.instance));
+        CompoundSparseCellNameType.WithCollection nameType = new 
CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, 
ColumnToCollectionType.getInstance(typeMap));
+        ColumnDefinition definition = new ColumnDefinition(cfm, 
ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance), 0, 
ColumnDefinition.Kind.REGULAR);
+
+        List<Cell> cells = new ArrayList<>(columnValues.size());
+        if (columnValues != null)
+        {
+            for (int i = 0; i < columnValues.size(); i++)
+            {
+                ByteBuffer key = columnValues.get(i);
+                cells.add(new BufferCell(nameType.create(Composites.EMPTY, 
definition, key), ByteBufferUtil.EMPTY_BYTE_BUFFER));
+            };
+        }
+
+        return bound.setAppliesTo(SetType.getInstance(Int32Type.instance), 
cells == null ? null : cells.iterator(), conditionValues, bound.operator);
+    }
+
+    @Test
+    public void testSetCollectionBoundAppliesTo() throws 
InvalidRequestException
+    {
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new 
ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance), null, 
null, null, null, null);
+
+        // EQ
+        ColumnCondition condition = ColumnCondition.condition(definition, 
null, new Sets.Value(set(ONE)), Relation.Type.EQ);
+        ColumnCondition.CollectionBound bound = 
(ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list()));
+        assertFalse(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list()));
+
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // NEQ
+        condition = ColumnCondition.condition(definition, null, new 
Sets.Value(set(ONE)), Relation.Type.NEQ);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list()));
+        assertTrue(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list()));
+
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LT
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.LT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list()));
+        assertFalse(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list()));
+
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LTE
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.LTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list()));
+        assertFalse(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertTrue(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), list()));
+
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertTrue(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GT
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.GT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(), list()));
+        assertTrue(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list()));
+
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GTE
+        condition = ColumnCondition.condition(definition, null, new 
Lists.Value(Arrays.asList(ONE)), Relation.Type.GTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list()));
+        assertTrue(setAppliesTo(bound, set(ZERO), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list(ZERO)));
+        assertTrue(setAppliesTo(bound, set(ONE), list(ONE, TWO)));
+        assertFalse(setAppliesTo(bound, set(ONE, TWO), list(ONE)));
+        assertTrue(setAppliesTo(bound, set(), list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), list()));
+
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ONE)));
+        assertFalse(setAppliesTo(bound, set(ONE), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+    }
+
+    // values should be a list of key, value, key, value, ...
+    private static Map<ByteBuffer, ByteBuffer> map(ByteBuffer... values)
+    {
+        Map<ByteBuffer, ByteBuffer> map = new TreeMap<>();
+        for (int i = 0; i < values.length; i += 2)
+            map.put(values[i], values[i + 1]);
+
+        return map;
+    }
+
+    private static boolean mapAppliesTo(ColumnCondition.CollectionBound bound, 
Map<ByteBuffer, ByteBuffer> conditionValues, Map<ByteBuffer, ByteBuffer> 
columnValues)
+    {
+        CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY 
KEY, b map<int, int>)", "ks");
+        Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
+        typeMap.put(ByteBufferUtil.bytes("b"), 
MapType.getInstance(Int32Type.instance, Int32Type.instance));
+        CompoundSparseCellNameType.WithCollection nameType = new 
CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, 
ColumnToCollectionType.getInstance(typeMap));
+        ColumnDefinition definition = new ColumnDefinition(cfm, 
ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, 
Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
+
+        List<Cell> cells = new ArrayList<>(columnValues.size());
+        if (columnValues != null)
+        {
+            for (Map.Entry<ByteBuffer, ByteBuffer> entry : 
columnValues.entrySet())
+                cells.add(new BufferCell(nameType.create(Composites.EMPTY, 
definition, entry.getKey()), entry.getValue()));
+        }
+
+        return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, 
Int32Type.instance), cells.iterator(), conditionValues, bound.operator);
+    }
+
+    @Test
+    public void testMapCollectionBoundIsSatisfiedByValue() throws 
InvalidRequestException
+    {
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new 
ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, 
Int32Type.instance), null, null, null, null, null);
+
+        Map<ByteBuffer, ByteBuffer> placeholderMap = new TreeMap<>();
+        placeholderMap.put(ONE, ONE);
+        Maps.Value placeholder = new Maps.Value(placeholderMap);
+
+        // EQ
+        ColumnCondition condition = ColumnCondition.condition(definition, 
null, placeholder, Relation.Type.EQ);
+        ColumnCondition.CollectionBound bound = 
(ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
+
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map()));
+        assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // NEQ
+        condition = ColumnCondition.condition(definition, null, placeholder, 
Relation.Type.NEQ);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map()));
+        assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LT
+        condition = ColumnCondition.condition(definition, null, placeholder, 
Relation.Type.LT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map()));
+        assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // LTE
+        condition = ColumnCondition.condition(definition, null, placeholder, 
Relation.Type.LTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map()));
+        assertFalse(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GT
+        condition = ColumnCondition.condition(definition, null, placeholder, 
Relation.Type.GT);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(), map()));
+        assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertFalse(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // GTE
+        condition = ColumnCondition.condition(definition, null, placeholder, 
Relation.Type.GTE);
+        bound = (ColumnCondition.CollectionBound) 
condition.bind(QueryOptions.DEFAULT);
+
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map()));
+        assertTrue(mapAppliesTo(bound, map(ZERO, ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ZERO, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ZERO), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ZERO)));
+        assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE, TWO, 
ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE, TWO, ONE), map(ONE, 
ONE)));
+        assertTrue(mapAppliesTo(bound, map(), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map()));
+
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), 
map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ONE)));
+        assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+        assertTrue(mapAppliesTo(bound, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
+        assertTrue(mapAppliesTo(bound, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cassandra/blob/3d5f3a65/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 adc1551..daf0835 100644
--- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
@@ -131,6 +131,13 @@ public class MultiColumnRelationTest extends CQLTester
     }
 
     @Test
+    public void testNonEqualsRelation() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)");
+        assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b) != (0)");
+    }
+
+    @Test
     public void testMultipleClustering() throws Throwable
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY 
(a, b, c, d))");

Reply via email to