diff --git a/CHANGES.txt b/CHANGES.txt index 7a3d45a682..0dabdd53dc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -33,6 +33,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) diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index e9da6e5db4..ecffe31d44 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; } diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java index 50b3fbbfe2..6656fd64f1 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 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 { 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 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[] 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); } diff --git a/src/java/org/apache/cassandra/metrics/FileCacheMetrics.java b/src/java/org/apache/cassandra/metrics/FileCacheMetrics.java index 9b21de6eb4..42444f349e 100644 --- a/src/java/org/apache/cassandra/metrics/FileCacheMetrics.java +++ b/src/java/org/apache/cassandra/metrics/FileCacheMetrics.java @@ -28,6 +28,8 @@ import org.apache.cassandra.service.FileCacheService; public class FileCacheMetrics { + private static final MetricNameFactory factory = new DefaultNameFactory("FileCache"); + /** Total number of hits */ public final Meter hits; /** Total number of requests */ @@ -39,9 +41,9 @@ public class FileCacheMetrics public FileCacheMetrics() { - hits = Metrics.newMeter(new MetricName(FileCacheService.class, "Hits"), "hits", TimeUnit.SECONDS); - requests = Metrics.newMeter(new MetricName(FileCacheService.class, "Requests"), "requests", TimeUnit.SECONDS); - hitRate = Metrics.newGauge(new MetricName(FileCacheService.class, "HitRate"), new RatioGauge() + hits = Metrics.newMeter(factory.createMetricName("Hits"), "hits", TimeUnit.SECONDS); + requests = Metrics.newMeter(factory.createMetricName("Requests"), "requests", TimeUnit.SECONDS); + hitRate = Metrics.newGauge(factory.createMetricName("HitRate"), new RatioGauge() { protected double getNumerator() { @@ -53,7 +55,7 @@ public class FileCacheMetrics return requests.count(); } }); - size = Metrics.newGauge(new MetricName(FileCacheService.class, "Size"), new Gauge() + size = Metrics.newGauge(factory.createMetricName("Size"), new Gauge() { public Long value() {