You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2014/05/20 18:30:14 UTC

[1/2] git commit: Change serialization format for UDT

Repository: cassandra
Updated Branches:
  refs/heads/trunk a44ee2ad4 -> d3e778706


Change serialization format for UDT

patch by slebresne; reviewed by iamaleksey for CASSANDRA-7209


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

Branch: refs/heads/trunk
Commit: db9ef0b6db3b3198cc0c67eb00505f0ef53b87e3
Parents: 86cd423
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Tue May 13 14:03:54 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Tue May 20 18:29:52 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 doc/native_protocol_v3.spec                     |   9 +-
 .../org/apache/cassandra/config/CFMetaData.java |   4 +-
 .../org/apache/cassandra/config/UTMetaData.java |  16 +-
 .../org/apache/cassandra/cql3/UserTypes.java    |  21 ++-
 .../cql3/statements/AlterTypeStatement.java     |  26 +--
 .../cql3/statements/CreateTypeStatement.java    |   8 +-
 .../cql3/statements/DropTypeStatement.java      |  19 +-
 .../cassandra/cql3/statements/Selection.java    |  10 +-
 .../apache/cassandra/db/marshal/UserType.java   | 175 +++++++++++++++++--
 .../apache/cassandra/transport/DataType.java    |  14 +-
 11 files changed, 230 insertions(+), 73 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index ef678f1..e7e7c7f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -16,6 +16,7 @@
  * remove unused classes (CASSANDRA-7197)
  * Limit user types to the keyspace they are defined in (CASSANDRA-6643)
  * Add validate method to CollectionType (CASSANDRA-7208)
+ * New serialization format for UDT values (CASSANDRA-7209)
 Merged from 2.0:
  * Always reallocate buffers in HSHA (CASSANDRA-6285)
  * (Hadoop) support authentication in CqlRecordReader (CASSANDRA-7221)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/doc/native_protocol_v3.spec
----------------------------------------------------------------------
diff --git a/doc/native_protocol_v3.spec b/doc/native_protocol_v3.spec
index 6662f1c..6719838 100644
--- a/doc/native_protocol_v3.spec
+++ b/doc/native_protocol_v3.spec
@@ -758,12 +758,13 @@ Table of Contents
 
 7. User defined types
 
-  This section describe the serialization format for User defined types (UDT) values.
+  This section describes the serialization format for User defined types (UDT) values.
   UDT values are the values of the User Defined Types as defined in section 4.2.5.2.
 
