Merge branch 'cassandra-3.0' into cassandra-3.X

* cassandra-3.0:
  Batch with multiple conditional updates for the same partition causes AssertionError
This commit is contained in:
Sylvain Lebresne 2016-11-07 14:10:42 +01:00
commit 2b45673131
3 changed files with 127 additions and 5 deletions

View File

@ -99,6 +99,7 @@
* Restore resumable hints delivery (CASSANDRA-11960)
* Properly report LWT contention (CASSANDRA-12626)
Merged from 3.0:
* Batch with multiple conditional updates for the same partition causes AssertionError (CASSANDRA-12867)
* Make AbstractReplicationStrategy extendable from outside its package (CASSANDRA-12788)
* Don't tell users to turn off consistent rangemovements during rebuild. (CASSANDRA-12296)
* Fix CommitLogTest.testDeleteIfNotDirty (CASSANDRA-12854)

View File

@ -93,12 +93,29 @@ public class CQL3CasRequest implements CASRequest
{
assert condition instanceof ExistCondition || condition instanceof NotExistCondition;
RowCondition previous = getConditionsForRow(clustering);
if (previous != null && !(previous.getClass().equals(condition.getClass())))
if (previous != null)
{
// 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");
if (previous.getClass().equals(condition.getClass()))
{
// We can get here if a BATCH has 2 different statements on the same row with the same "exist" condition.
// For instance (assuming 'k' is the full PK):
// BEGIN BATCH
// INSERT INTO t(k, v1) VALUES (0, 'foo') IF NOT EXISTS;
// INSERT INTO t(k, v2) VALUES (0, 'bar') IF NOT EXISTS;
// APPLY BATCH;
// Of course, those can be trivially rewritten by the user as a single INSERT statement, but we still don't
// want this to be a problem (see #12867 in particular), so we simply return (the condition itself has
// already be set).
assert hasExists; // We shouldn't have a previous condition unless hasExists has been set already.
return;
}
else
{
// 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);

View File

@ -1792,4 +1792,108 @@ public class InsertUpdateIfConditionTest extends CQLTester
assertRows(execute("SELECT * FROM %s WHERE a = 7"),
row(7, 7, null, null, 7));
}
/**
* Test for CASSANDRA-12060, using a table without clustering.
*/
@Test
public void testMultiExistConditionOnSameRowNoClustering() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, v1 text, v2 text)");
// Multiple inserts on the same row with not exist conditions
assertRows(execute("BEGIN BATCH "
+ "INSERT INTO %1$s (k, v1) values (0, 'foo') IF NOT EXISTS; "
+ "INSERT INTO %1$s (k, v2) values (0, 'bar') IF NOT EXISTS; "
+ "APPLY BATCH"),
row(true));
assertRows(execute("SELECT * FROM %s WHERE k = 0"), row(0, "foo", "bar"));
// Same, but both insert on the same column: doing so would almost surely be a user error, but that's the
// original case reported in #12867, so being thorough.
assertRows(execute("BEGIN BATCH "
+ "INSERT INTO %1$s (k, v1) values (1, 'foo') IF NOT EXISTS; "
+ "INSERT INTO %1$s (k, v1) values (1, 'bar') IF NOT EXISTS; "
+ "APPLY BATCH"),
row(true));
// As all statement gets the same timestamp, the biggest value ends up winning, so that's "foo"
assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, "foo", null));
// Multiple deletes on the same row with exists conditions (note that this is somewhat non-sensical, one of the
// delete is redundant, we're just checking it doesn't break something)
assertRows(execute("BEGIN BATCH "
+ "DELETE FROM %1$s WHERE k = 0 IF EXISTS; "
+ "DELETE FROM %1$s WHERE k = 0 IF EXISTS; "
+ "APPLY BATCH"),
row(true));
assertEmpty(execute("SELECT * FROM %s WHERE k = 0"));
// Validate we can't mix different type of conditions however
assertInvalidMessage("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row",
"BEGIN BATCH "
+ "INSERT INTO %1$s (k, v1) values (1, 'foo') IF NOT EXISTS; "
+ "DELETE FROM %1$s WHERE k = 1 IF EXISTS; "
+ "APPLY BATCH");
assertInvalidMessage("Cannot mix IF conditions and IF NOT EXISTS for the same row",
"BEGIN BATCH "
+ "INSERT INTO %1$s (k, v1) values (1, 'foo') IF NOT EXISTS; "
+ "UPDATE %1$s SET v2 = 'bar' WHERE k = 1 IF v1 = 'foo'; "
+ "APPLY BATCH");
}
/**
* Test for CASSANDRA-12060, using a table with clustering.
*/
@Test
public void testMultiExistConditionOnSameRowClustering() throws Throwable
{
createTable("CREATE TABLE %s (k int, t int, v1 text, v2 text, PRIMARY KEY (k, t))");
// Multiple inserts on the same row with not exist conditions
assertRows(execute("BEGIN BATCH "
+ "INSERT INTO %1$s (k, t, v1) values (0, 0, 'foo') IF NOT EXISTS; "
+ "INSERT INTO %1$s (k, t, v2) values (0, 0, 'bar') IF NOT EXISTS; "
+ "APPLY BATCH"),
row(true));
assertRows(execute("SELECT * FROM %s WHERE k = 0"), row(0, 0, "foo", "bar"));
// Same, but both insert on the same column: doing so would almost surely be a user error, but that's the
// original case reported in #12867, so being thorough.
assertRows(execute("BEGIN BATCH "
+ "INSERT INTO %1$s (k, t, v1) values (1, 0, 'foo') IF NOT EXISTS; "
+ "INSERT INTO %1$s (k, t, v1) values (1, 0, 'bar') IF NOT EXISTS; "
+ "APPLY BATCH"),
row(true));
// As all statement gets the same timestamp, the biggest value ends up winning, so that's "foo"
assertRows(execute("SELECT * FROM %s WHERE k = 1"), row(1, 0, "foo", null));
// Multiple deletes on the same row with exists conditions (note that this is somewhat non-sensical, one of the
// delete is redundant, we're just checking it doesn't break something)
assertRows(execute("BEGIN BATCH "
+ "DELETE FROM %1$s WHERE k = 0 AND t = 0 IF EXISTS; "
+ "DELETE FROM %1$s WHERE k = 0 AND t = 0 IF EXISTS; "
+ "APPLY BATCH"),
row(true));
assertEmpty(execute("SELECT * FROM %s WHERE k = 0"));
// Validate we can't mix different type of conditions however
assertInvalidMessage("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row",
"BEGIN BATCH "
+ "INSERT INTO %1$s (k, t, v1) values (1, 0, 'foo') IF NOT EXISTS; "
+ "DELETE FROM %1$s WHERE k = 1 AND t = 0 IF EXISTS; "
+ "APPLY BATCH");
assertInvalidMessage("Cannot mix IF conditions and IF NOT EXISTS for the same row",
"BEGIN BATCH "
+ "INSERT INTO %1$s (k, t, v1) values (1, 0, 'foo') IF NOT EXISTS; "
+ "UPDATE %1$s SET v2 = 'bar' WHERE k = 1 AND t = 0 IF v1 = 'foo'; "
+ "APPLY BATCH");
}
}