You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sn...@apache.org on 2016/02/25 16:50:21 UTC
[1/3] cassandra git commit: Failed aggregate creation breaks server
permanently
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 <sn...@snazy.de>
Authored: Thu Feb 25 07:47:21 2016 -0800
Committer: Robert Stupp <sn...@snazy.de>
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)
+ {
+ }
+ }
+ }
}
[2/3] cassandra git commit: Failed aggregate creation breaks server
permanently
Posted by sn...@apache.org.
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/trunk
Commit: c6ed2e0bb30b9e96764858b93bd2021d195be510
Parents: 71b1c4a
Author: Robert Stupp <sn...@snazy.de>
Authored: Thu Feb 25 07:47:21 2016 -0800
Committer: Robert Stupp <sn...@snazy.de>
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)
+ {
+ }
+ }
+ }
}
[3/3] cassandra git commit: Merge branch 'cassandra-3.0' into trunk
Posted by sn...@apache.org.
Merge branch 'cassandra-3.0' into trunk
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/6237022e
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/6237022e
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/6237022e
Branch: refs/heads/trunk
Commit: 6237022e0234a71f5ca3d01aaefcecfd28bcdf71
Parents: bf25e66 c6ed2e0
Author: Robert Stupp <sn...@snazy.de>
Authored: Thu Feb 25 07:49:39 2016 -0800
Committer: Robert Stupp <sn...@snazy.de>
Committed: Thu Feb 25 07:49:39 2016 -0800
----------------------------------------------------------------------
CHANGES.txt | 1 +
.../org/apache/cassandra/cql3/CQL3Type.java | 238 ++++++++-----------
.../org/apache/cassandra/cql3/Constants.java | 9 +-
.../cassandra/cql3/functions/CastFcts.java | 4 +-
.../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 ++++++
12 files changed, 251 insertions(+), 246 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/6237022e/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index bc21818,bdcf328..fbf9085
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,34 -1,5 +1,35 @@@
-3.0.4
+3.4
+ * Allow custom tracing implementations (CASSANDRA-10392)
+ * Extract LoaderOptions to be able to be used from outside (CASSANDRA-10637)
+ * fix OnDiskIndexTest to properly treat empty ranges (CASSANDRA-11205)
+ * fix TrackerTest to handle new notifications (CASSANDRA-11178)
+ * add SASI validation for partitioner and complex columns (CASSANDRA-11169)
+ * Add caching of encrypted credentials in PasswordAuthenticator (CASSANDRA-7715)
+ * fix SASI memtable switching on flush (CASSANDRA-11159)
+ * Remove duplicate offline compaction tracking (CASSANDRA-11148)
+ * fix EQ semantics of analyzed SASI indexes (CASSANDRA-11130)
+ * Support long name output for nodetool commands (CASSANDRA-7950)
+ * Encrypted hints (CASSANDRA-11040)
+ * SASI index options validation (CASSANDRA-11136)
+ * Optimize disk seek using min/max column name meta data when the LIMIT clause is used
+ (CASSANDRA-8180)
+ * Add LIKE support to CQL3 (CASSANDRA-11067)
+ * Generic Java UDF types (CASSANDRA-10819)
+ * cqlsh: Include sub-second precision in timestamps by default (CASSANDRA-10428)
+ * Set javac encoding to utf-8 (CASSANDRA-11077)
+ * Integrate SASI index into Cassandra (CASSANDRA-10661)
+ * Add --skip-flush option to nodetool snapshot
+ * Skip values for non-queried columns (CASSANDRA-10657)
+ * Add support for secondary indexes on static columns (CASSANDRA-8103)
+ * CommitLogUpgradeTestMaker creates broken commit logs (CASSANDRA-11051)
+ * Add metric for number of dropped mutations (CASSANDRA-10866)
+ * Simplify row cache invalidation code (CASSANDRA-10396)
+ * Support user-defined compaction through nodetool (CASSANDRA-10660)
+ * Stripe view locks by key and table ID to reduce contention (CASSANDRA-10981)
+ * Add nodetool gettimeout and settimeout commands (CASSANDRA-10953)
+ * Add 3.0 metadata to sstablemetadata output (CASSANDRA-10838)
+Merged from 3.0:
+ * 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/6237022e/src/java/org/apache/cassandra/cql3/CQL3Type.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/6237022e/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/functions/CastFcts.java
index 8669225,0000000..b5d3698
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
+++ b/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
@@@ -1,344 -1,0 +1,342 @@@
+/*
+ * 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.functions;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.AsciiType;
+import org.apache.cassandra.db.marshal.BooleanType;
+import org.apache.cassandra.db.marshal.ByteType;
+import org.apache.cassandra.db.marshal.CounterColumnType;
+import org.apache.cassandra.db.marshal.DecimalType;
+import org.apache.cassandra.db.marshal.DoubleType;
+import org.apache.cassandra.db.marshal.FloatType;
+import org.apache.cassandra.db.marshal.InetAddressType;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.db.marshal.IntegerType;
+import org.apache.cassandra.db.marshal.LongType;
+import org.apache.cassandra.db.marshal.ShortType;
+import org.apache.cassandra.db.marshal.SimpleDateType;
+import org.apache.cassandra.db.marshal.TimeType;
+import org.apache.cassandra.db.marshal.TimeUUIDType;
+import org.apache.cassandra.db.marshal.TimestampType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.UUIDType;
+import org.apache.commons.lang3.text.WordUtils;
+
+/**
+ * Casting functions
+ *
+ */
+public final class CastFcts
+{
+ private static final String FUNCTION_NAME_PREFIX = "castAs";
+
+ public static Collection<Function> all()
+ {
+ List<Function> functions = new ArrayList<>();
+
+ @SuppressWarnings("unchecked")
+ final AbstractType<? extends Number>[] numericTypes = new AbstractType[] {ByteType.instance,
+ ShortType.instance,
+ Int32Type.instance,
+ LongType.instance,
+ FloatType.instance,
+ DoubleType.instance,
+ DecimalType.instance,
+ CounterColumnType.instance,
+ IntegerType.instance};
+
+ for (AbstractType<? extends Number> inputType : numericTypes)
+ {
+ addFunctionIfNeeded(functions, inputType, ByteType.instance, Number::byteValue);
+ addFunctionIfNeeded(functions, inputType, ShortType.instance, Number::shortValue);
+ addFunctionIfNeeded(functions, inputType, Int32Type.instance, Number::intValue);
+ addFunctionIfNeeded(functions, inputType, LongType.instance, Number::longValue);
+ addFunctionIfNeeded(functions, inputType, FloatType.instance, Number::floatValue);
+ addFunctionIfNeeded(functions, inputType, DoubleType.instance, Number::doubleValue);
+ addFunctionIfNeeded(functions, inputType, DecimalType.instance, p -> BigDecimal.valueOf(p.doubleValue()));
+ addFunctionIfNeeded(functions, inputType, IntegerType.instance, p -> BigInteger.valueOf(p.longValue()));
+ functions.add(CastAsTextFunction.create(inputType, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(inputType, UTF8Type.instance));
+ }
+
+ functions.add(JavaFunctionWrapper.create(AsciiType.instance, UTF8Type.instance, p -> p));
+
+ functions.add(CastAsTextFunction.create(InetAddressType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(InetAddressType.instance, UTF8Type.instance));
+
+ functions.add(CastAsTextFunction.create(BooleanType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(BooleanType.instance, UTF8Type.instance));
+
+ functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, SimpleDateType.instance, TimeFcts.timeUuidtoDate));
+ functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, TimestampType.instance, TimeFcts.timeUuidToTimestamp));
+ functions.add(CastAsTextFunction.create(TimeUUIDType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(TimeUUIDType.instance, UTF8Type.instance));
+ functions.add(CassandraFunctionWrapper.create(TimestampType.instance, SimpleDateType.instance, TimeFcts.timestampToDate));
+ functions.add(CastAsTextFunction.create(TimestampType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(TimestampType.instance, UTF8Type.instance));
+ functions.add(CassandraFunctionWrapper.create(SimpleDateType.instance, TimestampType.instance, TimeFcts.dateToTimestamp));
+ functions.add(CastAsTextFunction.create(SimpleDateType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(SimpleDateType.instance, UTF8Type.instance));
+ functions.add(CastAsTextFunction.create(TimeType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(TimeType.instance, UTF8Type.instance));
+
+ functions.add(CastAsTextFunction.create(UUIDType.instance, AsciiType.instance));
+ functions.add(CastAsTextFunction.create(UUIDType.instance, UTF8Type.instance));
+
+ return functions;
+ }
+
+ /**
+ * Creates the name of the cast function use to cast to the specified type.
+ *
+ * @param outputType the output type
+ * @return the name of the cast function use to cast to the specified type
+ */
+ public static String getFunctionName(AbstractType<?> outputType)
+ {
+ return getFunctionName(outputType.asCQL3Type());
+ }
+
+ /**
+ * Creates the name of the cast function use to cast to the specified type.
+ *
+ * @param outputType the output type
+ * @return the name of the cast function use to cast to the specified type
+ */
+ public static String getFunctionName(CQL3Type outputType)
+ {
+ return FUNCTION_NAME_PREFIX + WordUtils.capitalize(toLowerCaseString(outputType));
+ }
+
+ /**
+ * Adds to the list a function converting the input type in to the output type if they are not the same.
+ *
+ * @param functions the list to add to
+ * @param inputType the input type
+ * @param outputType the output type
+ * @param converter the function use to convert the input type into the output type
+ */
+ private static <I, O> void addFunctionIfNeeded(List<Function> functions,
+ AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ java.util.function.Function<I, O> converter)
+ {
+ if (!inputType.equals(outputType))
+ functions.add(wrapJavaFunction(inputType, outputType, converter));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <O, I> Function wrapJavaFunction(AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ java.util.function.Function<I, O> converter)
+ {
+ return inputType.equals(CounterColumnType.instance)
+ ? JavaCounterFunctionWrapper.create(outputType, (java.util.function.Function<Long, O>) converter)
+ : JavaFunctionWrapper.create(inputType, outputType, converter);
+ }
+
+ private static String toLowerCaseString(CQL3Type type)
+ {
+ return type.toString().toLowerCase();
+ }
+
+ /**
+ * Base class for the CAST functions.
+ *
+ * @param <I> the input type
+ * @param <O> the output type
+ */
+ private static abstract class CastFunction<I, O> extends NativeScalarFunction
+ {
+ public CastFunction(AbstractType<I> inputType, AbstractType<O> outputType)
+ {
+ super(getFunctionName(outputType), outputType, inputType);
+ }
+
+ @Override
+ public String columnName(List<String> columnNames)
+ {
+ return String.format("cast(%s as %s)", columnNames.get(0), toLowerCaseString(outputType().asCQL3Type()));
+ }
+
+ @SuppressWarnings("unchecked")
+ protected AbstractType<O> outputType()
+ {
+ return (AbstractType<O>) returnType;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected AbstractType<I> inputType()
+ {
+ return (AbstractType<I>) argTypes.get(0);
+ }
+ }
+
+ /**
+ * <code>CastFunction</code> that implements casting by wrapping a java <code>Function</code>.
+ *
+ * @param <I> the input parameter
+ * @param <O> the output parameter
+ */
+ private static class JavaFunctionWrapper<I, O> extends CastFunction<I, O>
+ {
+ /**
+ * The java function used to convert the input type into the output one.
+ */
+ private final java.util.function.Function<I, O> converter;
+
+ public static <I, O> JavaFunctionWrapper<I, O> create(AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ java.util.function.Function<I, O> converter)
+ {
+ return new JavaFunctionWrapper<I, O>(inputType, outputType, converter);
+ }
+
+ protected JavaFunctionWrapper(AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ java.util.function.Function<I, O> converter)
+ {
+ super(inputType, outputType);
+ this.converter = converter;
+ }
+
+ public final ByteBuffer execute(int protocolVersion, List<ByteBuffer> parameters)
+ {
+ ByteBuffer bb = parameters.get(0);
+ if (bb == null)
+ return null;
+
+ return outputType().decompose(converter.apply(compose(bb)));
+ }
+
+ protected I compose(ByteBuffer bb)
+ {
+ return inputType().compose(bb);
+ }
+ }
+
+ /**
+ * <code>JavaFunctionWrapper</code> for counter columns.
+ *
+ * <p>Counter columns need to be handled in a special way because their binary representation is converted into
+ * the one of a BIGINT before functions are applied.</p>
+ *
+ * @param <O> the output parameter
+ */
+ private static class JavaCounterFunctionWrapper<O> extends JavaFunctionWrapper<Long, O>
+ {
+ public static <O> JavaFunctionWrapper<Long, O> create(AbstractType<O> outputType,
+ java.util.function.Function<Long, O> converter)
+ {
+ return new JavaCounterFunctionWrapper<O>(outputType, converter);
+ }
+
+ protected JavaCounterFunctionWrapper(AbstractType<O> outputType,
+ java.util.function.Function<Long, O> converter)
+ {
+ super(CounterColumnType.instance, outputType, converter);
+ }
+
+ protected Long compose(ByteBuffer bb)
+ {
+ return LongType.instance.compose(bb);
+ }
+ }
+
+ /**
+ * <code>CastFunction</code> that implements casting by wrapping an existing <code>NativeScalarFunction</code>.
+ *
+ * @param <I> the input parameter
+ * @param <O> the output parameter
+ */
+ private static final class CassandraFunctionWrapper<I, O> extends CastFunction<I, O>
+ {
+ /**
+ * The native scalar function used to perform the conversion.
+ */
+ private final NativeScalarFunction delegate;
+
+ public static <I, O> CassandraFunctionWrapper<I, O> create(AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ NativeScalarFunction delegate)
+ {
+ return new CassandraFunctionWrapper<I, O>(inputType, outputType, delegate);
+ }
+
+ private CassandraFunctionWrapper(AbstractType<I> inputType,
+ AbstractType<O> outputType,
+ NativeScalarFunction delegate)
+ {
+ super(inputType, outputType);
+ assert delegate.argTypes().size() == 1 && inputType.equals(delegate.argTypes().get(0));
+ assert outputType.equals(delegate.returnType());
+ this.delegate = delegate;
+ }
+
+ public ByteBuffer execute(int protocolVersion, List<ByteBuffer> parameters)
+ {
+ return delegate.execute(protocolVersion, parameters);
+ }
+ }
+
+ /**
+ * <code>CastFunction</code> that can be used to cast a type into ascii or text types.
+ *
+ * @param <I> the input parameter
+ */
+ private static final class CastAsTextFunction<I> extends CastFunction<I, String>
+ {
+
+ public static <I> CastAsTextFunction<I> create(AbstractType<I> inputType,
+ AbstractType<String> outputType)
+ {
+ return new CastAsTextFunction<I>(inputType, outputType);
+ }
+
+ private CastAsTextFunction(AbstractType<I> inputType,
+ AbstractType<String> outputType)
+ {
+ super(inputType, outputType);
+ }
+
+ public ByteBuffer execute(int protocolVersion, List<ByteBuffer> parameters)
+ {
+ ByteBuffer bb = parameters.get(0);
+ if (bb == null)
+ return null;
+
- StringBuilder builder = new StringBuilder();
- inputType().getSerializer().toCQLLiteral(bb, builder);
- return outputType().decompose(builder.toString());
++ return outputType().decompose(inputType().getSerializer().toCQLLiteral(bb));
+ }
+ }
+
+ /**
+ * The class must not be instantiated as it contains only static variables.
+ */
+ private CastFcts()
+ {
+ }
+}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/6237022e/src/java/org/apache/cassandra/schema/SchemaKeyspace.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/6237022e/src/java/org/apache/cassandra/serializers/TypeSerializer.java
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/6237022e/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java
----------------------------------------------------------------------