DATACASS-406 - Support nested @UserDefinedType for schema creation.
Works for simple user defined types and those used in Lists or Sets. It does not work yet for maps and other collection-like types. Related tickets: DATACASS-409. Original pull request: #100.
This commit is contained in:
committed by
Mark Paluch
parent
15f9169640
commit
ca60ca6433
@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
|
||||
* This class generates CQL to create user types (UDT) and tables.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @since 1.5
|
||||
* @see org.springframework.data.cassandra.mapping.Table
|
||||
* @see org.springframework.data.cassandra.mapping.UserDefinedType
|
||||
@@ -143,6 +144,16 @@ public class CassandraPersistentEntitySchemaCreator {
|
||||
return specifications;
|
||||
}
|
||||
|
||||
private Map<CqlIdentifier, CassandraPersistentEntity<?>> getEntitiesByTableName(Collection<? extends CassandraPersistentEntity<?>> entities) {
|
||||
// TODO simplify by using Java 8 Streams API in 2.0.x
|
||||
Map<CqlIdentifier, CassandraPersistentEntity<?>> byTableName = new HashMap<CqlIdentifier, CassandraPersistentEntity<?>>();
|
||||
|
||||
for (CassandraPersistentEntity<?> entity : entities) {
|
||||
byTableName.put(entity.getTableName(), entity);
|
||||
}
|
||||
return byTableName;
|
||||
}
|
||||
|
||||
private void visitUserTypes(CassandraPersistentEntity<?> entity, final Set<CqlIdentifier> seen) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
|
||||
|
||||
@@ -60,6 +60,7 @@ import com.datastax.driver.core.UserType;
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
* @author John Blum
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class BasicCassandraMappingContext
|
||||
extends AbstractMappingContext<CassandraPersistentEntity<?>, CassandraPersistentProperty>
|
||||
@@ -68,7 +69,7 @@ public class BasicCassandraMappingContext
|
||||
protected ApplicationContext context;
|
||||
|
||||
protected CassandraPersistentEntityMetadataVerifier verifier =
|
||||
new CompositeCassandraPersistentEntityMetadataVerifier();
|
||||
new CompositeCassandraPersistentEntityMetadataVerifier();
|
||||
|
||||
protected ClassLoader beanClassLoader;
|
||||
|
||||
@@ -118,7 +119,7 @@ public class BasicCassandraMappingContext
|
||||
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
|
||||
|
||||
Assert.state(entity != null,
|
||||
String.format("Unknown persistent entity class name [%s]", entityClassName));
|
||||
String.format("Unknown persistent entity class name [%s]", entityClassName));
|
||||
|
||||
String entityTableName = entityMapping.getTableName();
|
||||
|
||||
@@ -127,10 +128,9 @@ public class BasicCassandraMappingContext
|
||||
}
|
||||
|
||||
processMappingOverrides(entity, entityMapping);
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Unknown persistent entity name [%s]", entityClassName), e);
|
||||
String.format("Unknown persistent entity name [%s]", entityClassName), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public class BasicCassandraMappingContext
|
||||
CassandraPersistentProperty property = entity.getPersistentProperty(mapping.getPropertyName());
|
||||
|
||||
Assert.notNull(property, String.format("Entity class [%s] has no persistent property named [%s]",
|
||||
entity.getType().getName(), mapping.getPropertyName()));
|
||||
entity.getType().getName(), mapping.getPropertyName()));
|
||||
|
||||
boolean forceQuote = Boolean.valueOf(mapping.getForceQuote());
|
||||
|
||||
@@ -259,7 +259,7 @@ public class BasicCassandraMappingContext
|
||||
protected <T> CassandraPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
|
||||
UserDefinedType userDefinedType = AnnotatedElementUtils.findMergedAnnotation(
|
||||
typeInformation.getType(), UserDefinedType.class);
|
||||
typeInformation.getType(), UserDefinedType.class);
|
||||
|
||||
CassandraPersistentEntity<T> entity;
|
||||
|
||||
@@ -302,13 +302,13 @@ public class BasicCassandraMappingContext
|
||||
|
||||
@Override
|
||||
public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
|
||||
CassandraPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
CassandraPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
return createPersistentProperty(field, descriptor, owner, (CassandraSimpleTypeHolder) simpleTypeHolder);
|
||||
}
|
||||
|
||||
public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
|
||||
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
|
||||
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
return new BasicCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder, userTypeResolver);
|
||||
}
|
||||
@@ -389,24 +389,20 @@ public class BasicCassandraMappingContext
|
||||
public void doWithPersistentProperty(CassandraPersistentProperty primaryKeyProperty) {
|
||||
if (primaryKeyProperty.isPartitionKeyColumn()) {
|
||||
specification.partitionKeyColumn(primaryKeyProperty.getColumnName(),
|
||||
getDataType(primaryKeyProperty));
|
||||
}
|
||||
else { // it's a cluster column
|
||||
getDataType(primaryKeyProperty));
|
||||
} else { // it's a cluster column
|
||||
specification.clusteredKeyColumn(primaryKeyProperty.getColumnName(),
|
||||
getDataType(primaryKeyProperty), primaryKeyProperty.getPrimaryKeyOrdering());
|
||||
getDataType(primaryKeyProperty), primaryKeyProperty.getPrimaryKeyOrdering());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (property.isIdProperty() || property.isPartitionKeyColumn()) {
|
||||
specification.partitionKeyColumn(property.getColumnName(), getDataType(property));
|
||||
}
|
||||
else if (property.isClusterKeyColumn()) {
|
||||
} else if (property.isClusterKeyColumn()) {
|
||||
specification.clusteredKeyColumn(property.getColumnName(), getDataType(property),
|
||||
property.getPrimaryKeyOrdering());
|
||||
}
|
||||
else {
|
||||
property.getPrimaryKeyOrdering());
|
||||
} else {
|
||||
specification.column(property.getColumnName(), getDataType(property));
|
||||
}
|
||||
}
|
||||
@@ -434,8 +430,12 @@ public class BasicCassandraMappingContext
|
||||
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(CassandraPersistentProperty property) {
|
||||
specification.field(property.getColumnName(), getDataType(property));
|
||||
public void doWithPersistentProperty(final CassandraPersistentProperty property) {
|
||||
|
||||
specification.field(
|
||||
property.getColumnName(),
|
||||
getDataTypeWithUserTypeFactory(property, UserDataTypeProvider.Fake)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -470,6 +470,10 @@ public class BasicCassandraMappingContext
|
||||
@Override
|
||||
public DataType getDataType(CassandraPersistentProperty property) {
|
||||
|
||||
return getDataTypeWithUserTypeFactory(property, UserDataTypeProvider.Simple);
|
||||
}
|
||||
|
||||
private DataType getDataTypeWithUserTypeFactory(CassandraPersistentProperty property, UserDataTypeProvider userDataTypeProvider) {
|
||||
if (property.isCompositePrimaryKey()) {
|
||||
return property.getDataType();
|
||||
}
|
||||
@@ -478,10 +482,12 @@ public class BasicCassandraMappingContext
|
||||
return property.getDataType();
|
||||
}
|
||||
|
||||
CassandraPersistentEntity<?> persistentEntity = getPersistentEntity(property.getType());
|
||||
CassandraPersistentEntity<?> persistentEntity = getPersistentEntity(property.getActualType());
|
||||
|
||||
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
|
||||
return persistentEntity.getUserType();
|
||||
|
||||
DataType elementType = getUserDataType(property, userDataTypeProvider, persistentEntity);
|
||||
if (elementType != null) return elementType;
|
||||
}
|
||||
|
||||
if (customConversions.hasCustomWriteTarget(property.getType())) {
|
||||
@@ -507,13 +513,33 @@ public class BasicCassandraMappingContext
|
||||
return property.getDataType();
|
||||
}
|
||||
|
||||
private DataType getUserDataType(CassandraPersistentProperty property, UserDataTypeProvider userDataTypeProvider, CassandraPersistentEntity<?> persistentEntity) {
|
||||
DataType elementType = userDataTypeProvider.get(persistentEntity);
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
|
||||
if (Set.class.isAssignableFrom(property.getType())) {
|
||||
return DataType.set(elementType);
|
||||
}
|
||||
|
||||
if (List.class.isAssignableFrom(property.getType())) {
|
||||
return DataType.list(elementType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!property.isCollectionLike() && !property.isMapLike()) {
|
||||
return elementType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#getDataType(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public DataType getDataType(Class<?> type) {
|
||||
return (customConversions.hasCustomWriteTarget(type)
|
||||
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type));
|
||||
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -530,4 +556,44 @@ public class BasicCassandraMappingContext
|
||||
public boolean contains(Class<?> type) {
|
||||
return entitiesByType.containsKey(type);
|
||||
}
|
||||
|
||||
enum UserDataTypeProvider {
|
||||
|
||||
Simple {
|
||||
@Override
|
||||
public DataType get(CassandraPersistentEntity<?> entity) {
|
||||
return entity.getUserType();
|
||||
}
|
||||
},
|
||||
|
||||
Fake {
|
||||
@Override
|
||||
public DataType get(CassandraPersistentEntity<?> entity) {
|
||||
return new FakeUserType(entity.getTableName());
|
||||
}
|
||||
};
|
||||
|
||||
abstract DataType get(CassandraPersistentEntity<?> entity);
|
||||
}
|
||||
|
||||
|
||||
static class FakeUserType extends DataType {
|
||||
|
||||
private final CqlIdentifier type;
|
||||
|
||||
protected FakeUserType(CqlIdentifier type) {
|
||||
super(Name.UDT);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFrozen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("frozen<%s>", type.toCql());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -39,8 +40,9 @@ import com.datastax.driver.core.UserType;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CassandraPersistentEntitySchemaCreator}.
|
||||
*
|
||||
* @author Mark Paluch.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CassandraPersistentEntitySchemaCreatorUnitTests {
|
||||
@@ -66,34 +68,87 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
|
||||
when(adminOperations.getCqlOperations()).thenReturn(operations);
|
||||
}
|
||||
|
||||
@Test // DATACASS-172
|
||||
public void shouldCreateTypesInOrder() {
|
||||
@Test
|
||||
public void createsCorrectTypeForSimpleTypes(){
|
||||
|
||||
context.getPersistentEntity(MoonType.class);
|
||||
context.getPersistentEntity(PlanetType.class);
|
||||
context.getPersistentEntity(UniverseType.class);
|
||||
|
||||
when(metadata.getUserType("universetype")).thenReturn(universetype);
|
||||
when(metadata.getUserType("moontype")).thenReturn(moontype);
|
||||
CassandraPersistentEntitySchemaCreator schemaCreator =
|
||||
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
|
||||
|
||||
schemaCreator.createUserTypes(false);
|
||||
|
||||
verifyTypesGetCreatedInOrderFor(
|
||||
"universetype",
|
||||
"moontype"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsCorrectTypeForSets(){
|
||||
|
||||
context.getPersistentEntity(PlanetType.class);
|
||||
|
||||
|
||||
|
||||
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context,
|
||||
adminOperations);
|
||||
|
||||
schemaCreator.createUserTypes(false);
|
||||
|
||||
verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
|
||||
verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
|
||||
verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
|
||||
verify(operations).execute(matches("CREATE TYPE planettype .* set<.*moontype>.*"));
|
||||
|
||||
verifyTypesGetCreatedInOrderFor(
|
||||
"universetype",
|
||||
"moontype",
|
||||
"planettype"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsCorrectTypeForLists(){
|
||||
context.getPersistentEntity(SpaceAgencyType.class);
|
||||
|
||||
CassandraPersistentEntitySchemaCreator schemaCreator =
|
||||
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
|
||||
|
||||
schemaCreator.createUserTypes(false);
|
||||
|
||||
verify(operations).execute(matches("CREATE TYPE spaceagencytype .* list<.*astronauttype>.*"));
|
||||
|
||||
verifyTypesGetCreatedInOrderFor(
|
||||
"astronauttype",
|
||||
"spaceagencytype"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsCorrectTypesForNestedTypes(){
|
||||
|
||||
context.getPersistentEntity(PlanetType.class);
|
||||
|
||||
CassandraPersistentEntitySchemaCreator schemaCreator =
|
||||
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
|
||||
|
||||
schemaCreator.createUserTypes(false);
|
||||
|
||||
verifyTypesGetCreatedInOrderFor(
|
||||
"universetype",
|
||||
"moontype",
|
||||
"planettype"
|
||||
);
|
||||
}
|
||||
|
||||
private void verifyTypesGetCreatedInOrderFor(String ... typenames) {
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(operations);
|
||||
|
||||
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
|
||||
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
|
||||
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
|
||||
for (String typename : typenames) {
|
||||
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE " + typename));
|
||||
}
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class UniverseType {
|
||||
String name;
|
||||
}
|
||||
@@ -110,4 +165,14 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
|
||||
Set<MoonType> moons;
|
||||
UniverseType universeType;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
static class AstronautType {
|
||||
String name;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
static class SpaceAgencyType {
|
||||
List<AstronautType> astronauts;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user