This is an automated email from the ASF dual-hosted git repository.

edimitrova pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new e0074a31ef Add support for NOT operators in WHERE clauses 
(CASSANDRA-18584) In addition - Fixed Three Valued Logic
e0074a31ef is described below

commit e0074a31ef26adaebff6ac0657e4471fc805f93f
Author: Benjamin Lerer <[email protected]>
AuthorDate: Thu Mar 21 11:11:10 2024 +0100

    Add support for NOT operators in WHERE clauses (CASSANDRA-18584)
    In addition - Fixed Three Valued Logic
    
    patch by Piotr Kolaczkowski; reviewed by Benjamin Lerer, Ekaterina 
Dimitrova, Andres de la Pena for CASSANDRA-18584
    
    Co-authored-by: Benjamin Lerer <[email protected]>
    Co-authored by: Ekaterina Dimitrova<[email protected]>
---
 CHANGES.txt                                        |   1 +
 doc/cql3/CQL.textile                               |  14 +-
 .../cassandra/examples/BNF/select_statement.bnf    |   2 +-
 .../cassandra/pages/developing/cql/changes.adoc    |   1 +
 .../pages/developing/cql/cql_singlefile.adoc       |   2 +-
 pylib/cqlshlib/cql3handling.py                     |   4 +-
 pylib/cqlshlib/test/test_cqlsh_completion.py       |   6 +-
 src/antlr/Parser.g                                 |  14 +-
 src/java/org/apache/cassandra/cql3/Operator.java   | 173 +++++-
 src/java/org/apache/cassandra/cql3/Relation.java   |   6 +-
 .../cassandra/cql3/conditions/ColumnCondition.java |   2 +-
 .../cql3/restrictions/ClusteringElements.java      |   5 +
 .../cql3/restrictions/MergedRestriction.java       |   6 +-
 .../restrictions/PartitionKeyRestrictions.java     |   2 +-
 .../cql3/restrictions/SimpleRestriction.java       |  14 +-
 .../org/apache/cassandra/db/filter/RowFilter.java  |   9 +-
 .../operations/SelectMultiColumnRelationTest.java  | 584 ++++++++++++++++++++-
 .../operations/SelectSingleColumnRelationTest.java | 499 +++++++++++++++++-
 .../cql3/validation/operations/SelectTest.java     | 312 +++++++++--
 .../index/sai/cql/TokenRangeReadTest.java          |  31 ++
 .../index/sai/cql/UnindexedExpressionsTest.java    |   4 +-
 21 files changed, 1593 insertions(+), 98 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index d3e54a69f7..26cb5d2c29 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.1
+ * Add support for NOT operators in WHERE clauses. Fixed Three Valued Logic 
(CASSANDRA-18584)
  * Allow getendpoints for system tables and make sure getNaturalReplicas work 
for MetaStrategy (CASSANDRA-19846)
  * On upgrade, handle pre-existing tables with unexpected table ids 
(CASSANDRA-19845)
  * Reconfigure CMS before assassinate (CASSANDRA-19768)
diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile
index d60b4d476f..4c2b09acc3 100644
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@ -940,6 +940,10 @@ bc(syntax)..
              | <identifier> IN <variable>
              | '(' <identifier> (',' <identifier>)* ')' IN '(' ( <term-tuple> 
( ',' <term-tuple>)* )? ')'
              | '(' <identifier> (',' <identifier>)* ')' IN <variable>
+             | <identifier> NOT IN '(' ( <term> ( ',' <term>)* )? ')'
+             | <identifier> NOT IN <variable>
+             | '(' <identifier> (',' <identifier>)* ')' NOT IN '(' ( 
<term-tuple> ( ',' <term-tuple>)* )? ')'
+             | '(' <identifier> (',' <identifier>)* ')' NOT IN <variable>
 
 <option> ::= TIMESTAMP <integer>
            | TTL <integer>
@@ -993,12 +997,16 @@ bc(syntax)..
 
 <where-clause> ::= <relation> ( AND <relation> )*
 
-<relation> ::= <identifier> <op> <term>
+<relation> ::= <identifier> (<op> | NOT CONTAINS ( KEY )?) <term>
              | '(' <identifier> (',' <identifier>)* ')' <op> <term-tuple>
              | <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
              | <identifier> IN <variable>
              | '(' <identifier> (',' <identifier>)* ')' IN '(' ( <term-tuple> 
( ',' <term-tuple>)* )? ')'
              | '(' <identifier> (',' <identifier>)* ')' IN <variable>
+             | <identifier> NOT IN '(' ( <term> ( ',' <term>)* )? ')'
+             | <identifier> NOT IN <variable>
+             | '(' <identifier> (',' <identifier>)* ')' NOT IN '(' ( 
<term-tuple> ( ',' <term-tuple>)* )? ')'
+             | '(' <identifier> (',' <identifier>)* ')' NOT IN <variable>
 
 <op> ::= '=' | '<' | '>' | '<=' | '>=' | CONTAINS ( KEY )?
 <in-values> ::= (<variable> | '(' ( <term> ( ',' <term> )* )? ')')
@@ -1118,9 +1126,11 @@ bc(syntax)..
              | '(' <identifier> (',' <identifier>)* ')' <op> <term-tuple>
              | <identifier> IN '(' ( <term> ( ',' <term>)* )? ')'
              | '(' <identifier> (',' <identifier>)* ')' IN '(' ( <term-tuple> 
( ',' <term-tuple>)* )? ')'
+             | <identifier> NOT IN '(' ( <term> ( ',' <term>)* )? ')'
+             | '(' <identifier> (',' <identifier>)* ')' NOT IN '(' ( 
<term-tuple> ( ',' <term-tuple>)* )? ')'
              | TOKEN '(' <identifier> ( ',' <identifer>)* ')' <op> <term>
 
-<op> ::= '=' | '<' | '>' | '<=' | '>=' | CONTAINS | CONTAINS KEY
+<op> ::= '=' | '<' | '>' | '<=' | '>=' | CONTAINS | CONTAINS KEY | NOT 
CONTAINS | NOT CONTAINS KEY
 <group-by> ::= (<identifier>,)* (<identifier> | <function>)
 <order-by> ::= <ordering> ( ',' <odering> )*
 <ordering> ::= <identifer> ( ASC | DESC )?
diff --git a/doc/modules/cassandra/examples/BNF/select_statement.bnf 
b/doc/modules/cassandra/examples/BNF/select_statement.bnf
index f53da41da5..260d55dcd3 100644
--- a/doc/modules/cassandra/examples/BNF/select_statement.bnf
+++ b/doc/modules/cassandra/examples/BNF/select_statement.bnf
@@ -16,6 +16,6 @@ where_clause::= `relation` ( AND `relation` )*
 relation::= column_name operator term
        '(' column_name ( ',' column_name )* ')' operator tuple_literal 
        TOKEN '(' column_name# ( ',' column_name )* ')' operator term 
-operator::= '=' | '<' | '>' | '<=' | '>=' | '!=' | IN | CONTAINS | CONTAINS 
KEY 
+operator::= '=' | '<' | '>' | '<=' | '>=' | '!=' | IN | NOT IN | CONTAINS | 
NOT CONTAINS | CONTAINS KEY | NOT CONTAINS KEY
 group_by_clause::= column_name ( ',' column_name )* 
 ordering_clause::= column_name [ ASC | DESC ] ( ',' column_name [ ASC | DESC ] 
)*
diff --git a/doc/modules/cassandra/pages/developing/cql/changes.adoc 
b/doc/modules/cassandra/pages/developing/cql/changes.adoc
index e307919e3a..2dbfae7ecf 100644
--- a/doc/modules/cassandra/pages/developing/cql/changes.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/changes.adoc
@@ -6,6 +6,7 @@ The following describes the changes in each version of CQL.
 
 * Add support for the BETWEEN operator in WHERE clauses (`19604`)
 * Add support for GENERATED PASSWORD clause (`17457`)
+* Add support for NOT operator in WHERE clauses ('18584')
 
 == 3.4.7
 
diff --git a/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc 
b/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc
index 18dd52e13d..bff1a65f47 100644
--- a/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc
@@ -1686,7 +1686,7 @@ FROM  +
 | `(' (`,' )* `)' IN `(' ( ( `,' )* )? `)' +
 | TOKEN `(' ( `,' )* `)'
 
-::= `=' | `<' | `>' | `<=' | `>=' | CONTAINS | CONTAINS KEY +
+::= `=' | `<' | `>' | `<=' | `>=' | CONTAINS | CONTAINS KEY | NOT CONTAINS | 
NOT CONTAINS KEY +
 ::= (`,' )* +
 ::= ( `,' )* +
 ::= ( ASC | DESC )? +
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index e09094f317..ce2b3726c4 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -738,11 +738,11 @@ syntax_rules += r'''
                     ;
 <whereClause> ::= <relation> ( "AND" <relation> )*
                 ;
-<relation> ::= [rel_lhs]=<cident> ( "[" <term> "]" )? ( "=" | "<" | ">" | "<=" 
| ">=" | "CONTAINS" ( "KEY" )? ) <term>
+<relation> ::= [rel_lhs]=<cident> ( "[" <term> "]" )? ( "=" | "<" | ">" | "<=" 
| ">=" | "!=" | ( "NOT" )? "CONTAINS" ( "KEY" )? ) <term>
              | token="TOKEN" "(" [rel_tokname]=<cident>
                                  ( "," [rel_tokname]=<cident> )*
                              ")" ("=" | "<" | ">" | "<=" | ">=") 
<tokenDefinition>
-             | [rel_lhs]=<cident> "IN" "(" <term> ( "," <term> )* ")"
+             | [rel_lhs]=<cident> (( "NOT" )? "IN" ) "(" <term> ( "," <term> 
)* ")"
              | [rel_lhs]=<cident> "BETWEEN" <term> "AND" <term>
              ;
 <selectClause> ::= "DISTINCT"? <selector> ("AS" <cident>)? ("," <selector> 
("AS" <cident>)?)*
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py 
b/pylib/cqlshlib/test/test_cqlsh_completion.py
index a787f3fcb2..112474e7c7 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -381,7 +381,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
lonel",
                             immediate='ykey ')
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
lonelykey ",
-                            choices=['=', '<=', '>=', '>', '<', 'BETWEEN', 
'CONTAINS', 'IN', '['])
+                            choices=['=', '<=', '>=', '>', '<', '!=', 
'BETWEEN', 'CONTAINS', 'IN', 'NOT', '['])
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
lonelykey = 0.0 ",
                             choices=['AND', 'IF', ';'])
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
lonelykey = 0.0 AND ",
@@ -397,7 +397,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
                             choices=['EXISTS', '<quotedName>', '<identifier>'])
 
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ",
-                            choices=['>=', '!=', '<=', 'IN', '[', ';', '=', 
'<', '>', '.', 'CONTAINS'])
+                            choices=['>=', '!=', '<=', 'IN','[', ';', '=', 
'<', '>', '.', 'CONTAINS'])
 
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
TOKEN(lonelykey) <= TOKEN(13) IF lonelykey ",
                             choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 
'CONTAINS'])
@@ -464,7 +464,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
                             choices=['a', 'b', 'TOKEN('])
 
         self.trycompletions('DELETE FROM twenty_rows_composite_table USING 
TIMESTAMP 0 WHERE a ',
-                            choices=['<=', '>=', 'BETWEEN', 'CONTAINS', 'IN', 
'[', '=', '<', '>'])
+                            choices=['<=', '>=', 'BETWEEN', 'CONTAINS', 'IN', 
'NOT' , '[', '=', '<', '>', '!='])
 
         self.trycompletions('DELETE FROM twenty_rows_composite_table USING 
TIMESTAMP 0 WHERE TOKEN(',
                             immediate='a ')
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index d774022e19..099feaa1db 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -1756,7 +1756,7 @@ columnCondition returns [ColumnCondition.Raw condition]
     // Note: we'll reject duplicates later
     : column=cident
         ( op=relationType t=term       { $condition = 
ColumnCondition.Raw.simpleCondition(column, op, Terms.Raw.of(t)); }
-        | op=containsOperator t=term   { $condition = 
ColumnCondition.Raw.simpleCondition(column, op, Terms.Raw.of(t)); }
+        | K_CONTAINS (K_KEY)? t=term   { $condition = 
ColumnCondition.Raw.simpleCondition(column, $K_KEY != null ? 
Operator.CONTAINS_KEY : Operator.CONTAINS, Terms.Raw.of(t)); }
         | K_IN v=singleColumnInValues  { $condition = 
ColumnCondition.Raw.simpleCondition(column, Operator.IN, v); }
         | '[' element=term ']'
             ( op=relationType t=term      { $condition = 
ColumnCondition.Raw.collectionElementCondition(column, element, op, 
Terms.Raw.of(t)); }
@@ -1804,8 +1804,8 @@ relation[WhereClause.Builder clauses]
            | K_BETWEEN betweenValues=singleColumnBetweenValues { 
$clauses.add(Relation.singleColumn(name, Operator.BETWEEN, betweenValues)); }
            | K_LIKE t=term { $clauses.add(Relation.singleColumn(name, 
Operator.LIKE, t)); }
            | K_IS K_NOT K_NULL { $clauses.add(Relation.singleColumn(name, 
Operator.IS_NOT, Constants.NULL_LITERAL)); }
-           | K_IN inValue=singleColumnInValues { 
$clauses.add(Relation.singleColumn(name, Operator.IN, inValue)); }
-           | rt=containsOperator t=term { 
$clauses.add(Relation.singleColumn(name, rt, t)); }
+           | rtInOperator=inOperator inValue=singleColumnInValues { 
$clauses.add(Relation.singleColumn(name, rtInOperator, inValue)); }
+           | rtContainsOperator=containsOperator t=term { 
$clauses.add(Relation.singleColumn(name, rtContainsOperator, t)); }
            )
     | K_TOKEN l=tupleOfIdentifiers
         ( type=relationType t=term { $clauses.add(Relation.token(l, type, t)); 
}
@@ -1813,7 +1813,7 @@ relation[WhereClause.Builder clauses]
         )
     | name=cident '[' key=term ']' type=relationType t=term { 
$clauses.add(Relation.mapElement(name, key, type, t)); }
     | ids=tupleOfIdentifiers
-        ( K_IN inValue=multiColumnInValues { 
$clauses.add(Relation.multiColumn(ids, Operator.IN, inValue)); }
+        ( rt=inOperator inValue=multiColumnInValues { 
$clauses.add(Relation.multiColumn(ids, rt, inValue)); }
         | type=relationType v=multiColumnValue 
{$clauses.add(Relation.multiColumn(ids, type, v)); }
         | K_BETWEEN t1=multiColumnValue K_AND t2=multiColumnValue { 
$clauses.add(Relation.multiColumn(ids, Operator.BETWEEN, 
Terms.Raw.of(List.of(t1, t2)))); }
         )
@@ -1822,6 +1822,12 @@ relation[WhereClause.Builder clauses]
 
 containsOperator returns [Operator o]
     : K_CONTAINS { o = Operator.CONTAINS; } (K_KEY { o = 
Operator.CONTAINS_KEY; })?
+    | K_NOT K_CONTAINS { o = Operator.NOT_CONTAINS; } (K_KEY { o = 
Operator.NOT_CONTAINS_KEY; })?
+    ;
+
+inOperator returns [Operator o]
+    : K_IN { o = Operator.IN; }
+    | K_NOT K_IN { o = Operator.NOT_IN; }
     ;
 
 inMarker returns [Terms.Raw marker]
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java 
b/src/java/org/apache/cassandra/cql3/Operator.java
index 3b86f83fc9..18c7a9f3f9 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -326,7 +326,8 @@ public enum Operator
     IN(7)
     {
         @Override
-        public Kind kind() {
+        public Kind kind()
+        {
             return Kind.MULTI_VALUE;
         }
 
@@ -408,6 +409,12 @@ public enum Operator
         {
             return true;
         }
+
+        @Override
+        public Operator negate()
+        {
+            return NOT_CONTAINS;
+        }
     },
     CONTAINS_KEY(6)
     {
@@ -443,6 +450,12 @@ public enum Operator
         {
             return true;
         }
+
+        @Override
+        public Operator negate()
+        {
+            return NOT_CONTAINS_KEY;
+        }
     },
     NEQ(8)
     {
@@ -479,10 +492,17 @@ public enum Operator
             return leftOperand == null || type.compareCQL(leftOperand, 
elements) != 0;
         }
 
+        @Override
+        public void restrict(RangeSet<ClusteringElements> rangeSet, 
List<ClusteringElements> args)
+        {
+            assert args.size() == 1;
+            rangeSet.remove(ClusteringElements.notEqualTo(args.get(0)));
+        }
+
         @Override
         public boolean requiresFilteringOrIndexingFor(ColumnMetadata.Kind 
columnKind)
         {
-            return !columnKind.isPrimaryKeyKind();
+            return columnKind != ColumnMetadata.Kind.CLUSTERING;
         }
 
         @Override
@@ -491,11 +511,23 @@ public enum Operator
             return EQ;
         }
 
+        @Override
+        public boolean isSupportedByRestrictionsOn(ColumnsExpression 
expression)
+        {
+            return true;
+        }
+
         @Override
         protected boolean isSupportedByReadPath()
         {
             return false;
         }
+
+        @Override
+        public boolean isSlice()
+        {
+            return true;
+        }
     },
     IS_NOT(9)
     {
@@ -606,10 +638,143 @@ public enum Operator
             return true;
         }
     },
+    NOT_IN(16)
+    {
+        @Override
+        public Kind kind()
+        {
+            return Kind.MULTI_VALUE;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "NOT IN";
+        }
+        @Override
+        public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer 
leftOperand, ByteBuffer rightOperand)
+        {
+            return !IN.isSatisfiedBy(type, leftOperand, rightOperand);
+        }
+
+        @Override
+        public boolean isSatisfiedBy(MultiElementType<?> type, 
ComplexColumnData leftOperand, ByteBuffer rightOperand)
+        {
+            return !IN.isSatisfiedBy(type, leftOperand, rightOperand);
+        }
+
+        @Override
+        public boolean requiresFilteringOrIndexingFor(ColumnMetadata.Kind 
columnKind)
+        {
+            return columnKind != ColumnMetadata.Kind.CLUSTERING;
+        }
+
+        @Override
+        public void restrict(RangeSet<ClusteringElements> rangeSet, 
List<ClusteringElements> args)
+        {
+            for (ClusteringElements clustering : args)
+                rangeSet.remove(ClusteringElements.notEqualTo(clustering));
+        }
+
+        @Override
+        public Operator negate()
+        {
+            return IN;
+        }
+
+        @Override
+        public boolean isSlice()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean isSupportedByRestrictionsOn(ColumnsExpression 
expression)
+        {
+            return expression.kind() == ColumnsExpression.Kind.SINGLE_COLUMN 
|| expression.kind() == ColumnsExpression.Kind.MULTI_COLUMN;
+        }
+    },
+    NOT_CONTAINS(17)
+    {
+        @Override
+        public String toString()
+        {
+            return "NOT CONTAINS";
+        }
+
+        @Override
+        public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer 
leftOperand, ByteBuffer rightOperand)
+        {
+            return !CONTAINS.isSatisfiedBy(type, leftOperand, rightOperand);
+        }
+
+        @Override
+        public boolean isSatisfiedBy(MultiElementType<?> type, 
ComplexColumnData leftOperand, ByteBuffer rightOperand)
+        {
+            return !CONTAINS.isSatisfiedBy(type, leftOperand, rightOperand);
+        }
+
+        @Override
+        public boolean appliesToColumnValues()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean appliesToCollectionElements()
+        {
+            return true;
+        }
+
+        @Override
+        public Operator negate()
+        {
+            return CONTAINS;
+        }
+    },
+    NOT_CONTAINS_KEY(18)
+    {
+        @Override
+        public String toString()
+        {
+            return "NOT CONTAINS KEY";
+        }
+
+        @Override
+        public boolean isSatisfiedBy(AbstractType<?> type, ByteBuffer 
leftOperand, ByteBuffer rightOperand)
+        {
+            return !CONTAINS_KEY.isSatisfiedBy(type, leftOperand, 
rightOperand);
+        }
+
+        @Override
+        public boolean isSatisfiedBy(MultiElementType<?> type, 
ComplexColumnData leftOperand, ByteBuffer rightOperand)
+        {
+            return !CONTAINS_KEY.isSatisfiedBy(type, leftOperand, 
rightOperand);
+        }
+
+        @Override
+        public boolean appliesToColumnValues()
+        {
+            return false;
+        }
+
+        @Override
+        public boolean appliesToMapKeys()
+        {
+            return true;
+        }
+
+        @Override
+        public Operator negate()
+        {
+            return CONTAINS_KEY;
+        }
+    },
     BETWEEN(19)
     {
         @Override
-        public Kind kind() {
+        public Kind kind()
+        {
             return Kind.TERNARY;
         }
 
@@ -773,7 +938,7 @@ public enum Operator
             case SINGLE_COLUMN:
                 ColumnMetadata firstColumn = expression.firstColumn();
                 AbstractType<?> columnType = firstColumn.type;
-                if (isSlice())
+                if (isSlice() && this != Operator.NEQ)
                 {
                     if (columnType.referencesDuration())
                     {
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java 
b/src/java/org/apache/cassandra/cql3/Relation.java
index be9112c817..86d8db1cbb 100644
--- a/src/java/org/apache/cassandra/cql3/Relation.java
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -188,11 +188,11 @@ public final class Relation
      */
     public SingleRestriction toRestriction(TableMetadata table, 
VariableSpecifications boundNames)
     {
-        if (operator == Operator.NEQ)
-            throw invalidRequest("Unsupported '!=' relation: %s", this);
-
         ColumnsExpression columnsExpression = rawExpressions.prepare(table);
 
+        if (operator == Operator.NEQ && columnsExpression.kind() == 
ColumnsExpression.Kind.TOKEN)
+            throw invalidRequest("Unsupported '!=' relation: %s", this);
+
         // TODO support restrictions on list elements as we do in conditions, 
then we can probably move below validations
         //  to ElementExpression prepare/validateColumns
         if (columnsExpression.isMapElementExpression())
diff --git a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java 
b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
index 3c0aa7ca19..1d0afcddc2 100644
--- a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
@@ -353,7 +353,7 @@ public final class ColumnCondition
 
         private void validateOperationOnDurations(AbstractType<?> type)
         {
-            if (type.referencesDuration() && operator.isSlice())
+            if (type.referencesDuration() && operator.isSlice() && operator != 
Operator.NEQ)
             {
                 checkFalse(type.isCollection(), "Slice conditions are not 
supported on collections containing durations");
                 checkFalse(type.isTuple(), "Slice conditions are not supported 
on tuples containing durations");
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringElements.java 
b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringElements.java
index a7ce83ffb2..f8f04ebb57 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringElements.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringElements.java
@@ -277,6 +277,11 @@ public class ClusteringElements extends 
ForwardingList<ByteBuffer> implements Co
         return buildRangeSet(endpoint, false, BoundType.OPEN);
     }
 
+    public static Range<ClusteringElements> notEqualTo(ClusteringElements 
endpoint)
+    {
+        return Range.closed(endpoint.bottom(), endpoint.top());
+    }
+
     private static RangeSet<ClusteringElements> 
buildRangeSet(ClusteringElements endpoint, boolean upperBound, BoundType 
boundType)
     {
         TreeRangeSet<ClusteringElements> rangeSet = TreeRangeSet.create();
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java 
b/src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java
index 69b9a840a0..7976b78b7c 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/MergedRestriction.java
@@ -60,7 +60,7 @@ public final class MergedRestriction implements 
SingleRestriction
     private final boolean isMultiColumn;
 
     /**
-     * The number of restrictions that require {@code CONTAINS}, {@code 
CONTAINS_KEY} and Map equality restrictions.
+     * The number of restrictions that require {@code CONTAINS}, {@code 
CONTAINS_KEY}, {@code NOT_CONTAINS}, {@code NOT_CONTAINS_KEY} and Map equality 
restrictions.
      */
     private final int containsCount;
 
@@ -124,7 +124,7 @@ public final class MergedRestriction implements 
SingleRestriction
         checkOperator(other);
 
         if (restriction.isContains() != other.isContains())
-            throw invalidRequest("Collection column %s can only be restricted 
by CONTAINS, CONTAINS KEY," +
+            throw invalidRequest("Collection column %s can only be restricted 
by CONTAINS, CONTAINS KEY, NOT_CONTAINS, NOT_CONTAINS_KEY" +
                                  " or map-entry equality if it already 
restricted by one of those",
                                  restriction.firstColumn().name);
 
@@ -201,7 +201,7 @@ public final class MergedRestriction implements 
SingleRestriction
     }
 
     /**
-     * Checks if the restriction operator is a CONTAINS, CONTAINS_KEY or is an 
equality on a map element.
+     * Checks if the restriction operator is a CONTAINS, CONTAINS_KEY, 
NOT_CONTAINS, NOT_CONTAINS_KEY or is an equality on a map element.
      * @param restriction the restriction to check
      * @return {@code true} if the restriction operator is one of the contains 
operations, {@code false} otherwise.
      */
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeyRestrictions.java 
b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeyRestrictions.java
index ce3dfd4eef..e4df4c7a69 100644
--- 
a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeyRestrictions.java
+++ 
b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeyRestrictions.java
@@ -110,7 +110,7 @@ final class PartitionKeyRestrictions extends 
RestrictionSetWrapper
      */
     public List<ByteBuffer> values(IPartitioner partitioner, QueryOptions 
options, ClientState state)
     {
-        // if we need to perform filtering its means that this query is a 
partition range query and that
+        // if we need to perform filtering it means that this query is a 
partition range query and that
         // this method should not be called
         if (isEmpty() || needFiltering())
             throw new IllegalStateException("the query is a partition range 
query and this method should not be called");
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/SimpleRestriction.java 
b/src/java/org/apache/cassandra/cql3/restrictions/SimpleRestriction.java
index 223930a957..d8b6e7aec8 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/SimpleRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/SimpleRestriction.java
@@ -135,15 +135,17 @@ public final class SimpleRestriction implements 
SingleRestriction
     }
 
     /**
-     * Checks if this restriction operator is a CONTAINS, CONTAINS_KEY or is 
an equality on a map element.
+     * Checks if this restriction operator is a CONTAINS, CONTAINS_KEY, 
NOT_CONTAINS, NOT_CONTAINS_KEY or is an equality on a map element.
      * @return {@code true} if the restriction operator is one of the contains 
operations, {@code false} otherwise.
      */
     public boolean isContains()
     {
         return operator == Operator.CONTAINS
-               || operator == Operator.CONTAINS_KEY
+                || operator == Operator.CONTAINS_KEY
+                || operator == Operator.NOT_CONTAINS
+                || operator == Operator.NOT_CONTAINS_KEY
                 // TODO only map elements supported for now in restrictions
-               || columnsExpression.isMapElementExpression();
+                || columnsExpression.isMapElementExpression();
     }
 
     @Override
@@ -217,7 +219,9 @@ public final class SimpleRestriction implements 
SingleRestriction
     @Override
     public List<ClusteringElements> values(QueryOptions options)
     {
-        assert operator == Operator.EQ || operator == Operator.IN || operator 
== Operator.ANN;
+        assert operator == Operator.EQ ||
+               operator == Operator.IN ||
+               operator == Operator.ANN;
         return bindAndGetClusteringElements(options);
     }
 
@@ -327,7 +331,7 @@ public final class SimpleRestriction implements 
SingleRestriction
         {
             case SINGLE_COLUMN:
                 List<ByteBuffer> buffers = bindAndGet(options);
-                if (operator == Operator.IN || operator == Operator.BETWEEN)
+                if (operator.kind() != Operator.Kind.BINARY)
                 {
                     filter.add(column, operator, 
multiInputOperatorValues(column, buffers));
                 }
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java 
b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index 6c427873ba..4e126e200d 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -734,15 +734,18 @@ public class RowFilter implements 
Iterable<RowFilter.Expression>
             switch (operator)
             {
                 case CONTAINS:
+                case NOT_CONTAINS:
                     assert type instanceof CollectionType;
                     CollectionType<?> ct = (CollectionType<?>)type;
                     type = ct.kind == CollectionType.Kind.SET ? 
ct.nameComparator() : ct.valueComparator();
                     break;
                 case CONTAINS_KEY:
+                case NOT_CONTAINS_KEY:
                     assert type instanceof MapType;
                     type = ((MapType<?, ?>)type).nameComparator();
                     break;
                 case IN:
+                case NOT_IN:
                 case BETWEEN:
                     type = ListType.getInstance(type, false);
                     break;
@@ -783,7 +786,7 @@ public class RowFilter implements 
Iterable<RowFilter.Expression>
         public MapElementExpression(ColumnMetadata column, ByteBuffer key, 
Operator operator, ByteBuffer value)
         {
             super(column, operator, value);
-            assert column.type instanceof MapType && operator == Operator.EQ;
+            assert column.type instanceof MapType && (operator == Operator.EQ 
|| operator == Operator.NEQ);
             this.key = key;
         }
 
@@ -836,8 +839,8 @@ public class RowFilter implements 
Iterable<RowFilter.Expression>
             AbstractType<?> nt = mt.nameComparator();
             AbstractType<?> vt = mt.valueComparator();
             return cql
-                 ? String.format("%s[%s] = %s", column.name.toCQLString(), 
nt.toCQLString(key), vt.toCQLString(value))
-                 : String.format("%s[%s] = %s", column.name.toString(), 
nt.getString(key), vt.getString(value));
+                    ? String.format("%s[%s] %s %s", column.name.toCQLString(), 
nt.toCQLString(key), operator, vt.toCQLString(value))
+                    : String.format("%s[%s] %s %s", column.name.toString(), 
nt.getString(key), operator, vt.getString(value));
         }
 
         @Override
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
index cc8d589c67..1029196754 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java
@@ -132,7 +132,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
     }
 
     @Test
-    public void testMultiAndSingleColumnRelationMix() throws Throwable
+    public void testMultiAndSingleColumnRelationMix()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY 
(a, b, c, d))");
 
@@ -235,7 +235,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
     }
 
     @Test
-    public void testSeveralMultiColumnRelation() throws Throwable
+    public void testSeveralMultiColumnRelation()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY 
(a, b, c, d))");
 
@@ -390,11 +390,94 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testNonEqualsRelation() throws Throwable
+    public void testNonEqualsRelation()
     {
-        createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)");
-        assertInvalidMessage("Unsupported '!=' relation: (b) != (0)",
-                             "SELECT * FROM %s WHERE a = 0 AND (b) != (0)");
+        createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b, 
c))");
+        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0);
+        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 1);
+        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 0);
+        execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
+
+        // Excluding subtrees
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b) != 
(?)", 0, 0),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b) != 
(?)", 0, 1),
+                   row(0, 0, 0),
+                   row(0, 0, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b) != 
(?)", 0, -1),
+                   row(0, 0, 0),
+                   row(0, 0, 1),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b) != 
(?)", 0, 2),
+                   row(0, 0, 0),
+                   row(0, 0, 1),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+
+        // Excluding single rows
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) != 
(?, ?)", 0, -1, -1),
+                   row(0, 0, 0),
+                   row(0, 0, 1),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) != 
(?, ?)", 0, 0, 1),
+                   row(0, 0, 0),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) != 
(?, ?)", 0, 2, 2),
+                   row(0, 0, 0),
+                   row(0, 0, 1),
+                   row(0, 1, 0),
+                   row(0, 1, 1)
+        );
+
+        // Merging multiple != =
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) != ? 
AND (b, c) != ?", 0, tuple(0, 1), tuple(1, 0)),
+                   row(0, 0, 0),
+                   row(0, 1, 1)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND b != ? AND 
(b, c) != ?", 0, 1, tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b) != (?) 
AND (b, c) != ?", 0, 1, tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+
+        // Merging with < <= >= >
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND b < ? AND 
(b, c) != ?", 0, 1, tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND b <= ? AND 
(b, c) != ?", 0, 0, tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND b > ? AND 
(b, c) != ?", 0, 0, tuple(1, 1)),
+                   row(0, 1, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND b >= ? AND 
(b, c) != ?", 0, 1, tuple(1, 1)),
+                   row(0, 1, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) < ? 
AND (b, c) != ?", 0, tuple(0, 2), tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) <= ? 
AND (b, c) != ?", 0, tuple(0, 2), tuple(0, 1)),
+                   row(0, 0, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) > ? 
AND (b, c) != ?", 0, tuple(0, 0), tuple(1, 1)),
+                   row(0, 0, 1),
+                   row(0, 1, 0)
+        );
+        assertRows(execute("SELECT a, b, c FROM %s WHERE a = ? AND (b, c) >= ? 
AND (b, c) != ?", 0, tuple(0, 1), tuple(1, 1)),
+                   row(0, 0, 1),
+                   row(0, 1, 0)
+        );
     }
 
     @Test
@@ -773,7 +856,7 @@ public class SelectMultiColumnRelationTest extends CQLTester
     }
 
     @Test
-    public void testMultipleClusteringReversedComponents() throws Throwable
+    public void testMultipleClusteringReversedComponents()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY 
(a, b, c, d)) WITH CLUSTERING ORDER BY (b DESC, c ASC, d DESC)");
 
@@ -1134,7 +1217,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testINWithDuplicateValue() throws Throwable
+    public void testINWithDuplicateValue()
     {
         createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY (k1, 
k2))");
         execute("INSERT INTO %s (k1,  k2, v) VALUES (?, ?, ?)", 1, 1, 1);
@@ -1168,7 +1251,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testMixedOrderColumns1() throws Throwable
+    public void testMixedOrderColumns1()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, 
PRIMARY KEY (a, b, c, d, e)) WITH " +
                     " CLUSTERING ORDER BY (b DESC, c ASC, d DESC, e ASC)");
@@ -1650,7 +1733,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testMixedOrderColumns2() throws Throwable
+    public void testMixedOrderColumns2()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, 
PRIMARY KEY (a, b, c, d, e)) WITH " +
                     "CLUSTERING ORDER BY (b DESC, c ASC, d ASC, e ASC)");
@@ -1724,7 +1807,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testMixedOrderColumns3() throws Throwable
+    public void testMixedOrderColumns3()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b, 
c)) WITH " +
                     "CLUSTERING ORDER BY (b DESC, c ASC)");
@@ -1760,7 +1843,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testMixedOrderColumns4() throws Throwable
+    public void testMixedOrderColumns4()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, 
PRIMARY KEY (a, b, c, d, e)) WITH " +
                     "CLUSTERING ORDER BY (b ASC, c DESC, d DESC, e ASC)");
@@ -2120,7 +2203,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testMixedOrderColumnsInReverse() throws Throwable
+    public void testMixedOrderColumnsInReverse()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b, 
c)) WITH CLUSTERING ORDER BY (b ASC, c DESC);");
 
@@ -2156,7 +2239,7 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
      * migrated from cql_tests.py:TestCQL.simple_tuple_query_test()
      */
     @Test
-    public void testSimpleTupleQuery() throws Throwable
+    public void testSimpleTupleQuery()
     {
         createTable("create table %s (a int, b int, c int, d int , e int, 
PRIMARY KEY (a, b, c, d, e))");
 
@@ -2226,4 +2309,477 @@ public class SelectMultiColumnRelationTest extends 
CQLTester
         assertInvalidMessage("Multicolumn IN filters are not supported",
                              "SELECT * FROM %s WHERE (c2, c3) IN ((?, ?), (?, 
?)) ALLOW FILTERING", 1, 0, 2, 0);
     }
+
+    @Test
+    public void testNotInRestrictionsWithClustering()
+    {
+        createTable("CREATE TABLE %s (pk int, c1 int, c2 int, v int, primary 
key(pk, c1, c2))");
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 1, 
11);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 2, 
12);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 3, 
13);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 4, 
14);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 1, 
21);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 2, 
22);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 3, 
23);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 4, 
24);
+
+        // empty NOT IN
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
()", 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // non existent NOT IN:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
((?, ?))", 1, 2000, 2001),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // existing values in NOT IN, different ways of passing them:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
((0, 2), (0, 3), (1, 1), (1, 4))", 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
((?, ?), (?, ?), (?, ?), (?, ?))",
+                           1, 0, 2, 0, 3, 1, 1, 1, 4),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
(?, ?, ?, ?)",
+                           1, tuple(0, 2), tuple(0, 3), tuple(1, 1), tuple(1, 
4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
?",
+                           1, list(tuple(0, 2), tuple(0, 3), tuple(1, 1), 
tuple(1, 4))),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Tuples given in arbitrary order:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
(?, ?, ?, ?)",
+                           1, tuple(0, 3), tuple(1, 4), tuple(1, 1), tuple(0, 
2)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Multiple NOT IN:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
(?, ?) AND (c1, c2) NOT IN (?, ?)",
+                           1, tuple(0, 2), tuple(0, 3), tuple(1, 1), tuple(1, 
4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Multiple NOT IN, mixed markers:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
(?, ?) AND (c1, c2) NOT IN ?",
+                           1, tuple(0, 2), tuple(0, 3), list(tuple(1, 1), 
tuple(1, 4))),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Multiple NOT IN, mixed markers and values:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) NOT IN 
((0, 2), (0, 3)) AND (c1, c2) NOT IN ?",
+                           1, list(tuple(1, 1), tuple(1, 4))),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Mixed single-column and multicolumn restrictions:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 NOT IN ? AND 
(c1, c2) NOT IN ?",
+                           1, list(0), list(tuple(1, 1), tuple(1, 4))),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 NOT IN (?) 
AND (c1, c2) NOT IN (?, ?)",
+                           1, 0, tuple(1, 1), tuple(1, 4)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+    }
+
+    @Test
+    public void testNotInRestrictionsWithClusteringAndSlices()
+    {
+        createTable("CREATE TABLE %s (pk int, c1 int, c2 int, v int, primary 
key(pk, c1, c2))");
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 1, 
11);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 2, 
12);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 3, 
13);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 4, 
14);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 1, 
21);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 2, 
22);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 3, 
23);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 4, 
24);
+
+        // NOT IN values outside of slice bounds
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(2, 5)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(1, 1)),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // Empty result set
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(1, 3), tuple(1, 4)));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(0, 3), tuple(0, 2), tuple(0, 1)));
+
+        // NOT IN values inside slice bounds
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(0, 2)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(0, 2)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 3)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 3)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 4, 24));
+
+
+        // One NOT IN value exactly the same as the slice bound
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(0, 2), tuple(1, 2)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND (c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(0, 2), tuple(1, 2)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14),
+                   row(1, 1, 1, 21));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 1)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 1)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // NOT IN with both upper and lower bound
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 0, 3, 13),
+                   row(1, 1, 1, 21));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 1, 1, 21));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) <= ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 0, 3, 13),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) <= ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22));
+
+        // Mixed multi-column NOT IN with single column slice restriction:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 > ? AND (c1, 
c2) NOT IN (?)",
+                           1, 0, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND (c1, 
c2) NOT IN (?)",
+                           1, 1, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 < ? AND (c1, 
c2) NOT IN (?)",
+                           1, 1, tuple(0, 4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 <= ? AND (c1, 
c2) NOT IN (?)",
+                           1, 0, tuple(0, 4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND c1 
<= ? AND (c1, c2) NOT IN (?)",
+                           1, 0, 1, tuple(0, 4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // Mixed single-column NOT IN with multi-column slice restriction:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
c1 NOT IN (?)",
+                           1, tuple(0, 1), 1),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND c1 NOT IN (?)",
+                           1, tuple(0, 1), 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
c1 NOT IN (?)",
+                           1, tuple(1, 3), 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
c1 NOT IN (?)",
+                           1, tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND c1 NOT IN (?)",
+                           1, tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) <= ? AND c1 NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+    }
+
+    @Test
+    public void testNotInRestrictionsWithMixedOrderClusteringAndSlices()
+    {
+        createTable("CREATE TABLE %s (pk int, c1 int, c2 int, v int, primary 
key(pk, c1, c2)) WITH CLUSTERING ORDER BY (c1 DESC, c2 ASC)");
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 1, 
11);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 2, 
12);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 3, 
13);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 0, 4, 
14);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 1, 
21);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 2, 
22);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 3, 
23);
+        execute("INSERT INTO %s (pk, c1, c2, v) values (?, ?, ?, ?)", 1, 1, 4, 
24);
+
+        // NOT IN values outside of slice bounds
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(2, 5)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(1, 1)),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+        // Empty result set
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(1, 3), tuple(1, 4)));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(0, 3), tuple(0, 2), tuple(0, 1)));
+
+        // NOT IN values inside slice bounds
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(0, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 2), tuple(0, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 3)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 3)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 4, 24));
+
+        // One NOT IN value exactly the same as the slice bound
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
(c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(0, 2), tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND (c1, c2) NOT IN (?, ?)",
+                           1, tuple(1, 2), tuple(0, 2), tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 1)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) NOT IN (?)",
+                           1, tuple(1, 1), tuple(1, 1)),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+
+
+        // NOT IN with both upper and lower bound
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 1, 1, 21),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
(c1, c2) <= ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) <= ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 2), tuple(0, 4)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+
+        // Mixed multi-column NOT IN with single column slice restriction:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 > ? AND (c1, 
c2) NOT IN (?)",
+                           1, 0, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND (c1, 
c2) NOT IN (?)",
+                           1, 1, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 < ? AND (c1, 
c2) NOT IN (?)",
+                           1, 1, tuple(0, 4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 <= ? AND (c1, 
c2) NOT IN (?)",
+                           1, 0, tuple(0, 4)),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND c1 
<= ? AND (c1, c2) NOT IN (?)",
+                           1, 0, 1, tuple(0, 4)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13));
+
+        // Mixed single-column NOT IN with multi-column slice restriction:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
c1 NOT IN (?)",
+                           1, tuple(0, 1), 1),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND c1 NOT IN (?)",
+                           1, tuple(0, 1), 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
c1 NOT IN (?)",
+                           1, tuple(1, 3), 1),
+                   row(1, 0, 1, 11),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 3, 13),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) < ? AND 
c1 NOT IN (?)",
+                           1, tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) <= ? 
AND c1 NOT IN (?)",
+                           1, tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) >= ? 
AND (c1, c2) <= ? AND c1 NOT IN (?)",
+                           1, tuple(0, 2), tuple(1, 3), 0),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 2, 22),
+                   row(1, 1, 3, 23));
+
+        // Mixed single-column and multi column slices with multi column NOT 
IN:
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
c1 < ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 1), 1, tuple(0, 3)),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND (c1, c2) > ? AND 
c1 <= ? AND (c1, c2) NOT IN (?)",
+                           1, tuple(0, 1), 0, tuple(0, 3)),
+                   row(1, 0, 2, 12),
+                   row(1, 0, 4, 14));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 > ? AND (c1, 
c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, 0, tuple(1, 4), tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND (c1, 
c2) < ? AND (c1, c2) NOT IN (?)",
+                           1, 1, tuple(1, 4), tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 > ? AND c1 < 
? AND (c1, c2) NOT IN (?)",
+                           1, 0, 2, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+        assertRows(execute("SELECT * FROM %s WHERE pk = ? AND c1 >= ? AND c1 
<= ? AND (c1, c2) NOT IN (?)",
+                           1, 1, 1, tuple(1, 2)),
+                   row(1, 1, 1, 21),
+                   row(1, 1, 3, 23),
+                   row(1, 1, 4, 24));
+    }
  }
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
index 8ecae0d48a..2f683c4e59 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java
@@ -38,11 +38,11 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
         createIndex("CREATE INDEX ON %s (d)");
 
         assertInvalidMessage("Collection column 'b' (set<int>) cannot be 
restricted by a '=' relation",
-                             "SELECT * FROM %s WHERE a = 0 AND b=?", set(0));
+                             "SELECT * FROM %s WHERE a = 0 AND b = ?", set(0));
         assertInvalidMessage("Collection column 'c' (list<int>) cannot be 
restricted by a '=' relation",
-                             "SELECT * FROM %s WHERE a = 0 AND c=?", list(0));
+                             "SELECT * FROM %s WHERE a = 0 AND c = ?", 
list(0));
         assertInvalidMessage("Collection column 'd' (map<int, int>) cannot be 
restricted by a '=' relation",
-                             "SELECT * FROM %s WHERE a = 0 AND d=?", map(0, 
0));
+                             "SELECT * FROM %s WHERE a = 0 AND d = ?", map(0, 
0));
     }
 
     @Test
@@ -65,8 +65,10 @@ public class SelectSingleColumnRelationTest extends CQLTester
                              "SELECT * FROM %s WHERE c = 0 AND b IN (?)", 
set(0));
         assertInvalidMessage("Collection column 'b' (set<int>) cannot be 
restricted by a 'BETWEEN' relation",
                              "SELECT * FROM %s WHERE c = 0 AND b BETWEEN ? AND 
?", set(0), set(0));
-        assertInvalidMessage("Unsupported '!=' relation: b != 5",
-                "SELECT * FROM %s WHERE c = 0 AND b != 5");
+        assertInvalidMessage("Collection column 'b' (set<int>) cannot be 
restricted by a 'NOT IN' relation",
+                             "SELECT * FROM %s WHERE c = 0 AND b NOT IN (?)", 
set(0));
+        assertInvalidMessage("Collection column 'b' (set<int>) cannot be 
restricted by a '!=' relation",
+                             "SELECT * FROM %s WHERE c = 0 AND b != 5");
         assertInvalidMessage("Unsupported restriction: b IS NOT NULL",
                 "SELECT * FROM %s WHERE c = 0 AND b IS NOT NULL");
     }
@@ -216,7 +218,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testClusteringColumnRelationsWithClusteringOrder() throws 
Throwable
+    public void testClusteringColumnRelationsWithClusteringOrder()
     {
         createTable("CREATE TABLE %s (a text, b int, c int, d int, primary 
key(a, b, c)) WITH CLUSTERING ORDER BY (b DESC, c ASC);");
         execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "first", 1, 
5, 1);
@@ -313,7 +315,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testAllowFilteringWithIndexedColumnAndStaticColumns() throws 
Throwable
+    public void testAllowFilteringWithIndexedColumnAndStaticColumns()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, s int static, 
PRIMARY KEY(a, b))");
         createIndex("CREATE INDEX ON %s(c)");
@@ -340,7 +342,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testIndexQueriesOnComplexPrimaryKey() throws Throwable
+    public void testIndexQueriesOnComplexPrimaryKey()
     {
         createTable("CREATE TABLE %s (pk0 int, pk1 int, ck0 int, ck1 int, ck2 
int, value int, PRIMARY KEY ((pk0, pk1), ck0, ck1, ck2))");
 
@@ -464,7 +466,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testINWithDuplicateValue() throws Throwable
+    public void testINWithDuplicateValue()
     {
         createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY (k1, 
k2))");
         execute("INSERT INTO %s (k1,  k2, v) VALUES (?, ?, ?)", 1, 1, 1);
@@ -480,7 +482,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testLargeClusteringINValues() throws Throwable
+    public void testLargeClusteringINValues()
     {
         createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY (k, 
c))");
         execute("INSERT INTO %s (k, c, v) VALUES (0, 0, 0)");
@@ -595,7 +597,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testLimitWithUnset() throws Throwable
+    public void testLimitWithUnset()
     {
         createTable("CREATE TABLE %s (k int PRIMARY KEY, i int)");
         execute("INSERT INTO %s (k, i) VALUES (1, 1)");
@@ -616,12 +618,18 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
         assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN ?", unset());
         assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN(?)", unset());
         assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN(?,?)", 1, unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k NOT IN ? ALLOW FILTERING", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k NOT IN(?) ALLOW FILTERING", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k NOT IN(?,?) ALLOW FILTERING", 1, unset());
         // clustering column
         assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i = ?", unset());
         assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN ?", unset());
         assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN(?)", unset());
         assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN(?,?)", 1, unset());
         assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE i = ? ALLOW FILTERING", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i NOT IN ?", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i NOT IN(?)", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i NOT IN(?,?)", 1, unset());
         // indexed column
         assertInvalidMessage("Invalid unset value for column s", "SELECT * 
from %s WHERE s = ?", unset());
         // range
@@ -670,11 +678,16 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
         assertInvalidMessage("Undefined column name d", "SELECT * FROM %s 
WHERE d > 0 and d <= 2");
         assertInvalidMessage("Undefined column name d", "SELECT * FROM %s 
WHERE d CONTAINS 0");
         assertInvalidMessage("Undefined column name d", "SELECT * FROM %s 
WHERE d CONTAINS KEY 0");
+        assertInvalidMessage("Undefined column name d", "SELECT * FROM %s 
WHERE d NOT CONTAINS 0");
+        assertInvalidMessage("Undefined column name d", "SELECT * FROM %s 
WHERE d NOT CONTAINS KEY 0");
         assertInvalidMessage("Undefined column name d", "SELECT a AS d FROM %s 
WHERE d = 0");
         assertInvalidMessage("Undefined column name d", "SELECT b AS d FROM %s 
WHERE d IN (0, 1)");
+        assertInvalidMessage("Undefined column name d", "SELECT b AS d FROM %s 
WHERE d NOT IN (0, 1)");
         assertInvalidMessage("Undefined column name d", "SELECT b AS d FROM %s 
WHERE d > 0 and d <= 2");
         assertInvalidMessage("Undefined column name d", "SELECT c AS d FROM %s 
WHERE d CONTAINS 0");
         assertInvalidMessage("Undefined column name d", "SELECT c AS d FROM %s 
WHERE d CONTAINS KEY 0");
+        assertInvalidMessage("Undefined column name d", "SELECT c AS d FROM %s 
WHERE d NOT CONTAINS 0");
+        assertInvalidMessage("Undefined column name d", "SELECT c AS d FROM %s 
WHERE d NOT CONTAINS KEY 0");
         assertInvalidMessage("Undefined column name d", "SELECT d FROM %s 
WHERE a = 0");
         assertInvalidMessage("Undefined column name d", "SELECT d FROM %s 
WHERE b BETWEEN 0 AND 0");
     }
@@ -695,13 +708,18 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
         assertInvalidMessage(msg, "SELECT * FROM %s WHERE b <= ?", udt);
         assertInvalidMessage(msg, "SELECT * FROM %s WHERE b BETWEEN ? AND ?", 
udt, udt);
         assertInvalidMessage(msg, "SELECT * FROM %s WHERE b IN (?)", udt);
+        assertInvalidMessage(msg, "SELECT * FROM %s WHERE b NOT IN (?)", udt);
         assertInvalidMessage(msg, "SELECT * FROM %s WHERE b LIKE ?", udt);
-        assertInvalidMessage("Unsupported '!=' relation: b != {a: 0}",
-                             "SELECT * FROM %s WHERE b != {a: 0}", udt);
+        assertInvalidMessage(msg, "SELECT * FROM %s WHERE b != {a: 0}", udt);
+        assertInvalidMessage(msg, "SELECT * FROM %s WHERE b != {a: 0}", udt);
         assertInvalidMessage("Unsupported restriction: b IS NOT NULL",
                              "SELECT * FROM %s WHERE b IS NOT NULL", udt);
         assertInvalidMessage("Cannot use CONTAINS on non-collection column b",
                              "SELECT * FROM %s WHERE b CONTAINS ?", udt);
+        assertInvalidMessage("Cannot use NOT CONTAINS on non-collection column 
b",
+                             "SELECT * FROM %s WHERE b NOT CONTAINS ?", udt);
+        assertInvalidMessage("Cannot use NOT CONTAINS on non-collection column 
b",
+                             "SELECT * FROM %s WHERE b NOT CONTAINS ?", udt);
     }
 
     @Test
@@ -739,7 +757,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testInRestrictionsWithAllowFiltering() throws Throwable
+    public void testInRestrictionsWithAllowFiltering()
     {
         createTable("CREATE TABLE %s (pk1 int, pk2 int, c text, s int static, 
v int, primary key((pk1, pk2), c))");
         execute("INSERT INTO %s (pk1, pk2, c, s, v) values (?, ?, ?, ?, ?)", 
1, 0, "5", 1, 3);
@@ -792,7 +810,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testInRestrictionsWithAllowFilteringAndOrdering() throws 
Throwable
+    public void testInRestrictionsWithAllowFilteringAndOrdering()
     {
         createTable("CREATE TABLE %s (pk int, c text, v int, primary key(pk, 
c)) WITH CLUSTERING ORDER BY (c DESC)");
         execute("INSERT INTO %s (pk, c, v) values (?, ?, ?)", 1, "0", 5);
@@ -839,7 +857,7 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
     }
 
     @Test
-    public void testSliceRestrictionWithNegativeClusteringColumnValues() 
throws Throwable
+    public void testSliceRestrictionWithNegativeClusteringColumnValues()
     {
         createTable("CREATE TABLE %s (pk int, c int, v int, PRIMARY KEY (pk, 
c))");
 
@@ -861,4 +879,453 @@ public class SelectSingleColumnRelationTest extends 
CQLTester
                    row(1, -2, -2),
                    row(1, -1, -1));
     }
+
+    @Test
+    public void testClusteringSlicesWithNotIn()
+    {
+        createTable("CREATE TABLE %s (a text, b int, c int, d int, primary 
key(a, b, c))");
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 1, 
4, 1);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
5, 2);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
6, 3);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
7, 4);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 3, 
8, 5);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 3, 
9, 6);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 4, 
1, 7);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 4, 
2, 8);
+
+        // restrict first clustering column by NOT IN
+        assertRows(execute("select * from %s where a = ? and b not in ?", 
"key", list(2, 4, 5)),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?, 
?)", "key", 2, 4, 5),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+
+        // use different order of items in NOT IN list:
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?, 
?)", "key", 5, 2, 4),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?, 
?)", "key", 5, 4, 2),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+
+        // restrict last clustering column by NOT IN
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ?", "key", 2, list(5, 6)),
+                   row("key", 2, 7, 4));
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?, ?)", "key", 2, 5, 6),
+                   row("key", 2, 7, 4));
+
+        // empty NOT IN should have no effect:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ?", "key", 2, list()),
+                   row("key", 2, 5, 2),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 7, 4));
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ()", "key", 2),
+                   row("key", 2, 5, 2),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 7, 4));
+
+        // NOT IN value that doesn't match any data should have no effect:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?)", "key", 2, 0),
+                   row("key", 2, 5, 2),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 7, 4));
+
+        // Duplicate NOT IN values:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?, ?)", "key", 2, 5, 5),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 7, 4));
+
+        // mix NOT IN and '<' and '<=' comparison on the same column
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2, 5), 1)); // empty
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2, 5), 3),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 2),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 3),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 10),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+
+        // mix NOT IN and '>' and '>=' comparison on the same column
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 2),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 2),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 4),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 0),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+
+        // mix NOT IN and range slice
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(2), 1, 4),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ? and b < ?", "key", list(2), 1, 4),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b <= ?", "key", list(2), 1, 4),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ? and b <= ?", "key", list(2), 1, 4),
+                   row("key", 1, 4, 1),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+
+        // Collision between a slice bound and NOT IN value:
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2), 2),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 2),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 4, 1, 7),
+                   row("key", 4, 2, 8));
+
+        // NOT IN value outside the slice range:
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(0), 2, 4),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(10), 2, 4),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+
+        // multiple NOT IN on the same column, use different ways of passing a 
list
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
not in ?", "key", list(1, 2), list(4)),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?) 
and b not in (?)", "key", 1, 2, 4),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?) 
and b not in ?", "key", 1, 2, list(4)),
+                   row("key", 3, 8, 5),
+                   row("key", 3, 9, 6));
+
+        // mix IN and NOT IN
+        assertRows(execute("select * from %s where a = ? and b in ? and c not 
in ?", "key", list(2, 3), list(5, 6, 9)),
+                   row("key", 2, 7, 4),
+                   row("key", 3, 8, 5));
+    }
+
+    @Test
+    public void testClusteringSlicesWithNotInAndReverseOrdering()
+    {
+        createTable("CREATE TABLE %s (a text, b int, c int, d int, primary 
key(a, b, c)) with clustering order by (b desc, c desc)");
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 1, 
4, 1);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
5, 2);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
6, 3);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 2, 
7, 4);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 3, 
8, 5);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 3, 
9, 6);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 4, 
1, 7);
+        execute("insert into %s (a, b, c, d) values (?, ?, ?, ?)", "key", 4, 
2, 8);
+
+        // restrict first clustering column by NOT IN
+        assertRows(execute("select * from %s where a = ? and b not in ?", 
"key", list(2, 4, 5)),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?, 
?)", "key", 2, 4, 5),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+
+        // restrict last clustering column by NOT IN
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ?", "key", 2, list(5, 6)),
+                   row("key", 2, 7, 4));
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?, ?)", "key", 2, 5, 6),
+                   row("key", 2, 7, 4));
+
+        // empty NOT IN should have no effect:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ?", "key", 2, list()),
+                   row("key", 2, 7, 4),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 5, 2));
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in ()", "key", 2),
+                   row("key", 2, 7, 4),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 5, 2));
+
+        // NOT IN value that doesn't match any data should have no effect:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?)", "key", 2, 0),
+                   row("key", 2, 7, 4),
+                   row("key", 2, 6, 3),
+                   row("key", 2, 5, 2));
+
+        // Duplicate NOT IN values:
+        assertRows(execute("select * from %s where a = ? and b = ? and c not 
in (?, ?)", "key", 2, 5, 5),
+                   row("key", 2, 7, 4),
+                   row("key", 2, 6, 3));
+
+        // mix NOT IN and '<' and '<=' comparison on the same column
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2, 5), 1)); // empty
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2, 5), 3),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 2),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 3),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
<= ?", "key", list(2), 10),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+
+        // mix NOT IN and '>' and '>=' comparison on the same column
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 1),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 2),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 2),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 4),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ?", "key", list(2), 0),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+
+        // mix NOT IN and range slice
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(2), 1, 4),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ? and b < ?", "key", list(2), 1, 4),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b <= ?", "key", list(2), 1, 4),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
>= ? and b <= ?", "key", list(2), 1, 4),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5),
+                   row("key", 1, 4, 1));
+
+        // Collision between a slice bound and NOT IN value:
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
< ?", "key", list(2), 2),
+                   row("key", 1, 4, 1));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ?", "key", list(2), 2),
+                   row("key", 4, 2, 8),
+                   row("key", 4, 1, 7),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+
+        // NOT IN value outside the slice range:
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(0), 2, 4),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
> ? and b < ?", "key", list(10), 2, 4),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+
+        // multiple NOT IN on the same column, use different ways of passing a 
list
+        assertRows(execute("select * from %s where a = ? and b not in ? and b 
not in ?", "key", list(1, 2), list(4)),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?) 
and b not in (?)", "key", 1, 2, 4),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+        assertRows(execute("select * from %s where a = ? and b not in (?, ?) 
and b not in ?", "key", 1, 2, list(4)),
+                   row("key", 3, 9, 6),
+                   row("key", 3, 8, 5));
+
+        // mix IN and NOT IN
+        assertRows(execute("select * from %s where a = ? and b in ? and c not 
in ?", "key", list(2, 3), list(5, 6, 9)),
+                   row("key", 3, 8, 5),
+                   row("key", 2, 7, 4));
+    }
+
+    @Test
+    public void testNotInRestrictionsWithAllowFiltering()
+    {
+        createTable("CREATE TABLE %s (pk int, c int, v int, primary key(pk, 
c))");
+        execute("insert into %s (pk, c, v) values (?, ?, ?)", 1, 1, 1);
+        execute("insert into %s (pk, c, v) values (?, ?, ?)", 1, 2, 2);
+        execute("insert into %s (pk, c, v) values (?, ?, ?)", 1, 3, 3);
+        execute("insert into %s (pk, c, v) values (?, ?, ?)", 1, 4, 4);
+        execute("insert into %s (pk, c, v) values (?, ?, ?)", 1, 5, 5);
+
+        // empty NOT IN set
+        assertRows(execute("select * from %s where pk = ? and v not in ? allow 
filtering", 1, list()),
+                   row(1, 1, 1),
+                   row(1, 2, 2),
+                   row(1, 3, 3),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in () 
allow filtering", 1),
+                   row(1, 1, 1),
+                   row(1, 2, 2),
+                   row(1, 3, 3),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+
+        // NOT IN with values that don't match any data
+        assertRows(execute("select * from %s where pk = ? and v not in (?, ?) 
allow filtering", 1, -6, 20),
+                   row(1, 1, 1),
+                   row(1, 2, 2),
+                   row(1, 3, 3),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+
+        // NOT IN that excludes a few values
+        assertRows(execute("select * from %s where pk = ? and v not in ? allow 
filtering", 1, list(2, 3)),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in (?, ?) 
allow filtering", 1, 2, 3),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+
+        // NOT IN with one-sided slice filters:
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
< ? allow filtering", 1, list(2, 3), 5),
+                   row(1, 1, 1),
+                   row(1, 4, 4));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
< ? allow filtering", 1, list(2, 3, 10), 5),
+                   row(1, 1, 1),
+                   row(1, 4, 4));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
<= ? allow filtering", 1, list(2, 3), 5),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
<= ? allow filtering", 1, list(2, 3, 10), 5),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? allow filtering", 1, list(2, 3), 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? allow filtering", 1, list(0, 2, 3), 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
>= ? allow filtering", 1, list(2, 3), 1),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
>= ? allow filtering", 1, list(0, 2, 3), 1),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+
+        // NOT IN with range filters:
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? and v < ? allow filtering", 1, list(2, 3), 1, 4)); // empty
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? and v < ? allow filtering", 1, list(2, 3), 1, 4)); // empty
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? and v < ? allow filtering", 1, list(2, 3), 0, 5),
+                   row(1, 1, 1),
+                   row(1, 4, 4));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
>= ? and v < ? allow filtering", 1, list(2, 3), 1, 4),
+                   row(1, 1, 1));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
> ? and v <= ? allow filtering", 1, list(2, 3), 1, 4),
+                   row(1, 4, 4));
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
>= ? and v <= ? allow filtering", 1, list(2, 3), 1, 4),
+                   row(1, 1, 1),
+                   row(1, 4, 4));
+
+        // more than one NOT IN clause
+        assertRows(execute("select * from %s where pk = ? and v not in ? and v 
not in ? allow filtering", 1, list(2), list(3)),
+                   row(1, 1, 1),
+                   row(1, 4, 4),
+                   row(1, 5, 5));
+    }
+
+    @Test
+    public void testNonEqualsRelationWithFiltering()
+    {
+        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 (?, ?, ?)", 0, 3, 3);
+
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? ALLOW 
FILTERING", 0, 0),
+                   row(0, 1),
+                   row(0, 2),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? ALLOW 
FILTERING", 0, 1),
+                   row(0, 0),
+                   row(0, 2),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? ALLOW 
FILTERING", 0, -1),
+                   row(0, 0),
+                   row(0, 1),
+                   row(0, 2),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? ALLOW 
FILTERING", 0, 5),
+                   row(0, 0),
+                   row(0, 1),
+                   row(0, 2),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? AND c 
!= ? ALLOW FILTERING", 0, 1, 2),
+                   row(0, 0),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? AND c < 
? ALLOW FILTERING", 0, 1, 2),
+                   row(0, 0)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? AND c 
<= ? ALLOW FILTERING", 0, 1, 2),
+                   row(0, 0),
+                   row(0, 2)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? AND c > 
? ALLOW FILTERING", 0, 2, 0),
+                   row(0, 1),
+                   row(0, 3)
+        );
+        assertRows(execute("SELECT a, b FROM %s WHERE a = ? AND c != ? AND c 
>= ? ALLOW FILTERING", 0, 2, 1),
+                   row(0, 1),
+                   row(0, 3)
+        );
+    }
 }
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java 
b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
index b21944fc7d..5bdff0f831 100644
--- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java
@@ -32,10 +32,9 @@ import 
org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.index.internal.CassandraIndex;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
 import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
+import static org.junit.Assert.*;
 
 
 /**
@@ -301,7 +300,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.select_key_in_test()
      */
     @Test
-    public void testSelectKeyIn() throws Throwable
+    public void testSelectKeyIn()
     {
         createTable("CREATE TABLE %s (userid uuid PRIMARY KEY, firstname text, 
lastname text, age int)");
 
@@ -635,7 +634,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.token_range_test()
      */
     @Test
-    public void testTokenRange() throws Throwable
+    public void testTokenRange()
     {
         createTable(" CREATE TABLE %s (k int PRIMARY KEY, c int, v int)");
 
@@ -668,7 +667,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.count_test()
      */
     @Test
-    public void testSelectCount() throws Throwable
+    public void testSelectCount()
     {
         createTable(" CREATE TABLE %s (kind text, time int, value1 int, value2 
int, PRIMARY KEY(kind, time))");
 
@@ -691,7 +690,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.range_query_test()
      */
     @Test
-    public void testRangeQuery() throws Throwable
+    public void testRangeQuery()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, e int, f 
text, PRIMARY KEY (a, b, c, d, e) )");
 
@@ -765,7 +764,7 @@ public class SelectTest extends CQLTester
      * Migrated from cql_tests.py:TestCQL.bug_4882_test()
      */
     @Test
-    public void testDifferentOrdering() throws Throwable
+    public void testDifferentOrdering()
     {
         createTable(" CREATE TABLE %s ( k int, c1 int, c2 int, v int, PRIMARY 
KEY (k, c1, c2) ) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)");
 
@@ -910,7 +909,7 @@ public class SelectTest extends CQLTester
      * Migrated from cql_tests.py:TestCQL.ticket_5230_test()
      */
     @Test
-    public void testMultipleClausesOnPrimaryKey() throws Throwable
+    public void testMultipleClausesOnPrimaryKey()
     {
         createTable("CREATE TABLE %s (key text, c text, v text, PRIMARY 
KEY(key, c))");
 
@@ -930,7 +929,7 @@ public class SelectTest extends CQLTester
     {
         createTable("CREATE TABLE %s (key text PRIMARY KEY)");
 
-        // We just want to make sure this doesn 't NPE server side
+        // We just want to make sure this doesn't NPE server side
         assertInvalid("select * from %s where token(key) > 
token(int(3030343330393233)) limit 1");
     }
 
@@ -938,7 +937,7 @@ public class SelectTest extends CQLTester
      * Migrated from cql_tests.py:TestCQL.clustering_order_and_functions_test()
      */
     @Test
-    public void testFunctionsWithClusteringDesc() throws Throwable
+    public void testFunctionsWithClusteringDesc()
     {
         createTable("CREATE TABLE %s ( k int, t timeuuid, PRIMARY KEY (k, t) ) 
WITH CLUSTERING ORDER BY (t DESC)");
 
@@ -1026,7 +1025,7 @@ public class SelectTest extends CQLTester
      * Migrated from cql_tests.py:TestCQL.select_distinct_with_deletions_test()
      */
     @Test
-    public void testSelectDistinctWithDeletions() throws Throwable
+    public void testSelectDistinctWithDeletions()
     {
         createTable("CREATE TABLE %s (k int PRIMARY KEY, c int, v int)");
 
@@ -1127,7 +1126,7 @@ public class SelectTest extends CQLTester
      * Migrated from cql_tests.py:TestCQL.bug_6327_test()
      */
     @Test
-    public void testSelectInClauseAtOne() throws Throwable
+    public void testSelectInClauseAtOne()
     {
         createTable("CREATE TABLE %s ( k int, v int, PRIMARY KEY (k, v))");
 
@@ -1144,7 +1143,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.select_count_paging_test()
      */
     @Test
-    public void testSelectCountPaging() throws Throwable
+    public void testSelectCountPaging()
     {
         createTable("create table %s (field1 text, field2 timeuuid, field3 
boolean, primary key(field1, field2))");
         createIndex("create index on %s (field3)");
@@ -1161,7 +1160,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.clustering_order_in_test()
      */
     @Test
-    public void testClusteringOrder() throws Throwable
+    public void testClusteringOrder()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY ((a, 
b), c) ) with clustering order by (c desc)");
 
@@ -1180,7 +1179,7 @@ public class SelectTest extends CQLTester
      * migrated from cql_tests.py:TestCQL.bug7105_test()
      */
     @Test
-    public void testSelectInFinalColumn() throws Throwable
+    public void testSelectInFinalColumn()
     {
         createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY 
(a, b))");
 
@@ -1380,17 +1379,34 @@ public class SelectTest extends CQLTester
             
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE c CONTAINS 2");
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                 "SELECT * FROM %s WHERE c NOT CONTAINS 2");
+
             assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 ALLOW 
FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+
             assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND c 
CONTAINS 3 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE c NOT CONTAINS 2 ALLOW 
FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)),
+                       row(2, 3, list(3, 6), set(6, 12), map(3, 6)));
+
+            assertRows(execute("SELECT * FROM %s WHERE c NOT CONTAINS 2 AND c 
NOT CONTAINS 3 ALLOW FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
+            assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND c NOT 
CONTAINS 3 ALLOW FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
             // Checks filtering for sets
             
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE d CONTAINS 4");
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                 "SELECT * FROM %s WHERE d NOT CONTAINS 4");
+
             assertRows(execute("SELECT * FROM %s WHERE d CONTAINS 4 ALLOW 
FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
@@ -1398,26 +1414,71 @@ public class SelectTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE d CONTAINS 4 AND d 
CONTAINS 6 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE d NOT CONTAINS 4 ALLOW 
FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)),
+                       row(2, 3, list(3, 6), set(6, 12), map(3, 6)));
+
+            assertRows(execute("SELECT * FROM %s WHERE d NOT CONTAINS 4 AND d 
NOT CONTAINS 6 ALLOW FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
+            assertRows(execute("SELECT * FROM %s WHERE d CONTAINS 4 AND d NOT 
CONTAINS 6 ALLOW FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
             // Checks filtering for maps
             
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE e CONTAINS 2");
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                 "SELECT * FROM %s WHERE e NOT CONTAINS 2");
+
             assertRows(execute("SELECT * FROM %s WHERE e CONTAINS 2 ALLOW 
FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE e NOT CONTAINS 2 ALLOW 
FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)),
+                       row(2, 3, list(3, 6), set(6, 12), map(3, 6)));
+
             assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 ALLOW 
FILTERING"),
                        row(1, 2, list(1, 6), set(2, 12), map(1, 6)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE e NOT CONTAINS KEY 1 
ALLOW FILTERING"),
+                       row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
+                       row(2, 3, list(3, 6), set(6, 12), map(3, 6)));
+
+            assertRows(execute("SELECT * FROM %s WHERE e CONTAINS 2 AND e NOT 
CONTAINS KEY 1 ALLOW FILTERING"),
+                       row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
+
             assertRows(execute("SELECT * FROM %s WHERE e[1] = 6 ALLOW 
FILTERING"),
                        row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
 
+            execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 2, [1, 6], {2, 
12}, {1: 6})");
+            execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 3, [3, 2], {6, 
4}, {3: 2})");
+            execute("INSERT INTO %s (a, b, c, d, e) VALUES (1, 4, [1, 2], {2, 
4}, {1: 2})");
+            execute("INSERT INTO %s (a, b, c, d, e) VALUES (2, 3, [3, 6], {6, 
12}, {3: 6})");
+
+            assertRows(execute("SELECT * FROM %s WHERE e[1] != 6 ALLOW 
FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
+            assertEmpty(execute("SELECT * FROM %s WHERE e[1] != 6 AND e[3] != 
2 ALLOW FILTERING"));
+
+            assertEmpty(execute("SELECT * FROM %s WHERE e[1] = 6 AND e[3] = 2 
ALLOW FILTERING"));
+
+            assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 AND 
e[1] != 6 ALLOW FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
             assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 AND e 
CONTAINS 2 ALLOW FILTERING"),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 AND e 
NOT CONTAINS 2 ALLOW FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
             assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND d 
CONTAINS 4 AND e CONTAINS KEY 3 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
+
+            assertRows(execute("SELECT * FROM %s WHERE c CONTAINS 2 AND d 
CONTAINS 4 AND e NOT CONTAINS KEY 1 ALLOW FILTERING"),
+                       row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
         });
 
         // Checks filtering with null
@@ -1434,6 +1495,19 @@ public class SelectTest extends CQLTester
         assertInvalidMessage("Invalid null value for e[1]",
                              "SELECT * FROM %s WHERE e[1] = null ALLOW 
FILTERING");
 
+        assertInvalidMessage("Invalid null value for column c",
+                             "SELECT * FROM %s WHERE c NOT CONTAINS null ALLOW 
FILTERING");
+        assertInvalidMessage("Invalid null value for column d",
+                             "SELECT * FROM %s WHERE d NOT CONTAINS null ALLOW 
FILTERING");
+        assertInvalidMessage("Invalid null value for column e",
+                             "SELECT * FROM %s WHERE e NOT CONTAINS null ALLOW 
FILTERING");
+        assertInvalidMessage("Invalid null value for column e",
+                             "SELECT * FROM %s WHERE e NOT CONTAINS KEY null 
ALLOW FILTERING");
+        assertInvalidMessage("Invalid null map key for column e",
+                             "SELECT * FROM %s WHERE e[null] != 2 ALLOW 
FILTERING");
+        assertInvalidMessage("Invalid null value for e[1]",
+                             "SELECT * FROM %s WHERE e[1] != null ALLOW 
FILTERING");
+
         // Checks filtering with unset
         assertInvalidMessage("Invalid unset value for column c",
                              "SELECT * FROM %s WHERE c CONTAINS ? ALLOW 
FILTERING",
@@ -1453,6 +1527,25 @@ public class SelectTest extends CQLTester
         assertInvalidMessage("Invalid unset value for e[1]",
                              "SELECT * FROM %s WHERE e[1] = ? ALLOW FILTERING",
                              unset());
+
+        assertInvalidMessage("Invalid unset value for column c",
+                             "SELECT * FROM %s WHERE c NOT CONTAINS ? ALLOW 
FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column d",
+                             "SELECT * FROM %s WHERE d NOT CONTAINS ? ALLOW 
FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column e",
+                             "SELECT * FROM %s WHERE e NOT CONTAINS ? ALLOW 
FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column e",
+                             "SELECT * FROM %s WHERE e NOT CONTAINS KEY ? 
ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset map key for column e",
+                             "SELECT * FROM %s WHERE e[?] != 2 ALLOW 
FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for e[1]",
+                             "SELECT * FROM %s WHERE e[1] != ? ALLOW 
FILTERING",
+                             unset());
     }
 
     @Test
@@ -1554,6 +1647,9 @@ public class SelectTest extends CQLTester
             assertInvalidMessage("Map-entry predicates on frozen map column e 
are not supported",
                                  "SELECT * FROM %s WHERE e[1] = 6 ALLOW 
FILTERING");
 
+            assertInvalidMessage("Map-entry predicates on frozen map column e 
are not supported",
+                                 "SELECT * FROM %s WHERE e[1] != 6 ALLOW 
FILTERING");
+
             assertRows(execute("SELECT * FROM %s WHERE e CONTAINS KEY 1 AND e 
CONTAINS 2 ALLOW FILTERING"),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
@@ -1639,7 +1735,7 @@ public class SelectTest extends CQLTester
     }
 
     @Test
-    public void testCKQueryWithValueOver64K() throws Throwable
+    public void testCKQueryWithValueOver64K()
     {
         createTable("CREATE TABLE %s (a text, b text, PRIMARY KEY (a, b))");
 
@@ -1922,42 +2018,80 @@ public class SelectTest extends CQLTester
             assertRows(execute("SELECT * FROM %s WHERE b >= 4 AND c CONTAINS 2 
ALLOW FILTERING"),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                "SELECT * FROM %s WHERE b < 0 AND c NOT 
CONTAINS 2");
+
             assertRows(
                     execute("SELECT * FROM %s WHERE a > 0 AND b <= 3 AND c 
CONTAINS 2 AND c CONTAINS 3 ALLOW FILTERING"),
-                       row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
+                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
+
+            assertRows(
+                    execute("SELECT * FROM %s WHERE a > 0 AND b <= 3 AND c NOT 
CONTAINS 1 AND c NOT CONTAINS 6 ALLOW FILTERING"),
+                    row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
 
             // Checks filtering for sets
             
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                     "SELECT * FROM %s WHERE a = 1 AND d CONTAINS 4");
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                    "SELECT * FROM %s WHERE a = 1 AND d NOT CONTAINS 4");
+
             assertRows(execute("SELECT * FROM %s WHERE d CONTAINS 4 ALLOW 
FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE d NOT CONTAINS 4 ALLOW 
FILTERING"),
+                       row(2, 3, list(3, 6), set(6, 12), map(3, 6)),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
             assertRows(execute("SELECT * FROM %s WHERE d CONTAINS 4 AND d 
CONTAINS 6 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE d NOT CONTAINS 4 AND d 
NOT CONTAINS 6 ALLOW FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
             // Checks filtering for maps
             
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                                  "SELECT * FROM %s WHERE e CONTAINS 2");
 
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                                 "SELECT * FROM %s WHERE e NOT CONTAINS 2");
+
             assertRows(execute("SELECT * FROM %s WHERE a < 2 AND b >= 3 AND e 
CONTAINS 2 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE a < 2 AND b >= 3 AND e 
NOT CONTAINS 6 ALLOW FILTERING"),
+                       row(1, 3, list(3, 2), set(6, 4), map(3, 2)),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND e CONTAINS 
KEY 1 ALLOW FILTERING"),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)),
                        row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
 
+            assertRows(execute("SELECT * FROM %s WHERE a = 1 AND e NOT 
CONTAINS KEY 3 ALLOW FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
             assertRows(execute("SELECT * FROM %s WHERE a in (1) AND b in (2) 
AND e[1] = 6 ALLOW FILTERING"),
                        row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
 
+            assertRows(execute("SELECT * FROM %s WHERE a in (1) AND b in (2) 
AND e[1] != 2 ALLOW FILTERING"),
+                       row(1, 2, list(1, 6), set(2, 12), map(1, 6)));
+
             assertRows(execute("SELECT * FROM %s WHERE a = 1 AND e CONTAINS 
KEY 1 AND e CONTAINS 2 ALLOW FILTERING"),
                        row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
 
+            assertRows(execute("SELECT * FROM %s WHERE a = 1 AND e CONTAINS 
KEY 1 AND e NOT CONTAINS 6 ALLOW FILTERING"),
+                       row(1, 4, list(1, 2), set(2, 4), map(1, 2)));
+
             assertRows(
                     execute("SELECT * FROM %s WHERE a >= 1 AND b in (3) AND c 
CONTAINS 2 AND d CONTAINS 4 AND e CONTAINS KEY 3 ALLOW FILTERING"),
                        row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
+
+            assertRows(
+                    execute("SELECT * FROM %s WHERE a >= 1 AND b in (3) AND c 
CONTAINS 2 AND d CONTAINS 4 AND e NOT CONTAINS KEY 1 ALLOW FILTERING"),
+                    row(1, 3, list(3, 2), set(6, 4), map(3, 2)));
         });
 
         // Checks filtering with null
@@ -1973,6 +2107,19 @@ public class SelectTest extends CQLTester
                              "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND 
e[null] = 2 ALLOW FILTERING");
         assertInvalidMessage("Invalid null value for e[1]",
                              "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e[1] 
= null ALLOW FILTERING");
+        assertInvalidMessage("Invalid null value for column c",
+                             "SELECT * FROM %s WHERE a > 1 AND c NOT CONTAINS 
null ALLOW FILTERING");
+        assertInvalidMessage("Invalid null value for column d",
+                             "SELECT * FROM %s WHERE b < 1 AND d NOT CONTAINS 
null ALLOW FILTERING");
+        assertInvalidMessage("Invalid null value for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e 
NOT CONTAINS null ALLOW FILTERING");
+        assertInvalidMessage("Invalid null value for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e 
NOT CONTAINS KEY null ALLOW FILTERING");
+        assertInvalidMessage("Invalid null map key for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND 
e[null] != 2 ALLOW FILTERING");
+        assertInvalidMessage("Invalid null value for e[1]",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e[1] 
!= null ALLOW FILTERING");
+
 
         // Checks filtering with unset
         assertInvalidMessage("Invalid unset value for column c",
@@ -1993,6 +2140,24 @@ public class SelectTest extends CQLTester
         assertInvalidMessage("Invalid unset value for e[1]",
                              "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e[1] 
= ? ALLOW FILTERING",
                              unset());
+        assertInvalidMessage("Invalid unset value for column c",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND c 
NOT CONTAINS ? ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column d",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND d 
NOT CONTAINS ? ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e 
NOT CONTAINS ? ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e 
NOT CONTAINS KEY ? ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset map key for column e",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e[?] 
!= 2 ALLOW FILTERING",
+                             unset());
+        assertInvalidMessage("Invalid unset value for e[1]",
+                             "SELECT * FROM %s WHERE a >= 1 AND b < 1 AND e[1] 
!= ? ALLOW FILTERING",
+                             unset());
     }
 
     @Test
@@ -2157,12 +2322,26 @@ public class SelectTest extends CQLTester
 
             assertRows(execute("SELECT * FROM %s WHERE a = 21 AND b CONTAINS 2 
ALLOW FILTERING"),
                        row(21, list(2, 3), 24));
+
+            assertRows(execute("SELECT * FROM %s WHERE a = 21 AND b NOT 
CONTAINS 2 ALLOW FILTERING"),
+                       row(21, list(3, 3), 34));
+
             assertInvalidMessage("Clustering column restrictions require the 
use of secondary indices" +
                                  " or filtering for map-element restrictions 
and for the following operators: CONTAINS, CONTAINS KEY, LIKE, ANN",
                                  "SELECT * FROM %s WHERE a = 21 AND b CONTAINS 
2");
 
+            assertInvalidMessage("Clustering column restrictions require the 
use of secondary indices or " +
+                                 "filtering for map-element restrictions and 
for the following operators: CONTAINS, " +
+                                 "CONTAINS KEY, LIKE, ANN, NOT CONTAINS, NOT 
CONTAINS KEY",
+                                 "SELECT * FROM %s WHERE b NOT CONTAINS 2");
+
             assertRows(execute("SELECT * FROM %s WHERE b CONTAINS 2 ALLOW 
FILTERING"),
                        row(21, list(2, 3), 24));
+
+            assertRows(execute("SELECT * FROM %s WHERE b NOT CONTAINS 2 ALLOW 
FILTERING"),
+                       row(11, list(1, 3), 14),
+                       row(21, list(3, 3), 34));
+
             assertInvalidMessage("Clustering column restrictions require the 
use of secondary indices" +
                                  " or filtering for map-element restrictions 
and for the following operators: CONTAINS, CONTAINS KEY, LIKE, ANN",
                                  "SELECT * FROM %s WHERE b CONTAINS 2");
@@ -2171,6 +2350,8 @@ public class SelectTest extends CQLTester
                        row(11, list(1, 3), 14),
                        row(21, list(2, 3), 24),
                        row(21, list(3, 3), 34));
+
+            assertRows(execute("SELECT * FROM %s WHERE b NOT CONTAINS 3 ALLOW 
FILTERING"));
         });
 
         // non-first clustering column
@@ -2184,12 +2365,28 @@ public class SelectTest extends CQLTester
 
             assertRows(execute("SELECT * FROM %s WHERE a = 21 AND c CONTAINS 2 
ALLOW FILTERING"),
                        row(21, 22, list(2, 3), 24));
+
+            assertRows(execute("SELECT * FROM %s WHERE a = 21 AND c NOT 
CONTAINS 2 ALLOW FILTERING"),
+                       row(21, 22, list(3, 3), 34));
+
             assertInvalidMessage("Clustering column restrictions require the 
use of secondary indices" +
                                  " or filtering for map-element restrictions 
and for the following operators: CONTAINS, CONTAINS KEY, LIKE, ANN",
                                  "SELECT * FROM %s WHERE a = 21 AND c CONTAINS 
2");
 
+            assertInvalidMessage("Clustering column restrictions require the 
use of secondary indices or filtering" +
+                                 " for map-element restrictions and for the 
following operators: CONTAINS, CONTAINS KEY, LIKE, " +
+                                 "ANN, NOT CONTAINS, NOT CONTAINS KEY",
+                                 "SELECT * FROM %s WHERE a = 21 AND c NOT 
CONTAINS 2");
+
             assertRows(execute("SELECT * FROM %s WHERE b > 20 AND c CONTAINS 2 
ALLOW FILTERING"),
                        row(21, 22, list(2, 3), 24));
+
+            assertRows(execute("SELECT * FROM %s WHERE b > 20 AND c NOT 
CONTAINS 2 ALLOW FILTERING"),
+                       row(21, 22, list(3, 3), 34));
+
+            assertInvalidMessage("Clustering column \"c\" cannot be restricted 
(preceding column \"b\" is restricted by a non-EQ relation)",
+                                 "SELECT * FROM %s WHERE b > 20 AND c NOT 
CONTAINS 2");
+
             assertInvalidMessage("Clustering column \"c\" cannot be restricted 
(preceding column \"b\" is restricted by a non-EQ relation)",
                                  "SELECT * FROM %s WHERE b > 20 AND c CONTAINS 
2");
 
@@ -2197,6 +2394,8 @@ public class SelectTest extends CQLTester
                        row(11, 12, list(1, 3), 14),
                        row(21, 22, list(2, 3), 24),
                        row(21, 22, list(3, 3), 34));
+
+            assertEmpty(execute("SELECT * FROM %s WHERE c NOT CONTAINS 3 ALLOW 
FILTERING"));
         });
 
         createTable("CREATE TABLE %s (a int, b int, c frozen<map<text, text>>, 
d int, PRIMARY KEY (a, b, c))");
@@ -2208,8 +2407,15 @@ public class SelectTest extends CQLTester
         beforeAndAfterFlush(() -> {
             assertRows(execute("SELECT * FROM %s WHERE b > 20 AND c CONTAINS 
KEY '2' ALLOW FILTERING"),
                        row(21, 22, map("2", "3"), 24));
+
+            assertRows(execute("SELECT * FROM %s WHERE b > 20 AND c NOT 
CONTAINS KEY '2' ALLOW FILTERING"),
+                       row(21, 22, map("3", "3"), 34));
+
             assertInvalidMessage("Clustering column \"c\" cannot be restricted 
(preceding column \"b\" is restricted by a non-EQ relation)",
                                  "SELECT * FROM %s WHERE b > 20 AND c CONTAINS 
KEY '2'");
+
+            assertInvalidMessage("Clustering column \"c\" cannot be restricted 
(preceding column \"b\" is restricted by a non-EQ relation)",
+                                 "SELECT * FROM %s WHERE b > 20 AND c NOT 
CONTAINS KEY '2'");
         });
     }
 
@@ -2241,6 +2447,9 @@ public class SelectTest extends CQLTester
         
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
                              "SELECT * FROM %s WHERE pk CONTAINS KEY 1");
 
+        
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE,
+                             "SELECT * FROM %s WHERE pk NOT CONTAINS KEY 1");
+
         beforeAndAfterFlush(() -> {
             assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk 
CONTAINS KEY 1 ALLOW FILTERING"),
                                     row(map(1, 2), 1, 1),
@@ -2262,6 +2471,23 @@ public class SelectTest extends CQLTester
 
             assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk 
CONTAINS KEY 1 AND ck = 1 AND v = 3 ALLOW FILTERING"),
                                     row(map(1, 2, 3, 4), 1, 3));
+
+            assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk NOT 
CONTAINS KEY 1 ALLOW FILTERING"),
+                                    row(map(5, 6), 5, 5),
+                                    row(map(7, 8), 6, 6));
+
+            assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk 
CONTAINS KEY 1 AND pk NOT CONTAINS 4 ALLOW FILTERING"),
+                                    row(map(1, 2), 1, 1),
+                                    row(map(1, 2), 2, 2));
+
+            assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk NOT 
CONTAINS KEY 1 AND pk CONTAINS 8 ALLOW FILTERING"),
+                                    row(map(7, 8), 6, 6));
+
+            assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk NOT 
CONTAINS KEY 1 AND pk NOT CONTAINS 8 ALLOW FILTERING"),
+                                    row(map(5, 6), 5, 5));
+
+            assertRowsIgnoringOrder(execute("SELECT * FROM %s WHERE pk NOT 
CONTAINS KEY 1 AND v = 5 ALLOW FILTERING"),
+                                    row(map(5, 6), 5, 5));
         });
     }
 
@@ -2343,8 +2569,16 @@ public class SelectTest extends CQLTester
                        row(21, 22, 23, list(2, 4)),
                        row(21, 25, 26, list(2, 7)));
 
-            assertRows(executeFilteringOnly("SELECT a, b, c, d FROM %s WHERE b 
> 20 AND d CONTAINS 2 AND d contains 4"),
+            assertRows(executeFilteringOnly("SELECT a, b, c, d FROM %s WHERE b 
> 20 AND d NOT CONTAINS 2"),
+                       row(31, 32, 33, list(3, 4)));
+
+            assertRows(executeFilteringOnly("SELECT a, b, c, d FROM %s WHERE b 
> 20 AND d CONTAINS 2 AND d CONTAINS 4"),
                        row(21, 22, 23, list(2, 4)));
+
+            assertRows(executeFilteringOnly("SELECT a, b, c, d FROM %s WHERE b 
> 20 AND d NOT CONTAINS 2 AND d CONTAINS 4"),
+                       row(31, 32, 33, list(3, 4)));
+
+            assertRows(executeFilteringOnly("SELECT a, b, c, d FROM %s WHERE b 
> 20 AND d NOT CONTAINS 2 AND d NOT CONTAINS 4"));
         });
     }
 
@@ -2413,7 +2647,7 @@ public class SelectTest extends CQLTester
      * Check select with ith different column order. See CASSANDRA-10988
      */
     @Test
-    public void testClusteringOrderWithSlice() throws Throwable
+    public void testClusteringOrderWithSlice()
     {
         // non-compound, ASC order
         createTable("CREATE TABLE %s (a text, b int, PRIMARY KEY (a, b)) WITH 
CLUSTERING ORDER BY (b ASC)");
@@ -2844,7 +3078,7 @@ public class SelectTest extends CQLTester
     }
 
     @Test
-    public void testWithDistinctAndJsonAsColumnName() throws Throwable
+    public void testWithDistinctAndJsonAsColumnName()
     {
         createTable("CREATE TABLE %s (distinct int, json int, value int, 
PRIMARY KEY(distinct, json))");
         execute("INSERT INTO %s (distinct, json, value) VALUES (0, 0, 0)");
@@ -2947,6 +3181,9 @@ public class SelectTest extends CQLTester
                 assertRows(execute("SELECT * FROM %s WHERE m IN ({1:1s, 2:2s}, 
{1:1s, 3:3s}) ALLOW FILTERING"),
                         row(0, map(1, Duration.from("1s"), 2, 
Duration.from("2s"))),
                         row(2, map(1, Duration.from("1s"), 3, 
Duration.from("3s"))));
+
+                assertRows(execute("SELECT * FROM %s WHERE m NOT CONTAINS 1s 
ALLOW FILTERING"),
+                           row(1, map(2, Duration.from("2s"), 3, 
Duration.from("3s"))));
             }
             else
             {
@@ -3066,30 +3303,41 @@ public class SelectTest extends CQLTester
         beforeAndAfterFlush(() -> {
             // lists
             assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 1 ALLOW 
FILTERING"), row(1, 0), row(0, 0), row(0, 2));
+            assertRows(execute("SELECT k, v FROM %s WHERE l NOT CONTAINS 4 
ALLOW FILTERING"), row(0, 0), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND l CONTAINS 
1 ALLOW FILTERING"), row(0, 0), row(0, 2));
+            assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND l NOT 
CONTAINS 4 ALLOW FILTERING"), row(0, 0), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 2 ALLOW 
FILTERING"), row(1, 0), row(0, 0));
             assertEmpty(execute("SELECT k, v FROM %s WHERE l CONTAINS 6 ALLOW 
FILTERING"));
 
             // sets
             assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'a' ALLOW 
FILTERING" ), row(0, 0), row(0, 2));
+            assertRowsIgnoringOrder(execute("SELECT k, v FROM %s WHERE s NOT 
CONTAINS 'a' ALLOW FILTERING" ), row(0, 1), row(1, 1));
             assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND s CONTAINS 
'a' ALLOW FILTERING"), row(0, 0), row(0, 2));
+            assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND s NOT 
CONTAINS 'a' ALLOW FILTERING" ), row(0, 1));
             assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'd' ALLOW 
FILTERING"), row(1, 1));
             assertEmpty(execute("SELECT k, v FROM %s  WHERE s CONTAINS 'e' 
ALLOW FILTERING"));
+            assertRows(execute("SELECT k, v FROM %s  WHERE s NOT CONTAINS 'a' 
AND s NOT CONTAINS 'c' ALLOW FILTERING"), row(1, 1));
 
             // maps
             assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 1 ALLOW 
FILTERING"), row(1, 0), row(1, 1), row(0, 0), row(0, 1));
+            assertRows(execute("SELECT k, v FROM %s WHERE m NOT CONTAINS 1 
ALLOW FILTERING"), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS 
1 ALLOW FILTERING"), row(0, 0), row(0, 1));
+            assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m NOT 
CONTAINS 1 ALLOW FILTERING"), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 2 ALLOW 
FILTERING"), row(0, 1));
             assertEmpty(execute("SELECT k, v FROM %s  WHERE m CONTAINS 4 ALLOW 
FILTERING"));
+            assertEmpty(execute("SELECT k, v FROM %s  WHERE m NOT CONTAINS 1 
AND m NOT CONTAINS 3 ALLOW FILTERING"));
 
             assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'a' 
ALLOW FILTERING"), row(1, 1), row(0, 0), row(0, 1));
+            assertRows(execute("SELECT k, v FROM %s WHERE m NOT CONTAINS KEY 
'a' ALLOW FILTERING"), row(1, 0), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS 
KEY 'a' ALLOW FILTERING"), row(0, 0), row(0, 1));
+            assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m NOT 
CONTAINS KEY 'a' ALLOW FILTERING"), row(0, 2));
             assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS 
KEY 'c' ALLOW FILTERING"), row(0, 2));
+            assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m NOT 
CONTAINS KEY 'c' ALLOW FILTERING"), row(0, 0), row(0, 1));
         });
     }
 
     @Test
-    public void testMixedTTLOnColumns() throws Throwable
+    public void testMixedTTLOnColumns()
     {
         createTable("CREATE TABLE %s (k int PRIMARY KEY, i int)");
         execute("INSERT INTO %s (k) VALUES (2);");
@@ -3108,14 +3356,14 @@ public class SelectTest extends CQLTester
             if ( i % 2 == 0) // Every odd row has a null i/ttl
                 assertTrue(row.getInt("name_ttl") >= 90 && 
row.getInt("name_ttl") <= 100);
             else
-                assertTrue(row.has("name_ttl") == false);
+                assertFalse(row.has("name_ttl"));
 
             i++;
         }
     }
 
     @Test
-    public void testMixedTTLOnColumnsWide() throws Throwable
+    public void testMixedTTLOnColumnsWide()
     {
         createTable("CREATE TABLE %s (k int, c int, i int, PRIMARY KEY (k, 
c))");
         execute("INSERT INTO %s (k, c) VALUES (2, 2);");
@@ -3138,14 +3386,14 @@ public class SelectTest extends CQLTester
             if ( i % 2 == 0) // Every odd row has a null i/ttl
                 assertTrue(row.getInt("name_ttl") >= 90 && 
row.getInt("name_ttl") <= 100);
             else
-                assertTrue(row.has("name_ttl") == false);
+                assertFalse(row.has("name_ttl"));
 
             i++;
         }
     }
 
     @Test // CASSANDRA-14989
-    public void testTokenFctAcceptsValidArguments() throws Throwable
+    public void testTokenFctAcceptsValidArguments()
     {
         createTable("CREATE TABLE %s (k1 uuid, k2 text, PRIMARY KEY ((k1, 
k2)))");
         execute("INSERT INTO %s (k1, k2) VALUES (uuid(), 'k2')");
@@ -3219,7 +3467,7 @@ public class SelectTest extends CQLTester
     }
 
     @Test
-    public void testQuotedMapTextData() throws Throwable
+    public void testQuotedMapTextData()
     {
         createTable("CREATE TABLE " + KEYSPACE + ".t1 (id int, data text, 
PRIMARY KEY (id))");
         createTable("CREATE TABLE " + KEYSPACE + ".t2 (id int, data map<int, 
text>, PRIMARY KEY (id))");
@@ -3232,7 +3480,7 @@ public class SelectTest extends CQLTester
     }
 
     @Test
-    public void testQuotedSimpleCollectionsData() throws Throwable
+    public void testQuotedSimpleCollectionsData()
     {
         createTable("CREATE TABLE " + KEYSPACE + ".t3 (id int, set_data 
set<text>, list_data list<text>, tuple_data tuple<int, text>, PRIMARY KEY 
(id))");
 
@@ -3244,7 +3492,7 @@ public class SelectTest extends CQLTester
     }
 
     @Test
-    public void testQuotedUDTData() throws Throwable
+    public void testQuotedUDTData()
     {
         createType("CREATE TYPE " + KEYSPACE + ".random (data text)");
         createTable("CREATE TABLE " + KEYSPACE + ".t4 (id int, udt_data 
frozen<random>, PRIMARY KEY (id))");
@@ -3268,12 +3516,12 @@ public class SelectTest extends CQLTester
         assertInvalidMessage("Collection column 'm' (map<text, text>) cannot 
be restricted by a '>' relation",
                              "SELECT * FROM %s WHERE m > {'lmn' : 'f'} AND 
m['lmn'] = 'foo2'");
 
-        assertInvalidMessage("Collection column fm can only be restricted by 
CONTAINS, CONTAINS KEY," +
-                             " or map-entry equality if it already restricted 
by one of those",
+        assertInvalidMessage("Collection column fm can only be restricted by 
CONTAINS, CONTAINS KEY, NOT_CONTAINS, " +
+                             "NOT_CONTAINS_KEY or map-entry equality if it 
already restricted by one of those",
                              "SELECT * FROM %s WHERE fm > {'lmn' : 'f'} AND fm 
CONTAINS 'foo'");
 
-        assertInvalidMessage("Collection column fm can only be restricted by 
CONTAINS, CONTAINS KEY," +
-                             " or map-entry equality if it already restricted 
by one of those",
+        assertInvalidMessage("Collection column fm can only be restricted by 
CONTAINS, CONTAINS KEY, NOT_CONTAINS, " +
+                             "NOT_CONTAINS_KEY or map-entry equality if it 
already restricted by one of those",
                              "SELECT * FROM %s WHERE fm > {'lmn' : 'f'} AND fm 
CONTAINS KEY 'lmn'");
 
         assertInvalidMessage("Map-entry predicates on frozen map column fm are 
not supported",
diff --git 
a/test/unit/org/apache/cassandra/index/sai/cql/TokenRangeReadTest.java 
b/test/unit/org/apache/cassandra/index/sai/cql/TokenRangeReadTest.java
index 67a030ef9f..d63c2631e6 100644
--- a/test/unit/org/apache/cassandra/index/sai/cql/TokenRangeReadTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/cql/TokenRangeReadTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.cassandra.index.sai.cql;
 
+import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
 import org.junit.Test;
 
 import org.apache.cassandra.index.sai.SAITester;
@@ -41,4 +42,34 @@ public class TokenRangeReadTest extends SAITester
             assertRows(execute("SELECT * FROM %s WHERE token(k1) >= token(1) 
AND token(k1) <= token(1) AND v1 = '1'"), row(1, "1"));
         });
     }
+
+    @Test
+    public void testTokenRangeWithNotContains() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 int, v1 set<text>, PRIMARY KEY 
(k1))");
+        createIndex(format("CREATE CUSTOM INDEX ON %%s(v1) USING '%s'", 
StorageAttachedIndex.class.getName()));
+
+        execute("INSERT INTO %S(k1, v1) values(1, {'a', 'b', 'c'})");
+        execute("INSERT INTO %S(k1, v1) values(2, {'a', 'd'})");
+
+        beforeAndAfterFlush(() -> {
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT k1 FROM %s WHERE v1 NOT CONTAINS 'd'");
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT k1 FROM %s WHERE token(k1) >= token(1) AND token(k1) <= token(1) AND v1 
NOT CONTAINS 'z'");
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT k1 FROM %s WHERE token(k1) >= token(2) AND token(k1) <= token(2) AND v1 
NOT CONTAINS 'z'");
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT k1 FROM %s WHERE token(k1) > token(2) AND token(k1) <= token(2) AND v1 
NOT CONTAINS 'z'");
+            
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT k1 FROM %s WHERE token(k1) >= token(2) AND token(k1) < token(2) AND v1 
NOT CONTAINS 'z'");
+        });
+    }
+
+    @Test
+    public void testInvalidOperatorTokenRangeRead() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k1 int, v1 set<text>, PRIMARY KEY 
(k1))");
+        createIndex(format("CREATE CUSTOM INDEX ON %%s(v1) USING '%s'", 
StorageAttachedIndex.class.getName()));
+
+        execute("INSERT INTO %S(k1, v1) values(1, {'a', 'b', 'c'})");
+        execute("INSERT INTO %S(k1, v1) values(2, {'a', 'd'})");
+
+        assertInvalidMessage("Unsupported '!=' relation: token(k1) != 
'token(2)","SELECT k1 FROM %s WHERE token(k1) != 'token(2)'");
+    }
 }
diff --git 
a/test/unit/org/apache/cassandra/index/sai/cql/UnindexedExpressionsTest.java 
b/test/unit/org/apache/cassandra/index/sai/cql/UnindexedExpressionsTest.java
index 6caec99445..6818f0edc2 100644
--- a/test/unit/org/apache/cassandra/index/sai/cql/UnindexedExpressionsTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/cql/UnindexedExpressionsTest.java
@@ -69,9 +69,7 @@ public class UnindexedExpressionsTest extends SAITester
         // The IS NOT operator is only valid on materialized views
         assertInvalidMessage("Unsupported restriction:", "SELECT pk FROM %s 
WHERE val1 = 1 AND val2 is not null");
 
-        // The != operator is currently not supported at all
-        assertInvalidMessage("Unsupported '!=' relation: val2 != '22'", 
"SELECT pk FROM %s WHERE val1 = 1 AND val2 != '22'");
-
+        
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT pk FROM %s WHERE val1 = 1 AND val2 != '22'");
         
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT pk FROM %s WHERE val1 = 1 AND val2 < '22'");
         
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT pk FROM %s WHERE val1 = 1 AND val2 <= '11'");
         
assertInvalidMessage(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE, 
"SELECT pk FROM %s WHERE val1 = 1 AND val2 >= '11'");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to