-  A UDT value is a [short] n indicating the number of values (field) of UDT values
-  followed by n elements. Each element is a [short bytes] representing the serialized
-  field.
+  A UDT value is composed of successive [bytes] values, one for each field of the UDT
+  value (in the order defined by the type). A UDT value will generally have one value
+  for each field of the type it represents, but it is allowed to have less values than
+  the type has fields.
 
 
 8. Result paging

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/src/java/org/apache/cassandra/config/CFMetaData.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java
index 2df42ae..f13b408 100644
--- a/src/java/org/apache/cassandra/config/CFMetaData.java
+++ b/src/java/org/apache/cassandra/config/CFMetaData.java
@@ -216,8 +216,8 @@ public final class CFMetaData
     public static final CFMetaData SchemaUserTypesCf = compile("CREATE TABLE " + SystemKeyspace.SCHEMA_USER_TYPES_CF + " ("
                                                                + "keyspace_name text,"
                                                                + "type_name text,"
-                                                               + "column_names list<text>,"
-                                                               + "column_types list<text>,"
+                                                               + "field_names list<text>,"
+                                                               + "field_types list<text>,"
                                                                + "PRIMARY KEY (keyspace_name, type_name)"
                                                                + ") WITH COMMENT='Defined user types' AND gc_grace_seconds=8640");
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/src/java/org/apache/cassandra/config/UTMetaData.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/UTMetaData.java b/src/java/org/apache/cassandra/config/UTMetaData.java
index b502910..178e653 100644
--- a/src/java/org/apache/cassandra/config/UTMetaData.java
+++ b/src/java/org/apache/cassandra/config/UTMetaData.java
@@ -53,8 +53,8 @@ public final class UTMetaData
         {
             String keyspace = row.getString("keyspace_name");
             ByteBuffer name = ByteBufferUtil.bytes(row.getString("type_name"));
-            List<String> rawColumns = row.getList("column_names", UTF8Type.instance);
-            List<String> rawTypes = row.getList("column_types", UTF8Type.instance);
+            List<String> rawColumns = row.getList("field_names", UTF8Type.instance);
+            List<String> rawTypes = row.getList("field_types", UTF8Type.instance);
 
             List<ByteBuffer> columns = new ArrayList<>(rawColumns.size());
             for (String rawColumn : rawColumns)
@@ -97,13 +97,13 @@ public final class UTMetaData
         Composite prefix = CFMetaData.SchemaUserTypesCf.comparator.make(newType.name);
         CFRowAdder adder = new CFRowAdder(cf, prefix, timestamp);
 
-        adder.resetCollection("column_names");
-        adder.resetCollection("column_types");
+        adder.resetCollection("field_names");
+        adder.resetCollection("field_types");
 
-        for (ByteBuffer name : newType.columnNames)
-            adder.addListEntry("column_names", name);
-        for (AbstractType<?> type : newType.types)
-            adder.addListEntry("column_types", type.toString());
+        for (ByteBuffer name : newType.fieldNames)
+            adder.addListEntry("field_names", name);
+        for (AbstractType<?> type : newType.fieldTypes)
+            adder.addListEntry("field_types", type.toString());
 
         return mutation;
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/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 2faa960..651fa23 100644
--- a/src/java/org/apache/cassandra/cql3/UserTypes.java
+++ b/src/java/org/apache/cassandra/cql3/UserTypes.java
@@ -35,7 +35,10 @@ public abstract class UserTypes
 
     public static ColumnSpecification fieldSpecOf(ColumnSpecification column, int field)
     {
-        return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier(column.name + "." + field, true), ((UserType)column.type).types.get(field));
+        return new ColumnSpecification(column.ksName,
+                                       column.cfName,
+                                       new ColumnIdentifier(column.name + "." + field, true),
+                                       ((UserType)column.type).fieldTypes.get(field));
     }
 
     public static class Literal implements Term.Raw
@@ -54,9 +57,9 @@ public abstract class UserTypes
             UserType ut = (UserType)receiver.type;
             boolean allTerminal = true;
             List<Term> values = new ArrayList<>(entries.size());
-            for (int i = 0; i < ut.types.size(); i++)
+            for (int i = 0; i < ut.fieldTypes.size(); i++)
             {
-                ColumnIdentifier field = new ColumnIdentifier(ut.columnNames.get(i), UTF8Type.instance);
+                ColumnIdentifier field = new ColumnIdentifier(ut.fieldNames.get(i), UTF8Type.instance);
                 Term value = entries.get(field).prepare(keyspace, fieldSpecOf(receiver, i));
 
                 if (value instanceof Term.NonTerminal)
@@ -74,9 +77,9 @@ public abstract class UserTypes
                 throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type()));
 
             UserType ut = (UserType)receiver.type;
