diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBean.java index 3ae3c0029..c3f04dae4 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors + * Copyright 2013-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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.cassandra.config; import org.springframework.cassandra.config.CassandraCqlSessionFactoryBean; @@ -21,6 +20,7 @@ import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.core.CassandraAdminOperations; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaCreator; +import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaDropper; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.util.Assert; @@ -97,8 +97,17 @@ public class CassandraSessionFactoryBean extends CassandraCqlSessionFactoryBean CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator( getMappingContext(), getCassandraAdminOperations()); - schemaCreator.createUserTypes(drop, dropUnused, ifNotExists); - schemaCreator.createTables(drop, dropUnused, ifNotExists); + if (drop) { + + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper( + getMappingContext(), getCassandraAdminOperations()); + + schemaDropper.dropTables(dropUnused); + schemaDropper.dropUserTypes(dropUnused); + } + + schemaCreator.createUserTypes(ifNotExists); + schemaCreator.createTables(ifNotExists); } /* (non-Javadoc) */ diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java index 87e090acf..9b1fa858a 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-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. @@ -25,10 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.datastax.driver.core.KeyspaceMetadata; -import com.datastax.driver.core.TableMetadata; -import com.datastax.driver.core.UserType; - import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; import org.springframework.cassandra.core.cql.generator.CreateUserTypeCqlGenerator; @@ -42,7 +38,7 @@ import org.springframework.util.Assert; /** * Schema creation support for Cassandra based on {@link CassandraMappingContext} and {@link CassandraPersistentEntity}. - * This class generates CQL to drop, recreate and create user types (UDT) and tables. + * This class generates CQL to create user types (UDT) and tables. * * @author Mark Paluch * @since 1.5 @@ -73,18 +69,11 @@ public class CassandraPersistentEntitySchemaCreator { } /** - * Create user types. Can drop types and drop unused types. + * Create tables from types known to {@link CassandraMappingContext}. * - * @param dropTables {@literal true} to drop tables before creation. - * @param dropUnused {@literal true} to drop unused tables before creation. Table usage is determined by existing - * table mappings. * @param ifNotExists {@literal true} to create tables using {@code IF NOT EXISTS}. */ - public void createTables(boolean dropTables, boolean dropUnused, boolean ifNotExists) { - - if (dropTables) { - dropTables(dropUnused); - } + public void createTables(boolean ifNotExists) { List specifications = createTableSpecifications(ifNotExists); @@ -97,7 +86,7 @@ public class CassandraPersistentEntitySchemaCreator { protected List createTableSpecifications(boolean ifNotExists) { Collection> entities = new ArrayList>( - mappingContext.getTableEntities()); + mappingContext.getTableEntities()); List specifications = new ArrayList(); @@ -108,32 +97,12 @@ public class CassandraPersistentEntitySchemaCreator { return specifications; } - /* (non-Javadoc) */ - private void dropTables(boolean dropUnused) { - - KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata(); - - for (TableMetadata table : keyspaceMetadata.getTables()) { - if (dropUnused || mappingContext.usesTable(table)) { - cassandraAdminOperations.dropTable(CqlIdentifier.cqlId(table.getName())); - } - } - } - /** - * Create user types. Can drop types and drop unused types. + * Create user types from types known to {@link CassandraMappingContext}. * - * @param dropUserTypes {@literal true} to drop types before creation. - * @param dropUnused {@literal true} to drop unused types before creation. Type usage is determined from existing - * mapped {@link org.springframework.data.cassandra.mapping.UserDefinedType}s and UDT names on field - * specifications. * @param ifNotExists {@literal true} to create types using {@code IF NOT EXISTS}. */ - public void createUserTypes(boolean dropUserTypes, boolean dropUnused, boolean ifNotExists) { - - if (dropUserTypes) { - dropUserTypes(dropUnused); - } + public void createUserTypes(boolean ifNotExists) { List specifications = createUserTypeSpecifications(ifNotExists); @@ -149,8 +118,7 @@ public class CassandraPersistentEntitySchemaCreator { mappingContext.getUserDefinedTypeEntities()); // TODO simplify by using Java 8 Streams API in 2.0.x - Map> byTableName = - new HashMap>(); + Map> byTableName = new HashMap>(); for (CassandraPersistentEntity entity : entities) { byTableName.put(entity.getTableName(), entity); @@ -172,8 +140,8 @@ public class CassandraPersistentEntitySchemaCreator { for (CqlIdentifier identifier : ordered) { if (created.add(identifier)) { - specifications.add(mappingContext.getCreateUserTypeSpecificationFor(byTableName.get(identifier)) - .ifNotExists(ifNotExists)); + specifications.add( + mappingContext.getCreateUserTypeSpecificationFor(byTableName.get(identifier)).ifNotExists(ifNotExists)); } } } @@ -197,27 +165,4 @@ public class CassandraPersistentEntitySchemaCreator { } }); } - - /* (non-Javadoc) */ - private void dropUserTypes(boolean dropUnused) { - - KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata(); - - Collection> userDefinedTypeEntities = mappingContext.getUserDefinedTypeEntities(); - Set canRecreate = new HashSet(); - - for (CassandraPersistentEntity userDefinedTypeEntity : userDefinedTypeEntities) { - canRecreate.add(userDefinedTypeEntity.getTableName()); - } - - for (UserType userType : keyspaceMetadata.getUserTypes()) { - CqlIdentifier identifier = CqlIdentifier.cqlId(userType.getTypeName()); - - if (canRecreate.contains(identifier)) { - cassandraAdminOperations.dropUserType(identifier); - } else if (dropUnused && !mappingContext.usesUserType(userType)) { - cassandraAdminOperations.dropUserType(identifier); - } - } - } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropper.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropper.java new file mode 100644 index 000000000..8b05fda65 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropper.java @@ -0,0 +1,107 @@ +/* + * 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.core; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.util.Assert; + +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.UserType; + +/** + * Schema drop support for Cassandra based on {@link CassandraMappingContext} and {@link CassandraPersistentEntity}. + * This class generates CQL to drop user types (UDT) and tables. + * + * @author Mark Paluch + * @since 1.5 + * @see org.springframework.data.cassandra.mapping.Table + * @see org.springframework.data.cassandra.mapping.UserDefinedType + * @see org.springframework.data.cassandra.mapping.CassandraType + */ +public class CassandraPersistentEntitySchemaDropper { + + private final CassandraAdminOperations cassandraAdminOperations; + private final CassandraMappingContext mappingContext; + + /** + * Creates a new {@link CassandraPersistentEntitySchemaDropper} for the given {@link CassandraMappingContext} and + * {@link CassandraAdminOperations}. + * + * @param mappingContext must not be {@literal null}. + * @param cassandraAdminOperations must not be {@literal null}. + */ + public CassandraPersistentEntitySchemaDropper(CassandraMappingContext mappingContext, + CassandraAdminOperations cassandraAdminOperations) { + + Assert.notNull(cassandraAdminOperations, "CassandraAdminOperations must not be null"); + Assert.notNull(mappingContext, "CassandraMappingContext must not be null"); + + this.cassandraAdminOperations = cassandraAdminOperations; + this.mappingContext = mappingContext; + } + + /** + * Drop tables that exist in the keyspace. + * + * @param dropUnused {@literal true} to drop unused tables. Table usage is determined by existing table mappings. + */ + public void dropTables(boolean dropUnused) { + + KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata(); + + for (TableMetadata table : keyspaceMetadata.getTables()) { + if (dropUnused || mappingContext.usesTable(table)) { + cassandraAdminOperations.dropTable(CqlIdentifier.cqlId(table.getName())); + } + } + } + + /** + * Drop user types that exist in the keyspace. + * + * @param dropUnused {@literal true} to drop unused types before creation. Type usage is determined from existing + * mapped {@link org.springframework.data.cassandra.mapping.UserDefinedType}s and UDT names on field + * specifications. + */ + public void dropUserTypes(boolean dropUnused) { + + KeyspaceMetadata keyspaceMetadata = cassandraAdminOperations.getKeyspaceMetadata(); + + Collection> userDefinedTypeEntities = mappingContext.getUserDefinedTypeEntities(); + Set canRecreate = new HashSet(); + + for (CassandraPersistentEntity userDefinedTypeEntity : userDefinedTypeEntities) { + canRecreate.add(userDefinedTypeEntity.getTableName()); + } + + for (UserType userType : keyspaceMetadata.getUserTypes()) { + CqlIdentifier identifier = CqlIdentifier.cqlId(userType.getTypeName()); + + if (canRecreate.contains(identifier)) { + cassandraAdminOperations.dropUserType(identifier); + } else if (dropUnused && !mappingContext.usesUserType(userType)) { + cassandraAdminOperations.dropUserType(identifier); + } + } + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java index f2f93b045..4a552a1b5 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-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. @@ -17,6 +17,8 @@ package org.springframework.data.cassandra.core; import static org.mockito.Mockito.*; +import lombok.Data; + import java.util.Set; import org.junit.Before; @@ -34,8 +36,6 @@ import org.springframework.data.cassandra.mapping.UserTypeResolver; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.UserType; -import lombok.Data; - /** * Unit tests for {@link CassandraPersistentEntitySchemaCreator}. * @@ -48,9 +48,6 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests { @Mock KeyspaceMetadata metadata; @Mock UserType universetype; @Mock UserType moontype; - @Mock UserType manufacturertype; - @Mock UserType biketype; - @Mock UserType tiretype; BasicCassandraMappingContext context = new BasicCassandraMappingContext(); @@ -78,7 +75,7 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests { CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context, operations); - schemaCreator.createUserTypes(false, false, false); + schemaCreator.createUserTypes(false); verify(operations).execute(Mockito.contains("CREATE TYPE universetype")); verify(operations).execute(Mockito.contains("CREATE TYPE moontype")); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropperUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropperUnitTests.java new file mode 100644 index 000000000..7b812ba68 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaDropperUnitTests.java @@ -0,0 +1,181 @@ +/* + * 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.core; + +import static org.mockito.Mockito.*; + +import lombok.Data; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.data.annotation.Id; +import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; +import org.springframework.data.cassandra.mapping.Table; +import org.springframework.data.cassandra.mapping.UserDefinedType; +import org.springframework.data.cassandra.mapping.UserTypeResolver; + +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.TableMetadata; +import com.datastax.driver.core.UserType; + +/** + * Unit tests for {@link CassandraPersistentEntitySchemaDropper}. + * + * @author Mark Paluch. + */ +@SuppressWarnings("unchecked") +@RunWith(MockitoJUnitRunner.class) +public class CassandraPersistentEntitySchemaDropperUnitTests { + + @Mock CassandraAdminOperations operations; + @Mock KeyspaceMetadata metadata; + @Mock UserType universetype; + @Mock UserType moontype; + @Mock UserType planettype; + @Mock TableMetadata person; + @Mock TableMetadata contact; + + BasicCassandraMappingContext context = new BasicCassandraMappingContext(); + + /** + * @see DATACASS-355 + */ + @Before + public void setUp() throws Exception { + + context.setUserTypeResolver(new UserTypeResolver() { + @Override + public UserType resolveType(CqlIdentifier typeName) { + return metadata.getUserType(typeName.toCql()); + } + }); + + when(operations.getKeyspaceMetadata()).thenReturn(metadata); + when(universetype.getTypeName()).thenReturn("universetype"); + when(moontype.getTypeName()).thenReturn("moontype"); + when(planettype.getTypeName()).thenReturn("planettype"); + when(person.getName()).thenReturn("person"); + when(contact.getName()).thenReturn("contact"); + } + + /** + * @see DATACASS-355 + */ + @Test + public void shouldDropTypes() throws Exception { + + context.setInitialEntitySet(new HashSet>(Arrays.asList(MoonType.class, UniverseType.class))); + context.afterPropertiesSet(); + + when(metadata.getUserTypes()).thenReturn(Arrays.asList(universetype, moontype, planettype)); + + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper(context, + operations); + + schemaDropper.dropUserTypes(true); + + verify(operations).dropUserType(CqlIdentifier.cqlId("universetype")); + verify(operations).dropUserType(CqlIdentifier.cqlId("moontype")); + verify(operations).dropUserType(CqlIdentifier.cqlId("planettype")); + verify(operations).getKeyspaceMetadata(); + verifyNoMoreInteractions(operations); + } + + /** + * @see DATACASS-355 + */ + @Test + public void dropUserTypesShouldRetainUnusedTypes() { + + context.setInitialEntitySet(new HashSet>(Arrays.asList(MoonType.class, UniverseType.class))); + context.afterPropertiesSet(); + + when(metadata.getUserTypes()).thenReturn(Arrays.asList(universetype, moontype, planettype)); + + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper(context, + operations); + + schemaDropper.dropUserTypes(false); + + verify(operations).dropUserType(CqlIdentifier.cqlId("universetype")); + verify(operations).dropUserType(CqlIdentifier.cqlId("moontype")); + verify(operations).getKeyspaceMetadata(); + verifyNoMoreInteractions(operations); + } + + /** + * @see DATACASS-355 + */ + @Test + public void shouldDropTables() throws Exception { + + context.setInitialEntitySet(Collections.singleton(Person.class)); + context.afterPropertiesSet(); + + when(metadata.getTables()).thenReturn(Arrays.asList(person, contact)); + + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper(context, + operations); + + schemaDropper.dropTables(true); + + verify(operations).dropTable(CqlIdentifier.cqlId("person")); + verify(operations).dropTable(CqlIdentifier.cqlId("contact")); + verify(operations).getKeyspaceMetadata(); + verifyNoMoreInteractions(operations); + } + + @Test + public void dropTablesShouldRetainUnusedTables() throws Exception { + + context.setInitialEntitySet(Collections.singleton(Person.class)); + context.afterPropertiesSet(); + + when(metadata.getTables()).thenReturn(Arrays.asList(person, contact)); + + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper(context, + operations); + + schemaDropper.dropTables(false); + + verify(operations).dropTable(CqlIdentifier.cqlId("person")); + verify(operations).getKeyspaceMetadata(); + verifyNoMoreInteractions(operations); + } + + @UserDefinedType + @Data + static class UniverseType {} + + @UserDefinedType + static class MoonType {} + + @UserDefinedType + static class PlanetType {} + + @Table + static class Person { + @Id String id; + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java index 74617de52..0ae29e77d 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-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. @@ -31,6 +31,7 @@ import org.springframework.data.cassandra.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaCreator; +import org.springframework.data.cassandra.core.CassandraPersistentEntitySchemaDropper; import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver; @@ -75,9 +76,15 @@ class CassandraOperationsProducer { cassandraTemplate.execute(createKeyspaceSpecification); cassandraTemplate.execute("USE " + KEYSPACE_NAME); - CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(mappingContext, cassandraTemplate); - schemaCreator.createUserTypes(false, false, true); - schemaCreator.createTables(false, false, true); + CassandraPersistentEntitySchemaDropper schemaDropper = new CassandraPersistentEntitySchemaDropper(mappingContext, + cassandraTemplate); + schemaDropper.dropTables(false); + schemaDropper.dropUserTypes(false); + + CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(mappingContext, + cassandraTemplate); + schemaCreator.createUserTypes(false); + schemaCreator.createTables(false); for (CassandraPersistentEntity entity : cassandraTemplate.getConverter().getMappingContext() .getTableEntities()) {