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.
This commit is contained in:
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
<logger name="org.springframework" level="ERROR" />
|
<logger name="org.springframework" level="ERROR" />
|
||||||
<logger name="org.springframework.cassandra" level="ERROR" />
|
<logger name="org.springframework.cassandra" level="ERROR" />
|
||||||
|
<logger name="org.springframework.data.cassandra" level="ERROR" />
|
||||||
|
|
||||||
<logger name="com.datastax" level="ERROR" />
|
<logger name="com.datastax" level="ERROR" />
|
||||||
|
|
||||||
<!-- See https://issues.apache.org/jira/browse/CASSANDRA-8220 -->
|
<!-- See https://issues.apache.org/jira/browse/CASSANDRA-8220 -->
|
||||||
|
|||||||
@@ -358,7 +358,13 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
|||||||
if (id instanceof MapId) {
|
if (id instanceof MapId) {
|
||||||
|
|
||||||
for (Map.Entry<String, Serializable> entry : ((MapId) id).entrySet()) {
|
for (Map.Entry<String, Serializable> 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ import org.springframework.cassandra.core.PrimaryKeyType;
|
|||||||
import org.springframework.core.SpringVersion;
|
import org.springframework.core.SpringVersion;
|
||||||
import org.springframework.core.convert.ConverterNotFoundException;
|
import org.springframework.core.convert.ConverterNotFoundException;
|
||||||
import org.springframework.data.cassandra.RowMockUtil;
|
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.BasicCassandraMappingContext;
|
||||||
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
|
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
|
||||||
import org.springframework.data.cassandra.mapping.CassandraType;
|
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.Assignment;
|
||||||
import com.datastax.driver.core.querybuilder.BuiltStatement;
|
import com.datastax.driver.core.querybuilder.BuiltStatement;
|
||||||
import com.datastax.driver.core.querybuilder.Clause;
|
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.Delete.Where;
|
||||||
import com.datastax.driver.core.querybuilder.Insert;
|
import com.datastax.driver.core.querybuilder.Insert;
|
||||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||||
@@ -742,6 +746,45 @@ public class MappingCassandraConverterUnitTests {
|
|||||||
assertThat(getAssignmentValues(update), contains((Object) LocalDate.fromYearMonthDay(2010, 7, 4)));
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> List<T> getListValue(Insert statement) {
|
private <T> List<T> getListValue(Insert statement) {
|
||||||
List<Object> values = getValues(statement);
|
List<Object> values = getValues(statement);
|
||||||
@@ -774,30 +817,47 @@ public class MappingCassandraConverterUnitTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<Object> getAssignmentValues(Update statement) {
|
private Collection<Object> getAssignmentValues(Update statement) {
|
||||||
|
return getAssignments(statement).values();
|
||||||
|
}
|
||||||
|
|
||||||
List<Object> result = new ArrayList<Object>();
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, Object> getAssignments(Update statement) {
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||||
|
|
||||||
Assignments assignments = (Assignments) ReflectionTestUtils.getField(statement, "assignments");
|
Assignments assignments = (Assignments) ReflectionTestUtils.getField(statement, "assignments");
|
||||||
List<Assignment> listOfAssignments = (List<Assignment>) ReflectionTestUtils.getField(assignments, "assignments");
|
List<Assignment> listOfAssignments = (List<Assignment>) ReflectionTestUtils.getField(assignments, "assignments");
|
||||||
for (Assignment assignment : listOfAssignments) {
|
for (Assignment assignment : listOfAssignments) {
|
||||||
result.add(ReflectionTestUtils.getField(assignment, "value"));
|
result.put(assignment.getColumnName(), ReflectionTestUtils.getField(assignment, "value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Object> getWhereValues(Update statement) {
|
private Collection<Object> getWhereValues(Update update) {
|
||||||
return getWhereValues(statement.where());
|
return getWherePredicates(update.where()).values();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Object> getWhereValues(BuiltStatement where) {
|
private Collection<Object> getWhereValues(BuiltStatement where) {
|
||||||
|
return getWherePredicates(where).values();
|
||||||
|
}
|
||||||
|
|
||||||
List<Object> result = new ArrayList<Object>();
|
private Map<String, Object> getWherePredicates(Update statement) {
|
||||||
|
return getWherePredicates(statement.where());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getWherePredicates(Delete statement) {
|
||||||
|
return getWherePredicates(statement.where());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getWherePredicates(BuiltStatement where) {
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||||
|
|
||||||
List<Clause> clauses = (List<Clause>) ReflectionTestUtils.getField(where, "clauses");
|
List<Clause> clauses = (List<Clause>) ReflectionTestUtils.getField(where, "clauses");
|
||||||
for (Clause clause : clauses) {
|
for (Clause clause : clauses) {
|
||||||
result.add(ReflectionTestUtils.getField(clause, "value"));
|
result.put((String) ReflectionTestUtils.invokeMethod(clause, "name"), ReflectionTestUtils.getField(clause, "value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ import org.springframework.cassandra.core.RetryPolicy;
|
|||||||
import org.springframework.cassandra.core.WriteOptions;
|
import org.springframework.cassandra.core.WriteOptions;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
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.Book;
|
||||||
import org.springframework.data.cassandra.test.integration.simpletons.BookCondition;
|
import org.springframework.data.cassandra.test.integration.simpletons.BookCondition;
|
||||||
import org.springframework.data.cassandra.test.integration.simpletons.BookReference;
|
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.QueryBuilder;
|
||||||
import com.datastax.driver.core.querybuilder.Select;
|
import com.datastax.driver.core.querybuilder.Select;
|
||||||
|
import com.datastax.driver.core.utils.UUIDs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link CassandraOperations}.
|
* Integration tests for {@link CassandraOperations}.
|
||||||
@@ -63,12 +66,11 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getEntityBasePackages() {
|
public String[] getEntityBasePackages() {
|
||||||
return new String[] { Book.class.getPackage().getName() };
|
return new String[] { Book.class.getPackage().getName(), UserToken.class.getPackage().getName() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired CassandraOperations template;
|
||||||
CassandraOperations template;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
@@ -749,13 +751,72 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed
|
|||||||
assertThat(selectedBooks.get(0), is(instanceOf(Book.class)));
|
assertThat(selectedBooks.get(0), is(instanceOf(Book.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://jira.spring.io/browse/DATACASS-206">DATACASS-206</a>
|
||||||
|
*/
|
||||||
|
@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 <a href="https://jira.spring.io/browse/DATACASS-206">DATACASS-206</a>
|
||||||
|
*/
|
||||||
|
@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) {
|
WriteOptions newWriteOptions(ConsistencyLevel consistencyLevel, RetryPolicy retryPolicy, int timeToLive) {
|
||||||
return new WriteOptions(consistencyLevel, retryPolicy, timeToLive);
|
return new WriteOptions(consistencyLevel, retryPolicy, timeToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> Iterable<T> toIterable(final Iterator<T> iterator) {
|
<T> Iterable<T> toIterable(final Iterator<T> iterator) {
|
||||||
return new Iterable<T>() {
|
return new Iterable<T>() {
|
||||||
@Override public Iterator<T> iterator() {
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
return iterator;
|
return iterator;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user