Establish consistent distinction between non-existing partition and NULL value 
for LWTs on static columns

patch by Alex Petrov; reviewed by Sylvain Lebresne for CASSANDRA-12060


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

Branch: refs/heads/cassandra-3.0
Commit: ead27d9e664726da7695fa11ea7e022c11ee7590
Parents: ec60487
Author: Alex Petrov <oleksandr.pet...@gmail.com>
Authored: Wed Aug 10 13:15:30 2016 +0200
Committer: Sylvain Lebresne <sylv...@datastax.com>
Committed: Wed Sep 21 16:27:32 2016 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../cql3/statements/CQL3CasRequest.java         | 113 ++++++---
 .../operations/InsertUpdateIfConditionTest.java | 249 +++++++++++++------
 3 files changed, 254 insertions(+), 109 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/ead27d9e/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index da56e00..abffd80 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0.10
+ * Establish consistent distinction between non-existing partition and NULL 
value for LWTs on static columns (CASSANDRA-12060)
  * Extend ColumnIdentifier.internedInstances key to include the type that 
generated the byte buffer (CASSANDRA-12516)
  * Backport CASSANDRA-10756 (race condition in NativeTransportService 
shutdown) (CASSANDRA-12472)
  * If CF has no clustering columns, any row cache is full partition cache 
(CASSANDRA-12499)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ead27d9e/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java 
b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
index 9564005..cf4110c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
@@ -26,8 +26,8 @@ import com.google.common.collect.Multimap;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.cql3.*;
 import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
-import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.db.rows.Row;
 import org.apache.cassandra.db.partitions.FilteredPartition;
 import org.apache.cassandra.db.partitions.Partition;
 import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -48,10 +48,13 @@ public class CQL3CasRequest implements CASRequest
     private final boolean updatesStaticRow;
     private boolean hasExists; // whether we have an exist or if not exist 
condition
 
+    // Conditions on the static row. We keep it separate from 'conditions' as 
most things related to the static row are
+    // special cases anyway.
+    private RowCondition staticConditions;
     // We index RowCondition by the clustering of the row they applied to for 
2 reasons:
-    //   1) this allows to keep things sorted to build the ColumnSlice array 
below
+    //   1) this allows to keep things sorted to build the read command below
     //   2) this allows to detect when contradictory conditions are set (not 
exists with some other conditions on the same row)
-    private final SortedMap<Clustering, RowCondition> conditions;
+    private final TreeMap<Clustering, RowCondition> conditions;
 
     private final List<RowUpdate> updates = new ArrayList<>();
 
@@ -78,34 +81,37 @@ public class CQL3CasRequest implements CASRequest
 
     public void addNotExist(Clustering clustering) throws 
