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:
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user