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

bereng 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 d80bb323c9 CONTAINS and CONTAINS KEY support for Lightweight 
Transactions
d80bb323c9 is described below

commit d80bb323c9dd8f814fe02e16dec510557c7d5101
Author: ROCHETEAU Antoine <[email protected]>
AuthorDate: Thu Jan 6 13:46:50 2022 +0100

    CONTAINS and CONTAINS KEY support for Lightweight Transactions
    
    patch by ROCHETEAU Antoine; reviewed by Benjamin Lerer, Berenguer Blasi for 
CASSANDRA-10537
---
 CHANGES.txt                                        |   1 +
 .../cassandra/pages/cql/cql_singlefile.adoc        |   2 +
 pylib/cqlshlib/cql3handling.py                     |   2 +-
 pylib/cqlshlib/test/test_cqlsh_completion.py       |  19 +-
 src/antlr/Parser.g                                 |   1 +
 src/java/org/apache/cassandra/cql3/Operator.java   |  18 ++
 .../cassandra/cql3/conditions/ColumnCondition.java |  42 +++++
 .../cql3/conditions/ColumnConditionTest.java       |  61 ++++++
 .../InsertUpdateIfConditionCollectionsTest.java    | 207 +++++++++++++++++++--
 .../operations/InsertUpdateIfConditionTest.java    |   1 +
 10 files changed, 331 insertions(+), 23 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 414164181d..0f60d18244 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.1
+ * Add support for CONTAINS and CONTAINS KEY in conditional UPDATE and DELETE 
statement (CASSANDRA-10537)
  * Migrate advanced config parameters to the new Config types (CASSANDRA-17431)
  * Make null to be meaning disabled and leave 0 as a valid value for 
permissions_update_interval, roles_update_interval, credentials_update_interval 
(CASSANDRA-17431)
  * Fix typo in Config annotation (CASSANDRA-17431)
diff --git a/doc/modules/cassandra/pages/cql/cql_singlefile.adoc 
b/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
index 56541e0231..4fe8c10a65 100644
--- a/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
+++ b/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
@@ -1386,6 +1386,7 @@ WHERE  +
 | `.' `='
 
 ::=  +
+| CONTAINS (KEY)? +
 | IN  +
 | `[' `]'  +
 | `[' `]' IN  +