-            for (int i = 0; i < ut.types.size(); i++)
+            for (int i = 0; i < ut.fieldTypes.size(); i++)
             {
-                ColumnIdentifier field = new ColumnIdentifier(ut.columnNames.get(i), UTF8Type.instance);
+                ColumnIdentifier field = new ColumnIdentifier(ut.fieldNames.get(i), UTF8Type.instance);
                 Term.Raw value = entries.get(field);
                 if (value == null)
                     throw new InvalidRequestException(String.format("Invalid user type literal for %s: missing field %s", receiver, field));
@@ -140,7 +143,7 @@ public abstract class UserTypes
 
         public void collectMarkerSpecification(VariableSpecifications boundNames)
         {
-            for (int i = 0; i < type.types.size(); i++)
+            for (int i = 0; i < type.fieldTypes.size(); i++)
                 values.get(i).collectMarkerSpecification(boundNames);
         }
 
@@ -151,14 +154,14 @@ public abstract class UserTypes
             options = options.withProtocolVersion(3);
 
             ByteBuffer[] buffers = new ByteBuffer[values.size()];
-            for (int i = 0; i < type.types.size(); i++)
+            for (int i = 0; i < type.fieldTypes.size(); i++)
             {
                 ByteBuffer buffer = values.get(i).bindAndGet(options);
                 if (buffer == null)
                     throw new InvalidRequestException("null is not supported inside user type literals");
                 if (buffer.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
                     throw new InvalidRequestException(String.format("Value for field %s is too long. User type fields are limited to %d bytes but %d bytes provided",
-                                                                    UTF8Type.instance.getString(type.columnNames.get(i)),
+                                                                    UTF8Type.instance.getString(type.fieldNames.get(i)),
                                                                     FBUtilities.MAX_UNSIGNED_SHORT,
                                                                     buffer.remaining()));
 
@@ -175,7 +178,7 @@ public abstract class UserTypes
         @Override
         public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException
         {
-            return CompositeType.build(bindInternal(options));
+            return UserType.buildValue(bindInternal(options));
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/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 3ea8d63..eac936f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java
@@ -138,8 +138,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
 
     private static int getIdxOfField(UserType type, ColumnIdentifier field)
     {
-        for (int i = 0; i < type.types.size(); i++)
-            if (field.bytes.equals(type.columnNames.get(i)))
+        for (int i = 0; i < type.fieldTypes.size(); i++)
+            if (field.bytes.equals(type.fieldNames.get(i)))
                 return i;
         return -1;
     }
@@ -183,8 +183,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
                 return updated;
 
             // Otherwise, check for nesting
-            List<AbstractType<?>> updatedTypes = updateTypes(ut.types, keyspace, toReplace, updated);
-            return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.columnNames), updatedTypes);
+            List<AbstractType<?>> updatedTypes = updateTypes(ut.fieldTypes, keyspace, toReplace, updated);
+            return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames), updatedTypes);
         }
         else if (type instanceof CompositeType)
         {
@@ -275,12 +275,12 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
             if (getIdxOfField(toUpdate, fieldName) >= 0)
                 throw new InvalidRequestException(String.format("Cannot add new field %s to type %s: a field of the same name already exists", fieldName, name));
 
-            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.columnNames.size() + 1);
-            newNames.addAll(toUpdate.columnNames);
+            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.fieldNames.size() + 1);
+            newNames.addAll(toUpdate.fieldNames);
             newNames.add(fieldName.bytes);
 
-            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.types.size() + 1);
-            newTypes.addAll(toUpdate.types);
+            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes.size() + 1);
+            newTypes.addAll(toUpdate.fieldTypes);
             newTypes.add(type.prepare(keyspace()).getType());
 
             return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes);
@@ -292,12 +292,12 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
             if (idx < 0)
                 throw new InvalidRequestException(String.format("Unknown field %s in type %s", fieldName, name));
 
-            AbstractType<?> previous = toUpdate.types.get(idx);
+            AbstractType<?> previous = toUpdate.fieldTypes.get(idx);
             if (!type.prepare(keyspace()).getType().isCompatibleWith(previous))
                 throw new InvalidRequestException(String.format("Type %s is incompatible with previous type %s of field %s in user type %s", type, previous.asCQL3Type(), fieldName, name));
 
-            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.columnNames);
-            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.types);
+            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.fieldNames);
+            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes);
             newTypes.set(idx, type.prepare(keyspace()).getType());
 
             return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes);
