Repository: cassandra Updated Branches: refs/heads/trunk 0564c8b42 -> 0409abc26
Add support for + and - operations on dates patch by Benjamin Lerer; reviewed by Alex Petrov for CASSANDRA-11936 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/0409abc2 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/0409abc2 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/0409abc2 Branch: refs/heads/trunk Commit: 0409abc26a9bd0dba59bccb37c668f6608dd6ab9 Parents: 0564c8b Author: Benjamin Lerer <[email protected]> Authored: Tue Feb 7 11:24:22 2017 +0100 Committer: Benjamin Lerer <[email protected]> Committed: Tue Feb 7 11:24:22 2017 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + NEWS.txt | 6 + doc/source/cql/operators.rst | 29 ++- .../org/apache/cassandra/cql3/Duration.java | 72 ++++++++ .../cassandra/cql3/functions/CastFcts.java | 10 +- .../cassandra/cql3/functions/OperationFcts.java | 144 +++++++++++---- .../cassandra/cql3/functions/TimeFcts.java | 177 +++++++------------ .../cassandra/db/marshal/SimpleDateType.java | 16 +- .../cassandra/db/marshal/TemporalType.java | 103 +++++++++++ .../apache/cassandra/db/marshal/TimeType.java | 2 +- .../cassandra/db/marshal/TimeUUIDType.java | 28 ++- .../cassandra/db/marshal/TimestampType.java | 21 ++- .../org/apache/cassandra/cql3/DurationTest.java | 78 +++++++- .../cql3/functions/OperationFctsTest.java | 98 ++++++++++ .../cassandra/cql3/functions/TimeFctsTest.java | 19 +- 15 files changed, 636 insertions(+), 168 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index e1708b7..905a750 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0 + * Add support for + and - operations on dates (CASSANDRA-11936) * Fix consistency of incrementally repaired data (CASSANDRA-9143) * Increase commitlog version (CASSANDRA-13161) * Make TableMetadata immutable, optimize Schema (CASSANDRA-9425) http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/NEWS.txt ---------------------------------------------------------------------- diff --git a/NEWS.txt b/NEWS.txt index ee1fd6d..9c183f6 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -18,6 +18,12 @@ using the provided 'sstableupgrade' tool. New features ------------ + - Support for arithmetic operations between `timestamp`/`date` and `duration` has been added. + See CASSANDRA-11936 + - Support for arithmetic operations on number has been added. See CASSANDRA-11935 + +3.11 +==== Upgrading --------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/doc/source/cql/operators.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/operators.rst b/doc/source/cql/operators.rst index 05f1c61..45f52d9 100644 --- a/doc/source/cql/operators.rst +++ b/doc/source/cql/operators.rst @@ -17,6 +17,8 @@ .. highlight:: cql .. _arithmetic_operators: +.. _number-arithmetic: +.. _datetime--arithmetic: Arithmetic Operators -------------------- @@ -26,15 +28,20 @@ CQL supports the following operators: =============== ======================================================================================================= Operator Description =============== ======================================================================================================= - \- (unary) Negates operand - \+ Addition - \- Substraction - \* Multiplication + \- (unary) Negates operand + \+ Addition + \- Substraction + \* Multiplication / Division % Returns the remainder of a division =============== ======================================================================================================= -Arithmetic operations are only supported on numeric types or counters. +.. _number-arithmetic: + +Number Arithmetic +^^^^^^^^^^^^^^^^^ + +All arithmetic operations are supported on numeric types or counters. The return type of the operation will be based on the operand types: @@ -55,3 +62,15 @@ The return type of the operation will be based on the operand types: ``*``, ``/`` and ``%`` operators have a higher precedence level than ``+`` and ``-`` operator. By consequence, they will be evaluated before. If two operator in an expression have the same precedence level, they will be evaluated left to right based on their position in the expression. + +.. _datetime--arithmetic: + +Datetime Arithmetic +^^^^^^^^^^^^^^^^^^^ + +A ``duration`` can be added (+) or substracted (-) from a ``timestamp`` or a ``date`` to create a new +``timestamp`` or ``date``. So for instance:: + + SELECT * FROM myTable WHERE t = '2017-01-01' - 2d + +will select all the records with a value of ``t`` which is in the last 2 days of 2016. http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/Duration.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Duration.java b/src/java/org/apache/cassandra/cql3/Duration.java index 48f8850..e6151cb 100644 --- a/src/java/org/apache/cassandra/cql3/Duration.java +++ b/src/java/org/apache/cassandra/cql3/Duration.java @@ -17,6 +17,9 @@ */ package org.apache.cassandra.cql3; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,6 +29,7 @@ import org.apache.cassandra.serializers.MarshalException; import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; +import static org.apache.commons.lang.time.DateUtils.MILLIS_PER_DAY; /** * Represents a duration. A durations store separately months, days, and seconds due to the fact that @@ -265,6 +269,53 @@ public final class Duration return nanoseconds; } + /** + * Adds this duration to the specified time in milliseconds. + * @param timeInMillis the time to which the duration must be added + * @return the specified time plus this duration + */ + public long addTo(long timeInMillis) + { + return add(timeInMillis, months, days, nanoseconds); + } + + /** + * Substracts this duration from the specified time in milliseconds. + * @param timeInMillis the time from which the duration must be substracted + * @return the specified time minus this duration + */ + public long substractFrom(long timeInMillis) + { + return add(timeInMillis, -months, -days, -nanoseconds); + } + + /** + * Adds the specified months, days and nanoseconds to the specified time in milliseconds. + * + * @param timeInMillis the time to which the months, days and nanoseconds must be added + * @param months the number of months to add + * @param days the number of days to add + * @param nanoseconds the number of nanoseconds to add + * @return the specified time plus the months, days and nanoseconds + */ + private static long add(long timeInMillis, int months, int days, long nanoseconds) + { + // If the duration does not contains any months we can can ignore daylight saving, + // as time zones are not supported, and simply look at the milliseconds + if (months == 0) + { + long durationInMillis = (days * MILLIS_PER_DAY) + (nanoseconds / NANOS_PER_MILLI); + return timeInMillis + durationInMillis; + } + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US); + calendar.setTimeInMillis(timeInMillis); + calendar.add(Calendar.MONTH, months); + calendar.add(Calendar.DAY_OF_MONTH, days); + calendar.add(Calendar.MILLISECOND, (int) (nanoseconds / NANOS_PER_MILLI)); + return calendar.getTimeInMillis(); + } + @Override public int hashCode() { @@ -309,6 +360,27 @@ public final class Duration } /** + * Checks if that duration has a day precision (nothing bellow the day level). + * @return {@code true} if that duration has a day precision, {@code false} otherwise + */ + public boolean hasDayPrecision() + { + return getNanoseconds() == 0; + } + + /** + * Checks if that duration has a millisecond precision (nothing bellow the millisecond level). + * @return {@code true} if that duration has a millisecond precision, {@code false} otherwise + */ + public boolean hasMillisecondPrecision() + { + // Checks that the duration has no data bellow milliseconds. We can do that by checking that the last + // 6 bits of the number of nanoseconds are all zeros. The compiler will replace the call to + // numberOfTrailingZeros by a TZCNT instruction. + return Long.numberOfTrailingZeros(getNanoseconds()) >= 6; + } + + /** * Appends the result of the division to the specified builder if the dividend is not zero. * * @param builder the builder to append to http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/CastFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/CastFcts.java b/src/java/org/apache/cassandra/cql3/functions/CastFcts.java index 9e5c729..be1d55b 100644 --- a/src/java/org/apache/cassandra/cql3/functions/CastFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/CastFcts.java @@ -46,6 +46,8 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UUIDType; import org.apache.cassandra.transport.ProtocolVersion; +import static org.apache.cassandra.cql3.functions.TimeFcts.*; + import org.apache.commons.lang3.text.WordUtils; /** @@ -93,14 +95,14 @@ public final class CastFcts 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(CassandraFunctionWrapper.create(TimeUUIDType.instance, SimpleDateType.instance, toDate(TimeUUIDType.instance))); + functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, TimestampType.instance, toTimestamp(TimeUUIDType.instance))); 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(CassandraFunctionWrapper.create(TimestampType.instance, SimpleDateType.instance, toDate(TimestampType.instance))); 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(CassandraFunctionWrapper.create(SimpleDateType.instance, TimestampType.instance, toTimestamp(SimpleDateType.instance))); functions.add(CastAsTextFunction.create(SimpleDateType.instance, AsciiType.instance)); functions.add(CastAsTextFunction.create(SimpleDateType.instance, UTF8Type.instance)); functions.add(CastAsTextFunction.create(TimeType.instance, AsciiType.instance)); http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java index 0a039c0..9232fc1 100644 --- a/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java @@ -37,18 +37,26 @@ public final class OperationFcts { ADDITION('+', "_add") { - protected ByteBuffer execute(NumberType<?> resultType, - NumberType<?> leftType, - ByteBuffer left, - NumberType<?> rightType, - ByteBuffer right) + protected ByteBuffer executeOnNumerics(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) { return resultType.add(leftType, left, rightType, right); } + + @Override + protected ByteBuffer executeOnTemporals(TemporalType<?> type, + ByteBuffer temporal, + ByteBuffer duration) + { + return type.addDuration(temporal, duration); + } }, SUBSTRACTION('-', "_substract") { - protected ByteBuffer execute(NumberType<?> resultType, + protected ByteBuffer executeOnNumerics(NumberType<?> resultType, NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, @@ -56,10 +64,18 @@ public final class OperationFcts { return resultType.substract(leftType, left, rightType, right); } + + @Override + protected ByteBuffer executeOnTemporals(TemporalType<?> type, + ByteBuffer temporal, + ByteBuffer duration) + { + return type.substractDuration(temporal, duration); + } }, MULTIPLICATION('*', "_multiply") { - protected ByteBuffer execute(NumberType<?> resultType, + protected ByteBuffer executeOnNumerics(NumberType<?> resultType, NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, @@ -70,7 +86,7 @@ public final class OperationFcts }, DIVISION('/', "_divide") { - protected ByteBuffer execute(NumberType<?> resultType, + protected ByteBuffer executeOnNumerics(NumberType<?> resultType, NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, @@ -81,7 +97,7 @@ public final class OperationFcts }, MODULO('%', "_modulo") { - protected ByteBuffer execute(NumberType<?> resultType, + protected ByteBuffer executeOnNumerics(NumberType<?> resultType, NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, @@ -108,7 +124,7 @@ public final class OperationFcts } /** - * Executes the operation between the specified operand. + * Executes the operation between the specified numeric operand. * * @param resultType the result ype of the operation * @param leftType the type of the left operand @@ -117,11 +133,26 @@ public final class OperationFcts * @param right the right operand * @return the operation result */ - protected abstract ByteBuffer execute(NumberType<?> resultType, - NumberType<?> leftType, - ByteBuffer left, - NumberType<?> rightType, - ByteBuffer right); + protected abstract ByteBuffer executeOnNumerics(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right); + + /** + * Executes the operation on the specified temporal operand. + * + * @param type the temporal type + * @param temporal the temporal value + * @param duration the duration + * @return the operation result + */ + protected ByteBuffer executeOnTemporals(TemporalType<?> type, + ByteBuffer temporal, + ByteBuffer duration) + { + throw new UnsupportedOperationException(); + } /** * Returns the {@code OPERATOR} associated to the specified function. @@ -178,14 +209,18 @@ public final class OperationFcts for (NumberType<?> right : numericTypes) { NumberType<?> returnType = returnType(left, right); - functions.add(new OperationFunction(returnType, left, OPERATION.ADDITION, right)); - functions.add(new OperationFunction(returnType, left, OPERATION.SUBSTRACTION, right)); - functions.add(new OperationFunction(returnType, left, OPERATION.MULTIPLICATION, right)); - functions.add(new OperationFunction(returnType, left, OPERATION.DIVISION, right)); - functions.add(new OperationFunction(returnType, left, OPERATION.MODULO, right)); + for (OPERATION operation : OPERATION.values()) + functions.add(new NumericOperationFunction(returnType, left, operation, right)); } - functions.add(new NegationFunction(left)); + functions.add(new NumericNegationFunction(left)); + } + + for (OPERATION operation : new OPERATION[] {OPERATION.ADDITION, OPERATION.SUBSTRACTION}) + { + functions.add(new TemporalOperationFunction(TimestampType.instance, operation)); + functions.add(new TemporalOperationFunction(SimpleDateType.instance, operation)); } + return functions; } @@ -298,16 +333,16 @@ public final class OperationFcts } /** - * Function that execute operations. + * Base class for functions that execute operations. */ - private static class OperationFunction extends NativeScalarFunction + private static abstract class OperationFunction extends NativeScalarFunction { private final OPERATION operation; - public OperationFunction(NumberType<?> returnType, - NumberType<?> left, + public OperationFunction(AbstractType<?> returnType, + AbstractType<?> left, OPERATION operation, - NumberType<?> right) + AbstractType<?> right) { super(operation.functionName, returnType, left, right); this.operation = operation; @@ -326,13 +361,9 @@ public final class OperationFcts if (left == null || !left.hasRemaining() || right == null || !right.hasRemaining()) return null; - NumberType<?> leftType = (NumberType<?>) argTypes().get(0); - NumberType<?> rightType = (NumberType<?>) argTypes().get(1); - NumberType<?> resultType = (NumberType<?>) returnType(); - try { - return operation.execute(resultType, leftType, left, rightType, right); + return doExecute(left, operation, right); } catch (Exception e) { @@ -340,22 +371,67 @@ public final class OperationFcts } } + protected abstract ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right); + /** * Returns the operator symbol. * @return the operator symbol */ - private char getOperator() + private final char getOperator() { return operation.symbol; } } /** + * Function that execute operations on numbers. + */ + private static class NumericOperationFunction extends OperationFunction + { + public NumericOperationFunction(NumberType<?> returnType, + NumberType<?> left, + OPERATION operation, + NumberType<?> right) + { + super(returnType, left, operation, right); + } + + @Override + protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right) + { + NumberType<?> leftType = (NumberType<?>) argTypes().get(0); + NumberType<?> rightType = (NumberType<?>) argTypes().get(1); + NumberType<?> resultType = (NumberType<?>) returnType(); + + return operation.executeOnNumerics(resultType, leftType, left, rightType, right); + } + } + + /** + * Function that execute operations on temporals (timestamp, date, ...). + */ + private static class TemporalOperationFunction extends OperationFunction + { + public TemporalOperationFunction(TemporalType<?> type, + OPERATION operation) + { + super(type, type, operation, DurationType.instance); + } + + @Override + protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right) + { + TemporalType<?> resultType = (TemporalType<?>) returnType(); + return operation.executeOnTemporals(resultType, left, right); + } + } + + /** * Function that negate a number. */ - private static class NegationFunction extends NativeScalarFunction + private static class NumericNegationFunction extends NativeScalarFunction { - public NegationFunction(NumberType<?> inputType) + public NumericNegationFunction(NumberType<?> inputType) { super(NEGATION_FUNCTION_NAME, inputType, inputType); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java index e682dcd..944f183 100644 --- a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java @@ -19,7 +19,6 @@ package org.apache.cassandra.cql3.functions; import java.nio.ByteBuffer; import java.util.Collection; -import java.util.Date; import java.util.List; import com.google.common.collect.ImmutableList; @@ -37,26 +36,30 @@ public abstract class TimeFcts public static Collection<Function> all() { - return ImmutableList.of(nowFct, + return ImmutableList.of(now(TimeUUIDType.instance), minTimeuuidFct, maxTimeuuidFct, dateOfFct, unixTimestampOfFct, - timeUuidtoDate, - timeUuidToTimestamp, - timeUuidToUnixTimestamp, - timestampToUnixTimestamp, - timestampToDate, - dateToUnixTimestamp, - dateToTimestamp); + toDate(TimeUUIDType.instance), + toTimestamp(TimeUUIDType.instance), + toUnixTimestamp(TimeUUIDType.instance), + toUnixTimestamp(TimestampType.instance), + toDate(SimpleDateType.instance), + toUnixTimestamp(SimpleDateType.instance), + toTimestamp(SimpleDateType.instance)); } - public static final Function nowFct = new NativeScalarFunction("now", TimeUUIDType.instance) + public static final Function now(final TemporalType<?> type) { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + return new NativeScalarFunction("now", type) { - return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes()); - } + @Override + public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + return type.now(); + } + }; }; public static final Function minTimeuuidFct = new NativeScalarFunction("mintimeuuid", TimeUUIDType.instance, TimestampType.instance) @@ -134,114 +137,66 @@ public abstract class TimeFcts } }; - /** - * Function that convert a value of <code>TIMEUUID</code> into a value of type <code>DATE</code>. - */ - public static final NativeScalarFunction timeUuidtoDate = new NativeScalarFunction("todate", SimpleDateType.instance, TimeUUIDType.instance) - { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) - { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; - - long timeInMillis = UUIDGen.unixTimestamp(UUIDGen.getUUID(bb)); - return SimpleDateType.instance.fromTimeInMillis(timeInMillis); - } - }; - - /** - * Function that convert a value of type <code>TIMEUUID</code> into a value of type <code>TIMESTAMP</code>. - */ - public static final NativeScalarFunction timeUuidToTimestamp = new NativeScalarFunction("totimestamp", TimestampType.instance, TimeUUIDType.instance) - { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) - { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; - - long timeInMillis = UUIDGen.unixTimestamp(UUIDGen.getUUID(bb)); - return TimestampType.instance.fromTimeInMillis(timeInMillis); - } - }; - - /** - * Function that convert a value of type <code>TIMEUUID</code> into an UNIX timestamp. - */ - public static final NativeScalarFunction timeUuidToUnixTimestamp = new NativeScalarFunction("tounixtimestamp", LongType.instance, TimeUUIDType.instance) - { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) - { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; - - return ByteBufferUtil.bytes(UUIDGen.unixTimestamp(UUIDGen.getUUID(bb))); - } - }; - - /** - * Function that convert a value of type <code>TIMESTAMP</code> into an UNIX timestamp. - */ - public static final NativeScalarFunction timestampToUnixTimestamp = new NativeScalarFunction("tounixtimestamp", LongType.instance, TimestampType.instance) - { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) - { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; - - Date date = TimestampType.instance.compose(bb); - return date == null ? null : ByteBufferUtil.bytes(date.getTime()); - } - }; - /** - * Function that convert a value of type <code>TIMESTAMP</code> into a <code>DATE</code>. + * Creates a function that convert a value of the specified type into a <code>DATE</code>. + * @param type the temporal type + * @return a function that convert a value of the specified type into a <code>DATE</code>. */ - public static final NativeScalarFunction timestampToDate = new NativeScalarFunction("todate", SimpleDateType.instance, TimestampType.instance) + public static final NativeScalarFunction toDate(final TemporalType<?> type) { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + return new NativeScalarFunction("todate", SimpleDateType.instance, type) { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; - - Date date = TimestampType.instance.compose(bb); - return date == null ? null : SimpleDateType.instance.fromTimeInMillis(date.getTime()); - } - }; + public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + ByteBuffer bb = parameters.get(0); + if (bb == null || !bb.hasRemaining()) + return null; + + long millis = type.toTimeInMillis(bb); + return SimpleDateType.instance.fromTimeInMillis(millis); + } + }; + } /** - * Function that convert a value of type <code>TIMESTAMP</code> into a <code>DATE</code>. + * Creates a function that convert a value of the specified type into a <code>TIMESTAMP</code>. + * @param type the temporal type + * @return a function that convert a value of the specified type into a <code>TIMESTAMP</code>. */ - public static final NativeScalarFunction dateToTimestamp = new NativeScalarFunction("totimestamp", TimestampType.instance, SimpleDateType.instance) + public static final NativeScalarFunction toTimestamp(final TemporalType<?> type) { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + return new NativeScalarFunction("totimestamp", TimestampType.instance, type) { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; + public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + ByteBuffer bb = parameters.get(0); + if (bb == null || !bb.hasRemaining()) + return null; + + long millis = type.toTimeInMillis(bb); + return TimestampType.instance.fromTimeInMillis(millis); + } + }; + } - long millis = SimpleDateType.instance.toTimeInMillis(bb); - return TimestampType.instance.fromTimeInMillis(millis); - } - }; - - /** - * Function that convert a value of type <code>DATE</code> into an UNIX timestamp. - */ - public static final NativeScalarFunction dateToUnixTimestamp = new NativeScalarFunction("tounixtimestamp", LongType.instance, SimpleDateType.instance) - { - public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) - { - ByteBuffer bb = parameters.get(0); - if (bb == null) - return null; + /** + * Creates a function that convert a value of the specified type into an UNIX timestamp. + * @param type the temporal type + * @return a function that convert a value of the specified type into an UNIX timestamp. + */ + public static final NativeScalarFunction toUnixTimestamp(final TemporalType<?> type) + { + return new NativeScalarFunction("tounixtimestamp", LongType.instance, type) + { + public ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + ByteBuffer bb = parameters.get(0); + if (bb == null || !bb.hasRemaining()) + return null; - return ByteBufferUtil.bytes(SimpleDateType.instance.toTimeInMillis(bb)); - } - }; + return ByteBufferUtil.bytes(type.toTimeInMillis(bb)); + } + }; + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java index 9db5e36..f883ccd 100644 --- a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java +++ b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java @@ -21,14 +21,18 @@ import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.Duration; import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.statements.RequestValidations; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.SimpleDateSerializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; -public class SimpleDateType extends AbstractType<Integer> +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + +public class SimpleDateType extends TemporalType<Integer> { public static final SimpleDateType instance = new SimpleDateType(); @@ -39,11 +43,13 @@ public class SimpleDateType extends AbstractType<Integer> return ByteBufferUtil.bytes(SimpleDateSerializer.dateStringToDays(source)); } + @Override public ByteBuffer fromTimeInMillis(long millis) throws MarshalException { return ByteBufferUtil.bytes(SimpleDateSerializer.timeInMillisToDay(millis)); } + @Override public long toTimeInMillis(ByteBuffer buffer) throws MarshalException { return SimpleDateSerializer.dayToTimeInMillis(ByteBufferUtil.toInt(buffer)); @@ -85,4 +91,12 @@ public class SimpleDateType extends AbstractType<Integer> { return SimpleDateSerializer.instance; } + + @Override + protected void validateDuration(Duration duration) + { + // Checks that the duration has no data below days. + if (!duration.hasDayPrecision()) + throw invalidRequest("The duration must have a day precision. Was: %s", duration); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TemporalType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TemporalType.java b/src/java/org/apache/cassandra/db/marshal/TemporalType.java new file mode 100644 index 0000000..4e2ac5a --- /dev/null +++ b/src/java/org/apache/cassandra/db/marshal/TemporalType.java @@ -0,0 +1,103 @@ +/* + * 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.db.marshal; + +import java.nio.ByteBuffer; + +import org.apache.cassandra.cql3.Duration; + +/** + * Base type for temporal types (timestamp, date ...). + * + */ +public abstract class TemporalType<T> extends AbstractType<T> +{ + protected TemporalType(ComparisonType comparisonType) + { + super(comparisonType); + } + + /** + * Returns the current temporal value. + * @return the current temporal value. + */ + public ByteBuffer now() + { + return fromTimeInMillis(System.currentTimeMillis()); + } + + /** + * Converts this temporal in UNIX timestamp. + * @param value the temporal value. + * @return the UNIX timestamp corresponding to this temporal. + */ + public long toTimeInMillis(ByteBuffer value) + { + throw new UnsupportedOperationException(); + } + + /** + * Returns the temporal value corresponding to the specified UNIX timestamp. + * @param timeInMillis the UNIX timestamp to convert + * @return the temporal value corresponding to the specified UNIX timestamp + */ + public ByteBuffer fromTimeInMillis(long timeInMillis) + { + throw new UnsupportedOperationException(); + } + + /** + * Adds the duration to the specified value. + * + * @param temporal the value to add to + * @param duration the duration to add + * @return the addition result + */ + public ByteBuffer addDuration(ByteBuffer temporal, + ByteBuffer duration) + { + long timeInMillis = toTimeInMillis(temporal); + Duration d = DurationType.instance.compose(duration); + validateDuration(d); + return fromTimeInMillis(d.addTo(timeInMillis)); + } + + /** + * Substract the duration from the specified value. + * + * @param temporal the value to substract from + * @param duration the duration to substract + * @return the substracion result + */ + public ByteBuffer substractDuration(ByteBuffer temporal, + ByteBuffer duration) + { + long timeInMillis = toTimeInMillis(temporal); + Duration d = DurationType.instance.compose(duration); + validateDuration(d); + return fromTimeInMillis(d.substractFrom(timeInMillis)); + } + + /** + * Validates that the duration has the correct precision. + * @param duration the duration to validate. + */ + protected void validateDuration(Duration duration) + { + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TimeType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TimeType.java b/src/java/org/apache/cassandra/db/marshal/TimeType.java index 99f4f67..4b9d3a1 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimeType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimeType.java @@ -30,7 +30,7 @@ import org.apache.cassandra.transport.ProtocolVersion; /** * Nanosecond resolution time values */ -public class TimeType extends AbstractType<Long> +public class TimeType extends TemporalType<Long> { public static final TimeType instance = new TimeType(); private TimeType() {super(ComparisonType.BYTE_ORDER);} // singleton http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/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 f8e58db..39d1513 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java @@ -21,13 +21,15 @@ import java.nio.ByteBuffer; import java.util.UUID; import org.apache.cassandra.cql3.CQL3Type; +import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.Constants; import org.apache.cassandra.cql3.Term; import org.apache.cassandra.serializers.TypeSerializer; +import org.apache.cassandra.utils.UUIDGen; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.TimeUUIDSerializer; -public class TimeUUIDType extends AbstractType<UUID> +public class TimeUUIDType extends TemporalType<UUID> { public static final TimeUUIDType instance = new TimeUUIDType(); @@ -134,4 +136,28 @@ public class TimeUUIDType extends AbstractType<UUID> { return 16; } + + @Override + public long toTimeInMillis(ByteBuffer value) + { + return UUIDGen.unixTimestamp(UUIDGen.getUUID(value)); + } + + @Override + public ByteBuffer addDuration(ByteBuffer temporal, ByteBuffer duration) + { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuffer substractDuration(ByteBuffer temporal, ByteBuffer duration) + { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuffer now() + { + return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes()); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/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 ae74e2f..0699050 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimestampType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimestampType.java @@ -21,7 +21,10 @@ import java.nio.ByteBuffer; import java.util.Date; import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.Duration; import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.statements.RequestValidations; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.cql3.CQL3Type; @@ -31,6 +34,8 @@ import org.apache.cassandra.serializers.TimestampSerializer; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + /** * Type for date-time values. * @@ -38,7 +43,7 @@ import org.apache.cassandra.utils.ByteBufferUtil; * pre-unix-epoch dates, sorting them *after* post-unix-epoch ones (due to it's * use of unsigned bytes comparison). */ -public class TimestampType extends AbstractType<Date> +public class TimestampType extends TemporalType<Date> { private static final Logger logger = LoggerFactory.getLogger(TimestampType.class); @@ -65,12 +70,19 @@ public class TimestampType extends AbstractType<Date> return ByteBufferUtil.bytes(TimestampSerializer.dateStringToTimestamp(source)); } + @Override public ByteBuffer fromTimeInMillis(long millis) throws MarshalException { return ByteBufferUtil.bytes(millis); } @Override + public long toTimeInMillis(ByteBuffer value) + { + return ByteBufferUtil.toLong(value); + } + + @Override public Term fromJSONObject(Object parsed) throws MarshalException { if (parsed instanceof Long) @@ -132,4 +144,11 @@ public class TimestampType extends AbstractType<Date> { return 8; } + + @Override + protected void validateDuration(Duration duration) + { + if (!duration.hasMillisecondPrecision()) + throw invalidRequest("The duration must have a millisecond precision. Was: %s", duration); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/DurationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/DurationTest.java b/test/unit/org/apache/cassandra/cql3/DurationTest.java index b8f4400..ef031c4 100644 --- a/test/unit/org/apache/cassandra/cql3/DurationTest.java +++ b/test/unit/org/apache/cassandra/cql3/DurationTest.java @@ -18,6 +18,14 @@ */ package org.apache.cassandra.cql3; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.commons.lang3.time.DateUtils; + import org.junit.Assert; import org.junit.Test; @@ -99,11 +107,79 @@ public class DurationTest assertInvalidDuration("P0002-00-20", "Unable to convert 'P0002-00-20' to a duration"); } + @Test + public void testAddTo() + { + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("0m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("10us").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:10:00"), Duration.from("10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T01:30:00"), Duration.from("90m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T02:10:00"), Duration.from("2h10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-23T00:10:00"), Duration.from("2d10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-24T01:00:00"), Duration.from("2d25h").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-10-21T00:00:00"), Duration.from("1mo").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2017-11-21T00:00:00"), Duration.from("14mo").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2017-02-28T00:00:00"), Duration.from("12mo").addTo(toMillis("2016-02-29T00:00:00"))); + } + + @Test + public void testAddToWithNegativeDurations() + { + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("-0m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("-10us").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T23:50:00"), Duration.from("-10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T22:30:00"), Duration.from("-90m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T21:50:00"), Duration.from("-2h10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-18T23:50:00"), Duration.from("-2d10m").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-17T23:00:00"), Duration.from("-2d25h").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-08-21T00:00:00"), Duration.from("-1mo").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2015-07-21T00:00:00"), Duration.from("-14mo").addTo(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2015-02-28T00:00:00"), Duration.from("-12mo").addTo(toMillis("2016-02-29T00:00:00"))); + } + + @Test + public void testSubstractFrom() + { + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("0m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("10us").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T23:50:00"), Duration.from("10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T22:30:00"), Duration.from("90m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-20T21:50:00"), Duration.from("2h10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-18T23:50:00"), Duration.from("2d10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-17T23:00:00"), Duration.from("2d25h").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-08-21T00:00:00"), Duration.from("1mo").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2015-07-21T00:00:00"), Duration.from("14mo").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2015-02-28T00:00:00"), Duration.from("12mo").substractFrom(toMillis("2016-02-29T00:00:00"))); + } + + @Test + public void testSubstractWithNegativeDurations() + { + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("-0m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:00:00"), Duration.from("-10us").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T00:10:00"), Duration.from("-10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T01:30:00"), Duration.from("-90m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-21T02:10:00"), Duration.from("-2h10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-23T00:10:00"), Duration.from("-2d10m").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-09-24T01:00:00"), Duration.from("-2d25h").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2016-10-21T00:00:00"), Duration.from("-1mo").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2017-11-21T00:00:00"), Duration.from("-14mo").substractFrom(toMillis("2016-09-21T00:00:00"))); + assertEquals(toMillis("2017-02-28T00:00:00"), Duration.from("-12mo").substractFrom(toMillis("2016-02-29T00:00:00"))); + } + + private long toMillis(String timeAsString) + { + SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + parser.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = parser.parse(timeAsString, new ParsePosition(0)); + return DateUtils.truncate(date, Calendar.SECOND).getTime(); + } + public void assertInvalidDuration(String duration, String expectedErrorMessage) { try { - System.out.println(Duration.from(duration)); + Duration.from(duration); Assert.fail(); } catch (InvalidRequestException e) http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java b/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java index 6de5fdb..49c9e30 100644 --- a/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java +++ b/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java @@ -19,12 +19,15 @@ package org.apache.cassandra.cql3.functions; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Date; import org.junit.Test; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.exceptions.OperationExecutionException; +import org.apache.cassandra.serializers.SimpleDateSerializer; +import org.apache.cassandra.serializers.TimestampSerializer; public class OperationFctsTest extends CQLTester { @@ -741,4 +744,99 @@ public class OperationFctsTest extends CQLTester assertRows(execute("SELECT a + (int) ?, b + (tinyint) ?, c + (smallint) ? FROM %s", Integer.MAX_VALUE, Byte.MAX_VALUE, Short.MAX_VALUE), row(Integer.MIN_VALUE, Byte.MIN_VALUE, Short.MIN_VALUE)); } + + @Test + public void testOperationsWithDuration() throws Throwable + { + // Test with timestamp type. + createTable("CREATE TABLE %s (pk int, time timestamp, v int, primary key (pk, time))"); + + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:10:00 UTC', 1)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:12:00 UTC', 2)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:14:00 UTC', 3)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:15:00 UTC', 4)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:21:00 UTC', 5)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:22:00 UTC', 6)"); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 5m", toTimestamp("2016-09-27 16:20:00 UTC")), + row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5), + row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6)); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? - 10m", toTimestamp("2016-09-27 16:25:00 UTC")), + row(1, toTimestamp("2016-09-27 16:15:00 UTC"), 4), + row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5), + row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6)); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? + 5m", toTimestamp("2016-09-27 16:15:00 UTC")), + row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5), + row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6)); + + assertRows(execute("SELECT time - 10m FROM %s WHERE pk = 1"), + row(toTimestamp("2016-09-27 16:00:00 UTC")), + row(toTimestamp("2016-09-27 16:02:00 UTC")), + row(toTimestamp("2016-09-27 16:04:00 UTC")), + row(toTimestamp("2016-09-27 16:05:00 UTC")), + row(toTimestamp("2016-09-27 16:11:00 UTC")), + row(toTimestamp("2016-09-27 16:12:00 UTC"))); + + assertInvalidMessage("the '%' operation is not supported between time and 10m", + "SELECT time %% 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the '*' operation is not supported between time and 10m", + "SELECT time * 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the '/' operation is not supported between time and 10m", + "SELECT time / 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the operation 'timestamp - duration' failed: The duration must have a millisecond precision. Was: 10us", + "SELECT * FROM %s WHERE pk = 1 AND time > ? - 10us", toTimestamp("2016-09-27 16:15:00 UTC")); + + // Test with date type. + createTable("CREATE TABLE %s (pk int, time date, v int, primary key (pk, time))"); + + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27', 1)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-28', 2)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-29', 3)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-30', 4)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-10-01', 5)"); + execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-10-04', 6)"); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 5d", toDate("2016-10-04")), + row(1, toDate("2016-09-30"), 4), + row(1, toDate("2016-10-01"), 5), + row(1, toDate("2016-10-04"), 6)); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 6d", toDate("2016-10-04")), + row(1, toDate("2016-09-29"), 3), + row(1, toDate("2016-09-30"), 4), + row(1, toDate("2016-10-01"), 5), + row(1, toDate("2016-10-04"), 6)); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? + 1d", toDate("2016-10-01")), + row(1, toDate("2016-10-04"), 6)); + + assertRows(execute("SELECT time - 2d FROM %s WHERE pk = 1"), + row(toDate("2016-09-25")), + row(toDate("2016-09-26")), + row(toDate("2016-09-27")), + row(toDate("2016-09-28")), + row(toDate("2016-09-29")), + row(toDate("2016-10-02"))); + + assertInvalidMessage("the '%' operation is not supported between time and 10m", + "SELECT time %% 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the '*' operation is not supported between time and 10m", + "SELECT time * 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the '/' operation is not supported between time and 10m", + "SELECT time / 10m FROM %s WHERE pk = 1"); + assertInvalidMessage("the operation 'date - duration' failed: The duration must have a day precision. Was: 10m", + "SELECT * FROM %s WHERE pk = 1 AND time > ? - 10m", toDate("2016-10-04")); + } + + private Date toTimestamp(String timestampAsString) + { + return new Date(TimestampSerializer.dateStringToTimestamp(timestampAsString)); + } + + private int toDate(String dateAsString) + { + return SimpleDateSerializer.dateStringToDays(dateAsString); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java b/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java index 5b9737f..b7b99b4 100644 --- a/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java +++ b/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java @@ -34,6 +34,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; +import static org.apache.cassandra.cql3.functions.TimeFcts.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -87,7 +88,7 @@ public class TimeFctsTest long timeInMillis = dateTime.getMillis(); ByteBuffer input = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0)); - ByteBuffer output = executeFunction(TimeFcts.timeUuidToTimestamp, input); + ByteBuffer output = executeFunction(toTimestamp(TimeUUIDType.instance), input); assertEquals(dateTime.toDate(), TimestampType.instance.compose(output)); } @@ -113,7 +114,7 @@ public class TimeFctsTest long timeInMillis = dateTime.getMillis(); ByteBuffer input = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0)); - ByteBuffer output = executeFunction(TimeFcts.timeUuidToUnixTimestamp, input); + ByteBuffer output = executeFunction(toUnixTimestamp(TimeUUIDType.instance), input); assertEquals(timeInMillis, LongType.instance.compose(output).longValue()); } @@ -126,7 +127,7 @@ public class TimeFctsTest long timeInMillis = dateTime.getMillis(); ByteBuffer input = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0)); - ByteBuffer output = executeFunction(TimeFcts.timeUuidtoDate, input); + ByteBuffer output = executeFunction(toDate(TimeUUIDType.instance), input); long expectedTime = DateTimeFormat.forPattern("yyyy-MM-dd") .withZone(DateTimeZone.UTC) @@ -144,7 +145,7 @@ public class TimeFctsTest .parseDateTime("2015-05-21"); ByteBuffer input = SimpleDateType.instance.fromString("2015-05-21"); - ByteBuffer output = executeFunction(TimeFcts.dateToTimestamp, input); + ByteBuffer output = executeFunction(toTimestamp(SimpleDateType.instance), input); assertEquals(dateTime.toDate(), TimestampType.instance.compose(output)); } @@ -156,7 +157,7 @@ public class TimeFctsTest .parseDateTime("2015-05-21"); ByteBuffer input = SimpleDateType.instance.fromString("2015-05-21"); - ByteBuffer output = executeFunction(TimeFcts.dateToUnixTimestamp, input); + ByteBuffer output = executeFunction(toUnixTimestamp(SimpleDateType.instance), input); assertEquals(dateTime.getMillis(), LongType.instance.compose(output).longValue()); } @@ -168,14 +169,14 @@ public class TimeFctsTest .parseDateTime("2015-05-21"); ByteBuffer input = TimestampType.instance.fromString("2015-05-21 11:03:02+00"); - ByteBuffer output = executeFunction(TimeFcts.timestampToDate, input); + ByteBuffer output = executeFunction(toDate(TimestampType.instance), input); assertEquals(dateTime.getMillis(), SimpleDateType.instance.toTimeInMillis(output)); } @Test public void testTimestampToDateWithEmptyInput() { - ByteBuffer output = executeFunction(TimeFcts.timestampToDate, ByteBufferUtil.EMPTY_BYTE_BUFFER); + ByteBuffer output = executeFunction(toDate(TimestampType.instance), ByteBufferUtil.EMPTY_BYTE_BUFFER); assertNull(output); } @@ -187,14 +188,14 @@ public class TimeFctsTest .parseDateTime("2015-05-21 11:03:02"); ByteBuffer input = TimestampType.instance.decompose(dateTime.toDate()); - ByteBuffer output = executeFunction(TimeFcts.timestampToUnixTimestamp, input); + ByteBuffer output = executeFunction(toUnixTimestamp(TimestampType.instance), input); assertEquals(dateTime.getMillis(), LongType.instance.compose(output).longValue()); } @Test public void testTimestampToUnixTimestampWithEmptyInput() { - ByteBuffer output = executeFunction(TimeFcts.timestampToUnixTimestamp, ByteBufferUtil.EMPTY_BYTE_BUFFER); + ByteBuffer output = executeFunction(TimeFcts.toUnixTimestamp(TimestampType.instance), ByteBufferUtil.EMPTY_BYTE_BUFFER); assertNull(output); }
