Distinguish between null and unset in the native protocol (v4)

patch by odpeer; reviewed by blerer for CASSANDRA-7304


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/48f64468
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/48f64468
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/48f64468

Branch: refs/heads/trunk
Commit: 48f644686b48357354f16c74b02b6d2c450a8c2d
Parents: bf51f24
Author: Oded Peer <peer.o...@gmail.com>
Authored: Mon Apr 20 15:28:09 2015 +0200
Committer: Sylvain Lebresne <sylv...@datastax.com>
Committed: Mon Apr 20 15:30:19 2015 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |  12 ++
 doc/native_protocol_v4.spec                     |   9 +-
 .../org/apache/cassandra/cql3/Attributes.java   |   7 ++
 .../apache/cassandra/cql3/ColumnCondition.java  |  12 +-
 .../org/apache/cassandra/cql3/Constants.java    |  19 +++-
 src/java/org/apache/cassandra/cql3/Lists.java   |  43 ++++---
 src/java/org/apache/cassandra/cql3/Maps.java    |  43 +++++--
 .../org/apache/cassandra/cql3/QueryOptions.java |   4 +-
 .../apache/cassandra/cql3/QueryProcessor.java   |   3 +
 src/java/org/apache/cassandra/cql3/Sets.java    |  38 ++++---
 src/java/org/apache/cassandra/cql3/Tuples.java  |   8 ++
 .../org/apache/cassandra/cql3/UserTypes.java    |   4 +
 .../cassandra/cql3/functions/FunctionCall.java  |   7 +-
 .../cql3/restrictions/AbstractRestriction.java  |   2 +
 .../restrictions/SingleColumnRestriction.java   |  20 +++-
 .../cql3/statements/ModificationStatement.java  |   8 +-
 .../cql3/statements/RequestValidations.java     |  17 +++
 .../cql3/statements/SelectStatement.java        |   5 +-
 .../db/composites/CompositesBuilder.java        |  23 +++-
 .../apache/cassandra/db/marshal/UserType.java   |   5 +
 .../org/apache/cassandra/transport/CBUtil.java  |  27 ++++-
 .../transport/messages/BatchMessage.java        |   2 +-
 .../transport/messages/ExecuteMessage.java      |   2 +-
 .../apache/cassandra/utils/ByteBufferUtil.java  |   3 +
 .../org/apache/cassandra/cql3/CQLTester.java    |  11 ++
 .../apache/cassandra/cql3/CollectionsTest.java  | 100 +++++++++++++++++
 .../cassandra/cql3/ColumnConditionTest.java     |   7 ++
 .../cassandra/cql3/ContainsRelationTest.java    |  12 ++
 .../apache/cassandra/cql3/ModificationTest.java | 112 +++++++++++++++++++
 .../cassandra/cql3/MultiColumnRelationTest.java |  22 ++++
 .../cql3/SingleColumnRelationTest.java          |  47 +++++++-
 .../apache/cassandra/cql3/TupleTypeTest.java    |  13 +++
 .../apache/cassandra/cql3/UserTypesTest.java    |  15 +++
 34 files changed, 594 insertions(+), 69 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 3dbd42f..d1d6dea 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0
+ * Distinguish between null and unset in protocol v4 (CASSANDRA-7304)
  * Add user/role permissions for user-defined functions (CASSANDRA-7557)
  * Allow cassandra config to be updated to restart daemon without unloading 
classes (CASSANDRA-9046)
  * Don't initialize compaction writer before checking if iter is empty 
(CASSANDRA-9117)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/NEWS.txt
----------------------------------------------------------------------
diff --git a/NEWS.txt b/NEWS.txt
index 7db07f0..03008de 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -58,6 +58,18 @@ New features
    - The node now keeps up when streaming is failed during bootstrapping. You 
can
      use new `nodetool bootstrap resume` command to continue streaming after 
resolving
      an issue.
+   - Protocol version 4 specifies that bind variables do not require having a
+     value when executing a statement. Bind variables without a value are
+     called 'unset'. The 'unset' bind variable is serialized as the int
+     value '-2' without following bytes.
+     In an EXECUTE or BATCH request an unset bind value does not modify the 
value and
+     does not create a tombstone, an unset bind ttl is treated as 'unlimited',
+     an unset bind timestamp is treated as 'now', an unset bind counter 
operation
+     does not change the counter value.
+     Unset tuple field, UDT field and map key are not allowed.
+     In a QUERY request an unset limit is treated as 'unlimited'.
+     Unset WHERE clauses with unset partition column, clustering column
+     or index column are not allowed.
 
 
 Upgrading

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/doc/native_protocol_v4.spec
----------------------------------------------------------------------
diff --git a/doc/native_protocol_v4.spec b/doc/native_protocol_v4.spec
index ac57749..99483e3 100644
--- a/doc/native_protocol_v4.spec
+++ b/doc/native_protocol_v4.spec
@@ -206,6 +206,11 @@ Table of Contents
     [string list]  A [short] n, followed by n [string].
     [bytes]        A [int] n, followed by n bytes if n >= 0. If n < 0,
                    no byte should follow and the value represented is `null`.
+    [value]        A [int] n, followed by n bytes if n >= 0.
+                   If n == -1 no byte should follow and the value represented 
is `null`.
+                   If n == -2 no byte should follow and the value represented 
is
+                   `not set` not resulting in any change to the existing value.
+                   n < -2 is an invalid value and results in an error.
     [short bytes]  A [short] n, followed by n bytes if n >= 0.
 
     [option]       A pair of <id><value> where <id> is a [short] representing
@@ -306,7 +311,7 @@ Table of Contents
       in particular influence what the remainder of the message contains.
       A flag is set if the bit corresponding to its `mask` is set. Supported
       flags are, given there mask:
-        0x01: Values. In that case, a [short] <n> followed by <n> [bytes]
+        0x01: Values. In that case, a [short] <n> followed by <n> [value]
               values are provided. Those value are used for bound variables in
               the query. Optionally, if the 0x40 flag is present, each value
               will be preceded by a [string] name, representing the name of
@@ -418,7 +423,7 @@ Table of Contents
        - <n> is a [short] indicating the number (possibly 0) of following 
values.
        - <name_i> is the optional name of the following <value_i>. It must be 
present
          if and only if the 0x40 flag is provided for the batch.