@@ -321,8 +321,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
 
         protected UserType makeUpdatedType(UserType toUpdate) throws InvalidRequestException
         {
-            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.columnNames);
-            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.types);
+            List<ByteBuffer> newNames = new ArrayList<>(toUpdate.fieldNames);
+            List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes);
 
             for (Map.Entry<ColumnIdentifier, ColumnIdentifier> entry : renames.entrySet())
             {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
index aa8b769..cd3e3e5 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java
@@ -74,12 +74,12 @@ public class CreateTypeStatement extends SchemaAlteringStatement
 
     public static void checkForDuplicateNames(UserType type) throws InvalidRequestException
     {
-        for (int i = 0; i < type.types.size() - 1; i++)
+        for (int i = 0; i < type.fieldTypes.size() - 1; i++)
         {
-            ByteBuffer fieldName = type.columnNames.get(i);
-            for (int j = i+1; j < type.types.size(); j++)
+            ByteBuffer fieldName = type.fieldNames.get(i);
+            for (int j = i+1; j < type.fieldTypes.size(); j++)
             {
-                if (fieldName.equals(type.columnNames.get(j)))
+                if (fieldName.equals(type.fieldNames.get(j)))
                     throw new InvalidRequestException(String.format("Duplicate field name %s in type %s",
                                                                     UTF8Type.instance.getString(fieldName),
                                                                     UTF8Type.instance.getString(type.name)));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/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 6521f68..10fc366 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java
@@ -91,18 +91,19 @@ public class DropTypeStatement extends SchemaAlteringStatement
 
     private boolean isUsedBy(AbstractType<?> toCheck) throws RequestValidationException
     {
-        if (toCheck instanceof CompositeType)
+        if (toCheck instanceof UserType)
         {
-            CompositeType ct = (CompositeType)toCheck;
+            UserType ut = (UserType)toCheck;
+            if (name.getKeyspace().equals(ut.keyspace) && name.getUserTypeName().equals(ut.name))
+                return true;
 
-            if ((ct instanceof UserType))
-            {
-                UserType ut = (UserType)ct;
-                if (name.getKeyspace().equals(ut.keyspace) && name.getUserTypeName().equals(ut.name))
+            for (AbstractType<?> subtype : ut.fieldTypes)
+                if (isUsedBy(subtype))
                     return true;
-            }
-
-            // Also reach into subtypes
+        }
+        else if (toCheck instanceof CompositeType)
+        {
+            CompositeType ct = (CompositeType)toCheck;
             for (AbstractType<?> subtype : ct.types)
                 if (isUsedBy(subtype))
                     return true;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/src/java/org/apache/cassandra/cql3/statements/Selection.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java
index 3769e97..4990e11 100644
--- a/src/java/org/apache/cassandra/cql3/statements/Selection.java
+++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java
@@ -142,13 +142,13 @@ public abstract class Selection
                 throw new InvalidRequestException(String.format("Invalid field selection: %s of type %s is not a user type", withField.selected, type.asCQL3Type()));
 
             UserType ut = (UserType)type;
-            for (int i = 0; i < ut.types.size(); i++)
+            for (int i = 0; i < ut.fieldTypes.size(); i++)
             {
-                if (!ut.columnNames.get(i).equals(withField.field.bytes))
+                if (!ut.fieldNames.get(i).equals(withField.field.bytes))
                     continue;
 
                 if (metadata != null)
-                    metadata.add(makeFieldSelectSpec(cfm, withField, ut.types.get(i), raw.alias));
+                    metadata.add(makeFieldSelectSpec(cfm, withField, ut.fieldTypes.get(i), raw.alias));
                 return new FieldSelector(ut, i, selected);
             }
             throw new InvalidRequestException(String.format("%s of type %s has no field %s", withField.selected, type.asCQL3Type(), withField.field));
@@ -472,13 +472,13 @@ public abstract class Selection
 
         public AbstractType<?> getType()
         {
-            return type.types.get(field);
+            return type.fieldTypes.get(field);
         }
 
         @Override
         public String toString()
         {
-            return String.format("%s.%s", selected, UTF8Type.instance.getString(type.columnNames.get(field)));
+            return String.format("%s.%s", selected, UTF8Type.instance.getString(type.fieldNames.get(field)));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/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 973a5be..50b3fbb 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -19,6 +19,7 @@ package org.apache.cassandra.db.marshal;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import com.google.common.base.Objects;
@@ -26,27 +27,27 @@ import com.google.common.base.Objects;
 import org.apache.cassandra.cql3.CQL3Type;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.SyntaxException;
+import org.apache.cassandra.serializers.*;
+import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.Pair;
 
 /**
  * A user defined type.
- *
- * The serialized format and sorting is exactly the one of CompositeType, but
- * we keep additional metadata (the name of the type and the names
- * of the columns).
  */
-public class UserType extends CompositeType
+public class UserType extends AbstractType<ByteBuffer>
 {
     public final String keyspace;
     public final ByteBuffer name;
-    public final List<ByteBuffer> columnNames;
+    public final List<ByteBuffer> fieldNames;
+    public final List<AbstractType<?>> fieldTypes;
 
-    public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> columnNames, List<AbstractType<?>> types)
+    public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> fieldNames, List<AbstractType<?>> fieldTypes)
     {
-        super(types);
+        assert fieldNames.size() == fieldTypes.size();
         this.keyspace = keyspace;
         this.name = name;
-        this.columnNames = columnNames;
+        this.fieldNames = fieldNames;
+        this.fieldTypes = fieldTypes;
     }
 
     public static UserType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
@@ -69,10 +70,160 @@ public class UserType extends CompositeType
         return UTF8Type.instance.compose(name);
     }
 
+    public int compare(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 i = 0;
+        while (bb1.remaining() > 0 && bb2.remaining() > 0)
+        {
+            AbstractType<?> comparator = fieldTypes.get(i);
+
+            int size1 = bb1.getInt();
+            int size2 = bb2.getInt();
+
+            // Handle nulls
+            if (size1 < 0)
+            {
+                if (size2 < 0)
+                    continue;
+                return -1;
+            }
+            if (size2 < 0)
+                return 1;
+
+            ByteBuffer value1 = ByteBufferUtil.readBytes(bb1, size1);
+            ByteBuffer value2 = ByteBufferUtil.readBytes(bb2, size2);
+            int cmp = comparator.compare(value1, value2);
+            if (cmp != 0)
+                return cmp;
+
+            ++i;
+        }
+
+        if (bb1.remaining() == 0)
+            return bb2.remaining() == 0 ? 0 : -1;
+
+        // bb1.remaining() > 0 && bb2.remaining() == 0
+        return 1;
+    }
+
+    @Override
+    public void validate(ByteBuffer bytes) throws MarshalException
+    {
+        ByteBuffer input = bytes.duplicate();
+        for (int i = 0; i < fieldTypes.size(); i++)
+        {
+            // we allow the input to have less fields than declared so as to support field addition.
+            if (!input.hasRemaining())
+                return;
+
+            if (input.remaining() < 4)
+                throw new MarshalException(String.format("Not enough bytes to read size of %dth field %s", i, fieldNames.get(i)));
+
+            int size = input.getInt();
+            // We don't handle null just yet, but we should fix that soon (CASSANDRA-7206)
+            if (size < 0)
+                throw new MarshalException("Nulls are not yet supported inside UDT values");
+
+            if (input.remaining() < size)
+                throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, fieldNames.get(i)));
+
+            ByteBuffer field = ByteBufferUtil.readBytes(input, size);
+            fieldTypes.get(i).validate(field);
+        }
+
+        // We're allowed to get less fields than declared, but not more
+        if (input.hasRemaining())
+            throw new MarshalException("Invalid remaining data after end of UDT value");
+    }
+
+    /**
+     * Split a UDT value into its fields values.
+     */
+    public ByteBuffer[] split(ByteBuffer value)
+    {
+        ByteBuffer[] fields = new ByteBuffer[fieldTypes.size()];
+        ByteBuffer input = value.duplicate();
+        for (int i = 0; i < fieldTypes.size(); i++)
+        {
+            if (!input.hasRemaining())
+                return Arrays.copyOfRange(fields, 0, i);
+
+            int size = input.getInt();
+            fields[i] = size < 0 ? null : ByteBufferUtil.readBytes(input, size);
+        }
+        return fields;
+    }
+
+    public static ByteBuffer buildValue(ByteBuffer[] fields)
+    {
+        int totalLength = 0;
+        for (ByteBuffer field : fields)
+            totalLength += 4 + field.remaining();
+
+        ByteBuffer result = ByteBuffer.allocate(totalLength);
+        for (ByteBuffer field : fields)
+        {
+            result.putInt(field.remaining());
+            result.put(field.duplicate());
+        }
+        result.rewind();
+        return result;
+    }
+
+    @Override
+    public String getString(ByteBuffer value)
+    {
+        StringBuilder sb = new StringBuilder();
+        ByteBuffer input = value.duplicate();
+        for (int i = 0; i < fieldTypes.size(); i++)
+        {
+            if (!input.hasRemaining())
+                return sb.toString();
+
+            if (i > 0)
+                sb.append(":");
+
+            AbstractType<?> type = fieldTypes.get(i);
+            int size = input.getInt();
+            assert size >= 0; // We don't support nulls yet, but we will likely do with #7206 and we'll need
+                              // a way to represent it as a string (without it conflicting with a user value)
+            ByteBuffer field = ByteBufferUtil.readBytes(input, size);
+            // We use ':' as delimiter so escape it if it's in the generated string
+            sb.append(field == null ? "null" : type.getString(value).replaceAll(":", "\\\\:"));
+        }
+        return sb.toString();
+    }
+
+    public ByteBuffer fromString(String source)
+    {
+        // Split the input on non-escaped ':' characters
+        List<String> fieldStrings = AbstractCompositeType.split(source);
+        ByteBuffer[] fields = new ByteBuffer[fieldStrings.size()];
+        for (int i = 0; i < fieldStrings.size(); i++)
+        {
+            AbstractType<?> type = fieldTypes.get(i);
+            // TODO: we'll need to handle null somehow here once we support them
+            String fieldString = fieldStrings.get(i).replaceAll("\\\\:", ":");
+            fields[i] = type.fromString(fieldString);
+        }
+        return buildValue(fields);
+    }
+
+    public TypeSerializer<ByteBuffer> getSerializer()
+    {
+        return BytesSerializer.instance;
+    }
+
     @Override
     public final int hashCode()
     {
-        return Objects.hashCode(keyspace, name, columnNames, types);
+        return Objects.hashCode(keyspace, name, fieldNames, fieldTypes);
     }
 
     @Override
@@ -82,7 +233,7 @@ public class UserType extends CompositeType
             return false;
 
         UserType that = (UserType)o;
-        return keyspace.equals(that.keyspace) && name.equals(that.name) && columnNames.equals(that.columnNames) && types.equals(that.types);
+        return keyspace.equals(that.keyspace) && name.equals(that.name) && fieldNames.equals(that.fieldNames) && fieldTypes.equals(that.fieldTypes);
     }
 
     @Override
@@ -94,6 +245,6 @@ public class UserType extends CompositeType
     @Override
     public String toString()
     {
-        return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, columnNames, types);
+        return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, fieldTypes);
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/db9ef0b6/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 3cff973..2410378 100644
--- a/src/java/org/apache/cassandra/transport/DataType.java
+++ b/src/java/org/apache/cassandra/transport/DataType.java
@@ -135,11 +135,11 @@ public enum DataType implements OptionCodec.Codecable<DataType>
                 UserType udt = (UserType)value;
                 CBUtil.writeString(udt.keyspace, cb);
                 CBUtil.writeString(UTF8Type.instance.compose(udt.name), cb);
-                cb.writeShort(udt.columnNames.size());
-                for (int i = 0; i < udt.columnNames.size(); i++)
+                cb.writeShort(udt.fieldNames.size());
+                for (int i = 0; i < udt.fieldNames.size(); i++)
                 {
-                    CBUtil.writeString(UTF8Type.instance.compose(udt.columnNames.get(i)), cb);
-                    codec.writeOne(DataType.fromType(udt.types.get(i), version), cb, version);
+                    CBUtil.writeString(UTF8Type.instance.compose(udt.fieldNames.get(i)), cb);
+                    codec.writeOne(DataType.fromType(udt.fieldTypes.get(i), version), cb, version);
                 }
                 break;
         }
