You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2016/09/21 14:29:17 UTC
[1/4] cassandra git commit: If CF has no clustering columns,
any row cache is full partition cache (CASSANDRA-12499)
Repository: cassandra
Updated Branches:
refs/heads/cassandra-3.0 ec6048758 -> ead27d9e6
refs/heads/trunk eace9aadd -> 5f0a77b6d
If CF has no clustering columns, any row cache is full partition cache (CASSANDRA-12499)
Patch by Jeff Jirsa ; Reviewed by Sylvain Lebresne for CASSANDRA-12499
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/ec604875
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/ec604875
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/ec604875
Branch: refs/heads/trunk
Commit: ec604875886a5790b627f0fa22cea4fca34532dd
Parents: 517b652
Author: Jeff Jirsa <je...@crowdstrike.com>
Authored: Tue Sep 20 17:16:33 2016 -0700
Committer: Jeff Jirsa <je...@crowdstrike.com>
Committed: Tue Sep 20 17:21:38 2016 -0700
----------------------------------------------------------------------
CHANGES.txt | 1 +
.../db/SinglePartitionReadCommand.java | 6 +-
.../unit/org/apache/cassandra/SchemaLoader.java | 21 ++++--
.../org/apache/cassandra/db/RowCacheTest.java | 72 ++++++++++++++++++++
4 files changed, 93 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/ec604875/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 791f99c..da56e00 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,7 @@
3.0.10
* 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)
Merged from 2.1:
* Add system property to set the max number of native transport requests in queue (CASSANDRA-11363)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/ec604875/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
index 73eb9bd..886a918 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
@@ -407,7 +407,11 @@ public class SinglePartitionReadCommand extends ReadCommand
cfs.metric.rowCacheMiss.inc();
Tracing.trace("Row cache miss");
- boolean cacheFullPartitions = metadata().params.caching.cacheAllRows();
+ // Note that on tables with no clustering keys, any positive value of
+ // rowsToCache implies caching the full partition
+ boolean cacheFullPartitions = metadata().clusteringColumns().size() > 0 ?
+ metadata().params.caching.cacheAllRows() :
+ metadata().params.caching.cacheRows();
// To be able to cache what we read, what we read must at least covers what the cache holds, that
// is the 'rowsToCache' first rows of the partition. We could read those 'rowsToCache' first rows
http://git-wip-us.apache.org/repos/asf/cassandra/blob/ec604875/test/unit/org/apache/cassandra/SchemaLoader.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java
index dd4884c..87e8e1e 100644
--- a/test/unit/org/apache/cassandra/SchemaLoader.java
+++ b/test/unit/org/apache/cassandra/SchemaLoader.java
@@ -205,6 +205,7 @@ public class SchemaLoader
Tables.of(
standardCFMD(ks_rcs, "CFWithoutCache").caching(CachingParams.CACHE_NOTHING),
standardCFMD(ks_rcs, "CachedCF").caching(CachingParams.CACHE_EVERYTHING),
+ standardCFMD(ks_rcs, "CachedNoClustering", 1, IntegerType.instance, IntegerType.instance, null).caching(CachingParams.CACHE_EVERYTHING),
standardCFMD(ks_rcs, "CachedIntCF").
caching(new CachingParams(true, 100)))));
@@ -348,18 +349,22 @@ public class SchemaLoader
public static CFMetaData standardCFMD(String ksName, String cfName, int columnCount, AbstractType<?> keyType, AbstractType<?> valType, AbstractType<?> clusteringType)
{
- CFMetaData.Builder builder = CFMetaData.Builder.create(ksName, cfName)
- .addPartitionKey("key", keyType)
- .addClusteringColumn("name", clusteringType)
- .addRegularColumn("val", valType);
+ CFMetaData.Builder builder;
+ builder = CFMetaData.Builder.create(ksName, cfName)
+ .addPartitionKey("key", keyType)
+ .addRegularColumn("val", valType);
+
+ if(clusteringType != null)
+ builder = builder.addClusteringColumn("name", clusteringType);
for (int i = 0; i < columnCount; i++)
builder.addRegularColumn("val" + i, AsciiType.instance);
return builder.build()
- .compression(getCompressionParameters());
+ .compression(getCompressionParameters());
}
+
public static CFMetaData denseCFMD(String ksName, String cfName)
{
return denseCFMD(ksName, cfName, AsciiType.instance);
@@ -524,11 +529,15 @@ public class SchemaLoader
for (int i = offset; i < offset + numberOfRows; i++)
{
RowUpdateBuilder builder = new RowUpdateBuilder(cfm, FBUtilities.timestampMicros(), ByteBufferUtil.bytes("key"+i));
- builder.clustering(ByteBufferUtil.bytes("col"+ i)).add("val", ByteBufferUtil.bytes("val" + i));
+ if (cfm.clusteringColumns() != null && !cfm.clusteringColumns().isEmpty())
+ builder.clustering(ByteBufferUtil.bytes("col"+ i)).add("val", ByteBufferUtil.bytes("val" + i));
+ else
+ builder.add("val", ByteBufferUtil.bytes("val"+i));
builder.build().apply();
}
}
+
public static void cleanupSavedCaches()
{
File cachesDir = new File(DatabaseDescriptor.getSavedCachesLocation());
http://git-wip-us.apache.org/repos/asf/cassandra/blob/ec604875/test/unit/org/apache/cassandra/db/RowCacheTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/db/RowCacheTest.java b/test/unit/org/apache/cassandra/db/RowCacheTest.java
index 267e5e4..cff574a 100644
--- a/test/unit/org/apache/cassandra/db/RowCacheTest.java
+++ b/test/unit/org/apache/cassandra/db/RowCacheTest.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.Util;
import org.apache.cassandra.cache.RowCacheKey;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
+import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.filter.ColumnFilter;
@@ -58,6 +59,7 @@ public class RowCacheTest
private static final String KEYSPACE_CACHED = "RowCacheTest";
private static final String CF_CACHED = "CachedCF";
private static final String CF_CACHEDINT = "CachedIntCF";
+ private static final String CF_CACHEDNOCLUSTER = "CachedNoClustering";
@BeforeClass
public static void defineSchema() throws ConfigurationException
@@ -65,6 +67,8 @@ public class RowCacheTest
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace(KEYSPACE_CACHED,
KeyspaceParams.simple(1),
+ SchemaLoader.standardCFMD(KEYSPACE_CACHED, CF_CACHEDNOCLUSTER, 1, AsciiType.instance, AsciiType.instance, null)
+ .caching(new CachingParams(true, 100)),
SchemaLoader.standardCFMD(KEYSPACE_CACHED, CF_CACHED).caching(CachingParams.CACHE_EVERYTHING),
SchemaLoader.standardCFMD(KEYSPACE_CACHED, CF_CACHEDINT, 1, IntegerType.instance)
.caching(new CachingParams(true, 100)));
@@ -206,6 +210,74 @@ public class RowCacheTest
}
@Test
+ public void testRowCacheNoClustering() throws Exception
+ {
+ CompactionManager.instance.disableAutoCompaction();
+
+ Keyspace keyspace = Keyspace.open(KEYSPACE_CACHED);
+ ColumnFamilyStore cachedStore = keyspace.getColumnFamilyStore(CF_CACHEDNOCLUSTER);
+
+ // empty the row cache
+ CacheService.instance.invalidateRowCache();
+
+ // set global row cache size to 1 MB
+ CacheService.instance.setRowCacheCapacityInMB(1);
+
+ // inserting 100 rows into column family
+ SchemaLoader.insertData(KEYSPACE_CACHED, CF_CACHEDNOCLUSTER, 0, 100);
+
+ // now reading rows one by one and checking if row cache grows
+ for (int i = 0; i < 100; i++)
+ {
+ DecoratedKey key = Util.dk("key" + i);
+
+ Util.getAll(Util.cmd(cachedStore, key).build());
+
+ assertEquals(CacheService.instance.rowCache.size(), i + 1);
+ assert(cachedStore.containsCachedParition(key)); // current key should be stored in the cache
+ }
+
+ // insert 10 more keys
+ SchemaLoader.insertData(KEYSPACE_CACHED, CF_CACHEDNOCLUSTER, 100, 10);
+
+ for (int i = 100; i < 110; i++)
+ {
+ DecoratedKey key = Util.dk("key" + i);
+
+ Util.getAll(Util.cmd(cachedStore, key).build());
+ assert cachedStore.containsCachedParition(key); // cache should be populated with the latest rows read (old ones should be popped)
+
+ // checking if cell is read correctly after cache
+ CachedPartition cp = cachedStore.getRawCachedPartition(key);
+ try (UnfilteredRowIterator ai = cp.unfilteredIterator(ColumnFilter.selection(cp.columns()), Slices.ALL, false))
+ {
+ assert ai.hasNext();
+ Row r = (Row)ai.next();
+ assertFalse(ai.hasNext());
+
+ Iterator<Cell> ci = r.cells().iterator();
+ assert(ci.hasNext());
+ Cell cell = ci.next();
+
+ assert cell.column().name.bytes.equals(ByteBufferUtil.bytes("val"));
+ assert cell.value().equals(ByteBufferUtil.bytes("val" + i));
+ }
+ }
+
+ // clear 100 rows from the cache
+ int keysLeft = 109;
+ for (int i = 109; i >= 10; i--)
+ {
+ cachedStore.invalidateCachedPartition(Util.dk("key" + i));
+ assert CacheService.instance.rowCache.size() == keysLeft;
+ keysLeft--;
+ }
+
+ CacheService.instance.setRowCacheCapacityInMB(0);
+
+ }
+
+ @Test
public void testRowCacheLoad() throws Exception
{
CacheService.instance.setRowCacheCapacityInMB(1);
[2/4] cassandra git commit: Establish consistent distinction between
non-existing partition and NULL value for LWTs on static columns
Posted by sl...@apache.org.
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/trunk
Commit: ead27d9e664726da7695fa11ea7e022c11ee7590
Parents: ec60487
Author: Alex Petrov <ol...@gmail.com>
Authored: Wed Aug 10 13:15:30 2016 +0200
Committer: Sylvain Lebresne <sy...@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"
[4/4] cassandra git commit: Merge branch 'cassandra-3.0' into trunk
Posted by sl...@apache.org.
Merge branch 'cassandra-3.0' into trunk
* cassandra-3.0:
Establish consistent distinction between non-existing partition and NULL value for LWTs on static columns
If CF has no clustering columns, any row cache is full partition cache (CASSANDRA-12499)
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/5f0a77b6
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/5f0a77b6
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/5f0a77b6
Branch: refs/heads/trunk
Commit: 5f0a77b6d3a303c14c29892f617e7e261bc1de3a
Parents: eace9aa ead27d9
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Wed Sep 21 16:28:58 2016 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Wed Sep 21 16:28:58 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/5f0a77b6/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index 15fb000,abffd80..f49171e
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,72 -1,15 +1,73 @@@
-3.0.10
- * Establish consistent distinction between non-existing partition and NULL value for LWTs on static columns (CASSANDRA-12060)
+3.10
+ * Add a blocking task to Index, run before joining the ring (CASSANDRA-12039)
+ * Fix NPE when using CQLSSTableWriter (CASSANDRA-12667)
+ * Support optional backpressure strategies at the coordinator (CASSANDRA-9318)
+ * Make randompartitioner work with new vnode allocation (CASSANDRA-12647)
+ * Fix cassandra-stress graphing (CASSANDRA-12237)
+ * Allow filtering on partition key columns for queries without secondary indexes (CASSANDRA-11031)
+ * Fix Cassandra Stress reporting thread model and precision (CASSANDRA-12585)
+ * Add JMH benchmarks.jar (CASSANDRA-12586)
+ * Add row offset support to SASI (CASSANDRA-11990)
+ * Cleanup uses of AlterTableStatementColumn (CASSANDRA-12567)
+ * Add keep-alive to streaming (CASSANDRA-11841)
+ * Tracing payload is passed through newSession(..) (CASSANDRA-11706)
+ * avoid deleting non existing sstable files and improve related log messages (CASSANDRA-12261)
+ * json/yaml output format for nodetool compactionhistory (CASSANDRA-12486)
+ * Retry all internode messages once after a connection is
+ closed and reopened (CASSANDRA-12192)
+ * Add support to rebuild from targeted replica (CASSANDRA-9875)
+ * Add sequence distribution type to cassandra stress (CASSANDRA-12490)
+ * "SELECT * FROM foo LIMIT ;" does not error out (CASSANDRA-12154)
+ * Define executeLocally() at the ReadQuery Level (CASSANDRA-12474)
+ * Extend read/write failure messages with a map of replica addresses
+ to error codes in the v5 native protocol (CASSANDRA-12311)
+ * Fix rebuild of SASI indexes with existing index files (CASSANDRA-12374)
+ * Let DatabaseDescriptor not implicitly startup services (CASSANDRA-9054, 12550)
+ * Fix clustering indexes in presence of static columns in SASI (CASSANDRA-12378)
+ * Fix queries on columns with reversed type on SASI indexes (CASSANDRA-12223)
+ * Added slow query log (CASSANDRA-12403)
+ * Count full coordinated request against timeout (CASSANDRA-12256)
+ * Allow TTL with null value on insert and update (CASSANDRA-12216)
+ * Make decommission operation resumable (CASSANDRA-12008)
+ * Add support to one-way targeted repair (CASSANDRA-9876)
+ * Remove clientutil jar (CASSANDRA-11635)
+ * Fix compaction throughput throttle (CASSANDRA-12366)
+ * Delay releasing Memtable memory on flush until PostFlush has finished running (CASSANDRA-12358)
+ * Cassandra stress should dump all setting on startup (CASSANDRA-11914)
+ * Make it possible to compact a given token range (CASSANDRA-10643)
+ * Allow updating DynamicEndpointSnitch properties via JMX (CASSANDRA-12179)
+ * Collect metrics on queries by consistency level (CASSANDRA-7384)
+ * Add support for GROUP BY to SELECT statement (CASSANDRA-10707)
+ * Deprecate memtable_cleanup_threshold and update default for memtable_flush_writers (CASSANDRA-12228)
+ * Upgrade to OHC 0.4.4 (CASSANDRA-12133)
+ * Add version command to cassandra-stress (CASSANDRA-12258)
+ * Create compaction-stress tool (CASSANDRA-11844)
+ * Garbage-collecting compaction operation and schema option (CASSANDRA-7019)
+ * Add beta protocol flag for v5 native protocol (CASSANDRA-12142)
+ * Support filtering on non-PRIMARY KEY columns in the CREATE
+ MATERIALIZED VIEW statement's WHERE clause (CASSANDRA-10368)
+ * Unify STDOUT and SYSTEMLOG logback format (CASSANDRA-12004)
+ * COPY FROM should raise error for non-existing input files (CASSANDRA-12174)
+ * Faster write path (CASSANDRA-12269)
+ * Option to leave omitted columns in INSERT JSON unset (CASSANDRA-11424)
+ * Support json/yaml output in nodetool tpstats (CASSANDRA-12035)
+ * Expose metrics for successful/failed authentication attempts (CASSANDRA-10635)
+ * Prepend snapshot name with "truncated" or "dropped" when a snapshot
+ is taken before truncating or dropping a table (CASSANDRA-12178)
+ * Optimize RestrictionSet (CASSANDRA-12153)
+ * cqlsh does not automatically downgrade CQL version (CASSANDRA-12150)
+ * Omit (de)serialization of state variable in UDAs (CASSANDRA-9613)
+ * Create a system table to expose prepared statements (CASSANDRA-8831)
+ * Reuse DataOutputBuffer from ColumnIndex (CASSANDRA-11970)
+ * Remove DatabaseDescriptor dependency from SegmentedFile (CASSANDRA-11580)
+ * Add supplied username to authentication error messages (CASSANDRA-12076)
+ * Remove pre-startup check for open JMX port (CASSANDRA-12074)
+ * Remove compaction Severity from DynamicEndpointSnitch (CASSANDRA-11738)
+ * Restore resumable hints delivery (CASSANDRA-11960)
+Merged from 3.0:
* 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)
-Merged from 2.1:
- * Add system property to set the max number of native transport requests in queue (CASSANDRA-11363)
-
-
-3.0.9
++ * Establish consistent distinction between non-existing partition and NULL value for LWTs on static columns (CASSANDRA-12060)
* Handle composite prefixes with final EOC=0 as in 2.x and refactor LegacyLayout.decodeBound (CASSANDRA-12423)
- * Fix paging for 2.x to 3.x upgrades (CASSANDRA-11195)
* select_distinct_with_deletions_test failing on non-vnode environments (CASSANDRA-11126)
* Stack Overflow returned to queries while upgrading (CASSANDRA-12527)
* Fix legacy regex for temporary files from 2.2 (CASSANDRA-12565)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/5f0a77b6/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/5f0a77b6/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
----------------------------------------------------------------------
[3/4] cassandra git commit: Establish consistent distinction between
non-existing partition and NULL value for LWTs on static columns
Posted by sl...@apache.org.
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 <ol...@gmail.com>
Authored: Wed Aug 10 13:15:30 2016 +0200
Committer: Sylvain Lebresne <sy...@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"