InvalidRequestException
     {
-        RowCondition previous = conditions.put(clustering, new 
NotExistCondition(clustering));
-        if (previous != null && !(previous instanceof NotExistCondition))
-        {
-            // these should be prevented by the parser, but it doesn't hurt to 
check
-            if (previous instanceof ExistCondition)
-                throw new InvalidRequestException("Cannot mix IF EXISTS and IF 
NOT EXISTS conditions for the same row");
-            else
-                throw new InvalidRequestException("Cannot mix IF conditions 
and IF NOT EXISTS for the same row");
-        }
-        hasExists = true;
+        addExistsCondition(clustering, new NotExistCondition(clustering), 
true);
     }
 
     public void addExist(Clustering clustering) throws InvalidRequestException
     {
-        RowCondition previous = conditions.put(clustering, new 
ExistCondition(clustering));
-        // this should be prevented by the parser, but it doesn't hurt to check
-        if (previous instanceof NotExistCondition)
-            throw new InvalidRequestException("Cannot mix IF EXISTS and IF NOT 
EXISTS conditions for the same row");
+        addExistsCondition(clustering, new ExistCondition(clustering), false);
+    }
+
+    private void addExistsCondition(Clustering clustering, RowCondition 
condition, boolean isNotExist)
+    {
+        assert condition instanceof ExistCondition || condition instanceof 
NotExistCondition;
+        RowCondition previous = getConditionsForRow(clustering);
+        if (previous != null && 
!(previous.getClass().equals(condition.getClass())))
+        {
+            // these should be prevented by the parser, but it doesn't hurt to 
check
+            throw (previous instanceof NotExistCondition || previous 
instanceof ExistCondition)
+                ? new InvalidRequestException("Cannot mix IF EXISTS and IF NOT 
EXISTS conditions for the same row")
+                : new InvalidRequestException("Cannot mix IF conditions and IF 
" + (isNotExist ? "NOT " : "") + "EXISTS for the same row");
+        }
+
+        setConditionsForRow(clustering, condition);
         hasExists = true;
     }
 
     public void addConditions(Clustering clustering, 
Collection<ColumnCondition> conds, QueryOptions options) throws 
InvalidRequestException
     {
-        RowCondition condition = conditions.get(clustering);
+        RowCondition condition = getConditionsForRow(clustering);
         if (condition == null)
         {
             condition = new ColumnsConditions(clustering);
-            conditions.put(clustering, condition);
+            setConditionsForRow(clustering, condition);
         }
         else if (!(condition instanceof ColumnsConditions))
         {
@@ -114,6 +120,25 @@ public class CQL3CasRequest implements CASRequest
         ((ColumnsConditions)condition).addConditions(conds, options);
     }
 
+    private RowCondition getConditionsForRow(Clustering clustering)
+    {
+        return clustering == Clustering.STATIC_CLUSTERING ? staticConditions : 
conditions.get(clustering);
+    }
+
+    private void setConditionsForRow(Clustering clustering, RowCondition 
condition)
+    {
+        if (clustering == Clustering.STATIC_CLUSTERING)
+        {
+            assert staticConditions == null;
+            staticConditions = condition;
+        }
+        else
+        {
+            RowCondition previous = conditions.put(clustering, condition);
+            assert previous == null;
+        }
+    }
+
     private PartitionColumns columnsToRead()
     {
         // If all our conditions are columns conditions (IF x = ?), then it's 
enough to query
@@ -135,23 +160,37 @@ public class CQL3CasRequest implements CASRequest
 
     public SinglePartitionReadCommand readCommand(int nowInSec)
     {
-        assert !conditions.isEmpty();
-        Slices.Builder builder = new Slices.Builder(cfm.comparator, 
conditions.size());
-        // We always read CQL rows entirely as on CAS failure we want to be 
able to distinguish between "row exists
-        // but all values for which there were conditions are null" and "row 
doesn't exists", and we can't rely on the
-        // row marker for that (see #6623)
-        for (Clustering clustering : conditions.keySet())
-        {
-            if (clustering != Clustering.STATIC_CLUSTERING)
-                builder.add(Slice.make(clustering));
-        }
-
-        ClusteringIndexSliceFilter filter = new 
ClusteringIndexSliceFilter(builder.build(), false);
+        assert staticConditions != null || !conditions.isEmpty();
+
+        // With only a static condition, we still want to make the distinction 
between a non-existing partition and one
+        // that exists (has some live data) but has not static content. So we 
query the first live row of the partition.
+        if (conditions.isEmpty())
+            return SinglePartitionReadCommand.create(cfm,
+                                                     nowInSec,
+                                                     
ColumnFilter.selection(columnsToRead()),
+                                                     RowFilter.NONE,
+                                                     DataLimits.cqlLimits(1),
+                                                     key,
+                                                     new 
ClusteringIndexSliceFilter(Slices.ALL, false));
+
+        ClusteringIndexNamesFilter filter = new 
ClusteringIndexNamesFilter(conditions.navigableKeySet(), false);
         return SinglePartitionReadCommand.create(cfm, nowInSec, key, 
ColumnFilter.selection(columnsToRead()), filter);
     }
 
+    /**
+     * Checks whether the conditions represented by this object applies 
provided the current state of the partition on
+     * which those conditions are.
+     *
+     * @param current the partition with current data corresponding to these 
conditions. More precisely, this must be
+     * the result of executing the command returned by {@link #readCommand}. 
This can be empty but it should not be
+     * {@code null}.
+     * @return whether the conditions represented by this object applies or 
not.
+     */
     public boolean appliesTo(FilteredPartition current) throws 
InvalidRequestException
     {
+        if (staticConditions != null && !staticConditions.appliesTo(current))
+            return false;
+
         for (RowCondition condition : conditions.values())
         {
             if (!condition.appliesTo(current))
@@ -232,7 +271,7 @@ public class CQL3CasRequest implements CASRequest
 
         public boolean appliesTo(FilteredPartition current)
         {
-            return current == null || current.getRow(clustering) == null;
+            return current.getRow(clustering) == null;
         }
     }
 
@@ -245,7 +284,7 @@ public class CQL3CasRequest implements CASRequest
 
         public boolean appliesTo(FilteredPartition current)
         {
-            return current != null && current.getRow(clustering) != null;
+            return current.getRow(clustering) != null;
         }
     }
 
@@ -269,12 +308,10 @@ public class CQL3CasRequest implements CASRequest
 
         public boolean appliesTo(FilteredPartition current) throws 
InvalidRequestException
         {
-            if (current == null)
-                return conditions.isEmpty();
-
+            Row row = current.getRow(clustering);
             for (ColumnCondition.Bound condition : conditions.values())
             {
-                if (!condition.appliesTo(current.getRow(clustering)))
+                if (!condition.appliesTo(row))
                     return false;
             }
             return true;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ead27d9e/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
----------------------------------------------------------------------
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 a1ee4f8..352100e 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
@@ -111,7 +111,8 @@ public class InsertUpdateIfConditionTest extends CQLTester
         // Shouldn't apply
         assertRows(execute("UPDATE %s SET v1 = 3, v2 = 'bar' WHERE k = 0 IF 
EXISTS"), row(false));
 
-        // Should apply
+        // Shouldn't apply
+        assertEmpty(execute("SELECT * FROM %s WHERE k = 0"));
         assertRows(execute("DELETE FROM %s WHERE k = 0 IF v1 IN (null)"), 
row(true));
 
         createTable(" CREATE TABLE %s (k int, c int, v1 text, PRIMARY KEY(k, 
c))");
@@ -1038,14 +1039,20 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
         assertRows(execute("SELECT * FROM %s WHERE a = 6"),
                    row(6, 6, 6, "a"));
 
+        execute("INSERT INTO %s (a, b, s, d) values (7, 7, 100, 'a')");
+        assertRows(execute("UPDATE %s SET s = 7 WHERE a = 7 IF s = 101"),
+                   row(false, 100));
+        assertRows(execute("SELECT * FROM %s WHERE a = 7"),
+                   row(7, 7, 100, "a"));
+
         // pre-existing row with null in the static column
         execute("INSERT INTO %s (a, b, d) values (7, 7, 'a')");
         assertRows(execute("UPDATE %s SET s = 7 WHERE a = 7 IF s = NULL"),
-                   row(true));
+                   row(false, 100));
         assertRows(execute("SELECT * FROM %s WHERE a = 7"),
-                   row(7, 7, 7, "a"));
+                   row(7, 7, 100, "a"));
 
-        // deleting row before CAS
+        // deleting row before CAS makes it effectively non-existing
         execute("DELETE FROM %s WHERE a = 8;");
         assertRows(execute("UPDATE %s SET s = 8 WHERE a = 8 IF s = NULL"),
                    row(true));
@@ -1054,70 +1061,151 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
     }
 
     @Test
-    public void testConditionalUpdatesWithNullValues() throws Throwable
+    public void testConditionalUpdatesWithNonExistingValues() throws Throwable
     {
         createTable("CREATE TABLE %s (a int, b int, s int static, d text, 
PRIMARY KEY (a, b))");
 
-        // pre-populate, leave out static column
-        for (int i = 1; i <= 5; i++)
-            execute("INSERT INTO %s (a, b) VALUES (?, ?)", i, i);
+        assertRows(execute("UPDATE %s SET s = 1 WHERE a = 1 IF s = NULL"),
+                   row(true));
+        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 1"),
+                   row(1, 1, null));
 
-        conditionalUpdatesWithNonExistingOrNullValues();
+        assertRows(execute("UPDATE %s SET s = 2 WHERE a = 2 IF s IN 
(10,20,NULL)"),
+                   row(true));
+        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 2"),
+                   row(2, 2, null));
+
+        assertRows(execute("UPDATE %s SET s = 4 WHERE a = 4 IF s != 4"),
+                   row(true));
+        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 4"),
+                   row(4, 4, null));
 
         // rejected: IN doesn't contain null
-        assertRows(execute("UPDATE %s SET s = 30 WHERE a = 3 IF s IN 
(10,20,30)"),
+        assertRows(execute("UPDATE %s SET s = 3 WHERE a = 3 IF s IN 
(10,20,30)"),
                    row(false));
-        assertRows(execute("SELECT * FROM %s WHERE a = 3"),
-                   row(3, 3, null, null));
+        assertEmpty(execute("SELECT a, s, d FROM %s WHERE a = 3"));
 
         // rejected: comparing number with NULL always returns false
-        for (String operator: new String[] { ">", "<", ">=", "<=", "="})
+        for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
         {
             assertRows(execute("UPDATE %s SET s = 50 WHERE a = 5 IF s " + 
operator + " 3"),
                        row(false));
-            assertRows(execute("SELECT * FROM %s WHERE a = 5"),
-                       row(5, 5, null, null));
+            assertEmpty(execute("SELECT * FROM %s WHERE a = 5"));
         }
-
     }
 
     @Test
-    public void testConditionalUpdatesWithNonExistingValues() throws Throwable
+    public void testConditionalUpdatesWithNullValues() throws Throwable
     {
-        createTable("CREATE TABLE %s (a int, b int, s int static, d text, 
PRIMARY KEY (a, b))");
+        createTable("CREATE TABLE %s (a int, b int, s int static, d int, 
PRIMARY KEY (a, b))");
+
+        // pre-populate, leave out static column
+        for (int i = 1; i <= 5; i++)
+        {
+            execute("INSERT INTO %s (a, b) VALUES (?, ?)", i, 1);
+            execute("INSERT INTO %s (a, b) VALUES (?, ?)", i, 2);
+        }
 
-        conditionalUpdatesWithNonExistingOrNullValues();
+        assertRows(execute("UPDATE %s SET s = 100 WHERE a = 1 IF s = NULL"),
+                   row(true));
+        assertRows(execute("SELECT a, b, s, d FROM %s WHERE a = 1"),
+                   row(1, 1, 100, null),
+                   row(1, 2, 100, null));
+
+        assertRows(execute("UPDATE %s SET s = 200 WHERE a = 2 IF s IN 
(10,20,NULL)"),
+                   row(true));
+        assertRows(execute("SELECT a, b, s, d FROM %s WHERE a = 2"),
+                   row(2, 1, 200, null),
+                   row(2, 2, 200, null));
 
         // rejected: IN doesn't contain null
-        assertRows(execute("UPDATE %s SET s = 3 WHERE a = 3 IF s IN 
(10,20,30)"),
-                   row(false));
-        assertEmpty(execute("SELECT a, s, d FROM %s WHERE a = 3"));
+        assertRows(execute("UPDATE %s SET s = 30 WHERE a = 3 IF s IN 
(10,20,30)"),
+                   row(false, null));
+        assertRows(execute("SELECT * FROM %s WHERE a = 3"),
+                   row(3, 1, null, null),
+                   row(3, 2, null, null));
+
+        assertRows(execute("UPDATE %s SET s = 400 WHERE a = 4 IF s IN 
(10,20,NULL)"),
+                   row(true));
+        assertRows(execute("SELECT * FROM %s WHERE a = 4"),
+                   row(4, 1, 400, null),
+                   row(4, 2, 400, null));
 
         // rejected: comparing number with NULL always returns false
-        for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
+        for (String operator: new String[] { ">", "<", ">=", "<=", "="})
         {
             assertRows(execute("UPDATE %s SET s = 50 WHERE a = 5 IF s " + 
operator + " 3"),
-                       row(false));
-            assertEmpty(execute("SELECT * FROM %s WHERE a = 5"));
+                       row(false, null));
+            assertRows(execute("SELECT * FROM %s WHERE a = 5"),
+                       row(5, 1, null, null),
+                       row(5, 2, null, null));
         }
+
+        assertRows(execute("UPDATE %s SET s = 500 WHERE a = 5 IF s != 5"),
+                   row(true));
+        assertRows(execute("SELECT a, b, s, d FROM %s WHERE a = 5"),
+                   row(5, 1, 500, null),
+                   row(5, 2, 500, null));
+
+        // Similar test, although with two static columns to test limits
+        createTable("CREATE TABLE %s (a int, b int, s1 int static, s2 int 
static, d int, PRIMARY KEY (a, b))");
+
+        for (int i = 1; i <= 5; i++)
+            for (int j = 0; j < 5; j++)
+                execute("INSERT INTO %s (a, b, d) VALUES (?, ?, ?)", i, j, i + 
j);
+
+        assertRows(execute("UPDATE %s SET s2 = 100 WHERE a = 1 IF s1 = NULL"),
+                   row(true));
+
+        execute("INSERT INTO %s (a, b, s1) VALUES (?, ?, ?)", 2, 2, 2);
+        assertRows(execute("UPDATE %s SET s1 = 100 WHERE a = 2 IF s2 = NULL"),
+                   row(true));
+
+        execute("INSERT INTO %s (a, b, s1) VALUES (?, ?, ?)", 2, 2, 2);
+        assertRows(execute("UPDATE %s SET s1 = 100 WHERE a = 2 IF s2 = NULL"),
+                   row(true));
     }
 
-    private void conditionalUpdatesWithNonExistingOrNullValues() throws 
Throwable
+    @Test
+    public void testStaticsWithMultipleConditions() throws Throwable
     {
-        assertRows(execute("UPDATE %s SET s = 1 WHERE a = 1 IF s = NULL"),
-                   row(true));
-        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 1"),
-                   row(1, 1, null));
+        createTable("CREATE TABLE %s (a int, b int, s1 int static, s2 int 
static, d int, PRIMARY KEY (a, b))");
 
-        assertRows(execute("UPDATE %s SET s = 2 WHERE a = 2 IF s IN 
(10,20,NULL)"),
+        for (int i = 1; i <= 5; i++)
+        {
+            execute("INSERT INTO %s (a, b, d) VALUES (?, ?, ?)", i, 1, 5);
+            execute("INSERT INTO %s (a, b, d) VALUES (?, ?, ?)", i, 2, 6);
+        }
+
+        assertRows(execute("BEGIN BATCH\n"
+                           + "UPDATE %1$s SET s2 = 102 WHERE a = 1 IF s1 = 
null;\n"
+                           + "UPDATE %1$s SET s1 = 101 WHERE a = 1 IF s2 = 
null;\n"
+                           + "APPLY BATCH"),
                    row(true));
-        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 2"),
-                   row(2, 2, null));
+        assertRows(execute("SELECT * FROM %s WHERE a = 1"),
+                   row(1, 1, 101, 102, 5),
+                   row(1, 2, 101, 102, 6));
 
-        assertRows(execute("UPDATE %s SET s = 4 WHERE a = 4 IF s != 4"),
+
+        assertRows(execute("BEGIN BATCH\n"
+                           + "UPDATE %1$s SET s2 = 202 WHERE a = 2 IF s1 = 
null;\n"
+                           + "UPDATE %1$s SET s1 = 201 WHERE a = 2 IF s2 = 
null;\n"
+                           + "UPDATE %1$s SET d = 203 WHERE a = 2 AND b = 1 IF 
d = 5;\n"
+                           + "UPDATE %1$s SET d = 204 WHERE a = 2 AND b = 2 IF 
d = 6;\n"
+                           + "APPLY BATCH"),
                    row(true));
-        assertRows(execute("SELECT a, s, d FROM %s WHERE a = 4"),
-                   row(4, 4, null));
+
+        assertRows(execute("SELECT * FROM %s WHERE a = 2"),
+                   row(2, 1, 201, 202, 203),
+                   row(2, 2, 201, 202, 204));
+
+        assertRows(execute("BEGIN BATCH\n"
+                           + "UPDATE %1$s SET s2 = 202 WHERE a = 20 IF s1 = 
null;\n"
+                           + "UPDATE %1$s SET s1 = 201 WHERE a = 20 IF s2 = 
null;\n"
+                           + "UPDATE %1$s SET d = 203 WHERE a = 20 AND b = 1 
IF d = 5;\n"
+                           + "UPDATE %1$s SET d = 204 WHERE a = 20 AND b = 2 
IF d = 6;\n"
+                           + "APPLY BATCH"),
+                   row(false));
     }
 
     @Test
@@ -1129,7 +1217,14 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
         for (int i = 1; i <= 6; i++)
             execute("INSERT INTO %s (a, b) VALUES (?, ?)", i, i);
 
-        testConditionalUpdatesWithNonExistingOrNullValuesWithBatch();
+        // applied: null is indistiguishable from empty value, lwt condition 
is executed before INSERT
+        assertRows(execute("BEGIN BATCH\n"
+                           + "INSERT INTO %1$s (a, b, d) values (2, 2, 'a');\n"
+                           + "UPDATE %1$s SET s = 2 WHERE a = 2 IF s = null;\n"
+                           + "APPLY BATCH"),
+                   row(true));
+        assertRows(execute("SELECT * FROM %s WHERE a = 2"),
+                   row(2, 2, 2, "a"));
 
         // rejected: comparing number with null value always returns false
         for (String operator: new String[] { ">", "<", ">=", "<=", "="})
@@ -1138,20 +1233,36 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
                                + "INSERT INTO %1$s (a, b, s, d) values (3, 3, 
40, 'a');\n"
                                + "UPDATE %1$s SET s = 30 WHERE a = 3 IF s " + 
operator + " 5;\n"
                                + "APPLY BATCH"),
-                       row(false));
+                       row(false, 3, 3, null));
             assertRows(execute("SELECT * FROM %s WHERE a = 3"),
                        row(3, 3, null, null));
         }
 
+        // applied: lwt condition is executed before INSERT, update is applied 
after it
+        assertRows(execute("BEGIN BATCH\n"
+                           + "INSERT INTO %1$s (a, b, s, d) values (4, 4, 4, 
'a');\n"
+                           + "UPDATE %1$s SET s = 5 WHERE a = 4 IF s = null;\n"
+                           + "APPLY BATCH"),
+                   row(true));
+        assertRows(execute("SELECT * FROM %s WHERE a = 4"),
+                   row(4, 4, 5, "a"));
+
+        assertRows(execute("BEGIN BATCH\n"
+                           + "INSERT INTO %1$s (a, b, s, d) values (5, 5, 5, 
'a');\n"
+                           + "UPDATE %1$s SET s = 6 WHERE a = 5 IF s IN 
(1,2,null);\n"
+                           + "APPLY BATCH"),
+                   row(true));
+        assertRows(execute("SELECT * FROM %s WHERE a = 5"),
+                   row(5, 5, 6, "a"));
+
         // rejected: IN doesn't contain null
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s, d) values (6, 6, 70, 
'a');\n"
                            + "UPDATE %1$s SET s = 60 WHERE a = 6 IF s IN 
(1,2,3);\n"
                            + "APPLY BATCH"),
-                   row(false));
+                   row(false, 6, 6, null));
         assertRows(execute("SELECT * FROM %s WHERE a = 6"),
                    row(6, 6, null, null));
