From 580adfb5c1986e5bf61ba50b97ceb79b45f92de7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 30 Jun 2016 15:04:39 +0200 Subject: [PATCH] DATACASS-206 - Fix delete using custom column names. We now use the specified column name from the entity annotation to construct the where clause in a delete statement. Original pull request: #73. --- spring-cql/src/test/resources/logback.xml | 2 + .../convert/MappingCassandraConverter.java | 8 +- .../MappingCassandraConverterUnitTests.java | 78 ++++++++++++++++--- .../data/cassandra/domain/UserToken.java | 44 +++++++++++ .../CassandraOperationsIntegrationTests.java | 69 +++++++++++++++- 5 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/UserToken.java diff --git a/spring-cql/src/test/resources/logback.xml b/spring-cql/src/test/resources/logback.xml index bcd2ae87e..77ebbd2af 100644 --- a/spring-cql/src/test/resources/logback.xml +++ b/spring-cql/src/test/resources/logback.xml @@ -9,6 +9,8 @@ + + diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java index ae9968dc7..d4a64933c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/MappingCassandraConverter.java @@ -358,7 +358,13 @@ public class MappingCassandraConverter extends AbstractCassandraConverter if (id instanceof MapId) { for (Map.Entry entry : ((MapId) id).entrySet()) { - where.and(QueryBuilder.eq(entry.getKey(), entry.getValue())); + + CassandraPersistentProperty persistentProperty = entity.getPersistentProperty(entry.getKey()); + if (persistentProperty != null) { + where.and(QueryBuilder.eq(persistentProperty.getColumnName().toCql(), entry.getValue())); + } else { + where.and(QueryBuilder.eq(entry.getKey(), entry.getValue())); + } } return; } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java index a17d0fa51..cfdb577ee 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java @@ -33,11 +33,13 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -52,6 +54,7 @@ import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.core.SpringVersion; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.data.cassandra.RowMockUtil; +import org.springframework.data.cassandra.domain.UserToken; import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraType; @@ -69,6 +72,7 @@ import com.datastax.driver.core.Row; import com.datastax.driver.core.querybuilder.Assignment; import com.datastax.driver.core.querybuilder.BuiltStatement; import com.datastax.driver.core.querybuilder.Clause; +import com.datastax.driver.core.querybuilder.Delete; import com.datastax.driver.core.querybuilder.Delete.Where; import com.datastax.driver.core.querybuilder.Insert; import com.datastax.driver.core.querybuilder.QueryBuilder; @@ -742,6 +746,45 @@ public class MappingCassandraConverterUnitTests { assertThat(getAssignmentValues(update), contains((Object) LocalDate.fromYearMonthDay(2010, 7, 4))); } + /** + * @see DATACASS-206 + */ + @Test + public void updateShouldUseSpecifiedColumnNames() { + + UserToken userToken = new UserToken(); + userToken.setUserId(UUID.randomUUID()); + userToken.setToken(UUID.randomUUID()); + userToken.setAdminComment("admin comment"); + userToken.setUserComment("user comment"); + + Update update = QueryBuilder.update("table"); + mappingCassandraConverter.write(userToken, update); + + assertThat(getAssignments(update), hasEntry("admincomment", (Object) "admin comment")); + assertThat(getAssignments(update), hasEntry("user_comment", (Object) "user comment")); + + assertThat(getWherePredicates(update), hasEntry("user_id", (Object) userToken.getUserId())); + } + + /** + * @see DATACASS-206 + */ + @Test + public void deleteShouldUseSpecifiedColumnNames() { + + UserToken userToken = new UserToken(); + userToken.setUserId(UUID.randomUUID()); + userToken.setToken(UUID.randomUUID()); + userToken.setAdminComment("admin comment"); + userToken.setUserComment("user comment"); + + Delete delete = QueryBuilder.delete().from("table"); + mappingCassandraConverter.write(userToken, delete.where()); + + assertThat(getWherePredicates(delete), hasEntry("user_id", (Object) userToken.getUserId())); + } + @SuppressWarnings("unchecked") private List getListValue(Insert statement) { List values = getValues(statement); @@ -774,30 +817,47 @@ public class MappingCassandraConverterUnitTests { } @SuppressWarnings("unchecked") - private List getAssignmentValues(Update statement) { + private Collection getAssignmentValues(Update statement) { + return getAssignments(statement).values(); + } - List result = new ArrayList(); + @SuppressWarnings("unchecked") + private Map getAssignments(Update statement) { + + Map result = new LinkedHashMap(); Assignments assignments = (Assignments) ReflectionTestUtils.getField(statement, "assignments"); List listOfAssignments = (List) ReflectionTestUtils.getField(assignments, "assignments"); for (Assignment assignment : listOfAssignments) { - result.add(ReflectionTestUtils.getField(assignment, "value")); + result.put(assignment.getColumnName(), ReflectionTestUtils.getField(assignment, "value")); } return result; } - private List getWhereValues(Update statement) { - return getWhereValues(statement.where()); + private Collection getWhereValues(Update update) { + return getWherePredicates(update.where()).values(); } - private List getWhereValues(BuiltStatement where) { + private Collection getWhereValues(BuiltStatement where) { + return getWherePredicates(where).values(); + } - List result = new ArrayList(); + private Map getWherePredicates(Update statement) { + return getWherePredicates(statement.where()); + } + + private Map getWherePredicates(Delete statement) { + return getWherePredicates(statement.where()); + } + + private Map getWherePredicates(BuiltStatement where) { + + Map result = new LinkedHashMap(); List clauses = (List) ReflectionTestUtils.getField(where, "clauses"); for (Clause clause : clauses) { - result.add(ReflectionTestUtils.getField(clause, "value")); + result.put((String) ReflectionTestUtils.invokeMethod(clause, "name"), ReflectionTestUtils.getField(clause, "value")); } return result; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/UserToken.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/UserToken.java new file mode 100644 index 000000000..9104c7294 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/UserToken.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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.domain; + +import java.util.UUID; + +import org.springframework.cassandra.core.PrimaryKeyType; +import org.springframework.data.cassandra.mapping.CassandraType; +import org.springframework.data.cassandra.mapping.Column; +import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.mapping.Table; + +import com.datastax.driver.core.DataType.Name; + +import lombok.Data; + +/** + * @author Mark Paluch + */ +@Table("user_tokens") +@Data +public class UserToken { + + @PrimaryKeyColumn(name = "user_id", type = PrimaryKeyType.PARTITIONED, + ordinal = 0) @CassandraType(type = Name.UUID) private UUID userId; + @PrimaryKeyColumn(name = "auth_token", type = PrimaryKeyType.CLUSTERED, + ordinal = 1) @CassandraType(type = Name.UUID) private UUID token; + + @Column("user_comment") String userComment; + String adminComment; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java index 1b5ecf5f8..857fe0f1b 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java @@ -36,6 +36,8 @@ import org.springframework.cassandra.core.RetryPolicy; import org.springframework.cassandra.core.WriteOptions; import org.springframework.context.annotation.Configuration; import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.domain.UserToken; +import org.springframework.data.cassandra.repository.support.BasicMapId; import org.springframework.data.cassandra.test.integration.simpletons.Book; import org.springframework.data.cassandra.test.integration.simpletons.BookCondition; import org.springframework.data.cassandra.test.integration.simpletons.BookReference; @@ -46,6 +48,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.utils.UUIDs; /** * Integration tests for {@link CassandraOperations}. @@ -63,12 +66,11 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed @Override public String[] getEntityBasePackages() { - return new String[] { Book.class.getPackage().getName() }; + return new String[] { Book.class.getPackage().getName(), UserToken.class.getPackage().getName() }; } } - @Autowired - CassandraOperations template; + @Autowired CassandraOperations template; @Before public void before() { @@ -749,13 +751,72 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed assertThat(selectedBooks.get(0), is(instanceOf(Book.class))); } + /** + * @see DATACASS-206 + */ + @Test + public void shouldUseSpecifiedColumnNamesForSingleEntityModifyingOperations() { + + UserToken userToken = new UserToken(); + userToken.setToken(UUIDs.startOf(System.currentTimeMillis())); + userToken.setUserId(UUIDs.endOf(System.currentTimeMillis())); + + template.insert(userToken); + + userToken.setUserComment("comment"); + template.update(userToken); + + UserToken loaded = template.selectOneById(UserToken.class, + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); + + assertThat(loaded, is(notNullValue())); + assertThat(loaded.getUserComment(), is(equalTo("comment"))); + + template.delete(userToken); + + UserToken loadAfterDelete = template.selectOneById(UserToken.class, + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); + + assertThat(loadAfterDelete, is(nullValue())); + } + + /** + * @see DATACASS-206 + */ + @Test + public void shouldUseSpecifiedColumnNamesForMultiEntityModifyingOperations() { + + UserToken userToken = new UserToken(); + userToken.setToken(UUIDs.startOf(System.currentTimeMillis())); + userToken.setUserId(UUIDs.endOf(System.currentTimeMillis())); + + template.insert(Arrays.asList(userToken)); + + userToken.setUserComment("comment"); + template.update(Arrays.asList(userToken)); + + UserToken loaded = template.selectOneById(UserToken.class, + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); + + assertThat(loaded, is(notNullValue())); + assertThat(loaded.getUserComment(), is(equalTo("comment"))); + + template.delete(Arrays.asList(userToken)); + + UserToken loadAfterDelete = template.selectOneById(UserToken.class, + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); + + assertThat(loadAfterDelete, is(nullValue())); + } + WriteOptions newWriteOptions(ConsistencyLevel consistencyLevel, RetryPolicy retryPolicy, int timeToLive) { return new WriteOptions(consistencyLevel, retryPolicy, timeToLive); } Iterable toIterable(final Iterator iterator) { return new Iterable() { - @Override public Iterator iterator() { + @Override + public Iterator iterator() { return iterator; } };