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/23 18:56:38 UTC

git commit: Support nul/non-existant fields in UDT

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.1 0b9b1fca0 -> ca7e9d5c4


Support nul/non-existant fields in UDT

patch by slebresne; reviewed by iamaleskey for CASSANDRA-7206


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

Branch: refs/heads/cassandra-2.1
Commit: ca7e9d5c49b08fa4981d5986ef68c82b3777e282
Parents: 0b9b1fc
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Fri May 23 18:49:44 2014 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Fri May 23 18:56:29 2014 +0200

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../org/apache/cassandra/cql3/UserTypes.java    | 16 +++-----
 .../apache/cassandra/db/marshal/UserType.java   | 39 ++++++++++++++------
 3 files changed, 34 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/ca7e9d5c/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 108c859..9bde1f3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -24,6 +24,7 @@
  * Use prepared statements internally (CASSANDRA-6975)
  * Fix broken paging state with prepared statement (CASSANDRA-7120)
  * Fix IllegalArgumentException in CqlStorage (CASSANDRA-7287)
+ * Allow nulls/non-existant fields in UDT (CASSANDRA-7206)
 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/ca7e9d5c/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 e9da6e5..ecffe31 100644
--- a/src/java/org/apache/cassandra/cql3/UserTypes.java
+++ b/src/java/org/apache/cassandra/cql3/UserTypes.java
@@ -20,7 +20,6 @@ package org.apache.cassandra.cql3;
 import java.nio.ByteBuffer;
 import java.util.*;
 
-import org.apache.cassandra.db.marshal.CompositeType;
 import org.apache.cassandra.db.marshal.UserType;
 import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.InvalidRequestException;
@@ -59,7 +58,10 @@ public abstract class UserTypes
             for (int i = 0; i < ut.fieldTypes.size(); i++)
             {
                 ColumnIdentifier field = new ColumnIdentifier(ut.fieldNames.get(i), UTF8Type.instance);
-                Term value = entries.get(field).prepare(keyspace, fieldSpecOf(receiver, i));
+                Term.Raw raw = entries.get(field);
+                if (raw == null)
+                    raw = Constants.NULL_LITERAL;
+                Term value = raw.prepare(keyspace, fieldSpecOf(receiver, i));
 
                 if (value instanceof Term.NonTerminal)
                     allTerminal = false;
@@ -81,7 +83,7 @@ public abstract class UserTypes
                 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));
+                    continue;
 
                 ColumnSpecification fieldSpec = fieldSpecOf(receiver, i);
                 if (!value.isAssignableTo(keyspace, fieldSpec))
@@ -154,13 +156,7 @@ public abstract class UserTypes
 
             ByteBuffer[] buffers = new ByteBuffer[values.size()];
             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");
-
-                buffers[i] = buffer;
-            }
+                buffers[i] = values.get(i).bindAndGet(options);
             return buffers;
         }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/ca7e9d5c/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 50b3fbb..6656fd6 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -126,9 +126,10 @@ public class UserType extends AbstractType<ByteBuffer>
                 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)
+
+            // size < 0 means null value
             if (size < 0)
-                throw new MarshalException("Nulls are not yet supported inside UDT values");
+                continue;
 
             if (input.remaining() < size)
                 throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, fieldNames.get(i)));
@@ -164,13 +165,20 @@ public class UserType extends AbstractType<ByteBuffer>
     {
         int totalLength = 0;
         for (ByteBuffer field : fields)
-            totalLength += 4 + field.remaining();
+            totalLength += 4 + (field == null ? 0 : field.remaining());
 
         ByteBuffer result = ByteBuffer.allocate(totalLength);
         for (ByteBuffer field : fields)
         {
-            result.putInt(field.remaining());
-            result.put(field.duplicate());
+            if (field == null)
+            {
+                result.putInt(-1);
+            }
+            else
+            {
+                result.putInt(field.remaining());
+                result.put(field.duplicate());
+            }
         }
         result.rewind();
         return result;
@@ -191,11 +199,15 @@ public class UserType extends AbstractType<ByteBuffer>
 
             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)
+            if (size < 0)
+            {
+                sb.append("@");
+                continue;
+            }
+
             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(":", "\\\\:"));
+            // We use ':' as delimiter, and @ to represent null, so escape them in the generated string
+            sb.append(type.getString(field).replaceAll(":", "\\\\:").replaceAll("@", "\\\\@"));
         }
         return sb.toString();
     }
@@ -207,10 +219,13 @@ public class UserType extends AbstractType<ByteBuffer>
         ByteBuffer[] fields = new ByteBuffer[fieldStrings.size()];
         for (int i = 0; i < fieldStrings.size(); i++)
         {
+            String fieldString = fieldStrings.get(i);
+            // We use @ to represent nulls
+            if (fieldString.equals("@"))
+                continue;
+
             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);
+            fields[i] = type.fromString(fieldString.replaceAll("\\\\:", ":").replaceAll("\\\\@", "@"));
         }
         return buildValue(fields);
     }