Repository: cassandra Updated Branches: refs/heads/cassandra-2.2 193665757 -> 4df4f79c5 refs/heads/trunk 9718e13d7 -> 5c4a5b3b2
Empty INITCOND treated as null in aggregate patch by Robert Stupp; reviewed by Sylvain Lebresne for CASSANDRA-9457 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/4df4f79c Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/4df4f79c Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/4df4f79c Branch: refs/heads/cassandra-2.2 Commit: 4df4f79c563232285137ab2d506836de4e4efa1d Parents: 1936657 Author: Robert Stupp <[email protected]> Authored: Thu Jun 4 16:01:23 2015 +0200 Committer: Robert Stupp <[email protected]> Committed: Thu Jun 4 16:01:23 2015 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cassandra/cql3/functions/UDFunction.java | 21 ++- .../cassandra/cql3/functions/UDHelper.java | 7 + .../statements/CreateAggregateStatement.java | 2 + .../cassandra/db/marshal/AbstractType.java | 8 + .../cassandra/db/marshal/BooleanType.java | 5 + .../cassandra/db/marshal/CounterColumnType.java | 5 + .../apache/cassandra/db/marshal/DateType.java | 5 + .../cassandra/db/marshal/DecimalType.java | 5 + .../apache/cassandra/db/marshal/DoubleType.java | 5 + .../apache/cassandra/db/marshal/FloatType.java | 5 + .../cassandra/db/marshal/InetAddressType.java | 5 + .../apache/cassandra/db/marshal/Int32Type.java | 5 + .../cassandra/db/marshal/IntegerType.java | 5 + .../cassandra/db/marshal/LexicalUUIDType.java | 5 + .../apache/cassandra/db/marshal/LongType.java | 5 + .../cassandra/db/marshal/ReversedType.java | 5 + .../cassandra/db/marshal/TimeUUIDType.java | 5 + .../cassandra/db/marshal/TimestampType.java | 5 + .../apache/cassandra/db/marshal/UUIDType.java | 5 + .../apache/cassandra/cql3/AggregationTest.java | 69 +++++++++ .../org/apache/cassandra/cql3/UDHelperTest.java | 146 +++++++++++++++++++ test/unit/org/apache/cassandra/cql3/UFTest.java | 120 ++++++++++++++- 23 files changed, 439 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 882279f..2ed9ce9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.2 + * Empty INITCOND treated as null in aggregate (CASSANDRA-9457) * Remove use of Cell in Thrift MapReduce classes (CASSANDRA-8609) * Integrate pre-release Java Driver 2.2-rc1, custom build (CASSANDRA-9493) * Clean up gossiper logic for old versions (CASSANDRA-9370) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/cql3/functions/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index a01f08f..0bf6078 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -146,8 +146,8 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct public boolean isCallableWrtNullable(List<ByteBuffer> parameters) { if (!calledOnNullInput) - for (ByteBuffer parameter : parameters) - if (parameter == null || parameter.remaining() == 0) + for (int i = 0; i < parameters.size(); i++) + if (UDHelper.isNullOrEmpty(argTypes.get(i), parameters.get(i))) return false; return true; } @@ -194,37 +194,42 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct */ protected Object compose(int protocolVersion, int argIndex, ByteBuffer value) { - return value == null ? null : argDataTypes[argIndex].deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + return UDHelper.isNullOrEmpty(argTypes.get(argIndex), value) ? null : argDataTypes[argIndex].deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } // do not remove - used by generated Java UDFs protected float compose_float(int protocolVersion, int argIndex, ByteBuffer value) { - return value == null ? 0f : (float)DataType.cfloat().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + assert value != null && value.remaining() > 0; + return (float)DataType.cfloat().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } // do not remove - used by generated Java UDFs protected double compose_double(int protocolVersion, int argIndex, ByteBuffer value) { - return value == null ? 0d : (double)DataType.cdouble().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + assert value != null && value.remaining() > 0; + return (double)DataType.cdouble().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } // do not remove - used by generated Java UDFs protected int compose_int(int protocolVersion, int argIndex, ByteBuffer value) { - return value == null ? 0 : (int)DataType.cint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + assert value != null && value.remaining() > 0; + return (int)DataType.cint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } // do not remove - used by generated Java UDFs protected long compose_long(int protocolVersion, int argIndex, ByteBuffer value) { - return value == null ? 0L : (long)DataType.bigint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + assert value != null && value.remaining() > 0; + return (long)DataType.bigint().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } // do not remove - used by generated Java UDFs protected boolean compose_boolean(int protocolVersion, int argIndex, ByteBuffer value) { - return value != null && (boolean) DataType.cboolean().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); + assert value != null && value.remaining() > 0; + return (boolean) DataType.cboolean().deserialize(value, ProtocolVersion.fromInt(protocolVersion)); } /** http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/cql3/functions/UDHelper.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDHelper.java b/src/java/org/apache/cassandra/cql3/functions/UDHelper.java index 55a0888..d1d12e6 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDHelper.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDHelper.java @@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.functions; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.util.List; import com.datastax.driver.core.DataType; @@ -119,4 +120,10 @@ public final class UDHelper throw new RuntimeException("cannot parse driver type " + cqlType.getType().toString(), e); } } + + public static boolean isNullOrEmpty(AbstractType<?> type, ByteBuffer bb) + { + return bb == null || + (bb.remaining() == 0 && type.isEmptyValueMeaningless()); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/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 ae793a3..8b6c8d6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java @@ -112,6 +112,8 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement { ColumnSpecification receiver = new ColumnSpecification(functionName.keyspace, "--dummy--", new ColumnIdentifier("(aggregate_initcond)", true), stateType); initcond = ival.prepare(functionName.keyspace, receiver).bindAndGet(QueryOptions.DEFAULT); + if (Constants.NULL_LITERAL != ival && UDHelper.isNullOrEmpty(stateType, initcond)) + throw new InvalidRequestException("INITCOND must not be empty for all types except TEXT, ASCII, BLOB"); } return super.prepare(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/AbstractType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index 6602414..aa25a81 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -257,6 +257,14 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer> } /** + * Returns {@code true} for types where empty should be handled like {@code null} like {@link Int32Type}. + */ + public boolean isEmptyValueMeaningless() + { + return false; + } + + /** * @param ignoreFreezing if true, the type string will not be wrapped with FrozenType(...), even if this type is frozen. */ public String toString(boolean ignoreFreezing) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/BooleanType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/BooleanType.java b/src/java/org/apache/cassandra/db/marshal/BooleanType.java index 5c1bf42..bfe8c34 100644 --- a/src/java/org/apache/cassandra/db/marshal/BooleanType.java +++ b/src/java/org/apache/cassandra/db/marshal/BooleanType.java @@ -37,6 +37,11 @@ public class BooleanType extends AbstractType<Boolean> BooleanType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java index 0fea87b..4b3ce82 100644 --- a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java +++ b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java @@ -32,6 +32,11 @@ public class CounterColumnType extends AbstractType<Long> CounterColumnType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public boolean isCounter() { return true; http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/DateType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DateType.java b/src/java/org/apache/cassandra/db/marshal/DateType.java index 806bbcf..359ce52 100644 --- a/src/java/org/apache/cassandra/db/marshal/DateType.java +++ b/src/java/org/apache/cassandra/db/marshal/DateType.java @@ -39,6 +39,11 @@ public class DateType extends AbstractType<Date> DateType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/DecimalType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DecimalType.java b/src/java/org/apache/cassandra/db/marshal/DecimalType.java index f1388ce..4052d70 100644 --- a/src/java/org/apache/cassandra/db/marshal/DecimalType.java +++ b/src/java/org/apache/cassandra/db/marshal/DecimalType.java @@ -34,6 +34,11 @@ public class DecimalType extends AbstractType<BigDecimal> DecimalType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/DoubleType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DoubleType.java b/src/java/org/apache/cassandra/db/marshal/DoubleType.java index fdfd2d2..661b3c9 100644 --- a/src/java/org/apache/cassandra/db/marshal/DoubleType.java +++ b/src/java/org/apache/cassandra/db/marshal/DoubleType.java @@ -33,6 +33,11 @@ public class DoubleType extends AbstractType<Double> DoubleType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/FloatType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/FloatType.java b/src/java/org/apache/cassandra/db/marshal/FloatType.java index 722df87..af02cad 100644 --- a/src/java/org/apache/cassandra/db/marshal/FloatType.java +++ b/src/java/org/apache/cassandra/db/marshal/FloatType.java @@ -34,6 +34,11 @@ public class FloatType extends AbstractType<Float> FloatType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/InetAddressType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/InetAddressType.java b/src/java/org/apache/cassandra/db/marshal/InetAddressType.java index a4eac07..4901c74 100644 --- a/src/java/org/apache/cassandra/db/marshal/InetAddressType.java +++ b/src/java/org/apache/cassandra/db/marshal/InetAddressType.java @@ -34,6 +34,11 @@ public class InetAddressType extends AbstractType<InetAddress> InetAddressType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { return ByteBufferUtil.compareUnsigned(o1, o2); http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/Int32Type.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/Int32Type.java b/src/java/org/apache/cassandra/db/marshal/Int32Type.java index 9ea8f78..67d8142 100644 --- a/src/java/org/apache/cassandra/db/marshal/Int32Type.java +++ b/src/java/org/apache/cassandra/db/marshal/Int32Type.java @@ -35,6 +35,11 @@ public class Int32Type extends AbstractType<Integer> { } // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/IntegerType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/IntegerType.java b/src/java/org/apache/cassandra/db/marshal/IntegerType.java index 5fe8fd8..a3741d4 100644 --- a/src/java/org/apache/cassandra/db/marshal/IntegerType.java +++ b/src/java/org/apache/cassandra/db/marshal/IntegerType.java @@ -60,6 +60,11 @@ public final class IntegerType extends AbstractType<BigInteger> IntegerType() {/* singleton */} + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer lhs, ByteBuffer rhs) { return IntegerType.compareIntegers(lhs, rhs); http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java b/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java index 3ca5c74..3e00d71 100644 --- a/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java @@ -36,6 +36,11 @@ public class LexicalUUIDType extends AbstractType<UUID> { } // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { if (!o1.hasRemaining() || !o2.hasRemaining()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/LongType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/LongType.java b/src/java/org/apache/cassandra/db/marshal/LongType.java index 31bad93..d77d7d0 100644 --- a/src/java/org/apache/cassandra/db/marshal/LongType.java +++ b/src/java/org/apache/cassandra/db/marshal/LongType.java @@ -33,6 +33,11 @@ public class LongType extends AbstractType<Long> LongType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { return compareLongs(o1, o2); http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/ReversedType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/ReversedType.java b/src/java/org/apache/cassandra/db/marshal/ReversedType.java index 14d069a..2181f74 100644 --- a/src/java/org/apache/cassandra/db/marshal/ReversedType.java +++ b/src/java/org/apache/cassandra/db/marshal/ReversedType.java @@ -60,6 +60,11 @@ public class ReversedType<T> extends AbstractType<T> this.baseType = baseType; } + public boolean isEmptyValueMeaningless() + { + return baseType.isEmptyValueMeaningless(); + } + public int compare(ByteBuffer o1, ByteBuffer o2) { // An empty byte buffer is always smaller http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java index 3b38582..a1d8d82 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java @@ -35,6 +35,11 @@ public class TimeUUIDType extends AbstractType<UUID> { } // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer b1, ByteBuffer b2) { // Compare for length http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/TimestampType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TimestampType.java b/src/java/org/apache/cassandra/db/marshal/TimestampType.java index 095f2c2..38e0296 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimestampType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimestampType.java @@ -45,6 +45,11 @@ public class TimestampType extends AbstractType<Date> private TimestampType() {} // singleton + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer o1, ByteBuffer o2) { return LongType.compareLongs(o1, o2); http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/src/java/org/apache/cassandra/db/marshal/UUIDType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/UUIDType.java b/src/java/org/apache/cassandra/db/marshal/UUIDType.java index eba696e..0250eb20 100644 --- a/src/java/org/apache/cassandra/db/marshal/UUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/UUIDType.java @@ -50,6 +50,11 @@ public class UUIDType extends AbstractType<UUID> { } + public boolean isEmptyValueMeaningless() + { + return true; + } + public int compare(ByteBuffer b1, ByteBuffer b2) { // Compare for length http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/test/unit/org/apache/cassandra/cql3/AggregationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/AggregationTest.java b/test/unit/org/apache/cassandra/cql3/AggregationTest.java index af775e6..af68ddc 100644 --- a/test/unit/org/apache/cassandra/cql3/AggregationTest.java +++ b/test/unit/org/apache/cassandra/cql3/AggregationTest.java @@ -1378,4 +1378,73 @@ public class AggregationTest extends CQLTester assertInvalidMessage("The function arguments should not be frozen", "DROP AGGREGATE %s (frozen<" + myType + ">);"); } + + @Test + public void testEmptyValues() throws Throwable + { + createTable("CREATE TABLE %s (a int primary key, b text)"); + execute("INSERT INTO %s (a, b) VALUES (1, '')"); + execute("INSERT INTO %s (a, b) VALUES (2, '')"); + execute("INSERT INTO %s (a, b) VALUES (3, '')"); + + String fCON = createFunction(KEYSPACE, + "text, text", + "CREATE FUNCTION %s(a text, b text) " + + "CALLED ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java " + + "AS 'return a + \"x\" + b + \"y\";'"); + + String fCONf = createFunction(KEYSPACE, + "text", + "CREATE FUNCTION %s(a text) " + + "CALLED ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java " + + "AS 'return \"fin\" + a;'"); + + String aCON = createAggregate(KEYSPACE, + "text, text", + "CREATE AGGREGATE %s(text) " + + "SFUNC " + shortFunctionName(fCON) + ' ' + + "STYPE text " + + "FINALFUNC " + shortFunctionName(fCONf) + ' ' + + "INITCOND ''"); + + String fRNON = createFunction(KEYSPACE, + "text", + "CREATE FUNCTION %s(a text, b text) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java " + + "AS 'return a + \"x\" + b + \"y\";'"); + + String fRNONf = createFunction(KEYSPACE, + "text", + "CREATE FUNCTION %s(a text) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE java " + + "AS 'return \"fin\" + a;'"); + + String aRNON = createAggregate(KEYSPACE, + "int, int", + "CREATE AGGREGATE %s(text) " + + "SFUNC " + shortFunctionName(fRNON) + ' ' + + "STYPE text " + + "FINALFUNC " + shortFunctionName(fRNONf) + ' ' + + "INITCOND ''"); + + assertRows(execute("SELECT " + aCON + "(b) FROM %s"), row("finxyxyxy")); + assertRows(execute("SELECT " + aRNON + "(b) FROM %s"), row("finxyxyxy")); + + createTable("CREATE TABLE %s (a int primary key, b text)"); + execute("INSERT INTO %s (a, b) VALUES (1, null)"); + execute("INSERT INTO %s (a, b) VALUES (2, null)"); + execute("INSERT INTO %s (a, b) VALUES (3, null)"); + + assertRows(execute("SELECT " + aCON + "(b) FROM %s"), row("finxnullyxnullyxnully")); + assertRows(execute("SELECT " + aRNON + "(b) FROM %s"), row("fin")); + + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/test/unit/org/apache/cassandra/cql3/UDHelperTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UDHelperTest.java b/test/unit/org/apache/cassandra/cql3/UDHelperTest.java new file mode 100644 index 0000000..4a5e78e --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/UDHelperTest.java @@ -0,0 +1,146 @@ +/* + * 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; + +import java.nio.ByteBuffer; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.cql3.functions.UDHelper; +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.BytesType; +import org.apache.cassandra.db.marshal.CounterColumnType; +import org.apache.cassandra.db.marshal.DateType; +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.ReversedType; +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.cassandra.serializers.MarshalException; +import org.apache.cassandra.serializers.TypeSerializer; +import org.apache.cassandra.utils.ByteBufferUtil; + +public class UDHelperTest +{ + static class UFTestCustomType extends AbstractType<String> + { + + public ByteBuffer fromString(String source) throws MarshalException + { + return ByteBuffer.wrap(source.getBytes()); + } + + public Term fromJSONObject(Object parsed) throws MarshalException + { + throw new UnsupportedOperationException(); + } + + public TypeSerializer<String> getSerializer() + { + return UTF8Type.instance.getSerializer(); + } + + public int compare(ByteBuffer o1, ByteBuffer o2) + { + return o1.compareTo(o2); + } + } + + @Test + public void testEmptyVariableLengthTypes() + { + AbstractType<?>[] types = new AbstractType<?>[]{ + AsciiType.instance, + BytesType.instance, + UTF8Type.instance, + new UFTestCustomType() + }; + + for (AbstractType<?> type : types) + { + Assert.assertFalse("type " + type.getClass().getName(), + UDHelper.isNullOrEmpty(type, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + } + } + + @Test + public void testNonEmptyPrimitiveTypes() + { + AbstractType<?>[] types = new AbstractType<?>[]{ + TimeType.instance, + SimpleDateType.instance, + ByteType.instance, + ShortType.instance + }; + + for (AbstractType<?> type : types) + { + try + { + type.getSerializer().validate(ByteBufferUtil.EMPTY_BYTE_BUFFER); + Assert.fail(type.getClass().getSimpleName()); + } + catch (MarshalException e) + { + // + } + } + } + + @Test + public void testEmptiableTypes() + { + AbstractType<?>[] types = new AbstractType<?>[]{ + BooleanType.instance, + CounterColumnType.instance, + DateType.instance, + DecimalType.instance, + DoubleType.instance, + FloatType.instance, + InetAddressType.instance, + Int32Type.instance, + IntegerType.instance, + LongType.instance, + TimestampType.instance, + TimeUUIDType.instance, + UUIDType.instance + }; + + for (AbstractType<?> type : types) + { + Assert.assertTrue(type.getClass().getSimpleName(), UDHelper.isNullOrEmpty(type, ByteBufferUtil.EMPTY_BYTE_BUFFER)); + Assert.assertTrue("reversed " + type.getClass().getSimpleName(), + UDHelper.isNullOrEmpty(ReversedType.getInstance(type), ByteBufferUtil.EMPTY_BYTE_BUFFER)); + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/4df4f79c/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index e1b2b5f..f041b3a 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -18,10 +18,17 @@ package org.apache.cassandra.cql3; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.*; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import com.datastax.driver.core.*; @@ -36,6 +43,7 @@ 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 org.apache.cassandra.utils.ByteBufferUtil; public class UFTest extends CQLTester { @@ -2442,4 +2450,112 @@ public class UFTest extends CQLTester assertInvalidMessage("The function arguments should not be frozen", "DROP FUNCTION " + functionName + "(frozen<" + myType + ">);"); } + + @Test + public void testEmptyString() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, sval text, aval ascii, bval blob, empty_int int)"); + execute("INSERT INTO %s (key, sval, aval, bval, empty_int) VALUES (?, ?, ?, ?, blobAsInt(0x))", 1, "", "", ByteBuffer.allocate(0)); + + String fNameSRC = createFunction(KEYSPACE_PER_TEST, "text", + "CREATE OR REPLACE FUNCTION %s(val text) " + + "CALLED ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameSCC = createFunction(KEYSPACE_PER_TEST, "text", + "CREATE OR REPLACE FUNCTION %s(val text) " + + "CALLED ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE JAVA\n" + + "AS 'return \"\";'"); + + String fNameSRN = createFunction(KEYSPACE_PER_TEST, "text", + "CREATE OR REPLACE FUNCTION %s(val text) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameSCN = createFunction(KEYSPACE_PER_TEST, "text", + "CREATE OR REPLACE FUNCTION %s(val text) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS text " + + "LANGUAGE JAVA\n" + + "AS 'return \"\";'"); + + String fNameBRC = createFunction(KEYSPACE_PER_TEST, "blob", + "CREATE OR REPLACE FUNCTION %s(val blob) " + + "CALLED ON NULL INPUT " + + "RETURNS blob " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameBCC = createFunction(KEYSPACE_PER_TEST, "blob", + "CREATE OR REPLACE FUNCTION %s(val blob) " + + "CALLED ON NULL INPUT " + + "RETURNS blob " + + "LANGUAGE JAVA\n" + + "AS 'return ByteBuffer.allocate(0);'"); + + String fNameBRN = createFunction(KEYSPACE_PER_TEST, "blob", + "CREATE OR REPLACE FUNCTION %s(val blob) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS blob " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameBCN = createFunction(KEYSPACE_PER_TEST, "blob", + "CREATE OR REPLACE FUNCTION %s(val blob) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS blob " + + "LANGUAGE JAVA\n" + + "AS 'return ByteBuffer.allocate(0);'"); + + String fNameIRC = createFunction(KEYSPACE_PER_TEST, "int", + "CREATE OR REPLACE FUNCTION %s(val int) " + + "CALLED ON NULL INPUT " + + "RETURNS int " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameICC = createFunction(KEYSPACE_PER_TEST, "int", + "CREATE OR REPLACE FUNCTION %s(val int) " + + "CALLED ON NULL INPUT " + + "RETURNS int " + + "LANGUAGE JAVA\n" + + "AS 'return 0;'"); + + String fNameIRN = createFunction(KEYSPACE_PER_TEST, "int", + "CREATE OR REPLACE FUNCTION %s(val int) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS int " + + "LANGUAGE JAVA\n" + + "AS 'return val;'"); + + String fNameICN = createFunction(KEYSPACE_PER_TEST, "blob", + "CREATE OR REPLACE FUNCTION %s(val int) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS int " + + "LANGUAGE JAVA\n" + + "AS 'return 0;'"); + + assertRows(execute("SELECT " + fNameSRC + "(sval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSRN + "(sval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSCC + "(sval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSCN + "(sval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSRC + "(aval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSRN + "(aval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSCC + "(aval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameSCN + "(aval) FROM %s"), row("")); + assertRows(execute("SELECT " + fNameBRC + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertRows(execute("SELECT " + fNameBRN + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertRows(execute("SELECT " + fNameBCC + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertRows(execute("SELECT " + fNameBCN + "(bval) FROM %s"), row(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + assertRows(execute("SELECT " + fNameIRC + "(empty_int) FROM %s"), row(new Object[]{null})); + assertRows(execute("SELECT " + fNameIRN + "(empty_int) FROM %s"), row(new Object[]{null})); + assertRows(execute("SELECT " + fNameICC + "(empty_int) FROM %s"), row(0)); + assertRows(execute("SELECT " + fNameICN + "(empty_int) FROM %s"), row(new Object[]{null})); + } }
