Repository: cassandra Updated Branches: refs/heads/cassandra-3.0 71b1c4a63 -> c6ed2e0bb refs/heads/trunk bf25e668f -> 6237022e0
Failed aggregate creation breaks server permanently patch by Robert Stupp; reviewed by Sylvain Lebresne for CASSANDRA-11064 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/c6ed2e0b Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/c6ed2e0b Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/c6ed2e0b Branch: refs/heads/cassandra-3.0 Commit: c6ed2e0bb30b9e96764858b93bd2021d195be510 Parents: 71b1c4a Author: Robert Stupp <[email protected]> Authored: Thu Feb 25 07:47:21 2016 -0800 Committer: Robert Stupp <[email protected]> Committed: Thu Feb 25 07:49:01 2016 -0800 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../org/apache/cassandra/cql3/CQL3Type.java | 238 ++++++++----------- .../org/apache/cassandra/cql3/Constants.java | 9 +- .../statements/CreateAggregateStatement.java | 21 ++ .../apache/cassandra/schema/SchemaKeyspace.java | 2 +- .../serializers/AbstractTextSerializer.java | 30 +-- .../cassandra/serializers/BytesSerializer.java | 14 +- .../serializers/TimestampSerializer.java | 9 +- .../cassandra/serializers/TypeSerializer.java | 9 +- .../cassandra/cql3/CQL3TypeLiteralTest.java | 88 +++---- .../validation/operations/AggregationTest.java | 72 ++++++ 11 files changed, 250 insertions(+), 243 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index aefc02e..bdcf328 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0.4 + * Failed aggregate creation breaks server permanently (CASSANDRA-11064) * Add sstabledump tool (CASSANDRA-7464) * Introduce backpressure for hints (CASSANDRA-10972) * Fix ClusteringPrefix not being able to read tombstone range boundaries (CASSANDRA-11158) http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/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 4e67346..95524d9 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Type.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java @@ -43,21 +43,13 @@ public interface CQL3Type public AbstractType<?> getType(); /** - * Generate CQL literal from this type's serialized representation using the specified protocol version. - * Convinience method for {@link #toCQLLiteral(ByteBuffer, int, StringBuilder)} that just returns a {@code String}. + * Generates CQL literal from a binary value of this type. + * + * @param buffer the value to convert to a CQL literal. This value must be + * serialized with {@code version} of the native protocol. + * @param version the native protocol version in which {@code buffer} is encoded. */ - public default String asCQLLiteral(ByteBuffer buffer, int version) - { - StringBuilder sb = new StringBuilder(); - toCQLLiteral(buffer, version, sb); - return sb.toString(); - } - - /** - * Generate CQL literal from this type's serialized representation using the specified protocol version. - * Some work is delegated to {@link org.apache.cassandra.serializers.TypeSerializer#toCQLLiteral(ByteBuffer, StringBuilder)}. - */ - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target); + public String toCQLLiteral(ByteBuffer buffer, int version); public enum Native implements CQL3Type { @@ -102,14 +94,14 @@ public interface CQL3Type /** * Delegate to - * {@link org.apache.cassandra.serializers.TypeSerializer#toCQLLiteral(ByteBuffer, StringBuilder)} + * {@link org.apache.cassandra.serializers.TypeSerializer#toCQLLiteral(ByteBuffer)} * for native types as most CQL literal representations work fine with the default * {@link org.apache.cassandra.serializers.TypeSerializer#toString(Object)} * {@link org.apache.cassandra.serializers.TypeSerializer#deserialize(ByteBuffer)} implementations. */ - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer, int version) { - type.getSerializer().toCQLLiteral(buffer, target); + return type.getSerializer().toCQLLiteral(buffer); } @Override @@ -143,12 +135,10 @@ public interface CQL3Type return type; } - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer, int version) { - if (buffer == null) - target.append("null"); - else - target.append(type.getString(buffer)); + // *always* use the 'blob' syntax to express custom types in CQL + return Native.BLOB.toCQLLiteral(buffer, version); } @Override @@ -193,59 +183,36 @@ public interface CQL3Type return true; } - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer, int version) { - // Not sure whether the !buffer.hasRemaining() check is correct here or whether an empty - // BB should be returned as "[]" resp "{}" or whether it is not valid at all. - // - // Currently, all empty collections return '[]' or '{}'. Except frozen collections with - // a null BB return 'null'. - // - if (buffer == null || !buffer.hasRemaining()) - { - if (buffer == null && type.isFrozenCollection()) - { - target.append("null"); - } - else - { - switch (type.kind) - { - case LIST: - target.append("[]"); - break; - case SET: - case MAP: - target.append("{}"); - break; - } - } - } - else - { - int size = CollectionSerializer.readCollectionSize(buffer, version); + if (buffer == null) + return "null"; - switch (type.kind) - { - case LIST: - CQL3Type elements = ((ListType) type).getElementsType().asCQL3Type(); - target.append('['); - generateSetOrListCQLLiteral(buffer, version, target, size, elements); - target.append(']'); - break; - case SET: - elements = ((SetType) type).getElementsType().asCQL3Type(); - target.append('{'); - generateSetOrListCQLLiteral(buffer, version, target, size, elements); - target.append('}'); - break; - case MAP: - target.append('{'); - generateMapCQLLiteral(buffer, version, target, size); - target.append('}'); - break; - } + StringBuilder target = new StringBuilder(); + buffer = buffer.duplicate(); + int size = CollectionSerializer.readCollectionSize(buffer, version); + + switch (type.kind) + { + case LIST: + CQL3Type elements = ((ListType) type).getElementsType().asCQL3Type(); + target.append('['); + generateSetOrListCQLLiteral(buffer, version, target, size, elements); + target.append(']'); + break; + case SET: + elements = ((SetType) type).getElementsType().asCQL3Type(); + target.append('{'); + generateSetOrListCQLLiteral(buffer, version, target, size, elements); + target.append('}'); + break; + case MAP: + target.append('{'); + generateMapCQLLiteral(buffer, version, target, size); + target.append('}'); + break; } + return target.toString(); } private void generateMapCQLLiteral(ByteBuffer buffer, int version, StringBuilder target, int size) @@ -257,10 +224,10 @@ public interface CQL3Type if (i > 0) target.append(", "); ByteBuffer element = CollectionSerializer.readValue(buffer, version); - keys.toCQLLiteral(element, version, target); + target.append(keys.toCQLLiteral(element, version)); target.append(": "); element = CollectionSerializer.readValue(buffer, version); - values.toCQLLiteral(element, version, target); + target.append(values.toCQLLiteral(element, version)); } } @@ -271,7 +238,7 @@ public interface CQL3Type if (i > 0) target.append(", "); ByteBuffer element = CollectionSerializer.readValue(buffer, version); - elements.toCQLLiteral(element, version, target); + target.append(elements.toCQLLiteral(element, version)); } } @@ -348,47 +315,47 @@ public interface CQL3Type return type; } - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer, int version) { if (buffer == null) - { - target.append("null"); - } - else - { - target.append('{'); - for (int i = 0; i < type.size(); i++) - { - // we allow the input to have less fields than declared so as to support field addition. - if (!buffer.hasRemaining()) - break; + return "null"; - if (buffer.remaining() < 4) - throw new MarshalException(String.format("Not enough bytes to read size of %dth field %s", i, type.fieldName(i))); - int size = buffer.getInt(); + StringBuilder target = new StringBuilder(); + buffer = buffer.duplicate(); + target.append('{'); + for (int i = 0; i < type.size(); i++) + { + // we allow the input to have less fields than declared so as to support field addition. + if (!buffer.hasRemaining()) + break; - if (i > 0) - target.append(", "); + if (buffer.remaining() < 4) + throw new MarshalException(String.format("Not enough bytes to read size of %dth field %s", i, type.fieldName(i))); - target.append(ColumnIdentifier.maybeQuote(type.fieldNameAsString(i))); - target.append(": "); + int size = buffer.getInt(); - // size < 0 means null value - if (size < 0) - { - target.append("null"); - continue; - } + if (i > 0) + target.append(", "); - if (buffer.remaining() < size) - throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, type.fieldName(i))); + target.append(ColumnIdentifier.maybeQuote(type.fieldNameAsString(i))); + target.append(": "); - ByteBuffer field = ByteBufferUtil.readBytes(buffer, size); - type.fieldType(i).asCQL3Type().toCQLLiteral(field, version, target); + // size < 0 means null value + if (size < 0) + { + target.append("null"); + continue; } - target.append('}'); + + if (buffer.remaining() < size) + throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, type.fieldName(i))); + + ByteBuffer field = ByteBufferUtil.readBytes(buffer, size); + target.append(type.fieldType(i).asCQL3Type().toCQLLiteral(field, version)); } + target.append('}'); + return target.toString(); } @Override @@ -438,47 +405,46 @@ public interface CQL3Type return type; } - public void toCQLLiteral(ByteBuffer buffer, int version, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer, int version) { if (buffer == null) + return "null"; + + StringBuilder target = new StringBuilder(); + buffer = buffer.duplicate(); + target.append('('); + boolean first = true; + for (int i = 0; i < type.size(); i++) { - target.append("null"); - } - else - { - target.append('('); - boolean first = true; - for (int i = 0; i < type.size(); i++) - { - // we allow the input to have less fields than declared so as to support field addition. - if (!buffer.hasRemaining()) - break; + // we allow the input to have less fields than declared so as to support field addition. + if (!buffer.hasRemaining()) + break; - if (buffer.remaining() < 4) - throw new MarshalException(String.format("Not enough bytes to read size of %dth component", i)); + if (buffer.remaining() < 4) + throw new MarshalException(String.format("Not enough bytes to read size of %dth component", i)); - int size = buffer.getInt(); + int size = buffer.getInt(); - if (first) - first = false; - else - target.append(", "); + if (first) + first = false; + else + target.append(", "); - // size < 0 means null value - if (size < 0) - { - target.append("null"); - continue; - } + // size < 0 means null value + if (size < 0) + { + target.append("null"); + continue; + } - if (buffer.remaining() < size) - throw new MarshalException(String.format("Not enough bytes to read %dth component", i)); + if (buffer.remaining() < size) + throw new MarshalException(String.format("Not enough bytes to read %dth component", i)); - ByteBuffer field = ByteBufferUtil.readBytes(buffer, size); - type.type(i).asCQL3Type().toCQLLiteral(field, version, target); - } - target.append(')'); + ByteBuffer field = ByteBufferUtil.readBytes(buffer, size); + target.append(type.type(i).asCQL3Type().toCQLLiteral(field, version)); } + target.append(')'); + return target.toString(); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/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 425dd85..4982c49 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/Constants.java @@ -143,9 +143,12 @@ public abstract class Constants validator = ((ReversedType<?>) validator).baseType; try { - // BytesType doesn't want it's input prefixed by '0x'. - if (type == Type.HEX && validator instanceof BytesType) - return validator.fromString(text.substring(2)); + if (type == Type.HEX) + // Note that validator could be BytesType, but it could also be a custom type, so + // we hardcode BytesType (rather than using 'validator') in the call below. + // Further note that BytesType doesn't want it's input prefixed by '0x', hence the substring. + return BytesType.instance.fromString(text.substring(2)); + if (validator instanceof CounterColumnType) return LongType.instance.fromString(text); return validator.fromString(text); http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java index 50f4f12..7066570 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java @@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; +import java.util.Objects; import java.util.List; import org.apache.cassandra.auth.*; @@ -29,11 +30,13 @@ import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; +import org.apache.cassandra.transport.Server; /** * A {@code CREATE AGGREGATE} statement parsed from a CQL query. @@ -111,6 +114,24 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement if (ival != null) { initcond = Terms.asBytes(functionName.keyspace, ival.toString(), stateType); + + if (initcond != null) + { + try + { + stateType.validate(initcond); + } + catch (MarshalException e) + { + throw new InvalidRequestException(String.format("Invalid value for INITCOND of type %s%s", stateType.asCQL3Type(), + e.getMessage() == null ? "" : String.format(" (%s)", e.getMessage()))); + } + } + + // Sanity check that converts the initcond to a CQL literal and parse it back to avoid getting in CASSANDRA-11064. + String initcondAsCql = stateType.asCQL3Type().toCQLLiteral(initcond, Server.CURRENT_VERSION); + assert Objects.equals(initcond, Terms.asBytes(functionName.keyspace, initcondAsCql, stateType)); + if (Constants.NULL_LITERAL != ival && UDHelper.isNullOrEmpty(stateType, initcond)) throw new InvalidRequestException("INITCOND must not be empty for all types except TEXT, ASCII, BLOB"); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java index 9e05a73..6e9d44b 100644 --- a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java +++ b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java @@ -845,7 +845,7 @@ public final class SchemaKeyspace .add("final_func", aggregate.finalFunction() != null ? aggregate.finalFunction().name().name : null) .add("initcond", aggregate.initialCondition() != null // must use the frozen state type here, as 'null' for unfrozen collections may mean 'empty' - ? aggregate.stateType().freeze().asCQL3Type().asCQLLiteral(aggregate.initialCondition(), Server.CURRENT_VERSION) + ? aggregate.stateType().freeze().asCQL3Type().toCQLLiteral(aggregate.initialCondition(), Server.CURRENT_VERSION) : null) .build(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java b/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java index 4d2694e..7a3afed 100644 --- a/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java +++ b/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java @@ -17,12 +17,14 @@ */ package org.apache.cassandra.serializers; -import org.apache.cassandra.utils.ByteBufferUtil; - import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; +import org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.utils.ByteBufferUtil; + public abstract class AbstractTextSerializer implements TypeSerializer<String> { private final Charset charset; @@ -64,26 +66,10 @@ public abstract class AbstractTextSerializer implements TypeSerializer<String> * Caveat: it does only generate literals with single quotes and not pg-style literals. */ @Override - public void toCQLLiteral(ByteBuffer buffer, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer) { - if (buffer == null) - { - target.append("null"); - } - else - { - String s = deserialize(buffer); - - target.append('\''); - for (int i=0; i<s.length(); i++) - { - char c = s.charAt(i); - if (c == '\'') - target.append("''"); - else - target.append(c); - } - target.append('\''); - } + return buffer == null + ? "null" + : '\'' + StringUtils.replace(deserialize(buffer), "'", "''") + '\''; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/serializers/BytesSerializer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/serializers/BytesSerializer.java b/src/java/org/apache/cassandra/serializers/BytesSerializer.java index 85251b8..ed0bf77 100644 --- a/src/java/org/apache/cassandra/serializers/BytesSerializer.java +++ b/src/java/org/apache/cassandra/serializers/BytesSerializer.java @@ -54,16 +54,10 @@ public class BytesSerializer implements TypeSerializer<ByteBuffer> } @Override - public void toCQLLiteral(ByteBuffer buffer, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer) { - if (buffer == null) - { - target.append("null"); - } - else - { - target.append("0x"); - target.append(toString(deserialize(buffer))); - } + return buffer == null + ? "null" + : "0x" + toString(deserialize(buffer)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/serializers/TimestampSerializer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/serializers/TimestampSerializer.java b/src/java/org/apache/cassandra/serializers/TimestampSerializer.java index ad56cd5..fbd98d1 100644 --- a/src/java/org/apache/cassandra/serializers/TimestampSerializer.java +++ b/src/java/org/apache/cassandra/serializers/TimestampSerializer.java @@ -190,11 +190,10 @@ public class TimestampSerializer implements TypeSerializer<Date> * @see #FORMATTER_UTC */ @Override - public void toCQLLiteral(ByteBuffer buffer, StringBuilder target) + public String toCQLLiteral(ByteBuffer buffer) { - if (buffer == null || !buffer.hasRemaining()) - target.append("null"); - else - target.append(FORMATTER_UTC.get().format(deserialize(buffer))); + return buffer == null || !buffer.hasRemaining() + ? "null" + : FORMATTER_UTC.get().format(deserialize(buffer)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/src/java/org/apache/cassandra/serializers/TypeSerializer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/serializers/TypeSerializer.java b/src/java/org/apache/cassandra/serializers/TypeSerializer.java index e7bb830..e66c36d 100644 --- a/src/java/org/apache/cassandra/serializers/TypeSerializer.java +++ b/src/java/org/apache/cassandra/serializers/TypeSerializer.java @@ -35,12 +35,11 @@ public interface TypeSerializer<T> public Class<T> getType(); - public default void toCQLLiteral(ByteBuffer buffer, StringBuilder target) + public default String toCQLLiteral(ByteBuffer buffer) { - if (buffer == null || !buffer.hasRemaining()) - target.append("null"); - else - target.append(toString(deserialize(buffer))); + return buffer == null || !buffer.hasRemaining() + ? "null" + : toString(deserialize(buffer)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java b/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java index f6cd8df..02ed1a8 100644 --- a/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java +++ b/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java @@ -23,15 +23,7 @@ import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.EnumMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; @@ -302,39 +294,30 @@ public class CQL3TypeLiteralTest @Test public void testCollectionNullAndEmpty() { + // An empty collection is one with a size of 0 (note that rely on the fact that protocol version < 3 are not + // supported anymore and so the size of a collection is always on 4 bytes). + ByteBuffer emptyCollection = ByteBufferUtil.bytes(0); + for (int version = Server.MIN_SUPPORTED_VERSION; version <= Server.CURRENT_VERSION; version++) { - // empty, frozen collections - Value value = new Value("[]", ListType.getInstance(UTF8Type.instance, false).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - value = new Value("{}", SetType.getInstance(UTF8Type.instance, false).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, false).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - - // empty, non-frozen collections - value = new Value("[]", ListType.getInstance(UTF8Type.instance, true).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - value = new Value("{}", SetType.getInstance(UTF8Type.instance, true).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true).asCQL3Type(), ByteBufferUtil.EMPTY_BYTE_BUFFER); - compareCqlLiteral(version, value); - - // null, frozen collections - value = new Value("null", ListType.getInstance(UTF8Type.instance, false).asCQL3Type(), null); - compareCqlLiteral(version, value); - value = new Value("null", SetType.getInstance(UTF8Type.instance, false).asCQL3Type(), null); - compareCqlLiteral(version, value); - value = new Value("null", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, false).asCQL3Type(), null); - compareCqlLiteral(version, value); - - // null, non-frozen collections - value = new Value("[]", ListType.getInstance(UTF8Type.instance, true).asCQL3Type(), null); - compareCqlLiteral(version, value); - value = new Value("{}", SetType.getInstance(UTF8Type.instance, true).asCQL3Type(), null); - compareCqlLiteral(version, value); - value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true).asCQL3Type(), null); - compareCqlLiteral(version, value); + for (boolean frozen : Arrays.asList(true, false)) + { + // empty + Value value = new Value("[]", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection); + compareCqlLiteral(version, value); + value = new Value("{}", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection); + compareCqlLiteral(version, value); + value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), emptyCollection); + compareCqlLiteral(version, value); + + // null + value = new Value("null", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null); + compareCqlLiteral(version, value); + value = new Value("null", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null); + compareCqlLiteral(version, value); + value = new Value("null", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), null); + compareCqlLiteral(version, value); + } } } @@ -393,7 +376,7 @@ public class CQL3TypeLiteralTest { assertEquals(msg, value.expected, - value.cql3Type.asCQLLiteral(buffer, version)); + value.cql3Type.toCQLLiteral(buffer, version)); } catch (RuntimeException e) { @@ -436,24 +419,7 @@ public class CQL3TypeLiteralTest if (allowNull && randBool(0.05d)) { - // generate 'null' collection - if (collectionType.isMultiCell()) - { - switch (collectionType.kind) - { - case LIST: - expected.append("[]"); - break; - case SET: - case MAP: - expected.append("{}"); - break; - } - } - else - { - expected.append("null"); - } + expected.append("null"); buffer = null; } else @@ -498,7 +464,7 @@ public class CQL3TypeLiteralTest buffers.add(el.value.duplicate()); if (expected.length() > 1) expected.append(", "); - el.cql3Type.toCQLLiteral(el.value, version, expected); + expected.append(el.cql3Type.toCQLLiteral(el.value, version)); if (collectionType.kind == CollectionType.Kind.MAP) { @@ -506,7 +472,7 @@ public class CQL3TypeLiteralTest el = generateAnyValue(version, values); buffers.add(el.value.duplicate()); expected.append(": "); - el.cql3Type.toCQLLiteral(el.value, version, expected); + expected.append(el.cql3Type.toCQLLiteral(el.value, version)); } } expected.append(bracketClose); http://git-wip-us.apache.org/repos/asf/cassandra/blob/c6ed2e0b/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java index c903127..e5420c9 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.cql3.validation.operations; import java.math.BigDecimal; +import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; @@ -44,11 +45,15 @@ import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.UntypedResultSet.Row; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.db.SystemKeyspace; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.DynamicCompositeType; +import org.apache.cassandra.db.marshal.TypeParser; import org.apache.cassandra.exceptions.FunctionExecutionException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.Event; +import org.apache.cassandra.transport.Server; import org.apache.cassandra.transport.messages.ResultMessage; import static org.junit.Assert.assertEquals; @@ -1878,4 +1883,71 @@ public class AggregationTest extends CQLTester assertRows(execute("SELECT final_func, initcond FROM system_schema.aggregates WHERE keyspace_name=? AND aggregate_name=?", KEYSPACE, shortFunctionName(aggregateName)), row(finalFunc, initCond)); } + + public void testCustomTypeInitcond() throws Throwable + { + try + { + String type = "DynamicCompositeType(s => UTF8Type, i => Int32Type)"; + + executeNet(Server.CURRENT_VERSION, + "CREATE FUNCTION " + KEYSPACE + ".f11064(i 'DynamicCompositeType(s => UTF8Type, i => Int32Type)')\n" + + "RETURNS NULL ON NULL INPUT\n" + + "RETURNS '" + type + "'\n" + + "LANGUAGE java\n" + + "AS 'return i;'"); + + // create aggregate using the 'composite syntax' for composite types + executeNet(Server.CURRENT_VERSION, + "CREATE AGGREGATE " + KEYSPACE + ".a11064()\n" + + "SFUNC f11064 " + + "STYPE '" + type + "'\n" + + "INITCOND 's@foo:i@32'"); + + AbstractType<?> compositeType = TypeParser.parse(type); + ByteBuffer compositeTypeValue = compositeType.fromString("s@foo:i@32"); + String compositeTypeString = compositeType.asCQL3Type().toCQLLiteral(compositeTypeValue, Server.CURRENT_VERSION); + // ensure that the composite type is serialized using the 'blob syntax' + assertTrue(compositeTypeString.startsWith("0x")); + + // ensure that the composite type is 'serialized' using the 'blob syntax' in the schema + assertRows(execute("SELECT initcond FROM system_schema.aggregates WHERE keyspace_name=? AND aggregate_name=?", KEYSPACE, "a11064"), + row(compositeTypeString)); + + // create aggregate using the 'blob syntax' for composite types + executeNet(Server.CURRENT_VERSION, + "CREATE AGGREGATE " + KEYSPACE + ".a11064_2()\n" + + "SFUNC f11064 " + + "STYPE '" + type + "'\n" + + "INITCOND " + compositeTypeString); + + // ensure that the composite type is 'serialized' using the 'blob syntax' in the schema + assertRows(execute("SELECT initcond FROM system_schema.aggregates WHERE keyspace_name=? AND aggregate_name=?", KEYSPACE, "a11064_2"), + row(compositeTypeString)); + } + finally + { + try + { + execute("DROP AGGREGATE " + KEYSPACE + ".a11064_2"); + } + catch (Exception ignore) + { + } + try + { + execute("DROP AGGREGATE " + KEYSPACE + ".a11064"); + } + catch (Exception ignore) + { + } + try + { + execute("DROP FUNCTION " + KEYSPACE + ".f11064"); + } + catch (Exception ignore) + { + } + } + } }