-
     }
 
     @Test
@@ -1159,31 +1270,6 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
     {
         createTable("CREATE TABLE %s (a int, b int, s int static, d text, 
PRIMARY KEY (a, b))");
 
-        testConditionalUpdatesWithNonExistingOrNullValuesWithBatch();
-
-        // rejected: comparing number with non-existing value always returns 
false
-        for (String operator: new String[] { ">", "<", ">=", "<=", "="})
-        {
-            assertRows(execute("BEGIN BATCH\n"
-                               + "INSERT INTO %1$s (a, b, s, d) values (3, 3, 
3, 'a');\n"
-                               + "UPDATE %1$s SET s = 3 WHERE a = 3 IF s " + 
operator + " 5;\n"
-                               + "APPLY BATCH"),
-                       row(false));
-            assertEmpty(execute("SELECT * FROM %s WHERE a = 3"));
-        }
-
-        // rejected: IN doesn't contain null
-        assertRows(execute("BEGIN BATCH\n"
-                           + "INSERT INTO %1$s (a, b, s, d) values (6, 6, 6, 
'a');\n"
-                           + "UPDATE %1$s SET s = 7 WHERE a = 6 IF s IN 
(1,2,3);\n"
-                           + "APPLY BATCH"),
-                   row(false));
-        assertEmpty(execute("SELECT * FROM %s WHERE a = 6"));
-    }
-
-    private void testConditionalUpdatesWithNonExistingOrNullValuesWithBatch() 
throws Throwable
-    {
-        // applied: null is indistiguishable from empty value, lwt condition 
is executed before INSERT
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, d) values (2, 2, 'a');\n"
                            + "UPDATE %1$s SET s = 2 WHERE a = 2 IF s = null;\n"
