DATACASS-424 - Upgrade to Cassandra driver 3.2.0.

Enforce frozen user types in collection types when creating tables and frozen user types when referencing a user type from another type. Previously, the driver returned frozen user-types and no interaction from our side was required. Move user-type specifics to UserTypeUtil.

Introduce compatibility code within the tests to build against different driver versions.
This commit is contained in:
Mark Paluch
2017-04-19 11:06:42 +02:00
parent ca32108d8b
commit dfc53b635a
6 changed files with 278 additions and 45 deletions

View File

@@ -91,7 +91,7 @@
<build.cassandra.ssl_storage_port>17001</build.cassandra.ssl_storage_port>
<build.cassandra.storage_port>17000</build.cassandra.storage_port>
<cassandra.version>3.9</cassandra.version>
<cassandra-driver.version>3.1.3</cassandra-driver.version>
<cassandra-driver.version>3.2.0</cassandra-driver.version>
<dist.id>spring-data-cassandra</dist.id>
<el.version>1.0</el.version>
<failsafe.version>2.16</failsafe.version>

View File

@@ -71,7 +71,7 @@ public class CreateUserTypeCqlGeneratorIntegrationTests extends AbstractKeyspace
assertThat(address.getFieldNames()).contains("zip", "city");
}
@Test // DATACASS-172
@Test // DATACASS-172, DATACASS-424
public void createNestedUserType() {
CreateUserTypeSpecification addressSpec = CreateUserTypeSpecification //
@@ -89,6 +89,12 @@ public class CreateUserTypeCqlGeneratorIntegrationTests extends AbstractKeyspace
.name("person").ifNotExists().field("address", address) //
.field("city", DataType.varchar());
session.execute(toCql(personSpec));
// Cassandra driver compatibility code: driver 3.0.x for frozen UDT in UDT types.
String cql = toCql(personSpec);
if (!cql.contains("frozen<") && !cql.contains(".address>")) {
cql = cql.replaceAll("address .*\\.address", "address frozen<address>");
}
session.execute(cql);
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.mapping.UserTypeUtil.FrozenLiteralDataType;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.AbstractMappingContext;
@@ -376,13 +377,13 @@ public class BasicCassandraMappingContext
entity.getPersistentProperties().filter((property) -> !property.isCompositePrimaryKey()).forEach(property -> {
if (property.isIdProperty() || property.isPartitionKeyColumn()) {
specification.partitionKeyColumn(property.getColumnName(), getDataType(property));
specification.partitionKeyColumn(property.getColumnName(),
UserTypeUtil.potentiallyFreeze(getDataType(property)));
} else if (property.isClusterKeyColumn()) {
specification.clusteredKeyColumn(property.getColumnName(), getDataType(property),
property.getPrimaryKeyOrdering());
specification.clusteredKeyColumn(property.getColumnName(),
UserTypeUtil.potentiallyFreeze(getDataType(property)), property.getPrimaryKeyOrdering());
} else {
specification.column(property.getColumnName(), getDataType(property));
specification.column(property.getColumnName(), UserTypeUtil.potentiallyFreeze(getDataType(property)));
}
});
@@ -575,36 +576,4 @@ public class BasicCassandraMappingContext
*/
abstract DataType getDataType(CassandraPersistentEntity<?> entity);
}
/**
* @author Jens Schauder
* @since 1.5.1
*/
static class FrozenLiteralDataType extends DataType {
private final CqlIdentifier type;
protected FrozenLiteralDataType(CqlIdentifier type) {
super(Name.UDT);
this.type = type;
}
/* (non-Javadoc)
* @see com.datastax.driver.core.DataType#isFrozen()
*/
@Override
public boolean isFrozen() {
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("frozen<%s>", type.toCql());
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2017 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.mapping;
import static org.springframework.cassandra.core.cql.CqlIdentifier.*;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.util.Assert;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.CollectionType;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.UserType;
/**
* {@link com.datastax.driver.core.UserType} utility methods. Mainly for internal use within the framework.
*
* @author Mark Paluch
* @since 2.0
*/
class UserTypeUtil {
/**
* Potentially create a frozen variant of {@code dataType}. Frozen types are required for nested UDTs (a user-type
* referencing another user-type) or UDTs within collection types.
*
* @param dataType must not be {@literal null}.
* @return the potentially frozen {@link DataType}.
*/
static DataType potentiallyFreeze(DataType dataType) {
Assert.notNull(dataType, "DataType must not be null");
if (dataType.getName() == Name.LIST && dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
DataType typeArgument = collectionType.getTypeArguments().get(0);
if (typeArgument instanceof CollectionType || isNonFrozenUdt(typeArgument)) {
return DataType.list(potentiallyFreeze(typeArgument), collectionType.isFrozen());
}
}
if (dataType.getName() == Name.SET && dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
DataType typeArgument = collectionType.getTypeArguments().get(0);
if (typeArgument instanceof CollectionType || isNonFrozenUdt(typeArgument)) {
return DataType.set(potentiallyFreeze(typeArgument), collectionType.isFrozen());
}
}
if (dataType.getName() == Name.MAP && dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
DataType keyType = collectionType.getTypeArguments().get(0);
DataType valueType = collectionType.getTypeArguments().get(1);
if (keyType instanceof CollectionType || valueType instanceof CollectionType || isNonFrozenUdt(keyType)
|| isNonFrozenUdt(valueType)) {
return DataType.map(potentiallyFreeze(keyType), potentiallyFreeze(valueType), collectionType.isFrozen());
}
}
return isNonFrozenUdt(dataType) ? new FrozenLiteralDataType(getTypeName(dataType)) : dataType;
}
private static CqlIdentifier getTypeName(DataType dataType) {
if (dataType instanceof UserType) {
return CqlIdentifier.cqlId(((UserType) dataType).getTypeName());
}
return cqlId(dataType.asFunctionParameterString());
}
private static boolean isNonFrozenUdt(DataType dataType) {
return dataType.getName() == Name.UDT && !dataType.isFrozen();
}
/**
* @author Jens Schauder
* @since 1.5.1
*/
static class FrozenLiteralDataType extends DataType {
private final CqlIdentifier type;
FrozenLiteralDataType(CqlIdentifier type) {
super(Name.UDT);
this.type = type;
}
/* (non-Javadoc)
* @see com.datastax.driver.core.DataType#isFrozen()
*/
@Override
public boolean isFrozen() {
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("frozen<%s>", type.toCql());
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2017 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.mapping;
import static org.assertj.core.api.Assertions.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.UserType;
/**
* Integration tests for creation of UDT types through {@link BasicCassandraMappingContext}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CreateUserTypeIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
@Configuration
public static class Config extends IntegrationTestConfig {
@Bean
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setInitialEntitySet(new HashSet<>(Arrays.asList(Car.class, Engine.class, Manufacturer.class)));
CustomConversions customConversions = customConversions();
mappingContext.setCustomConversions(customConversions);
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster().getObject(), getKeyspaceName()));
return mappingContext;
}
}
@Autowired Session session;
@Test // DATACASS-424
public void shouldCreateUserTypes() {
KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
Collection<UserType> userTypes = keyspace.getUserTypes();
assertThat(userTypes).extracting("typeName").contains("engine", "manufacturer");
}
@Table
@Getter
@AllArgsConstructor
private static class Car {
@Id String id;
Engine engine;
}
@UserDefinedType
@Getter
@AllArgsConstructor
private static class Engine {
Manufacturer manufacturer;
List<Manufacturer> alternative;
}
@UserDefinedType
@Getter
@AllArgsConstructor
private static class Manufacturer {
String name;
}
}

View File

@@ -23,6 +23,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.util.Arrays;
@@ -368,17 +369,36 @@ public class StringBasedCassandraQueryUnitTests {
}
}
@SuppressWarnings("unchecked")
private UserType createUserType(String typeName, Collection<Field> fields) {
try {
Constructor<UserType> constructor = UserType.class.getDeclaredConstructor(String.class, String.class,
Collection.class, ProtocolVersion.class, CodecRegistry.class);
constructor.setAccessible(true);
return constructor.newInstance(typeName, typeName, fields, ProtocolVersion.NEWEST_SUPPORTED,
CodecRegistry.DEFAULT_INSTANCE);
Constructor<UserType>[] declaredConstructors = (Constructor[]) UserType.class.getDeclaredConstructors();
for (Constructor<UserType> constructor : declaredConstructors) {
if (Modifier.isPrivate(constructor.getModifiers())) {
continue;
}
constructor.setAccessible(true);
if (constructor.getParameterCount() == 5) {
// Cassandra driver 3.0.x - 3.1.x
return constructor.newInstance(typeName, typeName, fields, ProtocolVersion.NEWEST_SUPPORTED,
CodecRegistry.DEFAULT_INSTANCE);
}
// Cassandra driver 3.2.x
return constructor.newInstance(typeName, typeName, false, fields, ProtocolVersion.NEWEST_SUPPORTED,
CodecRegistry.DEFAULT_INSTANCE);
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
throw new IllegalStateException("No suitable constructor found");
}
private interface SampleRepository extends Repository<Person, String> {