DATACASS-465 - Introduces @Frozen annotation to mark schema elements as Frozen.
`@Frozen` is supported on UDTs, collection like properties (`Set`, `List` and `Map`), and their type parameters if those are UDTs.
For example
@Frozen Map<String, @Frozen MyUdt> propertyName;
Original pull request: #172.
This commit is contained in:
committed by
Mark Paluch
parent
2b05a68eea
commit
17adf20a14
@@ -88,8 +88,18 @@ public interface ColumnType {
|
||||
* @return
|
||||
*/
|
||||
static CassandraColumnType listOf(CassandraColumnType componentType) {
|
||||
return listOf(componentType, FrozenInfo.NOT_FROZEN);
|
||||
}
|
||||
/**
|
||||
* Creates a List {@link ColumnType} given its {@link CassandraColumnType component type}.
|
||||
*
|
||||
* @param componentType must not be {@literal null}.
|
||||
* @param frozen
|
||||
* @return
|
||||
*/
|
||||
static CassandraColumnType listOf(CassandraColumnType componentType, FrozenInfo frozen) {
|
||||
return new DefaultCassandraColumnType(ClassTypeInformation.LIST,
|
||||
() -> DataTypes.listOf(componentType.getDataType()), componentType);
|
||||
() -> DataTypes.listOf(componentType.getDataType(), frozen.self), componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +124,17 @@ public interface ColumnType {
|
||||
* @return
|
||||
*/
|
||||
static CassandraColumnType setOf(CassandraColumnType componentType) {
|
||||
return new DefaultCassandraColumnType(ClassTypeInformation.SET, () -> DataTypes.setOf(componentType.getDataType()),
|
||||
return setOf(componentType, FrozenInfo.NOT_FROZEN);
|
||||
}
|
||||
/**
|
||||
* Creates a Set {@link ColumnType} given its {@link CassandraColumnType component type}.
|
||||
*
|
||||
* @param componentType must not be {@literal null}.
|
||||
* @param frozen must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static CassandraColumnType setOf(CassandraColumnType componentType, FrozenInfo frozen) {
|
||||
return new DefaultCassandraColumnType(ClassTypeInformation.SET, () -> DataTypes.setOf(componentType.getDataType(),frozen.self),
|
||||
componentType);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.core.convert;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.Frozen;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -62,6 +63,21 @@ public interface ColumnTypeResolver {
|
||||
*/
|
||||
CassandraColumnType resolve(TypeInformation<?> typeInformation);
|
||||
|
||||
/**
|
||||
* Resolve a {@link CassandraColumnType} from {@link TypeInformation}. Considers potentially registered custom
|
||||
* converters and simple type rules.
|
||||
*
|
||||
* @param typeInformation must not be {@literal null}.
|
||||
* @param frozen must not be {@literal null}.
|
||||
* @return
|
||||
* @see org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder
|
||||
* @see CassandraCustomConversions
|
||||
* @throws org.springframework.dao.InvalidDataAccessApiUsageException
|
||||
*/
|
||||
default CassandraColumnType resolve(TypeInformation<?> typeInformation, FrozenInfo frozen) {
|
||||
return resolve(typeInformation, FrozenInfo.NOT_FROZEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a {@link CassandraColumnType} from a {@link CassandraType} annotation.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core.convert;
|
||||
|
||||
import java.lang.reflect.AnnotatedParameterizedType;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -25,13 +27,14 @@ import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType.Name;
|
||||
import org.springframework.data.cassandra.core.mapping.Frozen;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -95,6 +98,8 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
|
||||
Assert.notNull(property, "Property must not be null");
|
||||
|
||||
FrozenInfo frozen = getFrozenInfo(property);
|
||||
|
||||
if (property.isAnnotationPresent(CassandraType.class)) {
|
||||
|
||||
CassandraType annotation = property.getRequiredAnnotation(CassandraType.class);
|
||||
@@ -122,7 +127,32 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
return resolve(annotation);
|
||||
}
|
||||
|
||||
return resolve(property.getTypeInformation());
|
||||
TypeInformation<?> typeInformation = property.getTypeInformation();
|
||||
return resolve(typeInformation, frozen);
|
||||
}
|
||||
|
||||
private FrozenInfo getFrozenInfo(CassandraPersistentProperty property) {
|
||||
|
||||
FrozenInfo frozen = FrozenInfo.frozen(property.isAnnotationPresent(Frozen.class));
|
||||
AnnotatedType annotatedType = property.findAnnotatedType(Frozen.class);
|
||||
|
||||
if (annotatedType instanceof AnnotatedParameterizedType) {
|
||||
|
||||
AnnotatedParameterizedType apt = (AnnotatedParameterizedType) annotatedType;
|
||||
AnnotatedType[] annotatedTypes = apt.getAnnotatedActualTypeArguments();
|
||||
|
||||
if (annotatedTypes.length > 0) {
|
||||
|
||||
AnnotatedType type1 = annotatedTypes[0];
|
||||
frozen = frozen.withFirstTypeParameter(type1.isAnnotationPresent(Frozen.class));
|
||||
if (annotatedTypes.length > 1) {
|
||||
|
||||
AnnotatedType type2 = annotatedTypes[1];
|
||||
frozen = frozen.withSecondTypeParameter(type2.isAnnotationPresent(Frozen.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
return frozen;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -131,6 +161,15 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
*/
|
||||
@Override
|
||||
public CassandraColumnType resolve(TypeInformation<?> typeInformation) {
|
||||
return resolve(typeInformation, FrozenInfo.NOT_FROZEN);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.cassandra.core.convert.ColumnTypeResolver#resolve(org.springframework.data.util.TypeInformation, org.springframework.data.cassandra.core.convert.FrozenInfo)
|
||||
*/
|
||||
@Override
|
||||
public CassandraColumnType resolve(TypeInformation<?> typeInformation, FrozenInfo frozen) {
|
||||
|
||||
return getCustomWriteTarget(typeInformation).map(it -> {
|
||||
return createCassandraTypeDescriptor(tryResolve(it), ClassTypeInformation.from(it));
|
||||
@@ -140,7 +179,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
return ColumnType.create(String.class, DataTypes.TEXT);
|
||||
}
|
||||
|
||||
return createCassandraTypeDescriptor(typeInformation);
|
||||
return createCassandraTypeDescriptor(typeInformation, frozen);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -350,19 +389,27 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
return new DefaultCassandraColumnType(typeInformation, dataType);
|
||||
}
|
||||
|
||||
private CassandraColumnType createCassandraTypeDescriptor(TypeInformation<?> typeInformation) {
|
||||
private CassandraColumnType createCassandraTypeDescriptor(TypeInformation<?> typeInformation, FrozenInfo frozen) {
|
||||
|
||||
if (List.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return ColumnType.listOf(resolve(typeInformation.getRequiredComponentType()));
|
||||
|
||||
FrozenInfo elementFrozen = frozen.forFirstTypeParameter();
|
||||
return ColumnType.listOf(resolve(typeInformation.getRequiredComponentType(), elementFrozen), frozen);
|
||||
}
|
||||
|
||||
if (Set.class.isAssignableFrom(typeInformation.getType())) {
|
||||
return ColumnType.setOf(resolve(typeInformation.getRequiredComponentType()));
|
||||
|
||||
FrozenInfo elementFrozen = frozen.forFirstTypeParameter();
|
||||
return ColumnType.setOf(resolve(typeInformation.getRequiredComponentType(), elementFrozen), frozen);
|
||||
}
|
||||
|
||||
if (typeInformation.isMap()) {
|
||||
return ColumnType.mapOf(resolve(typeInformation.getRequiredComponentType()),
|
||||
resolve(typeInformation.getRequiredMapValueType()));
|
||||
|
||||
FrozenInfo frozenKey = frozen.forFirstTypeParameter();
|
||||
FrozenInfo frozenValue = frozen.forSecondTypeParameter();
|
||||
|
||||
return ColumnType.mapOf(resolve(typeInformation.getRequiredComponentType(), frozenKey),
|
||||
resolve(typeInformation.getRequiredMapValueType(), frozenValue));
|
||||
}
|
||||
|
||||
CassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(typeInformation);
|
||||
@@ -370,7 +417,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
if (persistentEntity != null) {
|
||||
|
||||
if (persistentEntity.isUserDefinedType()) {
|
||||
return new DefaultCassandraColumnType(typeInformation, getUserType(persistentEntity));
|
||||
return new DefaultCassandraColumnType(typeInformation, getUserType(persistentEntity, frozen.self));
|
||||
}
|
||||
|
||||
if (persistentEntity.isTupleType()) {
|
||||
@@ -394,10 +441,11 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
return new DefaultCassandraColumnType(typeInformation, dataType);
|
||||
}
|
||||
|
||||
private DataType getUserType(CassandraPersistentEntity<?> persistentEntity) {
|
||||
private DataType getUserType(CassandraPersistentEntity<?> persistentEntity, boolean frozen) {
|
||||
|
||||
CqlIdentifier identifier = persistentEntity.getTableName();
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType userType = userTypeResolver.resolveType(identifier);
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType userType = userTypeResolver.resolveType(identifier)
|
||||
.copy(frozen);
|
||||
|
||||
if (userType == null) {
|
||||
throw new MappingException(String.format("User type [%s] not found", identifier));
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
/**
|
||||
* Information about if a type or its elements should be frozen or not.
|
||||
*/
|
||||
public class FrozenInfo {
|
||||
|
||||
public static final FrozenInfo NOT_FROZEN = new FrozenInfo();
|
||||
|
||||
final boolean self;
|
||||
final boolean firstTypeParameter;
|
||||
final boolean secondTypeParameter;
|
||||
|
||||
public static FrozenInfo frozen(boolean frozen) {
|
||||
return new FrozenInfo(frozen);
|
||||
}
|
||||
|
||||
private FrozenInfo() {
|
||||
this(false, false, false);
|
||||
}
|
||||
|
||||
private FrozenInfo(boolean selfFrozen) {
|
||||
this(selfFrozen, false, false);
|
||||
}
|
||||
|
||||
private FrozenInfo(boolean self, boolean firstTypeParameter, boolean secondTypeParameter) {
|
||||
|
||||
this.self = self;
|
||||
this.firstTypeParameter = firstTypeParameter;
|
||||
this.secondTypeParameter = secondTypeParameter;
|
||||
}
|
||||
|
||||
FrozenInfo withFirstTypeParameter(boolean firstTypeParameter) {
|
||||
return new FrozenInfo(self, firstTypeParameter, secondTypeParameter);
|
||||
}
|
||||
|
||||
FrozenInfo withSecondTypeParameter(boolean secondTypeParameter) {
|
||||
return new FrozenInfo(self, firstTypeParameter, secondTypeParameter);
|
||||
}
|
||||
|
||||
FrozenInfo forFirstTypeParameter() {
|
||||
return new FrozenInfo(firstTypeParameter);
|
||||
}
|
||||
|
||||
FrozenInfo forSecondTypeParameter() {
|
||||
return new FrozenInfo(secondTypeParameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020 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
|
||||
*
|
||||
* https://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.mapping;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies a type as "frozen".
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @see <a href="https://express-cassandra.readthedocs.io/en/stable/advanced/#frozen-collections">Documentation about frozen collections</a>
|
||||
* @since 3.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = {ElementType.TYPE_USE, ElementType.FIELD, ElementType.METHOD})
|
||||
public @interface Frozen {
|
||||
|
||||
}
|
||||
@@ -23,17 +23,23 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.Frozen;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
|
||||
import com.datastax.oss.driver.api.core.data.TupleValue;
|
||||
import com.datastax.oss.driver.api.core.type.DataType;
|
||||
import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
import com.datastax.oss.driver.api.core.type.ListType;
|
||||
import com.datastax.oss.driver.api.core.type.MapType;
|
||||
import com.datastax.oss.driver.api.core.type.SetType;
|
||||
import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
|
||||
|
||||
/**
|
||||
@@ -44,7 +50,8 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
|
||||
public class ColumnTypeResolverUnitTests {
|
||||
|
||||
CassandraMappingContext mappingContext = new CassandraMappingContext();
|
||||
ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext, null, () -> CodecRegistry.DEFAULT,
|
||||
ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext,
|
||||
SchemaFactory.ShallowUserTypeResolver.INSTANCE, () -> CodecRegistry.DEFAULT,
|
||||
mappingContext::getCustomConversions);
|
||||
|
||||
@Test // DATACASS-743
|
||||
@@ -183,8 +190,7 @@ public class ColumnTypeResolverUnitTests {
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
CassandraColumnType columnType = resolver.resolve(entity.getRequiredPersistentProperty("tupleValue"));
|
||||
assertThat(columnType.getType())
|
||||
.isEqualTo(TupleValue.class);
|
||||
assertThat(columnType.getType()).isEqualTo(TupleValue.class);
|
||||
|
||||
assertThatThrownBy(columnType::getDataType).isInstanceOf(MappingException.class);
|
||||
}
|
||||
@@ -201,6 +207,108 @@ public class ColumnTypeResolverUnitTests {
|
||||
assertThat(resolver.resolve(timeUUIDProperty).getDataType()).isEqualTo(DataTypes.TIMEUUID);
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void listPropertyWithFrozenAnnotation() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenList")).getDataType();
|
||||
assertThat(dataType).isInstanceOf(ListType.class);
|
||||
assertThat(((ListType) dataType).isFrozen()).describedAs("The list itself should be frozen").isTrue();
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void listPropertyWithFrozenAnnotationOnElement() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenListContent")).getDataType();
|
||||
|
||||
assertThat(dataType).isInstanceOf(ListType.class);
|
||||
assertThat(((ListType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.").isFalse();
|
||||
|
||||
DataType elementType = ((ListType) dataType).getElementType();
|
||||
assertThat(elementType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) elementType).isFrozen())
|
||||
.describedAs("The element type should be frozen").isTrue();
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void setPropertyWithFrozenAnnotation() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenSet")).getDataType();
|
||||
assertThat(dataType).isInstanceOf(SetType.class);
|
||||
assertThat(((SetType) dataType).isFrozen()).describedAs("The set itself should be frozen").isTrue();
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void setPropertyWithFrozenAnnotationOnElement() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenSetContent")).getDataType();
|
||||
|
||||
assertThat(dataType).isInstanceOf(SetType.class);
|
||||
assertThat(((SetType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.").isFalse();
|
||||
|
||||
DataType elementType = ((SetType) dataType).getElementType();
|
||||
assertThat(elementType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) elementType).isFrozen())
|
||||
.describedAs("The element type should be frozen").isTrue();
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void mapPropertyWithFrozenAnnotationOnKey() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenMapKey")).getDataType();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(dataType).isInstanceOf(MapType.class);
|
||||
softly.assertThat(((MapType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.")
|
||||
.isFalse();
|
||||
|
||||
DataType keyType = ((MapType) dataType).getKeyType();
|
||||
softly.assertThat(keyType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) keyType).isFrozen())
|
||||
.describedAs("The key type should be frozen").isTrue();
|
||||
|
||||
DataType valueType = ((MapType) dataType).getValueType();
|
||||
softly.assertThat(valueType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) valueType).isFrozen())
|
||||
.describedAs("The value type should not be frozen").isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATACASS-465
|
||||
public void mapPropertyWithFrozenAnnotationOnValue() {
|
||||
|
||||
BasicCassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(Person.class);
|
||||
|
||||
DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenMapValue")).getDataType();
|
||||
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(dataType).isInstanceOf(MapType.class);
|
||||
softly.assertThat(((MapType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.")
|
||||
.isFalse();
|
||||
|
||||
DataType keyType = ((MapType) dataType).getKeyType();
|
||||
softly.assertThat(keyType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) keyType).isFrozen())
|
||||
.describedAs("The key type should not be frozen").isFalse();
|
||||
|
||||
DataType valueType = ((MapType) dataType).getValueType();
|
||||
softly.assertThat(valueType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
|
||||
softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) valueType).isFrozen())
|
||||
.describedAs("The value type should be frozen").isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
String name;
|
||||
@@ -229,6 +337,22 @@ public class ColumnTypeResolverUnitTests {
|
||||
typeArguments = { CassandraType.Name.INT, CassandraType.Name.TEXT }) Map<MyEnum, MyEnum> enumMapAsInt;
|
||||
|
||||
TupleValue tupleValue;
|
||||
|
||||
@Frozen List<String> frozenList;
|
||||
List<@Frozen MyUdt> frozenListContent;
|
||||
|
||||
@Frozen Set<String> frozenSet;
|
||||
Set<@Frozen MyUdt> frozenSetContent;
|
||||
|
||||
Map<@Frozen MyUdt, MyUdt> frozenMapKey;
|
||||
|
||||
Map<MyUdt, @Frozen MyUdt> frozenMapValue;
|
||||
|
||||
@UserDefinedType
|
||||
private static class MyUdt {
|
||||
String one;
|
||||
Integer two;
|
||||
}
|
||||
}
|
||||
|
||||
static class TypeWithUUIDColumn {
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.Element;
|
||||
|
||||
@@ -20,14 +20,15 @@ import static org.springframework.data.cassandra.core.mapping.CassandraType.*;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.AnnotatedParameterizedType;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -131,6 +132,24 @@ public class BasicCassandraPersistentPropertyUnitTests {
|
||||
assertThat(persistentProperty.findAnnotation(CassandraType.class)).isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates how to access annotations on type parameters.
|
||||
*/
|
||||
@Test // DATACASS-465
|
||||
public void parameterAnnotations() {
|
||||
|
||||
AnnotatedType annotatedType = findAnnotatedType(TypeWithMaps.class, "parameterizedWithParameterAnnotation");
|
||||
assertThat(annotatedType).isNotNull().isInstanceOf(AnnotatedParameterizedType.class);
|
||||
|
||||
AnnotatedParameterizedType apt = (AnnotatedParameterizedType) annotatedType;
|
||||
AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
|
||||
assertThat(annotatedActualTypeArguments)
|
||||
.extracting(a -> a.getType(), a -> Arrays.stream(a.getAnnotations()).map(an -> an instanceof Indexed).toArray())
|
||||
.containsExactly(tuple(String.class, new boolean[] {}), // annotation on key type
|
||||
tuple(String.class, new boolean[] { true }) // annotation on value type
|
||||
);
|
||||
}
|
||||
|
||||
private AnnotatedType findAnnotatedType(Class<?> type, String parameterized) {
|
||||
return getPropertyFor(TypeWithMaps.class, parameterized).findAnnotatedType(Indexed.class);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public class PartTreeCassandraQueryUnitTests {
|
||||
when(attachmentPoint.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT);
|
||||
when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT);
|
||||
|
||||
when(userTypeMock.copy(anyBoolean())).thenReturn(userTypeMock);
|
||||
when(userTypeMock.getAttachmentPoint()).thenReturn(attachmentPoint);
|
||||
when(userTypeMock.getFieldNames())
|
||||
.thenReturn(Arrays.asList("city", "country").stream().map(CqlIdentifier::fromCql).collect(Collectors.toList()));
|
||||
|
||||
Reference in New Issue
Block a user