This is an automated email from the ASF dual-hosted git repository.
bereng pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new d80bb323c9 CONTAINS and CONTAINS KEY support for Lightweight
Transactions
d80bb323c9 is described below
commit d80bb323c9dd8f814fe02e16dec510557c7d5101
Author: ROCHETEAU Antoine <[email protected]>
AuthorDate: Thu Jan 6 13:46:50 2022 +0100
CONTAINS and CONTAINS KEY support for Lightweight Transactions
patch by ROCHETEAU Antoine; reviewed by Benjamin Lerer, Berenguer Blasi for
CASSANDRA-10537
---
CHANGES.txt | 1 +
.../cassandra/pages/cql/cql_singlefile.adoc | 2 +
pylib/cqlshlib/cql3handling.py | 2 +-
pylib/cqlshlib/test/test_cqlsh_completion.py | 19 +-
src/antlr/Parser.g | 1 +
src/java/org/apache/cassandra/cql3/Operator.java | 18 ++
.../cassandra/cql3/conditions/ColumnCondition.java | 42 +++++
.../cql3/conditions/ColumnConditionTest.java | 61 ++++++
.../InsertUpdateIfConditionCollectionsTest.java | 207 +++++++++++++++++++--
.../operations/InsertUpdateIfConditionTest.java | 1 +
10 files changed, 331 insertions(+), 23 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 414164181d..0f60d18244 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -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)
diff --git a/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
b/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
index 56541e0231..4fe8c10a65 100644
--- a/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
+++ b/doc/modules/cassandra/pages/cql/cql_singlefile.adoc
@@ -1386,6 +1386,7 @@ WHERE +
| `.' `='
::= +
+| CONTAINS (KEY)? +
| IN +
| `[' `]' +
| `[' `]' IN +
@@ -1498,6 +1499,7 @@ WHERE +
::= ( | `(' ( ( `,' )* )? `)')
::= ( | `!=') +
+| CONTAINS (KEY)? +
| IN +
| `[' `]' ( | `!=') +
| `[' `]' IN +
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 4025a3767d..0e50efbf23 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -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>
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py
b/pylib/cqlshlib/test/test_cqlsh_completion.py
index 696028a283..06f7664712 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -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', ';'])
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index 27a01f3d3e..dbb2682617 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -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))); }
diff --git a/src/java/org/apache/cassandra/cql3/Operator.java
b/src/java/org/apache/cassandra/cql3/Operator.java
index 992056ceb9..bcb5f63be6 100644
--- a/src/java/org/apache/cassandra/cql3/Operator.java
+++ b/src/java/org/apache/cassandra/cql3/Operator.java
@@ -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;
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
index d27fd3fd92..e3f463a255 100644
--- a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java
@@ -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));
}
diff --git
a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
index 7a7e4b8d16..a896125430 100644
--- a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
+++ b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java
@@ -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));
+
}
}
diff --git
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
index 7ad41e3084..c0a5139b5c 100644
---
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
+++
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionCollectionsTest.java
@@ -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);
+ }
+
}
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 ac03cb5e14..627075f9de 100644
---
a/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
+++
b/test/unit/org/apache/cassandra/cql3/validation/operations/InsertUpdateIfConditionTest.java
@@ -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'");
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]