CONTAINS and CONTAINS KEY support for Lightweight Transactions

patch by ROCHETEAU Antoine; reviewed by Benjamin Lerer, Berenguer Blasi for CASSANDRA-10537
This commit is contained in:
ROCHETEAU Antoine 2022-01-06 13:46:50 +01:00 committed by Bereng
parent 0d860ec662
commit d80bb323c9
10 changed files with 331 additions and 23 deletions

View File

@ -1,4 +1,5 @@
4.1
* Add support for CONTAINS and CONTAINS KEY in conditional UPDATE and DELETE statement (CASSANDRA-10537)
* Migrate advanced config parameters to the new Config types (CASSANDRA-17431)
* Make null to be meaning disabled and leave 0 as a valid value for permissions_update_interval, roles_update_interval, credentials_update_interval (CASSANDRA-17431)
* Fix typo in Config annotation (CASSANDRA-17431)

View File

@ -1386,6 +1386,7 @@ WHERE +
| `.' `='
::= +
| CONTAINS (KEY)? +
| IN +
| `[' `]' +
| `[' `]' IN +
@ -1498,6 +1499,7 @@ WHERE +
::= ( | `(' ( ( `,' )* )? `)')
::= ( | `!=') +
| CONTAINS (KEY)? +
| IN +
| `[' `]' ( | `!=') +
| `[' `]' IN +

View File

@ -956,7 +956,7 @@ syntax_rules += r'''
;
<conditions> ::= <condition> ( "AND" <condition> )*
;
<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=") <term>)
<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=" | "CONTAINS" ( "KEY" )? ) <term>)
| ("IN" "(" <term> ( "," <term> )* ")" )
;
<condition> ::= conditioncol=<cident>

View File

@ -381,7 +381,16 @@ class TestCqlshCompletion(CqlshCompletionCase):
choices=['EXISTS', '<quotedName>', '<identifier>'])
self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ",
choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>', '.'])
choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>', '.', 'CONTAINS'])
self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF lonelykey ",
choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 'CONTAINS'])
self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF lonelykey CONTAINS ",
choices=['false', 'true', '<pgStringLiteral>',
'-', '<float>', 'TOKEN', '<identifier>',
'<uuid>', '{', '[', 'NULL', '<quotedStringLiteral>',
'<blobLiteral>', '<wholenumber>', 'KEY'])
def test_complete_in_delete(self):
self.trycompletions('DELETE F', choices=['FROM', '<identifier>', '<quotedName>'])
@ -463,7 +472,13 @@ class TestCqlshCompletion(CqlshCompletionCase):
choices=['EXISTS', '<identifier>', '<quotedName>'])
self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE '
'TOKEN(a) >= TOKEN(0) IF b '),
choices=['>=', '!=', '<=', 'IN', '=', '<', '>'])
choices=['>=', '!=', '<=', 'IN', '=', '<', '>', 'CONTAINS'])
self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE '
'TOKEN(a) >= TOKEN(0) IF b CONTAINS '),
choices=['false', 'true', '<pgStringLiteral>',
'-', '<float>', 'TOKEN', '<identifier>',
'<uuid>', '{', '[', 'NULL','<quotedStringLiteral>',
'<blobLiteral>','<wholenumber>', 'KEY'])
self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE '
'TOKEN(a) >= TOKEN(0) IF b < 0 '),
choices=['AND', ';'])

View File

@ -1662,6 +1662,7 @@ columnCondition[List<Pair<ColumnIdentifier, ColumnCondition.Raw>> conditions]
// Note: we'll reject duplicates later
: key=cident
( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleCondition(t, op))); }
| op=containsOperator t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleCondition(t, op))); }
| K_IN
( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(values))); }
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.simpleInCondition(marker))); }

View File

@ -340,4 +340,22 @@ public enum Operator
{
return this == IN;
}
/**
* Checks if this operator is CONTAINS operator.
* @return {@code true} if this operator is a CONTAINS operator, {@code false} otherwise.
*/
public boolean isContains()
{
return this == CONTAINS;
}
/**
* Checks if this operator is CONTAINS KEY operator.
* @return {@code true} if this operator is a CONTAINS operator, {@code false} otherwise.
*/
public boolean isContainsKey()
{
return this == CONTAINS_KEY;
}
}

View File

@ -495,6 +495,9 @@ public abstract class ColumnCondition
if (value == null)
return !iter.hasNext();
if(operator.isContains() || operator.isContainsKey())
return containsAppliesTo(type, iter, value.get(ProtocolVersion.CURRENT), operator);
switch (type.kind)
{
case LIST:
@ -573,6 +576,39 @@ public abstract class ColumnCondition
}
}
private static boolean containsAppliesTo(CollectionType<?> type, Iterator<Cell<?>> iter, ByteBuffer value, Operator operator)
{
AbstractType<?> compareType;
switch (type.kind)
{
case LIST:
compareType = ((ListType<?>)type).getElementsType();
break;
case SET:
compareType = ((SetType<?>)type).getElementsType();
break;
case MAP:
compareType = operator.isContainsKey() ? ((MapType<?, ?>)type).getKeysType() : ((MapType<?, ?>)type).getValuesType();
break;
default:
throw new AssertionError();
}
boolean appliesToSetOrMapKeys = (type.kind == CollectionType.Kind.SET || type.kind == CollectionType.Kind.MAP && operator.isContainsKey());
return containsAppliesTo(compareType, iter, value, appliesToSetOrMapKeys);
}
private static boolean containsAppliesTo(AbstractType<?> type, Iterator<Cell<?>> iter, ByteBuffer value, Boolean appliesToSetOrMapKeys)
{
while(iter.hasNext())
{
// for lists and map values we use the cell value; for sets and map keys we use the cell name
ByteBuffer cellValue = appliesToSetOrMapKeys ? iter.next().path().get(0) : iter.next().buffer();
if(type.compare(cellValue, value) == 0)
return true;
}
return false;
}
/**
* A condition on a UDT field
*/
@ -819,12 +855,18 @@ public abstract class ColumnCondition
private Terms prepareTerms(String keyspace, ColumnSpecification receiver)
{
checkFalse(operator.isContainsKey() && !(receiver.type instanceof MapType), "Cannot use CONTAINS KEY on non-map column %s", receiver.name);
checkFalse(operator.isContains() && !(receiver.type.isCollection()), "Cannot use CONTAINS on non-collection column %s", receiver.name);
if (operator.isIN())
{
return inValues == null ? Terms.ofListMarker(inMarker.prepare(keyspace, receiver), receiver.type)
: Terms.of(prepareTerms(keyspace, receiver, inValues));
}
if (operator.isContains() || operator.isContainsKey())
receiver = ((CollectionType<?>) receiver.type).makeCollectionReceiver(receiver, operator.isContainsKey());
return Terms.of(value.prepare(keyspace, receiver));
}

View File

@ -135,6 +135,22 @@ public class ColumnConditionTest
return bound.appliesTo(newRow(definition, rowValue));
}
private static boolean conditionContainsApplies(List<ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
private static boolean conditionContainsApplies(Map<ByteBuffer, ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
private static boolean conditionApplies(SortedSet<ByteBuffer> rowValue, Operator op, SortedSet<ByteBuffer> conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", SetType.getInstance(Int32Type.instance, true));
@ -143,6 +159,14 @@ public class ColumnConditionTest
return bound.appliesTo(newRow(definition, rowValue));
}
private static boolean conditionContainsApplies(SortedSet<ByteBuffer> rowValue, Operator op, ByteBuffer conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", SetType.getInstance(Int32Type.instance, true));
ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Constants.Value(conditionValue)));
ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT);
return bound.appliesTo(newRow(definition, rowValue));
}
private static boolean conditionApplies(Map<ByteBuffer, ByteBuffer> rowValue, Operator op, Map<ByteBuffer, ByteBuffer> conditionValue)
{
ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
@ -327,6 +351,14 @@ public class ColumnConditionTest
assertTrue(conditionApplies(list(ONE), GTE, list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
assertFalse(conditionApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, list(ONE)));
assertTrue(conditionApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
//CONTAINS
assertTrue(conditionContainsApplies(list(ZERO, ONE, TWO), CONTAINS, ONE));
assertFalse(conditionContainsApplies(list(ZERO, ONE), CONTAINS, TWO));
assertFalse(conditionContainsApplies(list(ZERO, ONE, TWO), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertFalse(conditionContainsApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ONE));
assertTrue(conditionContainsApplies(list(ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
private static SortedSet<ByteBuffer> set(ByteBuffer... values)
@ -422,6 +454,14 @@ public class ColumnConditionTest
assertTrue(conditionApplies(set(ONE), GTE, set(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
assertFalse(conditionApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, set(ONE)));
assertTrue(conditionApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, set(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// CONTAINS
assertTrue(conditionContainsApplies(set(ZERO, ONE, TWO), CONTAINS, ONE));
assertFalse(conditionContainsApplies(set(ZERO, ONE), CONTAINS, TWO));
assertFalse(conditionContainsApplies(set(ZERO, ONE, TWO), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertFalse(conditionContainsApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ONE));
assertTrue(conditionContainsApplies(set(ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
// values should be a list of key, value, key, value, ...
@ -550,5 +590,26 @@ public class ColumnConditionTest
assertFalse(conditionApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, map(ONE, ONE)));
assertTrue(conditionApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), GTE, map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE)));
assertTrue(conditionApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), GTE, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
//CONTAINS
assertTrue(conditionContainsApplies(map(ZERO, ONE), CONTAINS, ONE));
assertFalse(conditionContainsApplies(map(ZERO, ONE), CONTAINS, ZERO));
assertFalse(conditionContainsApplies(map(ONE, ONE), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertTrue(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), CONTAINS, ONE));
assertFalse(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertFalse(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ONE));
assertTrue(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS, ByteBufferUtil.EMPTY_BYTE_BUFFER));
//CONTAINS KEY
assertTrue(conditionContainsApplies(map(ZERO, ONE), CONTAINS_KEY, ZERO));
assertFalse(conditionContainsApplies(map(ZERO, ONE), CONTAINS_KEY, ONE));
assertFalse(conditionContainsApplies(map(ONE, ONE), CONTAINS_KEY, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertFalse(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), CONTAINS_KEY, ONE));
assertTrue(conditionContainsApplies(map(ByteBufferUtil.EMPTY_BYTE_BUFFER, ONE), CONTAINS_KEY, ByteBufferUtil.EMPTY_BYTE_BUFFER));
assertTrue(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, ONE));
assertFalse(conditionContainsApplies(map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), CONTAINS_KEY, ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
}

View File

@ -154,8 +154,8 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
checkInvalidUDT("v >= null", v, InvalidRequestException.class);
checkInvalidUDT("v IN null", v, SyntaxException.class);
checkInvalidUDT("v IN 367", v, SyntaxException.class);
checkInvalidUDT("v CONTAINS KEY 123", v, SyntaxException.class);
checkInvalidUDT("v CONTAINS 'bar'", v, SyntaxException.class);
checkInvalidUDT("v CONTAINS KEY 123", v, InvalidRequestException.class);
checkInvalidUDT("v CONTAINS 'bar'", v, InvalidRequestException.class);
/////////////////// null suffix on stored udt ////////////////////
v = userType("a", 0, "b", null);
@ -433,21 +433,35 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
void checkAppliesUDT(String condition, Object value) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value), row(true));
assertRows(execute("SELECT * FROM %s"), row(0, value));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition), row(true));
assertEmpty(execute("SELECT * FROM %s"));
execute("INSERT INTO %s (k, v) VALUES (0, ?)", value);
}
void checkDoesNotApplyUDT(String condition, Object value) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value),
row(false, value));
assertRows(execute("SELECT * FROM %s"), row(0, value));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k = 0 IF " + condition),
row(false, value));
assertRows(execute("SELECT * FROM %s"), row(0, value));
}
void checkInvalidUDT(String condition, Object value, Class<? extends Throwable> expected) throws Throwable
{
// UPDATE statement
assertInvalidThrow(expected, "UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value);
assertRows(execute("SELECT * FROM %s"), row(0, value));
// DELETE statement
assertInvalidThrow(expected, "DELETE FROM %s WHERE k = 0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, value));
}
/**
@ -472,10 +486,12 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_applies_list("l < ['z']");
check_applies_list("l <= ['z']");
check_applies_list("l IN (null, ['foo', 'bar', 'foobar'], ['a'])");
check_applies_list("l CONTAINS 'bar'");
// multiple conditions
check_applies_list("l > ['aaa', 'bbb'] AND l > ['aaa']");
check_applies_list("l != null AND l IN (['foo', 'bar', 'foobar'])");
check_applies_list("l CONTAINS 'foo' AND l CONTAINS 'foobar'");
// should not apply
check_does_not_apply_list("l = ['baz']");
@ -486,10 +502,12 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_does_not_apply_list("l <= ['a']");
check_does_not_apply_list("l IN (['a'], null)");
check_does_not_apply_list("l IN ()");
check_does_not_apply_list("l CONTAINS 'baz'");
// multiple conditions
check_does_not_apply_list("l IN () AND l IN (['foo', 'bar', 'foobar'])");
check_does_not_apply_list("l > ['zzz'] AND l < ['zzz']");
check_does_not_apply_list("l CONTAINS 'bar' AND l CONTAINS 'baz'");
check_invalid_list("l = [null]", InvalidRequestException.class);
check_invalid_list("l < null", InvalidRequestException.class);
@ -498,30 +516,42 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_invalid_list("l >= null", InvalidRequestException.class);
check_invalid_list("l IN null", SyntaxException.class);
check_invalid_list("l IN 367", SyntaxException.class);
check_invalid_list("l CONTAINS KEY 123", SyntaxException.class);
// not supported yet
check_invalid_list("m CONTAINS 'bar'", SyntaxException.class);
check_invalid_list("l CONTAINS KEY 123", InvalidRequestException.class);
check_invalid_list("l CONTAINS null", InvalidRequestException.class);
}
}
void check_applies_list(String condition) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition), row(true));
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
assertEmpty(execute("SELECT * FROM %s"));
execute("INSERT INTO %s(k, l) VALUES (0, ['foo', 'bar', 'foobar'])");
}
void check_does_not_apply_list(String condition) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition),
row(false, list("foo", "bar", "foobar")));
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition),
row(false, list("foo", "bar", "foobar")));
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
}
void check_invalid_list(String condition, Class<? extends Throwable> expected) throws Throwable
{
// UPDATE statement
assertInvalidThrow(expected, "UPDATE %s SET l = ['foo', 'bar', 'foobar'] WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
// DELETE statement
assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
}
/**
@ -543,7 +573,8 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
"DELETE FROM %s WHERE k=0 IF l[?] = ?", null, "foobar");
assertInvalidMessage("Invalid negative list index -2",
"DELETE FROM %s WHERE k=0 IF l[?] = ?", -2, "foobar");
assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS ?", 0, "bar");
assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF l[?] CONTAINS KEY ?", 0, "bar");
assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, null), row(false, list("foo", "bar", "foobar")));
assertRows(execute("DELETE FROM %s WHERE k=0 IF l[?] = ?", 1, "foobar"), row(false, list("foo", "bar", "foobar")));
assertRows(execute("SELECT * FROM %s"), row(0, list("foo", "bar", "foobar")));
@ -631,10 +662,12 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_applies_set("s < {'z'}");
check_applies_set("s <= {'z'}");
check_applies_set("s IN (null, {'bar', 'foo'}, {'a'})");
check_applies_set("s CONTAINS 'foo'");
// multiple conditions
check_applies_set("s > {'a'} AND s < {'z'}");
check_applies_set("s IN (null, {'bar', 'foo'}, {'a'}) AND s IN ({'a'}, {'bar', 'foo'}, null)");
check_applies_set("s CONTAINS 'foo' AND s CONTAINS 'bar'");
// should not apply
check_does_not_apply_set("s = {'baz'}");
@ -646,6 +679,7 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_does_not_apply_set("s IN ({'a'}, null)");
check_does_not_apply_set("s IN ()");
check_does_not_apply_set("s != null AND s IN ()");
check_does_not_apply_set("s CONTAINS 'baz'");
check_invalid_set("s = {null}", InvalidRequestException.class);
check_invalid_set("s < null", InvalidRequestException.class);
@ -654,32 +688,43 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_invalid_set("s >= null", InvalidRequestException.class);
check_invalid_set("s IN null", SyntaxException.class);
check_invalid_set("s IN 367", SyntaxException.class);
check_invalid_set("s CONTAINS KEY 123", SyntaxException.class);
check_invalid_set("s CONTAINS null", InvalidRequestException.class);
check_invalid_set("s CONTAINS KEY 123", InvalidRequestException.class);
// element access is not allow for sets
check_invalid_set("s['foo'] = 'foobar'", InvalidRequestException.class);
// not supported yet
check_invalid_set("m CONTAINS 'bar'", SyntaxException.class);
}
}
void check_applies_set(String condition) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition), row(true));
assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
assertEmpty(execute("SELECT * FROM %s"));
execute("INSERT INTO %s (k, s) VALUES (0, {'bar', 'foo'})");
}
void check_does_not_apply_set(String condition) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition), row(false, set("bar", "foo")));
assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(false, set("bar", "foo")));
assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
}
void check_invalid_set(String condition, Class<? extends Throwable> expected) throws Throwable
{
// UPDATE statement
assertInvalidThrow(expected, "UPDATE %s SET s = {'bar', 'foo'} WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
// DELETE statement
assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, set("bar", "foo")));
}
/**
@ -704,10 +749,13 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_applies_map("m <= {'z': 'z'}");
check_applies_map("m != {'a': 'a'}");
check_applies_map("m IN (null, {'a': 'a'}, {'foo': 'bar'})");
check_applies_map("m CONTAINS 'bar'");
check_applies_map("m CONTAINS KEY 'foo'");
// multiple conditions
check_applies_map("m > {'a': 'a'} AND m < {'z': 'z'}");
check_applies_map("m != null AND m IN (null, {'a': 'a'}, {'foo': 'bar'})");
check_applies_map("m CONTAINS 'bar' AND m CONTAINS KEY 'foo'");
// should not apply
check_does_not_apply_map("m = {'a': 'a'}");
@ -719,18 +767,16 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
check_does_not_apply_map("m IN ({'a': 'a'}, null)");
check_does_not_apply_map("m IN ()");
check_does_not_apply_map("m = null AND m != null");
check_does_not_apply_map("m CONTAINS 'foo'");
check_does_not_apply_map("m CONTAINS KEY 'bar'");
check_invalid_map("m = {null: null}", InvalidRequestException.class);
check_invalid_map("m = {'a': null}", InvalidRequestException.class);
check_invalid_map("m = {null: 'a'}", InvalidRequestException.class);
check_invalid_map("m CONTAINS null", InvalidRequestException.class);
check_invalid_map("m CONTAINS KEY null", InvalidRequestException.class);
check_invalid_map("m < null", InvalidRequestException.class);
check_invalid_map("m IN null", SyntaxException.class);
// not supported yet
check_invalid_map("m CONTAINS 'bar'", SyntaxException.class);
check_invalid_map("m CONTAINS KEY 'foo'", SyntaxException.class);
check_invalid_map("m CONTAINS null", SyntaxException.class);
check_invalid_map("m CONTAINS KEY null", SyntaxException.class);
}
}
@ -750,6 +796,8 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
assertInvalidMessage("Invalid null value for map element access",
"DELETE FROM %s WHERE k=0 IF m[?] = ?", null, "foo");
assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS ?", "foo", "bar");
assertInvalidSyntax("DELETE FROM %s WHERE k=0 IF m[?] CONTAINS KEY ?", "foo", "bar");
assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", "foo"), row(false, map("foo", "bar")));
assertRows(execute("DELETE FROM %s WHERE k=0 IF m[?] = ?", "foo", null), row(false, map("foo", "bar")));
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
@ -769,17 +817,17 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
@Test
public void testFrozenWithNullValues() throws Throwable
{
createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", "frozen<list<text>>"));
createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<list<text>>)");
execute("INSERT INTO %s (k, m) VALUES (0, null)");
assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", list("test"), list("comparison")), row(false, null));
createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", "frozen<map<text,int>>"));
createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<map<text,int>>)");
execute("INSERT INTO %s (k, m) VALUES (0, null)");
assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", map("test", 3), map("comparison", 2)), row(false, null));
createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, m %s)", "frozen<set<text>>"));
createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<set<text>>)");
execute("INSERT INTO %s (k, m) VALUES (0, null)");
assertRows(execute("UPDATE %s SET m = ? WHERE k = 0 IF m = ?", set("test"), set("comparison")), row(false, null));
@ -838,20 +886,32 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
void check_applies_map(String condition) throws Throwable
{
// UPDATE statement
assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition), row(true));
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(true));
assertEmpty(execute("SELECT * FROM %s"));
execute("INSERT INTO %s (k, m) VALUES (0, {'foo' : 'bar'})");
}
void check_does_not_apply_map(String condition) throws Throwable
{
assertRows(execute("UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition), row(false, map("foo", "bar")));
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
// DELETE statement
assertRows(execute("DELETE FROM %s WHERE k=0 IF " + condition), row(false, map("foo", "bar")));
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
}
void check_invalid_map(String condition, Class<? extends Throwable> expected) throws Throwable
{
// UPDATE statement
assertInvalidThrow(expected, "UPDATE %s SET m = {'foo': 'bar'} WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
// DELETE statement
assertInvalidThrow(expected, "DELETE FROM %s WHERE k=0 IF " + condition);
assertRows(execute("SELECT * FROM %s"), row(0, map("foo", "bar")));
}
@Test
@ -918,4 +978,111 @@ public class InsertUpdateIfConditionCollectionsTest extends CQLTester
"UPDATE %s SET v = {a: 0, b: 'bc'} WHERE k = 0 IF v.a IN ?", unset());
}
}
@Test
public void testNonFrozenEmptyCollection() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<text>)");
execute("INSERT INTO %s (k, l) VALUES (0, null)");
// Does apply
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", (ByteBuffer) null),
row(true));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", list()),
row(true));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l != ?", list("bar")),
row(true));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l < ?", list("a")),
row(true));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l <= ?", list("a")),
row(true));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l IN (?, ?)", null, list("bar")),
row(true));
// Does not apply
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l = ?", list("bar")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l > ?", list("a")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l >= ?", list("a")),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", unset()),
row(false, null));
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET l = null WHERE k = 0 IF l CONTAINS ?", (ByteBuffer) null);
createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<text>)");
execute("INSERT INTO %s (k, s) VALUES (0, null)");
// Does apply
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", (ByteBuffer) null),
row(true));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", set()),
row(true));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s != ?", set("bar")),
row(true));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s < ?", set("a")),
row(true));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s <= ?", set("a")),
row(true));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s IN (?, ?)", null, set("bar")),
row(true));
// Does not apply
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s = ?", set("bar")),
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s > ?", set("a")),
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s >= ?", set("a")),
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", unset()),
row(false, null));
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET s = null WHERE k = 0 IF s CONTAINS ?", (ByteBuffer) null);
createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<text, text>) ");
execute("INSERT INTO %s (k, m) VALUES (0, null)");
// Does apply
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", (ByteBuffer) null),
row(true));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", map()),
row(true));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m != ?", map("foo","bar")),
row(true));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m < ?", map("a","a")),
row(true));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m <= ?", map("a","a")),
row(true));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m IN (?, ?)", null, map("foo","bar")),
row(true));
// Does not apply
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m = ?", map("foo","bar")),
row(false, null));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m > ?", map("a", "a")),
row(false, null));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m >= ?", map("a", "a")),
row(false, null));
assertRows(execute("UPDATE %s SET m = null WHERE k = 0 IF m CONTAINS ?", "bar"),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", unset()),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", "foo"),
row(false, null));
assertRows(execute("UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", unset()),
row(false, null));
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS\"",
"UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS ?", (ByteBuffer) null);
assertInvalidMessage("Invalid comparison with null for operator \"CONTAINS KEY\"",
"UPDATE %s SET m = {} WHERE k = 0 IF m CONTAINS KEY ?", (ByteBuffer) null);
}
}

View File

@ -194,6 +194,7 @@ public class InsertUpdateIfConditionTest extends CQLTester
"UPDATE %s SET v1 = 'A' WHERE k = 0 AND c IN () IF EXISTS");
assertInvalidMessage("IN on the clustering key columns is not supported with conditional updates",
"UPDATE %s SET v1 = 'A' WHERE k = 0 AND c IN (1, 2) IF EXISTS");
assertInvalidMessage("Cannot use CONTAINS on non-collection column v1", "UPDATE %s SET v1 = 'B' WHERE k = 0 IF v1 CONTAINS 'A'");
}
/**