@@ -1498,6 +1499,7 @@ WHERE  +
 ::= ( | `(' ( ( `,' )* )? `)')
 
 ::= ( | `!=')  +
+| CONTAINS (KEY)? +
 | IN  +
 | `[' `]' ( | `!=')  +
 | `[' `]' IN  +
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 4025a3767d..0e50efbf23 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -956,7 +956,7 @@ syntax_rules += r'''
                ;
 <conditions> ::=  <condition> ( "AND" <condition> )*
                ;
-<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=") <term>)
+<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=" | "CONTAINS" 
( "KEY" )? ) <term>)
                            | ("IN" "(" <term> ( "," <term> )* ")" )
                          ;
 <condition> ::= conditioncol=<cident>
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py 
b/pylib/cqlshlib/test/test_cqlsh_completion.py
index 696028a283..06f7664712 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -381,7 +381,16 @@ class TestCqlshCompletion(CqlshCompletionCase):
                             choices=['EXISTS', '<quotedName>', '<identifier>'])
 
         self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ",
-                            choices=['>=', '!=', '<=', 'IN', '[', ';', '=', 
'<', '>', '.'])
+                            choices=['>=', '!=', '<=', 'IN', '[', ';', '=', 
'<', '>', '.', 'CONTAINS'])
+
+        self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
TOKEN(lonelykey) <= TOKEN(13) IF lonelykey ",
+                            choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 
'CONTAINS'])
+
+        self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE 
TOKEN(lonelykey) <= TOKEN(13) IF lonelykey CONTAINS ",
+                            choices=['false', 'true', '<pgStringLiteral>',
+                                     '-', '<float>', 'TOKEN', '<identifier>',
+                                     '<uuid>', '{', '[', 'NULL', 
'<quotedStringLiteral>',
+                                     '<blobLiteral>', '<wholenumber>', 'KEY'])
 
     def test_complete_in_delete(self):
         self.trycompletions('DELETE F', choices=['FROM', '<identifier>', 
'<quotedName>'])
@@ -463,7 +472,13 @@ class TestCqlshCompletion(CqlshCompletionCase):
                             choices=['EXISTS', '<identifier>', '<quotedName>'])
         self.trycompletions(('DELETE FROM twenty_rows_composite_table USING 
TIMESTAMP 0 WHERE '
                              'TOKEN(a) >= TOKEN(0) IF b '),
-                            choices=['>=', '!=', '<=', 'IN', '=', '<', '>'])
+                            choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 
'CONTAINS'])
+        self.trycompletions(('DELETE FROM twenty_rows_composite_table USING 
TIMESTAMP 0 WHERE '
+                             'TOKEN(a) >= TOKEN(0) IF b CONTAINS '),
+                            choices=['false', 'true', '<pgStringLiteral>',
+                                     '-', '<float>', 'TOKEN', '<identifier>',
+                                     '<uuid>', '{', '[', 
'NULL','<quotedStringLiteral>',
+                                     '<blobLiteral>','<wholenumber>', 'KEY'])
         self.trycompletions(('DELETE FROM twenty_rows_composite_table USING 
TIMESTAMP 0 WHERE '
                              'TOKEN(a) >= TOKEN(0) IF b < 0 '),
                             choices=['AND', ';'])
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index 27a01f3d3e..dbb2682617 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -1662,6 +1662,7 @@ columnCondition[List<Pair<ColumnIdentifier, 
ColumnCondition.Raw>> conditions]
     // Note: we'll reject duplicates later
     : key=cident
         ( op=relationType t=term { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleCondition(t, op))); }
+        | op=containsOperator t=term { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleCondition(t, op))); }
         | K_IN
             ( values=singleColumnInValues { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleInCondition(values))); }
             | marker=inMarker { conditions.add(Pair.create(key, 
ColumnCondition.Raw.simpleInCondition(marker))); }
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java 
b/src/java/org/apache/cassandra/cql3/Operator.java
index 992056ceb9..bcb5f63be6 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -340,4 +340,22 @@ public enum Operator
     {
         return this == IN;
     }
+
+    /**
+     * Checks if this operator is CONTAINS operator.
+     * @return {@code true} if this operator is a CONTAINS operator, {@code 
false} otherwise.
+     */
+    public boolean isContains()
+    {
+        return this == CONTAINS;
+    }
+
+    /**
+     * Checks if this operator is CONTAINS KEY operator.
+     * @return {@code true} if this operator is a CONTAINS operator, {@code 
false} otherwise.
+     */
+    public boolean isContainsKey()
+    {
+        return this == CONTAINS_KEY;
+    }
 }
diff --git a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java 
b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
index d27fd3fd92..e3f463a255 100644
--- a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
@@ -495,6 +495,9 @@ public abstract class ColumnCondition
             if (value == null)
                 return !iter.hasNext();
 
+            if(operator.isContains() || operator.isContainsKey())
+                return containsAppliesTo(type, iter, 
value.get(ProtocolVersion.CURRENT), operator);
+
             switch (type.kind)
             {
                 case LIST:
@@ -573,6 +576,39 @@ public abstract class ColumnCondition
         }
     }
 
+    private static boolean containsAppliesTo(CollectionType<?> type, 
Iterator<Cell<?>> iter, ByteBuffer value, Operator operator)
+    {
+        AbstractType<?> compareType;
+        switch (type.kind)
+        {
+            case LIST:
+                compareType = ((ListType<?>)type).getElementsType();
+                break;
+            case SET:
+                compareType = ((SetType<?>)type).getElementsType();
+                break;
+            case MAP:
+                compareType = operator.isContainsKey() ? ((MapType<?, 
?>)type).getKeysType() : ((MapType<?, ?>)type).getValuesType();
+                break;
+            default:
+                throw new AssertionError();
+        }
+        boolean appliesToSetOrMapKeys = (type.kind == CollectionType.Kind.SET 
|| type.kind == CollectionType.Kind.MAP && operator.isContainsKey());
+        return containsAppliesTo(compareType, iter, value, 
appliesToSetOrMapKeys);
+    }
+
+    private static boolean containsAppliesTo(AbstractType<?> type, 
Iterator<Cell<?>> iter, ByteBuffer value, Boolean appliesToSetOrMapKeys)
+    {
+        while(iter.hasNext())
+        {
+            // for lists and map values we use the cell value; for sets and 
map keys we use the cell name
+            ByteBuffer cellValue = appliesToSetOrMapKeys ? 
iter.next().path().get(0) : iter.next().buffer();
+            if(type.compare(cellValue, value) == 0)
+                return true;
+        }
+        return false;
+    }
+
     /**
      * A condition on a UDT field
      */
@@ -819,12 +855,18 @@ public abstract class ColumnCondition
 
         private Terms prepareTerms(String keyspace, ColumnSpecification 
receiver)
         {
+            checkFalse(operator.isContainsKey() && !(receiver.type instanceof 
MapType), "Cannot use CONTAINS KEY on non-map column %s", receiver.name);
+            checkFalse(operator.isContains() && 
!(receiver.type.isCollection()), "Cannot use CONTAINS on non-collection column 
%s", receiver.name);
+
             if (operator.isIN())
             {
                 return inValues == null ? 
Terms.ofListMarker(inMarker.prepare(keyspace, receiver), receiver.type)
                                         : Terms.of(prepareTerms(keyspace, 
receiver, inValues));
             }
 
+            if (operator.isContains() || operator.isContainsKey())
+                receiver = ((CollectionType<?>) 
receiver.type).makeCollectionReceiver(receiver, operator.isContainsKey());
+
             return Terms.of(value.prepare(keyspace, receiver));
         }
 
diff --git 
a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java 
b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
index 7a7e4b8d16..a896125430 100644
--- a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
+++ b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
@@ -135,6 +135,22 @@ public class ColumnConditionTest
         return bound.appliesTo(newRow(definition, rowValue));
     }
 
+    private static boolean conditionContainsApplies(List<ByteBuffer> rowValue, 
Operator op, ByteBuffer conditionValue)
+    {
+        ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", 
"c", ListType.getInstance(Int32Type.instance, true));
+        ColumnCondition condition = ColumnCondition.condition(definition, op, 
Terms.of(new Constants.Value(conditionValue)));
+        ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
+        return bound.appliesTo(newRow(definition, rowValue));
+    }
+
+    private static boolean conditionContainsApplies(Map<ByteBuffer, 
ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
+    {
+        ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", 
"c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
+        ColumnCondition condition = ColumnCondition.condition(definition, op, 
Terms.of(new Constants.Value(conditionValue)));
+        ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
+        return bound.appliesTo(newRow(definition, rowValue));
+    }
+
     private static boolean conditionApplies(SortedSet<ByteBuffer> rowValue, 
Operator op, SortedSet<ByteBuffer> conditionValue)
     {
         ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", 
"c", SetType.getInstance(Int32Type.instance, true));
@@ -143,6 +159,14 @@ public class ColumnConditionTest
         return bound.appliesTo(newRow(definition, rowValue));
     }
 
+    private static boolean conditionContainsApplies(SortedSet<ByteBuffer> 
rowValue, Operator op, ByteBuffer conditionValue)
+    {
+        ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", 
"c", SetType.getInstance(Int32Type.instance, true));
+        ColumnCondition condition = ColumnCondition.condition(definition, op, 
Terms.of(new Constants.Value(conditionValue)));
+        ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
+        return bound.appliesTo(newRow(definition, rowValue));
+    }
+
     private static boolean conditionApplies(Map<ByteBuffer, ByteBuffer> 
rowValue, Operator op, Map<ByteBuffer, ByteBuffer> conditionValue)
     {
         ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", 
"c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
@@ -327,6 +351,14 @@ public class ColumnConditionTest
         assertTrue(conditionApplies(list(ONE), GTE, 
list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
         assertFalse(conditionApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
GTE, list(ONE)));
         assertTrue(conditionApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
GTE, list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        //CONTAINS
+        assertTrue(conditionContainsApplies(list(ZERO, ONE, TWO), CONTAINS, 
ONE));
+        assertFalse(conditionContainsApplies(list(ZERO, ONE), CONTAINS, TWO));
+
+        assertFalse(conditionContainsApplies(list(ZERO, ONE, TWO), CONTAINS, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        
assertFalse(conditionContainsApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
CONTAINS, ONE));
+        
assertTrue(conditionContainsApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
     }
 
     private static SortedSet<ByteBuffer> set(ByteBuffer... values)
@@ -422,6 +454,14 @@ public class ColumnConditionTest
         assertTrue(conditionApplies(set(ONE), GTE, 
set(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
         assertFalse(conditionApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
GTE, set(ONE)));
         assertTrue(conditionApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
GTE, set(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        // CONTAINS
+        assertTrue(conditionContainsApplies(set(ZERO, ONE, TWO), CONTAINS, 
ONE));
+        assertFalse(conditionContainsApplies(set(ZERO, ONE), CONTAINS, TWO));
+
+        assertFalse(conditionContainsApplies(set(ZERO, ONE, TWO), CONTAINS, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        
assertFalse(conditionContainsApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
CONTAINS, ONE));
+        
assertTrue(conditionContainsApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), 
CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
     }
 
     // values should be a list of key, value, key, value, ...
@@ -550,5 +590,26 @@ public class ColumnConditionTest
         assertFalse(conditionApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, map(ONE, ONE)));
         assertTrue(conditionApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), GTE, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
         assertTrue(conditionApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER)));
+
+        //CONTAINS
+        assertTrue(conditionContainsApplies(map(ZERO, ONE), CONTAINS, ONE));
+        assertFalse(conditionContainsApplies(map(ZERO, ONE), CONTAINS, ZERO));
+
+        assertFalse(conditionContainsApplies(map(ONE, ONE), CONTAINS, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        
assertTrue(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), 
CONTAINS, ONE));
+        
assertFalse(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertFalse(conditionContainsApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ONE));
+        assertTrue(conditionContainsApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
+
+        //CONTAINS KEY
+        assertTrue(conditionContainsApplies(map(ZERO, ONE), CONTAINS_KEY, 
ZERO));
+        assertFalse(conditionContainsApplies(map(ZERO, ONE), CONTAINS_KEY, 
ONE));
+
+        assertFalse(conditionContainsApplies(map(ONE, ONE), CONTAINS_KEY, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        
assertFalse(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ONE), CONTAINS_KEY, ONE));
+        
assertTrue(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), 
CONTAINS_KEY, ByteBufferUtil.EMPTY_BYTE_BUFFER));
+        assertTrue(conditionContainsApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, ONE));
+        assertFalse(conditionContainsApplies(map(ONE, 
ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
+
     }
 }
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
index 7ad41e3084..c0a5139b5c 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
@@ -154,8 +154,8 @@ public class InsertUpdateIfConditionCollectionsTest extends 
CQLTester
             checkInvalidUDT("v >= null", v, InvalidRequestException.class);
             checkInvalidUDT("v IN null", v, SyntaxException.class);
             checkInvalidUDT("v IN 367", v, SyntaxException.class);
-            checkInvalidUDT("v CONTAINS KEY 123", v, SyntaxException.class);
-            checkInvalidUDT("v CONTAINS 'bar'", v, SyntaxException.class);
+            checkInvalidUDT("v CONTAINS KEY 123", v, 
InvalidRequestException.class);
+            checkInvalidUDT("v CONTAINS 'bar'", v, 
InvalidRequestException.class);
 
             /////////////////// null suffix on stored udt ////////////////////
             v = userType("a", 0, "b", null);
@@ -433,21 +433,35 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
 
     void checkAppliesUDT(String condition, Object value) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, 
value), row(true));
         assertRows(execute("SELECT * FROM %s"), row(0, value));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition), 
row(true));
+        assertEmpty(execute("SELECT * FROM %s"));
+        execute("INSERT INTO %s (k, v) VALUES (0, ?)", value);
     }
 
     void checkDoesNotApplyUDT(String condition, Object value) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, 
value),
                    row(false, value));
         assertRows(execute("SELECT * FROM %s"), row(0, value));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition),
+                   row(false, value));
+        assertRows(execute("SELECT * FROM %s"), row(0, value));
     }
 
     void checkInvalidUDT(String condition, Object value, Class<? extends 
Throwable> expected) throws Throwable
     {
+        // UPDATE statement
         assertInvalidThrow(expected, "UPDATE %s SET v = ?  WHERE k = 0 IF " + 
condition, value);
         assertRows(execute("SELECT * FROM %s"), row(0, value));
+        // DELETE statement
+        assertInvalidThrow(expected, "DELETE FROM %s WHERE k = 0 IF " + 
condition);
+        assertRows(execute("SELECT * FROM %s"), row(0, value));
     }
 
     /**
@@ -472,10 +486,12 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_applies_list("l < ['z']");
             check_applies_list("l <= ['z']");
             check_applies_list("l IN (null, ['foo', 'bar', 'foobar'], ['a'])");
+            check_applies_list("l CONTAINS 'bar'");
 
             // multiple conditions
             check_applies_list("l > ['aaa', 'bbb'] AND l > ['aaa']");
             check_applies_list("l != null AND l IN (['foo', 'bar', 
'foobar'])");
+            check_applies_list("l CONTAINS 'foo' AND l CONTAINS 'foobar'");
 
             // should not apply
             check_does_not_apply_list("l = ['baz']");
@@ -486,10 +502,12 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_does_not_apply_list("l <= ['a']");
             check_does_not_apply_list("l IN (['a'], null)");
             check_does_not_apply_list("l IN ()");
+            check_does_not_apply_list("l CONTAINS 'baz'");
 
             // multiple conditions
             check_does_not_apply_list("l IN () AND l IN (['foo', 'bar', 
'foobar'])");
             check_does_not_apply_list("l > ['zzz'] AND l < ['zzz']");
+            check_does_not_apply_list("l CONTAINS 'bar' AND l CONTAINS 'baz'");
 
             check_invalid_list("l = [null]", InvalidRequestException.class);
             check_invalid_list("l < null", InvalidRequestException.class);
@@ -498,30 +516,42 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_invalid_list("l >= null", InvalidRequestException.class);
             check_invalid_list("l IN null", SyntaxException.class);
             check_invalid_list("l IN 367", SyntaxException.class);
-            check_invalid_list("l CONTAINS KEY 123", SyntaxException.class);
-
-            // not supported yet
-            check_invalid_list("m CONTAINS 'bar'", SyntaxException.class);
+            check_invalid_list("l CONTAINS KEY 123", 
InvalidRequestException.class);
+            check_invalid_list("l CONTAINS null", 
InvalidRequestException.class);
         }
     }
 
     void check_applies_list(String condition) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE 
k=0 IF " + condition), row(true));
         assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), 
row(true));
+        assertEmpty(execute("SELECT * FROM %s"));
+        execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])");
     }
 
     void check_does_not_apply_list(String condition) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE 
k=0 IF " + condition),
                    row(false, list("foo", "bar", "foobar")));
         assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition),
+                   row(false, list("foo", "bar", "foobar")));
+        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
     }
 
     void check_invalid_list(String condition, Class<? extends Throwable> 
expected) throws Throwable
     {
+        // UPDATE statement
         assertInvalidThrow(expected, "UPDATE %s SET l = ['foo', 'bar', 
'foobar'] WHERE k=0 IF " + condition);
         assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
+        // DELETE statement
+        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + 
condition);
+        assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
     }
 
     /**
@@ -543,7 +573,8 @@ public class InsertUpdateIfConditionCollectionsTest extends 
CQLTester
                                  "DELETE FROM %s WHERE k=0 IF l[?] = ?", null, 
"foobar");
             assertInvalidMessage("Invalid negative list index -2",
                                  "DELETE FROM %s WHERE k=0 IF l[?] = ?", -2, 
"foobar");
-
+            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS ?", 
0, "bar");
+            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS KEY 
?", 0, "bar");
             assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, 
null), row(false, list("foo", "bar", "foobar")));
             assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, 
"foobar"), row(false, list("foo", "bar", "foobar")));
             assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", 
"foobar")));
@@ -631,10 +662,12 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_applies_set("s < {'z'}");
             check_applies_set("s <= {'z'}");
             check_applies_set("s IN (null, {'bar', 'foo'}, {'a'})");
+            check_applies_set("s CONTAINS 'foo'");
 
             // multiple conditions
             check_applies_set("s > {'a'} AND s < {'z'}");
             check_applies_set("s IN (null, {'bar', 'foo'}, {'a'}) AND s IN 
({'a'}, {'bar', 'foo'}, null)");
+            check_applies_set("s CONTAINS 'foo' AND s CONTAINS 'bar'");
 
             // should not apply
             check_does_not_apply_set("s = {'baz'}");
@@ -646,6 +679,7 @@ public class InsertUpdateIfConditionCollectionsTest extends 
CQLTester
             check_does_not_apply_set("s IN ({'a'}, null)");
             check_does_not_apply_set("s IN ()");
             check_does_not_apply_set("s != null AND s IN ()");
+            check_does_not_apply_set("s CONTAINS 'baz'");
 
             check_invalid_set("s = {null}", InvalidRequestException.class);
             check_invalid_set("s < null", InvalidRequestException.class);
@@ -654,32 +688,43 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_invalid_set("s >= null", InvalidRequestException.class);
             check_invalid_set("s IN null", SyntaxException.class);
             check_invalid_set("s IN 367", SyntaxException.class);
-            check_invalid_set("s CONTAINS KEY 123", SyntaxException.class);
+            check_invalid_set("s CONTAINS null", 
InvalidRequestException.class);
+            check_invalid_set("s CONTAINS KEY 123", 
InvalidRequestException.class);
 
             // element access is not allow for sets
             check_invalid_set("s['foo'] = 'foobar'", 
InvalidRequestException.class);
-
-            // not supported yet
-            check_invalid_set("m CONTAINS 'bar'", SyntaxException.class);
         }
     }
 
     void check_applies_set(String condition) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + 
condition), row(true));
         assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), 
row(true));
+        assertEmpty(execute("SELECT * FROM %s"));
+        execute("INSERT INTO %s (k, s) VALUES (0, {'bar', 'foo'})");
     }
 
     void check_does_not_apply_set(String condition) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + 
condition), row(false, set("bar", "foo")));
         assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), 
row(false, set("bar", "foo")));
+        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
     }
 
     void check_invalid_set(String condition, Class<? extends Throwable> 
expected) throws Throwable
     {
+        // UPDATE statement
         assertInvalidThrow(expected, "UPDATE %s SET s = {'bar', 'foo'} WHERE 
k=0 IF " + condition);
         assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
+        // DELETE statement
+        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + 
condition);
+        assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
     }
 
     /**
@@ -704,10 +749,13 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_applies_map("m <= {'z': 'z'}");
             check_applies_map("m != {'a': 'a'}");
             check_applies_map("m IN (null, {'a': 'a'}, {'foo': 'bar'})");
+            check_applies_map("m CONTAINS 'bar'");
+            check_applies_map("m CONTAINS KEY 'foo'");
 
             // multiple conditions
             check_applies_map("m > {'a': 'a'} AND m < {'z': 'z'}");
             check_applies_map("m != null AND m IN (null, {'a': 'a'}, {'foo': 
'bar'})");
+            check_applies_map("m CONTAINS 'bar' AND m CONTAINS KEY 'foo'");
 
             // should not apply
             check_does_not_apply_map("m = {'a': 'a'}");
@@ -719,18 +767,16 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
             check_does_not_apply_map("m IN ({'a': 'a'}, null)");
             check_does_not_apply_map("m IN ()");
             check_does_not_apply_map("m = null AND m != null");
+            check_does_not_apply_map("m CONTAINS 'foo'");
+            check_does_not_apply_map("m CONTAINS KEY 'bar'");
 
             check_invalid_map("m = {null: null}", 
InvalidRequestException.class);
             check_invalid_map("m = {'a': null}", 
InvalidRequestException.class);
             check_invalid_map("m = {null: 'a'}", 
InvalidRequestException.class);
+            check_invalid_map("m CONTAINS null", 
InvalidRequestException.class);
+            check_invalid_map("m CONTAINS KEY null", 
InvalidRequestException.class);
             check_invalid_map("m < null", InvalidRequestException.class);
             check_invalid_map("m IN null", SyntaxException.class);
-
-            // not supported yet
-            check_invalid_map("m CONTAINS 'bar'", SyntaxException.class);
-            check_invalid_map("m CONTAINS KEY 'foo'", SyntaxException.class);
-            check_invalid_map("m CONTAINS null", SyntaxException.class);
-            check_invalid_map("m CONTAINS KEY null", SyntaxException.class);
         }
     }
 
@@ -750,6 +796,8 @@ public class InsertUpdateIfConditionCollectionsTest extends 
CQLTester
             execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
             assertInvalidMessage("Invalid null value for map element access",
                                  "DELETE FROM %s WHERE k=0 IF m[?] = ?", null, 
"foo");
+            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS ?", 
"foo", "bar");
+            assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS KEY 
?", "foo", "bar");
             assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", 
"foo"), row(false, map("foo", "bar")));
             assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", 
null), row(false, map("foo", "bar")));
             assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
@@ -769,17 +817,17 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
     @Test
     public void testFrozenWithNullValues() throws Throwable
     {
-        createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m 
%s)", "frozen<list<text>>"));
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m 
frozen<list<text>>)");
         execute("INSERT INTO %s (k, m) VALUES (0, null)");
 
         assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", 
list("test"), list("comparison")), row(false, null));
 
-        createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m 
%s)", "frozen<map<text,int>>"));
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m 
frozen<map<text,int>>)");
         execute("INSERT INTO %s (k, m) VALUES (0, null)");
 
         assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", 
map("test", 3), map("comparison", 2)), row(false, null));
 
-        createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m 
%s)", "frozen<set<text>>"));
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m 
frozen<set<text>>)");
         execute("INSERT INTO %s (k, m) VALUES (0, null)");
 
         assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", 
set("test"), set("comparison")), row(false, null));
@@ -838,20 +886,32 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
 
     void check_applies_map(String condition) throws Throwable
     {
+        // UPDATE statement
         assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + 
condition), row(true));
         assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), 
row(true));
+        assertEmpty(execute("SELECT * FROM %s"));
+        execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
     }
 
     void check_does_not_apply_map(String condition) throws Throwable
     {
         assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + 
condition), row(false, map("foo", "bar")));
         assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
+        // DELETE statement
+        assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), 
row(false, map("foo", "bar")));
+        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
     }
 
     void check_invalid_map(String condition, Class<? extends Throwable> 
expected) throws Throwable
     {
+        // UPDATE statement
         assertInvalidThrow(expected, "UPDATE %s SET m = {'foo': 'bar'} WHERE 
k=0 IF " + condition);
         assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
+        // DELETE statement
+        assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + 
condition);
+        assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
     }
 
     @Test
@@ -918,4 +978,111 @@ public class InsertUpdateIfConditionCollectionsTest 
extends CQLTester
                                  "UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 
0 IF v.a IN ?", unset());
         }
     }
+
+    @Test
+    public void testNonFrozenEmptyCollection() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<text>)");
+        execute("INSERT INTO %s (k, l) VALUES (0, null)");
+
+        // Does apply
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", 
(ByteBuffer) null),
+                   row(true));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", 
list()),
+                   row(true));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l != ?", 
list("bar")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", 
list("a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l <= ?", 
list("a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l IN (?, 
?)", null, list("bar")),
+                   row(true));
+
+        // Does not apply
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", 
list("bar")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l > ?", 
list("a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l >= ?", 
list("a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS 
?", "bar"),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS 
?", unset()),
+                   row(false, null));
+
+        assertInvalidMessage("Invalid comparison with null for operator 
\"CONTAINS\"",
+                             "UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS 
?", (ByteBuffer) null);
+
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<text>)");
+        execute("INSERT INTO %s (k, s) VALUES (0, null)");
+
+        // Does apply
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", 
(ByteBuffer) null),
+                   row(true));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", 
set()),
+                   row(true));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s != ?", 
set("bar")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s < ?", 
set("a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s <= ?", 
set("a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s IN (?, 
?)", null, set("bar")),
+                   row(true));
+
+        // Does not apply
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", 
set("bar")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s > ?", 
set("a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s >= ?", 
set("a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS 
?", "bar"),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS 
?", unset()),
+                   row(false, null));
+
+        assertInvalidMessage("Invalid comparison with null for operator 
\"CONTAINS\"",
+                             "UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS 
?", (ByteBuffer) null);
+
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<text, text>) ");
+        execute("INSERT INTO %s (k, m) VALUES (0, null)");
+
+        // Does apply
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", 
(ByteBuffer) null),
+                   row(true));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", 
map()),
+                   row(true));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m != ?", 
map("foo","bar")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m < ?", 
map("a","a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m <= ?", 
map("a","a")),
+                   row(true));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m IN (?, 
?)", null, map("foo","bar")),
+                   row(true));
+
+        // Does not apply
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", 
map("foo","bar")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m > ?", 
map("a", "a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m >= ?", 
map("a", "a")),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS 
?", "bar"),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", 
unset()),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY 
?", "foo"),
+                   row(false, null));
+        assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY 
?", unset()),
+                   row(false, null));
+
+        assertInvalidMessage("Invalid comparison with null for operator 
\"CONTAINS\"",
+                             "UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS 
?", (ByteBuffer) null);
+        assertInvalidMessage("Invalid comparison with null for operator 
\"CONTAINS KEY\"",
+                             "UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS 
KEY ?", (ByteBuffer) null);
+    }
+
 }
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
index ac03cb5e14..627075f9de 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
@@ -194,6 +194,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
                              "UPDATE %s SET v1 = 'A' WHERE k = 0 AND c IN () 
IF EXISTS");
         assertInvalidMessage("IN on the clustering key columns is not 
supported with conditional updates",
                              "UPDATE %s SET v1 = 'A' WHERE k = 0 AND c IN (1, 
2) IF EXISTS");
+        assertInvalidMessage("Cannot use CONTAINS on non-collection column 
v1", "UPDATE %s SET v1 = 'B' WHERE k = 0 IF v1 CONTAINS 'A'");
     }
 
     /**


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

Reply via email to