mirror of https://github.com/apache/cassandra
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:
parent
0d860ec662
commit
d80bb323c9
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1386,6 +1386,7 @@ WHERE +
|
|||
| `.' `='
|
||||
|
||||
::= +
|
||||
| CONTAINS (KEY)? +
|
||||
| IN +
|
||||
| `[' `]' +
|
||||
| `[' `]' IN +
|
||||
|
|
@ -1498,6 +1499,7 @@ WHERE +
|
|||
::= ( | `(' ( ( `,' )* )? `)')
|
||||
|
||||
::= ( | `!=') +
|
||||
| CONTAINS (KEY)? +
|
||||
| IN +
|
||||
| `[' `]' ( | `!=') +
|
||||
| `[' `]' IN +
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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', ';'])
|
||||
|
|
|
|||
|
|
@ -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))); }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue