You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2014/11/11 20:33:45 UTC

[1/4] cassandra git commit: Support for frozen collections

Repository: cassandra
Updated Branches:
  refs/heads/trunk f2e2862f5 -> fb4356a36


http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/TypeParser.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TypeParser.java b/src/java/org/apache/cassandra/db/marshal/TypeParser.java
index 1b83180..cdb5679 100644
--- a/src/java/org/apache/cassandra/db/marshal/TypeParser.java
+++ b/src/java/org/apache/cassandra/db/marshal/TypeParser.java
@@ -241,7 +241,7 @@ public class TypeParser
 
     public Map<ByteBuffer, CollectionType> getCollectionsParameters() throws SyntaxException, ConfigurationException
     {
-        Map<ByteBuffer, CollectionType> map = new HashMap<ByteBuffer, CollectionType>();
+        Map<ByteBuffer, CollectionType> map = new HashMap<>();
 
         if (isEOS())
             return map;
@@ -539,25 +539,37 @@ public class TypeParser
      */
     public static String stringifyTypeParameters(List<AbstractType<?>> types)
     {
-        StringBuilder sb = new StringBuilder();
-        sb.append('(').append(StringUtils.join(types, ",")).append(')');
-        return sb.toString();
+        return stringifyTypeParameters(types, false);
+    }
+
+    /**
+     * Helper function to ease the writing of AbstractType.toString() methods.
+     */
+    public static String stringifyTypeParameters(List<AbstractType<?>> types, boolean ignoreFreezing)
+    {
+        StringBuilder sb = new StringBuilder("(");
+        for (int i = 0; i < types.size(); i++)
+        {
+            if (i > 0)
+                sb.append(",");
+            sb.append(types.get(i).toString(ignoreFreezing));
+        }
+        return sb.append(')').toString();
     }
 
-    public static String stringifyCollectionsParameters(Map<ByteBuffer, CollectionType> collections)
+    public static String stringifyCollectionsParameters(Map<ByteBuffer, ? extends CollectionType> collections)
     {
         StringBuilder sb = new StringBuilder();
         sb.append('(');
         boolean first = true;
-        for (Map.Entry<ByteBuffer, CollectionType> entry : collections.entrySet())
+        for (Map.Entry<ByteBuffer, ? extends CollectionType> entry : collections.entrySet())
         {
             if (!first)
-            {
                 sb.append(',');
-            }
+
             first = false;
             sb.append(ByteBufferUtil.bytesToHex(entry.getKey())).append(":");
-            entry.getValue().appendToStringBuilder(sb);
+            sb.append(entry.getValue());
         }
         sb.append(')');
         return sb.toString();
@@ -572,7 +584,9 @@ public class TypeParser
         {
             sb.append(',');
             sb.append(ByteBufferUtil.bytesToHex(columnNames.get(i))).append(":");
-            sb.append(columnTypes.get(i).toString());
+
+            // omit FrozenType(...) from fields because it is currently implicit
+            sb.append(columnTypes.get(i).toString(true));
         }
         sb.append(')');
         return sb.toString();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 44c208f..180d713 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -61,7 +61,7 @@ public class UserType extends TupleType
         for (Pair<ByteBuffer, AbstractType> p : params.right)
         {
             columnNames.add(p.left);
-            columnTypes.add(p.right);
+            columnTypes.add(p.right.freeze());
         }
         return new UserType(keyspace, name, columnNames, columnTypes);
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java b/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java
index 7a6be71..2ba4dbf 100644
--- a/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java
+++ b/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java
@@ -148,9 +148,9 @@ public class CqlStorage extends AbstractCassandraStorage
         }
         AbstractType elementValidator;
         if (validator instanceof SetType)
-            elementValidator = ((SetType<?>) validator).elements;
+            elementValidator = ((SetType<?>) validator).getElementsType();
         else if (validator instanceof ListType)
-            elementValidator = ((ListType<?>) validator).elements;
+            elementValidator = ((ListType<?>) validator).getElementsType();
         else 
             return;
         
@@ -167,8 +167,8 @@ public class CqlStorage extends AbstractCassandraStorage
     /** set the values of set/list at and after the position of the tuple */
     private void setMapTupleValues(Tuple tuple, int position, Object value, AbstractType<?> validator) throws ExecException
     {
-        AbstractType<?> keyValidator = ((MapType<?, ?>) validator).keys;
-        AbstractType<?> valueValidator = ((MapType<?, ?>) validator).values;
+        AbstractType<?> keyValidator = ((MapType<?, ?>) validator).getKeysType();
+        AbstractType<?> valueValidator = ((MapType<?, ?>) validator).getValuesType();
         
         int i = 0;
         Tuple innerTuple = TupleFactory.getInstance().newTuple(((Map<?,?>) value).size());

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
index 2a5e809..29ae2fd 100644
--- a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java
@@ -19,8 +19,10 @@
 package org.apache.cassandra.serializers;
 
 import java.nio.ByteBuffer;
+import java.util.Collection;
 import java.util.List;
 
+import org.apache.cassandra.transport.Server;
 import org.apache.cassandra.utils.ByteBufferUtil;
 
 public abstract class CollectionSerializer<T> implements TypeSerializer<T>
@@ -35,17 +37,17 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
     {
         List<ByteBuffer> values = serializeValues(value);
         // See deserialize() for why using the protocol v3 variant is the right thing to do.
-        return pack(values, getElementCount(value), 3);
+        return pack(values, getElementCount(value), Server.VERSION_3);
     }
 
     public T deserialize(ByteBuffer bytes)
     {
         // The only cases we serialize/deserialize collections internally (i.e. not for the protocol sake),
         // is:
-        //  1) when collections are in UDT values
+        //  1) when collections are frozen
         //  2) for internal calls.
         // In both case, using the protocol 3 version variant is the right thing to do.
-        return deserializeForNativeProtocol(bytes, 3);
+        return deserializeForNativeProtocol(bytes, Server.VERSION_3);
     }
 
     public ByteBuffer reserializeToV3(ByteBuffer bytes)
@@ -55,11 +57,11 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
 
     public void validate(ByteBuffer bytes) throws MarshalException
     {
-        // Same thing than above
-        validateForNativeProtocol(bytes, 3);
+        // Same thing as above
+        validateForNativeProtocol(bytes, Server.VERSION_3);
     }
 
-    public static ByteBuffer pack(List<ByteBuffer> buffers, int elements, int version)
+    public static ByteBuffer pack(Collection<ByteBuffer> buffers, int elements, int version)
     {
         int size = 0;
         for (ByteBuffer bb : buffers)
@@ -74,7 +76,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
 
     protected static void writeCollectionSize(ByteBuffer output, int elements, int version)
     {
-        if (version >= 3)
+        if (version >= Server.VERSION_3)
             output.putInt(elements);
         else
             output.putShort((short)elements);
@@ -82,12 +84,12 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
 
     public static int readCollectionSize(ByteBuffer input, int version)
     {
-        return version >= 3 ? input.getInt() : ByteBufferUtil.readShortLength(input);
+        return version >= Server.VERSION_3 ? input.getInt() : ByteBufferUtil.readShortLength(input);
     }
 
     protected static int sizeOfCollectionSize(int elements, int version)
     {
-        return version >= 3 ? 4 : 2;
+        return version >= Server.VERSION_3 ? 4 : 2;
     }
 
     protected static void writeValue(ByteBuffer output, ByteBuffer value, int version)
@@ -113,7 +115,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
 
     public static ByteBuffer readValue(ByteBuffer input, int version)
     {
-        if (version >= 3)
+        if (version >= Server.VERSION_3)
         {
             int size = input.getInt();
             if (size < 0)
@@ -129,7 +131,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
 
     protected static int sizeOfValue(ByteBuffer value, int version)
     {
-        if (version >= 3)
+        if (version >= Server.VERSION_3)
         {
             return value == null ? 4 : 4 + value.remaining();
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/serializers/ListSerializer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java b/src/java/org/apache/cassandra/serializers/ListSerializer.java
index 7387e1b..aeee2b9 100644
--- a/src/java/org/apache/cassandra/serializers/ListSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java
@@ -18,12 +18,12 @@
 
 package org.apache.cassandra.serializers;
 
+import org.apache.cassandra.transport.Server;
+
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.*;
 
-import org.apache.cassandra.utils.ByteBufferUtil;
-
 public class ListSerializer<T> extends CollectionSerializer<List<T>>
 {
     // interning instances
@@ -111,6 +111,34 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
         }
     }
 
+    /**
+     * Returns the element at the given index in a list.
+     * @param serializedList a serialized list
+     * @param index the index to get
+     * @return the serialized element at the given index, or null if the index exceeds the list size
+     */
+    public ByteBuffer getElement(ByteBuffer serializedList, int index)
+    {
+        try
+        {
+            ByteBuffer input = serializedList.duplicate();
+            int n = readCollectionSize(input, Server.VERSION_3);
+            if (n <= index)
+                return null;
+
+            for (int i = 0; i < index; i++)
+            {
+                int length = input.getInt();
+                input.position(input.position() + length);
+            }
+            return readValue(input, Server.VERSION_3);
+        }
+        catch (BufferUnderflowException e)
+        {
+            throw new MarshalException("Not enough bytes to read a list");
+        }
+    }
+
     public String toString(List<T> value)
     {
         StringBuilder sb = new StringBuilder();
@@ -118,13 +146,9 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
         for (T element : value)
         {
             if (isFirst)
-            {
                 isFirst = false;
-            }
             else
-            {
                 sb.append("; ");
-            }
             sb.append(elements.toString(element));
         }
         return sb.toString();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/serializers/MapSerializer.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/serializers/MapSerializer.java b/src/java/org/apache/cassandra/serializers/MapSerializer.java
index dadadd0..bc8e509 100644
--- a/src/java/org/apache/cassandra/serializers/MapSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/MapSerializer.java
@@ -22,6 +22,8 @@ import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.*;
 
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.transport.Server;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.Pair;
 
@@ -114,6 +116,38 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
         }
     }
 
+    /**
+     * Given a serialized map, gets the value associated with a given key.
+     * @param serializedMap a serialized map
+     * @param serializedKey a serialized key
+     * @param keyType the key type for the map
+     * @return the value associated with the key if one exists, null otherwise
+     */
+    public ByteBuffer getSerializedValue(ByteBuffer serializedMap, ByteBuffer serializedKey, AbstractType keyType)
+    {
+        try
+        {
+            ByteBuffer input = serializedMap.duplicate();
+            int n = readCollectionSize(input, Server.VERSION_3);
+            for (int i = 0; i < n; i++)
+            {
+                ByteBuffer kbb = readValue(input, Server.VERSION_3);
+                ByteBuffer vbb = readValue(input, Server.VERSION_3);
+                int comparison = keyType.compare(kbb, serializedKey);
+                if (comparison == 0)
+                    return vbb;
+                else if (comparison > 0)
+                    // since the map is in sorted order, we know we've gone too far and the element doesn't exist
+                    return null;
+            }
+            return null;
+        }
+        catch (BufferUnderflowException e)
+        {
+            throw new MarshalException("Not enough bytes to read a map");
+        }
+    }
+
     public String toString(Map<K, V> value)
     {
         StringBuilder sb = new StringBuilder();
@@ -121,13 +155,9 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
         for (Map.Entry<K, V> element : value.entrySet())
         {
             if (isFirst)
-            {
                 isFirst = false;
-            }
             else
-            {
                 sb.append("; ");
-            }
             sb.append('(');
             sb.append(keys.toString(element.getKey()));
             sb.append(", ");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/transport/DataType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/transport/DataType.java b/src/java/org/apache/cassandra/transport/DataType.java
index a45d7ce..0ea353a 100644
--- a/src/java/org/apache/cassandra/transport/DataType.java
+++ b/src/java/org/apache/cassandra/transport/DataType.java
@@ -214,18 +214,18 @@ public enum DataType implements OptionCodec.Codecable<DataType>
             {
                 if (type instanceof ListType)
                 {
-                    return Pair.<DataType, Object>create(LIST, ((ListType)type).elements);
+                    return Pair.<DataType, Object>create(LIST, ((ListType)type).getElementsType());
                 }
                 else if (type instanceof MapType)
                 {
                     MapType mt = (MapType)type;
-                    return Pair.<DataType, Object>create(MAP, Arrays.asList(mt.keys, mt.values));
+                    return Pair.<DataType, Object>create(MAP, Arrays.asList(mt.getKeysType(), mt.getValuesType()));
                 }
-                else
+                else if (type instanceof SetType)
                 {
-                    assert type instanceof SetType;
-                    return Pair.<DataType, Object>create(SET, ((SetType)type).elements);
+                    return Pair.<DataType, Object>create(SET, ((SetType)type).getElementsType());
                 }
+                throw new AssertionError();
             }
 
             if (type instanceof UserType && version >= 3)
@@ -251,12 +251,12 @@ public enum DataType implements OptionCodec.Codecable<DataType>
                 case CUSTOM:
                     return TypeParser.parse((String)entry.right);
                 case LIST:
-                    return ListType.getInstance((AbstractType)entry.right);
+                    return ListType.getInstance((AbstractType)entry.right, true);
                 case SET:
-                    return SetType.getInstance((AbstractType)entry.right);
+                    return SetType.getInstance((AbstractType)entry.right, true);
                 case MAP:
                     List<AbstractType> l = (List<AbstractType>)entry.right;
-                    return MapType.getInstance(l.get(0), l.get(1));
+                    return MapType.getInstance(l.get(0), l.get(1), true);
                 case UDT:
                     return (AbstractType)entry.right;
                 case TUPLE:

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 6e4a5a9..aacfb4b 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.Schema;
-import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.Directories;
 import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.db.marshal.*;
@@ -45,7 +44,6 @@ import org.apache.cassandra.exceptions.*;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.service.StorageService;
-import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Base class for CQL tests.
@@ -96,10 +94,10 @@ public abstract class CQLTester
             {
                 try
                 {
-                    schemaChange(String.format("DROP TABLE %s.%s", KEYSPACE, tableToDrop));
+                    schemaChange(String.format("DROP TABLE IF EXISTS %s.%s", KEYSPACE, tableToDrop));
 
                     for (String typeName : typesToDrop)
-                        schemaChange(String.format("DROP TYPE %s.%s", KEYSPACE, typeName));
+                        schemaChange(String.format("DROP TYPE IF EXISTS %s.%s", KEYSPACE, typeName));
 
                     // Dropping doesn't delete the sstables. It's not a huge deal but it's cleaner to cleanup after us
                     // Thas said, we shouldn't delete blindly before the SSTableDeletingTask for the table we drop
@@ -186,6 +184,21 @@ public abstract class CQLTester
         schemaChange(fullQuery);
     }
 
+    protected void createTableMayThrow(String query) throws Throwable
+    {
+        currentTable = "table_" + seqNumber.getAndIncrement();
+        String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
+        logger.info(fullQuery);
+        try
+        {
+            QueryProcessor.executeOnceInternal(fullQuery);
+        }
+        catch (RuntimeException ex)
+        {
+            throw ex.getCause();
+        }
+    }
+
     protected void alterTable(String query)
     {
         String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
@@ -193,6 +206,20 @@ public abstract class CQLTester
         schemaChange(fullQuery);
     }
 
+    protected void alterTableMayThrow(String query) throws Throwable
+    {
+        String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
+        logger.info(fullQuery);
+        try
+        {
+            QueryProcessor.executeOnceInternal(fullQuery);
+        }
+        catch (RuntimeException ex)
+        {
+            throw ex.getCause();
+        }
+    }
+
     protected void createIndex(String query)
     {
         String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
@@ -200,6 +227,20 @@ public abstract class CQLTester
         schemaChange(fullQuery);
     }
 
+    protected void createIndexMayThrow(String query) throws Throwable
+    {
+        String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
+        logger.info(fullQuery);
+        try
+        {
+            QueryProcessor.executeOnceInternal(fullQuery);
+        }
+        catch (RuntimeException ex)
+        {
+            throw ex.getCause();
+        }
+    }
+
     private static void schemaChange(String query)
     {
         try
@@ -262,7 +303,7 @@ public abstract class CQLTester
         int i = 0;
         while (iter.hasNext() && i < rows.length)
         {
-            Object[] expected = rows[i++];
+            Object[] expected = rows[i];
             UntypedResultSet.Row actual = iter.next();
 
             Assert.assertEquals(String.format("Invalid number of (expected) values provided for row %d", i), meta.size(), expected.length);
@@ -278,6 +319,7 @@ public abstract class CQLTester
                     Assert.fail(String.format("Invalid value for row %d column %d (%s of type %s), expected <%s> but got <%s>",
                                               i, j, column.name, column.type.asCQL3Type(), formatValue(expectedByteValue, column.type), formatValue(actualValue, column.type)));
             }
+            i++;
         }
 
         if (iter.hasNext())
@@ -311,6 +353,11 @@ public abstract class CQLTester
 
     protected void assertInvalid(String query, Object... values) throws Throwable
     {
+        assertInvalidMessage(null, query, values);
+    }
+
+    protected void assertInvalidMessage(String errorMessage, String query, Object... values) throws Throwable
+    {
         try
         {
             execute(query, values);
@@ -321,7 +368,11 @@ public abstract class CQLTester
         }
         catch (InvalidRequestException e)
         {
-            // This is what we expect
+            if (errorMessage != null)
+            {
+                Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + e.getMessage() + "'",
+                        e.getMessage().contains(errorMessage));
+            }
         }
     }
 
@@ -401,7 +452,15 @@ public abstract class CQLTester
                 continue;
             }
 
-            buffers[i] = typeFor(value).decompose(serializeTuples(value));
+            try
+            {
+                buffers[i] = typeFor(value).decompose(serializeTuples(value));
+            }
+            catch (Exception ex)
+            {
+                logger.info("Error serializing query parameter {}:", value, ex);
+                throw ex;
+            }
         }
         return buffers;
     }
@@ -613,14 +672,14 @@ public abstract class CQLTester
         {
             List l = (List)value;
             AbstractType elt = l.isEmpty() ? BytesType.instance : typeFor(l.get(0));
-            return ListType.getInstance(elt);
+            return ListType.getInstance(elt, true);
         }
 
         if (value instanceof Set)
         {
             Set s = (Set)value;
             AbstractType elt = s.isEmpty() ? BytesType.instance : typeFor(s.iterator().next());
-            return SetType.getInstance(elt);
+            return SetType.getInstance(elt, true);
         }
 
         if (value instanceof Map)
@@ -638,7 +697,7 @@ public abstract class CQLTester
                 keys = typeFor(entry.getKey());
                 values = typeFor(entry.getValue());
             }
-            return MapType.getInstance(keys, values);
+            return MapType.getInstance(keys, values, true);
         }
 
         throw new IllegalArgumentException("Unsupported value type (value is " + value + ")");
@@ -674,5 +733,10 @@ public abstract class CQLTester
             sb.append(")");
             return sb.toString();
         }
+
+        public String toString()
+        {
+            return "TupleValue" + toCQLString();
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 205babc..3b1a826 100644
--- a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
+++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java
@@ -154,9 +154,9 @@ public class ColumnConditionTest
     {
         CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c list<int>)", "ks");
         Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
-        typeMap.put(ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance));
+        typeMap.put(ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance, true));
         CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
-        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
+        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
 
         List<Cell> cells = new ArrayList<>(columnValues.size());
         if (columnValues != null)
@@ -169,14 +169,14 @@ public class ColumnConditionTest
             };
         }
 
-        return bound.listAppliesTo(ListType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
+        return bound.listAppliesTo(ListType.getInstance(Int32Type.instance, true), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
     }
 
     @Test
     // sets use the same check as lists
     public void testListCollectionBoundAppliesTo() throws InvalidRequestException
     {
-        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance), null, null, null, null, null);
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance, true), null, null, null, null, null);
 
         // EQ
         ColumnCondition condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.EQ);
@@ -275,9 +275,9 @@ public class ColumnConditionTest
         assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
     }
 
-    private static Set<ByteBuffer> set(ByteBuffer... values)
+    private static SortedSet<ByteBuffer> set(ByteBuffer... values)
     {
-        Set results = new HashSet<ByteBuffer>(values.length);
+        SortedSet<ByteBuffer> results = new TreeSet<>(Int32Type.instance);
         results.addAll(Arrays.asList(values));
         return results;
     }
@@ -286,9 +286,9 @@ public class ColumnConditionTest
     {
         CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c set<int>)", "ks");
         Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
-        typeMap.put(ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance));
+        typeMap.put(ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance, true));
         CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
-        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
+        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
 
         List<Cell> cells = new ArrayList<>(columnValues.size());
         if (columnValues != null)
@@ -300,13 +300,13 @@ public class ColumnConditionTest
             };
         }
 
-        return bound.setAppliesTo(SetType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
+        return bound.setAppliesTo(SetType.getInstance(Int32Type.instance, true), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
     }
 
     @Test
     public void testSetCollectionBoundAppliesTo() throws InvalidRequestException
     {
-        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance), null, null, null, null, null);
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance, true), null, null, null, null, null);
 
         // EQ
         ColumnCondition condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.EQ);
@@ -419,9 +419,9 @@ public class ColumnConditionTest
     {
         CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b map<int, int>)", "ks");
         Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
-        typeMap.put(ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance));
+        typeMap.put(ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
         CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
-        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
+        ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
 
         List<Cell> cells = new ArrayList<>(columnValues.size());
         if (columnValues != null)
@@ -430,13 +430,13 @@ public class ColumnConditionTest
                 cells.add(new BufferCell(nameType.create(Composites.EMPTY, definition, entry.getKey()), entry.getValue()));
         }
 
-        return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, Int32Type.instance), cells.iterator(), conditionValues, bound.operator);
+        return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, Int32Type.instance, true), cells.iterator(), conditionValues, bound.operator);
     }
 
     @Test
     public void testMapCollectionBoundIsSatisfiedByValue() throws InvalidRequestException
     {
-        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, Int32Type.instance), null, null, null, null, null);
+        ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, Int32Type.instance, true), null, null, null, null, null);
 
         Map<ByteBuffer, ByteBuffer> placeholderMap = new TreeMap<>();
         placeholderMap.put(ONE, ONE);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java b/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java
new file mode 100644
index 0000000..203adae
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java
@@ -0,0 +1,791 @@
+/*
+ * 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.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.SyntaxException;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class FrozenCollectionsTest extends CQLTester
+{
+    @Test
+    public void testPartitionKeyUsage() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k frozen<set<int>> PRIMARY KEY, v int)");
+
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(), 1);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(1, 2, 3), 1);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(4, 5, 6), 0);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(7, 8, 9), 0);
+
+        // overwrite with an update
+        execute("UPDATE %s SET v=? WHERE k=?", 0, set());
+        execute("UPDATE %s SET v=? WHERE k=?", 0, set(1, 2, 3));
+
+        assertRows(execute("SELECT * FROM %s"),
+            row(set(), 0),
+            row(set(1, 2, 3), 0),
+            row(set(4, 5, 6), 0),
+            row(set(7, 8, 9), 0)
+        );
+
+        assertRows(execute("SELECT k FROM %s"),
+            row(set()),
+            row(set(1, 2, 3)),
+            row(set(4, 5, 6)),
+            row(set(7, 8, 9))
+        );
+
+        assertRows(execute("SELECT * FROM %s LIMIT 2"),
+                row(set(), 0),
+                row(set(1, 2, 3), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k=?", set(4, 5, 6)),
+            row(set(4, 5, 6), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k=?", set()),
+                row(set(), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k IN ?", list(set(4, 5, 6), set())),
+                row(set(4, 5, 6), 0),
+                row(set(), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE token(k) >= token(?)", set(4, 5, 6)),
+                row(set(4, 5, 6), 0),
+                row(set(7, 8, 9), 0)
+        );
+
+        assertInvalid("INSERT INTO %s (k, v) VALUES (null, 0)");
+
+        execute("DELETE FROM %s WHERE k=?", set());
+        execute("DELETE FROM %s WHERE k=?", set(4, 5, 6));
+        assertRows(execute("SELECT * FROM %s"),
+            row(set(1, 2, 3), 0),
+            row(set(7, 8, 9), 0)
+        );
+    }
+
+    @Test
+    public void testNestedPartitionKeyUsage() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k frozen<map<set<int>, list<int>>> PRIMARY KEY, v int)");
+
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(), 1);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(), list(1, 2, 3)), 0);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(1, 2, 3), list(1, 2, 3)), 1);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(4, 5, 6), list(1, 2, 3)), 0);
+        execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(7, 8, 9), list(1, 2, 3)), 0);
+
+        // overwrite with an update
+        execute("UPDATE %s SET v=? WHERE k=?", 0, map());
+        execute("UPDATE %s SET v=? WHERE k=?", 0, map(set(1, 2, 3), list(1, 2, 3)));
+
+        assertRows(execute("SELECT * FROM %s"),
+            row(map(), 0),
+            row(map(set(), list(1, 2, 3)), 0),
+            row(map(set(1, 2, 3), list(1, 2, 3)), 0),
+            row(map(set(4, 5, 6), list(1, 2, 3)), 0),
+            row(map(set(7, 8, 9), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT k FROM %s"),
+            row(map()),
+            row(map(set(), list(1, 2, 3))),
+            row(map(set(1, 2, 3), list(1, 2, 3))),
+            row(map(set(4, 5, 6), list(1, 2, 3))),
+            row(map(set(7, 8, 9), list(1, 2, 3)))
+        );
+
+        assertRows(execute("SELECT * FROM %s LIMIT 3"),
+            row(map(), 0),
+            row(map(set(), list(1, 2, 3)), 0),
+            row(map(set(1, 2, 3), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k=?", map(set(4, 5, 6), list(1, 2, 3))),
+            row(map(set(4, 5, 6), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k=?", map()),
+                row(map(), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k=?", map(set(), list(1, 2, 3))),
+                row(map(set(), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE k IN ?", list(map(set(4, 5, 6), list(1, 2, 3)), map(), map(set(), list(1, 2, 3)))),
+            row(map(set(4, 5, 6), list(1, 2, 3)), 0),
+            row(map(), 0),
+            row(map(set(), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE token(k) >= token(?)", map(set(4, 5, 6), list(1, 2, 3))),
+            row(map(set(4, 5, 6), list(1, 2, 3)), 0),
+            row(map(set(7, 8, 9), list(1, 2, 3)), 0)
+        );
+
+        execute("DELETE FROM %s WHERE k=?", map());
+        execute("DELETE FROM %s WHERE k=?", map(set(), list(1, 2, 3)));
+        execute("DELETE FROM %s WHERE k=?", map(set(4, 5, 6), list(1, 2, 3)));
+        assertRows(execute("SELECT * FROM %s"),
+            row(map(set(1, 2, 3), list(1, 2, 3)), 0),
+            row(map(set(7, 8, 9), list(1, 2, 3)), 0)
+        );
+
+    }
+
+    @Test
+    public void testClusteringKeyUsage() throws Throwable
+    {
+        for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
+        {
+            createTable("CREATE TABLE %s (a int, b frozen<set<int>>, c int, PRIMARY KEY (a, b))" + option);
+
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(), 1);
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(1, 2, 3), 1);
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(4, 5, 6), 0);
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(7, 8, 9), 0);
+
+            // overwrite with an update
+            execute("UPDATE %s SET c=? WHERE a=? AND b=?", 0, 0, set());
+            execute("UPDATE %s SET c=? WHERE a=? AND b=?", 0, 0, set(1, 2, 3));
+
+            assertRows(execute("SELECT * FROM %s"),
+                row(0, set(), 0),
+                row(0, set(1, 2, 3), 0),
+                row(0, set(4, 5, 6), 0),
+                row(0, set(7, 8, 9), 0)
+            );
+
+            assertRows(execute("SELECT b FROM %s"),
+                row(set()),
+                row(set(1, 2, 3)),
+                row(set(4, 5, 6)),
+                row(set(7, 8, 9))
+            );
+
+            assertRows(execute("SELECT * FROM %s LIMIT 2"),
+                row(0, set(), 0),
+                row(0, set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, set(4, 5, 6)),
+                row(0, set(4, 5, 6), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, set()),
+                row(0, set(), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b IN ?", 0, list(set(4, 5, 6), set())),
+                row(0, set(), 0),
+                row(0, set(4, 5, 6), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ?", 0, set(4, 5, 6)),
+                row(0, set(7, 8, 9), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b >= ?", 0, set(4, 5, 6)),
+                row(0, set(4, 5, 6), 0),
+                row(0, set(7, 8, 9), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b < ?", 0, set(4, 5, 6)),
+                row(0, set(), 0),
+                row(0, set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b <= ?", 0, set(4, 5, 6)),
+                row(0, set(), 0),
+                row(0, set(1, 2, 3), 0),
+                row(0, set(4, 5, 6), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ? AND b <= ?", 0, set(1, 2, 3), set(4, 5, 6)),
+                row(0, set(4, 5, 6), 0)
+            );
+
+            execute("DELETE FROM %s WHERE a=? AND b=?", 0, set());
+            execute("DELETE FROM %s WHERE a=? AND b=?", 0, set(4, 5, 6));
+            assertRows(execute("SELECT * FROM %s"),
+                row(0, set(1, 2, 3), 0),
+                row(0, set(7, 8, 9), 0)
+            );
+        }
+    }
+
+    @Test
+    public void testNestedClusteringKeyUsage() throws Throwable
+    {
+        for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
+        {
+            createTable("CREATE TABLE %s (a int, b frozen<map<set<int>, list<int>>>, c frozen<set<int>>, d int, PRIMARY KEY (a, b, c))" + option);
+
+            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(), set(), 0);
+            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(), list(1, 2, 3)), set(), 0);
+            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0);
+            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0);
+            execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0);
+
+            assertRows(execute("SELECT * FROM %s"),
+                row(0, map(), set(), 0),
+                row(0, map(set(), list(1, 2, 3)), set(), 0),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT b FROM %s"),
+                row(map()),
+                row(map(set(), list(1, 2, 3))),
+                row(map(set(1, 2, 3), list(1, 2, 3))),
+                row(map(set(4, 5, 6), list(1, 2, 3))),
+                row(map(set(7, 8, 9), list(1, 2, 3)))
+            );
+
+            assertRows(execute("SELECT c FROM %s"),
+                row(set()),
+                row(set()),
+                row(set(1, 2, 3)),
+                row(set(1, 2, 3)),
+                row(set(1, 2, 3))
+            );
+
+            assertRows(execute("SELECT * FROM %s LIMIT 3"),
+                row(0, map(), set(), 0),
+                row(0, map(set(), list(1, 2, 3)), set(), 0),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=0 ORDER BY b DESC LIMIT 4"),
+                row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(), list(1, 2, 3)), set(), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map()),
+                row(0, map(), set(), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map(set(), list(1, 2, 3))),
+                row(0, map(set(), list(1, 2, 3)), set(), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map(set(1, 2, 3), list(1, 2, 3))),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set()),
+                    row(0, map(set(), list(1, 2, 3)), set(), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND (b, c) IN ?", 0, list(tuple(map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)),
+                                                                                     tuple(map(), set()))),
+                row(0, map(), set(), 0),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
+                row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b >= ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b < ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
+                row(0, map(), set(), 0),
+                row(0, map(set(), list(1, 2, 3)), set(), 0),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b <= ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
+                row(0, map(), set(), 0),
+                row(0, map(set(), list(1, 2, 3)), set(), 0),
+                row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ? AND b <= ?", 0, map(set(1, 2, 3), list(1, 2, 3)), map(set(4, 5, 6), list(1, 2, 3))),
+                row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+
+            execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(), set());
+            assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(), set()));
+
+            execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set());
+            assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set()));
+
+            execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3));
+            assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)));
+
+            assertRows(execute("SELECT * FROM %s"),
+                    row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
+                    row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
+            );
+        }
+    }
+
+    @Test
+    public void testNormalColumnUsage() throws Throwable
+    {
+        for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
+        {
+            createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<map<set<int>, list<int>>>, c frozen<set<int>>)" + option);
+
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, map(), set());
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, map(set(), list(99999, 999999, 99999)), set());
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3));
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3));
+            execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 4, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3));
+
+            // overwrite with update
+            execute ("UPDATE %s SET b=? WHERE a=?", map(set(), list(1, 2, 3)), 1);
+
+            assertRows(execute("SELECT * FROM %s"),
+                row(0, map(), set()),
+                row(1, map(set(), list(1, 2, 3)), set()),
+                row(2, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3)),
+                row(3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)),
+                row(4, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3))
+            );
+
+            assertRows(execute("SELECT b FROM %s"),
+                row(map()),
+                row(map(set(), list(1, 2, 3))),
+                row(map(set(1, 2, 3), list(1, 2, 3))),
+                row(map(set(4, 5, 6), list(1, 2, 3))),
+                row(map(set(7, 8, 9), list(1, 2, 3)))
+            );
+
+            assertRows(execute("SELECT c FROM %s"),
+                row(set()),
+                row(set()),
+                row(set(1, 2, 3)),
+                row(set(1, 2, 3)),
+                row(set(1, 2, 3))
+            );
+
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 3),
+                row(3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3))
+            );
+
+            execute("UPDATE %s SET b=? WHERE a=?", null, 1);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 1),
+                row(1, null, set())
+            );
+
+            execute("UPDATE %s SET b=? WHERE a=?", map(), 1);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 1),
+                row(1, map(), set())
+            );
+
+            execute("UPDATE %s SET c=? WHERE a=?", null, 2);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 2),
+                row(2, map(set(1, 2, 3), list(1, 2, 3)), null)
+            );
+
+            execute("UPDATE %s SET c=? WHERE a=?", set(), 2);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 2),
+                    row(2, map(set(1, 2, 3), list(1, 2, 3)), set())
+            );
+
+            execute("DELETE b FROM %s WHERE a=?", 3);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 3),
+                row(3, null, set(1, 2, 3))
+            );
+
+            execute("DELETE c FROM %s WHERE a=?", 4);
+            assertRows(execute("SELECT * FROM %s WHERE a=?", 4),
+                row(4, map(set(7, 8, 9), list(1, 2, 3)), null)
+            );
+        }
+    }
+
+    @Test
+    public void testStaticColumnUsage() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int, b int, c frozen<map<set<int>, list<int>>> static, d int, PRIMARY KEY (a, b))");
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, map(), 0);
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, map(), 0);
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, map(set(), list(1, 2, 3)), 0);
+        execute("INSERT INTO %s (a, b, d) VALUES (?, ?, ?)", 1, 1, 0);
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0);
+
+        assertRows(execute("SELECT * FROM %s"),
+            row(0, 0, map(), 0),
+            row(0, 1, map(), 0),
+            row(1, 0, map(set(), list(1, 2, 3)), 0),
+            row(1, 1, map(set(), list(1, 2, 3)), 0),
+            row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, 1),
+            row(0, 1, map(), 0)
+        );
+
+        execute("DELETE c FROM %s WHERE a=?", 0);
+        assertRows(execute("SELECT * FROM %s"),
+                row(0, 0, null, 0),
+                row(0, 1, null, 0),
+                row(1, 0, map(set(), list(1, 2, 3)), 0),
+                row(1, 1, map(set(), list(1, 2, 3)), 0),
+                row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
+        );
+
+        execute("DELETE FROM %s WHERE a=?", 0);
+        assertRows(execute("SELECT * FROM %s"),
+                row(1, 0, map(set(), list(1, 2, 3)), 0),
+                row(1, 1, map(set(), list(1, 2, 3)), 0),
+                row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
+        );
+
+        execute("UPDATE %s SET c=? WHERE a=?", map(set(1, 2, 3), list(1, 2, 3)), 1);
+        assertRows(execute("SELECT * FROM %s"),
+                row(1, 0, map(set(1, 2, 3), list(1, 2, 3)), 0),
+                row(1, 1, map(set(1, 2, 3), list(1, 2, 3)), 0),
+                row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
+        );
+    }
+
+    private void assertInvalidCreateWithMessage(String createTableStatement, String errorMessage) throws Throwable
+    {
+         try
+        {
+            createTableMayThrow(createTableStatement);
+            Assert.fail("Expected CREATE TABLE statement to error: " + createTableStatement);
+        }
+        catch (InvalidRequestException | ConfigurationException | SyntaxException ex)
+        {
+            Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
+                    ex.getMessage().contains(errorMessage));
+        }
+    }
+
+    private void assertInvalidAlterWithMessage(String createTableStatement, String errorMessage) throws Throwable
+    {
+        try
+        {
+            alterTableMayThrow(createTableStatement);
+            Assert.fail("Expected CREATE TABLE statement to error: " + createTableStatement);
+        }
+        catch (InvalidRequestException | ConfigurationException ex)
+        {
+            Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
+                    ex.getMessage().contains(errorMessage));
+        }
+    }
+
+    @Test
+    public void testInvalidOperations() throws Throwable
+    {
+        // lists
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, l frozen<list<int>>)");
+        assertInvalid("UPDATE %s SET l[?]=? WHERE k=?", 0, 0, 0);
+        assertInvalid("UPDATE %s SET l = ? + l WHERE k=?", list(0), 0);
+        assertInvalid("UPDATE %s SET l = l + ? WHERE k=?", list(4), 0);
+        assertInvalid("UPDATE %s SET l = l - ? WHERE k=?", list(3), 0);
+        assertInvalid("DELETE l[?] FROM %s WHERE k=?", 0, 0);
+
+        // sets
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, s frozen<set<int>>)");
+        assertInvalid("UPDATE %s SET s = s + ? WHERE k=?", set(0), 0);
+        assertInvalid("UPDATE %s SET s = s - ? WHERE k=?", set(3), 0);
+
+        // maps
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<map<int, int>>)");
+        assertInvalid("UPDATE %s SET m[?]=? WHERE k=?", 0, 0, 0);
+        assertInvalid("UPDATE %s SET m = m + ? WHERE k=?", map(4, 4), 0);
+        assertInvalid("DELETE m[?] FROM %s WHERE k=?", 0, 0);
+
+        assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t set<set<int>>)",
+                "Non-frozen collections are not allowed inside collections");
+
+        assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen<set<counter>>)",
+                                       "Counters are not allowed inside collections");
+
+        assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen<text>)",
+                "frozen<> is only allowed on collections, tuples, and user-defined types");
+    }
+
+    @Test
+    public void testAltering() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a int, b frozen<list<int>>, c frozen<list<int>>, PRIMARY KEY (a, b))");
+
+        alterTable("ALTER TABLE %s ALTER c TYPE frozen<list<blob>>");
+
+        assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE frozen<list<blob>>",
+                                      "types are not order-compatible");
+
+        assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE list<int>",
+                                      "types are not order-compatible");
+
+        assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE list<blob>",
+                                      "types are incompatible");
+
+        alterTable("ALTER TABLE %s DROP c");
+        alterTable("ALTER TABLE %s ADD c frozen<set<int>>");
+        assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen<set<blob>>",
+                                      "types are incompatible");
+
+        alterTable("ALTER TABLE %s DROP c");
+        alterTable("ALTER TABLE %s ADD c frozen<map<int, int>>");
+        assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen<map<blob, int>>",
+                                      "types are incompatible");
+        alterTable("ALTER TABLE %s ALTER c TYPE frozen<map<int, blob>>");
+    }
+
+    private void assertInvalidIndexCreationWithMessage(String statement, String errorMessage) throws Throwable
+    {
+        try
+        {
+            createIndexMayThrow(statement);
+            Assert.fail("Expected index creation to fail: " + statement);
+        }
+        catch (InvalidRequestException ex)
+        {
+            Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
+                              ex.getMessage().contains(errorMessage));
+        }
+    }
+
+    @Test
+    public void testSecondaryIndex() throws Throwable
+    {
+        createTable("CREATE TABLE %s (a frozen<map<int, text>> PRIMARY KEY, b frozen<map<int, text>>)");
+
+        // for now, we don't support indexing values or keys of collections in the primary key
+        assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (full(a))", "Cannot create secondary index on partition key column");
+        assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(a))", "Cannot create index on keys of frozen<map> column");
+        assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(b))", "Cannot create index on keys of frozen<map> column");
+
+        createTable("CREATE TABLE %s (a int, b frozen<list<int>>, c frozen<set<int>>, d frozen<map<int, text>>, PRIMARY KEY (a, b))");
+
+        createIndex("CREATE INDEX ON %s (full(b))");
+        createIndex("CREATE INDEX ON %s (full(c))");
+        createIndex("CREATE INDEX ON %s (full(d))");
+
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, list(1, 2, 3), set(1, 2, 3), map(1, "a"));
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, list(4, 5, 6), set(1, 2, 3), map(1, "a"));
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, list(1, 2, 3), set(4, 5, 6), map(2, "b"));
+        execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, list(4, 5, 6), set(4, 5, 6), map(2, "b"));
+
+        // CONTAINS KEY doesn't work on non-maps
+        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
+                             "SELECT * FROM %s WHERE b CONTAINS KEY ?", 1);
+
+        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
+                             "SELECT * FROM %s WHERE b CONTAINS KEY ? ALLOW FILTERING", 1);
+
+        assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
+                             "SELECT * FROM %s WHERE c CONTAINS KEY ?", 1);
+
+        // normal indexes on frozen collections don't support CONTAINS or CONTAINS KEY
+        assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
+                             "SELECT * FROM %s WHERE b CONTAINS ?", 1);
+
+        assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
+                             "SELECT * FROM %s WHERE b CONTAINS ? ALLOW FILTERING", 1);
+
+        assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
+                             "SELECT * FROM %s WHERE d CONTAINS KEY ?", 1);
+
+        assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
+                             "SELECT * FROM %s WHERE d CONTAINS KEY ? ALLOW FILTERING", 1);
+
+        assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
+                             "SELECT * FROM %s WHERE b CONTAINS ? AND d CONTAINS KEY ? ALLOW FILTERING", 1, 1);
+
+        // index lookup on b
+        assertRows(execute("SELECT * FROM %s WHERE b=?", list(1, 2, 3)),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
+        );
+
+        assertEmpty(execute("SELECT * FROM %s WHERE b=?", list(-1)));
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND c=?", list(1, 2, 3), set(4, 5, 6));
+        assertRows(execute("SELECT * FROM %s WHERE b=? AND c=? ALLOW FILTERING", list(1, 2, 3), set(4, 5, 6)),
+            row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND c CONTAINS ?", list(1, 2, 3), 5);
+        assertRows(execute("SELECT * FROM %s WHERE b=? AND c CONTAINS ? ALLOW FILTERING", list(1, 2, 3), 5),
+            row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d=?", list(1, 2, 3), map(1, "a"));
+        assertRows(execute("SELECT * FROM %s WHERE b=? AND d=? ALLOW FILTERING", list(1, 2, 3), map(1, "a")),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d CONTAINS ?", list(1, 2, 3), "a");
+        assertRows(execute("SELECT * FROM %s WHERE b=? AND d CONTAINS ? ALLOW FILTERING", list(1, 2, 3), "a"),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d CONTAINS KEY ?", list(1, 2, 3), 1);
+        assertRows(execute("SELECT * FROM %s WHERE b=? AND d CONTAINS KEY ? ALLOW FILTERING", list(1, 2, 3), 1),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        // index lookup on c
+        assertRows(execute("SELECT * FROM %s WHERE c=?", set(1, 2, 3)),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        // ordering of c should not matter
+        assertRows(execute("SELECT * FROM %s WHERE c=?", set(2, 1, 3)),
+                row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+                row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertEmpty(execute("SELECT * FROM %s WHERE c=?", set(-1)));
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND b=?", set(1, 2, 3), list(1, 2, 3));
+        assertRows(execute("SELECT * FROM %s WHERE c=? AND b=? ALLOW FILTERING", set(1, 2, 3), list(1, 2, 3)),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND b CONTAINS ?", set(1, 2, 3), 1);
+        assertRows(execute("SELECT * FROM %s WHERE c=? AND b CONTAINS ? ALLOW FILTERING", set(1, 2, 3), 1),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d = ?", set(1, 2, 3), map(1, "a"));
+        assertRows(execute("SELECT * FROM %s WHERE c=? AND d = ? ALLOW FILTERING", set(1, 2, 3), map(1, "a")),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d CONTAINS ?", set(1, 2, 3), "a");
+        assertRows(execute("SELECT * FROM %s WHERE c=? AND d CONTAINS ? ALLOW FILTERING", set(1, 2, 3), "a"),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d CONTAINS KEY ?", set(1, 2, 3), 1);
+        assertRows(execute("SELECT * FROM %s WHERE c=? AND d CONTAINS KEY ? ALLOW FILTERING", set(1, 2, 3), 1),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        // index lookup on d
+        assertRows(execute("SELECT * FROM %s WHERE d=?", map(1, "a")),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE d=?", map(2, "b")),
+            row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b")),
+            row(1, list(4, 5, 6), set(4, 5, 6), map(2, "b"))
+        );
+
+        assertEmpty(execute("SELECT * FROM %s WHERE d=?", map(3, "c")));
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND c=?", map(1, "a"), set(1, 2, 3));
+        assertRows(execute("SELECT * FROM %s WHERE d=? AND b=? ALLOW FILTERING", map(1, "a"), list(1, 2, 3)),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND b CONTAINS ?", map(1, "a"), 3);
+        assertRows(execute("SELECT * FROM %s WHERE d=? AND b CONTAINS ? ALLOW FILTERING", map(1, "a"), 3),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND b=? AND c=?", map(1, "a"), list(1, 2, 3), set(1, 2, 3));
+        assertRows(execute("SELECT * FROM %s WHERE d=? AND b=? AND c=? ALLOW FILTERING", map(1, "a"), list(1, 2, 3), set(1, 2, 3)),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE d=? AND b CONTAINS ? AND c CONTAINS ? ALLOW FILTERING", map(1, "a"), 2, 2),
+            row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
+        );
+
+        execute("DELETE d FROM %s WHERE a=? AND b=?", 0, list(1, 2, 3));
+        assertRows(execute("SELECT * FROM %s WHERE d=?", map(1, "a")),
+            row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
+        );
+    }
+
+    @Test
+    public void testUserDefinedTypes() throws Throwable
+    {
+        String myType = createType("CREATE TYPE %s (a set<int>, b tuple<list<int>>)");
+        createTable("CREATE TABLE %s (k int PRIMARY KEY, v frozen<" + myType + ">)");
+        execute("INSERT INTO %s (k, v) VALUES (?, {a: ?, b: ?})", 0, set(1, 2, 3), tuple(list(1, 2, 3)));
+        assertRows(execute("SELECT v.a, v.b FROM %s WHERE k=?", 0),
+            row(set(1, 2, 3), tuple(list(1, 2, 3)))
+        );
+    }
+
+    private static String clean(String classname)
+    {
+        return StringUtils.remove(classname, "org.apache.cassandra.db.marshal.");
+    }
+
+    @Test
+    public void testToString()
+    {
+        // set<frozen<list<int>>>
+        SetType t = SetType.getInstance(ListType.getInstance(Int32Type.instance, false), true);
+        assertEquals("SetType(FrozenType(ListType(Int32Type)))", clean(t.toString()));
+        assertEquals("SetType(ListType(Int32Type))", clean(t.toString(true)));
+
+        // frozen<set<list<int>>>
+        t = SetType.getInstance(ListType.getInstance(Int32Type.instance, false), false);
+        assertEquals("FrozenType(SetType(ListType(Int32Type)))", clean(t.toString()));
+        assertEquals("SetType(ListType(Int32Type))", clean(t.toString(true)));
+
+        // map<frozen<list<int>>, int>
+        MapType m = MapType.getInstance(ListType.getInstance(Int32Type.instance, false), Int32Type.instance, true);
+        assertEquals("MapType(FrozenType(ListType(Int32Type)),Int32Type)", clean(m.toString()));
+        assertEquals("MapType(ListType(Int32Type),Int32Type)", clean(m.toString(true)));
+
+        // frozen<map<list<int>, int>>
+        m = MapType.getInstance(ListType.getInstance(Int32Type.instance, false), Int32Type.instance, false);
+        assertEquals("FrozenType(MapType(ListType(Int32Type),Int32Type))", clean(m.toString()));
+        assertEquals("MapType(ListType(Int32Type),Int32Type)", clean(m.toString(true)));
+
+        // tuple<set<int>>
+        List<AbstractType<?>> types = new ArrayList<>();
+        types.add(SetType.getInstance(Int32Type.instance, true));
+        TupleType tuple = new TupleType(types);
+        assertEquals("TupleType(SetType(Int32Type))", clean(tuple.toString()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java
index 53e7e71..ce935e3 100644
--- a/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java
+++ b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java
@@ -24,26 +24,30 @@ public class TupleTypeTest extends CQLTester
     @Test
     public void testTuplePutAndGet() throws Throwable
     {
-        createTable("CREATE TABLE %s (k int PRIMARY KEY, t frozen<tuple<int, text, double>>)");
+        String[] valueTypes = {"frozen<tuple<int, text, double>>", "tuple<int, text, double>"};
+        for (String valueType : valueTypes)
+        {
+            createTable("CREATE TABLE %s (k int PRIMARY KEY, t " + valueType + ")");
 
-        execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo", 3.4));
-        execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, "bar", 0.2));
-        assertAllRows(
-            row(0, tuple(3, "foo", 3.4)),
-            row(1, tuple(8, "bar", 0.2))
-        );
+            execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo", 3.4));
+            execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, "bar", 0.2));
+            assertAllRows(
+                row(0, tuple(3, "foo", 3.4)),
+                row(1, tuple(8, "bar", 0.2))
+            );
 
-        // nulls
-        execute("INSERT INTO %s (k, t) VALUES (?, ?)", 2, tuple(5, null, 3.4));
-        assertRows(execute("SELECT * FROM %s WHERE k=?", 2),
-            row(2, tuple(5, null, 3.4))
-        );
+            // nulls
+            execute("INSERT INTO %s (k, t) VALUES (?, ?)", 2, tuple(5, null, 3.4));
+            assertRows(execute("SELECT * FROM %s WHERE k=?", 2),
+                row(2, tuple(5, null, 3.4))
+            );
 
-        // incomplete tuple
-        execute("INSERT INTO %s (k, t) VALUES (?, ?)", 3, tuple(5, "bar"));
-        assertRows(execute("SELECT * FROM %s WHERE k=?", 3),
-            row(3, tuple(5, "bar"))
-        );
+            // incomplete tuple
+            execute("INSERT INTO %s (k, t) VALUES (?, ?)", 3, tuple(5, "bar"));
+            assertRows(execute("SELECT * FROM %s WHERE k=?", 3),
+                row(3, tuple(5, "bar"))
+            );
+        }
     }
 
     @Test
@@ -94,10 +98,4 @@ public class TupleTypeTest extends CQLTester
         assertInvalidSyntax("INSERT INTO %s (k, t) VALUES (0, ())");
         assertInvalid("INSERT INTO %s (k, t) VALUES (0, (2, 'foo', 3.1, 'bar'))");
     }
-
-    @Test
-    public void testNonFrozenTuple() throws Throwable
-    {
-        assertInvalid("CREATE TABLE wrong (k int PRIMARY KEY, v tuple<int, text>)");
-    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java b/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java
index 18156c3..92990e9 100644
--- a/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java
+++ b/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java
@@ -36,7 +36,7 @@ public class CollectionTypeTest
     @Test
     public void testListComparison()
     {
-        ListType<String> lt = ListType.getInstance(UTF8Type.instance);
+        ListType<String> lt = ListType.getInstance(UTF8Type.instance, true);
 
         ByteBuffer[] lists = new ByteBuffer[] {
             ByteBufferUtil.EMPTY_BYTE_BUFFER,
@@ -63,7 +63,7 @@ public class CollectionTypeTest
     @Test
     public void testSetComparison()
     {
-        SetType<String> st = SetType.getInstance(UTF8Type.instance);
+        SetType<String> st = SetType.getInstance(UTF8Type.instance, true);
 
         ByteBuffer[] sets = new ByteBuffer[] {
             ByteBufferUtil.EMPTY_BYTE_BUFFER,
@@ -90,7 +90,7 @@ public class CollectionTypeTest
     @Test
     public void testMapComparison()
     {
-        MapType<String, String> mt = MapType.getInstance(UTF8Type.instance, UTF8Type.instance);
+        MapType<String, String> mt = MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true);
 
         ByteBuffer[] maps = new ByteBuffer[] {
             ByteBufferUtil.EMPTY_BYTE_BUFFER,
@@ -119,8 +119,8 @@ public class CollectionTypeTest
     @Test
     public void listSerDerTest()
     {
-        ListSerializer<String> sls = ListType.getInstance(UTF8Type.instance).getSerializer();
-        ListSerializer<Integer> ils = ListType.getInstance(Int32Type.instance).getSerializer();
+        ListSerializer<String> sls = ListType.getInstance(UTF8Type.instance, true).getSerializer();
+        ListSerializer<Integer> ils = ListType.getInstance(Int32Type.instance, true).getSerializer();
 
         List<String> sl = Arrays.asList("Foo", "Bar");
         List<Integer> il = Arrays.asList(3, 1, 5);
@@ -143,8 +143,8 @@ public class CollectionTypeTest
     @Test
     public void setSerDerTest()
     {
-        SetSerializer<String> sss = SetType.getInstance(UTF8Type.instance).getSerializer();
-        SetSerializer<Integer> iss = SetType.getInstance(Int32Type.instance).getSerializer();
+        SetSerializer<String> sss = SetType.getInstance(UTF8Type.instance, true).getSerializer();
+        SetSerializer<Integer> iss = SetType.getInstance(Int32Type.instance, true).getSerializer();
 
         Set<String> ss = new HashSet(){{ add("Foo"); add("Bar"); }};
         Set<Integer> is = new HashSet(){{ add(3); add(1); add(5); }};
@@ -167,8 +167,8 @@ public class CollectionTypeTest
     @Test
     public void setMapDerTest()
     {
-        MapSerializer<String, String> sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance).getSerializer();
-        MapSerializer<Integer, Integer> ims = MapType.getInstance(Int32Type.instance, Int32Type.instance).getSerializer();
+        MapSerializer<String, String> sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true).getSerializer();
+        MapSerializer<Integer, Integer> ims = MapType.getInstance(Int32Type.instance, Int32Type.instance, true).getSerializer();
 
         Map<String, String> sm = new HashMap(){{ put("Foo", "xxx"); put("Bar", "yyy"); }};
         Map<Integer, Integer> im = new HashMap(){{ put(3, 0); put(1, 8); put(5, 2); }};
@@ -187,8 +187,8 @@ public class CollectionTypeTest
         // non map value
         assertInvalid(sms, UTF8Type.instance.getSerializer().serialize("foo"));
 
-        MapSerializer<Integer, String> sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance).getSerializer();
-        MapSerializer<String, Integer> isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance).getSerializer();
+        MapSerializer<Integer, String> sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance, true).getSerializer();
+        MapSerializer<String, Integer> isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance, true).getSerializer();
 
         // only key are invalid
         assertInvalid(isms, sb);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/test/unit/org/apache/cassandra/transport/SerDeserTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/transport/SerDeserTest.java b/test/unit/org/apache/cassandra/transport/SerDeserTest.java
index 9b66efb..649f7a2 100644
--- a/test/unit/org/apache/cassandra/transport/SerDeserTest.java
+++ b/test/unit/org/apache/cassandra/transport/SerDeserTest.java
@@ -31,7 +31,6 @@ import org.apache.cassandra.serializers.CollectionSerializer;
 import org.apache.cassandra.transport.Event.TopologyChange;
 import org.apache.cassandra.transport.Event.SchemaChange;
 import org.apache.cassandra.transport.Event.StatusChange;
-import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.Pair;
 
@@ -53,7 +52,7 @@ public class SerDeserTest
     public void collectionSerDeserTest(int version) throws Exception
     {
         // Lists
-        ListType<?> lt = ListType.getInstance(Int32Type.instance);
+        ListType<?> lt = ListType.getInstance(Int32Type.instance, true);
         List<Integer> l = Arrays.asList(2, 6, 1, 9);
 
         List<ByteBuffer> lb = new ArrayList<>(l.size());
@@ -63,7 +62,7 @@ public class SerDeserTest
         assertEquals(l, lt.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(lb, lb.size(), version), version));
 
         // Sets
-        SetType<?> st = SetType.getInstance(UTF8Type.instance);
+        SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
         Set<String> s = new LinkedHashSet<>();
         s.addAll(Arrays.asList("bar", "foo", "zee"));
 
@@ -74,7 +73,7 @@ public class SerDeserTest
         assertEquals(s, st.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(sb, sb.size(), version), version));
 
         // Maps
-        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance);
+        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true);
         Map<String, Long> m = new LinkedHashMap<>();
         m.put("bar", 12L);
         m.put("foo", 42L);
@@ -165,9 +164,9 @@ public class SerDeserTest
 
     public void udtSerDeserTest(int version) throws Exception
     {
-        ListType<?> lt = ListType.getInstance(Int32Type.instance);
-        SetType<?> st = SetType.getInstance(UTF8Type.instance);
-        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance);
+        ListType<?> lt = ListType.getInstance(Int32Type.instance, true);
+        SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
+        MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true);
 
         UserType udt = new UserType("ks",
                                     bb("myType"),

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java b/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java
index 6480d7a..bfa58ea 100644
--- a/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java
+++ b/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java
@@ -33,7 +33,7 @@ public class Lists extends Generator<List>
 
     public Lists(String name, Generator valueType, GeneratorConfig config)
     {
-        super(ListType.getInstance(valueType.type), config, name, List.class);
+        super(ListType.getInstance(valueType.type, true), config, name, List.class);
         this.valueType = valueType;
         buffer = new Object[(int) sizeDistribution.maxValue()];
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java
----------------------------------------------------------------------
diff --git a/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java b/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java
index 8246286..5c17b4e 100644
--- a/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java
+++ b/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java
@@ -32,7 +32,7 @@ public class Sets extends Generator<Set>
 
     public Sets(String name, Generator valueType, GeneratorConfig config)
     {
-        super(SetType.getInstance(valueType.type), config, name, Set.class);
+        super(SetType.getInstance(valueType.type, true), config, name, Set.class);
         this.valueType = valueType;
     }
 


[4/4] cassandra git commit: Merge branch 'cassandra-2.1' into trunk

Posted by ty...@apache.org.
Merge branch 'cassandra-2.1' into trunk

Conflicts:
	src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
	src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
	src/java/org/apache/cassandra/db/marshal/ListType.java
	src/java/org/apache/cassandra/db/marshal/TypeParser.java
	src/java/org/apache/cassandra/serializers/ListSerializer.java
	src/java/org/apache/cassandra/serializers/MapSerializer.java


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

Branch: refs/heads/trunk
Commit: fb4356a36a3066bf1c35d6f5e5b8472619b47960
Parents: f2e2862 ee55f36
Author: Tyler Hobbs <ty...@datastax.com>
Authored: Tue Nov 11 13:33:31 2014 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Tue Nov 11 13:33:31 2014 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 bin/cqlsh                                       |  18 +
 pylib/cqlshlib/cql3handling.py                  |  19 +-
 .../apache/cassandra/cql3/AbstractMarker.java   |   2 +-
 src/java/org/apache/cassandra/cql3/CQL3Row.java |   2 +-
 .../org/apache/cassandra/cql3/CQL3Type.java     | 158 ++--
 .../apache/cassandra/cql3/ColumnCondition.java  | 275 ++++---
 .../org/apache/cassandra/cql3/Constants.java    |   2 +-
 src/java/org/apache/cassandra/cql3/Cql.g        |  14 +-
 src/java/org/apache/cassandra/cql3/Lists.java   |  64 +-
 src/java/org/apache/cassandra/cql3/Maps.java    |  61 +-
 .../org/apache/cassandra/cql3/Operation.java    |  16 +-
 src/java/org/apache/cassandra/cql3/Sets.java    |  80 +-
 src/java/org/apache/cassandra/cql3/Term.java    |   6 +
 src/java/org/apache/cassandra/cql3/Tuples.java  |  13 +-
 .../apache/cassandra/cql3/UntypedResultSet.java |   6 +-
 .../apache/cassandra/cql3/UpdateParameters.java |   2 +-
 .../org/apache/cassandra/cql3/UserTypes.java    |   3 +-
 .../cql3/statements/AlterTableStatement.java    |  39 +-
 .../cql3/statements/AlterTypeStatement.java     |  18 +-
 .../cql3/statements/CreateIndexStatement.java   |  24 +-
 .../cql3/statements/CreateTableStatement.java   |  28 +-
 .../cql3/statements/DeleteStatement.java        |   2 +-
 .../cql3/statements/DropTypeStatement.java      |   6 +-
 .../cassandra/cql3/statements/IndexTarget.java  |  23 +-
 .../cassandra/cql3/statements/Restriction.java  |   6 +
 .../cql3/statements/SelectStatement.java        |  84 +-
 .../statements/SingleColumnRestriction.java     |  24 +
 .../org/apache/cassandra/db/CFRowAdder.java     |   4 +-
 .../db/composites/AbstractCellNameType.java     |   4 +-
 .../cassandra/db/composites/CellNameType.java   |   2 +-
 .../db/composites/CompositesBuilder.java        |  41 +
 .../composites/CompoundSparseCellNameType.java  |   5 +-
 .../cassandra/db/filter/ExtendedFilter.java     |  47 +-
 .../cassandra/db/index/SecondaryIndex.java      |   6 +-
 .../db/index/SecondaryIndexManager.java         |  34 +-
 .../db/index/SecondaryIndexSearcher.java        |   2 +
 .../db/index/composites/CompositesIndex.java    |   4 +-
 .../CompositesIndexOnCollectionValue.java       |   2 +-
 .../cassandra/db/marshal/AbstractType.java      |  18 +
 .../cassandra/db/marshal/CollectionType.java    | 113 ++-
 .../db/marshal/ColumnToCollectionType.java      |   2 +-
 .../apache/cassandra/db/marshal/FrozenType.java |  62 ++
 .../apache/cassandra/db/marshal/ListType.java   |  75 +-
 .../apache/cassandra/db/marshal/MapType.java    | 103 ++-
 .../apache/cassandra/db/marshal/SetType.java    |  68 +-
 .../apache/cassandra/db/marshal/TupleType.java  |   9 +-
 .../apache/cassandra/db/marshal/TypeParser.java |  33 +-
 .../apache/cassandra/db/marshal/UserType.java   |   2 +-
 .../apache/cassandra/hadoop/pig/CqlStorage.java |   8 +-
 .../serializers/CollectionSerializer.java       |  24 +-
 .../cassandra/serializers/ListSerializer.java   |  35 +-
 .../cassandra/serializers/MapSerializer.java    |  38 +-
 .../apache/cassandra/transport/DataType.java    |  16 +-
 .../org/apache/cassandra/cql3/CQLTester.java    |  82 +-
 .../cassandra/cql3/ColumnConditionTest.java     |  28 +-
 .../cassandra/cql3/FrozenCollectionsTest.java   | 791 +++++++++++++++++++
 .../apache/cassandra/cql3/TupleTypeTest.java    |  44 +-
 .../db/marshal/CollectionTypeTest.java          |  22 +-
 .../cassandra/transport/SerDeserTest.java       |  13 +-
 .../cassandra/stress/generate/values/Lists.java |   2 +-
 .../cassandra/stress/generate/values/Sets.java  |   2 +-
 62 files changed, 2166 insertions(+), 571 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index 1b981c6,5b63f48..d17f958
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,36 -1,5 +1,37 @@@
 +3.0
 + * Mark sstables as repaired after full repair (CASSANDRA-7586) 
 + * Extend Descriptor to include a format value and refactor reader/writer apis (CASSANDRA-7443)
 + * Integrate JMH for microbenchmarks (CASSANDRA-8151)
 + * Keep sstable levels when bootstrapping (CASSANDRA-7460)
 + * Add Sigar library and perform basic OS settings check on startup (CASSANDRA-7838)
 + * Support for aggregation functions (CASSANDRA-4914)
 + * Remove cassandra-cli (CASSANDRA-7920)
 + * Accept dollar quoted strings in CQL (CASSANDRA-7769)
 + * Make assassinate a first class command (CASSANDRA-7935)
 + * Support IN clause on any clustering column (CASSANDRA-4762)
 + * Improve compaction logging (CASSANDRA-7818)
 + * Remove YamlFileNetworkTopologySnitch (CASSANDRA-7917)
 + * Do anticompaction in groups (CASSANDRA-6851)
 + * Support pure user-defined functions (CASSANDRA-7395, 7526, 7562, 7740, 7781, 7929,
 +   7924, 7812, 8063)
 + * Permit configurable timestamps with cassandra-stress (CASSANDRA-7416)
 + * Move sstable RandomAccessReader to nio2, which allows using the
 +   FILE_SHARE_DELETE flag on Windows (CASSANDRA-4050)
 + * Remove CQL2 (CASSANDRA-5918)
 + * Add Thrift get_multi_slice call (CASSANDRA-6757)
 + * Optimize fetching multiple cells by name (CASSANDRA-6933)
 + * Allow compilation in java 8 (CASSANDRA-7028)
 + * Make incremental repair default (CASSANDRA-7250)
 + * Enable code coverage thru JaCoCo (CASSANDRA-7226)
 + * Switch external naming of 'column families' to 'tables' (CASSANDRA-4369) 
 + * Shorten SSTable path (CASSANDRA-6962)
 + * Use unsafe mutations for most unit tests (CASSANDRA-6969)
 + * Fix race condition during calculation of pending ranges (CASSANDRA-7390)
 + * Fail on very large batch sizes (CASSANDRA-8011)
 + * improve concurrency of repair (CASSANDRA-6455)
 +
  2.1.3
+  * Support for frozen collections (CASSANDRA-7859)
   * Fix overflow on histogram computation (CASSANDRA-8028)
   * Have paxos reuse the timestamp generation of normal queries (CASSANDRA-7801)
  Merged from 2.0:

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/pylib/cqlshlib/cql3handling.py
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/AbstractMarker.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/ColumnCondition.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Constants.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Lists.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Maps.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Sets.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Term.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/Tuples.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/UntypedResultSet.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/UserTypes.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index b6cbd30,09d1e52..7701cbd
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@@ -698,28 -700,63 +698,43 @@@ public class SelectStatement implement
          // we always do a slice for CQL3 tables, so it's ok to ignore them here
          assert !isColumnRange();
  
 -        CBuilder builder = cfm.comparator.prefixBuilder();
 +        CompositesBuilder builder = new CompositesBuilder(cfm.comparator.prefixBuilder(), cfm.comparator);
          Iterator<ColumnDefinition> idIter = cfm.clusteringColumns().iterator();
--        for (Restriction r : columnRestrictions)
++        for (int i = 0; i < columnRestrictions.length; i++)
          {
++            Restriction r = columnRestrictions[i];
              ColumnDefinition def = idIter.next();
              assert r != null && !r.isSlice();
  
-             List<ByteBuffer> values = r.values(options);
 -            if (r.isEQ())
++            if (r.isEQ() || !r.isMultiColumn())
+             {
 -                ByteBuffer val = r.values(options).get(0);
 -                if (val == null)
 -                    throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
 -                builder.add(val);
++                List<ByteBuffer> values = r.values(options);
++                if (values.isEmpty())
++                    return null;
++                builder.addEachElementToAll(values);
+             }
+             else
+             {
 -                if (!r.isMultiColumn())
 -                {
 -                    List<ByteBuffer> values = r.values(options);
 -                    // We have a IN, which we only support for the last column.
 -                    // If compact, just add all values and we're done. Otherwise,
 -                    // for each value of the IN, creates all the columns corresponding to the selection.
 -                    if (values.isEmpty())
 -                        return null;
 -                    SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
 -                    Iterator<ByteBuffer> iter = values.iterator();
 -                    while (iter.hasNext())
 -                    {
 -                        ByteBuffer val = iter.next();
 -                        if (val == null)
 -                            throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
++                // we have a multi-column IN restriction
++                List<List<ByteBuffer>> splitValues = ((MultiColumnRestriction.IN) r).splitValues(options);
++                if (splitValues.isEmpty())
++                    return null;
  
-             if (values.isEmpty())
-                 return null;
 -                        Composite prefix = builder.buildWith(val);
 -                        columns.addAll(addSelectedColumns(prefix));
 -                    }
 -                    return columns;
 -                }
 -                else
 -                {
 -                    // we have a multi-column IN restriction
 -                    List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(options);
 -                    TreeSet<CellName> inValues = new TreeSet<>(cfm.comparator);
 -                    for (List<ByteBuffer> components : values)
 -                    {
 -                        for (int i = 0; i < components.size(); i++)
 -                            if (components.get(i) == null)
 -                                throw new InvalidRequestException("Invalid null value in condition for column " + cfm.clusteringColumns().get(i + def.position()));
++                builder.addAllElementsToAll(splitValues);
+ 
 -                        Composite prefix = builder.buildWith(components);
 -                        inValues.addAll(addSelectedColumns(prefix));
 -                    }
 -                    return inValues;
 -                }
++                // increment i to skip the remainder of the multicolumn restriction
++                i += splitValues.get(0).size() - 1;
+             }
 +
-             builder.addEachElementToAll(values);
 +            if (builder.containsNull())
 +                throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s",
-                                                                 def.name));
++                        def.name));
          }
-         SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
+ 
 -        return addSelectedColumns(builder.build());
++        SortedSet<CellName> columns = new TreeSet<>(cfm.comparator);
 +        for (Composite composite : builder.build())
 +            columns.addAll(addSelectedColumns(composite));
- 
 +        return columns;
      }
  
      private SortedSet<CellName> addSelectedColumns(Composite prefix)
@@@ -1891,9 -1965,17 +1919,15 @@@
                  }
                  else if (restriction.isIN())
                  {
 -                    if (!restriction.isMultiColumn() && i != stmt.columnRestrictions.length - 1)
 -                        throw new InvalidRequestException(String.format("Clustering column \"%s\" cannot be restricted by an IN relation", cdef.name));
 -                    else if (stmt.selectACollection())
 +                    if (stmt.selectACollection())
                          throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by IN relation as a collection is selected by the query", cdef.name));
                  }
+                 /*
+                 else if (restriction.isContains() && !hasQueriableIndex)
+                 {
+                     throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by a CONTAINS relation without a secondary index", cdef.name));
+                 }
+                 */
  
                  previous = cdef;
              }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/composites/CellNameType.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
index 29fe905,0000000..4542ac5
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
+++ b/src/java/org/apache/cassandra/db/composites/CompositesBuilder.java
@@@ -1,219 -1,0 +1,260 @@@
 +/*
 + * 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.db.composites;
 +
 +import java.nio.ByteBuffer;
 +import java.util.ArrayList;
 +import java.util.Comparator;
 +import java.util.List;
 +import java.util.Set;
 +import java.util.TreeSet;
 +
 +import org.apache.cassandra.db.composites.Composite.EOC;
 +
 +import static java.util.Collections.singletonList;
 +
 +/**
 + * Builder that allow to build multiple composites at the same time.
 + */
 +public final class CompositesBuilder
 +{
 +    /**
 +     * The builder used to build the <code>Composite</code>s.
 +     */
 +    private final CBuilder builder;
 +
 +    /**
 +     * The comparator used to sort the returned <code>Composite</code>s.
 +     */
 +    private final Comparator<Composite> comparator;
 +
 +    /**
 +     * The elements of the composites
 +     */
 +    private final List<List<ByteBuffer>> elementsList = new ArrayList<>();
 +
 +    /**
 +     * The number of elements that still can be added.
 +     */
 +    private int remaining;
 +
 +    /**
 +     * <code>true</code> if the composites have been build, <code>false</code> otherwise.
 +     */
 +    private boolean built;
 +
 +    /**
 +     * <code>true</code> if the composites contains some <code>null</code> elements.
 +     */
 +    private boolean containsNull;
 +
 +    public CompositesBuilder(CBuilder builder, Comparator<Composite> comparator)
 +    {
 +        this.builder = builder;
 +        this.comparator = comparator;
 +        this.remaining = builder.remainingCount();
 +    }
 +
 +    /**
 +     * Adds the specified element to all the composites.
 +     * <p>
 +     * If this builder contains 2 composites: A-B and A-C a call to this method to add D will result in the composites:
 +     * A-B-D and A-C-D.
 +     * </p>
 +     *
 +     * @param value the value of the next element
 +     * @return this <code>CompositeBuilder</code>
 +     */
 +    public CompositesBuilder addElementToAll(ByteBuffer value)
 +    {
 +        checkUpdateable();
 +
 +        if (isEmpty())
 +            elementsList.add(new ArrayList<ByteBuffer>());
 +
 +        for (int i = 0, m = elementsList.size(); i < m; i++)
 +        {
 +            if (value == null)
 +                containsNull = true;
 +
 +            elementsList.get(i).add(value);
 +        }
 +        remaining--;
 +        return this;
 +    }
 +
 +    /**
 +     * Adds individually each of the specified elements to the end of all of the existing composites.
 +     * <p>
 +     * If this builder contains 2 composites: A-B and A-C a call to this method to add D and E will result in the 4
 +     * composites: A-B-D, A-B-E, A-C-D and A-C-E.
 +     * </p>
 +     *
 +     * @param values the elements to add
 +     * @return this <code>CompositeBuilder</code>
 +     */
 +    public CompositesBuilder addEachElementToAll(List<ByteBuffer> values)
 +    {
 +        checkUpdateable();
 +
 +        if (isEmpty())
 +            elementsList.add(new ArrayList<ByteBuffer>());
 +
 +        for (int i = 0, m = elementsList.size(); i < m; i++)
 +        {
 +            List<ByteBuffer> oldComposite = elementsList.remove(0);
 +
 +            for (int j = 0, n = values.size(); j < n; j++)
 +            {
 +                List<ByteBuffer> newComposite = new ArrayList<>(oldComposite);
 +                elementsList.add(newComposite);
 +
 +                ByteBuffer value = values.get(j);
 +
 +                if (value == null)
 +                    containsNull = true;
 +
 +                newComposite.add(values.get(j));
 +            }
 +        }
 +
 +        remaining--;
 +        return this;
 +    }
 +
++
++    /**
++     * Adds individually each of the specified list of elements to the end of all of the existing composites.
++     * <p>
++     * If this builder contains 2 composites: A-B and A-C a call to this method to add [[D, E], [F, G]] will result in the 4
++     * composites: A-B-D-E, A-B-F-G, A-C-D-E and A-C-F-G.
++     * </p>
++     *
++     * @param values the elements to add
++     * @return this <code>CompositeBuilder</code>
++     */
++    public CompositesBuilder addAllElementsToAll(List<List<ByteBuffer>> values)
++    {
++        assert !values.isEmpty();
++        checkUpdateable();
++
++        if (isEmpty())
++            elementsList.add(new ArrayList<ByteBuffer>());
++
++        for (int i = 0, m = elementsList.size(); i < m; i++)
++        {
++            List<ByteBuffer> oldComposite = elementsList.remove(0);
++
++            for (int j = 0, n = values.size(); j < n; j++)
++            {
++                List<ByteBuffer> newComposite = new ArrayList<>(oldComposite);
++                elementsList.add(newComposite);
++
++                List<ByteBuffer> value = values.get(j);
++
++                if (value.contains(null))
++                    containsNull = true;
++
++                newComposite.addAll(value);
++            }
++        }
++
++        remaining -= values.get(0).size();
++        return this;
++    }
++
 +    /**
 +     * Returns the number of elements that can be added to the composites.
 +     *
 +     * @return the number of elements that can be added to the composites.
 +     */
 +    public int remainingCount()
 +    {
 +        return remaining;
 +    }
 +
 +    /**
 +     * Checks if some elements can still be added to the composites.
 +     *
 +     * @return <code>true</code> if it is possible to add more elements to the composites, <code>false</code> otherwise.
 +     */
 +    public boolean hasRemaining()
 +    {
 +        return remaining > 0;
 +    }
 +
 +    /**
 +     * Checks if this builder is empty.
 +     *
 +     * @return <code>true</code> if this builder is empty, <code>false</code> otherwise.
 +     */
 +    public boolean isEmpty()
 +    {
 +        return elementsList.isEmpty();
 +    }
 +
 +    /**
 +     * Checks if the composites contains null elements.
 +     *
 +     * @return <code>true</code> if the composites contains <code>null</code> elements, <code>false</code> otherwise.
 +     */
 +    public boolean containsNull()
 +    {
 +        return containsNull;
 +    }
 +
 +    /**
 +     * Builds the <code>Composites</code>.
 +     *
 +     * @return the composites
 +     */
 +    public List<Composite> build()
 +    {
 +        return buildWithEOC(EOC.NONE);
 +    }
 +
 +    /**
 +     * Builds the <code>Composites</code> with the specified EOC.
 +     *
 +     * @return the composites
 +     */
 +    public List<Composite> buildWithEOC(EOC eoc)
 +    {
 +        built = true;
 +
 +        if (elementsList.isEmpty())
 +            return singletonList(builder.build().withEOC(eoc));
 +
 +        // Use a TreeSet to sort and eliminate duplicates
 +        Set<Composite> set = new TreeSet<Composite>(comparator);
 +
 +        for (int i = 0, m = elementsList.size(); i < m; i++)
 +        {
 +            List<ByteBuffer> elements = elementsList.get(i);
 +            set.add(builder.buildWith(elements).withEOC(eoc));
 +        }
 +
 +        return new ArrayList<>(set);
 +    }
 +
 +    private void checkUpdateable()
 +    {
 +        if (!hasRemaining() || built)
 +            throw new IllegalStateException("this CompositesBuilder cannot be updated anymore");
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
index 06a1e0e,fbbec1d..10a7ce3
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
@@@ -33,7 -34,7 +34,6 @@@ import java.util.concurrent.ConcurrentN
  import java.util.concurrent.ConcurrentSkipListMap;
  import java.util.concurrent.Future;
  
- import org.apache.cassandra.io.sstable.format.SSTableReader;
 -import org.apache.cassandra.utils.ByteBufferUtil;
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
@@@ -53,6 -54,7 +53,8 @@@ import org.apache.cassandra.db.filter.E
  import org.apache.cassandra.exceptions.ConfigurationException;
  import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.io.sstable.ReducingKeyIterator;
 -import org.apache.cassandra.io.sstable.SSTableReader;
++import org.apache.cassandra.io.sstable.format.SSTableReader;
++import org.apache.cassandra.utils.ByteBufferUtil;
  import org.apache.cassandra.utils.FBUtilities;
  import org.apache.cassandra.utils.concurrent.OpOrder;
  

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/marshal/AbstractType.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/marshal/TupleType.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/marshal/TypeParser.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/marshal/TypeParser.java
index 37f8708,cdb5679..fed6f7f
--- a/src/java/org/apache/cassandra/db/marshal/TypeParser.java
+++ b/src/java/org/apache/cassandra/db/marshal/TypeParser.java
@@@ -572,7 -584,9 +584,8 @@@ public class TypeParse
          {
              sb.append(',');
              sb.append(ByteBufferUtil.bytesToHex(columnNames.get(i))).append(":");
-             sb.append(columnTypes.get(i));
 -
+             // omit FrozenType(...) from fields because it is currently implicit
+             sb.append(columnTypes.get(i).toString(true));
          }
          sb.append(')');
          return sb.toString();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/db/marshal/UserType.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/src/java/org/apache/cassandra/serializers/MapSerializer.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/serializers/MapSerializer.java
index 7e132b8,bc8e509..8350f66
--- a/src/java/org/apache/cassandra/serializers/MapSerializer.java
+++ b/src/java/org/apache/cassandra/serializers/MapSerializer.java
@@@ -22,6 -22,9 +22,8 @@@ import java.nio.BufferUnderflowExceptio
  import java.nio.ByteBuffer;
  import java.util.*;
  
+ import org.apache.cassandra.db.marshal.AbstractType;
+ import org.apache.cassandra.transport.Server;
 -import org.apache.cassandra.utils.ByteBufferUtil;
  import org.apache.cassandra.utils.Pair;
  
  public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>

http://git-wip-us.apache.org/repos/asf/cassandra/blob/fb4356a3/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------


[2/4] cassandra git commit: Support for frozen collections

Posted by ty...@apache.org.
http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
index 7be635f..a17ee92 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -120,13 +120,12 @@ public class AlterTableStatement extends SchemaAlteringStatement
                     throw new InvalidRequestException(String.format("Cannot re-add previously dropped counter column %s", columnName));
 
                 AbstractType<?> type = validator.getType();
-                if (type instanceof CollectionType)
+                if (type.isCollection() && type.isMultiCell())
                 {
                     if (!cfm.comparator.supportCollections())
-                        throw new InvalidRequestException("Cannot use collection types with non-composite PRIMARY KEY");
+                        throw new InvalidRequestException("Cannot use non-frozen collections with a non-composite PRIMARY KEY");
                     if (cfm.isSuper())
-                        throw new InvalidRequestException("Cannot use collection types with Super column family");
-
+                        throw new InvalidRequestException("Cannot use non-frozen collections with super column families");
 
                     // If there used to be a collection column with the same name (that has been dropped), it will
                     // still be appear in the ColumnToCollectionType because or reasons explained on #6276. The same
@@ -151,35 +150,35 @@ public class AlterTableStatement extends SchemaAlteringStatement
             case ALTER:
                 assert columnName != null;
                 if (def == null)
-                    throw new InvalidRequestException(String.format("Cell %s was not found in table %s", columnName, columnFamily()));
+                    throw new InvalidRequestException(String.format("Column %s was not found in table %s", columnName, columnFamily()));
 
+                AbstractType<?> validatorType = validator.getType();
                 switch (def.kind)
                 {
                     case PARTITION_KEY:
-                        AbstractType<?> newType = validator.getType();
-                        if (newType instanceof CounterColumnType)
+                        if (validatorType instanceof CounterColumnType)
                             throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", columnName));
                         if (cfm.getKeyValidator() instanceof CompositeType)
                         {
                             List<AbstractType<?>> oldTypes = ((CompositeType) cfm.getKeyValidator()).types;
-                            if (!newType.isValueCompatibleWith(oldTypes.get(def.position())))
+                            if (!validatorType.isValueCompatibleWith(oldTypes.get(def.position())))
                                 throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
                                                                                columnName,
                                                                                oldTypes.get(def.position()).asCQL3Type(),
                                                                                validator));
 
                             List<AbstractType<?>> newTypes = new ArrayList<AbstractType<?>>(oldTypes);
-                            newTypes.set(def.position(), newType);
+                            newTypes.set(def.position(), validatorType);
                             cfm.keyValidator(CompositeType.getInstance(newTypes));
                         }
                         else
                         {
-                            if (!newType.isValueCompatibleWith(cfm.getKeyValidator()))
+                            if (!validatorType.isValueCompatibleWith(cfm.getKeyValidator()))
                                 throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
                                                                                columnName,
                                                                                cfm.getKeyValidator().asCQL3Type(),
                                                                                validator));
-                            cfm.keyValidator(newType);
+                            cfm.keyValidator(validatorType);
                         }
                         break;
                     case CLUSTERING_COLUMN:
@@ -187,22 +186,22 @@ public class AlterTableStatement extends SchemaAlteringStatement
                         // Note that CFMetaData.validateCompatibility already validate the change we're about to do. However, the error message it
                         // sends is a bit cryptic for a CQL3 user, so validating here for a sake of returning a better error message
                         // Do note that we need isCompatibleWith here, not just isValueCompatibleWith.
-                        if (!validator.getType().isCompatibleWith(oldType))
+                        if (!validatorType.isCompatibleWith(oldType))
                             throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are not order-compatible.",
                                                                            columnName,
                                                                            oldType.asCQL3Type(),
                                                                            validator));
 
-                        cfm.comparator = cfm.comparator.setSubtype(def.position(), validator.getType());
+                        cfm.comparator = cfm.comparator.setSubtype(def.position(), validatorType);
                         break;
                     case COMPACT_VALUE:
                         // See below
-                        if (!validator.getType().isValueCompatibleWith(cfm.getDefaultValidator()))
+                        if (!validatorType.isValueCompatibleWith(cfm.getDefaultValidator()))
                             throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
                                                                            columnName,
                                                                            cfm.getDefaultValidator().asCQL3Type(),
                                                                            validator));
-                        cfm.defaultValidator(validator.getType());
+                        cfm.defaultValidator(validatorType);
                         break;
                     case REGULAR:
                     case STATIC:
@@ -211,23 +210,23 @@ public class AlterTableStatement extends SchemaAlteringStatement
                         // allow it for CQL3 (see #5882) so validating it explicitly here. We only care about value compatibility
                         // though since we won't compare values (except when there is an index, but that is validated by
                         // ColumnDefinition already).
-                        if (!validator.getType().isValueCompatibleWith(def.type))
+                        if (!validatorType.isValueCompatibleWith(def.type))
                             throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
                                                                            columnName,
                                                                            def.type.asCQL3Type(),
                                                                            validator));
 
                         // For collections, if we alter the type, we need to update the comparator too since it includes
-                        // the type too (note that isValueCompatibleWith above has validated that the need type don't really
+                        // the type too (note that isValueCompatibleWith above has validated that the new type doesn't
                         // change the underlying sorting order, but we still don't want to have a discrepancy between the type
                         // in the comparator and the one in the ColumnDefinition as that would be dodgy).
-                        if (validator.getType() instanceof CollectionType)
-                            cfm.comparator = cfm.comparator.addOrUpdateCollection(def.name, (CollectionType)validator.getType());
+                        if (validatorType.isCollection() && validatorType.isMultiCell())
+                            cfm.comparator = cfm.comparator.addOrUpdateCollection(def.name, (CollectionType)validatorType);
 
                         break;
                 }
                 // In any case, we update the column definition
-                cfm.addOrReplaceColumnDefinition(def.withNewType(validator.getType()));
+                cfm.addOrReplaceColumnDefinition(def.withNewType(validatorType));
                 break;
 
             case DROP:

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
index cfdd65f..576011f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
@@ -214,23 +214,27 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
         {
             if (type instanceof ListType)
             {
-                AbstractType<?> t = updateWith(((ListType)type).elements, keyspace, toReplace, updated);
-                return t == null ? null : ListType.getInstance(t);
+                AbstractType<?> t = updateWith(((ListType)type).getElementsType(), keyspace, toReplace, updated);
+                if (t == null)
+                    return null;
+                return ListType.getInstance(t, type.isMultiCell());
             }
             else if (type instanceof SetType)
             {
-                AbstractType<?> t = updateWith(((SetType)type).elements, keyspace, toReplace, updated);
-                return t == null ? null : SetType.getInstance(t);
+                AbstractType<?> t = updateWith(((SetType)type).getElementsType(), keyspace, toReplace, updated);
+                if (t == null)
+                    return null;
+                return SetType.getInstance(t, type.isMultiCell());
             }
             else
             {
                 assert type instanceof MapType;
                 MapType mt = (MapType)type;
-                AbstractType<?> k = updateWith(mt.keys, keyspace, toReplace, updated);
-                AbstractType<?> v = updateWith(mt.values, keyspace, toReplace, updated);
+                AbstractType<?> k = updateWith(mt.getKeysType(), keyspace, toReplace, updated);
+                AbstractType<?> v = updateWith(mt.getValuesType(), keyspace, toReplace, updated);
                 if (k == null && v == null)
                     return null;
-                return MapType.getInstance(k == null ? mt.keys : k, v == null ? mt.values : v);
+                return MapType.getInstance(k == null ? mt.getKeysType() : k, v == null ? mt.getValuesType() : v, type.isMultiCell());
             }
         }
         else

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
index c93adc2..3032897 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
@@ -79,8 +79,24 @@ public class CreateIndexStatement extends SchemaAlteringStatement
             throw new InvalidRequestException("No column definition found for column " + target.column);
 
         boolean isMap = cd.type instanceof MapType;
-        if (target.isCollectionKeys && !isMap)
-            throw new InvalidRequestException("Cannot create index on keys of column " + target + " with non map type");
+        boolean isFrozenCollection = cd.type.isCollection() && !cd.type.isMultiCell();
+        if (target.isCollectionKeys)
+        {
+            if (!isMap)
+                throw new InvalidRequestException("Cannot create index on keys of column " + target + " with non-map type");
+            if (!cd.type.isMultiCell())
+                throw new InvalidRequestException("Cannot create index on keys of frozen<map> column " + target);
+        }
+        else if (target.isFullCollection)
+        {
+            if (!isFrozenCollection)
+                throw new InvalidRequestException("full() indexes can only be created on frozen collections");
+        }
+        else if (isFrozenCollection)
+        {
+            throw new InvalidRequestException("Frozen collections currently only support full-collection indexes. " +
+                                              "For example, 'CREATE INDEX ON <table>(full(<columnName>))'.");
+        }
 
         if (cd.getIndexType() != null)
         {
@@ -116,7 +132,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
             throw new InvalidRequestException("Secondary indexes are not allowed on static columns");
 
         if (cd.kind == ColumnDefinition.Kind.PARTITION_KEY && cd.isOnAllComponents())
-            throw new InvalidRequestException(String.format("Cannot add secondary index to already primarily indexed column %s", target.column));
+            throw new InvalidRequestException(String.format("Cannot create secondary index on partition key column %s", target.column));
     }
 
     public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
@@ -139,7 +155,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
             // For now, we only allow indexing values for collections, but we could later allow
             // to also index map keys, so we record that this is the values we index to make our
             // lives easier then.
-            if (cd.type.isCollection())
+            if (cd.type.isCollection() && cd.type.isMultiCell())
                 options = ImmutableMap.of(target.isCollectionKeys ? SecondaryIndex.INDEX_KEYS_OPTION_NAME
                                                                   : SecondaryIndex.INDEX_VALUES_OPTION_NAME, "");
             cd.setIndexType(IndexType.COMPOSITES, options);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
index 672bb50..5f70ab8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
@@ -199,16 +199,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
 
             CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns);
 
-            Map<ByteBuffer, CollectionType> definedCollections = null;
+            Map<ByteBuffer, CollectionType> definedMultiCellCollections = null;
             for (Map.Entry<ColumnIdentifier, CQL3Type.Raw> entry : definitions.entrySet())
             {
                 ColumnIdentifier id = entry.getKey();
                 CQL3Type pt = entry.getValue().prepare(keyspace());
-                if (pt.isCollection())
+                if (pt.isCollection() && ((CollectionType) pt.getType()).isMultiCell())
                 {
-                    if (definedCollections == null)
-                        definedCollections = new HashMap<ByteBuffer, CollectionType>();
-                    definedCollections.put(id.bytes, (CollectionType)pt.getType());
+                    if (definedMultiCellCollections == null)
+                        definedMultiCellCollections = new HashMap<>();
+                    definedMultiCellCollections.put(id.bytes, (CollectionType) pt.getType());
                 }
                 stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below
             }
@@ -246,16 +246,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
                     if (stmt.columns.isEmpty())
                         throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY");
 
-                    if (definedCollections != null)
-                        throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
+                    if (definedMultiCellCollections != null)
+                        throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE");
 
                     stmt.comparator = new SimpleSparseCellNameType(UTF8Type.instance);
                 }
                 else
                 {
-                    stmt.comparator = definedCollections == null
+                    stmt.comparator = definedMultiCellCollections == null
                                     ? new CompoundSparseCellNameType(Collections.<AbstractType<?>>emptyList())
-                                    : new CompoundSparseCellNameType.WithCollection(Collections.<AbstractType<?>>emptyList(), ColumnToCollectionType.getInstance(definedCollections));
+                                    : new CompoundSparseCellNameType.WithCollection(Collections.<AbstractType<?>>emptyList(), ColumnToCollectionType.getInstance(definedMultiCellCollections));
                 }
             }
             else
@@ -264,7 +264,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
                 // standard "dynamic" CF, otherwise it's a composite
                 if (useCompactStorage && columnAliases.size() == 1)
                 {
-                    if (definedCollections != null)
+                    if (definedMultiCellCollections != null)
                         throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
 
                     ColumnIdentifier alias = columnAliases.get(0);
@@ -294,16 +294,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
 
                     if (useCompactStorage)
                     {
-                        if (definedCollections != null)
+                        if (definedMultiCellCollections != null)
                             throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
 
                         stmt.comparator = new CompoundDenseCellNameType(types);
                     }
                     else
                     {
-                        stmt.comparator = definedCollections == null
+                        stmt.comparator = definedMultiCellCollections == null
                                         ? new CompoundSparseCellNameType(types)
-                                        : new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedCollections));
+                                        : new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedMultiCellCollections));
                     }
                 }
             }
@@ -385,7 +385,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
             AbstractType type = columns.get(t);
             if (type == null)
                 throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t));
-            if (type instanceof CollectionType)
+            if (type.isCollection() && type.isMultiCell())
                 throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t));
 
             columns.remove(t);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
index b49f60b..33c61e7 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
@@ -56,7 +56,7 @@ public class DeleteStatement extends ModificationStatement
             // However, if we delete only static colums, it's fine since we won't really use the prefix anyway.
             for (Operation deletion : deletions)
                 if (!deletion.column.isStatic())
-                    throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s since %s specified", getFirstEmptyKey(), deletion.column.name));
+                    throw new InvalidRequestException(String.format("Primary key column '%s' must be specified in order to delete column '%s'", getFirstEmptyKey().name, deletion.column.name));
         }
 
         if (deletions.isEmpty())

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
index 8bcaaf6..a3b82a4 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
@@ -117,11 +117,11 @@ public class DropTypeStatement extends SchemaAlteringStatement
         else if (toCheck instanceof CollectionType)
         {
             if (toCheck instanceof ListType)
-                return isUsedBy(((ListType)toCheck).elements);
+                return isUsedBy(((ListType)toCheck).getElementsType());
             else if (toCheck instanceof SetType)
-                return isUsedBy(((SetType)toCheck).elements);
+                return isUsedBy(((SetType)toCheck).getElementsType());
             else
-                return isUsedBy(((MapType)toCheck).keys) || isUsedBy(((MapType)toCheck).keys);
+                return isUsedBy(((MapType)toCheck).getKeysType()) || isUsedBy(((MapType)toCheck).getKeysType());
         }
         return false;
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
index dc77bcc..eeee907 100644
--- a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
+++ b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java
@@ -24,37 +24,46 @@ public class IndexTarget
 {
     public final ColumnIdentifier column;
     public final boolean isCollectionKeys;
+    public final boolean isFullCollection;
 
-    private IndexTarget(ColumnIdentifier column, boolean isCollectionKeys)
+    private IndexTarget(ColumnIdentifier column, boolean isCollectionKeys, boolean isFullCollection)
     {
         this.column = column;
         this.isCollectionKeys = isCollectionKeys;
+        this.isFullCollection = isFullCollection;
     }
 
     public static class Raw
     {
         private final ColumnIdentifier.Raw column;
-        public final boolean isCollectionKeys;
+        private final boolean isCollectionKeys;
+        private final boolean isFullCollection;
 
-        private Raw(ColumnIdentifier.Raw column, boolean isCollectionKeys)
+        private Raw(ColumnIdentifier.Raw column, boolean isCollectionKeys, boolean isFullCollection)
         {
             this.column = column;
             this.isCollectionKeys = isCollectionKeys;
+            this.isFullCollection = isFullCollection;
         }
 
-        public static Raw of(ColumnIdentifier.Raw c)
+        public static Raw valuesOf(ColumnIdentifier.Raw c)
         {
-            return new Raw(c, false);
+            return new Raw(c, false, false);
         }
 
         public static Raw keysOf(ColumnIdentifier.Raw c)
         {
-            return new Raw(c, true);
+            return new Raw(c, true, false);
+        }
+
+        public static Raw fullCollection(ColumnIdentifier.Raw c)
+        {
+            return new Raw(c, false, true);
         }
 
         public IndexTarget prepare(CFMetaData cfm)
         {
-            return new IndexTarget(column.prepare(cfm), isCollectionKeys);
+            return new IndexTarget(column.prepare(cfm), isCollectionKeys, isFullCollection);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/Restriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/Restriction.java b/src/java/org/apache/cassandra/cql3/statements/Restriction.java
index 5307cbb..659ed95 100644
--- a/src/java/org/apache/cassandra/cql3/statements/Restriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/Restriction.java
@@ -40,6 +40,12 @@ public interface Restriction
     public boolean isContains();
     public boolean isMultiColumn();
 
+    /**
+     * Returns true if, when applied to a clustering column, this restriction can be handled through one or more slices
+     * alone without filtering.  For example, EQ restrictions can be represented as a slice, but CONTAINS cannot.
+     */
+    public boolean canEvaluateWithSlices();
+
     // Not supported by Slice, but it's convenient to have here
     public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 84cbdc0..09d1e52 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -707,33 +707,52 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             ColumnDefinition def = idIter.next();
             assert r != null && !r.isSlice();
 
-            List<ByteBuffer> values = r.values(options);
-            if (values.size() == 1)
+            if (r.isEQ())
             {
-                ByteBuffer val = values.get(0);
+                ByteBuffer val = r.values(options).get(0);
                 if (val == null)
                     throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
                 builder.add(val);
             }
             else
             {
-                // We have a IN, which we only support for the last column.
-                // If compact, just add all values and we're done. Otherwise,
-                // for each value of the IN, creates all the columns corresponding to the selection.
-                if (values.isEmpty())
-                    return null;
-                SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
-                Iterator<ByteBuffer> iter = values.iterator();
-                while (iter.hasNext())
+                if (!r.isMultiColumn())
                 {
-                    ByteBuffer val = iter.next();
-                    if (val == null)
-                        throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
+                    List<ByteBuffer> values = r.values(options);
+                    // We have a IN, which we only support for the last column.
+                    // If compact, just add all values and we're done. Otherwise,
+                    // for each value of the IN, creates all the columns corresponding to the selection.
+                    if (values.isEmpty())
+                        return null;
+                    SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
+                    Iterator<ByteBuffer> iter = values.iterator();
+                    while (iter.hasNext())
+                    {
+                        ByteBuffer val = iter.next();
+                        if (val == null)
+                            throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
+
+                        Composite prefix = builder.buildWith(val);
+                        columns.addAll(addSelectedColumns(prefix));
+                    }
+                    return columns;
+                }
+                else
+                {
+                    // we have a multi-column IN restriction
+                    List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(options);
+                    TreeSet<CellName> inValues = new TreeSet<>(cfm.comparator);
+                    for (List<ByteBuffer> components : values)
+                    {
+                        for (int i = 0; i < components.size(); i++)
+                            if (components.get(i) == null)
+                                throw new InvalidRequestException("Invalid null value in condition for column " + cfm.clusteringColumns().get(i + def.position()));
 
-                    Composite prefix = builder.buildWith(val);
-                    columns.addAll(addSelectedColumns(prefix));
+                        Composite prefix = builder.buildWith(components);
+                        inValues.addAll(addSelectedColumns(prefix));
+                    }
+                    return inValues;
                 }
-                return columns;
             }
         }
 
@@ -778,6 +797,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
         }
     }
 
+    /** Returns true if a non-frozen collection is selected, false otherwise. */
     private boolean selectACollection()
     {
         if (!cfm.comparator.hasCollections())
@@ -785,7 +805,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
 
         for (ColumnDefinition def : selection.getColumns())
         {
-            if (def.type instanceof CollectionType)
+            if (def.type.isCollection() && def.type.isMultiCell())
                 return true;
         }
 
@@ -830,7 +850,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             // But if the actual comparator itself is reversed, we must inversed the bounds too.
             Bound b = isReversed == isReversedType(def) ? bound : Bound.reverse(bound);
             Restriction r = restrictions[def.position()];
-            if (isNullRestriction(r, b))
+            if (isNullRestriction(r, b) || !r.canEvaluateWithSlices())
             {
                 // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix.
                 // For composites, if there was preceding component and we're computing the end, we must change the last component
@@ -846,6 +866,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             }
             else
             {
+                // IN or EQ
                 List<ByteBuffer> values = r.values(options);
                 if (values.size() != 1)
                 {
@@ -1088,7 +1109,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                 expressions.add(new IndexExpression(def.name.bytes, Operator.EQ, value));
             }
         }
-        
+
         if (usesSecondaryIndexing)
         {
             ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
@@ -1258,13 +1279,13 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             return;
         }
 
-        if (def.type.isCollection())
+        if (def.type.isMultiCell())
         {
-            List<Cell> collection = row.getCollection(def.name);
-            ByteBuffer value = collection == null
+            List<Cell> cells = row.getMultiCellColumn(def.name);
+            ByteBuffer buffer = cells == null
                              ? null
-                             : ((CollectionType)def.type).serializeForNativeProtocol(collection, options.getProtocolVersion());
-            result.add(value);
+                             : ((CollectionType)def.type).serializeForNativeProtocol(cells, options.getProtocolVersion());
+            result.add(buffer);
             return;
         }
 
@@ -1470,16 +1491,26 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                 stmt.usesSecondaryIndexing = true;
 
             if (!stmt.usesSecondaryIndexing)
-                stmt.restrictedColumns.removeAll(cfm.clusteringColumns());
+            {
+                for (ColumnDefinition def : cfm.clusteringColumns())
+                {
+                    // Remove clustering column restrictions that can be handled by slices; the remainder will be
+                    // handled by filters (which may require a secondary index).
+                    Restriction restriction = stmt.columnRestrictions[def.position()];
+                    if (restriction != null)
+                    {
+                        if (restriction.canEvaluateWithSlices())
+                            stmt.restrictedColumns.remove(def);
+                        else
+                            stmt.usesSecondaryIndexing = true;
+                    }
+                }
+            }
 
             // Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
-            // there is restrictions not covered by the PK.
+            // there are restrictions not covered by the PK.
             if (!stmt.metadataRestrictions.isEmpty())
-            {
-                if (!hasQueriableIndex)
-                    throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
                 stmt.usesSecondaryIndexing = true;
-            }
 
             if (stmt.usesSecondaryIndexing)
                 validateSecondaryIndexSelections(stmt);
@@ -1510,6 +1541,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
             SecondaryIndex index = indexManager.getIndexForColumn(def.name.bytes);
             if (index != null && index.supportsOperator(relation.operator()))
                 return new boolean[]{true, def.kind == ColumnDefinition.Kind.CLUSTERING_COLUMN};
+
             return new boolean[]{false, false};
         }
 
@@ -1692,8 +1724,8 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                                                    StorageService.getPartitioner().getTokenValidator());
             }
 
-            // We don't support relations against entire collections, like "numbers = {1, 2, 3}"
-            if (receiver.type.isCollection() && !(newRel.operator().equals(Operator.CONTAINS_KEY) || newRel.operator() == Operator.CONTAINS))
+            // We don't support relations against entire collections (unless they're frozen), like "numbers = {1, 2, 3}"
+            if (receiver.type.isCollection() && receiver.type.isMultiCell() && !(newRel.operator() == Operator.CONTAINS_KEY || newRel.operator() == Operator.CONTAINS))
             {
                 throw new InvalidRequestException(String.format("Collection column '%s' (%s) cannot be restricted by a '%s' relation",
                                                                 def.name, receiver.type.asCQL3Type(), newRel.operator()));
@@ -1760,7 +1792,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                     break;
                 case CONTAINS_KEY:
                     if (!(receiver.type instanceof MapType))
-                        throw new InvalidRequestException(String.format("Cannot use CONTAINS_KEY on non-map column %s", def.name));
+                        throw new InvalidRequestException(String.format("Cannot use CONTAINS KEY on non-map column %s", def.name));
                     // Fallthrough on purpose
                 case CONTAINS:
                 {
@@ -1771,6 +1803,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                         existingRestriction = new SingleColumnRestriction.Contains();
                     else if (!existingRestriction.isContains())
                         throw new InvalidRequestException(String.format("Collection column %s can only be restricted by CONTAINS or CONTAINS KEY", def.name));
+
                     boolean isKey = newRel.operator() == Operator.CONTAINS_KEY;
                     receiver = makeCollectionReceiver(receiver, isKey);
                     Term t = newRel.getValue().prepare(keyspace(), receiver);
@@ -1937,6 +1970,12 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
                     else if (stmt.selectACollection())
                         throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by IN relation as a collection is selected by the query", cdef.name));
                 }
+                /*
+                else if (restriction.isContains() && !hasQueriableIndex)
+                {
+                    throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by a CONTAINS relation without a secondary index", cdef.name));
+                }
+                */
 
                 previous = cdef;
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
index 7f8156e..b1c6ccc 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
@@ -73,6 +73,11 @@ public abstract class SingleColumnRestriction implements Restriction
             return onToken;
         }
 
+        public boolean canEvaluateWithSlices()
+        {
+            return true;
+        }
+
         @Override
         public String toString()
         {
@@ -127,6 +132,11 @@ public abstract class SingleColumnRestriction implements Restriction
             return false;
         }
 
+        public boolean canEvaluateWithSlices()
+        {
+            return true;
+        }
+
         @Override
         public String toString()
         {
@@ -181,6 +191,11 @@ public abstract class SingleColumnRestriction implements Restriction
             return false;
         }
 
+        public boolean canEvaluateWithSlices()
+        {
+            return true;
+        }
+
         @Override
         public String toString()
         {
@@ -231,6 +246,11 @@ public abstract class SingleColumnRestriction implements Restriction
             return onToken;
         }
 
+        public boolean canEvaluateWithSlices()
+        {
+            return true;
+        }
+
         /** Returns true if the start or end bound (depending on the argument) is set, false otherwise */
         public boolean hasBound(Bound b)
         {
@@ -412,6 +432,10 @@ public abstract class SingleColumnRestriction implements Restriction
             return false;
         }
 
+        public boolean canEvaluateWithSlices()
+        {
+            return false;
+        }
 
         @Override
         public String toString()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/CFRowAdder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/CFRowAdder.java b/src/java/org/apache/cassandra/db/CFRowAdder.java
index dfe49ee..3ff9171 100644
--- a/src/java/org/apache/cassandra/db/CFRowAdder.java
+++ b/src/java/org/apache/cassandra/db/CFRowAdder.java
@@ -64,7 +64,7 @@ public class CFRowAdder
     public CFRowAdder resetCollection(String cql3ColumnName)
     {
         ColumnDefinition def = getDefinition(cql3ColumnName);
-        assert def.type.isCollection();
+        assert def.type.isCollection() && def.type.isMultiCell();
         Composite name = cf.getComparator().create(prefix, def);
         cf.addAtom(new RangeTombstone(name.start(), name.end(), timestamp - 1, ldt));
         return this;
@@ -75,7 +75,7 @@ public class CFRowAdder
         ColumnDefinition def = getDefinition(cql3ColumnName);
         assert def.type instanceof MapType;
         MapType mt = (MapType)def.type;
-        CellName name = cf.getComparator().create(prefix, def, mt.keys.decompose(key));
+        CellName name = cf.getComparator().create(prefix, def, mt.getKeysType().decompose(key));
         return add(name, def, value);
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java b/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java
index e33cc63..c62f890 100644
--- a/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java
+++ b/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java
@@ -304,7 +304,7 @@ public abstract class AbstractCellNameType extends AbstractCType implements Cell
                         return cell;
                     }
 
-                    public List<Cell> getCollection(ColumnIdentifier name)
+                    public List<Cell> getMultiCellColumn(ColumnIdentifier name)
                     {
                         return null;
                     }
@@ -446,7 +446,7 @@ public abstract class AbstractCellNameType extends AbstractCType implements Cell
             return columns == null ? null : columns.get(name);
         }
 
-        public List<Cell> getCollection(ColumnIdentifier name)
+        public List<Cell> getMultiCellColumn(ColumnIdentifier name)
         {
             return collections == null ? null : collections.get(name);
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/composites/CellNameType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/composites/CellNameType.java b/src/java/org/apache/cassandra/db/composites/CellNameType.java
index 4f45d41..1e87296 100644
--- a/src/java/org/apache/cassandra/db/composites/CellNameType.java
+++ b/src/java/org/apache/cassandra/db/composites/CellNameType.java
@@ -95,7 +95,7 @@ public interface CellNameType extends CType
     public boolean supportCollections();
 
     /**
-     * The type of the collections (or null if the type has not collections).
+     * The type of the collections (or null if the type does not have any non-frozen collections).
      */
     public ColumnToCollectionType collectionType();
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java b/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java
index 27b3271..c88c6f4 100644
--- a/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java
+++ b/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java
@@ -24,10 +24,7 @@ import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.cql3.CQL3Row;
 import org.apache.cassandra.cql3.ColumnIdentifier;
-import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.db.marshal.CollectionType;
-import org.apache.cassandra.db.marshal.ColumnToCollectionType;
-import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.memory.AbstractAllocator;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
index 2d56bfa..6f2760a 100644
--- a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java
@@ -40,9 +40,7 @@ import org.apache.cassandra.db.DecoratedKey;
 import org.apache.cassandra.db.IndexExpression;
 import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.composites.Composite;
-import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.db.marshal.CollectionType;
-import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.*;
 
 /**
  * Extends a column filter (IFilter) to include a number of IndexExpression.
@@ -324,7 +322,7 @@ public abstract class ExtendedFilter
                 }
                 else
                 {
-                    if (def.type.isCollection())
+                    if (def.type.isCollection() && def.type.isMultiCell())
                     {
                         if (!collectionSatisfies(def, data, prefix, expression, collectionElement))
                             return false;
@@ -338,16 +336,49 @@ public abstract class ExtendedFilter
                 if (dataValue == null)
                     return false;
 
-                int v = validator.compare(dataValue, expression.value);
-                if (!satisfies(v, expression.operator))
-                    return false;
+                if (expression.operator == Operator.CONTAINS)
+                {
+                    assert def != null && def.type.isCollection() && !def.type.isMultiCell();
+                    CollectionType type = (CollectionType)def.type;
+                    switch (type.kind)
+                    {
+                        case LIST:
+                            ListType<?> listType = (ListType)def.type;
+                            if (!listType.getSerializer().deserialize(dataValue).contains(listType.getElementsType().getSerializer().deserialize(expression.value)))
+                                return false;
+                            break;
+                        case SET:
+                            SetType<?> setType = (SetType)def.type;
+                            if (!setType.getSerializer().deserialize(dataValue).contains(setType.getElementsType().getSerializer().deserialize(expression.value)))
+                                return false;
+                            break;
+                        case MAP:
+                            MapType<?,?> mapType = (MapType)def.type;
+                            if (!mapType.getSerializer().deserialize(dataValue).containsValue(mapType.getValuesType().getSerializer().deserialize(expression.value)))
+                                return false;
+                            break;
+                    }
+                }
+                else if (expression.operator == Operator.CONTAINS_KEY)
+                {
+                    assert def != null && def.type.isCollection() && !def.type.isMultiCell() && def.type instanceof MapType;
+                    MapType<?,?> mapType = (MapType)def.type;
+                    if (mapType.getSerializer().getSerializedValue(dataValue, expression.value, mapType.getKeysType()) == null)
+                        return false;
+                }
+                else
+                {
+                    int v = validator.compare(dataValue, expression.value);
+                    if (!satisfies(v, expression.operator))
+                        return false;
+                }
             }
             return true;
         }
 
         private static boolean collectionSatisfies(ColumnDefinition def, ColumnFamily data, Composite prefix, IndexExpression expr, ByteBuffer collectionElement)
         {
-            assert def.type.isCollection();
+            assert def.type.isCollection() && def.type.isMultiCell();
             CollectionType type = (CollectionType)def.type;
 
             if (expr.isContains())

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
index 044912f..62b9e4c 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
@@ -35,11 +35,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.cql3.Operator;
-import org.apache.cassandra.db.BufferDecoratedKey;
-import org.apache.cassandra.db.Cell;
-import org.apache.cassandra.db.ColumnFamilyStore;
-import org.apache.cassandra.db.DecoratedKey;
-import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.compaction.CompactionManager;
 import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.composites.CellNameType;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
index 7f8e845..fbbec1d 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
@@ -18,6 +18,7 @@
 package org.apache.cassandra.db.index;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -33,6 +34,7 @@ import java.util.concurrent.ConcurrentNavigableMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.Future;
 
+import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -585,6 +587,7 @@ public class SecondaryIndexManager
         }
 
         // Validate
+        boolean haveSupportedIndexLookup = false;
         for (Map.Entry<String, Set<IndexExpression>> expressions : expressionsByIndexType.entrySet())
         {
             Set<ByteBuffer> columns = columnsByIndexType.get(expressions.getKey());
@@ -593,8 +596,37 @@ public class SecondaryIndexManager
             for (IndexExpression expression : expressions.getValue())
             {
                 searcher.validate(expression);
+                haveSupportedIndexLookup |= secondaryIndex.supportsOperator(expression.operator);
             }
         }
+
+        if (!haveSupportedIndexLookup)
+        {
+            // build the error message
+            int i = 0;
+            StringBuilder sb = new StringBuilder("No secondary indexes on the restricted columns support the provided operators: ");
+            for (Map.Entry<String, Set<IndexExpression>> expressions : expressionsByIndexType.entrySet())
+            {
+                for (IndexExpression expression : expressions.getValue())
+                {
+                    if (i++ > 0)
+                        sb.append(", ");
+                    sb.append("'");
+                    String columnName;
+                    try
+                    {
+                        columnName = ByteBufferUtil.string(expression.column);
+                    }
+                    catch (CharacterCodingException ex)
+                    {
+                        columnName = "<unprintable>";
+                    }
+                    sb.append(columnName).append(" ").append(expression.operator).append(" <value>").append("'");
+                }
+            }
+
+            throw new InvalidRequestException(sb.toString());
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
index b9ccd8e..93e0643 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
@@ -18,12 +18,14 @@
 package org.apache.cassandra.db.index;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.util.*;
 
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.filter.ExtendedFilter;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.tracing.Tracing;
+import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
 
 public abstract class SecondaryIndexSearcher

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
index 410ea83..ec965fd 100644
--- a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
+++ b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
@@ -58,7 +58,7 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
 
     public static CompositesIndex create(ColumnDefinition cfDef)
     {
-        if (cfDef.type.isCollection())
+        if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
         {
             switch (((CollectionType)cfDef.type).kind)
             {
@@ -90,7 +90,7 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
     // Check SecondaryIndex.getIndexComparator if you want to know why this is static
     public static CellNameType getIndexComparator(CFMetaData baseMetadata, ColumnDefinition cfDef)
     {
-        if (cfDef.type.isCollection())
+        if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
         {
             switch (((CollectionType)cfDef.type).kind)
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
index e69e656..a11a0d9 100644
--- a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
+++ b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
@@ -98,7 +98,7 @@ public class CompositesIndexOnCollectionValue extends CompositesIndex
     @Override
     public boolean supportsOperator(Operator operator)
     {
-        return operator == Operator.CONTAINS;
+        return operator == Operator.CONTAINS && !(columnDef.type instanceof SetType);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/AbstractType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java
index 4e1f2a3..8dd2ff3 100644
--- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java
+++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java
@@ -223,6 +223,24 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
         return false;
     }
 
+    public boolean isMultiCell()
+    {
+        return false;
+    }
+
+    public AbstractType<?> freeze()
+    {
+        return this;
+    }
+
+    /**
+     * @param ignoreFreezing if true, the type string will not be wrapped with FrozenType(...), even if this type is frozen.
+     */
+    public String toString(boolean ignoreFreezing)
+    {
+        return this.toString();
+    }
+
     /**
      * The number of subcomponents this type has.
      * This is always 1, i.e. the type has only itself as "subcomponents", except for CompositeType.

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/CollectionType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
index e63f2a5..24ad533 100644
--- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java
+++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
@@ -20,20 +20,20 @@ package org.apache.cassandra.db.marshal;
 import java.nio.ByteBuffer;
 import java.util.List;
 
+import org.apache.cassandra.db.Cell;
+import org.apache.cassandra.transport.Server;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.cql3.CQL3Type;
-import org.apache.cassandra.db.Cell;
 import org.apache.cassandra.serializers.CollectionSerializer;
 import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
- * The abstract validator that is the base for maps, sets and lists.
+ * The abstract validator that is the base for maps, sets and lists (both frozen and non-frozen).
  *
  * Please note that this comparator shouldn't be used "manually" (through thrift for instance).
- *
  */
 public abstract class CollectionType<T> extends AbstractType<T>
 {
@@ -56,27 +56,9 @@ public abstract class CollectionType<T> extends AbstractType<T>
     public abstract AbstractType<?> nameComparator();
     public abstract AbstractType<?> valueComparator();
 
-    protected abstract void appendToStringBuilder(StringBuilder sb);
-
-    public abstract List<ByteBuffer> serializedValues(List<Cell> cells);
-
     @Override
     public abstract CollectionSerializer<T> getSerializer();
 
-    @Override
-    public void validateCellValue(ByteBuffer cellValue) throws MarshalException
-    {
-        valueComparator().validate(cellValue);
-    }
-
-    @Override
-    public String toString()
-    {
-        StringBuilder sb = new StringBuilder();
-        appendToStringBuilder(sb);
-        return sb.toString();
-    }
-
     public String getString(ByteBuffer bytes)
     {
         return BytesType.instance.getString(bytes);
@@ -94,25 +76,18 @@ public abstract class CollectionType<T> extends AbstractType<T>
         }
     }
 
-    @Override
-    public boolean isCompatibleWith(AbstractType<?> previous)
+    public boolean isCollection()
     {
-        if (this == previous)
-            return true;
-
-        if (!getClass().equals(previous.getClass()))
-            return false;
-
-        CollectionType tprev = (CollectionType) previous;
-        // The name is part of the Cell name, so we need sorting compatibility, i.e. isCompatibleWith().
-        // But value is the Cell value, so isValueCompatibleWith() is enough
-        return this.nameComparator().isCompatibleWith(tprev.nameComparator())
-            && this.valueComparator().isValueCompatibleWith(tprev.valueComparator());
+        return true;
     }
 
-    public boolean isCollection()
+    @Override
+    public void validateCellValue(ByteBuffer cellValue) throws MarshalException
     {
-        return true;
+        if (isMultiCell())
+            valueComparator().validate(cellValue);
+        else
+            super.validateCellValue(cellValue);
     }
 
     /**
@@ -124,9 +99,11 @@ public abstract class CollectionType<T> extends AbstractType<T>
         return kind == Kind.MAP;
     }
 
-    protected List<Cell> enforceLimit(List<Cell> cells, int version)
+    public List<Cell> enforceLimit(List<Cell> cells, int version)
     {
-        if (version >= 3 || cells.size() <= MAX_ELEMENTS)
+        assert isMultiCell();
+
+        if (version >= Server.VERSION_3 || cells.size() <= MAX_ELEMENTS)
             return cells;
 
         logger.error("Detected collection with {} elements, more than the {} limit. Only the first {} elements will be returned to the client. "
@@ -134,15 +111,75 @@ public abstract class CollectionType<T> extends AbstractType<T>
         return cells.subList(0, MAX_ELEMENTS);
     }
 
+    public abstract List<ByteBuffer> serializedValues(List<Cell> cells);
+
     public ByteBuffer serializeForNativeProtocol(List<Cell> cells, int version)
     {
+        assert isMultiCell();
         cells = enforceLimit(cells, version);
         List<ByteBuffer> values = serializedValues(cells);
         return CollectionSerializer.pack(values, cells.size(), version);
     }
 
+    @Override
+    public boolean isCompatibleWith(AbstractType<?> previous)
+    {
+        if (this == previous)
+            return true;
+
+        if (!getClass().equals(previous.getClass()))
+            return false;
+
+        CollectionType tprev = (CollectionType) previous;
+        if (this.isMultiCell() != tprev.isMultiCell())
+            return false;
+
+        // subclasses should handle compatibility checks for frozen collections
+        if (!this.isMultiCell())
+            return isCompatibleWithFrozen(tprev);
+
+        if (!this.nameComparator().isCompatibleWith(tprev.nameComparator()))
+            return false;
+
+        // the value comparator is only used for Cell values, so sorting doesn't matter
+        return this.valueComparator().isValueCompatibleWith(tprev.valueComparator());
+    }
+
+    @Override
+    public boolean isValueCompatibleWithInternal(AbstractType<?> previous)
+    {
+        // for multi-cell collections, compatibility and value-compatibility are the same
+        if (this.isMultiCell())
+            return isCompatibleWith(previous);
+
+        if (this == previous)
+            return true;
+
+        if (!getClass().equals(previous.getClass()))
+            return false;
+
+        CollectionType tprev = (CollectionType) previous;
+        if (this.isMultiCell() != tprev.isMultiCell())
+            return false;
+
+        // subclasses should handle compatibility checks for frozen collections
+        return isValueCompatibleWithFrozen(tprev);
+    }
+
+    /** A version of isCompatibleWith() to deal with non-multicell (frozen) collections */
+    protected abstract boolean isCompatibleWithFrozen(CollectionType<?> previous);
+
+    /** A version of isValueCompatibleWith() to deal with non-multicell (frozen) collections */
+    protected abstract boolean isValueCompatibleWithFrozen(CollectionType<?> previous);
+
     public CQL3Type asCQL3Type()
     {
         return new CQL3Type.Collection(this);
     }
+
+    @Override
+    public String toString()
+    {
+        return this.toString(false);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java b/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java
index a28b874..6fb32fb 100644
--- a/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java
@@ -33,7 +33,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
 public class ColumnToCollectionType extends AbstractType<ByteBuffer>
 {
     // interning instances
-    private static final Map<Map<ByteBuffer, CollectionType>, ColumnToCollectionType> instances = new HashMap<Map<ByteBuffer, CollectionType>, ColumnToCollectionType>();
+    private static final Map<Map<ByteBuffer, CollectionType>, ColumnToCollectionType> instances = new HashMap<>();
 
     public final Map<ByteBuffer, CollectionType> defined;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/FrozenType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/FrozenType.java b/src/java/org/apache/cassandra/db/marshal/FrozenType.java
new file mode 100644
index 0000000..f440c90
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/marshal/FrozenType.java
@@ -0,0 +1,62 @@
+/*
+ * 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.db.marshal;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.SyntaxException;
+import org.apache.cassandra.serializers.TypeSerializer;
+import org.apache.cassandra.serializers.MarshalException;
+
+/**
+ * A fake type that is only used for parsing type strings that include frozen types.
+ */
+public class FrozenType extends AbstractType<Void>
+{
+    public static AbstractType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
+    {
+        List<AbstractType<?>> innerTypes = parser.getTypeParameters();
+        if (innerTypes.size() != 1)
+            throw new SyntaxException("FrozenType() only accepts one parameter");
+
+        AbstractType<?> innerType = innerTypes.get(0);
+        return innerType.freeze();
+    }
+
+    public int compare(ByteBuffer o1, ByteBuffer o2)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getString(ByteBuffer bytes)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public ByteBuffer fromString(String source) throws MarshalException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public TypeSerializer<Void> getSerializer()
+    {
+        throw new UnsupportedOperationException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/ListType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/ListType.java b/src/java/org/apache/cassandra/db/marshal/ListType.java
index 171e179..510a526 100644
--- a/src/java/org/apache/cassandra/db/marshal/ListType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ListType.java
@@ -24,17 +24,21 @@ import org.apache.cassandra.db.Cell;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.SyntaxException;
 import org.apache.cassandra.serializers.CollectionSerializer;
-import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.serializers.ListSerializer;
-import org.apache.cassandra.utils.ByteBufferUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ListType<T> extends CollectionType<List<T>>
 {
+    private static final Logger logger = LoggerFactory.getLogger(ListType.class);
+
     // interning instances
-    private static final Map<AbstractType<?>, ListType> instances = new HashMap<AbstractType<?>, ListType>();
+    private static final Map<AbstractType<?>, ListType> instances = new HashMap<>();
+    private static final Map<AbstractType<?>, ListType> frozenInstances = new HashMap<>();
 
-    public final AbstractType<T> elements;
+    private final AbstractType<T> elements;
     public final ListSerializer<T> serializer;
+    private final boolean isMultiCell;
 
     public static ListType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
     {
@@ -42,25 +46,32 @@ public class ListType<T> extends CollectionType<List<T>>
         if (l.size() != 1)
             throw new ConfigurationException("ListType takes exactly 1 type parameter");
 
-        return getInstance(l.get(0));
+        return getInstance(l.get(0), true);
     }
 
-    public static synchronized <T> ListType<T> getInstance(AbstractType<T> elements)
+    public static synchronized <T> ListType<T> getInstance(AbstractType<T> elements, boolean isMultiCell)
     {
-        ListType<T> t = instances.get(elements);
+        Map<AbstractType<?>, ListType> internMap = isMultiCell ? instances : frozenInstances;
+        ListType<T> t = internMap.get(elements);
         if (t == null)
         {
-            t = new ListType<T>(elements);
-            instances.put(elements, t);
+            t = new ListType<T>(elements, isMultiCell);
+            internMap.put(elements, t);
         }
         return t;
     }
 
-    private ListType(AbstractType<T> elements)
+    private ListType(AbstractType<T> elements, boolean isMultiCell)
     {
         super(Kind.LIST);
         this.elements = elements;
         this.serializer = ListSerializer.getInstance(elements.getSerializer());
+        this.isMultiCell = isMultiCell;
+    }
+
+    public AbstractType<T> getElementsType()
+    {
+        return elements;
     }
 
     public AbstractType<UUID> nameComparator()
@@ -79,6 +90,35 @@ public class ListType<T> extends CollectionType<List<T>>
     }
 
     @Override
+    public AbstractType<?> freeze()
+    {
+        if (isMultiCell)
+            return getInstance(this.elements, false);
+        else
+            return this;
+    }
+
+    @Override
+    public boolean isMultiCell()
+    {
+        return isMultiCell;
+    }
+
+    @Override
+    public boolean isCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        assert !isMultiCell;
+        return this.elements.isCompatibleWith(((ListType) previous).elements);
+    }
+
+    @Override
+    public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        assert !isMultiCell;
+        return this.elements.isValueCompatibleWithInternal(((ListType) previous).elements);
+    }
+
+    @Override
     public int compare(ByteBuffer o1, ByteBuffer o2)
     {
         return compareListOrSet(elements, o1, o2);
@@ -86,7 +126,7 @@ public class ListType<T> extends CollectionType<List<T>>
 
     static int compareListOrSet(AbstractType<?> elementsComparator, ByteBuffer o1, ByteBuffer o2)
     {
-        // Note that this is only used if the collection is inside an UDT
+        // Note that this is only used if the collection is frozen
         if (!o1.hasRemaining() || !o2.hasRemaining())
             return o1.hasRemaining() ? 1 : o2.hasRemaining() ? -1 : 0;
 
@@ -108,13 +148,24 @@ public class ListType<T> extends CollectionType<List<T>>
         return size1 == size2 ? 0 : (size1 < size2 ? -1 : 1);
     }
 
-    protected void appendToStringBuilder(StringBuilder sb)
+    @Override
+    public String toString(boolean ignoreFreezing)
     {
-        sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements)));
+        boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
+
+        StringBuilder sb = new StringBuilder();
+        if (includeFrozenType)
+            sb.append(FrozenType.class.getName()).append("(");
+        sb.append(getClass().getName());
+        sb.append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements), ignoreFreezing || !isMultiCell));
+        if (includeFrozenType)
+            sb.append(")");
+        return sb.toString();
     }
 
     public List<ByteBuffer> serializedValues(List<Cell> cells)
     {
+        assert isMultiCell;
         List<ByteBuffer> bbs = new ArrayList<ByteBuffer>(cells.size());
         for (Cell c : cells)
             bbs.add(c.value());

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/MapType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/MapType.java b/src/java/org/apache/cassandra/db/marshal/MapType.java
index dbf6721..64f3e2a 100644
--- a/src/java/org/apache/cassandra/db/marshal/MapType.java
+++ b/src/java/org/apache/cassandra/db/marshal/MapType.java
@@ -24,19 +24,20 @@ import org.apache.cassandra.db.Cell;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.SyntaxException;
 import org.apache.cassandra.serializers.CollectionSerializer;
-import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.serializers.MapSerializer;
+import org.apache.cassandra.transport.Server;
 import org.apache.cassandra.utils.Pair;
-import org.apache.cassandra.utils.ByteBufferUtil;
 
 public class MapType<K, V> extends CollectionType<Map<K, V>>
 {
     // interning instances
-    private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> instances = new HashMap<Pair<AbstractType<?>, AbstractType<?>>, MapType>();
+    private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> instances = new HashMap<>();
+    private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> frozenInstances = new HashMap<>();
 
-    public final AbstractType<K> keys;
-    public final AbstractType<V> values;
+    private final AbstractType<K> keys;
+    private final AbstractType<V> values;
     private final MapSerializer<K, V> serializer;
+    private final boolean isMultiCell;
 
     public static MapType<?, ?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
     {
@@ -44,27 +45,39 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
         if (l.size() != 2)
             throw new ConfigurationException("MapType takes exactly 2 type parameters");
 
-        return getInstance(l.get(0), l.get(1));
+        return getInstance(l.get(0), l.get(1), true);
     }
 
-    public static synchronized <K, V> MapType<K, V> getInstance(AbstractType<K> keys, AbstractType<V> values)
+    public static synchronized <K, V> MapType<K, V> getInstance(AbstractType<K> keys, AbstractType<V> values, boolean isMultiCell)
     {
+        Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> internMap = isMultiCell ? instances : frozenInstances;
         Pair<AbstractType<?>, AbstractType<?>> p = Pair.<AbstractType<?>, AbstractType<?>>create(keys, values);
-        MapType<K, V> t = instances.get(p);
+        MapType<K, V> t = internMap.get(p);
         if (t == null)
         {
-            t = new MapType<K, V>(keys, values);
-            instances.put(p, t);
+            t = new MapType<>(keys, values, isMultiCell);
+            internMap.put(p, t);
         }
         return t;
     }
 
-    private MapType(AbstractType<K> keys, AbstractType<V> values)
+    private MapType(AbstractType<K> keys, AbstractType<V> values, boolean isMultiCell)
     {
         super(Kind.MAP);
         this.keys = keys;
         this.values = values;
         this.serializer = MapSerializer.getInstance(keys.getSerializer(), values.getSerializer());
+        this.isMultiCell = isMultiCell;
+    }
+
+    public AbstractType<K> getKeysType()
+    {
+        return keys;
+    }
+
+    public AbstractType<V> getValuesType()
+    {
+        return values;
     }
 
     public AbstractType<K> nameComparator()
@@ -78,29 +91,65 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
     }
 
     @Override
+    public boolean isMultiCell()
+    {
+        return isMultiCell;
+    }
+
+    @Override
+    public AbstractType<?> freeze()
+    {
+        if (isMultiCell)
+            return getInstance(this.keys, this.values, false);
+        else
+            return this;
+    }
+
+    @Override
+    public boolean isCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        assert !isMultiCell;
+        MapType tprev = (MapType) previous;
+        return keys.isCompatibleWith(tprev.keys) && values.isCompatibleWith(tprev.values);
+    }
+
+    @Override
+    public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        assert !isMultiCell;
+        MapType tprev = (MapType) previous;
+        return keys.isCompatibleWith(tprev.keys) && values.isValueCompatibleWith(tprev.values);
+    }
+
+    @Override
     public int compare(ByteBuffer o1, ByteBuffer o2)
     {
-        // Note that this is only used if the collection is inside an UDT
-        if (!o1.hasRemaining() || !o2.hasRemaining())
+        return compareMaps(keys, values, o1, o2);
+    }
+
+    public static int compareMaps(AbstractType<?> keysComparator, AbstractType<?> valuesComparator, ByteBuffer o1, ByteBuffer o2)
+    {
+         if (!o1.hasRemaining() || !o2.hasRemaining())
             return o1.hasRemaining() ? 1 : o2.hasRemaining() ? -1 : 0;
 
         ByteBuffer bb1 = o1.duplicate();
         ByteBuffer bb2 = o2.duplicate();
 
-        int size1 = CollectionSerializer.readCollectionSize(bb1, 3);
-        int size2 = CollectionSerializer.readCollectionSize(bb2, 3);
+        int protocolVersion = Server.VERSION_3;
+        int size1 = CollectionSerializer.readCollectionSize(bb1, protocolVersion);
+        int size2 = CollectionSerializer.readCollectionSize(bb2, protocolVersion);
 
         for (int i = 0; i < Math.min(size1, size2); i++)
         {
-            ByteBuffer k1 = CollectionSerializer.readValue(bb1, 3);
-            ByteBuffer k2 = CollectionSerializer.readValue(bb2, 3);
-            int cmp = keys.compare(k1, k2);
+            ByteBuffer k1 = CollectionSerializer.readValue(bb1, protocolVersion);
+            ByteBuffer k2 = CollectionSerializer.readValue(bb2, protocolVersion);
+            int cmp = keysComparator.compare(k1, k2);
             if (cmp != 0)
                 return cmp;
 
-            ByteBuffer v1 = CollectionSerializer.readValue(bb1, 3);
-            ByteBuffer v2 = CollectionSerializer.readValue(bb2, 3);
-            cmp = values.compare(v1, v2);
+            ByteBuffer v1 = CollectionSerializer.readValue(bb1, protocolVersion);
+            ByteBuffer v2 = CollectionSerializer.readValue(bb2, protocolVersion);
+            cmp = valuesComparator.compare(v1, v2);
             if (cmp != 0)
                 return cmp;
         }
@@ -119,13 +168,23 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
         return keys.isByteOrderComparable();
     }
 
-    protected void appendToStringBuilder(StringBuilder sb)
+    @Override
+    public String toString(boolean ignoreFreezing)
     {
-        sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Arrays.asList(keys, values)));
+        boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
+
+        StringBuilder sb = new StringBuilder();
+        if (includeFrozenType)
+            sb.append(FrozenType.class.getName()).append("(");
+        sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Arrays.asList(keys, values), ignoreFreezing || !isMultiCell));
+        if (includeFrozenType)
+            sb.append(")");
+        return sb.toString();
     }
 
     public List<ByteBuffer> serializedValues(List<Cell> cells)
     {
+        assert isMultiCell;
         List<ByteBuffer> bbs = new ArrayList<ByteBuffer>(cells.size() * 2);
         for (Cell c : cells)
         {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/SetType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/SetType.java b/src/java/org/apache/cassandra/db/marshal/SetType.java
index d2f7f12..e10f2a1 100644
--- a/src/java/org/apache/cassandra/db/marshal/SetType.java
+++ b/src/java/org/apache/cassandra/db/marshal/SetType.java
@@ -23,16 +23,17 @@ import java.util.*;
 import org.apache.cassandra.db.Cell;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.SyntaxException;
-import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.serializers.SetSerializer;
 
 public class SetType<T> extends CollectionType<Set<T>>
 {
     // interning instances
-    private static final Map<AbstractType<?>, SetType> instances = new HashMap<AbstractType<?>, SetType>();
+    private static final Map<AbstractType<?>, SetType> instances = new HashMap<>();
+    private static final Map<AbstractType<?>, SetType> frozenInstances = new HashMap<>();
 
-    public final AbstractType<T> elements;
+    private final AbstractType<T> elements;
     private final SetSerializer<T> serializer;
+    private final boolean isMultiCell;
 
     public static SetType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
     {
@@ -40,25 +41,32 @@ public class SetType<T> extends CollectionType<Set<T>>
         if (l.size() != 1)
             throw new ConfigurationException("SetType takes exactly 1 type parameter");
 
-        return getInstance(l.get(0));
+        return getInstance(l.get(0), true);
     }
 
-    public static synchronized <T> SetType<T> getInstance(AbstractType<T> elements)
+    public static synchronized <T> SetType<T> getInstance(AbstractType<T> elements, boolean isMultiCell)
     {
-        SetType<T> t = instances.get(elements);
+        Map<AbstractType<?>, SetType> internMap = isMultiCell ? instances : frozenInstances;
+        SetType<T> t = internMap.get(elements);
         if (t == null)
         {
-            t = new SetType<T>(elements);
-            instances.put(elements, t);
+            t = new SetType<T>(elements, isMultiCell);
+            internMap.put(elements, t);
         }
         return t;
     }
 
-    public SetType(AbstractType<T> elements)
+    public SetType(AbstractType<T> elements, boolean isMultiCell)
     {
         super(Kind.SET);
         this.elements = elements;
         this.serializer = SetSerializer.getInstance(elements.getSerializer());
+        this.isMultiCell = isMultiCell;
+    }
+
+    public AbstractType<T> getElementsType()
+    {
+        return elements;
     }
 
     public AbstractType<T> nameComparator()
@@ -72,6 +80,35 @@ public class SetType<T> extends CollectionType<Set<T>>
     }
 
     @Override
+    public boolean isMultiCell()
+    {
+        return isMultiCell;
+    }
+
+    @Override
+    public AbstractType<?> freeze()
+    {
+        if (isMultiCell)
+            return getInstance(this.elements, false);
+        else
+            return this;
+    }
+
+    @Override
+    public boolean isCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        assert !isMultiCell;
+        return this.elements.isCompatibleWith(((SetType) previous).elements);
+    }
+
+    @Override
+    public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
+    {
+        // because sets are ordered, any changes to the type must maintain the ordering
+        return isCompatibleWithFrozen(previous);
+    }
+
+    @Override
     public int compare(ByteBuffer o1, ByteBuffer o2)
     {
         return ListType.compareListOrSet(elements, o1, o2);
@@ -87,9 +124,19 @@ public class SetType<T> extends CollectionType<Set<T>>
         return elements.isByteOrderComparable();
     }
 
-    protected void appendToStringBuilder(StringBuilder sb)
+    @Override
+    public String toString(boolean ignoreFreezing)
     {
-        sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements)));
+        boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
+
+        StringBuilder sb = new StringBuilder();
+        if (includeFrozenType)
+            sb.append(FrozenType.class.getName()).append("(");
+        sb.append(getClass().getName());
+        sb.append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements), ignoreFreezing || !isMultiCell));
+        if (includeFrozenType)
+            sb.append(")");
+        return sb.toString();
     }
 
     public List<ByteBuffer> serializedValues(List<Cell> cells)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/db/marshal/TupleType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java
index 42aaba1..ddaf53f 100644
--- a/src/java/org/apache/cassandra/db/marshal/TupleType.java
+++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java
@@ -41,12 +41,17 @@ public class TupleType extends AbstractType<ByteBuffer>
 
     public TupleType(List<AbstractType<?>> types)
     {
+        for (int i = 0; i < types.size(); i++)
+            types.set(i, types.get(i).freeze());
         this.types = types;
     }
 
     public static TupleType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
     {
-        return new TupleType(parser.getTypeParameters());
+        List<AbstractType<?>> types = parser.getTypeParameters();
+        for (int i = 0; i < types.size(); i++)
+            types.set(i, types.get(i).freeze());
+        return new TupleType(types);
     }
 
     public AbstractType<?> type(int i)
@@ -293,6 +298,6 @@ public class TupleType extends AbstractType<ByteBuffer>
     @Override
     public String toString()
     {
-        return getClass().getName() + TypeParser.stringifyTypeParameters(types);
+        return getClass().getName() + TypeParser.stringifyTypeParameters(types, true);
     }
 }


[3/4] cassandra git commit: Support for frozen collections

Posted by ty...@apache.org.
Support for frozen collections

Patch by Tyler Hobbs; reviewed by Sylvain Lebresne for CASSANDRA-7859


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

Branch: refs/heads/trunk
Commit: ee55f361b76f9ce7dd2a21a0ff4e80da931c77d2
Parents: 0337620
Author: Tyler Hobbs <ty...@datastax.com>
Authored: Tue Nov 11 12:40:48 2014 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Tue Nov 11 12:40:48 2014 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 bin/cqlsh                                       |  18 +
 pylib/cqlshlib/cql3handling.py                  |  19 +-
 .../apache/cassandra/cql3/AbstractMarker.java   |   2 +-
 src/java/org/apache/cassandra/cql3/CQL3Row.java |   2 +-
 .../org/apache/cassandra/cql3/CQL3Type.java     | 158 ++--
 .../apache/cassandra/cql3/ColumnCondition.java  | 275 ++++---
 .../org/apache/cassandra/cql3/Constants.java    |   3 +-
 src/java/org/apache/cassandra/cql3/Cql.g        |  14 +-
 src/java/org/apache/cassandra/cql3/Lists.java   |  66 +-
 src/java/org/apache/cassandra/cql3/Maps.java    |  62 +-
 .../org/apache/cassandra/cql3/Operation.java    |  16 +-
 src/java/org/apache/cassandra/cql3/Sets.java    |  80 +-
 src/java/org/apache/cassandra/cql3/Term.java    |   6 +
 src/java/org/apache/cassandra/cql3/Tuples.java  |  13 +-
 .../apache/cassandra/cql3/UntypedResultSet.java |   6 +-
 .../apache/cassandra/cql3/UpdateParameters.java |   2 +-
 .../org/apache/cassandra/cql3/UserTypes.java    |   3 +-
 .../cql3/statements/AlterTableStatement.java    |  39 +-
 .../cql3/statements/AlterTypeStatement.java     |  18 +-
 .../cql3/statements/CreateIndexStatement.java   |  24 +-
 .../cql3/statements/CreateTableStatement.java   |  28 +-
 .../cql3/statements/DeleteStatement.java        |   2 +-
 .../cql3/statements/DropTypeStatement.java      |   6 +-
 .../cassandra/cql3/statements/IndexTarget.java  |  23 +-
 .../cassandra/cql3/statements/Restriction.java  |   6 +
 .../cql3/statements/SelectStatement.java        | 107 ++-
 .../statements/SingleColumnRestriction.java     |  24 +
 .../org/apache/cassandra/db/CFRowAdder.java     |   4 +-
 .../db/composites/AbstractCellNameType.java     |   4 +-
 .../cassandra/db/composites/CellNameType.java   |   2 +-
 .../composites/CompoundSparseCellNameType.java  |   5 +-
 .../cassandra/db/filter/ExtendedFilter.java     |  47 +-
 .../cassandra/db/index/SecondaryIndex.java      |   6 +-
 .../db/index/SecondaryIndexManager.java         |  32 +
 .../db/index/SecondaryIndexSearcher.java        |   2 +
 .../db/index/composites/CompositesIndex.java    |   4 +-
 .../CompositesIndexOnCollectionValue.java       |   2 +-
 .../cassandra/db/marshal/AbstractType.java      |  18 +
 .../cassandra/db/marshal/CollectionType.java    | 113 ++-
 .../db/marshal/ColumnToCollectionType.java      |   2 +-
 .../apache/cassandra/db/marshal/FrozenType.java |  62 ++
 .../apache/cassandra/db/marshal/ListType.java   |  77 +-
 .../apache/cassandra/db/marshal/MapType.java    | 105 ++-
 .../apache/cassandra/db/marshal/SetType.java    |  69 +-
 .../apache/cassandra/db/marshal/TupleType.java  |   9 +-
 .../apache/cassandra/db/marshal/TypeParser.java |  34 +-
 .../apache/cassandra/db/marshal/UserType.java   |   2 +-
 .../apache/cassandra/hadoop/pig/CqlStorage.java |   8 +-
 .../serializers/CollectionSerializer.java       |  24 +-
 .../cassandra/serializers/ListSerializer.java   |  36 +-
 .../cassandra/serializers/MapSerializer.java    |  38 +-
 .../apache/cassandra/transport/DataType.java    |  16 +-
 .../org/apache/cassandra/cql3/CQLTester.java    |  84 +-
 .../cassandra/cql3/ColumnConditionTest.java     |  28 +-
 .../cassandra/cql3/FrozenCollectionsTest.java   | 791 +++++++++++++++++++
 .../apache/cassandra/cql3/TupleTypeTest.java    |  44 +-
 .../db/marshal/CollectionTypeTest.java          |  22 +-
 .../cassandra/transport/SerDeserTest.java       |  13 +-
 .../cassandra/stress/generate/values/Lists.java |   2 +-
 .../cassandra/stress/generate/values/Sets.java  |   2 +-
 61 files changed, 2139 insertions(+), 591 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index b1b5df8..5b63f48 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.3
+ * Support for frozen collections (CASSANDRA-7859)
  * Fix overflow on histogram computation (CASSANDRA-8028)
  * Have paxos reuse the timestamp generation of normal queries (CASSANDRA-7801)
 Merged from 2.0:

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/bin/cqlsh
----------------------------------------------------------------------
diff --git a/bin/cqlsh b/bin/cqlsh
index 6ace914..f105273 100755
--- a/bin/cqlsh
+++ b/bin/cqlsh
@@ -486,6 +486,24 @@ def auto_format_udts():
     cassandra.cqltypes.UserType.make_udt_class = classmethod(new_make_udt_class)
 
 
+class FrozenType(cassandra.cqltypes._ParameterizedType):
+    """
+    Needed until the bundled python driver adds FrozenType.
+    """
+    typename = "frozen"
+    num_subtypes = 1
+
+    @classmethod
+    def deserialize_safe(cls, byts, protocol_version):
+        subtype, = cls.subtypes
+        return subtype.from_binary(byts)
+
+    @classmethod
+    def serialize_safe(cls, val, protocol_version):
+        subtype, = cls.subtypes
+        return subtype.to_binary(val, protocol_version)
+
+
 class Shell(cmd.Cmd):
     custom_prompt = os.getenv('CQLSH_PROMPT', '')
     if custom_prompt is not '':

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/pylib/cqlshlib/cql3handling.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index b1179ca..dbd3709 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -255,13 +255,20 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
 
 <userType> ::= utname=<cfOrKsName> ;
 
-<storageType> ::= <simpleStorageType> | <collectionType> | <userType> ;
+<storageType> ::= <simpleStorageType> | <collectionType> | <frozenCollectionType> | <userType> ;
 
+# Note: autocomplete for frozen collection types does not handle nesting past depth 1 properly,
+# but that's a lot of work to fix for little benefit.
 <collectionType> ::= "map" "<" <simpleStorageType> "," ( <simpleStorageType> | <userType> ) ">"
                    | "list" "<" ( <simpleStorageType> | <userType> ) ">"
                    | "set" "<" ( <simpleStorageType> | <userType> ) ">"
                    ;
 
+<frozenCollectionType> ::= "frozen" "<" "map"  "<" <storageType> "," <storageType> ">" ">"
+                         | "frozen" "<" "list" "<" <storageType> ">" ">"
+                         | "frozen" "<" "set"  "<" <storageType> ">" ">"
+                         ;
+
 <columnFamilyName> ::= ( ksname=<cfOrKsName> dot="." )? cfname=<cfOrKsName> ;
 
 <userTypeName> ::= ( ksname=<cfOrKsName> dot="." )? utname=<cfOrKsName> ;
@@ -906,11 +913,11 @@ syntax_rules += r'''
 <cfamOrdering> ::= [ordercol]=<cident> ( "ASC" | "DESC" )
                  ;
 
-<singleKeyCfSpec> ::= [newcolname]=<cident> <simpleStorageType> "PRIMARY" "KEY"
+<singleKeyCfSpec> ::= [newcolname]=<cident> <storageType> "PRIMARY" "KEY"
                       ( "," [newcolname]=<cident> <storageType> )*
                     ;
 
-<compositeKeyCfSpec> ::= [newcolname]=<cident> <simpleStorageType>
+<compositeKeyCfSpec> ::= [newcolname]=<cident> <storageType>
                          "," [newcolname]=<cident> <storageType> ( "static" )?
                          ( "," [newcolname]=<cident> <storageType> ( "static" )? )*
                          "," "PRIMARY" k="KEY" p="(" ( partkey=<pkDef> | [pkey]=<cident> )
@@ -986,7 +993,11 @@ def create_cf_composite_primary_key_comma_completer(ctxt, cass):
 
 syntax_rules += r'''
 <createIndexStatement> ::= "CREATE" "CUSTOM"? "INDEX" ("IF" "NOT" "EXISTS")? indexname=<identifier>? "ON"
-                               cf=<columnFamilyName> ( "(" col=<cident> ")" | "(" "KEYS"  "(" col=<cident> ")" ")")
+                               cf=<columnFamilyName> "(" (
+                                   col=<cident> |
+                                   "keys(" col=<cident> ")" |
+                                   "fullCollection(" col=<cident> ")"
+                               ) ")"
                                ( "USING" <stringLiteral> ( "WITH" "OPTIONS" "=" <mapLiteral> )? )?
                          ;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/AbstractMarker.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
index 10a4dff..d18790c 100644
--- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java
+++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java
@@ -99,7 +99,7 @@ public abstract class AbstractMarker extends Term.NonTerminal
         private static ColumnSpecification makeInReceiver(ColumnSpecification receiver)
         {
             ColumnIdentifier inName = new ColumnIdentifier("in(" + receiver.name + ")", true);
-            return new ColumnSpecification(receiver.ksName, receiver.cfName, inName, ListType.getInstance(receiver.type));
+            return new ColumnSpecification(receiver.ksName, receiver.cfName, inName, ListType.getInstance(receiver.type, false));
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/CQL3Row.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/CQL3Row.java b/src/java/org/apache/cassandra/cql3/CQL3Row.java
index 6fa2b64..e3e76d1 100644
--- a/src/java/org/apache/cassandra/cql3/CQL3Row.java
+++ b/src/java/org/apache/cassandra/cql3/CQL3Row.java
@@ -27,7 +27,7 @@ public interface CQL3Row
 {
     public ByteBuffer getClusteringColumn(int i);
     public Cell getColumn(ColumnIdentifier name);
-    public List<Cell> getCollection(ColumnIdentifier name);
+    public List<Cell> getMultiCellColumn(ColumnIdentifier name);
 
     public interface Builder
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/CQL3Type.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/CQL3Type.java b/src/java/org/apache/cassandra/cql3/CQL3Type.java
index 6d55285..b656de8 100644
--- a/src/java/org/apache/cassandra/cql3/CQL3Type.java
+++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java
@@ -26,9 +26,13 @@ import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.SyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public interface CQL3Type
 {
+    static final Logger logger = LoggerFactory.getLogger(CQL3Type.class);
+
     public boolean isCollection();
     public AbstractType<?> getType();
 
@@ -160,17 +164,30 @@ public interface CQL3Type
         @Override
         public String toString()
         {
+            boolean isFrozen = !this.type.isMultiCell();
+            StringBuilder sb = new StringBuilder(isFrozen ? "frozen<" : "");
             switch (type.kind)
             {
                 case LIST:
-                    return "list<" + ((ListType)type).elements.asCQL3Type() + ">";
+                    AbstractType<?> listType = ((ListType)type).getElementsType();
+                    sb.append("list<").append(listType.asCQL3Type());
+                    break;
                 case SET:
-                    return "set<" + ((SetType)type).elements.asCQL3Type() + ">";
+                    AbstractType<?> setType = ((SetType)type).getElementsType();
+                    sb.append("set<").append(setType.asCQL3Type());
+                    break;
                 case MAP:
-                    MapType mt = (MapType)type;
-                    return "map<" + mt.keys.asCQL3Type() + ", " + mt.values.asCQL3Type() + ">";
+                    AbstractType<?> keysType = ((MapType)type).getKeysType();
+                    AbstractType<?> valuesType = ((MapType)type).getValuesType();
+                    sb.append("map<").append(keysType.asCQL3Type()).append(", ").append(valuesType.asCQL3Type());
+                    break;
+                default:
+                    throw new AssertionError();
             }
-            throw new AssertionError();
+            sb.append(">");
+            if (isFrozen)
+                sb.append(">");
+            return sb.toString();
         }
     }
 
@@ -284,7 +301,9 @@ public interface CQL3Type
     // actual type used, so Raw is a "not yet prepared" CQL3Type.
     public abstract class Raw
     {
-        protected boolean frozen;
+        protected boolean frozen = false;
+
+        protected abstract boolean supportsFreezing();
 
         public boolean isCollection()
         {
@@ -296,10 +315,10 @@ public interface CQL3Type
             return false;
         }
 
-        public Raw freeze()
+        public void freeze() throws InvalidRequestException
         {
-            frozen = true;
-            return this;
+            String message = String.format("frozen<> is only allowed on collections, tuples, and user-defined types (got %s)", this);
+            throw new InvalidRequestException(message);
         }
 
         public abstract CQL3Type prepare(String keyspace) throws InvalidRequestException;
@@ -314,53 +333,30 @@ public interface CQL3Type
             return new RawUT(name);
         }
 
-        public static Raw map(CQL3Type.Raw t1, CQL3Type.Raw t2) throws InvalidRequestException
+        public static Raw map(CQL3Type.Raw t1, CQL3Type.Raw t2)
         {
-            if (t1.isCollection() || t2.isCollection())
-                throw new InvalidRequestException("map type cannot contain another collection");
-            if (t1.isCounter() || t2.isCounter())
-                throw new InvalidRequestException("counters are not allowed inside a collection");
-
             return new RawCollection(CollectionType.Kind.MAP, t1, t2);
         }
 
-        public static Raw list(CQL3Type.Raw t) throws InvalidRequestException
+        public static Raw list(CQL3Type.Raw t)
         {
-            if (t.isCollection())
-                throw new InvalidRequestException("list type cannot contain another collection");
-            if (t.isCounter())
-                throw new InvalidRequestException("counters are not allowed inside a collection");
-
             return new RawCollection(CollectionType.Kind.LIST, null, t);
         }
 
-        public static Raw set(CQL3Type.Raw t) throws InvalidRequestException
+        public static Raw set(CQL3Type.Raw t)
         {
-            if (t.isCollection())
-                throw new InvalidRequestException("set type cannot contain another collection");
-            if (t.isCounter())
-                throw new InvalidRequestException("counters are not allowed inside a collection");
-
             return new RawCollection(CollectionType.Kind.SET, null, t);
         }
 
-        public static Raw tuple(List<CQL3Type.Raw> ts) throws InvalidRequestException
+        public static Raw tuple(List<CQL3Type.Raw> ts)
         {
-            for (int i = 0; i < ts.size(); i++)
-                if (ts.get(i) != null && ts.get(i).isCounter())
-                    throw new InvalidRequestException("counters are not allowed inside tuples");
-
             return new RawTuple(ts);
         }
 
         public static Raw frozen(CQL3Type.Raw t) throws InvalidRequestException
         {
-            if (t instanceof RawUT)
-                return ((RawUT)t).freeze();
-            if (t instanceof RawTuple)
-                return ((RawTuple)t).freeze();
-
-            throw new InvalidRequestException("frozen<> is only currently only allowed on User-Defined and tuple types");
+            t.freeze();
+            return t;
         }
 
         private static class RawType extends Raw
@@ -377,6 +373,11 @@ public interface CQL3Type
                 return type;
             }
 
+            protected boolean supportsFreezing()
+            {
+                return false;
+            }
+
             public boolean isCounter()
             {
                 return type == Native.COUNTER;
@@ -402,12 +403,18 @@ public interface CQL3Type
                 this.values = values;
             }
 
-            public Raw freeze()
+            public void freeze() throws InvalidRequestException
             {
-                if (keys != null)
+                if (keys != null && keys.supportsFreezing())
                     keys.freeze();
-                values.freeze();
-                return super.freeze();
+                if (values != null && values.supportsFreezing())
+                    values.freeze();
+                frozen = true;
+            }
+
+            protected boolean supportsFreezing()
+            {
+                return true;
             }
 
             public boolean isCollection()
@@ -417,11 +424,28 @@ public interface CQL3Type
 
             public CQL3Type prepare(String keyspace) throws InvalidRequestException
             {
+                assert values != null : "Got null values type for a collection";
+
+                if (!frozen && values.supportsFreezing() && !values.frozen)
+                    throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
+                if (values.isCounter())
+                    throw new InvalidRequestException("Counters are not allowed inside collections: " + this);
+
+                if (keys != null)
+                {
+                    if (!frozen && keys.supportsFreezing() && !keys.frozen)
+                        throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
+                }
+
                 switch (kind)
                 {
-                    case LIST: return new Collection(ListType.getInstance(values.prepare(keyspace).getType()));
-                    case SET:  return new Collection(SetType.getInstance(values.prepare(keyspace).getType()));
-                    case MAP:  return new Collection(MapType.getInstance(keys.prepare(keyspace).getType(), values.prepare(keyspace).getType()));
+                    case LIST:
+                        return new Collection(ListType.getInstance(values.prepare(keyspace).getType(), !frozen));
+                    case SET:
+                        return new Collection(SetType.getInstance(values.prepare(keyspace).getType(), !frozen));
+                    case MAP:
+                        assert keys != null : "Got null keys type for a collection";
+                        return new Collection(MapType.getInstance(keys.prepare(keyspace).getType(), values.prepare(keyspace).getType(), !frozen));
                 }
                 throw new AssertionError();
             }
@@ -429,11 +453,13 @@ public interface CQL3Type
             @Override
             public String toString()
             {
+                String start = frozen? "frozen<" : "";
+                String end = frozen ? ">" : "";
                 switch (kind)
                 {
-                    case LIST: return "list<" + values + ">";
-                    case SET:  return "set<" + values + ">";
-                    case MAP:  return "map<" + keys + ", " + values + ">";
+                    case LIST: return start + "list<" + values + ">" + end;
+                    case SET:  return start + "set<" + values + ">" + end;
+                    case MAP:  return start + "map<" + keys + ", " + values + ">" + end;
                 }
                 throw new AssertionError();
             }
@@ -448,6 +474,11 @@ public interface CQL3Type
                 this.name = name;
             }
 
+            public void freeze()
+            {
+                frozen = true;
+            }
+
             public CQL3Type prepare(String keyspace) throws InvalidRequestException
             {
                 if (name.hasKeyspace())
@@ -477,7 +508,7 @@ public interface CQL3Type
                 return new UserDefined(name.toString(), type);
             }
 
-            public boolean isUDT()
+            protected boolean supportsFreezing()
             {
                 return true;
             }
@@ -498,12 +529,9 @@ public interface CQL3Type
                 this.types = types;
             }
 
-            public Raw freeze()
+            protected boolean supportsFreezing()
             {
-                for (CQL3Type.Raw t : types)
-                    if (t != null)
-                        t.freeze();
-                return super.freeze();
+                return true;
             }
 
             public boolean isCollection()
@@ -511,15 +539,29 @@ public interface CQL3Type
                 return false;
             }
 
-            public CQL3Type prepare(String keyspace) throws InvalidRequestException
+            public void freeze() throws InvalidRequestException
             {
-                List<AbstractType<?>> ts = new ArrayList<>(types.size());
                 for (CQL3Type.Raw t : types)
-                    ts.add(t.prepare(keyspace).getType());
+                {
+                    if (t.supportsFreezing())
+                        t.freeze();
+                }
+                frozen = true;
+            }
 
+            public CQL3Type prepare(String keyspace) throws InvalidRequestException
+            {
                 if (!frozen)
-                    throw new InvalidRequestException("Non-frozen tuples are not supported, please use frozen<>");
+                    freeze();
 
+                List<AbstractType<?>> ts = new ArrayList<>(types.size());
+                for (CQL3Type.Raw t : types)
+                {
+                    if (t.isCounter())
+                        throw new InvalidRequestException("Counters are not allowed inside tuples");
+
+                    ts.add(t.prepare(keyspace).getType());
+                }
                 return new Tuple(new TupleType(ts));
             }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 25cb07d..1a8e5a3 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java
@@ -20,7 +20,6 @@ package org.apache.cassandra.cql3;
 import java.nio.ByteBuffer;
 import java.util.*;
 
-import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterators;
 import static com.google.common.collect.Lists.newArrayList;
@@ -33,12 +32,13 @@ import org.apache.cassandra.db.composites.Composite;
 import org.apache.cassandra.db.filter.ColumnSlice;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.transport.Server;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * A CQL3 condition.
+ * A CQL3 condition on the value of a column or collection element.  For example, "UPDATE .. IF a = 0".
  */
 public class ColumnCondition
 {
@@ -235,12 +235,6 @@ public class ColumnCondition
             CellName name = current.metadata().comparator.create(rowPrefix, column);
             return isSatisfiedByValue(value, current.getColumn(name), column.type, operator, now);
         }
-
-        @Override
-        public int hashCode()
-        {
-            return Objects.hashCode(column, value, operator);
-        }
     }
 
     /**
@@ -275,12 +269,6 @@ public class ColumnCondition
             }
             return false;
         }
-
-        @Override
-        public int hashCode()
-        {
-            return Objects.hashCode(column, inValues, operator);
-        }
     }
 
     /** A condition on an element of a collection column. IN operators are not supported here, see ElementAccessInBound. */
@@ -305,16 +293,37 @@ public class ColumnCondition
 
             if (column.type instanceof MapType)
             {
-                Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement));
-                return isSatisfiedByValue(value, cell, ((MapType) column.type).values, operator, now);
+                MapType mapType = (MapType) column.type;
+                if (column.type.isMultiCell())
+                {
+                    Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement));
+                    return isSatisfiedByValue(value, cell, mapType.getValuesType(), operator, now);
+                }
+                else
+                {
+                    Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
+                    ByteBuffer mapElementValue = cell.isLive(now) ? mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType())
+                                                                  : null;
+                    return compareWithOperator(operator, mapType.getValuesType(), value, mapElementValue);
+                }
             }
 
             // sets don't have element access, so it's a list
-            assert column.type instanceof ListType;
-            ByteBuffer columnValue = getListItem(
-                    collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now),
-                    getListIndex(collectionElement));
-            return compareWithOperator(operator, ((ListType)column.type).elements, value, columnValue);
+            ListType listType = (ListType) column.type;
+            if (column.type.isMultiCell())
+            {
+                ByteBuffer columnValue = getListItem(
+                        collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now),
+                        getListIndex(collectionElement));
+                return compareWithOperator(operator, listType.getElementsType(), value, columnValue);
+            }
+            else
+            {
+                Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
+                ByteBuffer listElementValue = cell.isLive(now) ? listType.getSerializer().getElement(cell.value(), getListIndex(collectionElement))
+                                                               : null;
+                return compareWithOperator(operator, listType.getElementsType(), value, listElementValue);
+            }
         }
 
         static int getListIndex(ByteBuffer collectionElement) throws InvalidRequestException
@@ -338,12 +347,6 @@ public class ColumnCondition
         {
             return collectionElement;
         }
-
-        @Override
-        public int hashCode()
-        {
-            return Objects.hashCode(column, collectionElement, value, operator);
-        }
     }
 
     static class ElementAccessInBound extends Bound
@@ -375,47 +378,86 @@ public class ColumnCondition
             CellNameType nameType = current.metadata().comparator;
             if (column.type instanceof MapType)
             {
-                CellName name = nameType.create(rowPrefix, column, collectionElement);
-                Cell item = current.getColumn(name);
-                AbstractType<?> valueType = ((MapType) column.type).values;
-                for (ByteBuffer value : inValues)
+                MapType mapType = (MapType) column.type;
+                AbstractType<?> valueType = mapType.getValuesType();
+                if (column.type.isMultiCell())
                 {
-                    if (isSatisfiedByValue(value, item, valueType, Operator.EQ, now))
-                        return true;
+                    CellName name = nameType.create(rowPrefix, column, collectionElement);
+                    Cell item = current.getColumn(name);
+                    for (ByteBuffer value : inValues)
+                    {
+                        if (isSatisfiedByValue(value, item, valueType, Operator.EQ, now))
+                            return true;
+                    }
+                    return false;
+                }
+                else
+                {
+                    Cell cell = current.getColumn(nameType.create(rowPrefix, column));
+                    ByteBuffer mapElementValue  = null;
+                    if (cell != null && cell.isLive(now))
+                        mapElementValue =  mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType());
+                    for (ByteBuffer value : inValues)
+                    {
+                        if (value == null)
+                        {
+                            if (mapElementValue == null)
+                                return true;
+                            continue;
+                        }
+                        if (valueType.compare(value, mapElementValue) == 0)
+                            return true;
+                    }
+                    return false;
                 }
-                return false;
             }
 
-            assert column.type instanceof ListType;
-            ByteBuffer columnValue = ElementAccessBound.getListItem(
-                    collectionColumns(nameType.create(rowPrefix, column), current, now),
-                    ElementAccessBound.getListIndex(collectionElement));
+            ListType listType = (ListType) column.type;
+            AbstractType<?> elementsType = listType.getElementsType();
+            if (column.type.isMultiCell())
+            {
+                ByteBuffer columnValue = ElementAccessBound.getListItem(
+                        collectionColumns(nameType.create(rowPrefix, column), current, now),
+                        ElementAccessBound.getListIndex(collectionElement));
 
-            AbstractType<?> valueType = ((ListType) column.type).elements;
-            for (ByteBuffer value : inValues)
+                for (ByteBuffer value : inValues)
+                {
+                    if (compareWithOperator(Operator.EQ, elementsType, value, columnValue))
+                        return true;
+                }
+            }
+            else
             {
-                if (compareWithOperator(Operator.EQ, valueType, value, columnValue))
-                    return true;
+                Cell cell = current.getColumn(nameType.create(rowPrefix, column));
+                ByteBuffer listElementValue = null;
+                if (cell != null && cell.isLive(now))
+                    listElementValue = listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement));
+
+                for (ByteBuffer value : inValues)
+                {
+                    if (value == null)
+                    {
+                        if (listElementValue == null)
+                            return true;
+                        continue;
+                    }
+                    if (elementsType.compare(value, listElementValue) == 0)
+                        return true;
+                }
             }
             return false;
         }
-
-        @Override
-        public int hashCode()
-        {
-            return Objects.hashCode(column, collectionElement, inValues, operator);
-        }
     }
 
     /** A condition on an entire collection column. IN operators are not supported here, see CollectionInBound. */
     static class CollectionBound extends Bound
     {
-        public final Term.Terminal value;
+        private final Term.Terminal value;
 
         private CollectionBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
         {
             super(condition.column, condition.operator);
-            assert column.type instanceof CollectionType && condition.collectionElement == null;
+            assert column.type.isCollection() && condition.collectionElement == null;
             assert !condition.operator.equals(Operator.IN);
             this.value = condition.value.bind(options);
         }
@@ -424,18 +466,44 @@ public class ColumnCondition
         {
             CollectionType type = (CollectionType)column.type;
 
-            Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now);
+            if (type.isMultiCell())
+            {
+                Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now);
+                if (value == null)
+                {
+                    if (operator.equals(Operator.EQ))
+                        return !iter.hasNext();
+                    else if (operator.equals(Operator.NEQ))
+                        return iter.hasNext();
+                    else
+                        throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator));
+                }
+
+                return valueAppliesTo(type, iter, value, operator);
+            }
+
+            // frozen collections
+            Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
             if (value == null)
             {
-                if (operator.equals(Operator.EQ))
-                    return !iter.hasNext();
-                else if (operator.equals(Operator.NEQ))
-                    return iter.hasNext();
+                if (operator == Operator.EQ)
+                    return cell == null || !cell.isLive(now);
+                else if (operator == Operator.NEQ)
+                    return cell != null && cell.isLive(now);
                 else
                     throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator));
             }
 
-            return valueAppliesTo(type, iter, value, operator);
+            // make sure we use v3 serialization format for comparison
+            ByteBuffer conditionValue;
+            if (type.kind == CollectionType.Kind.LIST)
+                conditionValue = ((Lists.Value) value).getWithProtocolVersion(Server.VERSION_3);
+            else if (type.kind == CollectionType.Kind.SET)
+                conditionValue = ((Sets.Value) value).getWithProtocolVersion(Server.VERSION_3);
+            else
+                conditionValue = ((Maps.Value) value).getWithProtocolVersion(Server.VERSION_3);
+
+            return compareWithOperator(operator, type, conditionValue, cell.value());
         }
 
         static boolean valueAppliesTo(CollectionType type, Iterator<Cell> iter, Term.Terminal value, Operator operator)
@@ -495,15 +563,15 @@ public class ColumnCondition
 
         static boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements, Operator operator)
         {
-            return setOrListAppliesTo(type.elements, iter, elements.iterator(), operator, false);
+            return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false);
         }
 
         static boolean setAppliesTo(SetType type, Iterator<Cell> iter, Set<ByteBuffer> elements, Operator operator)
         {
             ArrayList<ByteBuffer> sortedElements = new ArrayList<>(elements.size());
             sortedElements.addAll(elements);
-            Collections.sort(sortedElements, type.elements);
-            return setOrListAppliesTo(type.elements, iter, sortedElements.iterator(), operator, true);
+            Collections.sort(sortedElements, type.getElementsType());
+            return setOrListAppliesTo(type.getElementsType(), iter, sortedElements.iterator(), operator, true);
         }
 
         static boolean mapAppliesTo(MapType type, Iterator<Cell> iter, Map<ByteBuffer, ByteBuffer> elements, Operator operator)
@@ -518,12 +586,12 @@ public class ColumnCondition
                 Cell c = iter.next();
 
                 // compare the keys
-                int comparison = type.keys.compare(c.name().collectionElement(), conditionEntry.getKey());
+                int comparison = type.getKeysType().compare(c.name().collectionElement(), conditionEntry.getKey());
                 if (comparison != 0)
                     return evaluateComparisonWithOperator(comparison, operator);
 
                 // compare the values
-                comparison = type.values.compare(c.value(), conditionEntry.getValue());
+                comparison = type.getValuesType().compare(c.value(), conditionEntry.getValue());
                 if (comparison != 0)
                     return evaluateComparisonWithOperator(comparison, operator);
             }
@@ -534,33 +602,11 @@ public class ColumnCondition
             // they're equal
             return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE;
         }
-
-        @Override
-        public int hashCode()
-        {
-            Object val = null;
-            if (value != null)
-            {
-                switch (((CollectionType)column.type).kind)
-                {
-                    case LIST:
-                        val = ((Lists.Value)value).elements.hashCode();
-                        break;
-                    case SET:
-                        val = ((Sets.Value)value).elements.hashCode();
-                        break;
-                    case MAP:
-                        val = ((Maps.Value)value).map.hashCode();
-                        break;
-                }
-            }
-            return Objects.hashCode(column, val);
-        }
     }
 
     public static class CollectionInBound extends Bound
     {
-        public final List<Term.Terminal> inValues;
+        private final List<Term.Terminal> inValues;
 
         private CollectionInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
         {
@@ -575,7 +621,7 @@ public class ColumnCondition
                 Lists.Marker inValuesMarker = (Lists.Marker) condition.value;
                 if (column.type instanceof ListType)
                 {
-                    ListType deserializer = ListType.getInstance(collectionType.valueComparator());
+                    ListType deserializer = ListType.getInstance(collectionType.valueComparator(), false);
                     for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
                     {
                         if (buffer == null)
@@ -586,7 +632,7 @@ public class ColumnCondition
                 }
                 else if (column.type instanceof MapType)
                 {
-                    MapType deserializer = MapType.getInstance(collectionType.nameComparator(), collectionType.valueComparator());
+                    MapType deserializer = MapType.getInstance(collectionType.nameComparator(), collectionType.valueComparator(), false);
                     for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
                     {
                         if (buffer == null)
@@ -597,7 +643,7 @@ public class ColumnCondition
                 }
                 else if (column.type instanceof SetType)
                 {
-                    SetType deserializer = SetType.getInstance(collectionType.valueComparator());
+                    SetType deserializer = SetType.getInstance(collectionType.valueComparator(), false);
                     for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
                     {
                         if (buffer == null)
@@ -618,45 +664,34 @@ public class ColumnCondition
         {
             CollectionType type = (CollectionType)column.type;
             CellName name = current.metadata().comparator.create(rowPrefix, column);
-
-            // copy iterator contents so that we can properly reuse them for each comparison with an IN value
-            List<Cell> cells = newArrayList(collectionColumns(name, current, now));
-            for (Term.Terminal value : inValues)
+            if (type.isMultiCell())
             {
-                if (CollectionBound.valueAppliesTo(type, cells.iterator(), value, Operator.EQ))
-                    return true;
+                // copy iterator contents so that we can properly reuse them for each comparison with an IN value
+                List<Cell> cells = newArrayList(collectionColumns(name, current, now));
+                for (Term.Terminal value : inValues)
+                {
+                    if (CollectionBound.valueAppliesTo(type, cells.iterator(), value, Operator.EQ))
+                        return true;
+                }
+                return false;
             }
-            return false;
-        }
-
-        @Override
-        public int hashCode()
-        {
-            List<Collection<ByteBuffer>> inValueBuffers = new ArrayList<>(inValues.size());
-            switch (((CollectionType)column.type).kind)
+            else
             {
-                case LIST:
-                    for (Term.Terminal term : inValues)
-                        inValueBuffers.add(term == null ? null : ((Lists.Value)term).elements);
-                    break;
-                case SET:
-                    for (Term.Terminal term : inValues)
-                        inValueBuffers.add(term == null ? null : ((Sets.Value)term).elements);
-                    break;
-                case MAP:
-                    for (Term.Terminal term : inValues)
+                Cell cell = current.getColumn(name);
+                for (Term.Terminal value : inValues)
+                {
+                    if (value == null)
                     {
-                        if (term != null)
-                        {
-                            inValueBuffers.add(((Maps.Value)term).map.keySet());
-                            inValueBuffers.add(((Maps.Value)term).map.values());
-                        }
-                        else
-                            inValueBuffers.add(null);
+                        if (cell == null || !cell.isLive(now))
+                            return true;
                     }
-                    break;
+                    else if (type.compare(((Term.CollectionTerminal)value).getWithProtocolVersion(Server.VERSION_3), cell.value()) == 0)
+                    {
+                        return true;
+                    }
+                }
+                return false;
             }
-            return Objects.hashCode(column, inValueBuffers, operator);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 a8f0120..01fbdf0 100644
--- a/src/java/org/apache/cassandra/cql3/Constants.java
+++ b/src/java/org/apache/cassandra/cql3/Constants.java
@@ -18,7 +18,6 @@
 package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
-import java.util.List;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -368,7 +367,7 @@ public abstract class Constants
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
             CellName cname = cf.getComparator().create(prefix, column);
-            if (column.type.isCollection())
+            if (column.type.isMultiCell())
                 cf.addAtom(params.makeRangeTombstone(cname.slice()));
             else
                 cf.addColumn(params.makeTombstone(cname));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index b1c598b..06ba81c 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -575,8 +575,9 @@ createIndexStatement returns [CreateIndexStatement expr]
     ;
 
 indexIdent returns [IndexTarget.Raw id]
-    : c=cident                { $id = IndexTarget.Raw.of(c); }
+    : c=cident                { $id = IndexTarget.Raw.valuesOf(c); }
     | K_KEYS '(' c=cident ')' { $id = IndexTarget.Raw.keysOf(c); }
+    | K_FULL '(' c=cident ')' { $id = IndexTarget.Raw.fullCollection(c); }
     ;
 
 
@@ -1148,21 +1149,21 @@ native_type returns [CQL3Type t]
 
 collection_type returns [CQL3Type.Raw pt]
     : K_MAP  '<' t1=comparatorType ',' t2=comparatorType '>'
-        { try {
+        {
             // if we can't parse either t1 or t2, antlr will "recover" and we may have t1 or t2 null.
             if (t1 != null && t2 != null)
                 $pt = CQL3Type.Raw.map(t1, t2);
-          } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
+        }
     | K_LIST '<' t=comparatorType '>'
-        { try { if (t != null) $pt = CQL3Type.Raw.list(t); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
+        { if (t != null) $pt = CQL3Type.Raw.list(t); }
     | K_SET  '<' t=comparatorType '>'
-        { try { if (t != null) $pt = CQL3Type.Raw.set(t); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
+        { if (t != null) $pt = CQL3Type.Raw.set(t); }
     ;
 
 tuple_type returns [CQL3Type.Raw t]
     : K_TUPLE '<' { List<CQL3Type.Raw> types = new ArrayList<>(); }
          t1=comparatorType { types.add(t1); } (',' tn=comparatorType { types.add(tn); })*
-      '>' { try { $t = CQL3Type.Raw.tuple(types); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); }}
+      '>' { $t = CQL3Type.Raw.tuple(types); }
     ;
 
 username
@@ -1227,6 +1228,7 @@ K_WHERE:       W H E R E;
 K_AND:         A N D;
 K_KEY:         K E Y;
 K_KEYS:        K E Y S;
+K_FULL:        F U L L;
 K_INSERT:      I N S E R T;
 K_UPDATE:      U P D A T E;
 K_WITH:        W I T H;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 9d22364..d5174d1 100644
--- a/src/java/org/apache/cassandra/cql3/Lists.java
+++ b/src/java/org/apache/cassandra/cql3/Lists.java
@@ -19,8 +19,6 @@ package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -34,6 +32,7 @@ import org.apache.cassandra.db.marshal.ListType;
 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.UUIDGen;
@@ -56,7 +55,7 @@ public abstract class Lists
 
     public static ColumnSpecification valueSpecOf(ColumnSpecification column)
     {
-        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).elements);
+        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).getElementsType());
     }
 
     public static class Literal implements Term.Raw
@@ -124,7 +123,7 @@ public abstract class Lists
         }
     }
 
-    public static class Value extends Term.MultiItemTerminal
+    public static class Value extends Term.MultiItemTerminal implements Term.CollectionTerminal
     {
         public final List<ByteBuffer> elements;
 
@@ -143,7 +142,7 @@ public abstract class Lists
                 List<ByteBuffer> elements = new ArrayList<ByteBuffer>(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.elements.decompose(element));
+                    elements.add(element == null ? null : type.getElementsType().decompose(element));
                 return new Value(elements);
             }
             catch (MarshalException e)
@@ -154,7 +153,12 @@ public abstract class Lists
 
         public ByteBuffer get(QueryOptions options)
         {
-            return CollectionSerializer.pack(elements, elements.size(), options.getProtocolVersion());
+            return getWithProtocolVersion(options.getProtocolVersion());
+        }
+
+        public ByteBuffer getWithProtocolVersion(int protocolVersion)
+        {
+            return CollectionSerializer.pack(elements, elements.size(), protocolVersion);
         }
 
         public boolean equals(ListType lt, Value v)
@@ -163,7 +167,7 @@ public abstract class Lists
                 return false;
 
             for (int i = 0; i < elements.size(); i++)
-                if (lt.elements.compare(elements.get(i), v.elements.get(i)) != 0)
+                if (lt.getElementsType().compare(elements.get(i), v.elements.get(i)) != 0)
                     return false;
 
             return true;
@@ -292,9 +296,12 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
-            // delete + append
-            CellName name = cf.getComparator().create(prefix, column);
-            cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            if (column.type.isMultiCell())
+            {
+                // delete + append
+                CellName name = cf.getComparator().create(prefix, column);
+                cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            }
             Appender.doAppend(t, cf, prefix, column, params);
         }
     }
@@ -324,6 +331,9 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            // we should not get here for frozen lists
+            assert column.type.isMultiCell() : "Attempted to set an individual element on a frozen list";
+
             ByteBuffer index = idx.bindAndGet(params.options);
             ByteBuffer value = t.bindAndGet(params.options);
 
@@ -362,23 +372,36 @@ 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);
         }
 
         static void doAppend(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
         {
             Term.Terminal value = t.bind(params.options);
-            // If we append null, do nothing. Note that for Setter, we've
-            // already removed the previous value so we're good here too
-            if (value == null)
-                return;
-
-            assert value instanceof Lists.Value;
-            List<ByteBuffer> toAdd = ((Lists.Value)value).elements;
-            for (int i = 0; i < toAdd.size(); i++)
+            Lists.Value listValue = (Lists.Value)value;
+            if (column.type.isMultiCell())
             {
-                ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
-                cf.addColumn(params.makeColumn(cf.getComparator().create(prefix, column, uuid), toAdd.get(i)));
+                // If we append null, do nothing. Note that for Setter, we've
+                // already removed the previous value so we're good here too
+                if (value == null)
+                    return;
+
+                List<ByteBuffer> toAdd = listValue.elements;
+                for (int i = 0; i < toAdd.size(); i++)
+                {
+                    ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
+                    cf.addColumn(params.makeColumn(cf.getComparator().create(prefix, column, uuid), toAdd.get(i)));
+                }
+            }
+            else
+            {
+                // for frozen lists, we're overwriting the whole cell value
+                CellName name = cf.getComparator().create(prefix, column);
+                if (value == null)
+                    cf.addAtom(params.makeTombstone(name));
+                else
+                    cf.addColumn(params.makeColumn(name, listValue.getWithProtocolVersion(Server.CURRENT_VERSION)));
             }
         }
     }
@@ -392,6 +415,7 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to prepend to a frozen list";
             Term.Terminal value = t.bind(params.options);
             if (value == null)
                 return;
@@ -424,6 +448,7 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to delete from a frozen list";
             List<Cell> existingList = params.getPrefetchedList(rowKey, column.name);
             // We want to call bind before possibly returning to reject queries where the value provided is not a list.
             Term.Terminal value = t.bind(params.options);
@@ -464,6 +489,7 @@ public abstract class Lists
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to delete an item by index from a frozen list";
             Term.Terminal index = t.bind(params.options);
             if (index == null)
                 throw new InvalidRequestException("Invalid null value for list index");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 ce0ba2a..5b58833 100644
--- a/src/java/org/apache/cassandra/cql3/Maps.java
+++ b/src/java/org/apache/cassandra/cql3/Maps.java
@@ -19,7 +19,6 @@ package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -36,6 +35,7 @@ import org.apache.cassandra.db.marshal.MapType;
 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.FBUtilities;
 import org.apache.cassandra.utils.Pair;
 
@@ -48,12 +48,12 @@ public abstract class Maps
 
     public static ColumnSpecification keySpecOf(ColumnSpecification column)
     {
-        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).keys);
+        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).getKeysType());
     }
 
     public static ColumnSpecification valueSpecOf(ColumnSpecification column)
     {
-        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).values);
+        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).getValuesType());
     }
 
     public static class Literal implements Term.Raw
@@ -86,7 +86,7 @@ public abstract class Maps
 
                 values.put(k, v);
             }
-            DelayedValue value = new DelayedValue(((MapType)receiver.type).keys, values);
+            DelayedValue value = new DelayedValue(((MapType)receiver.type).getKeysType(), values);
             return allTerminal ? value.bind(QueryOptions.DEFAULT) : value;
         }
 
@@ -134,7 +134,7 @@ public abstract class Maps
         }
     }
 
-    public static class Value extends Term.Terminal
+    public static class Value extends Term.Terminal implements Term.CollectionTerminal
     {
         public final Map<ByteBuffer, ByteBuffer> map;
 
@@ -152,7 +152,7 @@ public abstract class Maps
                 Map<?, ?> m = (Map<?, ?>)type.getSerializer().deserializeForNativeProtocol(value, version);
                 Map<ByteBuffer, ByteBuffer> map = new LinkedHashMap<ByteBuffer, ByteBuffer>(m.size());
                 for (Map.Entry<?, ?> entry : m.entrySet())
-                    map.put(type.keys.decompose(entry.getKey()), type.values.decompose(entry.getValue()));
+                    map.put(type.getKeysType().decompose(entry.getKey()), type.getValuesType().decompose(entry.getValue()));
                 return new Value(map);
             }
             catch (MarshalException e)
@@ -163,13 +163,18 @@ public abstract class Maps
 
         public ByteBuffer get(QueryOptions options)
         {
-            List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(2 * map.size());
+            return getWithProtocolVersion(options.getProtocolVersion());
+        }
+
+        public ByteBuffer getWithProtocolVersion(int protocolVersion)
+        {
+            List<ByteBuffer> buffers = new ArrayList<>(2 * map.size());
             for (Map.Entry<ByteBuffer, ByteBuffer> entry : map.entrySet())
             {
                 buffers.add(entry.getKey());
                 buffers.add(entry.getValue());
             }
-            return CollectionSerializer.pack(buffers, map.size(), options.getProtocolVersion());
+            return CollectionSerializer.pack(buffers, map.size(), protocolVersion);
         }
 
         public boolean equals(MapType mt, Value v)
@@ -184,7 +189,7 @@ public abstract class Maps
             {
                 Map.Entry<ByteBuffer, ByteBuffer> thisEntry = thisIter.next();
                 Map.Entry<ByteBuffer, ByteBuffer> thatEntry = thatIter.next();
-                if (mt.keys.compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.values.compare(thisEntry.getValue(), thatEntry.getValue()) != 0)
+                if (mt.getKeysType().compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.getValuesType().compare(thisEntry.getValue(), thatEntry.getValue()) != 0)
                     return false;
             }
 
@@ -266,9 +271,12 @@ public abstract class Maps
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
-            // delete + put
-            CellName name = cf.getComparator().create(prefix, column);
-            cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            if (column.type.isMultiCell())
+            {
+                // delete + put
+                CellName name = cf.getComparator().create(prefix, column);
+                cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            }
             Putter.doPut(t, cf, prefix, column, params);
         }
     }
@@ -292,6 +300,7 @@ public abstract class Maps
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to set a value for a single key on a frozen map";
             ByteBuffer key = k.bindAndGet(params.options);
             ByteBuffer value = t.bindAndGet(params.options);
             if (key == null)
@@ -325,21 +334,33 @@ 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);
         }
 
         static void doPut(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
         {
             Term.Terminal value = t.bind(params.options);
-            if (value == null)
-                return;
-            assert value instanceof Maps.Value;
-
-            Map<ByteBuffer, ByteBuffer> toAdd = ((Maps.Value)value).map;
-            for (Map.Entry<ByteBuffer, ByteBuffer> entry : toAdd.entrySet())
+            Maps.Value mapValue = (Maps.Value) value;
+            if (column.type.isMultiCell())
+            {
+                if (value == null)
+                    return;
+
+                for (Map.Entry<ByteBuffer, ByteBuffer> entry : mapValue.map.entrySet())
+                {
+                    CellName cellName = cf.getComparator().create(prefix, column, entry.getKey());
+                    cf.addColumn(params.makeColumn(cellName, entry.getValue()));
+                }
+            }
+            else
             {
-                CellName cellName = cf.getComparator().create(prefix, column, entry.getKey());
-                cf.addColumn(params.makeColumn(cellName, entry.getValue()));
+                // for frozen maps, we're overwriting the whole cell
+                CellName cellName = cf.getComparator().create(prefix, column);
+                if (value == null)
+                    cf.addAtom(params.makeTombstone(cellName));
+                else
+                    cf.addColumn(params.makeColumn(cellName, mapValue.getWithProtocolVersion(Server.CURRENT_VERSION)));
             }
         }
     }
@@ -353,6 +374,7 @@ public abstract class Maps
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to delete a single key in a frozen map";
             Term.Terminal key = t.bind(params.options);
             if (key == null)
                 throw new InvalidRequestException("Invalid null map key");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/Operation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java
index 32b6a12..816acb2 100644
--- a/src/java/org/apache/cassandra/cql3/Operation.java
+++ b/src/java/org/apache/cassandra/cql3/Operation.java
@@ -163,7 +163,7 @@ public abstract class Operation
             if (receiver.type instanceof CounterColumnType)
                 throw new InvalidRequestException(String.format("Cannot set the value of counter column %s (counters can only be incremented/decremented, not set)", receiver.name));
 
-            if (!(receiver.type instanceof CollectionType))
+            if (!(receiver.type.isCollection()))
                 return new Constants.Setter(receiver, v);
 
             switch (((CollectionType)receiver.type).kind)
@@ -206,6 +206,8 @@ public abstract class Operation
         {
             if (!(receiver.type instanceof CollectionType))
                 throw new InvalidRequestException(String.format("Invalid operation (%s) for non collection column %s", toString(receiver), receiver.name));
+            else if (!(receiver.type.isMultiCell()))
+                throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
 
             switch (((CollectionType)receiver.type).kind)
             {
@@ -255,6 +257,8 @@ public abstract class Operation
                     throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name));
                 return new Constants.Adder(receiver, v);
             }
+            else if (!(receiver.type.isMultiCell()))
+                throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
 
             switch (((CollectionType)receiver.type).kind)
             {
@@ -296,6 +300,8 @@ public abstract class Operation
                     throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name));
                 return new Constants.Substracter(receiver, value.prepare(keyspace, receiver));
             }
+            else if (!(receiver.type.isMultiCell()))
+                throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
 
             switch (((CollectionType)receiver.type).kind)
             {
@@ -308,7 +314,7 @@ public abstract class Operation
                     ColumnSpecification vr = new ColumnSpecification(receiver.ksName,
                                                                      receiver.cfName,
                                                                      receiver.name,
-                                                                     SetType.getInstance(((MapType)receiver.type).keys));
+                                                                     SetType.getInstance(((MapType)receiver.type).getKeysType(), false));
                     return new Sets.Discarder(receiver, value.prepare(keyspace, vr));
             }
             throw new AssertionError();
@@ -340,6 +346,8 @@ public abstract class Operation
 
             if (!(receiver.type instanceof ListType))
                 throw new InvalidRequestException(String.format("Invalid operation (%s) for non list column %s", toString(receiver), receiver.name));
+            else if (!(receiver.type.isMultiCell()))
+                throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen list column %s", toString(receiver), receiver.name));
 
             return new Lists.Prepender(receiver, v);
         }
@@ -394,8 +402,10 @@ public abstract class Operation
 
         public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
         {
-            if (!(receiver.type instanceof CollectionType))
+            if (!(receiver.type.isCollection()))
                 throw new InvalidRequestException(String.format("Invalid deletion operation for non collection column %s", receiver.name));
+            else if (!(receiver.type.isMultiCell()))
+                throw new InvalidRequestException(String.format("Invalid deletion operation for frozen collection column %s", receiver.name));
 
             switch (((CollectionType)receiver.type).kind)
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 315d7d3..c5a8e05 100644
--- a/src/java/org/apache/cassandra/cql3/Sets.java
+++ b/src/java/org/apache/cassandra/cql3/Sets.java
@@ -18,15 +18,7 @@
 package org.apache.cassandra.cql3;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.*;
 
 import com.google.common.base.Joiner;
 
@@ -34,11 +26,13 @@ import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.ColumnFamily;
 import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.composites.Composite;
+import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.db.marshal.MapType;
 import org.apache.cassandra.db.marshal.SetType;
 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;
 
@@ -51,7 +45,7 @@ public abstract class Sets
 
     public static ColumnSpecification valueSpecOf(ColumnSpecification column)
     {
-        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).elements);
+        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).getElementsType());
     }
 
     public static class Literal implements Term.Raw
@@ -87,7 +81,7 @@ public abstract class Sets
 
                 values.add(t);
             }
-            DelayedValue value = new DelayedValue(((SetType)receiver.type).elements, values);
+            DelayedValue value = new DelayedValue(((SetType)receiver.type).getElementsType(), values);
             return allTerminal ? value.bind(QueryOptions.DEFAULT) : value;
         }
 
@@ -97,7 +91,7 @@ public abstract class Sets
             {
                 // We've parsed empty maps as a set literal to break the ambiguity so
                 // handle that case now
-                if (receiver.type instanceof MapType && elements.isEmpty())
+                if ((receiver.type instanceof MapType) && elements.isEmpty())
                     return;
 
                 throw new InvalidRequestException(String.format("Invalid set literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()));
@@ -131,11 +125,11 @@ public abstract class Sets
         }
     }
 
-    public static class Value extends Term.Terminal
+    public static class Value extends Term.Terminal implements Term.CollectionTerminal
     {
-        public final Set<ByteBuffer> elements;
+        public final SortedSet<ByteBuffer> elements;
 
-        public Value(Set<ByteBuffer> elements)
+        public Value(SortedSet<ByteBuffer> elements)
         {
             this.elements = elements;
         }
@@ -147,9 +141,9 @@ 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);
-                Set<ByteBuffer> elements = new LinkedHashSet<ByteBuffer>(s.size());
+                SortedSet<ByteBuffer> elements = new TreeSet<ByteBuffer>(type.getElementsType());
                 for (Object element : s)
-                    elements.add(type.elements.decompose(element));
+                    elements.add(type.getElementsType().decompose(element));
                 return new Value(elements);
             }
             catch (MarshalException e)
@@ -160,7 +154,12 @@ public abstract class Sets
 
         public ByteBuffer get(QueryOptions options)
         {
-            return CollectionSerializer.pack(new ArrayList<ByteBuffer>(elements), elements.size(), options.getProtocolVersion());
+            return getWithProtocolVersion(options.getProtocolVersion());
+        }
+
+        public ByteBuffer getWithProtocolVersion(int protocolVersion)
+        {
+            return CollectionSerializer.pack(new ArrayList<>(elements), elements.size(), protocolVersion);
         }
 
         public boolean equals(SetType st, Value v)
@@ -170,8 +169,9 @@ public abstract class Sets
 
             Iterator<ByteBuffer> thisIter = elements.iterator();
             Iterator<ByteBuffer> thatIter = v.elements.iterator();
+            AbstractType elementsType = st.getElementsType();
             while (thisIter.hasNext())
-                if (st.elements.compare(thisIter.next(), thatIter.next()) != 0)
+                if (elementsType.compare(thisIter.next(), thatIter.next()) != 0)
                     return false;
 
             return true;
@@ -202,7 +202,7 @@ public abstract class Sets
 
         public Value bind(QueryOptions options) throws InvalidRequestException
         {
-            Set<ByteBuffer> buffers = new TreeSet<ByteBuffer>(comparator);
+            SortedSet<ByteBuffer> buffers = new TreeSet<>(comparator);
             for (Term t : elements)
             {
                 ByteBuffer bytes = t.bindAndGet(options);
@@ -246,9 +246,12 @@ public abstract class Sets
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
-            // delete + add
-            CellName name = cf.getComparator().create(prefix, column);
-            cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            if (column.type.isMultiCell())
+            {
+                // delete + add
+                CellName name = cf.getComparator().create(prefix, column);
+                cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
+            }
             Adder.doAdd(t, cf, prefix, column, params);
         }
     }
@@ -262,22 +265,35 @@ 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);
         }
 
         static void doAdd(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
         {
             Term.Terminal value = t.bind(params.options);
-            if (value == null)
-                return;
-
-            assert value instanceof Sets.Value : value;
+            Sets.Value setValue = (Sets.Value)value;
+            if (column.type.isMultiCell())
+            {
+                if (value == null)
+                    return;
 
-            Set<ByteBuffer> toAdd = ((Sets.Value)value).elements;
-            for (ByteBuffer bb : toAdd)
+                Set<ByteBuffer> toAdd = setValue.elements;
+                for (ByteBuffer bb : toAdd)
+                {
+                    CellName cellName = cf.getComparator().create(prefix, column, bb);
+                    cf.addColumn(params.makeColumn(cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
+                }
+            }
+            else
             {
-                CellName cellName = cf.getComparator().create(prefix, column, bb);
-                cf.addColumn(params.makeColumn(cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
+                // for frozen sets, we're overwriting the whole cell
+                CellName cellName = cf.getComparator().create(prefix, column);
+                if (value == null)
+                    cf.addAtom(params.makeTombstone(cellName));
+                else
+                    cf.addColumn(params.makeColumn(cellName, ((Value) value).getWithProtocolVersion(Server.CURRENT_VERSION)));
             }
         }
     }
@@ -292,6 +308,8 @@ public abstract class Sets
 
         public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
         {
+            assert column.type.isMultiCell() : "Attempted to remove items from a frozen set";
+
             Term.Terminal value = t.bind(params.options);
             if (value == null)
                 return;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/Term.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java
index e5206c8..1587df1 100644
--- a/src/java/org/apache/cassandra/cql3/Term.java
+++ b/src/java/org/apache/cassandra/cql3/Term.java
@@ -138,6 +138,12 @@ public interface Term
         public abstract List<ByteBuffer> getElements();
     }
 
+    public interface CollectionTerminal
+    {
+        /** Gets the value of the collection when serialized with the given protocol version format */
+        public ByteBuffer getWithProtocolVersion(int protocolVersion);
+    }
+
     /**
      * A non terminal term, i.e. a term that can only be reduce to a byte buffer
      * at execution time.

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 883cc60..4f1f141 100644
--- a/src/java/org/apache/cassandra/cql3/Tuples.java
+++ b/src/java/org/apache/cassandra/cql3/Tuples.java
@@ -248,13 +248,13 @@ public class Tuples
                 // but the deserialization does the validation (so we're fine).
                 List<?> l = (List<?>)type.getSerializer().deserializeForNativeProtocol(value, options.getProtocolVersion());
 
-                assert type.elements instanceof TupleType;
-                TupleType tupleType = (TupleType) type.elements;
+                assert type.getElementsType() instanceof TupleType;
+                TupleType tupleType = (TupleType) type.getElementsType();
 
                 // type.split(bytes)
                 List<List<ByteBuffer>> elements = new ArrayList<>(l.size());
                 for (Object element : l)
-                    elements.add(Arrays.asList(tupleType.split(type.elements.decompose(element))));
+                    elements.add(Arrays.asList(tupleType.split(type.getElementsType().decompose(element))));
                 return new InValue(elements);
             }
             catch (MarshalException e)
@@ -337,15 +337,16 @@ public class Tuples
                 if (i < receivers.size() - 1)
                     inName.append(",");
 
-                if (receiver.type instanceof CollectionType)
-                    throw new InvalidRequestException("Collection columns do not support IN relations");
+                if (receiver.type.isCollection() && receiver.type.isMultiCell())
+                    throw new InvalidRequestException("Non-frozen collection columns do not support IN relations");
+
                 types.add(receiver.type);
             }
             inName.append(')');
 
             ColumnIdentifier identifier = new ColumnIdentifier(inName.toString(), true);
             TupleType type = new TupleType(types);
-            return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, ListType.getInstance(type));
+            return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, ListType.getInstance(type, false));
         }
 
         public AbstractMarker prepare(String keyspace, List<? extends ColumnSpecification> receivers) throws InvalidRequestException

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/UntypedResultSet.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java
index 42d0cb8..fef70fb 100644
--- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java
+++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java
@@ -267,19 +267,19 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row>
         public <T> Set<T> getSet(String column, AbstractType<T> type)
         {
             ByteBuffer raw = data.get(column);
-            return raw == null ? null : SetType.getInstance(type).compose(raw);
+            return raw == null ? null : SetType.getInstance(type, true).compose(raw);
         }
 
         public <T> List<T> getList(String column, AbstractType<T> type)
         {
             ByteBuffer raw = data.get(column);
-            return raw == null ? null : ListType.getInstance(type).compose(raw);
+            return raw == null ? null : ListType.getInstance(type, true).compose(raw);
         }
 
         public <K, V> Map<K, V> getMap(String column, AbstractType<K> keyType, AbstractType<V> valueType)
         {
             ByteBuffer raw = data.get(column);
-            return raw == null ? null : MapType.getInstance(keyType, valueType).compose(raw);
+            return raw == null ? null : MapType.getInstance(keyType, valueType, true).compose(raw);
         }
 
         public List<ColumnSpecification> getColumns()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/src/java/org/apache/cassandra/cql3/UpdateParameters.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/UpdateParameters.java b/src/java/org/apache/cassandra/cql3/UpdateParameters.java
index 62ec09c..74c3214 100644
--- a/src/java/org/apache/cassandra/cql3/UpdateParameters.java
+++ b/src/java/org/apache/cassandra/cql3/UpdateParameters.java
@@ -97,6 +97,6 @@ public class UpdateParameters
             return Collections.emptyList();
 
         CQL3Row row = prefetchedLists.get(rowKey);
-        return row == null ? Collections.<Cell>emptyList() : row.getCollection(cql3ColumnName);
+        return row == null ? Collections.<Cell>emptyList() : row.getMultiCellColumn(cql3ColumnName);
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ee55f361/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 9d66c16..c92bc49 100644
--- a/src/java/org/apache/cassandra/cql3/UserTypes.java
+++ b/src/java/org/apache/cassandra/cql3/UserTypes.java
@@ -24,6 +24,7 @@ import org.apache.cassandra.db.marshal.CollectionType;
 import org.apache.cassandra.db.marshal.UserType;
 import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.transport.Server;
 
 /**
  * Static helper methods and classes for user types.
@@ -171,7 +172,7 @@ public abstract class UserTypes
                 buffers[i] = values.get(i).bindAndGet(options);
                 // 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 < 3 && type.fieldType(i).isCollection() && buffers[i] != null)
+                if (version < Server.VERSION_3 && type.fieldType(i).isCollection() && buffers[i] != null)
                     buffers[i] = ((CollectionType)type.fieldType(i)).getSerializer().reserializeToV3(buffers[i]);
             }
             return buffers;