@@ -166,10 +166,10 @@ public enum DataType implements OptionCodec.Codecable<DataType>
                 size += CBUtil.sizeOfString(udt.keyspace);
                 size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.name));
                 size += 2;
-                for (int i = 0; i < udt.columnNames.size(); i++)
+                for (int i = 0; i < udt.fieldNames.size(); i++)
                 {
-                    size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.columnNames.get(i)));
-                    size += codec.oneSerializedSize(DataType.fromType(udt.types.get(i), version), version);
+                    size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.fieldNames.get(i)));
+                    size += codec.oneSerializedSize(DataType.fromType(udt.fieldTypes.get(i), version), version);
                 }
                 return size;
             default:


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

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


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

Branch: refs/heads/trunk
Commit: d3e7787064d1ac833541b7c5529912e1c1307b99
Parents: a44ee2a db9ef0b
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Tue May 20 18:30:06 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Tue May 20 18:30:06 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 doc/native_protocol_v3.spec                     |   9 +-
 .../org/apache/cassandra/config/CFMetaData.java |   4 +-
 .../org/apache/cassandra/config/UTMetaData.java |  16 +-
 .../org/apache/cassandra/cql3/UserTypes.java    |  21 ++-
 .../cql3/statements/AlterTypeStatement.java     |  26 +--
 .../cql3/statements/CreateTypeStatement.java    |   8 +-
 .../cql3/statements/DropTypeStatement.java      |  19 +-
 .../cassandra/cql3/statements/Selection.java    |  10 +-
 .../apache/cassandra/db/marshal/UserType.java   | 175 +++++++++++++++++--
 .../apache/cassandra/transport/DataType.java    |  14 +-
 11 files changed, 230 insertions(+), 73 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/d3e77870/CHANGES.txt
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/d3e77870/src/java/org/apache/cassandra/config/CFMetaData.java
----------------------------------------------------------------------