DATACASS-302 - Support Cassandra time columns.
We now support Cassandra time columns via LocalTime types (JSR-310, Joda and ThreeTenBackport) in domain classes, queries and updates.
@Table
class Schedule {
@Id String id;
LocalTime scheduledAt;
}
This commit is contained in:
@@ -17,10 +17,22 @@ package org.springframework.data.cassandra.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion. {@link CassandraCustomConversions} also act as factory for
|
||||
@@ -37,6 +49,12 @@ public class CassandraCustomConversions extends org.springframework.data.convert
|
||||
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
/**
|
||||
* Set of types that indicate usage of Cassandra's time type. Time is represented as long so we need to imply the type
|
||||
* from an artificial simple type.
|
||||
*/
|
||||
private final static Set<Class<?>> NATIVE_TIME_TYPE_MARKERS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
@@ -48,6 +66,23 @@ public class CassandraCustomConversions extends org.springframework.data.convert
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(CassandraSimpleTypeHolder.HOLDER, STORE_CONVERTERS);
|
||||
|
||||
List<? extends Class<?>> timeMarkers = STORE_CONVERTERS.stream() //
|
||||
.filter(Converter.class::isInstance) //
|
||||
.map(Object::getClass) //
|
||||
.filter(it -> AnnotatedElementUtils.hasAnnotation(it, WritingConverter.class)) //
|
||||
.filter(it -> {
|
||||
|
||||
CassandraType annotation = AnnotatedElementUtils.getMergedAnnotation(it, CassandraType.class);
|
||||
return annotation != null && annotation.type() == Name.TIME;
|
||||
}) //
|
||||
.map(it -> {
|
||||
|
||||
ResolvableType classType = ResolvableType.forClass(it).as(Converter.class).getGeneric(0);
|
||||
return classType.getRawClass();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
NATIVE_TIME_TYPE_MARKERS = new HashSet<>(timeMarkers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,4 +93,18 @@ public class CassandraCustomConversions extends org.springframework.data.convert
|
||||
public CassandraCustomConversions(List<?> converters) {
|
||||
super(STORE_CONVERSIONS, converters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if the {@link Class type} is used to denote Cassandra's {@code time} column type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return {@literal true} if the type maps to Cassandra's {@code time} column type.
|
||||
* @since 2.1
|
||||
*/
|
||||
public boolean isNativeTimeTypeMarker(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
return NATIVE_TIME_TYPE_MARKERS.contains(ClassUtils.getUserClass(type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalTime;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
|
||||
/**
|
||||
* Helper class to register JSR-310 specific {@link Converter} implementations to convert between Cassandra types in
|
||||
* case the library is present on the classpath.
|
||||
@@ -53,6 +58,8 @@ public abstract class CassandraJodaTimeConverters {
|
||||
|
||||
converters.add(CassandraLocalDateToLocalDateConverter.INSTANCE);
|
||||
converters.add(LocalDateToCassandraLocalDateConverter.INSTANCE);
|
||||
converters.add(MillisOfDayToLocalTimeConverter.INSTANCE);
|
||||
converters.add(LocalTimeToMillisOfDayConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
@@ -89,4 +96,36 @@ public abstract class CassandraJodaTimeConverters {
|
||||
source.getDayOfMonth());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Long}s to their {@link LocalTime} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public enum MillisOfDayToLocalTimeConverter implements Converter<Long, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Long source) {
|
||||
return LocalTime.fromMillisOfDay(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link LocalTime}s to their {@link Long} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@WritingConverter
|
||||
@CassandraType(type = Name.TIME)
|
||||
public enum LocalTimeToMillisOfDayConverter implements Converter<LocalTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalTime source) {
|
||||
return (long) source.getMillisOfDay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,18 @@
|
||||
package org.springframework.data.cassandra.core.convert;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
|
||||
/**
|
||||
* Helper class to register JodaTime specific {@link Converter} implementations in case the library is present on the
|
||||
@@ -44,6 +51,8 @@ public abstract class CassandraJsr310Converters {
|
||||
|
||||
converters.add(CassandraLocalDateToLocalDateConverter.INSTANCE);
|
||||
converters.add(LocalDateToCassandraLocalDateConverter.INSTANCE);
|
||||
converters.add(MillisOfDayToLocalTimeConverter.INSTANCE);
|
||||
converters.add(LocalTimeToMillisOfDayConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
@@ -80,4 +89,38 @@ public abstract class CassandraJsr310Converters {
|
||||
source.getDayOfMonth());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Long}s to their {@link LocalTime} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public enum MillisOfDayToLocalTimeConverter implements Converter<Long, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Long source) {
|
||||
return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link LocalTime}s to their {@link Long} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@WritingConverter
|
||||
@CassandraType(type = Name.TIME)
|
||||
public enum LocalTimeToMillisOfDayConverter implements Converter<LocalTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalTime source) {
|
||||
return source.getLong(ChronoField.MILLI_OF_DAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,18 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.convert.ThreeTenBackPortConverters;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.threeten.bp.LocalDate;
|
||||
import org.threeten.bp.LocalTime;
|
||||
import org.threeten.bp.temporal.ChronoField;
|
||||
|
||||
import com.datastax.driver.core.DataType.Name;
|
||||
|
||||
/**
|
||||
* Helper class to register {@link Converter} implementations for the ThreeTen Backport project in case it's present on
|
||||
@@ -56,6 +63,8 @@ public abstract class CassandraThreeTenBackPortConverters {
|
||||
|
||||
converters.add(CassandraLocalDateToLocalDateConverter.INSTANCE);
|
||||
converters.add(LocalDateToCassandraLocalDateConverter.INSTANCE);
|
||||
converters.add(MillisOfDayToLocalTimeConverter.INSTANCE);
|
||||
converters.add(LocalTimeToMillisOfDayConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
@@ -92,4 +101,39 @@ public abstract class CassandraThreeTenBackPortConverters {
|
||||
source.getDayOfMonth());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Long}s to their {@link LocalTime} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
|
||||
public enum MillisOfDayToLocalTimeConverter implements Converter<Long, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Long source) {
|
||||
return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(source));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link LocalTime}s to their {@link Long} representation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@WritingConverter
|
||||
@CassandraType(type = Name.TIME)
|
||||
public enum LocalTimeToMillisOfDayConverter implements Converter<LocalTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalTime source) {
|
||||
return source.getLong(ChronoField.MILLI_OF_DAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,13 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;
|
||||
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeUtil.FrozenLiteralDataType;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.CustomConversions.StoreConversions;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -82,8 +82,7 @@ public class CassandraMappingContext
|
||||
|
||||
private @Nullable ClassLoader beanClassLoader;
|
||||
|
||||
private CustomConversions customConversions =
|
||||
new CustomConversions(StoreConversions.of(CassandraSimpleTypeHolder.HOLDER), Collections.emptyList());
|
||||
private CustomConversions customConversions = new CassandraCustomConversions(Collections.emptyList());
|
||||
|
||||
private Mapping mapping = new Mapping();
|
||||
|
||||
@@ -524,10 +523,7 @@ public class CassandraMappingContext
|
||||
* @since 1.5
|
||||
*/
|
||||
public DataType getDataType(Class<?> type) {
|
||||
|
||||
return this.customConversions.getCustomWriteTarget(type)
|
||||
.map(CassandraSimpleTypeHolder::getDataTypeFor)
|
||||
.orElseGet(() -> getDataTypeFor(type));
|
||||
return doGetDataType(type, this.customConversions.getCustomWriteTarget(type).orElse(type));
|
||||
}
|
||||
|
||||
public TupleType getTupleType(CassandraPersistentEntity<?> persistentEntity) {
|
||||
@@ -552,6 +548,7 @@ public class CassandraMappingContext
|
||||
return getDataTypeWithUserTypeFactory(property, DataTypeProvider.EntityUserType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DataType getDataTypeWithUserTypeFactory(CassandraPersistentProperty property,
|
||||
DataTypeProvider dataTypeProvider) {
|
||||
|
||||
@@ -591,6 +588,7 @@ public class CassandraMappingContext
|
||||
return getDataTypeWithUserTypeFactory(property.getTypeInformation(), dataTypeProvider, property::getDataType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DataType getDataTypeWithUserTypeFactory(TypeInformation<?> typeInformation, DataTypeProvider dataTypeProvider,
|
||||
Supplier<DataType> fallback) {
|
||||
|
||||
@@ -613,30 +611,56 @@ public class CassandraMappingContext
|
||||
}
|
||||
|
||||
Optional<DataType> customWriteTarget = this.customConversions.getCustomWriteTarget(typeInformation.getType())
|
||||
.map(CassandraSimpleTypeHolder::getDataTypeFor);
|
||||
.map(it -> doGetDataType(typeInformation.getType(), it));
|
||||
|
||||
DataType dataType = customWriteTarget
|
||||
.orElseGet(() -> this.customConversions.getCustomWriteTarget(typeInformation.getRequiredActualType().getType())
|
||||
.filter(it -> !typeInformation.isMap()).map(it -> {
|
||||
.orElseGet(() -> {
|
||||
Class<?> propertyType = typeInformation.getRequiredActualType().getType();
|
||||
return this.customConversions.getCustomWriteTarget(propertyType).filter(it -> !typeInformation.isMap())
|
||||
.map(it -> {
|
||||
|
||||
if (typeInformation.isCollectionLike()) {
|
||||
if (List.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return DataType.list(getDataTypeFor(it));
|
||||
if (typeInformation.isCollectionLike()) {
|
||||
if (List.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return DataType.list(doGetDataType(propertyType, it));
|
||||
}
|
||||
|
||||
if (Set.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return DataType.set(doGetDataType(propertyType, it));
|
||||
}
|
||||
}
|
||||
|
||||
if (Set.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return DataType.set(getDataTypeFor(it));
|
||||
}
|
||||
}
|
||||
return doGetDataType(propertyType, it);
|
||||
|
||||
return getDataTypeFor(it);
|
||||
|
||||
}).orElse(null));
|
||||
}).orElse(null);
|
||||
});
|
||||
|
||||
return dataType != null ? dataType
|
||||
: typeInformation.isMap() ? getMapDataType(typeInformation, dataTypeProvider) : fallback.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve Cassandra {@link DataType} with conditional handling of {@code time} data type.
|
||||
*
|
||||
* @param propertyType
|
||||
* @param converted
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private DataType doGetDataType(Class<?> propertyType, Class<?> converted) {
|
||||
|
||||
if (customConversions instanceof CassandraCustomConversions) {
|
||||
|
||||
CassandraCustomConversions conversions = (CassandraCustomConversions) customConversions;
|
||||
|
||||
if (conversions.isNativeTimeTypeMarker(converted)
|
||||
|| (conversions.isNativeTimeTypeMarker(propertyType) && Long.class.equals(converted))) {
|
||||
return DataType.time();
|
||||
}
|
||||
}
|
||||
|
||||
return CassandraSimpleTypeHolder.getDataTypeFor(converted);
|
||||
}
|
||||
|
||||
private TupleType getTupleType(DataTypeProvider dataTypeProvider, CassandraPersistentEntity<?> persistentEntity) {
|
||||
|
||||
List<DataType> types = new ArrayList<>();
|
||||
@@ -656,7 +680,7 @@ public class CassandraMappingContext
|
||||
|
||||
DataType keyType = getDataTypeWithUserTypeFactory(keyTypeInformation, dataTypeProvider, () -> {
|
||||
|
||||
DataType type = getDataTypeFor(keyTypeInformation.getType());
|
||||
DataType type = doGetDataType(keyTypeInformation.getType(), keyTypeInformation.getType());
|
||||
|
||||
if (type != null) {
|
||||
return type;
|
||||
@@ -667,7 +691,7 @@ public class CassandraMappingContext
|
||||
|
||||
DataType valueType = getDataTypeWithUserTypeFactory(valueTypeInformation, dataTypeProvider, () -> {
|
||||
|
||||
DataType type = getDataTypeFor(valueTypeInformation.getType());
|
||||
DataType type = doGetDataType(valueTypeInformation.getType(), valueTypeInformation.getType());
|
||||
|
||||
if (type != null) {
|
||||
return type;
|
||||
@@ -680,7 +704,7 @@ public class CassandraMappingContext
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DataType getUserDataType(TypeInformation<?> property, DataType elementType) {
|
||||
private DataType getUserDataType(TypeInformation<?> property, @Nullable DataType elementType) {
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core.mapping;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -121,6 +122,9 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
|
||||
// override UUID to timeuuid as regular uuid as the favored default
|
||||
classToDataType.put(UUID.class, DataType.uuid());
|
||||
|
||||
// override LocalTime to time as time columns are mapped to long by default
|
||||
classToDataType.put(LocalTime.class, DataType.time());
|
||||
|
||||
return classToDataType;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.data.cassandra.core.query.Criteria.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -33,6 +36,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
|
||||
import org.springframework.data.cassandra.core.cql.CqlTemplate;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
@@ -78,6 +82,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
SchemaTestUtils.potentiallyCreateTableFor(User.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TimeClass.class, template);
|
||||
SchemaTestUtils.truncate(User.class, template);
|
||||
SchemaTestUtils.truncate(UserToken.class, template);
|
||||
SchemaTestUtils.truncate(BookReference.class, template);
|
||||
@@ -478,4 +483,11 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
assertThat(ids).containsAll(expectedIds);
|
||||
assertThat(iterations).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class TimeClass {
|
||||
|
||||
@Id LocalTime id;
|
||||
LocalTime bar;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed 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.springframework.data.cassandra.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.joda.time.LocalTime;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraJodaTimeConverters.LocalTimeToMillisOfDayConverter;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraJodaTimeConverters.MillisOfDayToLocalTimeConverter;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CassandraJodaTimeConverters}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CassandraJodaTimeConvertersUnitTests {
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLocalTimeToLong() {
|
||||
|
||||
assertThat(MillisOfDayToLocalTimeConverter.INSTANCE.convert(3723000L))
|
||||
.isEqualTo(LocalTime.fromMillisOfDay(3723000L));
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLongToLocalTime() {
|
||||
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.MIDNIGHT)).isZero();
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.fromMillisOfDay(3723000L)))
|
||||
.isEqualTo(3723000L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed 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.springframework.data.cassandra.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraJsr310Converters.LocalTimeToMillisOfDayConverter;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraJsr310Converters.MillisOfDayToLocalTimeConverter;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CassandraJsr310Converters}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CassandraJsr310ConvertersUnitTests {
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLocalTimeToLong() {
|
||||
|
||||
assertThat(MillisOfDayToLocalTimeConverter.INSTANCE.convert(3723000L)).isEqualTo(LocalTime.of(1, 2, 3));
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLongToLocalTime() {
|
||||
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.MIDNIGHT)).isZero();
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.of(1, 2, 3))).isEqualTo(3723000L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed 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.springframework.data.cassandra.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraThreeTenBackPortConverters.LocalTimeToMillisOfDayConverter;
|
||||
import org.springframework.data.cassandra.core.convert.CassandraThreeTenBackPortConverters.MillisOfDayToLocalTimeConverter;
|
||||
import org.threeten.bp.LocalTime;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CassandraThreeTenBackPortConverters}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CassandraThreeTenBackPortConvertersUnitTests {
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLocalTimeToLong() {
|
||||
|
||||
assertThat(MillisOfDayToLocalTimeConverter.INSTANCE.convert(3723000L)).isEqualTo(LocalTime.of(1, 2, 3));
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldConvertLongToLocalTime() {
|
||||
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.MIDNIGHT)).isZero();
|
||||
assertThat(LocalTimeToMillisOfDayConverter.INSTANCE.convert(LocalTime.of(1, 2, 3))).isEqualTo(3723000L);
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,10 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.CassandraTemplate;
|
||||
@@ -48,8 +48,6 @@ import org.springframework.data.cassandra.support.CassandraVersion;
|
||||
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTest;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.Duration;
|
||||
import com.datastax.driver.core.LocalDate;
|
||||
@@ -431,7 +429,7 @@ public class CassandraTypeMappingIntegrationTests extends AbstractKeyspaceCreati
|
||||
operations.insert(entity);
|
||||
AllPossibleTypes loaded = load(entity);
|
||||
|
||||
Assertions.assertThat(loaded.getAnEnum()).isEqualTo(entity.getAnEnum());
|
||||
assertThat(loaded.getAnEnum()).isEqualTo(entity.getAnEnum());
|
||||
}
|
||||
|
||||
@Test // DATACASS-280
|
||||
@@ -545,6 +543,18 @@ public class CassandraTypeMappingIntegrationTests extends AbstractKeyspaceCreati
|
||||
assertThat(loaded.getLocalTime()).isEqualTo(entity.getLocalTime());
|
||||
}
|
||||
|
||||
@Test // DATACASS-296
|
||||
public void shouldReadAndWriteJodaLocalTime() {
|
||||
|
||||
AllPossibleTypes entity = new AllPossibleTypes("1");
|
||||
entity.setJodaLocalTime(org.joda.time.LocalTime.fromMillisOfDay(50000));
|
||||
|
||||
operations.insert(entity);
|
||||
AllPossibleTypes loaded = load(entity);
|
||||
|
||||
assertThat(loaded.getJodaLocalTime()).isEqualTo(entity.getJodaLocalTime());
|
||||
}
|
||||
|
||||
@Test // DATACASS-296
|
||||
public void shouldReadAndWriteInstant() {
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalTime;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
@@ -348,6 +348,17 @@ public class QueryMapperUnitTests {
|
||||
assertThat(mappedObject).contains(Criteria.where("tuple").is(tupleValue));
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldMapTime() {
|
||||
|
||||
Filter filter = Filter.from(Criteria.where("localDate").gt(LocalTime.fromMillisOfDay(1000)));
|
||||
|
||||
Filter mappedObject = this.queryMapper.getMappedObject(filter,
|
||||
this.mappingContext.getRequiredPersistentEntity(Person.class));
|
||||
|
||||
assertThat(mappedObject).contains(Criteria.where("localdate").gt(1000L));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATACASS-523
|
||||
public void referencingTupleElementsInQueryShouldFail() {
|
||||
|
||||
@@ -367,6 +378,8 @@ public class QueryMapperUnitTests {
|
||||
|
||||
Integer number;
|
||||
|
||||
LocalDate localDate;
|
||||
|
||||
MappedTuple tuple;
|
||||
|
||||
@Column("first_name") String firstName;
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.core.convert;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
@@ -238,6 +239,16 @@ public class UpdateMapperUnitTests {
|
||||
assertThat(update.toString()).isEqualTo("tuple = ('foo')");
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void shouldMapTime() {
|
||||
|
||||
Update update = this.updateMapper.getMappedObject(Update.empty().set("localTime", LocalTime.of(1, 2, 3)),
|
||||
this.persistentEntity);
|
||||
|
||||
assertThat(update.getUpdateOperations()).hasSize(1);
|
||||
assertThat(update.toString()).isEqualTo("localtime = 3723000");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATACASS-523
|
||||
public void referencingTupleElementsInQueryShouldFail() {
|
||||
this.updateMapper.getMappedObject(Update.empty().set("tuple.zip", "bar"), this.persistentEntity);
|
||||
@@ -252,6 +263,7 @@ public class UpdateMapperUnitTests {
|
||||
Map<String, Currency> map;
|
||||
Map<Manufacturer, Currency> manufacturers;
|
||||
Currency currency;
|
||||
LocalTime localTime;
|
||||
|
||||
Integer number;
|
||||
MappedTuple tuple;
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.util.NoSuchElementException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
@@ -41,6 +40,7 @@ import org.springframework.data.cassandra.core.cql.keyspace.ColumnSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification.ColumnFunction;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification;
|
||||
import org.springframework.data.cassandra.domain.AllPossibleTypes;
|
||||
import org.springframework.data.cassandra.support.UserTypeBuilder;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -463,6 +463,17 @@ public class CassandraMappingContextUnitTests {
|
||||
.isEqualTo(DataType.list(DataType.varchar()));
|
||||
}
|
||||
|
||||
@Test // DATACASS-302
|
||||
public void propertyTypeShouldMapToTime() {
|
||||
|
||||
CassandraPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(AllPossibleTypes.class);
|
||||
|
||||
assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("localTime")))
|
||||
.isEqualTo(DataType.time());
|
||||
assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("jodaLocalTime")))
|
||||
.isEqualTo(DataType.time());
|
||||
}
|
||||
|
||||
@Test // DATACASS-172, DATACASS-455
|
||||
public void shouldRegisterUdtTypes() {
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ public class AllPossibleTypes {
|
||||
org.joda.time.DateTime jodaDateTime;
|
||||
org.joda.time.LocalDate jodaLocalDate;
|
||||
org.joda.time.LocalDateTime jodaLocalDateTime;
|
||||
org.joda.time.LocalTime jodaLocalTime;
|
||||
|
||||
org.threeten.bp.Instant bpInstant;
|
||||
org.threeten.bp.LocalDate bpLocalDate;
|
||||
|
||||
@@ -9,6 +9,7 @@ This chapter summarizes changes and new features for each release.
|
||||
* Template API extended with `count(…)` and `exists(…)` methods accepting `Query`.
|
||||
* <<cassandra.template.query.fluent-template-api,Fluent API>> for CRUD operations.
|
||||
* Cassandra Mapped Tuple support via `@Tuple`.
|
||||
* Support for Cassandra `time` columns via `LocalTime`.
|
||||
* Support for `map` columns using User-defined/converted types.
|
||||
* <<cassandra.mapping-usage.events>>
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ for further details.
|
||||
(Joda, Java 8, JSR310-BackPort)
|
||||
| `date`
|
||||
|
||||
| `LocalTime`+
|
||||
(Joda, Java 8, JSR310-BackPort)
|
||||
| `time`
|
||||
|
||||
| `LocalDateTime`, `LocalTime`, `Instant` +
|
||||
(Joda, Java 8, JSR310-BackPort)
|
||||
| `timestamp`
|
||||
|
||||
Reference in New Issue
Block a user