@@ -1199,7 +1285,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
                            + "APPLY BATCH"),
                    row(true));
         assertRows(execute("SELECT * FROM %s WHERE a = 4"),
-                   row(4, 4, 5, "a"));
+                   row(4, 4, 5, "a")); // Note that the update wins because 5 
> 4 (we have a timestamp tie, so values are used)
 
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s, d) values (5, 5, 5, 
'a');\n"
@@ -1207,7 +1293,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
                            + "APPLY BATCH"),
                    row(true));
         assertRows(execute("SELECT * FROM %s WHERE a = 5"),
-                   row(5, 5, 6, "a"));
+                   row(5, 5, 6, "a")); // Same as above
 
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s, d) values (7, 7, 7, 
'a');\n"
@@ -1215,7 +1301,26 @@ public class InsertUpdateIfConditionTest extends 
CQLTester
                            + "APPLY BATCH"),
                    row(true));
         assertRows(execute("SELECT * FROM %s WHERE a = 7"),
-                   row(7, 7, 8, "a"));
+                   row(7, 7, 8, "a")); // Same as above
+
+        // rejected: comparing number with non-existing value always returns 
false
+        for (String operator: new String[] { ">", "<", ">=", "<=", "="})
+        {
+            assertRows(execute("BEGIN BATCH\n"
+                               + "INSERT INTO %1$s (a, b, s, d) values (3, 3, 
3, 'a');\n"
+                               + "UPDATE %1$s SET s = 3 WHERE a = 3 IF s " + 
operator + " 5;\n"
+                               + "APPLY BATCH"),
+                       row(false));
+            assertEmpty(execute("SELECT * FROM %s WHERE a = 3"));
+        }
+
+        // rejected: IN doesn't contain null
+        assertRows(execute("BEGIN BATCH\n"
+                           + "INSERT INTO %1$s (a, b, s, d) values (6, 6, 6, 
'a');\n"
+                           + "UPDATE %1$s SET s = 7 WHERE a = 6 IF s IN 
(1,2,3);\n"
+                           + "APPLY BATCH"),
+                   row(false));
+        assertEmpty(execute("SELECT * FROM %s WHERE a = 6"));
     }
 
     @Test