-       - <value_i> is the [bytes] to use for bound variable i (of bound 
variable <name_i>
+       - <value_i> is the [value] to use for bound variable i (of bound 
variable <name_i>
          if the 0x40 flag is used).
     - <consistency> is the [consistency] level for the operation.
     - <serial_consistency> is only present if the 0x10 flag is set. In that 
case,

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Attributes.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Attributes.java 
b/src/java/org/apache/cassandra/cql3/Attributes.java
index 4136ec5..f3d298b 100644
--- a/src/java/org/apache/cassandra/cql3/Attributes.java
+++ b/src/java/org/apache/cassandra/cql3/Attributes.java
@@ -28,6 +28,7 @@ import org.apache.cassandra.db.marshal.Int32Type;
 import org.apache.cassandra.db.marshal.LongType;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.serializers.MarshalException;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Utility class for the Parser to gather attributes for modification
@@ -86,6 +87,9 @@ public class Attributes
         if (tval == null)
             throw new InvalidRequestException("Invalid null value of 
timestamp");
 
+        if (tval == ByteBufferUtil.UNSET_BYTE_BUFFER)
+            return now;
+
         try
         {
             LongType.instance.validate(tval);
@@ -107,6 +111,9 @@ public class Attributes
         if (tval == null)
             throw new InvalidRequestException("Invalid null value of TTL");
 
+        if (tval == ByteBufferUtil.UNSET_BYTE_BUFFER) // treat as unlimited
+            return 0;
+
         try
         {
             Int32Type.instance.validate(tval);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/ColumnCondition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java 
b/src/java/org/apache/cassandra/cql3/ColumnCondition.java
index 921a073..acebfe7 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java
@@ -189,6 +189,8 @@ public class ColumnCondition
         /** Returns true if the operator is satisfied (i.e. "value operator 
otherValue == true"), false otherwise. */
         protected boolean compareWithOperator(Operator operator, 
AbstractType<?> type, ByteBuffer value, ByteBuffer otherValue) throws 
InvalidRequestException
         {
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                throw new InvalidRequestException("Invalid 'unset' value in 
condition");
             if (value == null)
             {
                 switch (operator)
@@ -278,7 +280,7 @@ public class ColumnCondition
             assert !(column.type instanceof CollectionType) && 
condition.collectionElement == null;
             assert condition.operator == Operator.IN;
             if (condition.inValues == null)
-                this.inValues = ((Lists.Marker) 
condition.value).bind(options).getElements();
+                this.inValues = ((Lists.Value) 
condition.value.bind(options)).getElements();
             else
             {
                 this.inValues = new ArrayList<>(condition.inValues.size());
@@ -389,7 +391,7 @@ public class ColumnCondition
             this.collectionElement = 
condition.collectionElement.bindAndGet(options);
 
             if (condition.inValues == null)
-                this.inValues = ((Lists.Marker) 
condition.value).bind(options).getElements();
+                this.inValues = ((Lists.Value) 
condition.value.bind(options)).getElements();
             else
             {
                 this.inValues = new ArrayList<>(condition.inValues.size());
@@ -656,7 +658,7 @@ public class ColumnCondition
                 if (column.type instanceof ListType)
                 {
                     ListType deserializer = 
ListType.getInstance(collectionType.valueComparator(), false);
-                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    for (ByteBuffer buffer : 
((Lists.Value)inValuesMarker.bind(options)).elements)
                     {
                         if (buffer == null)
                             this.inValues.add(null);
@@ -667,7 +669,7 @@ public class ColumnCondition
                 else if (column.type instanceof MapType)
                 {
                     MapType deserializer = 
MapType.getInstance(collectionType.nameComparator(), 
collectionType.valueComparator(), false);
-                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    for (ByteBuffer buffer : 
((Lists.Value)inValuesMarker.bind(options)).elements)
                     {
                         if (buffer == null)
                             this.inValues.add(null);
@@ -678,7 +680,7 @@ public class ColumnCondition
                 else if (column.type instanceof SetType)
                 {
                     SetType deserializer = 
SetType.getInstance(collectionType.valueComparator(), false);
-                    for (ByteBuffer buffer : 
inValuesMarker.bind(options).elements)
+                    for (ByteBuffer buffer : 
((Lists.Value)inValuesMarker.bind(options)).elements)
                     {
                         if (buffer == null)
                             this.inValues.add(null);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Constants.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Constants.java 
b/src/java/org/apache/cassandra/cql3/Constants.java
index 5f48160..8619b24 100644
--- a/src/java/org/apache/cassandra/cql3/Constants.java
+++ b/src/java/org/apache/cassandra/cql3/Constants.java
@@ -21,7 +21,6 @@ import java.nio.ByteBuffer;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.composites.CellName;
@@ -47,6 +46,8 @@ public abstract class Constants
         STRING, INTEGER, UUID, FLOAT, DATE, TIME, BOOLEAN, HEX;
     }
 
+    public static final Value UNSET_VALUE = new 
Value(ByteBufferUtil.UNSET_BYTE_BUFFER);
+
     public static final Term.Raw NULL_LITERAL = new Term.Raw()
     {
         public Term prepare(String keyspace, ColumnSpecification receiver) 
throws InvalidRequestException
@@ -288,7 +289,7 @@ public abstract class Constants
             try
             {
                 ByteBuffer value = options.getValues().get(bindIndex);
-                if (value != null)
+                if (value != null && value != ByteBufferUtil.UNSET_BYTE_BUFFER)
                     receiver.type.validate(value);
                 return value;
             }
@@ -301,7 +302,11 @@ public abstract class Constants
         public Value bind(QueryOptions options) throws InvalidRequestException
         {
             ByteBuffer bytes = bindAndGet(options);
-            return bytes == null ? null : new Constants.Value(bytes);
+            if (bytes == null)
+                return null;
+            if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return Constants.UNSET_VALUE;
+            return new Constants.Value(bytes);
         }
     }
 
@@ -316,7 +321,8 @@ public abstract class Constants
         {
             CellName cname = cf.getComparator().create(prefix, column);
             ByteBuffer value = t.bindAndGet(params.options);
-            cf.addColumn(value == null ? params.makeTombstone(cname) : 
params.makeColumn(cname, value));
+            if (value != ByteBufferUtil.UNSET_BYTE_BUFFER) // use reference 
equality and not object equality
+                cf.addColumn(value == null ? params.makeTombstone(cname) : 
params.makeColumn(cname, value));
         }
     }
 
@@ -332,6 +338,9 @@ public abstract class Constants
             ByteBuffer bytes = t.bindAndGet(params.options);
             if (bytes == null)
                 throw new InvalidRequestException("Invalid null value for 
counter increment");
+            if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return;
+
             long increment = ByteBufferUtil.toLong(bytes);
             CellName cname = cf.getComparator().create(prefix, column);
             cf.addColumn(params.makeCounter(cname, increment));
@@ -350,6 +359,8 @@ public abstract class Constants
             ByteBuffer bytes = t.bindAndGet(params.options);
             if (bytes == null)
                 throw new InvalidRequestException("Invalid null value for 
counter increment");
+            if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return;
 
             long increment = ByteBufferUtil.toLong(bytes);
             if (increment == Long.MIN_VALUE)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Lists.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Lists.java 
b/src/java/org/apache/cassandra/cql3/Lists.java
index 8b8375d..d695d8c 100644
--- a/src/java/org/apache/cassandra/cql3/Lists.java
+++ b/src/java/org/apache/cassandra/cql3/Lists.java
@@ -17,6 +17,8 @@
  */
 package org.apache.cassandra.cql3;
 
+import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
@@ -135,8 +137,8 @@ public abstract class Lists
             {
                 // Collections have this small hack that validate cannot be 
called on a serialized object,
                 // but compose does the validation (so we're fine).
-                List<?> l = 
(List<?>)type.getSerializer().deserializeForNativeProtocol(value, version);
-                List<ByteBuffer> elements = new 
ArrayList<ByteBuffer>(l.size());
+                List<?> l = 
type.getSerializer().deserializeForNativeProtocol(value, version);
+                List<ByteBuffer> elements = new ArrayList<>(l.size());
                 for (Object element : l)
                     // elements can be null in lists that represent a set of 
IN values
                     elements.add(element == null ? null : 
type.getElementsType().decompose(element));
@@ -199,7 +201,7 @@ public abstract class Lists
         {
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             List<ByteBuffer> buffers = new 
ArrayList<ByteBuffer>(elements.size());
             for (Term t : elements)
@@ -208,6 +210,8 @@ public abstract class Lists
 
                 if (bytes == null)
                     throw new InvalidRequestException("null is not supported 
inside collections");
+                if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    return UNSET_VALUE;
 
                 // We don't support value > 64K because the serialization 
format encode the length as an unsigned short.
                 if (bytes.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
@@ -237,10 +241,14 @@ public abstract class Lists
             assert receiver.type instanceof ListType;
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             ByteBuffer value = options.getValues().get(bindIndex);
-            return value == null ? null : Value.fromSerialized(value, 
(ListType)receiver.type, options.getProtocolVersion());
+            if (value == null)
+                return null;
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return UNSET_VALUE;
+            return Value.fromSerialized(value, (ListType)receiver.type, 
options.getProtocolVersion());
         }
     }
 
@@ -256,7 +264,7 @@ public abstract class Lists
     {
         // Our reference time (1 jan 2010, 00:00:00) in milliseconds.
         private static final long REFERENCE_TIME = 1262304000000L;
-        private static final AtomicReference<PrecisionTime> last = new 
AtomicReference<PrecisionTime>(new PrecisionTime(Long.MAX_VALUE, 0));
+        private static final AtomicReference<PrecisionTime> last = new 
AtomicReference<>(new PrecisionTime(Long.MAX_VALUE, 0));
 
         public final long millis;
         public final int nanos;
@@ -293,13 +301,15 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
-            if (column.type.isMultiCell())
+            Term.Terminal value = t.bind(params.options);
+            if (column.type.isMultiCell() && value != UNSET_VALUE)
             {
                 // delete + append
                 CellName name = cf.getComparator().create(prefix, column);
                 cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
             }
-            Appender.doAppend(t, cf, prefix, column, params);
+            if (value != UNSET_VALUE)
+                Appender.doAppend(cf, prefix, column, params, value);
         }
     }
 
@@ -336,6 +346,8 @@ public abstract class Lists
 
             if (index == null)
                 throw new InvalidRequestException("Invalid null value for list 
index");
+            if (index == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                throw new InvalidRequestException("Invalid unset value for 
list index");
 
             List<Cell> existingList = params.getPrefetchedList(rowKey, 
column.name);
             int idx = ByteBufferUtil.toInt(index);
@@ -349,7 +361,7 @@ public abstract class Lists
             {
                 cf.addColumn(params.makeTombstone(elementName));
             }
-            else
+            else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER)
             {
                 // We don't support value > 64K because the serialization 
format encode the length as an unsigned short.
                 if (value.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
@@ -372,12 +384,13 @@ public abstract class Lists
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
             assert column.type.isMultiCell() : "Attempted to append to a 
frozen list";
-            doAppend(t, cf, prefix, column, params);
+            Term.Terminal value = t.bind(params.options);
+            if (value != UNSET_VALUE)
+                doAppend(cf, prefix, column, params, value);
         }
 
-        static void doAppend(Term t, ColumnFamily cf, Composite prefix, 
ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
+        static void doAppend(ColumnFamily cf, Composite prefix, 
ColumnDefinition column, UpdateParameters params, Term.Terminal value) throws 
InvalidRequestException
         {
-            Term.Terminal value = t.bind(params.options);
             if (column.type.isMultiCell())
             {
                 // If we append null, do nothing. Note that for Setter, we've
@@ -414,7 +427,7 @@ public abstract class Lists
         {
             assert column.type.isMultiCell() : "Attempted to prepend to a 
frozen list";
             Term.Terminal value = t.bind(params.options);
-            if (value == null)
+            if (value == null || value == UNSET_VALUE)
                 return;
 
             long time = PrecisionTime.REFERENCE_TIME - 
(System.currentTimeMillis() - PrecisionTime.REFERENCE_TIME);
@@ -454,7 +467,7 @@ public abstract class Lists
             if (existingList.isEmpty())
                 return;
 
-            if (value == null)
+            if (value == null || value == UNSET_VALUE)
                 return;
 
             // Note: below, we will call 'contains' on this toDiscard list for 
each element of existingList.
@@ -489,6 +502,8 @@ public abstract class Lists
             Term.Terminal index = t.bind(params.options);
             if (index == null)
                 throw new InvalidRequestException("Invalid null value for list 
index");
+            if (index == Constants.UNSET_VALUE)
+                return;
 
             List<Cell> existingList = params.getPrefetchedList(rowKey, 
column.name);
             int idx = 
ByteBufferUtil.toInt(index.get(params.options.getProtocolVersion()));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Maps.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Maps.java 
b/src/java/org/apache/cassandra/cql3/Maps.java
index 6c7cfa6..5bb3a48 100644
--- a/src/java/org/apache/cassandra/cql3/Maps.java
+++ b/src/java/org/apache/cassandra/cql3/Maps.java
@@ -17,6 +17,8 @@
  */
 package org.apache.cassandra.cql3;
 
+import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
+
 import java.nio.ByteBuffer;
 import java.util.*;
 
@@ -32,6 +34,7 @@ import 
org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.serializers.CollectionSerializer;
 import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.transport.Server;
+import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.Pair;
 
@@ -67,7 +70,7 @@ public abstract class Maps
 
             ColumnSpecification keySpec = Maps.keySpecOf(receiver);
             ColumnSpecification valueSpec = Maps.valueSpecOf(receiver);
-            Map<Term, Term> values = new HashMap<Term, Term>(entries.size());
+            Map<Term, Term> values = new HashMap<>(entries.size());
             boolean allTerminal = true;
             for (Pair<Term.Raw, Term.Raw> entry : entries)
             {
@@ -157,8 +160,8 @@ public abstract class Maps
             {
                 // Collections have this small hack that validate cannot be 
called on a serialized object,
                 // but compose does the validation (so we're fine).
-                Map<?, ?> m = (Map<?, 
?>)type.getSerializer().deserializeForNativeProtocol(value, version);
-                Map<ByteBuffer, ByteBuffer> map = new 
LinkedHashMap<ByteBuffer, ByteBuffer>(m.size());
+                Map<?, ?> m = 
type.getSerializer().deserializeForNativeProtocol(value, version);
+                Map<ByteBuffer, ByteBuffer> map = new 
LinkedHashMap<>(m.size());
                 for (Map.Entry<?, ?> entry : m.entrySet())
                     map.put(type.getKeysType().decompose(entry.getKey()), 
type.getValuesType().decompose(entry.getValue()));
                 return new Value(map);
@@ -222,7 +225,7 @@ public abstract class Maps
         {
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             Map<ByteBuffer, ByteBuffer> buffers = new TreeMap<ByteBuffer, 
ByteBuffer>(comparator);
             for (Map.Entry<Term, Term> entry : elements.entrySet())
@@ -231,6 +234,8 @@ public abstract class Maps
                 ByteBuffer keyBytes = entry.getKey().bindAndGet(options);
                 if (keyBytes == null)
                     throw new InvalidRequestException("null is not supported 
inside collections");
+                if (keyBytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    throw new InvalidRequestException("unset value is not 
supported for map keys");
                 if (keyBytes.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
                     throw new InvalidRequestException(String.format("Map key 
is too long. Map keys are limited to %d bytes but %d bytes keys provided",
                                                                     
FBUtilities.MAX_UNSIGNED_SHORT,
@@ -239,6 +244,9 @@ public abstract class Maps
                 ByteBuffer valueBytes = entry.getValue().bindAndGet(options);
                 if (valueBytes == null)
                     throw new InvalidRequestException("null is not supported 
inside collections");
+                if (valueBytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    return UNSET_VALUE;
+
                 if (valueBytes.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
                     throw new InvalidRequestException(String.format("Map value 
is too long. Map values are limited to %d bytes but %d bytes value provided",
                                                                     
FBUtilities.MAX_UNSIGNED_SHORT,
@@ -264,10 +272,14 @@ public abstract class Maps
             assert receiver.type instanceof MapType;
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             ByteBuffer value = options.getValues().get(bindIndex);
-            return value == null ? null : Value.fromSerialized(value, 
(MapType)receiver.type, options.getProtocolVersion());
+            if (value == null)
+                return null;
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return UNSET_VALUE;
+            return Value.fromSerialized(value, (MapType)receiver.type, 
options.getProtocolVersion());
         }
     }
 
@@ -280,13 +292,15 @@ public abstract class Maps
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
-            if (column.type.isMultiCell())
+            Term.Terminal value = t.bind(params.options);
+            if (column.type.isMultiCell() && value != UNSET_VALUE)
             {
                 // delete + put
                 CellName name = cf.getComparator().create(prefix, column);
                 cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
             }
-            Putter.doPut(t, cf, prefix, column, params);
+            if (value != UNSET_VALUE)
+                Putter.doPut(cf, prefix, column, params, value);
         }
     }
 
@@ -314,6 +328,8 @@ public abstract class Maps
             ByteBuffer value = t.bindAndGet(params.options);
             if (key == null)
                 throw new InvalidRequestException("Invalid null map key");
+            if (key == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                throw new InvalidRequestException("Invalid unset map key");
 
             CellName cellName = cf.getComparator().create(prefix, column, key);
 
@@ -321,7 +337,7 @@ public abstract class Maps
             {
                 cf.addColumn(params.makeTombstone(cellName));
             }
-            else
+            else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER)
             {
                 // We don't support value > 64K because the serialization 
format encode the length as an unsigned short.
                 if (value.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
@@ -344,12 +360,13 @@ public abstract class Maps
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
             assert column.type.isMultiCell() : "Attempted to add items to a 
frozen map";
-            doPut(t, cf, prefix, column, params);
+            Term.Terminal value = t.bind(params.options);
+            if (value != UNSET_VALUE)
+                doPut(cf, prefix, column, params, value);
         }
 
-        static void doPut(Term t, ColumnFamily cf, Composite prefix, 
ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
+        static void doPut(ColumnFamily cf, Composite prefix, ColumnDefinition 
column, UpdateParameters params, Term.Terminal value) throws 
InvalidRequestException
         {
-            Term.Terminal value = t.bind(params.options);
             if (column.type.isMultiCell())
             {
                 if (value == null)
@@ -387,6 +404,8 @@ public abstract class Maps
             Term.Terminal key = t.bind(params.options);
             if (key == null)
                 throw new InvalidRequestException("Invalid null map key");
+            if (key == Constants.UNSET_VALUE)
+                throw new InvalidRequestException("Invalid unset map key");
 
             CellName cellName = cf.getComparator().create(prefix, column, 
key.get(params.options.getProtocolVersion()));
             cf.addColumn(params.makeTombstone(cellName));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/QueryOptions.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java 
b/src/java/org/apache/cassandra/cql3/QueryOptions.java
index 98c2395..fb46b9b 100644
--- a/src/java/org/apache/cassandra/cql3/QueryOptions.java
+++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java
@@ -312,13 +312,13 @@ public abstract class QueryOptions
             {
                 if (flags.contains(Flag.NAMES_FOR_VALUES))
                 {
-                    Pair<List<String>, List<ByteBuffer>> namesAndValues = 
CBUtil.readNameAndValueList(body);
+                    Pair<List<String>, List<ByteBuffer>> namesAndValues = 
CBUtil.readNameAndValueList(body, version);
                     names = namesAndValues.left;
                     values = namesAndValues.right;
                 }
                 else
                 {
-                    values = CBUtil.readValueList(body);
+                    values = CBUtil.readValueList(body, version);
                 }
             }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/QueryProcessor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java 
b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
index fcda13d..f494436 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -60,6 +60,7 @@ import org.apache.cassandra.service.pager.QueryPagers;
 import org.apache.cassandra.thrift.ThriftClientState;
 import org.apache.cassandra.tracing.Tracing;
 import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.MD5Digest;
 import org.apache.cassandra.utils.SemanticVersion;
@@ -192,6 +193,8 @@ public class QueryProcessor implements QueryHandler
         {
             throw new InvalidRequestException("Key may not be empty");
         }
+        if (key == ByteBufferUtil.UNSET_BYTE_BUFFER)
+            throw new InvalidRequestException("Key may not be unset");
 
         // check that key can be handled by FBUtilities.writeShortByteArray
         if (key.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Sets.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Sets.java 
b/src/java/org/apache/cassandra/cql3/Sets.java
index ce372f8..093f1dc 100644
--- a/src/java/org/apache/cassandra/cql3/Sets.java
+++ b/src/java/org/apache/cassandra/cql3/Sets.java
@@ -17,6 +17,8 @@
  */
 package org.apache.cassandra.cql3;
 
+import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
+
 import java.nio.ByteBuffer;
 import java.util.*;
 
@@ -68,7 +70,7 @@ public abstract class Sets
                 return new Maps.Value(Collections.<ByteBuffer, 
ByteBuffer>emptyMap());
 
             ColumnSpecification valueSpec = Sets.valueSpecOf(receiver);
-            Set<Term> values = new HashSet<Term>(elements.size());
+            Set<Term> values = new HashSet<>(elements.size());
             boolean allTerminal = true;
             for (Term.Raw rt : elements)
             {
@@ -147,8 +149,8 @@ public abstract class Sets
             {
                 // Collections have this small hack that validate cannot be 
called on a serialized object,
                 // but compose does the validation (so we're fine).
-                Set<?> s = 
(Set<?>)type.getSerializer().deserializeForNativeProtocol(value, version);
-                SortedSet<ByteBuffer> elements = new 
TreeSet<ByteBuffer>(type.getElementsType());
+                Set<?> s = 
type.getSerializer().deserializeForNativeProtocol(value, version);
+                SortedSet<ByteBuffer> elements = new 
TreeSet<>(type.getElementsType());
                 for (Object element : s)
                     elements.add(type.getElementsType().decompose(element));
                 return new Value(elements);
@@ -202,7 +204,7 @@ public abstract class Sets
         {
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             SortedSet<ByteBuffer> buffers = new TreeSet<>(comparator);
             for (Term t : elements)
@@ -211,6 +213,8 @@ public abstract class Sets
 
                 if (bytes == null)
                     throw new InvalidRequestException("null is not supported 
inside collections");
+                if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    return UNSET_VALUE;
 
                 // We don't support value > 64K because the serialization 
format encode the length as an unsigned short.
                 if (bytes.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
@@ -237,10 +241,14 @@ public abstract class Sets
             assert receiver.type instanceof SetType;
         }
 
-        public Value bind(QueryOptions options) throws InvalidRequestException
+        public Terminal bind(QueryOptions options) throws 
InvalidRequestException
         {
             ByteBuffer value = options.getValues().get(bindIndex);
-            return value == null ? null : Value.fromSerialized(value, 
(SetType)receiver.type, options.getProtocolVersion());
+            if (value == null)
+                return null;
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                return UNSET_VALUE;
+            return Value.fromSerialized(value, (SetType)receiver.type, 
options.getProtocolVersion());
         }
     }
 
@@ -253,13 +261,15 @@ public abstract class Sets
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
-            if (column.type.isMultiCell())
+            Term.Terminal value = t.bind(params.options);
+            if (column.type.isMultiCell() && value != UNSET_VALUE)
             {
                 // delete + add
                 CellName name = cf.getComparator().create(prefix, column);
                 cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
             }
-            Adder.doAdd(t, cf, prefix, column, params);
+            if (value != UNSET_VALUE)
+                Adder.doAdd(cf, prefix, column, params, value);
         }
     }
 
@@ -273,13 +283,13 @@ public abstract class Sets
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite 
prefix, UpdateParameters params) throws InvalidRequestException
         {
             assert column.type.isMultiCell() : "Attempted to add items to a 
frozen set";
-
-            doAdd(t, cf, prefix, column, params);
+            Term.Terminal value = t.bind(params.options);
+            if (value != UNSET_VALUE)
+                doAdd(cf, prefix, column, params, value);
         }
 
-        static void doAdd(Term t, ColumnFamily cf, Composite prefix, 
ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
+        static void doAdd(ColumnFamily cf, Composite prefix, ColumnDefinition 
column, UpdateParameters params, Term.Terminal value) throws 
InvalidRequestException
         {
-            Term.Terminal value = t.bind(params.options);
             if (column.type.isMultiCell())
             {
                 if (value == null)
@@ -287,6 +297,8 @@ public abstract class Sets
 
                 for (ByteBuffer bb : ((Value) value).elements)
                 {
+                    if (bb == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                        continue;
                     CellName cellName = cf.getComparator().create(prefix, 
column, bb);
                     cf.addColumn(params.makeColumn(cellName, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
                 }
@@ -316,7 +328,7 @@ public abstract class Sets
             assert column.type.isMultiCell() : "Attempted to remove items from 
a frozen set";
 
             Term.Terminal value = t.bind(params.options);
-            if (value == null)
+            if (value == null || value == UNSET_VALUE)
                 return;
 
             // This can be either a set or a single element

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/Tuples.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java 
b/src/java/org/apache/cassandra/cql3/Tuples.java
index 967ce37..92ccbce 100644
--- a/src/java/org/apache/cassandra/cql3/Tuples.java
+++ b/src/java/org/apache/cassandra/cql3/Tuples.java
@@ -30,6 +30,7 @@ import org.apache.cassandra.cql3.functions.Function;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.serializers.MarshalException;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Static helper methods and classes for tuples.
@@ -204,6 +205,9 @@ public class Tuples
             for (int i = 0; i < elements.size(); i++)
             {
                 buffers[i] = elements.get(i).bindAndGet(options);
+                // Since A tuple value is always written in its entirety 
Cassandra can't preserve a pre-existing value by 'not setting' the new value. 
Reject the query.
+                if (buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    throw new InvalidRequestException(String.format("Invalid 
unset value for tuple field number %d", i));
                 // Inside tuples, we must force the serialization of 
collections to v3 whatever protocol
                 // version is in use since we're going to store directly that 
serialized value.
                 if (version < 3 && type.type(i).isCollection())
@@ -387,6 +391,8 @@ public class Tuples
         public Value bind(QueryOptions options) throws InvalidRequestException
         {
             ByteBuffer value = options.getValues().get(bindIndex);
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                throw new InvalidRequestException(String.format("Invalid unset 
value for tuple %s", receiver.name));
             return value == null ? null : Value.fromSerialized(value, 
(TupleType)receiver.type);
         }
     }
@@ -405,6 +411,8 @@ public class Tuples
         public InValue bind(QueryOptions options) throws 
InvalidRequestException
         {
             ByteBuffer value = options.getValues().get(bindIndex);
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                throw new InvalidRequestException(String.format("Invalid unset 
value for %s", receiver.name));
             return value == null ? null : InValue.fromSerialized(value, 
(ListType)receiver.type, options);
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/UserTypes.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java 
b/src/java/org/apache/cassandra/cql3/UserTypes.java
index f00a376..b6d8521 100644
--- a/src/java/org/apache/cassandra/cql3/UserTypes.java
+++ b/src/java/org/apache/cassandra/cql3/UserTypes.java
@@ -26,6 +26,7 @@ import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.db.marshal.UserType;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.transport.Server;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Static helper methods and classes for user types.
@@ -185,6 +186,9 @@ public abstract class UserTypes
             for (int i = 0; i < type.size(); i++)
             {
                 buffers[i] = values.get(i).bindAndGet(options);
+                // Since A UDT value is always written in its entirety 
Cassandra can't preserve a pre-existing value by 'not setting' the new value. 
Reject the query.
+                if (buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                    throw new InvalidRequestException(String.format("Invalid 
unset value for field '%s' of user defined type %s", type.fieldNameAsString(i), 
type.getNameAsString()));
                 // Inside UDT values, we must force the serialization of 
collections to v3 whatever protocol
                 // version is in use since we're going to store directly that 
serialized value.
                 if (version < Server.VERSION_3 && 
type.fieldType(i).isCollection() && buffers[i] != null)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java 
b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
index d8efa7f..f42a093 100644
--- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
+++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java
@@ -24,6 +24,7 @@ import java.util.List;
 import com.google.common.collect.Iterables;
 
 import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.statements.RequestValidations;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.serializers.MarshalException;
@@ -65,7 +66,11 @@ public class FunctionCall extends Term.NonTerminal
     {
         List<ByteBuffer> buffers = new ArrayList<>(terms.size());
         for (Term t : terms)
-            buffers.add(t.bindAndGet(options));
+        {
+            ByteBuffer functionArg = t.bindAndGet(options);
+            RequestValidations.checkBindValueSet(functionArg, "Invalid unset 
value for argument in call to function %s", fun.name().name);
+            buffers.add(functionArg);
+        }
         return executeInternal(options.getProtocolVersion(), fun, buffers);
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java 
b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
index b212f4d..d36162c 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java
@@ -29,6 +29,7 @@ import 
org.apache.cassandra.exceptions.InvalidRequestException;
 
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
 
 /**
  * Base class for <code>Restriction</code>s
@@ -94,6 +95,7 @@ abstract class AbstractRestriction  implements Restriction
                                                      throws 
InvalidRequestException
     {
         checkNotNull(value, "Unsupported null value for indexed column %s", 
columnSpec.name);
+        checkBindValueSet(value, "Unsupported unset value for indexed column 
%s", columnSpec.name);
         checkFalse(value.remaining() > 0xFFFF, "Index expression values may 
not be larger than 64K");
         return value;
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java 
b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
index ad4893f..69ef5d2 100644
--- 
a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
+++ 
b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java
@@ -23,9 +23,12 @@ import java.util.*;
 import com.google.common.collect.Iterables;
 
 import org.apache.cassandra.config.ColumnDefinition;
+
 import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.Term.Terminal;
 import org.apache.cassandra.cql3.functions.Function;
 import org.apache.cassandra.cql3.statements.Bound;
+
 import org.apache.cassandra.db.IndexExpression;
 import org.apache.cassandra.db.composites.CompositesBuilder;
 import org.apache.cassandra.db.index.SecondaryIndex;
@@ -33,9 +36,12 @@ import org.apache.cassandra.db.index.SecondaryIndexManager;
 import org.apache.cassandra.db.marshal.CompositeType;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
+
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
 
 public abstract class SingleColumnRestriction extends AbstractRestriction
 {
@@ -136,6 +142,7 @@ public abstract class SingleColumnRestriction extends 
AbstractRestriction
         {
             builder.addElementToAll(value.bindAndGet(options));
             checkFalse(builder.containsNull(), "Invalid null value in 
condition for column %s", columnDef.name);
+            checkFalse(builder.containsUnset(), "Invalid unset value for 
column %s", columnDef.name);
             return builder;
         }
 
@@ -182,6 +189,7 @@ public abstract class SingleColumnRestriction extends 
AbstractRestriction
         {
             builder.addEachElementToAll(getValues(options));
             checkFalse(builder.containsNull(), "Invalid null value in 
condition for column %s", columnDef.name);
+            checkFalse(builder.containsUnset(), "Invalid unset value for 
column %s", columnDef.name);
             return builder;
         }
 
@@ -269,9 +277,10 @@ public abstract class SingleColumnRestriction extends 
AbstractRestriction
         @Override
         protected List<ByteBuffer> getValues(QueryOptions options) throws 
InvalidRequestException
         {
-            Term.MultiItemTerminal lval = (Term.MultiItemTerminal) 
marker.bind(options);
-            if (lval == null)
-                throw new InvalidRequestException("Invalid null value for IN 
restriction");
+            Terminal term = marker.bind(options);
+            checkNotNull(term, "Invalid null value for column %s", 
columnDef.name);
+            checkFalse(term == Constants.UNSET_VALUE, "Invalid unset value for 
column %s", columnDef.name);
+            Term.MultiItemTerminal lval = (Term.MultiItemTerminal) term;
             return lval.getElements();
         }
 
@@ -326,7 +335,10 @@ public abstract class SingleColumnRestriction extends 
AbstractRestriction
         @Override
         public CompositesBuilder appendBoundTo(CompositesBuilder builder, 
Bound bound, QueryOptions options)
         {
-            return 
builder.addElementToAll(slice.bound(bound).bindAndGet(options));
+            ByteBuffer value = slice.bound(bound).bindAndGet(options);
+            checkBindValueSet(value, "Invalid unset value for column %s", 
columnDef.name);
+            return builder.addElementToAll(value);
+
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index 657e6e0..e7a1a20 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -641,10 +641,12 @@ public abstract class ModificationStatement implements 
CQLStatement
 
         for (IMutation mutation : getMutations(options, true, 
queryState.getTimestamp()))
         {
-            // We don't use counters internally.
-            assert mutation instanceof Mutation;
+            assert mutation instanceof Mutation || mutation instanceof 
CounterMutation;
 
-            ((Mutation) mutation).apply();
+            if (mutation instanceof Mutation)
+                ((Mutation) mutation).apply();
+            else if (mutation instanceof CounterMutation)
+                ((CounterMutation) mutation).apply();
         }
         return null;
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/statements/RequestValidations.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/RequestValidations.java 
b/src/java/org/apache/cassandra/cql3/statements/RequestValidations.java
index c822325..fc07878 100644
--- a/src/java/org/apache/cassandra/cql3/statements/RequestValidations.java
+++ b/src/java/org/apache/cassandra/cql3/statements/RequestValidations.java
@@ -17,11 +17,13 @@
  */
 package org.apache.cassandra.cql3.statements;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 
 import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 import static org.apache.commons.lang3.ArrayUtils.EMPTY_OBJECT_ARRAY;
 
@@ -140,6 +142,21 @@ public final class RequestValidations
     }
 
     /**
+     * Checks that the specified bind marker value is set to a meaningful 
value.
+     * If it is not a <code>InvalidRequestException</code> will be thrown.
+     *
+     * @param b the <code>ByteBuffer</code> to test
+     * @param messageTemplate the template used to build the error message
+     * @param messageArgs the message arguments
+     * @throws InvalidRequestException if the specified bind marker value is 
not set to a meaningful value.
+     */
+    public static void checkBindValueSet(ByteBuffer b, String messageTemplate, 
Object... messageArgs)
+            throws InvalidRequestException
+    {
+        checkTrue(b != ByteBufferUtil.UNSET_BYTE_BUFFER, messageTemplate, 
messageArgs);
+    }
+
+    /**
      * Checks that the specified object is <code>null</code>.
      * If it is not an <code>InvalidRequestException</code> will be throws.
      *

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index c7791fb..8f7f75e 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -58,6 +58,7 @@ import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkFalse
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+import static org.apache.cassandra.utils.ByteBufferUtil.UNSET_BYTE_BUFFER;
 
 /**
  * Encapsulates a completely parsed SELECT query, including the target
@@ -466,7 +467,9 @@ public class SelectStatement implements CQLStatement
         if (limit != null)
         {
             ByteBuffer b = checkNotNull(limit.bindAndGet(options), "Invalid 
null value of limit");
-
+            // treat UNSET limit value as 'unlimited'
+            if (b == UNSET_BYTE_BUFFER)
+                return Integer.MAX_VALUE;
             try
             {
                 Int32Type.instance.validate(b);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java 
b/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
index 48bc802..25a510f 100644
--- a/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
+++ b/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
@@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
 import java.util.*;
 
 import org.apache.cassandra.db.composites.Composite.EOC;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 import static java.util.Collections.singletonList;
 
@@ -59,6 +60,11 @@ public final class CompositesBuilder
      */
     private boolean hasMissingElements;
 
+    /**
+     * <code>true</code> if the composites contains some <code>unset</code> 
elements.
+     */
+    private boolean containsUnset;
+
     public CompositesBuilder(CType ctype)
     {
         this.ctype = ctype;
@@ -85,7 +91,8 @@ public final class CompositesBuilder
         {
             if (value == null)
                 containsNull = true;
-
+            if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                containsUnset = true;
             elementsList.get(i).add(value);
         }
         size++;
@@ -128,6 +135,8 @@ public final class CompositesBuilder
 
                     if (value == null)
                         containsNull = true;
+                    if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+                        containsUnset = true;
 
                     newComposite.add(values.get(j));
                 }
@@ -177,6 +186,8 @@ public final class CompositesBuilder
 
                     if (value.contains(null))
                         containsNull = true;
+                    if (value.contains(ByteBufferUtil.UNSET_BYTE_BUFFER))
+                        containsUnset = true;
 
                     newComposite.addAll(value);
                 }
@@ -236,6 +247,16 @@ public final class CompositesBuilder
     }
 
     /**
+     * Checks if the composites contains unset elements.
+     *
+     * @return <code>true</code> if the composites contains <code>unset</code> 
elements, <code>false</code> otherwise.
+     */
+    public boolean containsUnset()
+    {
+        return containsUnset;
+    }
+
+    /**
      * Builds the <code>Composites</code>.
      *
      * @return the composites

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/db/marshal/UserType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java 
b/src/java/org/apache/cassandra/db/marshal/UserType.java
index ce8bb43..ec97a7f 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -95,6 +95,11 @@ public class UserType extends TupleType
         return fieldNames.get(i);
     }
 
+    public String fieldNameAsString(int i)
+    {
+        return stringFieldNames.get(i);
+    }
+
     public List<ByteBuffer> fieldNames()
     {
         return fieldNames;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/transport/CBUtil.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/transport/CBUtil.java 
b/src/java/org/apache/cassandra/transport/CBUtil.java
index 8f0dee0..73df554 100644
--- a/src/java/org/apache/cassandra/transport/CBUtil.java
+++ b/src/java/org/apache/cassandra/transport/CBUtil.java
@@ -374,6 +374,25 @@ public abstract class CBUtil
         return ByteBuffer.wrap(readRawBytes(slice));
     }
 
+    public static ByteBuffer readBoundValue(ByteBuf cb, int protocolVersion)
+    {
+        int length = cb.readInt();
+        if (length < 0)
+        {
+            if (protocolVersion < 4) // backward compatibility for pre-version 
4
+                return null;
+            if (length == -1)
+                return null;
+            else if (length == -2)
+                return ByteBufferUtil.UNSET_BYTE_BUFFER;
+            else
+                throw new ProtocolException("Invalid ByteBuf length " + 
length);
+        }
+        ByteBuf slice = cb.readSlice(length);
+
+        return ByteBuffer.wrap(readRawBytes(slice));
+    }
+
     public static void writeValue(byte[] bytes, ByteBuf cb)
     {
         if (bytes == null)
@@ -411,7 +430,7 @@ public abstract class CBUtil
         return 4 + (bytes == null ? 0 : bytes.remaining());
     }
 
-    public static List<ByteBuffer> readValueList(ByteBuf cb)
+    public static List<ByteBuffer> readValueList(ByteBuf cb, int 
protocolVersion)
     {
         int size = cb.readUnsignedShort();
         if (size == 0)
@@ -419,7 +438,7 @@ public abstract class CBUtil
 
         List<ByteBuffer> l = new ArrayList<ByteBuffer>(size);
         for (int i = 0; i < size; i++)
-            l.add(readValue(cb));
+            l.add(readBoundValue(cb, protocolVersion));
         return l;
     }
 
@@ -438,7 +457,7 @@ public abstract class CBUtil
         return size;
     }
 
-    public static Pair<List<String>, List<ByteBuffer>> 
readNameAndValueList(ByteBuf cb)
+    public static Pair<List<String>, List<ByteBuffer>> 
readNameAndValueList(ByteBuf cb, int protocolVersion)
     {
         int size = cb.readUnsignedShort();
         if (size == 0)
@@ -449,7 +468,7 @@ public abstract class CBUtil
         for (int i = 0; i < size; i++)
         {
             s.add(readString(cb));
-            l.add(readValue(cb));
+            l.add(readBoundValue(cb, protocolVersion));
         }
         return Pair.create(s, l);
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java 
b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
index 88394e7..8f144d1 100644
--- a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java
@@ -61,7 +61,7 @@ public class BatchMessage extends Message.Request
                     queryOrIds.add(MD5Digest.wrap(CBUtil.readBytes(body)));
                 else
                     throw new ProtocolException("Invalid query kind in BATCH 
messages. Must be 0 or 1 but got " + kind);
-                variables.add(CBUtil.readValueList(body));
+                variables.add(CBUtil.readValueList(body, version));
             }
             QueryOptions options = version < 3
                                  ? 
QueryOptions.fromPreV3Batch(CBUtil.readConsistencyLevel(body))

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java 
b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java
index 5f3e368..2b21376 100644
--- a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java
+++ b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java
@@ -47,7 +47,7 @@ public class ExecuteMessage extends Message.Request
             byte[] id = CBUtil.readBytes(body);
             if (version == 1)
             {
-                List<ByteBuffer> values = CBUtil.readValueList(body);
+                List<ByteBuffer> values = CBUtil.readValueList(body, version);
                 ConsistencyLevel consistency = 
CBUtil.readConsistencyLevel(body);
                 return new ExecuteMessage(MD5Digest.wrap(id), 
QueryOptions.fromProtocolV1(consistency, values));
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/utils/ByteBufferUtil.java 
b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
index ce16d3c..4fea55e 100644
--- a/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
+++ b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
@@ -78,6 +78,8 @@ import org.apache.cassandra.io.util.FileUtils;
 public class ByteBufferUtil
 {
     public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(new 
byte[0]);
+    /** Represents an unset value in bound variables */
+    public static final ByteBuffer UNSET_BYTE_BUFFER = ByteBuffer.wrap(new 
byte[]{});
 
     @Inline
     public static int compareUnsigned(ByteBuffer o1, ByteBuffer o2)
@@ -560,4 +562,5 @@ public class ByteBufferUtil
         int length = readShortLength(bb);
         return readBytes(bb, length);
     }
+
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java 
b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index 6a7e52f..e8feb28 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -59,6 +59,7 @@ import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.transport.Event;
 import org.apache.cassandra.transport.Server;
 import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Base class for CQL tests.
@@ -319,6 +320,11 @@ public abstract class CQLTester
         return tables.get(tables.size() - 1);
     }
 
+    protected ByteBuffer unset()
+    {
+        return ByteBufferUtil.UNSET_BYTE_BUFFER;
+    }
+
     protected void forcePreparedValues()
     {
         this.usePrepared = true;
@@ -799,6 +805,11 @@ public abstract class CQLTester
                 buffers[i] = null;
                 continue;
             }
+            else if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
+            {
+                buffers[i] = ByteBufferUtil.UNSET_BYTE_BUFFER;
+                continue;
+            }
 
             try
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/CollectionsTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/CollectionsTest.java 
b/test/unit/org/apache/cassandra/cql3/CollectionsTest.java
index 3d266b7..0e1fbd6 100644
--- a/test/unit/org/apache/cassandra/cql3/CollectionsTest.java
+++ b/test/unit/org/apache/cassandra/cql3/CollectionsTest.java
@@ -209,4 +209,104 @@ public class CollectionsTest extends CQLTester
 
         assertRows(execute("SELECT l FROM %s WHERE k = 0"), row((Object) 
null));
     }
+
+    @Test
+    public void testMapWithUnsetValues() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<text,text>)");
+        // set up
+        Object m = map("k", "v");
+        execute("INSERT INTO %s (k, m) VALUES (10, ?)", m);
+        assertRows(execute("SELECT m FROM %s WHERE k = 10"),
+                row(m)
+        );
+
+        // test putting an unset map, should not delete the contents
+        execute("INSERT INTO %s (k, m) VALUES (10, ?)", unset());
+        assertRows(execute("SELECT m FROM %s WHERE k = 10"),
+                row(m)
+        );
+        // test unset variables in a map update operaiotn, should not delete 
the contents
+        execute("UPDATE %s SET m['k'] = ? WHERE k = 10", unset());
+        assertRows(execute("SELECT m FROM %s WHERE k = 10"),
+                row(m)
+        );
+        assertInvalidMessage("Invalid unset map key", "UPDATE %s SET m[?] = 
'foo' WHERE k = 10", unset());
+
+        // test unset value for map key
+        assertInvalidMessage("Invalid unset map key", "DELETE m[?] FROM %s 
WHERE k = 10", unset());
+    }
+
+    @Test
+    public void testListWithUnsetValues() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<text>)");
+        // set up
+        Object l = list("foo", "foo");
+        execute("INSERT INTO %s (k, l) VALUES (10, ?)", l);
+        assertRows(execute("SELECT l FROM %s WHERE k = 10"),
+                row(l)
+        );
+
+        // replace list with unset value
+        execute("INSERT INTO %s (k, l) VALUES (10, ?)", unset());
+        assertRows(execute("SELECT l FROM %s WHERE k = 10"),
+                row(l)
+        );
+
+        // add to position
+        execute("UPDATE %s SET l[1] = ? WHERE k = 10", unset());
+        assertRows(execute("SELECT l FROM %s WHERE k = 10"),
+                row(l)
+        );
+
+        // set in index
+        assertInvalidMessage("Invalid unset value for list index", "UPDATE %s 
SET l[?] = 'foo' WHERE k = 10", unset());
+
+        // remove element by index
+        execute("DELETE l[?] FROM %s WHERE k = 10", unset());
+        assertRows(execute("SELECT l FROM %s WHERE k = 10"),
+                row(l)
+        );
+
+        // remove all occurrences of element
+        execute("UPDATE %s SET l = l - ? WHERE k = 10", unset());
+        assertRows(execute("SELECT l FROM %s WHERE k = 10"),
+                row(l)
+        );
+
+        // select with in clause
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
FROM %s WHERE k IN ?", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
FROM %s WHERE k IN (?)", unset());
+    }
+
+    @Test
+    public void testSetWithUnsetValues() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<text>)");
+
+        Object s = set("bar", "baz", "foo");
+        execute("INSERT INTO %s (k, s) VALUES (10, ?)", s);
+        assertRows(execute("SELECT s FROM %s WHERE k = 10"),
+                row(s)
+        );
+
+        // replace set with unset value
+        execute("INSERT INTO %s (k, s) VALUES (10, ?)", unset());
+        assertRows(execute("SELECT s FROM %s WHERE k = 10"),
+                row(s)
+        );
+
+        // add to set
+        execute("UPDATE %s SET s = s + ? WHERE k = 10", unset());
+        assertRows(execute("SELECT s FROM %s WHERE k = 10"),
+                row(s)
+        );
+
+        // remove all occurrences of element
+        execute("UPDATE %s SET s = s - ? WHERE k = 10", unset());
+        assertRows(execute("SELECT s FROM %s WHERE k = 10"),
+                row(s)
+        );
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java 
b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
index 3b1a826..c8b3a2f 100644
--- a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
+++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
@@ -31,6 +31,7 @@ import org.junit.Test;
 import java.nio.ByteBuffer;
 import java.util.*;
 
+import static org.apache.cassandra.utils.ByteBufferUtil.UNSET_BYTE_BUFFER;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
@@ -82,6 +83,7 @@ public class ColumnConditionTest
         assertTrue(isSatisfiedBy(bound, null, null));
         assertFalse(isSatisfiedBy(bound, ONE, null));
         assertFalse(isSatisfiedBy(bound, null, ONE));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
 
         // NEQ
         condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Operator.NEQ);
@@ -95,6 +97,7 @@ public class ColumnConditionTest
         assertFalse(isSatisfiedBy(bound, null, null));
         assertTrue(isSatisfiedBy(bound, ONE, null));
         assertTrue(isSatisfiedBy(bound, null, ONE));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
 
         // LT
         condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Operator.LT);
@@ -107,6 +110,7 @@ public class ColumnConditionTest
         assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
         assertThrowsIRE(bound, null, ONE);
         assertFalse(isSatisfiedBy(bound, ONE, null));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
 
         // LTE
         condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Operator.LTE);
@@ -119,6 +123,7 @@ public class ColumnConditionTest
         assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
         assertThrowsIRE(bound, null, ONE);
         assertFalse(isSatisfiedBy(bound, ONE, null));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
 
         // GT
         condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Operator.GT);
@@ -131,6 +136,7 @@ public class ColumnConditionTest
         assertFalse(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
         assertThrowsIRE(bound, null, ONE);
         assertFalse(isSatisfiedBy(bound, ONE, null));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
 
         // GT
         condition = ColumnCondition.condition(definition, new 
Constants.Value(ONE), Operator.GTE);
@@ -143,6 +149,7 @@ public class ColumnConditionTest
         assertTrue(isSatisfiedBy(bound, ByteBufferUtil.EMPTY_BYTE_BUFFER, 
ByteBufferUtil.EMPTY_BYTE_BUFFER));
         assertThrowsIRE(bound, null, ONE);
         assertFalse(isSatisfiedBy(bound, ONE, null));
+        assertThrowsIRE(bound, UNSET_BYTE_BUFFER, ONE);
     }
 
     private static List<ByteBuffer> list(ByteBuffer... values)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java 
b/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
index 60ea7e3..a987bb2 100644
--- a/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
@@ -29,6 +29,9 @@ public class ContainsRelationTest extends CQLTester
         assertInvalidMessage("Unsupported null value for indexed column 
categories",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, null);
 
+        assertInvalidMessage("Unsupported unset value for indexed column 
categories",
+                             "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, unset());
+
         assertInvalidMessage("Cannot execute this query as it might involve 
data filtering and thus may have unpredictable performance. If you want to 
execute this query despite the performance unpredictability, use ALLOW 
FILTERING",
                              "SELECT * FROM %s WHERE account = ? AND 
categories CONTAINS ? AND categories CONTAINS ?", "xyz", "lmn", "notPresent");
         assertEmpty(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS ? AND categories CONTAINS ? ALLOW FILTERING", "xyz", "lmn", 
"notPresent"));
@@ -59,6 +62,9 @@ public class ContainsRelationTest extends CQLTester
         assertInvalidMessage("Unsupported null value for indexed column 
categories",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, null);
 
+        assertInvalidMessage("Unsupported unset value for indexed column 
categories",
+                             "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, unset());
+
         assertInvalidMessage("Cannot execute this query as it might involve 
data filtering and thus may have unpredictable performance. If you want to 
execute this query despite the performance unpredictability, use ALLOW 
FILTERING",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ? AND categories CONTAINS ?",
                              "test", 5, "lmn", "notPresent");
@@ -108,6 +114,9 @@ public class ContainsRelationTest extends CQLTester
         assertInvalidMessage("Unsupported null value for indexed column 
categories",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS KEY ?", "test", 5, null);
 
+        assertInvalidMessage("Unsupported unset value for indexed column 
categories",
+                             "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS KEY ?", "test", 5, unset());
+
         assertInvalidMessage("Cannot execute this query as it might involve 
data filtering and thus may have unpredictable performance. If you want to 
execute this query despite the performance unpredictability, use ALLOW 
FILTERING",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS KEY ? AND categories CONTAINS KEY ?",
                              "test", 5, "lmn", "notPresent");
@@ -144,6 +153,9 @@ public class ContainsRelationTest extends CQLTester
         assertInvalidMessage("Unsupported null value for indexed column 
categories",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, null);
 
+        assertInvalidMessage("Unsupported unset value for indexed column 
categories",
+                             "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ?", "test", 5, unset());
+
         assertInvalidMessage("Cannot execute this query as it might involve 
data filtering and thus may have unpredictable performance. If you want to 
execute this query despite the performance unpredictability, use ALLOW 
FILTERING",
                              "SELECT * FROM %s WHERE account = ? AND id = ? 
AND categories CONTAINS ? AND categories CONTAINS ?"
                            , "test", 5, "foo", "notPresent");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/ModificationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/ModificationTest.java 
b/test/unit/org/apache/cassandra/cql3/ModificationTest.java
new file mode 100644
index 0000000..6397a15
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/ModificationTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import org.junit.Test;
+
+public class ModificationTest extends CQLTester
+{
+    @Test
+    public void testModificationWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s text, i int)");
+
+        // insert using nulls
+        execute("INSERT INTO %s (k, s, i) VALUES (10, ?, ?)", "text", 10);
+        execute("INSERT INTO %s (k, s, i) VALUES (10, ?, ?)", null, null);
+        assertRows(execute("SELECT s, i FROM %s WHERE k = 10"),
+                row(null, null) // sending null deletes the data
+        );
+        // insert using UNSET
+        execute("INSERT INTO %s (k, s, i) VALUES (11, ?, ?)", "text", 10);
+        execute("INSERT INTO %s (k, s, i) VALUES (11, ?, ?)", unset(), 
unset());
+        assertRows(execute("SELECT s, i FROM %s WHERE k=11"),
+                row("text", 10) // unset columns does not delete the existing 
data
+        );
+
+        assertInvalidMessage("Invalid unset value for column k", "UPDATE %s 
SET i = 0 WHERE k = ?", unset());
+        assertInvalidMessage("Invalid unset value for column k", "DELETE FROM 
%s WHERE k = ?", unset());
+        assertInvalidMessage("Invalid unset value for argument in call to 
function blobasint", "SELECT * FROM %s WHERE k = blobAsInt(?)", unset());
+    }
+
+    @Test
+    public void testTtlWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, i int)");
+        execute("INSERT INTO %s (k, i) VALUES (1, 1) USING TTL ?", unset()); 
// treat as 'unlimited'
+        assertRows(execute("SELECT ttl(i) FROM %s"),
+                row(new Object[] {null})
+        );
+    }
+
+    @Test
+    public void testTimestampWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, i int)");
+        execute("INSERT INTO %s (k, i) VALUES (1, 1) USING TIMESTAMP ?", 
unset()); // treat as 'now'
+    }
+
+    @Test
+    public void testCounterUpdatesWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, c counter)");
+
+        // set up
+        execute("UPDATE %s SET c = c + 1 WHERE k = 10");
+        assertRows(execute("SELECT c FROM %s WHERE k = 10"),
+                row(1L)
+        );
+        // increment
+        execute("UPDATE %s SET c = c + ? WHERE k = 10", 1L);
+        assertRows(execute("SELECT c FROM %s WHERE k = 10"),
+                row(2L)
+        );
+        execute("UPDATE %s SET c = c + ? WHERE k = 10", unset());
+        assertRows(execute("SELECT c FROM %s WHERE k = 10"),
+                row(2L) // no change to the counter value
+        );
+        // decrement
+        execute("UPDATE %s SET c = c - ? WHERE k = 10", 1L);
+        assertRows(execute("SELECT c FROM %s WHERE k = 10"),
+                row(1L)
+        );
+        execute("UPDATE %s SET c = c - ? WHERE k = 10", unset());
+        assertRows(execute("SELECT c FROM %s WHERE k = 10"),
+                row(1L) // no change to the counter value
+        );
+    }
+
+    @Test
+    public void testBatchWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s text, i int)");
+
+        // test batch and update
+        String qualifiedTable = keyspace() + "." + currentTable();
+        execute("BEGIN BATCH " +
+                 "INSERT INTO %s (k, s, i) VALUES (100, 'batchtext', 7); " +
+                 "INSERT INTO " + qualifiedTable + " (k, s, i) VALUES (111, 
'batchtext', 7); " +
+             "UPDATE " + qualifiedTable + " SET s=?, i=? WHERE k = 100; " +
+                 "UPDATE " + qualifiedTable + " SET s=?, i=? WHERE k=111; " +
+                 "APPLY BATCH;", null, unset(), unset(), null);
+        assertRows(execute("SELECT k, s, i FROM %s where k in (100,111)"),
+            row(100, null, 7),
+            row(111, "batchtext", null)
+        );
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java 
b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
index 8bd4e97..b380b1e 100644
--- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
@@ -911,4 +911,26 @@ public class MultiColumnRelationTest extends CQLTester
                        row(1, 1, 1));
         }
     }
+
+    @Test
+    public void testWithUnsetValues() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int, i int, j int, s text, PRIMARY 
KEY(k,i,j))");
+        createIndex("CREATE INDEX s_index ON %s (s)");
+
+        assertInvalidMessage("Invalid unset value for tuple field number 0",
+                             "SELECT * from %s WHERE (i, j) = (?,?) ALLOW 
FILTERING", unset(), 1);
+        assertInvalidMessage("Invalid unset value for tuple field number 0",
+                             "SELECT * from %s WHERE (i, j) IN ((?,?)) ALLOW 
FILTERING", unset(), 1);
+        assertInvalidMessage("Invalid unset value for tuple field number 1",
+                             "SELECT * from %s WHERE (i, j) > (1,?) ALLOW 
FILTERING", unset());
+        assertInvalidMessage("Invalid unset value for tuple (i,j)",
+                             "SELECT * from %s WHERE (i, j) = ? ALLOW 
FILTERING", unset());
+        assertInvalidMessage("Invalid unset value for tuple (j)",
+                             "SELECT * from %s WHERE i = ? AND (j) > ? ALLOW 
FILTERING", 1, unset());
+        assertInvalidMessage("Invalid unset value for tuple (i,j)",
+                             "SELECT * from %s WHERE (i, j) IN (?, ?) ALLOW 
FILTERING", unset(), tuple(1, 1));
+        assertInvalidMessage("Invalid unset value for in(i,j)",
+                             "SELECT * from %s WHERE (i, j) IN ? ALLOW 
FILTERING", unset());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/48f64468/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java 
b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
index 4bbec81..2b8fbd4 100644
--- a/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/SingleColumnRelationTest.java
@@ -125,7 +125,7 @@ public class SingleColumnRelationTest extends CQLTester
                    row("first", 2, 6, 2),
                    row("first", 3, 7, 3));
 
-        assertInvalidMessage("Invalid null value for IN restriction",
+        assertInvalidMessage("Invalid null value for column b",
                              "select * from %s where a = ? and b in ? and c in 
?", "first", null, Arrays.asList(7, 6));
 
         assertRows(execute("select * from %s where a = ? and c >= ? and b in 
(?, ?)", "first", 6, 3, 2),
@@ -505,4 +505,49 @@ public class SingleColumnRelationTest extends CQLTester
         assertRows(execute("SELECT * FROM %s WHERE a = ? AND c = ? AND d >= ? 
AND f = ? ALLOW FILTERING", 0, 1, 1, 5),
                    row(0, 0, 1, 1, 1, 5));
     }
+
+    @Test
+    public void testFunctionCallWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s text, i int)");
+
+        assertInvalidMessage("Invalid unset value for argument in call to 
function token",
+                             "SELECT * FROM %s WHERE token(k) >= token(?)", 
unset());
+        assertInvalidMessage("Invalid unset value for argument in call to 
function blobasint",
+                             "SELECT * FROM %s WHERE k = blobAsInt(?)", 
unset());
+    }
+
+    @Test
+    public void testLimitWithUnset() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, i int)");
+        execute("INSERT INTO %s (k, i) VALUES (1, 1)");
+        execute("INSERT INTO %s (k, i) VALUES (2, 1)");
+        assertRows(execute("SELECT k FROM %s LIMIT ?", unset()), // treat as 
'unlimited'
+                row(1),
+                row(2)
+        );
+    }
+
+    @Test
+    public void testWithUnsetValues() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int, i int, j int, s text, PRIMARY 
KEY(k,i,j))");
+        createIndex("CREATE INDEX s_index ON %s (s)");
+        // partition key
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k = ?", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN ?", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN(?)", unset());
+        assertInvalidMessage("Invalid unset value for column k", "SELECT * 
from %s WHERE k IN(?,?)", 1, unset());
+        // clustering column
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i = ?", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN ?", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN(?)", unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i IN(?,?)", 1, unset());
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE i = ? ALLOW FILTERING", unset());
+        // indexed column
+        assertInvalidMessage("Unsupported unset value for indexed column s", 
"SELECT * from %s WHERE s = ?", unset());
+        // range
+        assertInvalidMessage("Invalid unset value for column i", "SELECT * 
from %s WHERE k = 1 AND i > ?", unset());
+    }
 }

Reply via email to