DATACASS-606 - Support LWT for delete operations (DELETE .. IF EXISTS).

We now support lightweight transactions for entity deletion through DeleteOptions.

User user = …

DeleteOptions options = DeleteOptions.builder().withIfExists().build();
boolean applied = template.delete(user, options);
This commit is contained in:
Mark Paluch
2019-01-28 13:47:40 +01:00
parent aa54b8a9a1
commit 005c9a7078
14 changed files with 470 additions and 59 deletions

View File

@@ -113,9 +113,8 @@ public interface AsyncCassandraOperations {
<T> ListenableFuture<List<T>> select(Statement statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities.
*
* A sliced query translates the effective {@link Statement#getFetchSize() fetch size} to the page size.
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities. A sliced
* query translates the effective {@link Statement#getFetchSize() fetch size} to the page size.
*
* @param statement the CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
@@ -230,10 +229,9 @@ public interface AsyncCassandraOperations {
ListenableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class applying {@link Query}.
*
* This overridden method allows users to further refine the selection criteria using a {@link Query} predicate
* to determine how many entities of the given {@link Class type} match the criteria.
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
* to further refine the selection criteria using a {@link Query} predicate to determine how many entities of the
* given {@link Class type} match the criteria.
*
* @param query user-provided count {@link Query} to execute; must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
@@ -246,9 +244,9 @@ public interface AsyncCassandraOperations {
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's
* an instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass}
* or {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's an
* instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
@@ -338,6 +336,20 @@ public interface AsyncCassandraOperations {
*/
ListenableFuture<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link WriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see DeleteOptions#empty()
* @since 2.2
*/
default ListenableFuture<WriteResult> delete(Object entity, DeleteOptions options) throws DataAccessException {
return delete(entity, (QueryOptions) options);
}
/**
* Remove the given object from the table by id.
*

View File

@@ -249,7 +249,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(statement));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return getAsyncCqlOperations().query(statement, (row, rowNum) -> mapper.apply(row));
}
@@ -265,10 +265,10 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
ListenableFuture<ResultSet> resultSet = getAsyncCqlOperations().queryForResultSet(statement);
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(statement));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return new MappingListenableFutureAdapter<>(resultSet,
rs -> QueryUtils.readSlice(rs, (row, rowNum) -> mapper.apply(row), 0, getEffectiveFetchSize(statement)));
rs -> EntityQueryUtils.readSlice(rs, (row, rowNum) -> mapper.apply(row), 0, getEffectiveFetchSize(statement)));
}
/* (non-Javadoc)
@@ -282,7 +282,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
Assert.notNull(entityConsumer, "Entity Consumer must not be empty");
Assert.notNull(entityClass, "Entity type must not be null");
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(statement));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return getAsyncCqlOperations().query(statement, row -> {
entityConsumer.accept(mapper.apply(row));
@@ -504,7 +504,8 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
CqlIdentifier tableName = persistentEntity.getTableName();
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
Insert insert = EntityQueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(),
persistentEntity);
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert));
@@ -533,7 +534,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
Assert.notNull(options, "UpdateOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
Update update = EntityQueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, update));
@@ -562,7 +563,7 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations, Applica
Assert.notNull(options, "QueryOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
Delete delete = EntityQueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
maybeEmitEvent(new BeforeDeleteEvent<>(delete, entity.getClass(), tableName));

View File

@@ -134,9 +134,8 @@ public interface CassandraOperations extends FluentCassandraOperations {
<T> List<T> select(Statement statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities.
*
* A sliced query translates the effective {@link Statement#getFetchSize() fetch size} to the page size.
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities. A sliced
* query translates the effective {@link Statement#getFetchSize() fetch size} to the page size.
*
* @param statement the CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
@@ -257,10 +256,9 @@ public interface CassandraOperations extends FluentCassandraOperations {
long count(Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class applying {@link Query}.
*
* This overridden method allows users to further refine the selection criteria using a {@link Query} predicate
* to determine how many entities of the given {@link Class type} match the criteria.
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
* to further refine the selection criteria using a {@link Query} predicate to determine how many entities of the
* given {@link Class type} match the criteria.
*
* @param query user-defined count {@link Query} to execute; must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
@@ -273,9 +271,9 @@ public interface CassandraOperations extends FluentCassandraOperations {
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's
* an instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass}
* or {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's an
* instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
@@ -365,6 +363,20 @@ public interface CassandraOperations extends FluentCassandraOperations {
*/
WriteResult delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link WriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see DeleteOptions#empty()
* @since 2.2
*/
default WriteResult delete(Object entity, DeleteOptions options) throws DataAccessException {
return delete(entity, (QueryOptions) options);
}
/**
* Remove the given object from the table by id.
*

View File

@@ -258,7 +258,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(statement));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return getCqlOperations().query(statement, (row, rowNum) -> mapper.apply(row));
}
@@ -274,9 +274,10 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
ResultSet resultSet = getCqlOperations().queryForResultSet(statement);
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(statement));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return QueryUtils.readSlice(resultSet, (row, rowNum) -> mapper.apply(row), 0, getEffectiveFetchSize(statement));
return EntityQueryUtils.readSlice(resultSet, (row, rowNum) -> mapper.apply(row), 0,
getEffectiveFetchSize(statement));
}
/* (non-Javadoc)
@@ -291,7 +292,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
ResultSet resultSet = getCqlOperations().queryForResultSet(statement);
return StreamSupport.stream(resultSet.spliterator(), false)
.map(getMapper(entityClass, entityClass, QueryUtils.getTableName(statement)));
.map(getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement)));
}
/* (non-Javadoc)
@@ -551,7 +552,8 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
Insert insert = EntityQueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(),
persistentEntity);
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, insert));
@@ -581,7 +583,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
Assert.notNull(options, "UpdateOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
Update update = EntityQueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, update));
@@ -611,7 +613,7 @@ public class CassandraTemplate implements CassandraOperations, ApplicationEventP
Assert.notNull(options, "QueryOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
Delete delete = EntityQueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
maybeEmitEvent(new BeforeDeleteEvent<>(delete, entity.getClass(), tableName));

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2019 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 lombok.EqualsAndHashCode;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.springframework.data.cassandra.core.cql.WriteOptions;
import org.springframework.lang.Nullable;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.policies.RetryPolicy;
/**
* Extension to {@link WriteOptions} for use with {@code DELETE} operations.
*
* @author Mark Paluch
* @since 2.2
*/
@EqualsAndHashCode(callSuper = true)
public class DeleteOptions extends WriteOptions {
private static final DeleteOptions EMPTY = new DeleteOptionsBuilder().build();
private boolean ifExists;
private DeleteOptions(@Nullable ConsistencyLevel consistencyLevel, @Nullable RetryPolicy retryPolicy,
@Nullable Boolean tracing, @Nullable Integer fetchSize, Duration readTimeout, Duration ttl,
@Nullable Long timestamp, boolean ifExists) {
super(consistencyLevel, retryPolicy, tracing, fetchSize, readTimeout, ttl, timestamp);
this.ifExists = ifExists;
}
/**
* Create a new {@link DeleteOptionsBuilder}.
*
* @return a new {@link DeleteOptionsBuilder}.
*/
public static DeleteOptionsBuilder builder() {
return new DeleteOptionsBuilder();
}
/**
* Create default {@link DeleteOptions}.
*
* @return default {@link DeleteOptions}.
*/
public static DeleteOptions empty() {
return EMPTY;
}
/**
* Create a new {@link DeleteOptionsBuilder} to mutate properties of this {@link DeleteOptions}.
*
* @return a new {@link DeleteOptionsBuilder} initialized with this {@link DeleteOptions}.
*/
@Override
public DeleteOptionsBuilder mutate() {
return new DeleteOptionsBuilder(this);
}
/**
* @return {@literal true} to apply {@code IF EXISTS} to {@code UPDATE} operations.
*/
public boolean isIfExists() {
return this.ifExists;
}
/**
* Builder for {@link DeleteOptions}.
*
* @author Mark Paluch
*/
public static class DeleteOptionsBuilder extends WriteOptionsBuilder {
private boolean ifExists;
private DeleteOptionsBuilder() {}
private DeleteOptionsBuilder(DeleteOptions deleteOptions) {
super(deleteOptions);
this.ifExists = deleteOptions.ifExists;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#consistencyLevel(com.datastax.driver.core.ConsistencyLevel)
*/
@Override
public DeleteOptionsBuilder consistencyLevel(ConsistencyLevel consistencyLevel) {
super.consistencyLevel(consistencyLevel);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#retryPolicy(com.datastax.driver.core.policies.RetryPolicy)
*/
@Override
public DeleteOptionsBuilder retryPolicy(RetryPolicy driverRetryPolicy) {
super.retryPolicy(driverRetryPolicy);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#fetchSize(int)
*/
@Override
public DeleteOptionsBuilder fetchSize(int fetchSize) {
super.fetchSize(fetchSize);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#readTimeout(long)
*/
@Override
public DeleteOptionsBuilder readTimeout(long readTimeout) {
super.readTimeout(readTimeout);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#readTimeout(long, java.util.concurrent.TimeUnit)
*/
@Override
@Deprecated
public DeleteOptionsBuilder readTimeout(long readTimeout, TimeUnit timeUnit) {
super.readTimeout(readTimeout, timeUnit);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#readTimeout(java.time.Duration)
*/
@Override
public DeleteOptionsBuilder readTimeout(Duration readTimeout) {
super.readTimeout(readTimeout);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#ttl(java.time.Duration)
*/
@Override
public DeleteOptionsBuilder ttl(Duration ttl) {
super.ttl(ttl);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#tracing(boolean)
*/
@Override
public DeleteOptionsBuilder tracing(boolean tracing) {
super.tracing(tracing);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#withTracing()
*/
@Override
public DeleteOptionsBuilder withTracing() {
super.withTracing();
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#ttl(int)
*/
public DeleteOptionsBuilder ttl(int ttl) {
super.ttl(ttl);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#timestamp(long)
*/
@Override
public DeleteOptionsBuilder timestamp(long timestamp) {
super.timestamp(timestamp);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.cql.WriteOptions.WriteOptionsBuilder#timestamp(java.time.Instant)
*/
@Override
public DeleteOptionsBuilder timestamp(Instant timestamp) {
super.timestamp(timestamp);
return this;
}
/**
* Use light-weight transactions by applying {@code IF EXISTS}.
*
* @return {@code this} {@link DeleteOptionsBuilder}
*/
public DeleteOptionsBuilder withIfExists() {
return ifExists(true);
}
/**
* Use light-weight transactions by applying {@code IF EXISTS}.
*
* @param ifNotExists {@literal true} to enable {@code IF EXISTS}.
* @return {@code this} {@link DeleteOptionsBuilder}
*/
public DeleteOptionsBuilder ifExists(boolean ifNotExists) {
this.ifExists = ifNotExists;
return this;
}
/**
* Builds a new {@link DeleteOptions} with the configured values.
*
* @return a new {@link DeleteOptions} with the configured values
*/
public DeleteOptions build() {
return new DeleteOptions(this.consistencyLevel, this.retryPolicy, this.tracing, this.fetchSize, this.readTimeout,
this.ttl, this.timestamp, this.ifExists);
}
}
}

View File

@@ -61,7 +61,7 @@ import com.google.common.collect.Iterators;
* @author Mark Paluch
* @since 2.0
*/
class QueryUtils {
class EntityQueryUtils {
private static final Pattern FROM_REGEX = Pattern.compile(" FROM ([\"]?[\\w]*[\\\\.]?[\\w]*[\"]?)[\\s]?",
Pattern.CASE_INSENSITIVE);
@@ -155,7 +155,7 @@ class QueryUtils {
Delete delete = deleteSelection.from(tableName);
if (options instanceof WriteOptions) {
QueryOptionsUtil.addWriteOptions(delete, (WriteOptions) options);
addWriteOptions(delete, (WriteOptions) options);
} else {
QueryOptionsUtil.addQueryOptions(delete, options);
}
@@ -322,7 +322,16 @@ class QueryUtils {
Assert.notNull(delete, "Delete must not be null");
QueryOptionsUtil.addQueryOptions(delete, writeOptions);
QueryOptionsUtil.addWriteOptions(delete, writeOptions);
if (writeOptions instanceof DeleteOptions) {
DeleteOptions deleteOptions = (DeleteOptions) writeOptions;
if (deleteOptions.isIfExists()) {
delete.where().ifExists();
}
}
return delete;
}

View File

@@ -203,10 +203,9 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
Mono<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class applying {@link Query}.
*
* This overridden method allows users to further refine the selection criteria using a {@link Query} predicate
* to determine how many entities of the given {@link Class type} match the criteria.
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
* to further refine the selection criteria using a {@link Query} predicate to determine how many entities of the
* given {@link Class type} match the criteria.
*
* @param query user-defined count {@link Query} to execute; must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
@@ -219,9 +218,9 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's
* an instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass}
* or {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's an
* instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
@@ -311,6 +310,20 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
*/
Mono<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and emit the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link WriteResult} for this operation.
* @throws DataAccessException if there is any problem issuing the execution.
* @see DeleteOptions#empty()
* @since 2.2
*/
default Mono<WriteResult> delete(Object entity, DeleteOptions options) throws DataAccessException {
return delete(entity, (QueryOptions) options);
}
/**
* Remove the given object from the table by id.
*

View File

@@ -239,7 +239,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
Assert.notNull(cql, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
Function<Row, T> mapper = getMapper(entityClass, entityClass, QueryUtils.getTableName(cql));
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(cql));
return getReactiveCqlOperations().query(cql, (row, rowNum) -> mapper.apply(row));
}
@@ -263,7 +263,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
Integer effectiveFetchSize = tuple.getT2();
return resultSet.availableRows().collectList().map(it -> {
return QueryUtils.readSlice(it, resultSet.getExecutionInfo().getPagingState(), rowMapper, 1,
return EntityQueryUtils.readSlice(it, resultSet.getExecutionInfo().getPagingState(), rowMapper, 1,
effectiveFetchSize);
});
@@ -488,7 +488,8 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(), persistentEntity);
Insert insert = EntityQueryUtils.createInsertQuery(tableName.toCql(), entity, options, getConverter(),
persistentEntity);
// noinspection ConstantConditions
Mono<EntityWriteResult<T>> result = getReactiveCqlOperations() //
@@ -518,7 +519,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
Assert.notNull(options, "UpdateOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
Update update = EntityQueryUtils.createUpdateQuery(tableName.toCql(), entity, options, getConverter());
Mono<EntityWriteResult<T>> result = getReactiveCqlOperations() //
.execute(new StatementCallback(update)) //
@@ -547,7 +548,8 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations, A
Assert.notNull(options, "QueryOptions must not be null");
CqlIdentifier tableName = getTableName(entity);
Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
Delete delete = EntityQueryUtils.createDeleteQuery(tableName.toCql(), entity, options, getConverter());
Mono<WriteResult> result = getReactiveCqlOperations() //
.execute(new StatementCallback(delete)) //

View File

@@ -313,7 +313,7 @@ public class StatementFactory {
query.getQueryOptions().ifPresent(queryOptions -> {
if (queryOptions instanceof WriteOptions) {
QueryUtils.addWriteOptions(update, (WriteOptions) queryOptions);
EntityQueryUtils.addWriteOptions(update, (WriteOptions) queryOptions);
} else {
QueryOptionsUtil.addQueryOptions(update, queryOptions);
}
@@ -454,7 +454,7 @@ public class StatementFactory {
query.getQueryOptions().ifPresent(queryOptions -> {
if (queryOptions instanceof WriteOptions) {
QueryUtils.addWriteOptions(delete, (WriteOptions) queryOptions);
EntityQueryUtils.addWriteOptions(delete, (WriteOptions) queryOptions);
} else {
QueryOptionsUtil.addQueryOptions(delete, queryOptions);
}

View File

@@ -126,7 +126,7 @@ public abstract class QueryOptionsUtil {
*/
public static Delete addWriteOptions(Delete delete, WriteOptions writeOptions) {
Assert.notNull(delete, "Update must not be null");
Assert.notNull(delete, "Delete must not be null");
addQueryOptions(delete, writeOptions);

View File

@@ -284,6 +284,31 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-606
public void deleteShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.delete(user, lwtOptions)).wasApplied()).isTrue();
assertThat(getUninterruptibly(template.delete(user, lwtOptions)).wasApplied()).isFalse();
}
@Test // DATACASS-606
public void deleteByQueryShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg")).queryOptions(lwtOptions);
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUninterruptibly(template.delete(query, User.class))).isFalse();
}
@Test // DATACASS-56
public void shouldPageRequests() {

View File

@@ -348,6 +348,31 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
assertThat(template.selectOneById(user.getId(), User.class)).isNull();
}
@Test // DATACASS-606
public void deleteShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
assertThat(template.delete(user, lwtOptions).wasApplied()).isTrue();
assertThat(template.delete(user, lwtOptions).wasApplied()).isFalse();
}
@Test // DATACASS-606
public void deleteByQueryShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
Query query = Query.query(where("id").is("heisenberg")).queryOptions(lwtOptions);
assertThat(template.delete(query, User.class)).isTrue();
assertThat(template.delete(query, User.class)).isFalse();
}
@Test // DATACASS-182
public void stream() {

View File

@@ -23,16 +23,17 @@ import org.springframework.data.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.domain.User;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
/**
* Unit tests for {@link QueryUtils}.
* Unit tests for {@link EntityQueryUtils}.
*
* @author Mark Paluch
*/
public class QueryUtilsUnitTests {
public class EntityQueryUtilsUnitTests {
private final MappingCassandraConverter converter = new MappingCassandraConverter();
@@ -41,7 +42,7 @@ public class QueryUtilsUnitTests {
Select select = QueryBuilder.select().from("keyspace", "table");
CqlIdentifier tableName = QueryUtils.getTableName(select);
CqlIdentifier tableName = EntityQueryUtils.getTableName(select);
assertThat(tableName).isEqualTo(CqlIdentifier.of("table"));
}
@@ -49,16 +50,16 @@ public class QueryUtilsUnitTests {
@Test // DATACASS-106
public void shouldRetrieveTableNameFromSimpleStatement() {
assertThat(QueryUtils.getTableName(new SimpleStatement("SELECT * FROM table")))
assertThat(EntityQueryUtils.getTableName(new SimpleStatement("SELECT * FROM table")))
.isEqualTo(CqlIdentifier.of("table"));
assertThat(QueryUtils.getTableName(new SimpleStatement("SELECT * FROM foo.table where")))
assertThat(EntityQueryUtils.getTableName(new SimpleStatement("SELECT * FROM foo.table where")))
.isEqualTo(CqlIdentifier.of("table"));
}
@Test // DATACASS-106
public void shouldRetrieveQuotedTableNameFromSimpleStatement() {
CqlIdentifier tableName = QueryUtils.getTableName(new SimpleStatement("SELECT * from \"table\""));
CqlIdentifier tableName = EntityQueryUtils.getTableName(new SimpleStatement("SELECT * from \"table\""));
assertThat(tableName).isEqualTo(CqlIdentifier.of("table"));
}
@@ -67,10 +68,22 @@ public class QueryUtilsUnitTests {
public void shouldCreateInsertQuery() {
User user = new User("heisenberg", "Walter", "White");
Insert insert = QueryUtils.createInsertQuery("user", user, InsertOptions.builder().withIfNotExists().build(),
Insert insert = EntityQueryUtils.createInsertQuery("user", user, InsertOptions.builder().withIfNotExists().build(),
converter, converter.getMappingContext().getRequiredPersistentEntity(User.class));
assertThat(insert.toString())
.isEqualTo("INSERT INTO user (firstname,id,lastname) VALUES ('Walter','heisenberg','White') IF NOT EXISTS;");
}
@Test // DATACASS-606
public void shouldConsiderDeleteIfExists() {
User user = new User("heisenberg", "Walter", "White");
DeleteOptions options = DeleteOptions.builder().withIfExists().build();
Delete delete = EntityQueryUtils.createDeleteQuery("foo", user, options, converter);
assertThat(delete.toString()).isEqualTo("DELETE FROM foo WHERE id='heisenberg' IF EXISTS;");
}
}

View File

@@ -276,6 +276,46 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
verifyUser(user.getId()).verifyComplete();
}
@Test // DATACASS-606
public void deleteShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
template.delete(user, lwtOptions).map(WriteResult::wasApplied) //
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
template.delete(user, lwtOptions).map(WriteResult::wasApplied) //
.as(StepVerifier::create) //
.expectNext(false) //
.verifyComplete();
}
@Test // DATACASS-606
public void deleteByQueryShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
Query query = Query.query(where("id").is("heisenberg")).queryOptions(lwtOptions);
template.delete(query, User.class) //
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
template.delete(query, User.class) //
.as(StepVerifier::create) //
.expectNext(false) //
.verifyComplete();
}
@Test // DATACASS-343
public void shouldSelectByQueryWithSorting() {