@@ -1233,7 +1338,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
 
         // rejected: IN doesn't contain null
         assertRows(execute("DELETE s1 FROM %s WHERE a = 2 IF s2 IN 
(10,20,30)"),
-                   row(false));
+                   row(false, null));
         assertRows(execute("SELECT * FROM %s WHERE a = 2"),
                    row(2, 2, 2, null, 2));
 
@@ -1251,7 +1356,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
         for (String operator : new String[]{ ">", "<", ">=", "<=", "=" })
         {
             assertRows(execute("DELETE s1 FROM %s WHERE a = 5 IF s2 " + 
operator + " 3"),
-                       row(false));
+                       row(false, null));
             assertRows(execute("SELECT * FROM %s WHERE a = 5"),
                        row(5, 5, 5, null, 5));
         }
@@ -1262,7 +1367,6 @@ public class InsertUpdateIfConditionTest extends CQLTester
     {
         createTable("CREATE TABLE %s (a int, b int, s1 int static, s2 int 
static, v int, PRIMARY KEY (a, b))");
 
-        // applied: null is indistiguishable from empty value, lwt condition 
is executed before INSERT
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s1, v) values (2, 2, 2, 
2);\n"
                            + "DELETE s1 FROM %1$s WHERE a = 2 IF s2 = null;\n"
@@ -1290,6 +1394,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
                    row(false));
         assertEmpty(execute("SELECT * FROM %s WHERE a = 6"));
 
+        // Note that on equal timestamp, tombstone wins so the DELETE wins
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s1, v) values (4, 4, 4, 
4);\n"
                            + "DELETE s1 FROM %1$s WHERE a = 4 IF s2 = null;\n"
@@ -1298,6 +1403,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
         assertRows(execute("SELECT * FROM %s WHERE a = 4"),
                    row(4, 4, null, null, 4));
 
+        // Note that on equal timestamp, tombstone wins so the DELETE wins
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s1, v) VALUES (5, 5, 5, 
5);\n"
                            + "DELETE s1 FROM %1$s WHERE a = 5 IF s1 IN 
(1,2,null);\n"
@@ -1306,6 +1412,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
         assertRows(execute("SELECT * FROM %s WHERE a = 5"),
                    row(5, 5, null, null, 5));
 
+        // Note that on equal timestamp, tombstone wins so the DELETE wins
         assertRows(execute("BEGIN BATCH\n"
                            + "INSERT INTO %1$s (a, b, s1, v) values (7, 7, 7, 
7);\n"
                            + "DELETE s1 FROM %1$s WHERE a = 7 IF s2 != 7;\n"

Reply via email to