Deprecate ListenableFuture-based Template API and introduce CompletableFuture Template API replacement.

We now provide a CompletableFuture-based asynchronous Template API for CQL and entity operations. The ListenableFuture-future API is now deprecated and moved to legacy subpackages for easier migration.

Closes #1294
This commit is contained in:
Mark Paluch
2022-07-28 11:51:32 +02:00
parent a5047c91ed
commit a66556d93a
51 changed files with 7414 additions and 606 deletions

View File

@@ -86,7 +86,7 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf
}
/**
* Returns the a {@link CassandraManagedTypes} object holding the initial entity set.
* Returns the given {@link CassandraManagedTypes} object holding the initial entity set.
*
* @return new instance of {@link CassandraManagedTypes}.
* @throws ClassNotFoundException

View File

@@ -16,6 +16,7 @@
package org.springframework.data.cassandra.core;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.springframework.dao.DataAccessException;
@@ -27,7 +28,6 @@ import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.domain.Slice;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
@@ -74,7 +74,7 @@ public interface AsyncCassandraOperations {
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<List<T>> select(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
@@ -86,7 +86,7 @@ public interface AsyncCassandraOperations {
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
<T> CompletableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
@@ -97,14 +97,14 @@ public interface AsyncCassandraOperations {
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<T> selectOne(String cql, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
/**
* Execute the a Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* Execute the given Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* into Spring's DAO exception hierarchy.
*
* @param statement a Cassandra {@link Statement}, must not be {@literal null}.
@@ -112,7 +112,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @since 3.2
*/
ListenableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException;
CompletableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
@@ -122,7 +122,7 @@ public interface AsyncCassandraOperations {
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<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
@@ -134,7 +134,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
@@ -146,7 +146,7 @@ public interface AsyncCassandraOperations {
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
<T> CompletableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
@@ -157,7 +157,7 @@ public interface AsyncCassandraOperations {
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.data.cassandra.core.query.Query
@@ -171,7 +171,7 @@ public interface AsyncCassandraOperations {
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities.
@@ -182,7 +182,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
@@ -194,7 +194,7 @@ public interface AsyncCassandraOperations {
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
<T> CompletableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
@@ -205,7 +205,7 @@ public interface AsyncCassandraOperations {
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Update the queried entities and return {@literal true} if the update was applied.
@@ -215,7 +215,7 @@ public interface AsyncCassandraOperations {
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> update(Query query, Update update, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Boolean> update(Query query, Update update, Class<?> entityClass) throws DataAccessException;
/**
* Remove entities (rows)/columns from the table by {@link Query}.
@@ -225,7 +225,7 @@ public interface AsyncCassandraOperations {
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
@@ -238,7 +238,7 @@ public interface AsyncCassandraOperations {
* @return the number of existing entities.
* @throws DataAccessException if any problem occurs while executing the query.
*/
ListenableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
CompletableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
@@ -251,7 +251,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if any problem occurs while executing the query.
* @since 2.1
*/
ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
@@ -263,7 +263,7 @@ public interface AsyncCassandraOperations {
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
*/
ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
@@ -274,7 +274,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if any problem occurs while executing the query.
* @since 2.1
*/
ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
@@ -286,7 +286,7 @@ public interface AsyncCassandraOperations {
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
<T> CompletableFuture<T> selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
/**
* Insert the given entity and return the entity if the insert was applied.
@@ -295,7 +295,7 @@ public interface AsyncCassandraOperations {
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity) throws DataAccessException;
<T> CompletableFuture<T> insert(T entity) throws DataAccessException;
/**
* Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied.
@@ -306,7 +306,7 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see InsertOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) throws DataAccessException;
<T> CompletableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
@@ -315,7 +315,7 @@ public interface AsyncCassandraOperations {
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity) throws DataAccessException;
<T> CompletableFuture<T> update(T entity) throws DataAccessException;
/**
* Update the given entity applying {@link WriteOptions} and return the entity if the update was applied.
@@ -326,19 +326,19 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see UpdateOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) throws DataAccessException;
<T> CompletableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete was applied.
* Delete the given entity and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> delete(T entity) throws DataAccessException;
<T> CompletableFuture<T> delete(T entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied.
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
@@ -346,10 +346,10 @@ public interface AsyncCassandraOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see QueryOptions#empty()
*/
ListenableFuture<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
CompletableFuture<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete was applied.
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
@@ -358,7 +358,7 @@ public interface AsyncCassandraOperations {
* @see DeleteOptions#empty()
* @since 2.2
*/
default ListenableFuture<WriteResult> delete(Object entity, DeleteOptions options) throws DataAccessException {
default CompletableFuture<WriteResult> delete(Object entity, DeleteOptions options) throws DataAccessException {
return delete(entity, (QueryOptions) options);
}
@@ -372,7 +372,7 @@ public interface AsyncCassandraOperations {
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) throws DataAccessException;
CompletableFuture<Boolean> deleteById(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Execute a {@code TRUNCATE} query to remove all entities of a given class.
@@ -380,6 +380,6 @@ public interface AsyncCassandraOperations {
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> truncate(Class<?> entityClass) throws DataAccessException;
CompletableFuture<Void> truncate(Class<?> entityClass) throws DataAccessException;
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.core;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -35,14 +36,12 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.*;
import org.springframework.data.cassandra.core.cql.session.DefaultSessionFactory;
import org.springframework.data.cassandra.core.cql.util.CassandraFutureAdapter;
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
@@ -63,9 +62,7 @@ import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
@@ -119,8 +116,6 @@ public class AsyncCassandraTemplate
private final CassandraConverter converter;
private final CqlExceptionTranslator exceptionTranslator;
private final EntityOperations entityOperations;
private final StatementFactory statementFactory;
@@ -189,7 +184,6 @@ public class AsyncCassandraTemplate
this.converter = converter;
this.cqlOperations = asyncCqlTemplate;
this.entityOperations = new EntityOperations(converter);
this.exceptionTranslator = asyncCqlTemplate.getExceptionTranslator();
this.statementFactory = new StatementFactory(converter);
this.eventDelegate = new EntityLifecycleEventDelegate();
}
@@ -314,7 +308,7 @@ public class AsyncCassandraTemplate
// -------------------------------------------------------------------------
@Override
public <T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) {
public <T> CompletableFuture<List<T>> select(String cql, Class<T> entityClass) {
Assert.hasText(cql, "CQL must not be empty");
@@ -322,7 +316,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
public <T> CompletableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
@@ -333,7 +327,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) {
public <T> CompletableFuture<T> selectOne(String cql, Class<T> entityClass) {
Assert.hasText(cql, "CQL must not be empty");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -346,7 +340,7 @@ public class AsyncCassandraTemplate
// -------------------------------------------------------------------------
@Override
public ListenableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException {
public CompletableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException {
Assert.notNull(statement, "Statement must not be null");
@@ -354,7 +348,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<List<T>> select(Statement<?> statement, Class<T> entityClass) {
public <T> CompletableFuture<List<T>> select(Statement<?> statement, Class<T> entityClass) {
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -365,7 +359,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
public <T> CompletableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException {
Assert.notNull(statement, "Statement must not be null");
@@ -380,22 +374,21 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) {
return new MappingListenableFutureAdapter<>(select(statement, entityClass),
list -> list.isEmpty() ? null : list.get(0));
public <T> CompletableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) {
return select(statement, entityClass).thenApply(list -> list.isEmpty() ? null : list.get(0));
}
@Override
public <T> ListenableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) {
public <T> CompletableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) {
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
ListenableFuture<AsyncResultSet> resultSet = doQueryForResultSet(statement);
CompletableFuture<AsyncResultSet> resultSet = doQueryForResultSet(statement);
Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement));
return new MappingListenableFutureAdapter<>(resultSet,
return resultSet.thenApply(
rs -> EntityQueryUtils.readSlice(rs, (row, rowNum) -> mapper.apply(row), 0, getEffectivePageSize(statement)));
}
@@ -404,7 +397,7 @@ public class AsyncCassandraTemplate
// -------------------------------------------------------------------------
@Override
public <T> ListenableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException {
public <T> CompletableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -413,7 +406,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
public <T> CompletableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException {
Assert.notNull(query, "Query must not be null");
@@ -425,7 +418,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException {
public <T> CompletableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -435,7 +428,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException {
public <T> CompletableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -444,7 +437,7 @@ public class AsyncCassandraTemplate
}
@Override
public ListenableFuture<Boolean> update(Query query, org.springframework.data.cassandra.core.query.Update update,
public CompletableFuture<Boolean> update(Query query, org.springframework.data.cassandra.core.query.Update update,
Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
@@ -456,7 +449,7 @@ public class AsyncCassandraTemplate
}
@Override
public ListenableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException {
public CompletableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -464,7 +457,7 @@ public class AsyncCassandraTemplate
return doDelete(query, entityClass, getTableName(entityClass));
}
private ListenableFuture<Boolean> doDelete(Query query, Class<?> entityClass, CqlIdentifier tableName) {
private CompletableFuture<Boolean> doDelete(Query query, Class<?> entityClass, CqlIdentifier tableName) {
StatementBuilder<Delete> builder = getStatementFactory().delete(query, getRequiredPersistentEntity(entityClass),
tableName);
@@ -472,10 +465,9 @@ public class AsyncCassandraTemplate
maybeEmitEvent(() -> new BeforeDeleteEvent<>(delete, entityClass, tableName));
ListenableFuture<Boolean> future = doExecute(delete, AsyncResultSet::wasApplied);
CompletableFuture<Boolean> future = doExecute(delete, AsyncResultSet::wasApplied);
future.addCallback(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(delete, entityClass, tableName)),
e -> {});
future.thenAccept(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(delete, entityClass, tableName)));
return future;
}
@@ -485,7 +477,7 @@ public class AsyncCassandraTemplate
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Long> count(Class<?> entityClass) {
public CompletableFuture<Long> count(Class<?> entityClass) {
Assert.notNull(entityClass, "Entity type must not be null");
@@ -493,7 +485,7 @@ public class AsyncCassandraTemplate
}
@Override
public ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
public CompletableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -501,14 +493,14 @@ public class AsyncCassandraTemplate
return doCount(query, entityClass, getTableName(entityClass));
}
ListenableFuture<Long> doCount(Query query, Class<?> entityClass, CqlIdentifier tableName) {
CompletableFuture<Long> doCount(Query query, Class<?> entityClass, CqlIdentifier tableName) {
StatementBuilder<com.datastax.oss.driver.api.querybuilder.select.Select> countStatement = getStatementFactory()
.count(query, getRequiredPersistentEntity(entityClass), tableName);
SimpleStatement statement = countStatement.build();
ListenableFuture<Long> result = doExecute(statement, it -> {
CompletableFuture<Long> result = doExecute(statement, it -> {
SingleColumnRowMapper<Long> mapper = SingleColumnRowMapper.newInstance(Long.class);
@@ -516,11 +508,11 @@ public class AsyncCassandraTemplate
return mapper.mapRow(row, 0);
});
return new MappingListenableFutureAdapter<>(result, it -> it != null ? it : 0L);
return result.thenApply(it -> it != null ? it : 0L);
}
@Override
public ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) {
public CompletableFuture<Boolean> exists(Object id, Class<?> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -534,7 +526,7 @@ public class AsyncCassandraTemplate
}
@Override
public ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
public CompletableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -546,7 +538,7 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) {
public <T> CompletableFuture<T> selectOneById(Object id, Class<T> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -556,17 +548,16 @@ public class AsyncCassandraTemplate
StatementBuilder<Select> select = getStatementFactory().selectOneById(id, entity, tableName);
Function<Row, T> mapper = getMapper(entityClass, entityClass, tableName);
return new MappingListenableFutureAdapter<>(doQuery(select.build(), (row, rowNum) -> mapper.apply(row)),
it -> it.isEmpty() ? null : it.get(0));
return doQuery(select.build(), (row, rowNum) -> mapper.apply(row)).thenApply(it -> it.isEmpty() ? null : it.get(0));
}
@Override
public <T> ListenableFuture<T> insert(T entity) {
return new MappingListenableFutureAdapter<>(insert(entity, InsertOptions.empty()), EntityWriteResult::getEntity);
public <T> CompletableFuture<T> insert(T entity) {
return insert(entity, InsertOptions.empty()).thenApply(EntityWriteResult::getEntity);
}
@Override
public <T> ListenableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) {
public <T> CompletableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) {
Assert.notNull(entity, "Entity must not be null");
Assert.notNull(options, "InsertOptions must not be null");
@@ -574,7 +565,8 @@ public class AsyncCassandraTemplate
return doInsert(entity, options, getTableName(entity.getClass()));
}
private <T> ListenableFuture<EntityWriteResult<T>> doInsert(T entity, WriteOptions options, CqlIdentifier tableName) {
private <T> CompletableFuture<EntityWriteResult<T>> doInsert(T entity, WriteOptions options,
CqlIdentifier tableName) {
AdaptibleEntity<T> source = getEntityOperations().forEntity(maybeCallBeforeConvert(entity, tableName),
getConverter().getConversionService());
@@ -594,7 +586,7 @@ public class AsyncCassandraTemplate
return doInsert(builder.build(), entityToUse, source, tableName);
}
private <T> ListenableFuture<EntityWriteResult<T>> doInsertVersioned(SimpleStatement insert, T entity,
private <T> CompletableFuture<EntityWriteResult<T>> doInsertVersioned(SimpleStatement insert, T entity,
AdaptibleEntity<T> source, CqlIdentifier tableName) {
return executeSave(entity, tableName, insert, result -> {
@@ -608,19 +600,19 @@ public class AsyncCassandraTemplate
}
@SuppressWarnings("unused")
private <T> ListenableFuture<EntityWriteResult<T>> doInsert(SimpleStatement insert, T entity,
private <T> CompletableFuture<EntityWriteResult<T>> doInsert(SimpleStatement insert, T entity,
AdaptibleEntity<T> source, CqlIdentifier tableName) {
return executeSave(entity, tableName, insert);
}
@Override
public <T> ListenableFuture<T> update(T entity) {
return new MappingListenableFutureAdapter<>(update(entity, UpdateOptions.empty()), EntityWriteResult::getEntity);
public <T> CompletableFuture<T> update(T entity) {
return update(entity, UpdateOptions.empty()).thenApply(EntityWriteResult::getEntity);
}
@Override
public <T> ListenableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) {
public <T> CompletableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) {
Assert.notNull(entity, "Entity must not be null");
Assert.notNull(options, "UpdateOptions must not be null");
@@ -635,7 +627,7 @@ public class AsyncCassandraTemplate
: doUpdate(entityToUpdate, options, tableName, persistentEntity);
}
private <T> ListenableFuture<EntityWriteResult<T>> doUpdateVersioned(T entity, UpdateOptions options,
private <T> CompletableFuture<EntityWriteResult<T>> doUpdateVersioned(T entity, UpdateOptions options,
CqlIdentifier tableName, CassandraPersistentEntity<?> persistentEntity) {
AdaptibleEntity<T> source = getEntityOperations().forEntity(entity, getConverter().getConversionService());
@@ -655,7 +647,7 @@ public class AsyncCassandraTemplate
});
}
private <T> ListenableFuture<EntityWriteResult<T>> doUpdate(T entity, UpdateOptions options, CqlIdentifier tableName,
private <T> CompletableFuture<EntityWriteResult<T>> doUpdate(T entity, UpdateOptions options, CqlIdentifier tableName,
CassandraPersistentEntity<?> persistentEntity) {
StatementBuilder<Update> update = getStatementFactory().update(entity, options, persistentEntity, tableName);
@@ -664,12 +656,12 @@ public class AsyncCassandraTemplate
}
@Override
public <T> ListenableFuture<T> delete(T entity) {
return new MappingListenableFutureAdapter<>(delete(entity, QueryOptions.empty()), writeResult -> entity);
public <T> CompletableFuture<T> delete(T entity) {
return delete(entity, QueryOptions.empty()).thenApply(writeResult -> entity);
}
@Override
public ListenableFuture<WriteResult> delete(Object entity, QueryOptions options) {
public CompletableFuture<WriteResult> delete(Object entity, QueryOptions options) {
Assert.notNull(entity, "Entity must not be null");
Assert.notNull(options, "QueryOptions must not be null");
@@ -682,7 +674,7 @@ public class AsyncCassandraTemplate
: doDelete(entity, options, tableName);
}
private ListenableFuture<WriteResult> doDeleteVersioned(Object entity, QueryOptions options,
private CompletableFuture<WriteResult> doDeleteVersioned(Object entity, QueryOptions options,
AdaptibleEntity<Object> source, CqlIdentifier tableName) {
StatementBuilder<Delete> delete = getStatementFactory().delete(entity, options, getConverter(), tableName);
@@ -698,7 +690,7 @@ public class AsyncCassandraTemplate
});
}
private ListenableFuture<WriteResult> doDelete(Object entity, QueryOptions options, CqlIdentifier tableName) {
private CompletableFuture<WriteResult> doDelete(Object entity, QueryOptions options, CqlIdentifier tableName) {
StatementBuilder<Delete> delete = getStatementFactory().delete(entity, options, getConverter(), tableName);
@@ -706,7 +698,7 @@ public class AsyncCassandraTemplate
}
@Override
public ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) {
public CompletableFuture<Boolean> deleteById(Object id, Class<?> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
@@ -719,15 +711,14 @@ public class AsyncCassandraTemplate
maybeEmitEvent(() -> new BeforeDeleteEvent<>(delete, entityClass, tableName));
ListenableFuture<Boolean> future = doExecute(delete, AsyncResultSet::wasApplied);
future.addCallback(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(delete, entityClass, tableName)),
e -> {});
CompletableFuture<Boolean> future = doExecute(delete, AsyncResultSet::wasApplied);
future.thenAccept(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(delete, entityClass, tableName)));
return future;
}
@Override
public ListenableFuture<Void> truncate(Class<?> entityClass) {
public CompletableFuture<Void> truncate(Class<?> entityClass) {
Assert.notNull(entityClass, "Entity type must not be null");
@@ -737,11 +728,10 @@ public class AsyncCassandraTemplate
maybeEmitEvent(() -> new BeforeDeleteEvent<>(statement, entityClass, tableName));
ListenableFuture<Boolean> future = doExecute(statement, AsyncResultSet::wasApplied);
future.addCallback(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(statement, entityClass, tableName)),
e -> {});
CompletableFuture<Boolean> future = doExecute(statement, AsyncResultSet::wasApplied);
future.thenAccept(success -> maybeEmitEvent(() -> new AfterDeleteEvent<>(statement, entityClass, tableName)));
return new MappingListenableFutureAdapter<>(future, aBoolean -> null);
return future.thenApply(aBoolean -> null);
}
// -------------------------------------------------------------------------
@@ -758,24 +748,24 @@ public class AsyncCassandraTemplate
* @since 3.3.3
*/
protected AsyncPreparedStatementHandler createPreparedStatementHandler(Statement<?> statement) {
return new PreparedStatementHandler(statement, exceptionTranslator);
return new PreparedStatementHandler(statement);
}
private <T> ListenableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
private <T> CompletableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
SimpleStatement statement) {
return executeSave(entity, tableName, statement, ignore -> {});
}
private <T> ListenableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
private <T> CompletableFuture<EntityWriteResult<T>> executeSave(T entity, CqlIdentifier tableName,
SimpleStatement statement, Consumer<WriteResult> beforeAfterSaveEvent) {
maybeEmitEvent(() -> new BeforeSaveEvent<>(entity, tableName, statement));
T entityToSave = maybeCallBeforeSave(entity, tableName, statement);
ListenableFuture<AsyncResultSet> result = doQueryForResultSet(statement);
CompletableFuture<AsyncResultSet> result = doQueryForResultSet(statement);
return new MappingListenableFutureAdapter<>(result, resultSet -> {
return result.thenApply(resultSet -> {
EntityWriteResult<T> writeResult = new EntityWriteResult<>(
Collections.singletonList(resultSet.getExecutionInfo()), resultSet.wasApplied(), getFirstPage(resultSet),
@@ -789,14 +779,14 @@ public class AsyncCassandraTemplate
});
}
private ListenableFuture<WriteResult> executeDelete(Object entity, CqlIdentifier tableName, SimpleStatement statement,
Consumer<WriteResult> resultConsumer) {
private CompletableFuture<WriteResult> executeDelete(Object entity, CqlIdentifier tableName,
SimpleStatement statement, Consumer<WriteResult> resultConsumer) {
maybeEmitEvent(() -> new BeforeDeleteEvent<>(statement, entity.getClass(), tableName));
ListenableFuture<AsyncResultSet> result = doQueryForResultSet(statement);
CompletableFuture<AsyncResultSet> result = doQueryForResultSet(statement);
return new MappingListenableFutureAdapter<>(result, resultSet -> {
return result.thenApply(resultSet -> {
WriteResult writeResult = new WriteResult(Collections.singletonList(resultSet.getExecutionInfo()),
resultSet.wasApplied(), getFirstPage(resultSet));
@@ -809,7 +799,7 @@ public class AsyncCassandraTemplate
});
}
private <T> ListenableFuture<List<T>> doQuery(Statement<?> statement, RowMapper<T> rowMapper) {
private <T> CompletableFuture<List<T>> doQuery(Statement<?> statement, RowMapper<T> rowMapper) {
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
@@ -820,7 +810,7 @@ public class AsyncCassandraTemplate
return getAsyncCqlOperations().query(statement, rowMapper);
}
private ListenableFuture<Void> doQuery(Statement<?> statement, RowCallbackHandler callbackHandler) {
private CompletableFuture<Void> doQuery(Statement<?> statement, RowCallbackHandler callbackHandler) {
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
@@ -831,20 +821,22 @@ public class AsyncCassandraTemplate
return getAsyncCqlOperations().query(statement, callbackHandler);
}
private ListenableFuture<AsyncResultSet> doQueryForResultSet(Statement<?> statement) {
private CompletableFuture<AsyncResultSet> doQueryForResultSet(Statement<?> statement) {
return doExecute(statement, Function.identity());
}
private <T> ListenableFuture<T> doExecute(Statement<?> statement, Function<AsyncResultSet, T> mappingFunction) {
private <T> CompletableFuture<T> doExecute(Statement<?> statement, Function<AsyncResultSet, T> mappingFunction) {
if (PreparedStatementDelegate.canPrepare(isUsePreparedStatements(), statement, log)) {
AsyncPreparedStatementHandler statementHandler = createPreparedStatementHandler(statement);
return getAsyncCqlOperations().query(statementHandler, statementHandler,
(AsyncResultSetExtractor<T>) resultSet -> new AsyncResult<>(mappingFunction.apply(resultSet)));
(AsyncResultSetExtractor<T>) resultSet -> CompletableFuture
.completedFuture(mappingFunction.apply(resultSet)));
}
return new MappingListenableFutureAdapter<>(getAsyncCqlOperations().queryForResultSet(statement), mappingFunction);
return getAsyncCqlOperations().queryForResultSet(statement).thenApply(mappingFunction);
}
private static List<Row> getFirstPage(AsyncResultSet resultSet) {
@@ -871,8 +863,8 @@ public class AsyncCassandraTemplate
}
class GetConfiguredPageSize implements AsyncSessionCallback<Integer>, CqlProvider {
@Override
public ListenableFuture<Integer> doInSession(CqlSession session) {
return AsyncResult.forValue(getConfiguredPageSize(session));
public CompletableFuture<Integer> doInSession(CqlSession session) {
return CompletableFuture.completedFuture(getConfiguredPageSize(session));
}
@Override
@@ -881,7 +873,7 @@ public class AsyncCassandraTemplate
}
}
return getAsyncCqlOperations().execute(new GetConfiguredPageSize()).completable().join();
return getAsyncCqlOperations().execute(new GetConfiguredPageSize()).join();
}
@SuppressWarnings("unchecked")
@@ -936,22 +928,6 @@ public class AsyncCassandraTemplate
return object;
}
static class MappingListenableFutureAdapter<T, S>
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
private final Function<S, T> mapper;
MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
super(adaptee);
this.mapper = mapper;
}
@Override
protected T adapt(@Nullable S adapteeResult) {
return this.mapper.apply(adapteeResult);
}
}
/**
* General callback interface used to create and bind prepared CQL statements.
* <p>
@@ -971,19 +947,17 @@ public class AsyncCassandraTemplate
*
* @since 3.2
*/
public static class PreparedStatementHandler implements AsyncPreparedStatementHandler {
static class PreparedStatementHandler implements AsyncPreparedStatementHandler {
private final SimpleStatement statement;
private final PersistenceExceptionTranslator exceptionTranslator;
public PreparedStatementHandler(Statement<?> statement, PersistenceExceptionTranslator exceptionTranslator) {
public PreparedStatementHandler(Statement<?> statement) {
this.statement = PreparedStatementDelegate.getStatementForPrepare(statement);
this.exceptionTranslator = exceptionTranslator;
}
@Override
public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return new CassandraFutureAdapter<>(doPrepare(session), exceptionTranslator);
public CompletableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return doPrepare(session).toCompletableFuture();
}
/**

View File

@@ -141,7 +141,7 @@ public interface CassandraOperations extends FluentCassandraOperations {
// -------------------------------------------------------------------------
/**
* Execute the a Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* Execute the given Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* into Spring's DAO exception hierarchy.
*
* @param statement a Cassandra {@link Statement}, must not be {@literal null}.
@@ -373,7 +373,7 @@ public interface CassandraOperations extends FluentCassandraOperations {
<T> EntityWriteResult<T> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete was applied.
* Delete the given entity and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
@@ -381,7 +381,7 @@ public interface CassandraOperations extends FluentCassandraOperations {
void delete(Object entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied.
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
@@ -392,7 +392,7 @@ 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.
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.

View File

@@ -32,12 +32,12 @@ public class EntityWriteResult<T> extends WriteResult {
private final T entity;
EntityWriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows, T entity) {
protected EntityWriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows, T entity) {
super(executionInfo, wasApplied, rows);
this.entity = entity;
}
EntityWriteResult(ResultSet resultSet, T entity) {
protected EntityWriteResult(ResultSet resultSet, T entity) {
super(resultSet);
this.entity = entity;
}

View File

@@ -154,7 +154,7 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
// -------------------------------------------------------------------------
/**
* Execute the a Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* Execute the given Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* into Spring's DAO exception hierarchy.
*
* @param statement a Cassandra {@link Statement}, must not be {@literal null}.
@@ -318,7 +318,7 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
<T> Mono<EntityWriteResult<T>> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and emit the entity if the delete was applied.
* Delete the given entity and emit the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
@@ -327,7 +327,7 @@ public interface ReactiveCassandraOperations extends ReactiveFluentCassandraOper
<T> Mono<T> delete(T entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and emit the entity if the delete was applied.
* Delete the given entity applying {@link QueryOptions} and emit the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
@@ -338,7 +338,7 @@ 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.
* Delete the given entity applying {@link QueryOptions} and emit the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.

View File

@@ -211,7 +211,7 @@ public class StatementFactory {
* @param tableName must not be {@literal null}.
* @return the select builder.
*/
StatementBuilder<Select> selectOneById(Object id, CassandraPersistentEntity<?> persistentEntity,
public StatementBuilder<Select> selectOneById(Object id, CassandraPersistentEntity<?> persistentEntity,
CqlIdentifier tableName) {
Where where = new Where();
@@ -287,7 +287,7 @@ public class StatementFactory {
* @param persistentEntity the {@link CassandraPersistentEntity} to write insert values.
* @return the select builder.
*/
StatementBuilder<RegularInsert> insert(Object objectToInsert, WriteOptions options,
public StatementBuilder<RegularInsert> insert(Object objectToInsert, WriteOptions options,
CassandraPersistentEntity<?> persistentEntity, CqlIdentifier tableName) {
Assert.notNull(tableName, "TableName must not be null");
@@ -420,7 +420,7 @@ public class StatementFactory {
* @param tableName must not be {@literal null}.
* @return the update builder.
*/
StatementBuilder<com.datastax.oss.driver.api.querybuilder.update.Update> update(Object objectToUpdate,
public StatementBuilder<com.datastax.oss.driver.api.querybuilder.update.Update> update(Object objectToUpdate,
WriteOptions options, CassandraPersistentEntity<?> entity, CqlIdentifier tableName) {
Assert.notNull(tableName, "TableName must not be null");
@@ -459,7 +459,7 @@ public class StatementFactory {
* @param tableName must not be {@literal null}.
* @return the delete builder.
*/
StatementBuilder<Delete> deleteById(Object id, CassandraPersistentEntity<?> persistentEntity,
public StatementBuilder<Delete> deleteById(Object id, CassandraPersistentEntity<?> persistentEntity,
CqlIdentifier tableName) {
Where where = new Where();
@@ -529,7 +529,7 @@ public class StatementFactory {
* @param tableName must not be {@literal null}.
* @return the delete builder.
*/
StatementBuilder<Delete> delete(Object entity, QueryOptions options, EntityWriter<Object, Object> entityWriter,
public StatementBuilder<Delete> delete(Object entity, QueryOptions options, EntityWriter<Object, Object> entityWriter,
CqlIdentifier tableName) {
Assert.notNull(tableName, "TableName must not be null");

View File

@@ -40,14 +40,14 @@ public class WriteResult {
private final List<Row> rows;
WriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows) {
protected WriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows) {
this.executionInfo = executionInfo;
this.wasApplied = wasApplied;
this.rows = rows;
}
WriteResult(ResultSet resultSet) {
protected WriteResult(ResultSet resultSet) {
this.executionInfo = resultSet.getExecutionInfos();
this.wasApplied = resultSet.wasApplied();

View File

@@ -17,17 +17,17 @@ package org.springframework.data.cassandra.core.cql;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.lang.Nullable;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Interface specifying a basic set of CQL asynchronously executed operations. Exposes similar methods as
* {@link CqlTemplate}, but returns result handles or accepts callbacks as opposed to concrete results. Implemented by
@@ -58,7 +58,7 @@ public interface AsyncCqlOperations {
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException;
<T> CompletableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with static CQL
@@ -71,7 +71,7 @@ public interface AsyncCqlOperations {
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql) throws DataAccessException;
CompletableFuture<Boolean> execute(String cql) throws DataAccessException;
/**
* Issue a single CQL operation (such as an insert, update or delete statement) via a prepared statement, binding the
@@ -83,7 +83,7 @@ public interface AsyncCqlOperations {
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException;
CompletableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException;
/**
* Issue an statement using a {@link PreparedStatementBinder} to set bind parameters, with given CQL. Simpler than
@@ -97,7 +97,7 @@ public interface AsyncCqlOperations {
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb) throws DataAccessException;
CompletableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb) throws DataAccessException;
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
@@ -112,7 +112,7 @@ public interface AsyncCqlOperations {
* @return a result object returned by the action, or {@literal null}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
<T> CompletableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
@@ -126,7 +126,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, AsyncResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
<T> CompletableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
@@ -140,7 +140,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException;
CompletableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
@@ -154,7 +154,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException;
<T> CompletableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
@@ -167,7 +167,7 @@ public interface AsyncCqlOperations {
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
<T> CompletableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
throws DataAccessException;
/**
@@ -180,7 +180,7 @@ public interface AsyncCqlOperations {
* CQL type)
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
CompletableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
throws DataAccessException;
/**
@@ -194,7 +194,7 @@ public interface AsyncCqlOperations {
* @return the result {@link List}, containing mapped objects
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
<T> CompletableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
@@ -207,7 +207,7 @@ public interface AsyncCqlOperations {
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
<T> CompletableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
@@ -222,8 +222,8 @@ public interface AsyncCqlOperations {
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler)
throws DataAccessException;
CompletableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb,
RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
@@ -237,7 +237,7 @@ public interface AsyncCqlOperations {
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
<T> CompletableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException;
/**
@@ -255,7 +255,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException;
CompletableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
@@ -272,7 +272,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String)
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args) throws DataAccessException;
CompletableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
@@ -291,7 +291,7 @@ public interface AsyncCqlOperations {
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException;
<T> CompletableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
@@ -310,7 +310,7 @@ public interface AsyncCqlOperations {
* @see #queryForList(String, Class)
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
<T> CompletableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException;
/**
@@ -330,7 +330,7 @@ public interface AsyncCqlOperations {
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException;
CompletableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
@@ -349,7 +349,7 @@ public interface AsyncCqlOperations {
* @see #queryForMap(String)
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException;
CompletableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
@@ -369,7 +369,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
@@ -388,7 +388,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class)
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
@@ -404,7 +404,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping a
@@ -418,7 +418,8 @@ public interface AsyncCqlOperations {
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
@@ -434,7 +435,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException;
CompletableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
@@ -449,7 +450,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String)
*/
ListenableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException;
CompletableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
@@ -462,7 +463,7 @@ public interface AsyncCqlOperations {
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException;
CompletableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
@@ -476,7 +477,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, AsyncResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
<T> CompletableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException;
/**
@@ -491,7 +492,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
CompletableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
throws DataAccessException;
/**
@@ -506,7 +507,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
<T> CompletableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
@@ -523,7 +524,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException;
CompletableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
@@ -542,7 +543,7 @@ public interface AsyncCqlOperations {
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType) throws DataAccessException;
<T> CompletableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType) throws DataAccessException;
/**
* Execute a query for a result Map, given static CQL.
@@ -561,7 +562,7 @@ public interface AsyncCqlOperations {
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException;
CompletableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
@@ -581,7 +582,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
@@ -597,7 +598,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
<T> CompletableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
@@ -613,7 +614,7 @@ public interface AsyncCqlOperations {
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException;
CompletableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.PreparedStatement
@@ -627,7 +628,7 @@ public interface AsyncCqlOperations {
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator) throws DataAccessException;
CompletableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator) throws DataAccessException;
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
@@ -643,7 +644,7 @@ public interface AsyncCqlOperations {
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
<T> CompletableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
PreparedStatementCallback<T> action) throws DataAccessException;
/**
@@ -655,7 +656,7 @@ public interface AsyncCqlOperations {
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
<T> CompletableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
@@ -667,7 +668,7 @@ public interface AsyncCqlOperations {
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
CompletableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
@@ -679,7 +680,7 @@ public interface AsyncCqlOperations {
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator, RowMapper<T> rowMapper)
<T> CompletableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator, RowMapper<T> rowMapper)
throws DataAccessException;
/**
@@ -695,7 +696,7 @@ public interface AsyncCqlOperations {
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
<T> CompletableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
@@ -710,7 +711,7 @@ public interface AsyncCqlOperations {
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
CompletableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
@@ -726,6 +727,6 @@ public interface AsyncCqlOperations {
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
<T> CompletableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.core.cql;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
@@ -26,11 +27,8 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.cql.util.CassandraFutureAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
@@ -68,7 +66,8 @@ import com.datastax.oss.driver.api.core.cql.Statement;
*
* @author Mark Paluch
* @author John Blum
* @see ListenableFuture
* @since 2.0
* @see CompletableFuture
* @see PreparedStatementCreator
* @see PreparedStatementBinder
* @see PreparedStatementCallback
@@ -119,7 +118,7 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
// -------------------------------------------------------------------------
@Override
public <T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException {
public <T> CompletableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
@@ -135,15 +134,15 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(String cql) throws DataAccessException {
public CompletableFuture<Boolean> execute(String cql) throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
return new MappingListenableFutureAdapter<>(queryForResultSet(cql), AsyncResultSet::wasApplied);
return queryForResultSet(cql).thenApply(AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor)
public <T> CompletableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
@@ -156,59 +155,58 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
CompletionStage<T> results = getCurrentSession().executeAsync(applyStatementSettings(newStatement(cql)))
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
.thenCompose(Function.identity());
return new CassandraFutureAdapter<>(results, ex -> translateExceptionIfPossible("Query", cql, ex));
return results.exceptionallyCompose(exceptionComposition(ex -> translateExceptionIfPossible("Query", cql, ex)))
.toCompletableFuture();
} catch (DriverException e) {
throw translateException("Query", cql, e);
}
}
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
public CompletableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(cql, newAsyncResultSetExtractor(rowCallbackHandler));
return new MappingListenableFutureAdapter<>(results, o -> null);
CompletableFuture<?> results = query(cql, newAsyncResultSetExtractor(rowCallbackHandler));
return results.thenApply(o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException {
public <T> CompletableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException {
public CompletableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException {
public <T> CompletableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException {
public CompletableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException {
return queryForObject(cql, newColumnMapRowMapper());
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException {
public <T> CompletableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException {
return queryForObject(cql, newSingleColumnRowMapper(requiredType));
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException {
public <T> CompletableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException {
ListenableFuture<List<T>> results = query(cql, newAsyncResultSetExtractor(rowMapper));
CompletableFuture<List<T>> results = query(cql, newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
return results.thenApply(DataAccessUtils::requiredSingleResult);
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException {
public CompletableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException {
return query(cql, AsyncCqlTemplate::toResultSet);
}
@@ -217,15 +215,15 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException {
public CompletableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
return new MappingListenableFutureAdapter<>(queryForResultSet(statement), AsyncResultSet::wasApplied);
return queryForResultSet(statement).thenApply(AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
public <T> CompletableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
@@ -239,64 +237,64 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
CompletionStage<T> results = getCurrentSession() //
.executeAsync(applyStatementSettings(statement)) //
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
.thenCompose(Function.identity());
return new CassandraFutureAdapter<>(results, ex -> translateExceptionIfPossible("Query", toCql(statement), ex));
return results
.exceptionallyCompose(exceptionComposition(ex -> translateExceptionIfPossible("Query", toCql(statement), ex)))
.toCompletableFuture();
} catch (DriverException e) {
throw translateException("Query", toCql(statement), e);
}
}
@Override
public ListenableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
public CompletableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
throws DataAccessException {
ListenableFuture<?> result = query(statement, newAsyncResultSetExtractor(rowCallbackHandler));
CompletableFuture<?> result = query(statement, newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(result, o -> null),
getExceptionTranslator());
return result.thenApply(it -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper)
public <T> CompletableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper)
throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException {
public CompletableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
@Override
public <T> ListenableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType)
public <T> CompletableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType)
throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException {
public CompletableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException {
return queryForObject(statement, newColumnMapRowMapper());
}
@Override
public <T> ListenableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType)
public <T> CompletableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType)
throws DataAccessException {
return queryForObject(statement, newSingleColumnRowMapper(requiredType));
}
@Override
public <T> ListenableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper)
public <T> CompletableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper)
throws DataAccessException {
ListenableFuture<List<T>> results = query(statement, newAsyncResultSetExtractor(rowMapper));
CompletableFuture<List<T>> results = query(statement, newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
return results.thenApply(DataAccessUtils::requiredSingleResult);
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException {
public CompletableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException {
return query(statement, AsyncCqlTemplate::toResultSet);
}
@@ -305,33 +303,32 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator)
public CompletableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator)
throws DataAccessException {
return new MappingListenableFutureAdapter<>(query(preparedStatementCreator, AsyncCqlTemplate::toResultSet),
AsyncResultSet::wasApplied);
return query(preparedStatementCreator, AsyncCqlTemplate::toResultSet).thenApply(AsyncResultSet::wasApplied);
}
@Override
public ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException {
public CompletableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException {
return execute(cql, newPreparedStatementBinder(args));
}
@Override
public ListenableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb)
public CompletableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb)
throws DataAccessException {
return new MappingListenableFutureAdapter<>(
query(newAsyncPreparedStatementCreator(cql), psb, AsyncCqlTemplate::toResultSet), AsyncResultSet::wasApplied);
return query(newAsyncPreparedStatementCreator(cql), psb, AsyncCqlTemplate::toResultSet)
.thenApply(AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException {
public <T> CompletableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(newAsyncPreparedStatementCreator(cql), action);
}
@Override
public <T> ListenableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
public <T> CompletableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
PreparedStatementCallback<T> action) throws DataAccessException {
Assert.notNull(preparedStatementCreator, "PreparedStatementCreator must not be null");
@@ -346,14 +343,15 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
CqlSession currentSession = getCurrentSession();
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
preparedStatementCreator.createPreparedStatement(currentSession), preparedStatement -> {
try {
return action.doInPreparedStatement(currentSession, preparedStatement);
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}), getExceptionTranslator());
return preparedStatementCreator.createPreparedStatement(currentSession).thenApply(preparedStatement -> {
try {
return action.doInPreparedStatement(currentSession, preparedStatement);
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}).toCompletableFuture()
.exceptionallyCompose(exceptionComposition(exceptionTranslator::translateExceptionIfPossible));
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
@@ -361,31 +359,31 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
public <T> CompletableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
return query(preparedStatementCreator, null, resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
public CompletableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(preparedStatementCreator, null, newAsyncResultSetExtractor(rowCallbackHandler));
CompletableFuture<?> results = query(preparedStatementCreator, null,
newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
return results.thenApply(o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
public <T> CompletableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowMapper<T> rowMapper) throws DataAccessException {
return query(preparedStatementCreator, null, newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
public <T> CompletableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
Assert.notNull(preparedStatementCreator, "AsyncPreparedStatementCreator must not be null");
@@ -403,8 +401,8 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
CqlSession session = getCurrentSession();
ListenableFuture<Statement<?>> statementFuture = new MappingListenableFutureAdapter<>(
preparedStatementCreator.createPreparedStatement(session), preparedStatement -> {
CompletionStage<Statement<?>> statementFuture = preparedStatementCreator.createPreparedStatement(session)
.thenApply(preparedStatement -> {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executing prepared statement [%s]", toCql(preparedStatement)));
}
@@ -412,84 +410,81 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
return applyStatementSettings(psb != null ? psb.bindValues(preparedStatement) : preparedStatement.bind());
});
CompletableFuture<T> result = statementFuture.completable() //
CompletableFuture<T> result = statementFuture.toCompletableFuture() //
.thenCompose(session::executeAsync) //
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
.thenCompose(Function.identity());
return new CassandraFutureAdapter<>(result, exceptionTranslator);
return result.exceptionallyCompose(exceptionComposition(this::translateExceptionIfPossible));
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
public CompletableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(preparedStatementCreator, psb, newAsyncResultSetExtractor(rowCallbackHandler));
CompletableFuture<?> results = query(preparedStatementCreator, psb, newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
return results.thenApply(o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
public <T> CompletableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException {
return query(preparedStatementCreator, psb, newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
public <T> CompletableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args), resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
public CompletableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
throws DataAccessException {
ListenableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
CompletableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
return results.thenApply(o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args)
public <T> CompletableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
public <T> CompletableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb,
public CompletableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb,
RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), psb,
CompletableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), psb,
newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
return results.thenApply(o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
public <T> CompletableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args)
public CompletableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
@@ -497,7 +492,7 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
public <T> CompletableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
@@ -505,30 +500,29 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException {
public CompletableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException {
return queryForObject(cql, newColumnMapRowMapper(), args);
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args)
public <T> CompletableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args)
throws DataAccessException {
return queryForObject(cql, newSingleColumnRowMapper(requiredType), args);
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
public <T> CompletableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
ListenableFuture<List<T>> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
CompletableFuture<List<T>> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
return results.thenApply(DataAccessUtils::requiredSingleResult);
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException {
public CompletableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException {
return query(cql, AsyncCqlTemplate::toResultSet, args);
}
@@ -612,12 +606,30 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
return sessionFactory.getSession();
}
private static ListenableFuture<AsyncResultSet> toResultSet(AsyncResultSet resultSet) {
private static <T> Function<Throwable, CompletionStage<T>> exceptionComposition(
Function<RuntimeException, Exception> exceptionTranslator) {
return throwable -> {
SettableListenableFuture<AsyncResultSet> future = new SettableListenableFuture<>();
future.set(resultSet);
Throwable toTranslate = throwable;
Throwable translated = null;
if (toTranslate instanceof ExecutionException e) {
toTranslate = e.getCause();
}
return future;
if (toTranslate instanceof CompletionException e) {
toTranslate = e.getCause();
}
if (toTranslate instanceof RuntimeException e) {
translated = exceptionTranslator.apply(e);
}
return CompletableFuture.failedFuture(translated != null ? translated : toTranslate);
};
}
private static CompletableFuture<AsyncResultSet> toResultSet(AsyncResultSet resultSet) {
return CompletableFuture.completedFuture(resultSet);
}
private static RuntimeException translateException(PersistenceExceptionTranslator exceptionTranslator,
@@ -641,8 +653,10 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
@Override
public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return new CassandraFutureAdapter<>(session.prepareAsync(this.statement), exceptionTranslator);
public CompletableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return session.prepareAsync(this.statement)
.exceptionallyCompose(exceptionComposition(exceptionTranslator::translateExceptionIfPossible))
.toCompletableFuture();
}
@Override
@@ -663,26 +677,8 @@ public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOpera
}
@Override
@Nullable
public ListenableFuture<Void> extractData(AsyncResultSet resultSet) {
public CompletableFuture<Void> extractData(AsyncResultSet resultSet) {
return AsyncResultStream.from(resultSet).forEach(rowCallbackHandler::processRow);
}
}
private static class MappingListenableFutureAdapter<T, S>
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
private final Function<S, T> mapper;
private MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
super(adaptee);
this.mapper = mapper;
}
@Override
protected T adapt(S adapteeResult) throws ExecutionException {
return mapper.apply(adapteeResult);
}
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.cassandra.core.cql;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.concurrent.CompletionStage;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
@@ -23,8 +23,8 @@ import com.datastax.oss.driver.api.core.cql.PreparedStatement;
/**
* One of the two central callback interfaces used by the {@link AsyncCqlTemplate} class. This interface prepares a CQL
* statement returning a {@link org.springframework.util.concurrent.ListenableFuture} given a {@link CqlSession},
* provided by the {@link CqlTemplate} class.
* statement returning a {@link CompletionStage} given a {@link CqlSession}, provided by the {@link AsyncCqlTemplate}
* class.
* <p>
* Implementations may either create new prepared statements or reuse cached instances. Implementations do not need to
* concern themselves with {@link DriverException}s that may be thrown from operations they attempt. The
@@ -52,5 +52,5 @@ public interface AsyncPreparedStatementCreator {
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
* method. The {@link AsyncCqlTemplate} class will handle them.
*/
ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException;
CompletionStage<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException;
}

View File

@@ -15,12 +15,12 @@
*/
package org.springframework.data.cassandra.core.cql;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import java.util.concurrent.CompletableFuture;
import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
/**
* Callback interface used by {@link AsyncCqlTemplate}'s query methods. Implementations of this interface perform the
@@ -52,6 +52,5 @@ public interface AsyncResultSetExtractor<T> {
* there's no need to catch {@link DriverException})
* @throws DataAccessException in case of custom exceptions
*/
@Nullable
ListenableFuture<T> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException;
CompletableFuture<T> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException;
}

View File

@@ -15,17 +15,17 @@
*/
package org.springframework.data.cassandra.core.cql;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collector;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Asynchronous supplied sequence of elements supporting sequential operations over a {@link AsyncResultSet a result
@@ -84,11 +84,11 @@ class AsyncResultStream<T> {
* @param collector the {@link Collector} describing the reduction
* @return the result of the reduction
*/
<R, A> ListenableFuture<R> collect(Collector<? super T, A, R> collector) {
<R, A> CompletableFuture<R> collect(Collector<? super T, A, R> collector) {
Assert.notNull(collector, "Collector must not be null");
SettableListenableFuture<R> future = new SettableListenableFuture<>();
CompletableFuture<R> future = new CompletableFuture<>();
CollectState<A, R> collectState = new CollectState<>(collector);
collectState.collectAsync(future, this.resultSet);
@@ -106,11 +106,11 @@ class AsyncResultStream<T> {
*
* @param action a non-interfering action to perform on the elements.
*/
ListenableFuture<Void> forEach(Consumer<T> action) {
CompletableFuture<Void> forEach(Consumer<T> action) {
Assert.notNull(action, "Action must not be null");
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
CompletableFuture<Void> future = new CompletableFuture<>();
ForwardLoopState loopState = new ForwardLoopState(action);
loopState.forEachAsync(future, this.resultSet);
@@ -140,7 +140,7 @@ class AsyncResultStream<T> {
* @param target
* @param resultSet
*/
void forEachAsync(SettableListenableFuture<Void> target, AsyncResultSet resultSet) {
void forEachAsync(CompletableFuture<Void> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
@@ -149,12 +149,12 @@ class AsyncResultStream<T> {
try {
peekRow(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
target.completeExceptionally(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(null);
target.complete(null);
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
@@ -162,7 +162,7 @@ class AsyncResultStream<T> {
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
target.completeExceptionally(throwable);
} else {
forEachAsync(target, nextResultSet);
}
@@ -202,7 +202,7 @@ class AsyncResultStream<T> {
* @param target
* @param resultSet
*/
void collectAsync(SettableListenableFuture<R> target, AsyncResultSet resultSet) {
void collectAsync(CompletableFuture<R> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
@@ -211,12 +211,12 @@ class AsyncResultStream<T> {
try {
collectPage(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
target.completeExceptionally(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(finish());
target.complete(finish());
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
@@ -224,7 +224,7 @@ class AsyncResultStream<T> {
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
target.completeExceptionally(throwable);
} else {
collectAsync(target, nextResultSet);
}

View File

@@ -16,11 +16,11 @@
package org.springframework.data.cassandra.core.cql;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
@@ -57,7 +57,7 @@ public class AsyncRowMapperResultSetExtractor<T> implements AsyncResultSetExtrac
}
@Override
public ListenableFuture<List<T>> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException {
public CompletableFuture<List<T>> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException {
return AsyncResultStream.from(resultSet).map(rowMapper).collect(Collectors.toList());
}
}

View File

@@ -15,22 +15,24 @@
*/
package org.springframework.data.cassandra.core.cql;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import java.util.concurrent.CompletableFuture;
import org.springframework.dao.DataAccessException;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
/**
* Generic callback interface for code that operates asynchronously on a Cassandra {@link CqlSession}. Allows to execute
* any number of operations on a single session, using any type and number of statements.
* <p>
* This is particularly useful for delegating to existing data access code that expects a {@link CqlSession} to work on
* and throws {@link DriverException}. For newly written code, it is strongly recommended to use {@link CqlTemplate}'s
* more specific operations, for example a {@code query} or {@code update} variant.
* and throws {@link DriverException}. For newly written code, it is strongly recommended to use
* {@link AsyncCqlTemplate}'s more specific operations, for example a {@code query} or {@code update} variant.
*
* @author David Webb
* @author Mark Paluch
* @since 2.0
* @see AsyncCqlTemplate#execute(AsyncSessionCallback)
* @see AsyncCqlTemplate#query
* @see AsyncCqlTemplate#execute(String)
@@ -39,20 +41,20 @@ import org.springframework.util.concurrent.ListenableFuture;
public interface AsyncSessionCallback<T> {
/**
* Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link CqlSession}. Does not need to care about
* activating or closing the {@link CqlSession}.
* Gets called by {@link AsyncCqlTemplate#execute} with an active Cassandra {@link CqlSession}. Does not need to care
* about activating or closing the {@link CqlSession}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see {@link CqlTemplate#queryForObject} etc. A
* thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* objects. Note that there's special support for single step actions: see {@link AsyncCqlTemplate#queryForObject}
* etc. A thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* template.
*
* @param session active Cassandra Session, must not be {@literal null}.
* @return a result object, or {@code ListenableFuture<Void>} if none.
* @return a result object, or {@code CompletableFuture<Void>} if none.
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
* @throws DataAccessException in case of custom exceptions.
* @see AsyncCqlTemplate#queryForObject(String, Class)
* @see AsyncCqlTemplate#queryForResultSet(String)
*/
ListenableFuture<T> doInSession(CqlSession session) throws DriverException, DataAccessException;
CompletableFuture<T> doInSession(CqlSession session) throws DriverException, DataAccessException;
}

View File

@@ -25,7 +25,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet;
/**
* Convenient converter that can be used to convert a single-row-single-column, single-row-multi-column, or multi-row
* {@link ResultSet} into the a value of a given type. The majority of the expected usage is to convert a
* {@link ResultSet} into the given value of a given type. The majority of the expected usage is to convert a
* single-row-single-column result set into the target type.
* <p>
* The algorithm is:

View File

@@ -0,0 +1,742 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import java.util.List;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.cassandra.core.cql.ColumnMapRowMapper;
import org.springframework.data.cassandra.core.cql.CqlOperations;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.cql.PreparedStatementBinder;
import org.springframework.data.cassandra.core.cql.PreparedStatementCallback;
import org.springframework.data.cassandra.core.cql.RowCallbackHandler;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.cql.SingleColumnRowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* Interface specifying a basic set of CQL asynchronously executed operations. Exposes similar methods as
* {@link CqlTemplate}, but returns result handles or accepts callbacks as opposed to concrete results. Implemented by
* {@link AsyncCqlTemplate}. Not often used directly, but a useful option to enhance testability, as it can easily be
* mocked or stubbed.
*
* @author Mark Paluch
* @author John Blum
* @since 4.0
* @see AsyncCqlTemplate
* @see CqlOperations
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant
* {@link org.springframework.data.cassandra.core.cql.AsyncCqlOperations}.
*/
@Deprecated(since = "4.0", forRemoval = true)
public interface AsyncCqlOperations {
// -------------------------------------------------------------------------
// Methods dealing with a plain com.datastax.oss.driver.api.core.CqlSession
// -------------------------------------------------------------------------
/**
* Execute a CQL data access operation, implemented as callback action working on a
* {@link com.datastax.oss.driver.api.core.CqlSession}. This allows for implementing arbitrary data access operations,
* within Spring's managed CQL environment: that is, converting CQL
* {@link com.datastax.oss.driver.api.core.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param action the callback object that specifies the action.
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql) throws DataAccessException;
/**
* Issue a single CQL operation (such as an insert, update or delete statement) via a prepared statement, binding the
* given arguments.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException;
/**
* Issue an statement using a {@link PreparedStatementBinder} to set bind parameters, with given CQL. Simpler than
* using a {@link AsyncPreparedStatementCreator} as this method will create the {@link PreparedStatement}: The
* {@link PreparedStatementBinder} just needs to set parameters.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb) throws DataAccessException;
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
* This allows for implementing arbitrary data access operations on a single Statement, within Spring's managed CQL
* environment: that is, participating in Spring-managed transactions and converting CQL
* {@link com.datastax.oss.driver.api.core.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param action callback object that specifies the action, must not be {@literal null}.
* @return a result object returned by the action, or {@literal null}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param resultSetExtractor object that will extract all rows of results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the AsyncResultSetExtractor.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, AsyncResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
* {@link ResultSet} with a {@link AsyncResultSetExtractor}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param resultSetExtractor object that will extract results, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
* {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping each
* row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowMapper object that will map one object per row
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @return the result {@link List}, containing mapped objects
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param resultSetExtractor object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
* knows how to bind values to the query, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
* knows how to bind values to the query, mapping each row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @return a {@link List} that contains a {@link Map} per row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result {@link List}.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column,
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link List} that contains a {@link Map} per row
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String)
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result {@link List}.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class)
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException;
/**
* Execute a query for a result Map, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
* as argument array.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result Map. The queryForMap() methods defined by this interface are appropriate when you don't have a domain model.
* Otherwise, consider using one of the queryForObject() methods.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return the result Map (one entry for each column, using the column name as the key).
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String)
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
* {@literal null} as argument array.
* <p>
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
* column query; the returned result will be directly mapped to the corresponding object type.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @return the result object of the required type, or {@literal null} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result object.
* <p>
* The query is expected to be a single row/single column query; the returned result will be directly mapped to the
* corresponding object type.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the PreparedStatement to guess the corresponding CQL
* type)
* @return the result object of the required type, or {@literal null} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class)
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
* {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the single mapped object.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping a
* single result row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @return the single mapped object
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
* array.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* ResultSet.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param cql static CQL to execute, must not be {@literal null} or empty.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String)
*/
ListenableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
/**
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param resultSetExtractor object that will extract all rows of results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the AsyncResultSetExtractor.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, AsyncResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
throws DataAccessException;
/**
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param statement static CQL {@link Statement} to execute, must not be {@literal null} or empty.
* @return a {@link List} that contains a {@link Map} per row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType) throws DataAccessException;
/**
* Execute a query for a result Map, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
* as argument array.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
* {@literal null} as argument array.
* <p>
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
* column query; the returned result will be directly mapped to the corresponding object type.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @return the result object of the required type, or {@literal null} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
* {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the single mapped object.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
* array.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.PreparedStatement
// -------------------------------------------------------------------------
/**
* Issue a single CQL execute operation (such as an insert, update or delete statement) using a
* {@link AsyncPreparedStatementCreator} to provide CQL and any required parameters.
*
* @param preparedStatementCreator object that provides CQL and any necessary parameters, must not be {@literal null}.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator) throws DataAccessException;
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
* This allows for implementing arbitrary data access operations on a single {@link PreparedStatement}, within
* Spring's managed CQL environment: that is, participating in Spring-managed transactions and converting CQL
* {@link com.datastax.oss.driver.api.core.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param action callback object that specifies the action, must not be {@literal null}.
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
PreparedStatementCallback<T> action) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param resultSetExtractor object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
* Query using a prepared statement, mapping each row to a Java object via a {@link RowMapper}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator, RowMapper<T> rowMapper)
throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, reading the {@link ResultSet} with a {@link AsyncResultSetExtractor}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param resultSetExtractor object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link AsyncResultSetExtractor}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, reading the {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowCallbackHandler object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler) throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, mapping each row to a Java object via a {@link RowMapper}.
*
* @param preparedStatementCreator object that can create a {@link PreparedStatement} given a
* {@link com.datastax.oss.driver.api.core.CqlSession}, must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException;
}

View File

@@ -0,0 +1,700 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.cql.CassandraAccessor;
import org.springframework.data.cassandra.core.cql.CqlProvider;
import org.springframework.data.cassandra.core.cql.PreparedStatementBinder;
import org.springframework.data.cassandra.core.cql.PreparedStatementCallback;
import org.springframework.data.cassandra.core.cql.PreparedStatementCreator;
import org.springframework.data.cassandra.core.cql.ResultSetExtractor;
import org.springframework.data.cassandra.core.cql.RowCallbackHandler;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.cql.RowMapperResultSetExtractor;
import org.springframework.data.cassandra.core.cql.util.CassandraFutureAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* <b>This is the central class in the CQL core package for asynchronous Cassandra data access.</b> It simplifies the
* use of CQL and helps to avoid common errors. It executes core CQL workflow, leaving application code to provide CQL
* and extract results. This class executes CQL queries or updates, initiating iteration over {@link ResultSet}s and
* catching {@link DriverException} exceptions and translating them to the generic, more informative exception hierarchy
* defined in the {@code org.springframework.dao} package.
* <p>
* Code using this class need only implement callback interfaces, giving them a clearly defined contract. The
* {@link PreparedStatementCreator} callback interface creates a prepared statement given a Connection, providing CQL
* and any necessary parameters. The {@link AsyncResultSetExtractor} interface extracts values from a {@link ResultSet}.
* See also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces.
* <p>
* Can be used within a service implementation via direct instantiation with a {@link CqlSession} reference, or get
* prepared in an application context and given to services as bean reference. Note: The {@link CqlSession} should
* always be configured as a bean in the application context, in the first case given to the service directly, in the
* second case to the prepared template.
* <p>
* Because this class is parameterizable by the callback interfaces and the {@link PersistenceExceptionTranslator}
* interface, there should be no need to subclass it.
* <p>
* All CQL operations performed by this class are logged at debug level, using
* "org.springframework.data.cassandra.core.cqlTemplate" as log category.
* <p>
* <b>NOTE: An instance of this class is thread-safe once configured.</b>
*
* @author Mark Paluch
* @author John Blum
* @since 4.0
* @see ListenableFuture
* @see PreparedStatementCreator
* @see PreparedStatementBinder
* @see PreparedStatementCallback
* @see AsyncResultSetExtractor
* @see RowCallbackHandler
* @see RowMapper
* @see PersistenceExceptionTranslator
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant
* {@link org.springframework.data.cassandra.core.cql.AsyncCqlTemplate}.
*/
@Deprecated(since = "4.0", forRemoval = true)
public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOperations {
/**
* Create a new, uninitialized {@link AsyncCqlTemplate}. Note: The {@link SessionFactory} has to be set before using
* the instance.
*
* @see #setSessionFactory(SessionFactory)
*/
public AsyncCqlTemplate() {}
/**
* Create a new {@link AsyncCqlTemplate} with the given {@link CqlSession}.
*
* @param session the active Cassandra {@link CqlSession}, must not be {@literal null}.
* @throws IllegalStateException if {@link CqlSession} is {@literal null}.
*/
public AsyncCqlTemplate(CqlSession session) {
Assert.notNull(session, "Session must not be null");
setSession(session);
}
/**
* Constructs a new {@link AsyncCqlTemplate} with the given {@link SessionFactory}.
*
* @param sessionFactory the active Cassandra {@link SessionFactory}.
* @since 2.0
* @see SessionFactory
*/
public AsyncCqlTemplate(SessionFactory sessionFactory) {
Assert.notNull(sessionFactory, "SessionFactory must not be null");
setSessionFactory(sessionFactory);
}
// -------------------------------------------------------------------------
// Methods dealing with a plain com.datastax.oss.driver.api.core.CqlSession
// -------------------------------------------------------------------------
@Override
public <T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
try {
return action.doInSession(getCurrentSession());
} catch (DriverException e) {
throw translateException("SessionCallback", toCql(action), e);
}
}
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(String cql) throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
return new MappingListenableFutureAdapter<>(queryForResultSet(cql), AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
Assert.notNull(resultSetExtractor, "AsyncResultSetExtractor must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executing CQL statement [%s]", cql));
}
CompletionStage<T> results = getCurrentSession().executeAsync(applyStatementSettings(newStatement(cql)))
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
return new CassandraFutureAdapter<>(results, ex -> translateExceptionIfPossible("Query", cql, ex));
} catch (DriverException e) {
throw translateException("Query", cql, e);
}
}
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(cql, newAsyncResultSetExtractor(rowCallbackHandler));
return new MappingListenableFutureAdapter<>(results, o -> null);
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException {
return query(cql, newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException {
return queryForObject(cql, newColumnMapRowMapper());
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException {
return queryForObject(cql, newSingleColumnRowMapper(requiredType));
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException {
ListenableFuture<List<T>> results = query(cql, newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(String cql) throws DataAccessException {
return query(cql, AsyncCqlTemplate::toResultSet);
}
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(Statement<?> statement) throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
return new MappingListenableFutureAdapter<>(queryForResultSet(statement), AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> query(Statement<?> statement, AsyncResultSetExtractor<T> resultSetExtractor)
throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
Assert.notNull(resultSetExtractor, "AsyncResultSetExtractor must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executing statement [%s]", toCql(statement)));
}
CompletionStage<T> results = getCurrentSession() //
.executeAsync(applyStatementSettings(statement)) //
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
return new CassandraFutureAdapter<>(results, ex -> translateExceptionIfPossible("Query", toCql(statement), ex));
} catch (DriverException e) {
throw translateException("Query", toCql(statement), e);
}
}
@Override
public ListenableFuture<Void> query(Statement<?> statement, RowCallbackHandler rowCallbackHandler)
throws DataAccessException {
ListenableFuture<?> result = query(statement, newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(result, o -> null),
getExceptionTranslator());
}
@Override
public <T> ListenableFuture<List<T>> query(Statement<?> statement, RowMapper<T> rowMapper)
throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(Statement<?> statement) throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
@Override
public <T> ListenableFuture<List<T>> queryForList(Statement<?> statement, Class<T> elementType)
throws DataAccessException {
return query(statement, newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(Statement<?> statement) throws DataAccessException {
return queryForObject(statement, newColumnMapRowMapper());
}
@Override
public <T> ListenableFuture<T> queryForObject(Statement<?> statement, Class<T> requiredType)
throws DataAccessException {
return queryForObject(statement, newSingleColumnRowMapper(requiredType));
}
@Override
public <T> ListenableFuture<T> queryForObject(Statement<?> statement, RowMapper<T> rowMapper)
throws DataAccessException {
ListenableFuture<List<T>> results = query(statement, newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(Statement<?> statement) throws DataAccessException {
return query(statement, AsyncCqlTemplate::toResultSet);
}
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.PreparedStatement
// -------------------------------------------------------------------------
@Override
public ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator preparedStatementCreator)
throws DataAccessException {
return new MappingListenableFutureAdapter<>(query(preparedStatementCreator, AsyncCqlTemplate::toResultSet),
AsyncResultSet::wasApplied);
}
@Override
public ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException {
return execute(cql, newPreparedStatementBinder(args));
}
@Override
public ListenableFuture<Boolean> execute(String cql, @Nullable PreparedStatementBinder psb)
throws DataAccessException {
return new MappingListenableFutureAdapter<>(
query(newAsyncPreparedStatementCreator(cql), psb, AsyncCqlTemplate::toResultSet), AsyncResultSet::wasApplied);
}
@Override
public <T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(newAsyncPreparedStatementCreator(cql), action);
}
@Override
public <T> ListenableFuture<T> execute(AsyncPreparedStatementCreator preparedStatementCreator,
PreparedStatementCallback<T> action) throws DataAccessException {
Assert.notNull(preparedStatementCreator, "PreparedStatementCreator must not be null");
Assert.notNull(action, "PreparedStatementCallback object must not be null");
PersistenceExceptionTranslator exceptionTranslator = ex -> translateExceptionIfPossible("PreparedStatementCallback",
toCql(preparedStatementCreator), ex);
try {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Preparing statement [%s] using %s", toCql(preparedStatementCreator),
preparedStatementCreator));
}
CqlSession currentSession = getCurrentSession();
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
preparedStatementCreator.createPreparedStatement(currentSession), preparedStatement -> {
try {
return action.doInPreparedStatement(currentSession, preparedStatement);
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}), getExceptionTranslator());
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
return query(preparedStatementCreator, null, resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(preparedStatementCreator, null, newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
}
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
RowMapper<T> rowMapper) throws DataAccessException {
return query(preparedStatementCreator, null, newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
Assert.notNull(preparedStatementCreator, "AsyncPreparedStatementCreator must not be null");
Assert.notNull(resultSetExtractor, "AsyncResultSetExtractor object must not be null");
PersistenceExceptionTranslator exceptionTranslator = ex -> translateExceptionIfPossible("Query",
toCql(preparedStatementCreator), ex);
try {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Preparing statement [%s] using %s", toCql(preparedStatementCreator),
preparedStatementCreator));
}
CqlSession session = getCurrentSession();
ListenableFuture<Statement<?>> statementFuture = new MappingListenableFutureAdapter<>(
preparedStatementCreator.createPreparedStatement(session), preparedStatement -> {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Executing prepared statement [%s]", toCql(preparedStatement)));
}
return applyStatementSettings(psb != null ? psb.bindValues(preparedStatement) : preparedStatement.bind());
});
CompletableFuture<T> result = statementFuture.completable() //
.thenCompose(session::executeAsync) //
.thenApply(resultSetExtractor::extractData) //
.thenCompose(ListenableFuture::completable);
return new CassandraFutureAdapter<>(result, exceptionTranslator);
} catch (DriverException e) {
throw translateException(exceptionTranslator, e);
}
}
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(preparedStatementCreator, psb, newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
}
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator preparedStatementCreator,
@Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException {
return query(preparedStatementCreator, psb, newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(String cql, AsyncResultSetExtractor<T> resultSetExtractor, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args), resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rowCallbackHandler, Object... args)
throws DataAccessException {
ListenableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowMapper));
}
@Override
public <T> ListenableFuture<T> query(String cql, @Nullable PreparedStatementBinder psb,
AsyncResultSetExtractor<T> resultSetExtractor) throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, resultSetExtractor);
}
@Override
public ListenableFuture<Void> query(String cql, @Nullable PreparedStatementBinder psb,
RowCallbackHandler rowCallbackHandler) throws DataAccessException {
ListenableFuture<?> results = query(newAsyncPreparedStatementCreator(cql), psb,
newAsyncResultSetExtractor(rowCallbackHandler));
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(results, o -> null),
getExceptionTranslator());
}
@Override
public <T> ListenableFuture<List<T>> query(String cql, @Nullable PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, newAsyncResultSetExtractor(rowMapper));
}
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(newColumnMapRowMapper()));
}
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(newSingleColumnRowMapper(elementType)));
}
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException {
return queryForObject(cql, newColumnMapRowMapper(), args);
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args)
throws DataAccessException {
return queryForObject(cql, newSingleColumnRowMapper(requiredType), args);
}
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
ListenableFuture<List<T>> results = query(newAsyncPreparedStatementCreator(cql), newPreparedStatementBinder(args),
newAsyncResultSetExtractor(rowMapper));
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(results, DataAccessUtils::requiredSingleResult), getExceptionTranslator());
}
@Override
public ListenableFuture<AsyncResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException {
return query(cql, AsyncCqlTemplate::toResultSet, args);
}
// -------------------------------------------------------------------------
// Implementation hooks and helper methods
// -------------------------------------------------------------------------
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
*
* @param task readable text describing the task being attempted
* @param cql CQL query or update that caused the problem (may be {@literal null})
* @param ex the offending {@code RuntimeException}.
* @return the exception translation {@link Function}
* @see CqlProvider
*/
protected DataAccessException translateException(String task, @Nullable String cql, DriverException ex) {
return translate(task, cql, ex);
}
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
*
* @param task readable text describing the task being attempted
* @param cql CQL query or update that caused the problem (may be {@literal null})
* @param ex the offending {@code RuntimeException}.
* @return the translated {@link DataAccessException} or {@literal null} if translation not possible.
* @see CqlProvider
*/
@Nullable
protected DataAccessException translateExceptionIfPossible(String task, @Nullable String cql, RuntimeException ex) {
return translate(task, cql, ex);
}
/**
* Create a new CQL-based {@link AsyncPreparedStatementCreator} using the CQL passed in. By default, we'll create an
* {@link SimpleAsyncPreparedStatementCreator}. This method allows for the creation to be overridden by subclasses.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return the new {@link AsyncPreparedStatementCreator} to use
*/
protected AsyncPreparedStatementCreator newAsyncPreparedStatementCreator(String cql) {
return new SimpleAsyncPreparedStatementCreator(
(SimpleStatement) applyStatementSettings(SimpleStatement.newInstance(cql)),
ex -> translateExceptionIfPossible("PrepareStatement", cql, ex));
}
/**
* Constructs a new instance of the {@link ResultSetExtractor} adapting the given {@link RowCallbackHandler}.
*
* @param rowCallbackHandler {@link RowCallbackHandler} to adapt as a {@link ResultSetExtractor}.
* @return a {@link ResultSetExtractor} implementation adapting an instance of the {@link RowCallbackHandler}.
* @see AsyncRowCallbackHandlerResultSetExtractor
* @see ResultSetExtractor
* @see RowCallbackHandler
*/
protected AsyncRowCallbackHandlerResultSetExtractor newAsyncResultSetExtractor(
RowCallbackHandler rowCallbackHandler) {
return new AsyncRowCallbackHandlerResultSetExtractor(rowCallbackHandler);
}
/**
* Constructs a new instance of the {@link ResultSetExtractor} adapting the given {@link RowMapper}.
*
* @param rowMapper {@link RowMapper} to adapt as a {@link ResultSetExtractor}.
* @return a {@link ResultSetExtractor} implementation adapting an instance of the {@link RowMapper}.
* @see ResultSetExtractor
* @see RowMapper
* @see RowMapperResultSetExtractor
*/
protected <T> AsyncRowMapperResultSetExtractor<T> newAsyncResultSetExtractor(RowMapper<T> rowMapper) {
return new AsyncRowMapperResultSetExtractor<>(rowMapper);
}
private CqlSession getCurrentSession() {
SessionFactory sessionFactory = getSessionFactory();
Assert.state(sessionFactory != null, "SessionFactory is null");
return sessionFactory.getSession();
}
private static ListenableFuture<AsyncResultSet> toResultSet(AsyncResultSet resultSet) {
SettableListenableFuture<AsyncResultSet> future = new SettableListenableFuture<>();
future.set(resultSet);
return future;
}
private static RuntimeException translateException(PersistenceExceptionTranslator exceptionTranslator,
DriverException e) {
DataAccessException translated = exceptionTranslator.translateExceptionIfPossible(e);
return translated == null ? e : translated;
}
private static class SimpleAsyncPreparedStatementCreator implements AsyncPreparedStatementCreator, CqlProvider {
private final PersistenceExceptionTranslator exceptionTranslator;
private final SimpleStatement statement;
private SimpleAsyncPreparedStatementCreator(SimpleStatement statement,
PersistenceExceptionTranslator exceptionTranslator) {
this.statement = statement;
this.exceptionTranslator = exceptionTranslator;
}
@Override
public ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException {
return new CassandraFutureAdapter<>(session.prepareAsync(this.statement), exceptionTranslator);
}
@Override
public String getCql() {
return this.statement.getQuery();
}
}
/**
* Adapter to enable use of a {@link RowCallbackHandler} inside a {@link ResultSetExtractor}.
*/
protected static class AsyncRowCallbackHandlerResultSetExtractor implements AsyncResultSetExtractor<Void> {
private final RowCallbackHandler rowCallbackHandler;
protected AsyncRowCallbackHandlerResultSetExtractor(RowCallbackHandler rowCallbackHandler) {
this.rowCallbackHandler = rowCallbackHandler;
}
@Override
@Nullable
public ListenableFuture<Void> extractData(AsyncResultSet resultSet) {
return AsyncResultStream.from(resultSet).forEach(rowCallbackHandler::processRow);
}
}
private static class MappingListenableFutureAdapter<T, S>
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
private final Function<S, T> mapper;
private MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
super(adaptee);
this.mapper = mapper;
}
@Override
protected T adapt(S adapteeResult) throws ExecutionException {
return mapper.apply(adapteeResult);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.CqlProvider;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.cql.PreparedStatementCallback;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
/**
* One of the two central callback interfaces used by the
* {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate} class. This interface prepares a CQL
* statement returning a {@link ListenableFuture} given a {@link CqlSession}, provided by the {@link CqlTemplate} class.
* <p>
* Implementations may either create new prepared statements or reuse cached instances. Implementations do not need to
* concern themselves with {@link DriverException}s that may be thrown from operations they attempt. The
* {@link org.springframework.data.cassandra.core.cql.AsyncCqlTemplate} class will catch and handle
* {@link DriverException}s appropriately.
* <p>
* Classes implementing this interface should also implement the {@link CqlProvider} interface if it is able to provide
* the CQL it uses for {@link PreparedStatement} creation. This allows for better contextual information in case of
* exceptions.
*
* @author Mark Paluch
* @since 4.0
* @see org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate#execute(AsyncPreparedStatementCreator,
* PreparedStatementCallback)
* @see CqlProvider
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
public interface AsyncPreparedStatementCreator {
/**
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}s. The
* {@link CqlTemplate} will attempt to cache the {@link PreparedStatement}s for future use without the overhead of
* re-preparing on the entire cluster.
*
* @param session Session to use to create statement, must not be {@literal null}.
* @return a prepared statement.
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
* method. The {@link AsyncCqlTemplate} class will handle them.
*/
ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 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
*
* https://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.cql.legacy;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.RowCallbackHandler;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
/**
* Callback interface used by {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate}'s query
* methods. Implementations of this interface perform the actual work of extracting results from a
* {@link AsyncResultSet}, but don't need to worry about exception handling. {@link DriverException}s will be caught and
* handled by the calling {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate}.
* <p>
* This interface is mainly used within the CQL framework itself. A {@link RowMapper} is usually a simpler choice for
* {@link AsyncResultSet} processing, mapping one result object per row instead of one result object for the entire
* {@link AsyncResultSet}.
* <p>
* Note: In contrast to a {@link RowCallbackHandler}, a {@link AsyncResultSetExtractor} object is typically stateless
* and thus reusable, as long as it doesn't access stateful resources or keep result state within the object.
*
* @author Mark Paluch
* @since 4.0
* @see AsyncCqlTemplate
* @see RowMapper
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
public interface AsyncResultSetExtractor<T> {
/**
* Implementations must implement this method to process the entire {@link AsyncResultSet}.
*
* @param resultSet {@link AsyncResultSet} to extract data from.
* @return an arbitrary result object, or {@literal null} if none (the extractor will typically be stateful in the
* latter case).
* @throws DriverException if a {@link DriverException} is encountered getting column values or navigating (that is,
* there's no need to catch {@link DriverException})
* @throws DataAccessException in case of custom exceptions
*/
@Nullable
ListenableFuture<T> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException;
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2019-2022 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
*
* https://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.cql.legacy;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collector;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Asynchronous supplied sequence of elements supporting sequential operations over a {@link AsyncResultSet a result
* set}. An asynchronous stream represents a pipeline of operations to process a {@link AsyncResultSet}.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class AsyncResultStream<T> {
private final AsyncResultSet resultSet;
private final RowMapper<T> mapper;
private AsyncResultStream(AsyncResultSet resultSet, RowMapper<T> mapper) {
this.resultSet = resultSet;
this.mapper = mapper;
}
/**
* Creates a {@link AsyncResultStream} given {@link AsyncResultSet}.
*
* @param resultSet the result set to process.
* @return a new {@link AsyncResultStream} instance.
*/
static AsyncResultStream<Row> from(AsyncResultSet resultSet) {
Assert.notNull(resultSet, "AsyncResultSet must not be null");
return new AsyncResultStream<>(resultSet, (row, rowNum) -> row);
}
/**
* Returns a stream consisting of the results of applying the given function to the elements of this stream.
* <p>
* This is an intermediate operation.
*
* @param <R> The element type of the new stream
* @param mapper a non-interfering, stateless {@link RowMapper}.
*/
<R> AsyncResultStream<R> map(RowMapper<R> mapper) {
Assert.notNull(mapper, "RowMapper must not be null");
return new AsyncResultStream<>(resultSet, mapper);
}
/**
* Performs a mutable reduction operation on the elements of this stream using a {@link Collector} resulting in a
* {@link ListenableFuture}.
* <p>
* This is a terminal operation.
*
* @param <R> the type of the result
* @param <A> the intermediate accumulation type of the {@link Collector}
* @param collector the {@link Collector} describing the reduction
* @return the result of the reduction
*/
<R, A> ListenableFuture<R> collect(Collector<? super T, A, R> collector) {
Assert.notNull(collector, "Collector must not be null");
SettableListenableFuture<R> future = new SettableListenableFuture<>();
CollectState<A, R> collectState = new CollectState<>(collector);
collectState.collectAsync(future, this.resultSet);
return future;
}
/**
* Performs an action for each element of this stream. This method returns a {@link ListenableFuture} that completes
* without a value ({@code null}) once all elements have been processed.
* <p>
* This is a terminal operation.
* <p>
* If the action accesses shared state, it is responsible for providing the required synchronization.
*
* @param action a non-interfering action to perform on the elements.
*/
ListenableFuture<Void> forEach(Consumer<T> action) {
Assert.notNull(action, "Action must not be null");
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
ForwardLoopState loopState = new ForwardLoopState(action);
loopState.forEachAsync(future, this.resultSet);
return future;
}
/**
* State object for forward-looping using {@code forEach}.
*/
class ForwardLoopState {
private final AtomicInteger rowNumber = new AtomicInteger();
private final Consumer<T> consumer;
ForwardLoopState(Consumer<T> consumer) {
this.consumer = consumer;
}
void peekRow(Iterable<Row> rows) {
rows.forEach(row -> consumer.accept(mapper.mapRow(row, rowNumber.incrementAndGet())));
}
/**
* Recursive async iteration.
*
* @param target
* @param resultSet
*/
void forEachAsync(SettableListenableFuture<Void> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
}
try {
peekRow(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(null);
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
} else {
forEachAsync(target, nextResultSet);
}
});
}
}
}
/**
* State object for collecting rows using {@code collect}.
*/
class CollectState<A, R> {
private final AtomicInteger rowNumber = new AtomicInteger();
private volatile A intermediate;
private final Collector<? super T, A, R> collector;
CollectState(Collector<? super T, A, R> collector) {
this.collector = collector;
this.intermediate = collector.supplier().get();
}
void collectPage(Iterable<Row> rows) {
for (Row row : rows) {
collector.accumulator().accept(intermediate, mapper.mapRow(row, rowNumber.incrementAndGet()));
}
}
R finish() {
return collector.finisher().apply(intermediate);
}
/**
* Recursive collection.
*
* @param target
* @param resultSet
*/
void collectAsync(SettableListenableFuture<R> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
}
try {
collectPage(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(finish());
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
} else {
collectAsync(target, nextResultSet);
}
});
}
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.ResultSetExtractor;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
/**
* Adapter implementation of the {@link ResultSetExtractor} interface that delegates to a {@link RowMapper} which is
* supposed to create an object for each row. Each object is added to the results List of this
* {@link ResultSetExtractor}.
* <p>
* Useful for the typical case of one object per row in the database table. The number of entries in the results will
* match the number of rows.
* <p>
* Note that a {@link RowMapper} object is typically stateless and thus reusable.
*
* @author Mark Paluch
* @since 4.0
* @see RowMapper
* @see AsyncCqlTemplate
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
public class AsyncRowMapperResultSetExtractor<T> implements AsyncResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
/**
* Create a new {@link AsyncRowMapperResultSetExtractor}.
*
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
*/
public AsyncRowMapperResultSetExtractor(RowMapper<T> rowMapper) {
Assert.notNull(rowMapper, "RowMapper is must not be null");
this.rowMapper = rowMapper;
}
@Override
public ListenableFuture<List<T>> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException {
return AsyncResultStream.from(resultSet).map(rowMapper).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import org.springframework.dao.DataAccessException;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
/**
* Generic callback interface for code that operates asynchronously on a Cassandra {@link CqlSession}. Allows to execute
* any number of operations on a single session, using any type and number of statements.
* <p>
* This is particularly useful for delegating to existing data access code that expects a {@link CqlSession} to work on
* and throws {@link DriverException}. For newly written code, it is strongly recommended to use
* {@link AsyncCqlTemplate}'s more specific operations, for example a {@code query} or {@code update} variant.
*
* @author David Webb
* @author Mark Paluch
* @since 4.0
* @see AsyncCqlTemplate#execute(AsyncSessionCallback)
* @see AsyncCqlTemplate#query
* @see AsyncCqlTemplate#execute(String)
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
public interface AsyncSessionCallback<T> {
/**
* Gets called by {@link AsyncCqlTemplate#execute} with an active Cassandra {@link CqlSession}. Does not need to care
* about activating or closing the {@link CqlSession}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see {@link AsyncCqlTemplate#queryForObject}
* etc. A thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* template.
*
* @param session active Cassandra Session, must not be {@literal null}.
* @return a result object, or {@code ListenableFuture<Void>} if none.
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
* @throws DataAccessException in case of custom exceptions.
* @see AsyncCqlTemplate#queryForObject(String, Class)
* @see AsyncCqlTemplate#queryForResultSet(String)
*/
ListenableFuture<T> doInSession(CqlSession session) throws DriverException, DataAccessException;
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.util.concurrent.SuccessCallback;
/**
* Adapter class to {@link ListenableFuture} {@link ExecutionException} by applying a
* {@link PersistenceExceptionTranslator}.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class ExceptionTranslatingListenableFutureAdapter<T> implements ListenableFuture<T> {
private final ListenableFuture<T> adaptee;
private final ListenableFuture<T> future;
/**
* Create a new {@link ExceptionTranslatingListenableFutureAdapter} given a {@link ListenableFuture} and a
* {@link PersistenceExceptionTranslator}.
*
* @param adaptee must not be {@literal null}.
* @param persistenceExceptionTranslator must not be {@literal null}.
*/
ExceptionTranslatingListenableFutureAdapter(ListenableFuture<T> adaptee,
PersistenceExceptionTranslator persistenceExceptionTranslator) {
Assert.notNull(adaptee, "ListenableFuture must not be null");
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
this.adaptee = adaptee;
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
}
private static <T> ListenableFuture<T> adaptListenableFuture(ListenableFuture<T> listenableFuture,
PersistenceExceptionTranslator exceptionTranslator) {
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<>();
listenableFuture.addCallback(new ListenableFutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {
settableFuture.set(result);
}
@Override
public void onFailure(Throwable ex) {
if (ex instanceof RuntimeException) {
DataAccessException dataAccessException = exceptionTranslator
.translateExceptionIfPossible((RuntimeException) ex);
if (dataAccessException != null) {
settableFuture.setException(dataAccessException);
return;
}
}
settableFuture.setException(ex);
}
});
return settableFuture;
}
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
future.addCallback(callback);
}
@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
future.addCallback(successCallback, failureCallback);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return adaptee.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return adaptee.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
}

View File

@@ -0,0 +1,7 @@
/**
* CQL legacy core asynchronous support for easier migration.
*/
@NonNullApi
package org.springframework.data.cassandra.core.cql.legacy;
import org.springframework.lang.NonNullApi;

View File

@@ -61,8 +61,8 @@ public class CachedPreparedStatementCreator implements PreparedStatementCreator
/**
* Create a new {@link CachedPreparedStatementCreator} given {@link PreparedStatementCache} and
* {@link SimpleStatement} to prepare. Subsequent calls require the a {@link SimpleStatement} object with the same CQL
* test for a cache hit. Otherwise, the statement is likely to be re-prepared.
* {@link SimpleStatement} to prepare. Subsequent calls require the given {@link SimpleStatement} object with the same
* CQL test for a cache hit. Otherwise, the statement is likely to be re-prepared.
*
* @param cache must not be {@literal null}.
* @param statement must not be {@literal null}.
@@ -78,7 +78,7 @@ public class CachedPreparedStatementCreator implements PreparedStatementCreator
/**
* Create a new {@link CachedPreparedStatementCreator} given {@link PreparedStatementCache} and {@code cql} to
* prepare. Subsequent calls require the a CQL statement that {@link String#equals(Object) are equal} to the
* prepare. Subsequent calls require the given CQL statement that {@link String#equals(Object) are equal} to the
* previously used CQL string for a cache hit. Otherwise, the statement is likely to be re-prepared.
*
* @param cache must not be {@literal null}.
@@ -96,8 +96,8 @@ public class CachedPreparedStatementCreator implements PreparedStatementCreator
/**
* Create a new {@link CachedPreparedStatementCreator} given {@link PreparedStatementCache} and {@code cql} to
* prepare. This method applies {@link QueryOptions} to the {@link SimpleStatement} before preparing it. Subsequent
* calls require the a CQL statement that {@link String#equals(Object) are equal} to the previously used CQL string
* for a cache hit. Otherwise, the statement is likely to be re-prepared.
* calls require the given CQL statement that {@link String#equals(Object) are equal} to the previously used CQL
* string for a cache hit. Otherwise, the statement is likely to be re-prepared.
*
* @param cache must not be {@literal null}.
* @param cql must not be {@literal null} or empty.

View File

@@ -35,7 +35,9 @@ import org.springframework.util.concurrent.SuccessCallback;
*
* @since 3.0
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated since 3.0, use {@link CompletableFuture} directly.
*/
@Deprecated
public class CassandraFutureAdapter<T> implements ListenableFuture<T> {
private final CompletableFuture<T> completableFuture;

View File

@@ -0,0 +1,393 @@
/*
* Copyright 2016-2022 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
*
* https://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.legacy;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.DeleteOptions;
import org.springframework.data.cassandra.core.EntityWriteResult;
import org.springframework.data.cassandra.core.InsertOptions;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.WriteResult;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.WriteOptions;
import org.springframework.data.cassandra.core.cql.legacy.AsyncCqlOperations;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.domain.Slice;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* Interface specifying a basic set of asynchronous Cassandra operations. Implemented by {@link AsyncCassandraTemplate}.
* Not often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Mark Paluch
* @author John Blum
* @since 4.0
* @see AsyncCassandraTemplate
* @see AsyncCqlOperations
* @see Statement
* @see InsertOptions
* @see UpdateOptions
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant
* {@link org.springframework.data.cassandra.core.AsyncCassandraTemplate}.
*/
@Deprecated(since = "4.0", forRemoval = true)
public interface AsyncCassandraOperations {
/**
* Expose the underlying {@link AsyncCqlOperations} to allow asynchronous CQL operations.
*
* @return the underlying {@link AsyncCqlOperations}.
* @see AsyncCqlOperations
*/
AsyncCqlOperations getAsyncCqlOperations();
/**
* Returns the underlying {@link CassandraConverter}.
*
* @return the underlying {@link CassandraConverter}.
*/
CassandraConverter getConverter();
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param cql must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
/**
* Execute the given Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* into Spring's DAO exception hierarchy.
*
* @param statement a Cassandra {@link Statement}, must not be {@literal null}.
* @return the {@link AsyncResultSet}.
* @throws DataAccessException if there is any problem executing the query.
* @since 3.2
*/
ListenableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<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.
*
* @param statement the CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param statement must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.data.cassandra.core.query.Query
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities.
*
* @param query the query object used to create a CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param query must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Update the queried entities and return {@literal true} if the update was applied.
*
* @param query must not be {@literal null}.
* @param update must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> update(Query query, Update update, Class<?> entityClass) throws DataAccessException;
/**
* Remove entities (rows)/columns from the table by {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
/**
* Returns the number of rows for the given entity class.
*
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if any problem occurs while executing the query.
*/
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.
*
* @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}.
* @return the number of existing entities.
* @throws DataAccessException if any problem occurs while executing the query.
* @since 2.1
*/
ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* 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 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.
*/
ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
*
* @param query user-provided exists {@link Query} to execute; 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.
* @since 2.1
*/
ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
/**
* Insert the given entity and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity) throws DataAccessException;
/**
* Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link EntityWriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see InsertOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity) throws DataAccessException;
/**
* Update the given entity applying {@link WriteOptions} and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link EntityWriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see UpdateOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> delete(T entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete statement 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 QueryOptions#empty()
*/
ListenableFuture<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete statement 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.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Execute a {@code TRUNCATE} query to remove all entities of a given class.
*
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> truncate(Class<?> entityClass) throws DataAccessException;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2022 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
*
* https://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.legacy;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.Nullable;
/**
* Delegate class to encapsulate lifecycle event configuration and publishing. Event creation is deferred within an
* event {@link Supplier} to delay the actual event object creation.
*
* @author Mark Paluch
* @since 4.0
* @see ApplicationEventPublisher
*/
@Deprecated
class EntityLifecycleEventDelegate {
private @Nullable ApplicationEventPublisher publisher;
private boolean eventsEnabled = true;
public void setPublisher(@Nullable ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public boolean isEventsEnabled() {
return eventsEnabled;
}
public void setEventsEnabled(boolean eventsEnabled) {
this.eventsEnabled = eventsEnabled;
}
/**
* Publish an application event if event publishing is enabled.
*
* @param eventSupplier the supplier for application events.
*/
public void publishEvent(Supplier<?> eventSupplier) {
if (canPublishEvent()) {
publisher.publishEvent(eventSupplier.get());
}
}
private boolean canPublishEvent() {
return publisher != null && eventsEnabled;
}
}

View File

@@ -0,0 +1,359 @@
/*
* Copyright 2019-2022 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
*
* https://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.legacy;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.ReactiveCassandraTemplate;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.querybuilder.condition.Condition;
import com.datastax.oss.driver.api.querybuilder.delete.Delete;
import com.datastax.oss.driver.api.querybuilder.update.Update;
/**
* Common data access operations performed on an entity using a {@link MappingContext} containing mapping metadata.
*
* @author Mark Paluch
* @author John Blum
* @see CassandraTemplate
* @see AsyncCassandraTemplate
* @see ReactiveCassandraTemplate
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class EntityOperations {
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
private final EntityProjectionIntrospector introspector;
EntityOperations(CassandraConverter converter) {
this(converter.getMappingContext(), converter.getCustomConversions(), converter.getProjectionFactory());
}
EntityOperations(MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> context,
CustomConversions conversions, ProjectionFactory projectionFactory) {
this.mappingContext = context;
this.introspector = EntityProjectionIntrospector.create(projectionFactory,
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
.and(((target, underlyingType) -> !conversions.isSimpleType(target))),
context);
}
/**
* Creates a new {@link Entity} for the given bean.
*
* @param entity must not be {@literal null}.
* @return
*/
public <T> Entity<T> forEntity(T entity) {
Assert.notNull(entity, "Bean must not be null");
return MappedEntity.of(entity, getMappingContext());
}
/**
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
*
* @param entity must not be {@literal null}.
* @param conversionService must not be {@literal null}.
* @return
*/
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
Assert.notNull(entity, "Bean must not be null");
Assert.notNull(conversionService, "ConversionService must not be null");
return AdaptibleMappedEntity.of(entity, getMappingContext(), conversionService);
}
/**
* Returns the {@link MappingContext} used by this entity data access operations class to access mapping meta-data
* used to store (map) object to Cassandra tables.
*
* @return the {@link MappingContext} used by this entity data access operations class.
* @see org.springframework.data.cassandra.core.mapping.CassandraMappingContext
*/
CassandraPersistentEntity<?> getRequiredPersistentEntity(Class<?> entityClass) {
return getMappingContext().getRequiredPersistentEntity(ClassUtils.getUserClass(entityClass));
}
/**
* Returns the table name to which the entity shall be persisted.
*
* @param entityClass entity class, must not be {@literal null}.
* @return the table name to which the entity shall be persisted.
*/
CqlIdentifier getTableName(Class<?> entityClass) {
return getRequiredPersistentEntity(entityClass).getTableName();
}
/**
* Introspect the given {@link Class result type} in the context of the {@link Class entity type} whether the returned
* type is a projection and what property paths are participating in the projection.
*
* @param resultType the type to project on. Must not be {@literal null}.
* @param entityType the source domain type. Must not be {@literal null}.
* @return the introspection result.
* @since 3.4
* @see EntityProjectionIntrospector#introspect(Class, Class)
*/
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, Class<D> entityType) {
return introspector.introspect(resultType, entityType);
}
protected MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> getMappingContext() {
return this.mappingContext;
}
/**
* A representation of information about an entity.
*/
interface Entity<T> {
/**
* Returns whether the entity is versioned, i.e. if it contains a version property.
*
* @return
*/
default boolean isVersionedEntity() {
return false;
}
/**
* Returns the value of the version if the entity has a version property, {@literal null} otherwise.
*
* @return
*/
@Nullable
Object getVersion();
/**
* Returns the underlying bean.
*
* @return
*/
T getBean();
/**
* Returns whether the entity is considered to be new.
*
* @return
*/
boolean isNew();
}
/**
* Information and commands on an entity.
*/
interface AdaptibleEntity<T> extends Entity<T> {
/**
* Appends a {@code IF} condition to an {@link Update} statement for optimistic locking to perform the update only
* if the version number matches. This method accepts {@code currentVersionNumber} as the {@link Update} typically
* requires to increment the version number upon assembly time.
*
* @param update the {@link Update} statement to append the condition to.
* @param currentVersionNumber previous version number.
* @return the altered {@link Update} containing the {@code IF} condition for optimistic locking.
*/
StatementBuilder<Update> appendVersionCondition(StatementBuilder<Update> update, Number currentVersionNumber);
/**
* Appends a {@code IF} condition to an {@link Delete} statement for optimistic locking to perform the delete only
* if the version number matches. The {@link #getVersion() version number} is derived from the actual state as
* delete statements typically do not increment the version prior to statement creation.
*
* @param delete the {@link Delete} statement to append the condition to.
* @return the altered {@link Delete} containing the {@code IF} condition for optimistic locking.
* @see #getVersion()
*/
StatementBuilder<Delete> appendVersionCondition(StatementBuilder<Delete> delete);
/**
* Initializes the version property of the of the current entity if available.
*
* @return the entity with the version property updated if available.
*/
T initializeVersionProperty();
/**
* Increments the value of the version property if available.
*
* @return the entity with the version property incremented if available.
*/
T incrementVersion();
/**
* Returns the current version value if the entity has a version property.
*
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
* property.
*/
@Nullable
Number getVersion();
/**
* Returns the {@link CassandraPersistentEntity}.
*
* @return the {@link CassandraPersistentEntity}.
*/
CassandraPersistentEntity<?> getPersistentEntity();
}
private static class MappedEntity<T> implements Entity<T> {
private final CassandraPersistentEntity<?> entity;
private final PersistentPropertyAccessor<T> propertyAccessor;
protected MappedEntity(CassandraPersistentEntity<?> entity, PersistentPropertyAccessor<T> propertyAccessor) {
this.entity = entity;
this.propertyAccessor = propertyAccessor;
}
private static <T> MappedEntity<T> of(T bean,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> context) {
CassandraPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
return new MappedEntity<>(entity, propertyAccessor);
}
@Override
public T getBean() {
return this.propertyAccessor.getBean();
}
@Override
public boolean isNew() {
return this.entity.isNew(getBean());
}
@Override
public boolean isVersionedEntity() {
return this.entity.hasVersionProperty();
}
@Override
@Nullable
public Object getVersion() {
return this.propertyAccessor.getProperty(this.entity.getRequiredVersionProperty());
}
}
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
private final CassandraPersistentEntity<?> entity;
private final ConvertingPropertyAccessor<T> propertyAccessor;
private static <T> AdaptibleEntity<T> of(T bean,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext,
ConversionService conversionService) {
CassandraPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(bean.getClass());
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
return new AdaptibleMappedEntity<>(entity, new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
}
private AdaptibleMappedEntity(CassandraPersistentEntity<?> entity, ConvertingPropertyAccessor<T> propertyAccessor) {
super(entity, propertyAccessor);
this.entity = entity;
this.propertyAccessor = propertyAccessor;
}
@Override
public StatementBuilder<Update> appendVersionCondition(StatementBuilder<Update> update,
Number currentVersionNumber) {
return update.bind((statement, factory) -> {
return statement.if_(Condition.column(getVersionColumnName()).isEqualTo(factory.create(currentVersionNumber)));
});
}
@Override
public StatementBuilder<Delete> appendVersionCondition(StatementBuilder<Delete> delete) {
return delete.bind((statement, factory) -> {
return statement.if_(Condition.column(getVersionColumnName()).isEqualTo(factory.create(getVersion())));
});
}
@Override
public T initializeVersionProperty() {
if (this.entity.hasVersionProperty()) {
CassandraPersistentProperty versionProperty = this.entity.getRequiredVersionProperty();
this.propertyAccessor.setProperty(versionProperty, versionProperty.getType().isPrimitive() ? 1 : 0);
}
return this.propertyAccessor.getBean();
}
@Override
public T incrementVersion() {
CassandraPersistentProperty versionProperty = this.entity.getRequiredVersionProperty();
Number version = getVersion();
Number nextVersion = version == null ? 0 : version.longValue() + 1;
this.propertyAccessor.setProperty(versionProperty, nextVersion);
return this.propertyAccessor.getBean();
}
@Override
@Nullable
public Number getVersion() {
CassandraPersistentProperty versionProperty = this.entity.getRequiredVersionProperty();
return this.propertyAccessor.getProperty(versionProperty, Number.class);
}
@Override
public CassandraPersistentEntity<?> getPersistentEntity() {
return this.entity;
}
private CqlIdentifier getVersionColumnName() {
return this.entity.getRequiredVersionProperty().getColumnName();
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2016-2022 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
*
* https://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.legacy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.lang.Nullable;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* Simple utility class for working with the QueryBuilder API using mapped entities.
* <p>
* Only intended for internal use.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class EntityQueryUtils {
private static final Pattern FROM_REGEX = Pattern.compile(" FROM ([\"]?[\\w]*[\\\\.]?[\\w]*[\"]?)[\\s]?",
Pattern.CASE_INSENSITIVE);
/**
* Read a {@link Slice} of data from the {@link ResultSet} for a {@link Pageable}.
*
* @param resultSet must not be {@literal null}.
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
*/
static <T> Slice<T> readSlice(ResultSet resultSet, RowMapper<T> mapper, int page, int pageSize) {
int toRead = resultSet.getAvailableWithoutFetching();
return readSlice(() -> limit(resultSet.iterator(), toRead), resultSet.getExecutionInfo().getPagingState(), mapper,
page, pageSize);
}
/**
* Read a {@link Slice} of data from the {@link ResultSet} for a {@link Pageable}.
*
* @param resultSet must not be {@literal null}.
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
* @since 3.0
*/
static <T> Slice<T> readSlice(AsyncResultSet resultSet, RowMapper<T> mapper, int page, int pageSize) {
return readSlice(() -> limit(resultSet.currentPage().iterator(), resultSet.remaining()),
resultSet.getExecutionInfo().getPagingState(), mapper, page, pageSize);
}
/**
* Read a {@link Slice} of data from the {@link Iterable} of {@link Row}s for a {@link Pageable}.
*
* @param rows must not be {@literal null}.
* @param pagingState
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
* @since 2.1
*/
static <T> Slice<T> readSlice(Iterable<Row> rows, @Nullable ByteBuffer pagingState, RowMapper<T> mapper, int page,
int pageSize) {
List<T> result = new ArrayList<>(pageSize);
Iterator<Row> iterator = rows.iterator();
int index = 0;
while (iterator.hasNext()) {
T element = mapper.mapRow(iterator.next(), index++);
result.add(element);
}
CassandraPageRequest pageRequest = CassandraPageRequest.of(PageRequest.of(page, pageSize), pagingState);
return new SliceImpl<>(result, pageRequest, pagingState != null);
}
/**
* Extract the table name from a {@link Statement}.
*
* @param statement
* @return
* @since 2.1
*/
static CqlIdentifier getTableName(Statement<?> statement) {
String cql = statement instanceof SimpleStatement ? ((SimpleStatement) statement).getQuery() : statement.toString();
Matcher matcher = FROM_REGEX.matcher(cql);
if (matcher.find()) {
String cqlTableName = matcher.group(1);
int separator = cqlTableName.indexOf('.');
if (separator != -1) {
cqlTableName = cqlTableName.substring(separator + 1);
}
if (cqlTableName.startsWith("\"") || cqlTableName.endsWith("\"")) {
return CqlIdentifier.fromCql(cqlTableName.substring(separator + 1));
}
return CqlIdentifier.fromInternal(cqlTableName);
}
return CqlIdentifier.fromCql("unknown");
}
/**
* Returns a view containing the first {@code limitSize} elements of {@code iterator}. If {@code
* iterator} contains fewer than {@code limitSize} elements, the returned view contains all of its elements. The
* returned iterator supports {@code remove()} if {@code iterator} does.
*
* @param iterator the iterator to limit
* @param limitSize the maximum number of elements in the returned iterator
* @throws IllegalArgumentException if {@code limitSize} is negative
* @since 3.0
*/
private static <T> Iterator<T> limit(Iterator<T> iterator, int limitSize) {
return new Iterator<T>() {
private int count;
@Override
public boolean hasNext() {
return count < limitSize && iterator.hasNext();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
count++;
return iterator.next();
}
@Override
public void remove() {
iterator.remove();
}
};
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2018-2022 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
*
* https://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.legacy;
import java.util.List;
import org.springframework.data.cassandra.core.WriteResult;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* The result of a write operation for an entity.
*
* @author Mark Paluch
* @see WriteResult
*/
@Deprecated(since = "4.0", forRemoval = true)
class EntityWriteResult<T> extends org.springframework.data.cassandra.core.EntityWriteResult<T> {
public EntityWriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows, T entity) {
super(executionInfo, wasApplied, rows, entity);
}
}

View File

@@ -0,0 +1,292 @@
/*
* Copyright 2020-2022 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
*
* https://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.legacy;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.springframework.data.cassandra.core.cql.QueryExtractorDelegate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.function.SingletonSupplier;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
/**
* Support class for Cassandra Template API implementation classes that want to make use of prepared statements.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class PreparedStatementDelegate {
/**
* Bind values held in {@link SimpleStatement} to the {@link PreparedStatement} and apply query options that are set
* or do not match the default value.
*
* @param source
* @param ps
* @return the bound statement.
*/
static BoundStatement bind(SimpleStatement source, PreparedStatement ps) {
BoundStatementBuilder builder = ps.boundStatementBuilder(source.getPositionalValues().toArray());
Mapper mapper = Mapper.INSTANCE;
mapper.from(source.getExecutionProfileName()).whenHasText().to(builder::setExecutionProfileName);
mapper.from(source.getExecutionProfile()).whenNonNull().to(builder::setExecutionProfile);
mapper.from(source.getRoutingKeyspace()).whenNonNull().to(builder::setRoutingKeyspace);
mapper.from(source.getRoutingKey()).whenNonNull().to(builder::setRoutingKey);
mapper.from(source.getRoutingToken()).whenNonNull().to(builder::setRoutingToken);
mapper.from(source.isIdempotent()).whenNonNull().to(builder::setIdempotence);
mapper.from(source.isTracing()).whenNonNull().to(builder::setTracing);
mapper.from(source.getQueryTimestamp()).whenNot(it -> it == Statement.NO_DEFAULT_TIMESTAMP)
.to(builder::setQueryTimestamp);
mapper.from(source.getPagingState()).whenNonNull().to(builder::setPagingState);
mapper.from(source.getPageSize()).whenNot(it -> it == 0L).to(builder::setPageSize);
mapper.from(source.getConsistencyLevel()).whenNonNull().to(builder::setConsistencyLevel);
mapper.from(source.getSerialConsistencyLevel()).whenNonNull().to(builder::setSerialConsistencyLevel);
mapper.from(source.getTimeout()).whenNonNull().to(builder::setTimeout);
mapper.from(source.getNode()).whenNonNull().to(builder::setNode);
mapper.from(source.getNowInSeconds()).whenNot(it -> it == Statement.NO_NOW_IN_SECONDS).to(builder::setNowInSeconds);
Map<CqlIdentifier, Object> namedValues = source.getNamedValues();
ColumnDefinitions variableDefinitions = ps.getVariableDefinitions();
CodecRegistry codecRegistry = builder.codecRegistry();
for (Map.Entry<CqlIdentifier, Object> entry : namedValues.entrySet()) {
if (entry.getValue() == null) {
builder = builder.setToNull(entry.getKey());
} else {
DataType type = variableDefinitions.get(entry.getKey()).getType();
builder = builder.set(entry.getKey(), entry.getValue(), codecRegistry.codecFor(type));
}
}
return builder.build();
}
/**
* Ensure the given {@link Statement} is a {@link SimpleStatement}. Throw a {@link IllegalArgumentException}
* otherwise.
*
* @param statement
* @return the {@link SimpleStatement}.
*/
static SimpleStatement getStatementForPrepare(Statement<?> statement) {
if (statement instanceof SimpleStatement) {
return (SimpleStatement) statement;
}
throw new IllegalArgumentException(getMessage(statement));
}
/**
* Check whether to use prepared statements. When {@code usePreparedStatements} is {@literal true}, then verifying
* additionally that the given {@link Statement} is a {@link SimpleStatement}, otherwise log the mismatch and fallback
* to non-prepared usage.
*
* @param usePreparedStatements
* @param statement
* @param logger
* @return
*/
static boolean canPrepare(boolean usePreparedStatements, Statement<?> statement, Log logger) {
if (usePreparedStatements) {
if (statement instanceof SimpleStatement) {
return true;
}
logger.warn(getMessage(statement));
}
return false;
}
private static String getMessage(Statement<?> statement) {
String cql = QueryExtractorDelegate.getCql(statement);
if (StringUtils.hasText(cql)) {
return String.format("Cannot prepare statement %s (%s); Statement must be a SimpleStatement", cql, statement);
}
return String.format("Cannot prepare statement %s; Statement must be a SimpleStatement", statement);
}
enum Mapper {
INSTANCE;
/**
* Return a new {@link Source} from the specified value supplier that can be used to perform the mapping.
*
* @param <T> the source type
* @param supplier the value supplier
* @return a {@link Source} that can be used to complete the mapping
* @see #from(Object)
*/
public <T> Source<T> from(Supplier<T> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
return getSource(supplier);
}
/**
* Return a new {@link Source} from the specified value that can be used to perform the mapping.
*
* @param <T> the source type
* @param value the value
* @return a {@link Source} that can be used to complete the mapping
*/
public <T> Source<T> from(@Nullable T value) {
return from(() -> value);
}
private <T> Source<T> getSource(Supplier<T> supplier) {
return new Source<>(SingletonSupplier.of(supplier), t -> true);
}
}
/**
* A source value/supplier that is in the process of being mapped.
*
* @param <T> the source type
*/
static class Source<T> {
private final Supplier<T> supplier;
private final Predicate<T> predicate;
private Source(Supplier<T> supplier, Predicate<T> predicate) {
Assert.notNull(predicate, "Predicate must not be null");
this.supplier = supplier;
this.predicate = predicate;
}
/**
* Return a filtered version of the source that won't map non-null values or suppliers that throw a
* {@link NullPointerException}.
*
* @return a new filtered source instance
*/
public Source<T> whenNonNull() {
return new Source<>(this.supplier, Objects::nonNull);
}
/**
* Return a filtered version of the source that will only map values that are {@code true}.
*
* @return a new filtered source instance
*/
public Source<T> whenTrue() {
return when(Boolean.TRUE::equals);
}
/**
* Return a filtered version of the source that will only map values that are {@code false}.
*
* @return a new filtered source instance
*/
public Source<T> whenFalse() {
return when(Boolean.FALSE::equals);
}
/**
* Return a filtered version of the source that will only map values that have a {@code toString()} containing
* actual text.
*
* @return a new filtered source instance
*/
public Source<T> whenHasText() {
return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
}
/**
* Return a filtered version of the source that will only map values equal to the specified {@code object}.
*
* @param object the object to match
* @return a new filtered source instance
*/
public Source<T> whenEqualTo(Object object) {
return when(object::equals);
}
/**
* Return a filtered version of the source that won't map values that match the given predicate.
*
* @param predicate the predicate used to filter values
* @return a new filtered source instance
*/
public Source<T> whenNot(Predicate<T> predicate) {
Assert.notNull(predicate, "Predicate must not be null");
return when(predicate.negate());
}
/**
* Return a filtered version of the source that won't map values that don't match the given predicate.
*
* @param predicate the predicate used to filter values
* @return a new filtered source instance
*/
public Source<T> when(Predicate<T> predicate) {
Assert.notNull(predicate, "Predicate must not be null");
return new Source<>(this.supplier, (this.predicate != null) ? this.predicate.and(predicate) : predicate);
}
/**
* Complete the mapping by passing any non-filtered value to the specified consumer.
*
* @param consumer the consumer that should accept the value if it's not been filtered
*/
public void to(Consumer<T> consumer) {
Assert.notNull(consumer, "Consumer must not be null");
T value = this.supplier.get();
if (this.predicate.test(value)) {
consumer.accept(value);
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2022 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
*
* https://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.legacy;
import java.util.List;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
@Deprecated(since = "4.0", forRemoval = true)
class WriteResult extends org.springframework.data.cassandra.core.WriteResult {
WriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows) {
super(executionInfo, wasApplied, rows);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Apache Cassandra legacy asynchronous support for easier migration.
*/
@NonNullApi
package org.springframework.data.cassandra.core.legacy;
import org.springframework.lang.NonNullApi;

View File

@@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.cql.Statement
import org.springframework.data.cassandra.core.query.Query
import org.springframework.data.cassandra.core.query.Update
import org.springframework.data.domain.Slice
import org.springframework.util.concurrent.ListenableFuture
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
@@ -36,41 +36,63 @@ import kotlin.reflect.KClass
/**
* Extension for [AsyncCassandraOperations.select] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(cql)"))
fun <T : Any> AsyncCassandraOperations.select(cql: String, entityClass: KClass<T>): ListenableFuture<List<T>> =
select(cql, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(cql)")
)
fun <T : Any> AsyncCassandraOperations.select(
cql: String,
entityClass: KClass<T>
): CompletableFuture<List<T>> =
select(cql, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.select(cql: String): ListenableFuture<List<T>> =
select(cql, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.select(cql: String): CompletableFuture<List<T>> =
select(cql, T::class.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(cql, consumer)"))
fun <T : Any> AsyncCassandraOperations.select(cql: String, entityClass: KClass<T>, consumer: (T) -> Unit): ListenableFuture<Void> =
select(cql, consumer, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(cql, consumer)")
)
fun <T : Any> AsyncCassandraOperations.select(
cql: String,
entityClass: KClass<T>,
consumer: (T) -> Unit
): CompletableFuture<Void> =
select(cql, consumer, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.select(cql: String, crossinline consumer: (T) -> Unit): ListenableFuture<Void> =
select(cql, { consumer(it) }, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.select(
cql: String,
crossinline consumer: (T) -> Unit
): CompletableFuture<Void> =
select(cql, { consumer(it) }, T::class.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("selectOne<T>(cql)"))
fun <T : Any> AsyncCassandraOperations.selectOne(cql: String, entityClass: KClass<T>): ListenableFuture<T?> =
selectOne(cql, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("selectOne<T>(cql)")
)
fun <T : Any> AsyncCassandraOperations.selectOne(
cql: String,
entityClass: KClass<T>
): CompletableFuture<T?> =
selectOne(cql, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(cql: String): ListenableFuture<T?> =
selectOne(cql, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(cql: String): CompletableFuture<T?> =
selectOne(cql, T::class.java)
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
@@ -79,54 +101,82 @@ inline fun <reified T : Any> AsyncCassandraOperations.selectOne(cql: String): Li
/**
* Extension for [AsyncCassandraOperations.select] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(statement)"))
fun <T : Any> AsyncCassandraOperations.select(statement: Statement<*>, entityClass: KClass<T>): ListenableFuture<List<T>> =
select(statement, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(statement)")
)
fun <T : Any> AsyncCassandraOperations.select(
statement: Statement<*>,
entityClass: KClass<T>
): CompletableFuture<List<T>> =
select(statement, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.select(statement: Statement<*>): ListenableFuture<List<T>> =
select(statement, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.select(statement: Statement<*>): CompletableFuture<List<T>> =
select(statement, T::class.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(statement, consumer)"))
fun <T : Any> AsyncCassandraOperations.select(statement: Statement<*>, entityClass: KClass<T>, consumer: (T) -> Unit): ListenableFuture<Void> =
select(statement, consumer, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(statement, consumer)")
)
fun <T : Any> AsyncCassandraOperations.select(
statement: Statement<*>,
entityClass: KClass<T>,
consumer: (T) -> Unit
): CompletableFuture<Void> =
select(statement, consumer, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.select(statement: Statement<*>, crossinline consumer: (T) -> Unit): ListenableFuture<Void> =
select(statement, { consumer(it) }, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.select(
statement: Statement<*>,
crossinline consumer: (T) -> Unit
): CompletableFuture<Void> =
select(statement, { consumer(it) }, T::class.java)
/**
* Extension for [AsyncCassandraOperations.slice] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("slice<T>(statement)"))
fun <T : Any> AsyncCassandraOperations.slice(statement: Statement<*>, entityClass: KClass<T>): ListenableFuture<Slice<T>> =
slice(statement, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("slice<T>(statement)")
)
fun <T : Any> AsyncCassandraOperations.slice(
statement: Statement<*>,
entityClass: KClass<T>
): CompletableFuture<Slice<T>> =
slice(statement, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.slice] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.slice(statement: Statement<*>): ListenableFuture<Slice<T>> =
slice(statement, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.slice(statement: Statement<*>): CompletableFuture<Slice<T>> =
slice(statement, T::class.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("selectOne<T>(statement)"))
fun <T : Any> AsyncCassandraOperations.selectOne(statement: Statement<*>, entityClass: KClass<T>): ListenableFuture<T?> =
selectOne(statement, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("selectOne<T>(statement)")
)
fun <T : Any> AsyncCassandraOperations.selectOne(
statement: Statement<*>,
entityClass: KClass<T>
): CompletableFuture<T?> =
selectOne(statement, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(statement: Statement<*>): ListenableFuture<T?> =
selectOne(statement, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(statement: Statement<*>): CompletableFuture<T?> =
selectOne(statement, T::class.java)
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.data.cassandra.core.query.Query
@@ -135,82 +185,129 @@ inline fun <reified T : Any> AsyncCassandraOperations.selectOne(statement: State
/**
* Extension for [AsyncCassandraOperations.select] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(query)"))
fun <T : Any> AsyncCassandraOperations.select(query: Query, entityClass: KClass<T>): ListenableFuture<List<T>> =
select(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.select(
query: Query,
entityClass: KClass<T>
): CompletableFuture<List<T>> =
select(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.select(query: Query): ListenableFuture<List<T>> =
select(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.select(query: Query): CompletableFuture<List<T>> =
select(query, T::class.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(query, consumer)"))
fun <T : Any> AsyncCassandraOperations.select(query: Query, entityClass: KClass<T>, consumer: (T) -> Unit): ListenableFuture<Void> =
select(query, consumer, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(query, consumer)")
)
fun <T : Any> AsyncCassandraOperations.select(
query: Query,
entityClass: KClass<T>,
consumer: (T) -> Unit
): CompletableFuture<Void> =
select(query, consumer, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.select] providing a Consumer-like function leveraging reified type parameters.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("select<T>(query, consumer)"))
inline fun <reified T : Any> AsyncCassandraOperations.select(query: Query, crossinline consumer: (T) -> Unit): ListenableFuture<Void> =
select(query, { consumer(it) }, T::class.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("select<T>(query, consumer)")
)
inline fun <reified T : Any> AsyncCassandraOperations.select(
query: Query,
crossinline consumer: (T) -> Unit
): CompletableFuture<Void> =
select(query, { consumer(it) }, T::class.java)
/**
* Extension for [AsyncCassandraOperations.slice] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("slice<T>(query)"))
fun <T : Any> AsyncCassandraOperations.slice(query: Query, entityClass: KClass<T>): ListenableFuture<Slice<T>> =
slice(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("slice<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.slice(
query: Query,
entityClass: KClass<T>
): CompletableFuture<Slice<T>> =
slice(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.slice] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.slice(query: Query): ListenableFuture<Slice<T>> =
slice(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.slice(query: Query): CompletableFuture<Slice<T>> =
slice(query, T::class.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("selectOne<T>(query)"))
fun <T : Any> AsyncCassandraOperations.selectOne(query: Query, entityClass: KClass<T>): ListenableFuture<T?> =
selectOne(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("selectOne<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.selectOne(
query: Query,
entityClass: KClass<T>
): CompletableFuture<T?> =
selectOne(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.selectOne] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(query: Query): ListenableFuture<T?> =
selectOne(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.selectOne(query: Query): CompletableFuture<T?> =
selectOne(query, T::class.java)
/**
* Extension for [AsyncCassandraOperations.update] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("update<T>(query, update)"))
fun <T : Any> AsyncCassandraOperations.update(query: Query, update: Update, entityClass: KClass<T>): ListenableFuture<Boolean> =
update(query, update, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("update<T>(query, update)")
)
fun <T : Any> AsyncCassandraOperations.update(
query: Query,
update: Update,
entityClass: KClass<T>
): CompletableFuture<Boolean> =
update(query, update, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.update] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.update(query: Query, update: Update): ListenableFuture<Boolean> =
update(query, update, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.update(
query: Query,
update: Update
): CompletableFuture<Boolean> =
update(query, update, T::class.java)
/**
* Extension for [AsyncCassandraOperations.delete] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("delete<T>(query)"))
fun <T : Any> AsyncCassandraOperations.delete(query: Query, entityClass: KClass<T>): ListenableFuture<Boolean> =
delete(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("delete<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.delete(
query: Query,
entityClass: KClass<T>
): CompletableFuture<Boolean> =
delete(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.delete] leveraging reified type parameters.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> AsyncCassandraOperations.delete(query: Query): ListenableFuture<Boolean> =
delete(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.delete(query: Query): CompletableFuture<Boolean> =
delete(query, T::class.java)
// -------------------------------------------------------------------------
// Methods dealing with entities
@@ -220,89 +317,122 @@ inline fun <reified T : Any> AsyncCassandraOperations.delete(query: Query): List
* Extension for [AsyncCassandraOperations.count] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("count<T>()"))
fun <T : Any> AsyncCassandraOperations.count(entityClass: KClass<T>): ListenableFuture<Long> =
count(entityClass.java)
fun <T : Any> AsyncCassandraOperations.count(entityClass: KClass<T>): CompletableFuture<Long> =
count(entityClass.java)
/**
* Extension for [AsyncCassandraOperations.count] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.count(): ListenableFuture<Long> =
count(T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.count(): CompletableFuture<Long> =
count(T::class.java)
/**
* Extension for [AsyncCassandraOperations.count] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("count<T>(query)"))
fun <T : Any> AsyncCassandraOperations.count(query: Query, entityClass: KClass<T>): ListenableFuture<Long> =
count(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("count<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.count(
query: Query,
entityClass: KClass<T>
): CompletableFuture<Long> =
count(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.count] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.count(query: Query): ListenableFuture<Long> =
count(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.count(query: Query): CompletableFuture<Long> =
count(query, T::class.java)
/**
* Extension for [AsyncCassandraOperations.exists] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("exists<T>(id)"))
fun <T : Any> AsyncCassandraOperations.exists(id: Any, entityClass: KClass<T>): ListenableFuture<Boolean> =
exists(id, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("exists<T>(id)")
)
fun <T : Any> AsyncCassandraOperations.exists(
id: Any,
entityClass: KClass<T>
): CompletableFuture<Boolean> =
exists(id, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.exists] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.exists(id: Any): ListenableFuture<Boolean> =
exists(id, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.exists(id: Any): CompletableFuture<Boolean> =
exists(id, T::class.java)
/**
* Extension for [AsyncCassandraOperations.count] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("exists<T>(query)"))
fun <T : Any> AsyncCassandraOperations.exists(query: Query, entityClass: KClass<T>): ListenableFuture<Boolean> =
exists(query, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("exists<T>(query)")
)
fun <T : Any> AsyncCassandraOperations.exists(
query: Query,
entityClass: KClass<T>
): CompletableFuture<Boolean> =
exists(query, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.count] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.exists(query: Query): ListenableFuture<Boolean> =
exists(query, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.exists(query: Query): CompletableFuture<Boolean> =
exists(query, T::class.java)
/**
* Extension for [AsyncCassandraOperations.selectOneById] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("selectOneById<T>(id)"))
fun <T : Any> AsyncCassandraOperations.selectOneById(id: Any, entityClass: KClass<T>): ListenableFuture<T?> =
selectOneById(id, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("selectOneById<T>(id)")
)
fun <T : Any> AsyncCassandraOperations.selectOneById(
id: Any,
entityClass: KClass<T>
): CompletableFuture<T?> =
selectOneById(id, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.selectOneById] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.selectOneById(id: Any): ListenableFuture<T?> =
selectOneById(id, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.selectOneById(id: Any): CompletableFuture<T?> =
selectOneById(id, T::class.java)
/**
* Extension for [AsyncCassandraOperations.deleteById] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("deleteById<T>(id)"))
fun <T : Any> AsyncCassandraOperations.deleteById(id: Any, entityClass: KClass<T>): ListenableFuture<Boolean> =
deleteById(id, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("deleteById<T>(id)")
)
fun <T : Any> AsyncCassandraOperations.deleteById(
id: Any,
entityClass: KClass<T>
): CompletableFuture<Boolean> =
deleteById(id, entityClass.java)
/**
* Extension for [AsyncCassandraOperations.deleteById] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.deleteById(id: Any): ListenableFuture<Boolean> =
deleteById(id, T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.deleteById(id: Any): CompletableFuture<Boolean> =
deleteById(id, T::class.java)
/**
* Extension for [AsyncCassandraOperations.truncate] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("truncate<T>()"))
fun <T : Any> AsyncCassandraOperations.truncate(entityClass: KClass<T>): ListenableFuture<Void> =
truncate(entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("truncate<T>()")
)
fun <T : Any> AsyncCassandraOperations.truncate(entityClass: KClass<T>): CompletableFuture<Void> =
truncate(entityClass.java)
/**
* Extension for [AsyncCassandraOperations.truncate] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCassandraOperations.truncate(): ListenableFuture<Void> =
truncate(T::class.java)
inline fun <reified T : Any> AsyncCassandraOperations.truncate(): CompletableFuture<Void> =
truncate(T::class.java)

View File

@@ -18,7 +18,7 @@ package org.springframework.data.cassandra.core.cql
import com.datastax.oss.driver.api.core.cql.AsyncResultSet
import com.datastax.oss.driver.api.core.cql.Row
import com.datastax.oss.driver.api.core.cql.Statement
import org.springframework.util.concurrent.ListenableFuture
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
@@ -31,86 +31,129 @@ import kotlin.reflect.KClass
/**
* Extension for [AsyncCqlOperations.queryForObject] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("queryForObject<T>(cql)"))
fun <T : Any> AsyncCqlOperations.queryForObject(cql: String, entityClass: KClass<T>): ListenableFuture<T?> =
queryForObject(cql, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("queryForObject<T>(cql)")
)
fun <T : Any> AsyncCqlOperations.queryForObject(
cql: String,
entityClass: KClass<T>
): CompletableFuture<T?> =
queryForObject(cql, entityClass.java)
/**
* Extension for [AsyncCqlOperations.queryForObject] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(cql: String): ListenableFuture<T?> =
queryForObject(cql, T::class.java)
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(cql: String): CompletableFuture<T?> =
queryForObject(cql, T::class.java)
/**
* Extension for [AsyncCqlOperations.queryForObject] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("queryForObject<T>(cql, args)"))
fun <T : Any> AsyncCqlOperations.queryForObject(cql: String, entityClass: KClass<T>, vararg args: Any): ListenableFuture<T?> =
queryForObject(cql, entityClass.java, args)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("queryForObject<T>(cql, args)")
)
fun <T : Any> AsyncCqlOperations.queryForObject(
cql: String,
entityClass: KClass<T>,
vararg args: Any
): CompletableFuture<T?> =
queryForObject(cql, entityClass.java, args)
/**
* Extension for [AsyncCqlOperations.queryForObject] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(cql: String, vararg args: Any): ListenableFuture<T?> =
queryForObject(cql, T::class.java, args)
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(
cql: String,
vararg args: Any
): CompletableFuture<T?> =
queryForObject(cql, T::class.java, args)
/**
* Extension for [AsyncCqlOperations.queryForObject] leveraging reified type parameters.
*/
fun <T : Any> AsyncCqlOperations.queryForObject(cql: String, vararg args: Any, function: (Row, Int) -> T): ListenableFuture<T?> =
queryForObject(cql, RowMapper { row, i -> function(row, i) }, *args)
fun <T : Any> AsyncCqlOperations.queryForObject(
cql: String,
vararg args: Any,
function: (Row, Int) -> T
): CompletableFuture<T?> =
queryForObject(cql, RowMapper { row, i -> function(row, i) }, *args)
/**
* Extension for [AsyncCqlOperations.queryForObject] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("queryForObject<T>(statement)"))
fun <T : Any> AsyncCqlOperations.queryForObject(statement: Statement<*>, entityClass: KClass<T>): ListenableFuture<T?> =
queryForObject(statement, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("queryForObject<T>(statement)")
)
fun <T : Any> AsyncCqlOperations.queryForObject(
statement: Statement<*>,
entityClass: KClass<T>
): CompletableFuture<T?> =
queryForObject(statement, entityClass.java)
/**
* Extension for [AsyncCqlOperations.queryForObject] leveraging reified type parameters.
*/
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(statement: Statement<*>): ListenableFuture<T?> =
queryForObject(statement, T::class.java)
inline fun <reified T : Any> AsyncCqlOperations.queryForObject(statement: Statement<*>): CompletableFuture<T?> =
queryForObject(statement, T::class.java)
/**
* Extension for [AsyncCqlOperations.queryForList] leveraging reified type parameters.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> AsyncCqlOperations.queryForList(cql: String): ListenableFuture<List<T>> =
queryForList(cql, T::class.java)
inline fun <reified T : Any> AsyncCqlOperations.queryForList(cql: String): CompletableFuture<List<T>> =
queryForList(cql, T::class.java)
/**
* Extension for [AsyncCqlOperations.queryForList] leveraging reified type parameters.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> AsyncCqlOperations.queryForList(cql: String, vararg args: Any): ListenableFuture<List<T>> =
queryForList(cql, T::class.java, args)
inline fun <reified T : Any> AsyncCqlOperations.queryForList(
cql: String,
vararg args: Any
): CompletableFuture<List<T>> =
queryForList(cql, T::class.java, args)
/**
* Extension for [AsyncCqlOperations.queryForList] providing a [KClass] based variant.
*/
@Deprecated("Since 2.2, use the reified variant", replaceWith = ReplaceWith("queryForList<T>(statement)"))
fun <T : Any> AsyncCqlOperations.queryForList(statement: Statement<*>, entityClass: KClass<T>): ListenableFuture<List<T>> =
queryForList(statement, entityClass.java)
@Deprecated(
"Since 2.2, use the reified variant",
replaceWith = ReplaceWith("queryForList<T>(statement)")
)
fun <T : Any> AsyncCqlOperations.queryForList(
statement: Statement<*>,
entityClass: KClass<T>
): CompletableFuture<List<T>> =
queryForList(statement, entityClass.java)
/**
* Extension for [AsyncCqlOperations.queryForList] leveraging reified type parameters.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> AsyncCqlOperations.queryForList(statement: Statement<*>): ListenableFuture<List<T>> =
queryForList(statement, T::class.java)
inline fun <reified T : Any> AsyncCqlOperations.queryForList(statement: Statement<*>): CompletableFuture<List<T>> =
queryForList(statement, T::class.java)
/**
* Extension for [AsyncCqlOperations.query] providing a ResultSetExtractor-like function
* variant: `query("...", arg1, argN){ rs -> }`.
*/
inline fun <reified T : Any> AsyncCqlOperations.query(cql: String, vararg args: Any, crossinline function: (AsyncResultSet) -> ListenableFuture<T>): ListenableFuture<T?> =
query(cql, AsyncResultSetExtractor { function(it) }, *args)
inline fun <reified T : Any> AsyncCqlOperations.query(
cql: String,
vararg args: Any,
crossinline function: (AsyncResultSet) -> CompletableFuture<T>
): CompletableFuture<T?> =
query(cql, AsyncResultSetExtractor { function(it) }, *args)
/**
* Extension for [AsyncCqlOperations.query] providing a RowMapper-like function
* variant: `query("...", arg1, argN){ row, i -> }`.
*/
fun <T : Any> AsyncCqlOperations.query(cql: String, vararg args: Any, function: (Row, Int) -> T): ListenableFuture<List<T>> =
query(cql, RowMapper { row, i -> function(row, i) }, *args)
fun <T : Any> AsyncCqlOperations.query(
cql: String,
vararg args: Any,
function: (Row, Int) -> T
): CompletableFuture<List<T>> =
query(cql, RowMapper { row, i -> function(row, i) }, *args)

View File

@@ -38,7 +38,6 @@ import org.springframework.data.cassandra.repository.support.SchemaTestUtils;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.uuid.Uuids;
@@ -117,7 +116,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
assertThat(getUser(user.getId())).isNull();
ListenableFuture<User> insert = template.insert(user);
Future<User> insert = template.insert(user);
assertThat(getUninterruptibly(insert)).isEqualTo(user);
assertThat(getUser(user.getId())).isEqualTo(user);
@@ -130,7 +129,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<EntityWriteResult<User>> inserted = template.insert(user, lwtOptions);
Future<EntityWriteResult<User>> inserted = template.insert(user, lwtOptions);
assertThat(getUninterruptibly(inserted).wasApplied()).isTrue();
}
@@ -146,7 +145,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
user.setFirstname("Walter Hartwell");
ListenableFuture<EntityWriteResult<User>> lwt = template.insert(user, lwtOptions);
Future<EntityWriteResult<User>> lwt = template.insert(user, lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter");
@@ -159,7 +158,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
User result = getUninterruptibly(template.insert(user));
ListenableFuture<Long> count = template.count(User.class);
Future<Long> count = template.count(User.class);
assertThat(result).isSameAs(user);
assertThat(getUninterruptibly(count)).isEqualTo(1L);
}
@@ -207,7 +206,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<EntityWriteResult<User>> lwt = template.update(user, lwtOptions);
Future<EntityWriteResult<User>> lwt = template.update(user, lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId())).isNull();
@@ -223,7 +222,7 @@ class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingInt
user.setFirstname("Walter Hartwell");
ListenableFuture<EntityWriteResult<User>> updated = template.update(user, lwtOptions);
Future<EntityWriteResult<User>> updated = template.update(user, lwtOptions);
assertThat(getUninterruptibly(updated).wasApplied()).isTrue();
assertThat(getUninterruptibly(updated).getEntity()).isSameAs(user);

View File

@@ -46,7 +46,6 @@ import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.domain.VersionedUser;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
@@ -140,7 +139,7 @@ class AsyncCassandraTemplateUnitTests {
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
CompletableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
assertThat(getUninterruptibly(list)).hasSize(1).contains(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
@@ -165,7 +164,7 @@ class AsyncCassandraTemplateUnitTests {
List<User> list = new ArrayList<>();
ListenableFuture<Void> result = template.select("SELECT * FROM users", list::add, User.class);
CompletableFuture<Void> result = template.select("SELECT * FROM users", list::add, User.class);
assertThat(getUninterruptibly(result)).isNull();
assertThat(list).hasSize(1).contains(new User("myid", "Walter", "White"));
@@ -178,7 +177,7 @@ class AsyncCassandraTemplateUnitTests {
when(resultSet.currentPage()).thenThrow(new NoNodeAvailableException());
ListenableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
CompletableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
try {
list.get();
@@ -207,7 +206,7 @@ class AsyncCassandraTemplateUnitTests {
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<User> future = template.selectOne("SELECT * FROM users WHERE id='myid'", User.class);
CompletableFuture<User> future = template.selectOne("SELECT * FROM users WHERE id='myid'", User.class);
assertThat(getUninterruptibly(future)).isEqualTo(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
@@ -230,7 +229,7 @@ class AsyncCassandraTemplateUnitTests {
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<User> future = template.selectOneById("myid", User.class);
CompletableFuture<User> future = template.selectOneById("myid", User.class);
assertThat(getUninterruptibly(future)).isEqualTo(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
@@ -242,7 +241,7 @@ class AsyncCassandraTemplateUnitTests {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.selectOne("SELECT id FROM users WHERE id='myid'", String.class);
CompletableFuture<String> future = template.selectOne("SELECT id FROM users WHERE id='myid'", String.class);
assertThat(getUninterruptibly(future)).isNull();
}
@@ -252,7 +251,7 @@ class AsyncCassandraTemplateUnitTests {
when(resultSet.one()).thenReturn(row);
ListenableFuture<Boolean> future = template.exists("myid", User.class);
CompletableFuture<Boolean> future = template.exists("myid", User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
@@ -262,7 +261,7 @@ class AsyncCassandraTemplateUnitTests {
@Test // DATACASS-292
void existsShouldReturnNonExistingElement() {
ListenableFuture<Boolean> future = template.exists("myid", User.class);
CompletableFuture<Boolean> future = template.exists("myid", User.class);
assertThat(getUninterruptibly(future)).isFalse();
verify(session).executeAsync(statementCaptor.capture());
@@ -274,7 +273,7 @@ class AsyncCassandraTemplateUnitTests {
when(resultSet.one()).thenReturn(row);
ListenableFuture<Boolean> future = template.exists(Query.empty(), User.class);
CompletableFuture<Boolean> future = template.exists(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
@@ -288,7 +287,7 @@ class AsyncCassandraTemplateUnitTests {
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(User.class);
CompletableFuture<Long> future = template.count(User.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
@@ -302,7 +301,7 @@ class AsyncCassandraTemplateUnitTests {
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(Query.empty(), User.class);
CompletableFuture<Long> future = template.count(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
@@ -316,7 +315,7 @@ class AsyncCassandraTemplateUnitTests {
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.insert(user);
CompletableFuture<User> future = template.insert(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
@@ -333,7 +332,7 @@ class AsyncCassandraTemplateUnitTests {
VersionedUser user = new VersionedUser("heisenberg", "Walter", "White");
ListenableFuture<VersionedUser> future = template.insert(user);
CompletableFuture<VersionedUser> future = template.insert(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
@@ -350,7 +349,7 @@ class AsyncCassandraTemplateUnitTests {
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.insert(new User("heisenberg", "Walter", "White"));
CompletableFuture<User> future = template.insert(new User("heisenberg", "Walter", "White"));
try {
future.get();
@@ -369,7 +368,7 @@ class AsyncCassandraTemplateUnitTests {
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.update(user);
CompletableFuture<User> future = template.update(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
@@ -387,7 +386,7 @@ class AsyncCassandraTemplateUnitTests {
VersionedUser user = new VersionedUser("heisenberg", "Walter", "White");
user.setVersion(0L);
ListenableFuture<VersionedUser> future = template.update(user);
CompletableFuture<VersionedUser> future = template.update(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
@@ -461,7 +460,7 @@ class AsyncCassandraTemplateUnitTests {
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.update(new User("heisenberg", "Walter", "White"));
CompletableFuture<User> future = template.update(new User("heisenberg", "Walter", "White"));
try {
future.get();
@@ -480,7 +479,7 @@ class AsyncCassandraTemplateUnitTests {
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<Boolean> future = template.deleteById(user.getId(), User.class);
CompletableFuture<Boolean> future = template.deleteById(user.getId(), User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
@@ -494,7 +493,7 @@ class AsyncCassandraTemplateUnitTests {
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.delete(user);
CompletableFuture<User> future = template.delete(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
@@ -534,7 +533,7 @@ class AsyncCassandraTemplateUnitTests {
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.delete(new User("heisenberg", "Walter", "White"));
CompletableFuture<User> future = template.delete(new User("heisenberg", "Walter", "White"));
try {
future.get();

View File

@@ -27,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
@@ -150,8 +149,8 @@ class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrati
void queryPreparedStatementCreatorShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query(session -> new CompletableToListenableFutureAdapter<>(
session.prepareAsync("SELECT id FROM user WHERE id = ?;")), ps -> ps.bind("WHITE"), row -> {
getUninterruptibly(template.query(session -> session.prepareAsync("SELECT id FROM user WHERE id = ?;"),
ps -> ps.bind("WHITE"), row -> {
result.add(row.getString(0));
}));

View File

@@ -42,8 +42,6 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.cassandra.CassandraConnectionFailureException;
import org.springframework.data.cassandra.CassandraInvalidQueryException;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.ConsistencyLevel;
import com.datastax.oss.driver.api.core.CqlIdentifier;
@@ -107,7 +105,7 @@ class AsyncCqlTemplateUnitTests {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
CompletableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
try {
future.get();
@@ -203,8 +201,8 @@ class AsyncCqlTemplateUnitTests {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.query("UPDATE user SET a = 'b';",
(AsyncResultSetExtractor<Boolean>) it -> new AsyncResult<>(it.wasApplied()));
CompletableFuture<Boolean> future = template.query("UPDATE user SET a = 'b';",
(AsyncResultSetExtractor<Boolean>) it -> CompletableFuture.completedFuture(it.wasApplied()));
try {
future.get();
@@ -222,7 +220,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
try {
future.get();
@@ -240,7 +238,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@@ -250,7 +248,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> null);
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> null);
assertThat(getUninterruptibly(future)).isNull();
}
@@ -260,7 +258,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
try {
future.get();
@@ -280,7 +278,7 @@ class AsyncCqlTemplateUnitTests {
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK");
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", String.class);
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user", String.class);
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@@ -294,7 +292,7 @@ class AsyncCqlTemplateUnitTests {
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList("SELECT * FROM user", String.class);
CompletableFuture<List<String>> future = template.queryForList("SELECT * FROM user", String.class);
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
}
@@ -305,7 +303,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
CompletableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
assertThat(getUninterruptibly(future)).isTrue();
}
@@ -341,7 +339,7 @@ class AsyncCqlTemplateUnitTests {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<AsyncResultSet> future = asyncCqlTemplate
CompletableFuture<AsyncResultSet> future = asyncCqlTemplate
.queryForResultSet(SimpleStatement.newInstance("SELECT * from USERS"));
assertThat(getUninterruptibly(future).currentPage()).hasSize(3);
@@ -354,8 +352,8 @@ class AsyncCqlTemplateUnitTests {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<List<String>> future = asyncCqlTemplate.query(SimpleStatement.newInstance("SELECT * from USERS"),
(row, index) -> row.getString(0));
CompletableFuture<List<String>> future = asyncCqlTemplate
.query(SimpleStatement.newInstance("SELECT * from USERS"), (row, index) -> row.getString(0));
assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
@@ -367,8 +365,8 @@ class AsyncCqlTemplateUnitTests {
doTestStrings(5, ConsistencyLevel.ONE, null, asyncCqlTemplate -> {
ListenableFuture<List<String>> future = asyncCqlTemplate.query(SimpleStatement.newInstance("SELECT * from USERS"),
(row, index) -> row.getString(0));
CompletableFuture<List<String>> future = asyncCqlTemplate
.query(SimpleStatement.newInstance("SELECT * from USERS"), (row, index) -> row.getString(0));
assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
@@ -381,8 +379,8 @@ class AsyncCqlTemplateUnitTests {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.query(SimpleStatement.newInstance("UPDATE user SET a = 'b';"),
(AsyncResultSetExtractor<Boolean>) rs -> new AsyncResult<>(rs.wasApplied()));
CompletableFuture<Boolean> future = template.query(SimpleStatement.newInstance("UPDATE user SET a = 'b';"),
(AsyncResultSetExtractor<Boolean>) rs -> CompletableFuture.completedFuture(rs.wasApplied()));
try {
future.get();
@@ -400,7 +398,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
try {
@@ -419,7 +417,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@@ -430,7 +428,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> null);
assertThat(getUninterruptibly(future)).isNull();
}
@@ -441,7 +439,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
try {
future.get();
@@ -462,7 +460,7 @@ class AsyncCqlTemplateUnitTests {
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK");
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
String.class);
assertThat(getUninterruptibly(future)).isEqualTo("OK");
@@ -477,7 +475,7 @@ class AsyncCqlTemplateUnitTests {
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList(SimpleStatement.newInstance("SELECT * FROM user"),
CompletableFuture<List<String>> future = template.queryForList(SimpleStatement.newInstance("SELECT * FROM user"),
String.class);
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
@@ -489,7 +487,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute(SimpleStatement.newInstance("UPDATE user SET a = 'b';"));
CompletableFuture<Boolean> future = template.execute(SimpleStatement.newInstance("UPDATE user SET a = 'b';"));
assertThat(getUninterruptibly(future)).isTrue();
}
@@ -503,8 +501,8 @@ class AsyncCqlTemplateUnitTests {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<CompletionStage<AsyncResultSet>> futureOfFuture = asyncCqlTemplate.execute("SELECT * from USERS",
(session, ps) -> session.executeAsync(ps.bind("A")));
CompletableFuture<CompletionStage<AsyncResultSet>> futureOfFuture = asyncCqlTemplate
.execute("SELECT * from USERS", (session, ps) -> session.executeAsync(ps.bind("A")));
try {
assertThat(getUninterruptibly(futureOfFuture).toCompletableFuture().get().currentPage()).hasSize(3);
@@ -522,7 +520,7 @@ class AsyncCqlTemplateUnitTests {
when(this.preparedStatement.bind("White")).thenReturn(this.boundStatement);
when(this.resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> applied = asyncCqlTemplate.execute("UPDATE users SET name = ?", "White");
CompletableFuture<Boolean> applied = asyncCqlTemplate.execute("UPDATE users SET name = ?", "White");
assertThat(getUninterruptibly(applied)).isTrue();
});
@@ -540,8 +538,8 @@ class AsyncCqlTemplateUnitTests {
assertThat(e).hasMessageContaining("No node was available");
}
ListenableFuture<CompletionStage<AsyncResultSet>> future = template.execute(
session -> AsyncResult.forExecutionException(new NoNodeAvailableException()),
CompletableFuture<CompletionStage<AsyncResultSet>> future = template.execute(
session -> CompletableFuture.failedFuture(new NoNodeAvailableException()),
(session, ps) -> session.executeAsync(boundStatement));
try {
@@ -557,9 +555,10 @@ class AsyncCqlTemplateUnitTests {
@Test // DATACASS-292
void executePreparedStatementCreatorShouldTranslateStatementCallbackExceptions() throws Exception {
ListenableFuture<?> future = template.execute(session -> new AsyncResult<>(preparedStatement), (session, ps) -> {
throw new NoNodeAvailableException();
});
CompletableFuture<?> future = template.execute(session -> CompletableFuture.completedFuture(preparedStatement),
(session, ps) -> {
throw new NoNodeAvailableException();
});
try {
future.get();
@@ -578,8 +577,9 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<Iterable<Row>> future = template.query(session -> new AsyncResult<>(preparedStatement),
(AsyncResultSetExtractor<Iterable<Row>>) rs -> new AsyncResult<>(rs.currentPage()));
CompletableFuture<Iterable<Row>> future = template.query(
session -> CompletableFuture.completedFuture(preparedStatement),
(AsyncResultSetExtractor<Iterable<Row>>) rs -> CompletableFuture.completedFuture(rs.currentPage()));
assertThat(getUninterruptibly(future)).contains(row);
verify(preparedStatement).bind();
@@ -591,11 +591,11 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<AsyncResultSet> future = template
.query(session -> new AsyncResult<PreparedStatement>(preparedStatement), ps -> {
CompletableFuture<AsyncResultSet> future = template
.query(session -> CompletableFuture.completedFuture(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
}, (AsyncResultSetExtractor<AsyncResultSet>) CompletableFuture::completedFuture);
assertThat(getUninterruptibly(future).currentPage()).contains(row);
verify(preparedStatement).bind("a", "b");
@@ -604,11 +604,11 @@ class AsyncCqlTemplateUnitTests {
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldTranslatePrepareStatementExceptions() throws Exception {
ListenableFuture<AsyncResultSet> future = template
.query(session -> AsyncResult.forExecutionException(new NoNodeAvailableException()), ps -> {
CompletableFuture<AsyncResultSet> future = template
.query(session -> CompletableFuture.failedFuture(new NoNodeAvailableException()), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
}, (AsyncResultSetExtractor<AsyncResultSet>) CompletableFuture::completedFuture);
try {
future.get();
@@ -622,9 +622,10 @@ class AsyncCqlTemplateUnitTests {
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldTranslateBindExceptions() throws Exception {
ListenableFuture<AsyncResultSet> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
throw new NoNodeAvailableException();
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
CompletableFuture<AsyncResultSet> future = template
.query(session -> CompletableFuture.completedFuture(preparedStatement), ps -> {
throw new NoNodeAvailableException();
}, (AsyncResultSetExtractor<AsyncResultSet>) CompletableFuture::completedFuture);
try {
future.get();
@@ -641,10 +642,11 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(resultSetFuture);
ListenableFuture<AsyncResultSet> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
CompletableFuture<AsyncResultSet> future = template
.query(session -> CompletableFuture.completedFuture(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) CompletableFuture::completedFuture);
try {
future.get();
@@ -660,10 +662,11 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<List<Row>> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (row, rowNum) -> row);
CompletableFuture<List<Row>> future = template
.query(session -> CompletableFuture.completedFuture(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (row, rowNum) -> row);
assertThat(getUninterruptibly(future)).hasSize(1).contains(row);
verify(preparedStatement).bind("a", "b");
@@ -678,7 +681,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
try {
@@ -700,7 +703,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@@ -714,7 +717,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
CompletableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
try {
future.get();
@@ -755,8 +758,8 @@ class AsyncCqlTemplateUnitTests {
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList("SELECT * FROM user WHERE username = ?", String.class,
"Walter");
CompletableFuture<List<String>> future = template.queryForList("SELECT * FROM user WHERE username = ?",
String.class, "Walter");
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
}
@@ -770,7 +773,7 @@ class AsyncCqlTemplateUnitTests {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET username = ?", "Walter");
CompletableFuture<Boolean> future = template.execute("UPDATE user SET username = ?", "Walter");
assertThat(getUninterruptibly(future)).isTrue();
}
@@ -780,8 +783,7 @@ class AsyncCqlTemplateUnitTests {
}
private void doTestStrings(@Nullable Integer fetchSize, @Nullable ConsistencyLevel consistencyLevel,
@Nullable CqlIdentifier keyspace,
Consumer<AsyncCqlTemplate> cqlTemplateConsumer) {
@Nullable CqlIdentifier keyspace, Consumer<AsyncCqlTemplate> cqlTemplateConsumer) {
String[] results = { "Walter", "Hank", "Jesse" };

View File

@@ -27,8 +27,6 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
@@ -95,7 +93,7 @@ class AsyncResultStreamUnitTests {
List<String> rows = new ArrayList<>();
ListenableFuture<Void> completion = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
CompletableFuture<Void> completion = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.forEach(rows::add);
assertThatThrownBy(completion::get).hasRootCauseInstanceOf(RuntimeException.class);
@@ -106,7 +104,7 @@ class AsyncResultStreamUnitTests {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
CompletableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThat(collect.get()).containsOnly(row1);
}
@@ -119,7 +117,7 @@ class AsyncResultStreamUnitTests {
when(first.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(last));
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<String>> rows = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
CompletableFuture<List<String>> rows = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.collect(Collectors.toList());
assertThat(rows.get()).containsOnly("row-1", "row-2");
@@ -135,7 +133,7 @@ class AsyncResultStreamUnitTests {
when(first.fetchNextPage()).thenReturn(failed);
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
CompletableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThatThrownBy(collect::get).hasRootCauseInstanceOf(RuntimeException.class);
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
/**
* Integration tests for {@link AsyncCqlTemplate}.
*
* @author Mark Paluch
*/
class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
private static final AtomicBoolean initialized = new AtomicBoolean();
private AsyncCqlTemplate template;
@BeforeEach
void before() {
if (initialized.compareAndSet(false, true)) {
session.execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
}
session.execute("TRUNCATE user;");
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
template = new AsyncCqlTemplate();
template.setSession(getSession());
}
@Test // DATACASS-292
void executeShouldRemoveRecords() {
getUninterruptibly(template.execute("DELETE FROM user WHERE id = 'WHITE'"));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query("SELECT id FROM user;", row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectShouldReturnFirstColumn() {
String id = getUninterruptibly(template.queryForObject("SELECT id FROM user;", String.class));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(template.queryForMap("SELECT * FROM user;"));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
@Test // DATACASS-292
void executeStatementShouldRemoveRecords() {
getUninterruptibly(template.execute(SimpleStatement.newInstance("DELETE FROM user WHERE id = 'WHITE'")));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryStatementShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query(SimpleStatement.newInstance("SELECT id FROM user"), row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnFirstColumn() {
String id = getUninterruptibly(
template.queryForObject(SimpleStatement.newInstance("SELECT id FROM user"), String.class));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(
template.queryForMap(SimpleStatement.newInstance("SELECT * FROM user")));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
@Test // DATACASS-292
void executeWithArgsShouldRemoveRecords() {
getUninterruptibly(template.execute("DELETE FROM user WHERE id = ?", "WHITE"));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryPreparedStatementShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query("SELECT id FROM user WHERE id = ?;", row -> {
result.add(row.getString(0));
}, "WHITE"));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryPreparedStatementCreatorShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query(session -> new CompletableToListenableFutureAdapter<>(
session.prepareAsync("SELECT id FROM user WHERE id = ?;")), ps -> ps.bind("WHITE"), row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectWithArgsShouldReturnFirstColumn() {
String id = getUninterruptibly(template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE"));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectWithArgsShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE"));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

View File

@@ -0,0 +1,865 @@
/*
* Copyright 2016-2022 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
*
* https://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.cql.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.cassandra.CassandraConnectionFailureException;
import org.springframework.data.cassandra.CassandraInvalidQueryException;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.ConsistencyLevel;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.NoNodeAvailableException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
/**
* Unit tests for {@link AsyncCqlTemplate}.
*
* @author Mark Paluch
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class AsyncCqlTemplateUnitTests {
@Mock CqlSession session;
@Mock AsyncResultSet resultSet;
@Mock Row row;
@Mock PreparedStatement preparedStatement;
@Mock BoundStatement boundStatement;
@Mock ColumnDefinitions columnDefinitions;
private AsyncCqlTemplate template;
@BeforeEach
void setup() {
this.template = new AsyncCqlTemplate();
this.template.setSession(session);
}
// -------------------------------------------------------------------------
// Tests dealing with a plain com.datastax.oss.driver.api.core.CqlSession
// -------------------------------------------------------------------------
@Test // DATACASS-292
void executeCallbackShouldTranslateExceptions() {
try {
template.execute((AsyncSessionCallback<String>) session -> {
throw new InvalidQueryException(null, "wrong query");
});
fail("Missing CassandraInvalidQueryException");
} catch (CassandraInvalidQueryException e) {
assertThat(e).hasMessageContaining("wrong query");
}
}
@Test // DATACASS-292
void executeCqlShouldTranslateExceptions() throws Exception {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasMessageContaining("No node was available");
}
}
// -------------------------------------------------------------------------
// Tests dealing with static CQL
// -------------------------------------------------------------------------
@Test // DATACASS-292
void executeCqlShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
asyncCqlTemplate.execute("SELECT * from USERS");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-767
void executePreparedStatementShouldApplyKeyspace() {
doTestStrings(null, null, CqlIdentifier.fromCql("ks1"), cqlTemplate -> {
cqlTemplate.execute("SELECT * from USERS", (session, ps) -> session.executeAsync(ps.bind("A")));
});
ArgumentCaptor<SimpleStatement> captor = ArgumentCaptor.forClass(SimpleStatement.class);
verify(session).prepareAsync(captor.capture());
SimpleStatement statement = captor.getValue();
assertThat(statement.getKeyspace()).isEqualTo(CqlIdentifier.fromCql("ks1"));
}
@Test // DATACASS-292
void executeCqlWithArgumentsShouldCallExecution() {
doTestStrings(5, ConsistencyLevel.ONE, null, asyncCqlTemplate -> {
asyncCqlTemplate.execute("SELECT * from USERS");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryForResultSetShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
AsyncResultSet resultSet = getUninterruptibly(asyncCqlTemplate.queryForResultSet("SELECT * from USERS"));
assertThat(resultSet.currentPage()).hasSize(3);
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryWithResultSetExtractorShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
List<String> rows = getUninterruptibly(
asyncCqlTemplate.query("SELECT * from USERS", (row, index) -> row.getString(0)));
assertThat(rows).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryWithResultSetExtractorWithArgumentsShouldCallExecution() {
doTestStrings(5, ConsistencyLevel.ONE, null, asyncCqlTemplate -> {
List<String> rows = getUninterruptibly(
asyncCqlTemplate.query("SELECT * from USERS", (row, index) -> row.getString(0)));
assertThat(rows).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryCqlShouldTranslateExceptions() throws Exception {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.query("UPDATE user SET a = 'b';",
(AsyncResultSetExtractor<Boolean>) it -> new AsyncResult<>(it.wasApplied()));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasMessageContaining("No node was available");
}
}
@Test // DATACASS-292
void queryForObjectCqlShouldBeEmpty() throws Exception {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(EmptyResultDataAccessException.class)
.hasMessageContaining("expected 1, actual 0");
}
}
@Test // DATACASS-292
void queryForObjectCqlShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForObjectCqlShouldReturnNullValue() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> null);
assertThat(getUninterruptibly(future)).isNull();
}
@Test // DATACASS-292
void queryForObjectCqlShouldFailReturningManyRecords() throws Exception {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(IncorrectResultSizeDataAccessException.class)
.hasMessageContaining("expected 1, actual 2");
}
}
@Test // DATACASS-292
void queryForObjectCqlWithTypeShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK");
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user", String.class);
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForListCqlWithTypeShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList("SELECT * FROM user", String.class);
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
}
@Test // DATACASS-292
void executeCqlShouldReturnWasApplied() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET a = 'b';");
assertThat(getUninterruptibly(future)).isTrue();
}
// -------------------------------------------------------------------------
// Tests dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
@Test // DATACASS-292
void executeStatementShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
asyncCqlTemplate.execute(SimpleStatement.newInstance("SELECT * from USERS"));
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void executeStatementWithArgumentsShouldCallExecution() {
doTestStrings(5, ConsistencyLevel.ONE, null, asyncCqlTemplate -> {
asyncCqlTemplate.execute(SimpleStatement.newInstance("SELECT * from USERS"));
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryForResultStatementSetShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<AsyncResultSet> future = asyncCqlTemplate
.queryForResultSet(SimpleStatement.newInstance("SELECT * from USERS"));
assertThat(getUninterruptibly(future).currentPage()).hasSize(3);
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryWithResultSetStatementExtractorShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<List<String>> future = asyncCqlTemplate.query(SimpleStatement.newInstance("SELECT * from USERS"),
(row, index) -> row.getString(0));
assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryWithResultSetStatementExtractorWithArgumentsShouldCallExecution() {
doTestStrings(5, ConsistencyLevel.ONE, null, asyncCqlTemplate -> {
ListenableFuture<List<String>> future = asyncCqlTemplate.query(SimpleStatement.newInstance("SELECT * from USERS"),
(row, index) -> row.getString(0));
assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", "Jesse");
verify(session).executeAsync(any(Statement.class));
});
}
@Test // DATACASS-292
void queryStatementShouldTranslateExceptions() throws Exception {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture);
ListenableFuture<Boolean> future = template.query(SimpleStatement.newInstance("UPDATE user SET a = 'b';"),
(AsyncResultSetExtractor<Boolean>) rs -> new AsyncResult<>(rs.wasApplied()));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasMessageContaining("No node was available");
}
}
@Test // DATACASS-292
void queryForObjectStatementShouldBeEmpty() throws Exception {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(EmptyResultDataAccessException.class)
.hasMessageContaining("expected 1, actual 0");
}
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnNullValue() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> null);
assertThat(getUninterruptibly(future)).isNull();
}
@Test // DATACASS-292
void queryForObjectStatementShouldFailReturningManyRecords() throws Exception {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
(row, rowNum) -> "OK");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(IncorrectResultSizeDataAccessException.class)
.hasMessageContaining("expected 1, actual 2");
}
}
@Test // DATACASS-292
void queryForObjectStatementWithTypeShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK");
ListenableFuture<String> future = template.queryForObject(SimpleStatement.newInstance("SELECT * FROM user"),
String.class);
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForListStatementWithTypeShouldReturnRecord() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList(SimpleStatement.newInstance("SELECT * FROM user"),
String.class);
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
}
@Test // DATACASS-292
void executeStatementShouldReturnWasApplied() {
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute(SimpleStatement.newInstance("UPDATE user SET a = 'b';"));
assertThat(getUninterruptibly(future)).isTrue();
}
// -------------------------------------------------------------------------
// Methods dealing with prepared statements
// -------------------------------------------------------------------------
@Test // DATACASS-292
void queryPreparedStatementWithCallbackShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
ListenableFuture<CompletionStage<AsyncResultSet>> futureOfFuture = asyncCqlTemplate.execute("SELECT * from USERS",
(session, ps) -> session.executeAsync(ps.bind("A")));
try {
assertThat(getUninterruptibly(futureOfFuture).toCompletableFuture().get().currentPage()).hasSize(3);
} catch (Exception e) {
fail(e.getMessage(), e);
}
});
}
@Test // DATACASS-292
void executePreparedStatementWithCallbackShouldCallExecution() {
doTestStrings(asyncCqlTemplate -> {
when(this.preparedStatement.bind("White")).thenReturn(this.boundStatement);
when(this.resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> applied = asyncCqlTemplate.execute("UPDATE users SET name = ?", "White");
assertThat(getUninterruptibly(applied)).isTrue();
});
}
@Test // DATACASS-292
void executePreparedStatementCreatorShouldTranslateStatementCreationExceptions() throws Exception {
try {
template.execute(session -> {
throw new NoNodeAvailableException();
}, (session, ps) -> session.executeAsync(boundStatement));
fail("Missing CassandraConnectionFailureException");
} catch (CassandraConnectionFailureException e) {
assertThat(e).hasMessageContaining("No node was available");
}
ListenableFuture<CompletionStage<AsyncResultSet>> future = template.execute(
session -> AsyncResult.forExecutionException(new NoNodeAvailableException()),
(session, ps) -> session.executeAsync(boundStatement));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasMessageContaining("No node was available");
}
}
@Test // DATACASS-292
void executePreparedStatementCreatorShouldTranslateStatementCallbackExceptions() throws Exception {
ListenableFuture<?> future = template.execute(session -> new AsyncResult<>(preparedStatement), (session, ps) -> {
throw new NoNodeAvailableException();
});
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasMessageContaining("No node was available");
}
}
@Test // DATACASS-292
void queryPreparedStatementCreatorShouldReturnResult() {
when(preparedStatement.bind()).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<Iterable<Row>> future = template.query(session -> new AsyncResult<>(preparedStatement),
(AsyncResultSetExtractor<Iterable<Row>>) rs -> new AsyncResult<>(rs.currentPage()));
assertThat(getUninterruptibly(future)).contains(row);
verify(preparedStatement).bind();
}
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldReturnResult() {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<AsyncResultSet> future = template
.query(session -> new AsyncResult<PreparedStatement>(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
assertThat(getUninterruptibly(future).currentPage()).contains(row);
verify(preparedStatement).bind("a", "b");
}
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldTranslatePrepareStatementExceptions() throws Exception {
ListenableFuture<AsyncResultSet> future = template
.query(session -> AsyncResult.forExecutionException(new NoNodeAvailableException()), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class);
}
}
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldTranslateBindExceptions() throws Exception {
ListenableFuture<AsyncResultSet> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
throw new NoNodeAvailableException();
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class);
}
}
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderShouldTranslateExecutionExceptions() throws Exception {
TestResultSetFuture resultSetFuture = TestResultSetFuture.failed(new NoNodeAvailableException());
when(session.executeAsync(boundStatement)).thenReturn(resultSetFuture);
ListenableFuture<AsyncResultSet> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (AsyncResultSetExtractor<AsyncResultSet>) AsyncResult::new);
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class);
}
}
@Test // DATACASS-292
void queryPreparedStatementCreatorAndBinderAndMapperShouldReturnResult() {
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<List<Row>> future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> {
ps.bind("a", "b");
return boundStatement;
}, (row, rowNum) -> row);
assertThat(getUninterruptibly(future)).hasSize(1).contains(row);
verify(preparedStatement).bind("a", "b");
}
@Test // DATACASS-292
void queryForObjectPreparedStatementShouldBeEmpty() throws Exception {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.emptyList());
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(EmptyResultDataAccessException.class)
.hasMessageContaining("expected 1, actual 0");
}
}
@Test // DATACASS-292
void queryForObjectPreparedStatementShouldReturnRecord() {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForObjectPreparedStatementShouldFailReturningManyRecords() throws Exception {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
ListenableFuture<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?",
(row, rowNum) -> "OK", "Walter");
try {
future.get();
fail("Missing IncorrectResultSizeDataAccessException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(IncorrectResultSizeDataAccessException.class)
.hasMessageContaining("expected 1, actual 2");
}
}
@Test // DATACASS-292
void queryForObjectPreparedStatementWithTypeShouldReturnRecord() {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK");
Future<String> future = template.queryForObject("SELECT * FROM user WHERE username = ?", String.class, "Walter");
assertThat(getUninterruptibly(future)).isEqualTo("OK");
}
@Test // DATACASS-292
void queryForListPreparedStatementWithTypeShouldReturnRecord() {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.currentPage()).thenReturn(Arrays.asList(row, row));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
when(columnDefinitions.size()).thenReturn(1);
when(row.getString(0)).thenReturn("OK", "NOT OK");
ListenableFuture<List<String>> future = template.queryForList("SELECT * FROM user WHERE username = ?", String.class,
"Walter");
assertThat(getUninterruptibly(future)).contains("OK", "NOT OK");
}
@Test // DATACASS-292
void updatePreparedStatementShouldReturnApplied() {
when(session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(preparedStatement));
when(preparedStatement.bind("Walter")).thenReturn(boundStatement);
when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.wasApplied()).thenReturn(true);
ListenableFuture<Boolean> future = template.execute("UPDATE user SET username = ?", "Walter");
assertThat(getUninterruptibly(future)).isTrue();
}
private void doTestStrings(Consumer<AsyncCqlTemplate> cqlTemplateConsumer) {
doTestStrings(null, null, null, cqlTemplateConsumer);
}
private void doTestStrings(@Nullable Integer fetchSize, @Nullable ConsistencyLevel consistencyLevel,
@Nullable CqlIdentifier keyspace, Consumer<AsyncCqlTemplate> cqlTemplateConsumer) {
String[] results = { "Walter", "Hank", "Jesse" };
when(this.session.executeAsync((Statement) any())).thenReturn(new TestResultSetFuture(resultSet));
when(this.resultSet.currentPage()).thenReturn(Arrays.asList(row, row, row));
when(this.row.getString(0)).thenReturn(results[0], results[1], results[2]);
when(this.session.prepareAsync(anyString())).thenReturn(new TestPreparedStatementFuture(this.preparedStatement));
when(this.session.prepareAsync(any(SimpleStatement.class)))
.thenReturn(new TestPreparedStatementFuture(this.preparedStatement));
AsyncCqlTemplate template = new AsyncCqlTemplate();
template.setSession(this.session);
if (fetchSize != null) {
template.setFetchSize(fetchSize);
}
if (consistencyLevel != null) {
template.setConsistencyLevel(consistencyLevel);
}
if (keyspace != null) {
template.setKeyspace(keyspace);
}
cqlTemplateConsumer.accept(template);
ArgumentCaptor<Statement> statementArgumentCaptor = ArgumentCaptor.forClass(Statement.class);
verify(this.session).executeAsync(statementArgumentCaptor.capture());
Statement statement = statementArgumentCaptor.getValue();
if (fetchSize != null) {
assertThat(statement.getPageSize()).isEqualTo(fetchSize.intValue());
}
if (consistencyLevel != null) {
assertThat(statement.getConsistencyLevel()).isEqualTo(consistencyLevel);
}
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static class TestResultSetFuture extends CompletableFuture<AsyncResultSet> {
private TestResultSetFuture() {}
private TestResultSetFuture(AsyncResultSet result) {
complete(result);
}
/**
* Create a completed future that reports a failure given {@link Throwable}.
*
* @param throwable must not be {@literal null}.
* @return the completed/failed {@link TestResultSetFuture}.
*/
private static TestResultSetFuture failed(Throwable throwable) {
TestResultSetFuture future = new TestResultSetFuture();
future.completeExceptionally(throwable);
return future;
}
}
private static class TestPreparedStatementFuture extends CompletableFuture<PreparedStatement> {
public TestPreparedStatementFuture() {}
private TestPreparedStatementFuture(PreparedStatement ps) {
complete(ps);
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2019-2022 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
*
* https://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.cql.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Unit tests for {@link AsyncResultStream}.
*
* @author Mark Paluch
*/
class AsyncResultStreamUnitTests {
private AsyncResultSet first = mock(AsyncResultSet.class);
private AsyncResultSet last = mock(AsyncResultSet.class);
private Row row1 = mock(Row.class);
private Row row2 = mock(Row.class);
@Test // DATACASS-656
void shouldIterateFirstPage() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
List<Row> rows = new ArrayList<>();
AsyncResultStream.from(first).forEach(rows::add);
assertThat(rows).containsOnly(row1);
}
@Test // DATACASS-656
void shouldIterateMappedFirstPage() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
List<String> rows = new ArrayList<>();
AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum).forEach(rows::add);
assertThat(rows).containsOnly("row-1");
}
@Test // DATACASS-656
void shouldIterateMappedPages() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
when(last.currentPage()).thenReturn(Collections.singletonList(row2));
when(first.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(last));
when(first.hasMorePages()).thenReturn(true);
List<String> rows = new ArrayList<>();
AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum).forEach(rows::add);
assertThat(rows).containsOnly("row-1", "row-2");
}
@Test // DATACASS-656
void shouldPropagateExceptionOnIterate() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
CompletableFuture<AsyncResultSet> failed = new CompletableFuture<>();
failed.completeExceptionally(new RuntimeException("boo"));
when(first.fetchNextPage()).thenReturn(failed);
when(first.hasMorePages()).thenReturn(true);
List<String> rows = new ArrayList<>();
ListenableFuture<Void> completion = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.forEach(rows::add);
assertThatThrownBy(completion::get).hasRootCauseInstanceOf(RuntimeException.class);
}
@Test // DATACASS-656
void shouldCollectFirstPage() throws ExecutionException, InterruptedException {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThat(collect.get()).containsOnly(row1);
}
@Test // DATACASS-656
void shouldCollectMappedPages() throws ExecutionException, InterruptedException {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
when(last.currentPage()).thenReturn(Collections.singletonList(row2));
when(first.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(last));
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<String>> rows = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.collect(Collectors.toList());
assertThat(rows.get()).containsOnly("row-1", "row-2");
}
@Test // DATACASS-656
void shouldPropagateExceptionOnCollect() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
CompletableFuture<AsyncResultSet> failed = new CompletableFuture<>();
failed.completeExceptionally(new RuntimeException("boo"));
when(first.fetchNextPage()).thenReturn(failed);
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThatThrownBy(collect::get).hasRootCauseInstanceOf(RuntimeException.class);
}
}

View File

@@ -0,0 +1,380 @@
/*
* Copyright 2022 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
*
* https://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.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Future;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.DeleteOptions;
import org.springframework.data.cassandra.core.InsertOptions;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.domain.UserToken;
import org.springframework.data.cassandra.repository.support.SchemaTestUtils;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.uuid.Uuids;
/**
* Integration tests for {@link AsyncCassandraTemplate}.
*
* @author Mark Paluch
*/
class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
private AsyncCassandraTemplate template;
@BeforeEach
void setUp() {
MappingCassandraConverter converter = new MappingCassandraConverter();
CassandraTemplate cassandraTemplate = new CassandraTemplate(session, converter);
template = new AsyncCassandraTemplate(new AsyncCqlTemplate(session), converter);
prepareTemplate(template);
SchemaTestUtils.potentiallyCreateTableFor(User.class, cassandraTemplate);
SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, cassandraTemplate);
SchemaTestUtils.truncate(User.class, cassandraTemplate);
SchemaTestUtils.truncate(UserToken.class, cassandraTemplate);
}
/**
* Post-process the {@link AsyncCassandraTemplate} before running the tests.
*
* @param template
*/
void prepareTemplate(AsyncCassandraTemplate template) {
template.setUsePreparedStatements(false);
}
@Test // DATACASS-343
void shouldSelectByQueryWithSorting() {
UserToken token1 = new UserToken();
token1.setUserId(Uuids.endOf(System.currentTimeMillis()));
token1.setToken(Uuids.startOf(System.currentTimeMillis()));
token1.setUserComment("foo");
UserToken token2 = new UserToken();
token2.setUserId(token1.getUserId());
token2.setToken(Uuids.endOf(System.currentTimeMillis() + 100));
token2.setUserComment("bar");
getUninterruptibly(template.insert(token1));
getUninterruptibly(template.insert(token2));
Query query = Query.query(where("userId").is(token1.getUserId())).sort(Sort.by("token"));
assertThat(getUninterruptibly(template.select(query, UserToken.class))).containsSequence(token1, token2);
}
@Test // DATACASS-343
void shouldSelectOneByQuery() {
UserToken token1 = new UserToken();
token1.setUserId(Uuids.endOf(System.currentTimeMillis()));
token1.setToken(Uuids.startOf(System.currentTimeMillis()));
token1.setUserComment("foo");
getUninterruptibly(template.insert(token1));
Query query = Query.query(where("userId").is(token1.getUserId()));
assertThat(getUninterruptibly(template.selectOne(query, UserToken.class))).isEqualTo(token1);
}
@Test // DATACASS-292
void insertShouldInsertEntity() {
User user = new User("heisenberg", "Walter", "White");
assertThat(getUser(user.getId())).isNull();
ListenableFuture<User> insert = template.insert(user);
assertThat(getUninterruptibly(insert)).isEqualTo(user);
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-250
void insertShouldCreateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> inserted = template.insert(user,
lwtOptions);
assertThat(getUninterruptibly(inserted).wasApplied()).isTrue();
}
@Test // DATACASS-250
void insertShouldNotUpdateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user, lwtOptions));
user.setFirstname("Walter Hartwell");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> lwt = template.insert(user,
lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter");
}
@Test // DATACASS-292
void shouldInsertAndCountEntities() {
User user = new User("heisenberg", "Walter", "White");
User result = getUninterruptibly(template.insert(user));
ListenableFuture<Long> count = template.count(User.class);
assertThat(result).isSameAs(user);
assertThat(getUninterruptibly(count)).isEqualTo(1L);
}
@Test // DATACASS-512
void shouldInsertEntityAndCountByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("heisenberg")), User.class))).isOne();
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("foo")), User.class))).isZero();
}
@Test // DATACASS-512
void shouldInsertEntityAndExistsByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("heisenberg")), User.class))).isTrue();
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("foo")), User.class))).isFalse();
}
@Test // DATACASS-292
void updateShouldUpdateEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
User updated = getUninterruptibly(template.update(user));
assertThat(updated).isNotNull();
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-292
void updateShouldNotCreateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> lwt = template.update(user,
lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
void updateShouldUpdateEntityWithLwt() throws InterruptedException {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> updated = template.update(user,
lwtOptions);
assertThat(getUninterruptibly(updated).wasApplied()).isTrue();
assertThat(getUninterruptibly(updated).getEntity()).isSameAs(user);
}
@Test // DATACASS-343
void updateShouldUpdateEntityByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg"));
boolean result = getUninterruptibly(
template.update(query, Update.empty().set("firstname", "Walter Hartwell"), User.class));
assertThat(result).isTrue();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter Hartwell");
}
@Test // DATACASS-343
void deleteByQueryShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-343
void deleteColumnsByQueryShouldRemoveColumn() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg")).columns(Columns.from("lastname"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
User loaded = getUser(user.getId());
assertThat(loaded.getFirstname()).isEqualTo("Walter");
assertThat(loaded.getLastname()).isNull();
}
@Test // DATACASS-292
void deleteShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
User deleted = getUninterruptibly(template.delete(user));
assertThat(deleted).isNotNull();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
void deleteByIdShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Boolean deleted = getUninterruptibly(template.deleteById(user.getId(), User.class));
assertThat(deleted).isTrue();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-606
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();
}
@Test // DATACASS-606
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
void shouldPageRequests() {
Set<String> expectedIds = new LinkedHashSet<>();
for (int count = 0; count < 100; count++) {
User user = new User("heisenberg" + count, "Walter", "White");
expectedIds.add(user.getId());
getUninterruptibly(template.insert(user));
}
Set<String> ids = new HashSet<>();
Query query = Query.empty();
Slice<User> slice = getUninterruptibly(
template.slice(query.pageRequest(CassandraPageRequest.first(10)), User.class));
int iterations = 0;
do {
iterations++;
assertThat(slice).hasSize(10);
slice.stream().map(User::getId).forEach(ids::add);
if (slice.hasNext()) {
slice = getUninterruptibly(template.slice(query.pageRequest(slice.nextPageable()), User.class));
} else {
break;
}
} while (!slice.getContent().isEmpty());
assertThat(ids).containsAll(expectedIds);
assertThat(iterations).isEqualTo(10);
}
private User getUser(String id) {
return getUninterruptibly(template.selectOneById(id, User.class));
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception cause) {
throw new IllegalStateException(cause);
}
}
}

View File

@@ -0,0 +1,605 @@
/*
* Copyright 2022 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
*
* https://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.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.data.cassandra.CassandraConnectionFailureException;
import org.springframework.data.cassandra.core.DeleteOptions;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.mapping.event.BeforeConvertCallback;
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveCallback;
import org.springframework.data.cassandra.core.query.Filter;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.domain.VersionedUser;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.NoNodeAvailableException;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ColumnDefinition;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
import com.datastax.oss.driver.internal.core.type.codec.registry.DefaultCodecRegistry;
/**
* Unit tests for {@link AsyncCassandraTemplate}.
*
* @author Mark Paluch
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class AsyncCassandraTemplateUnitTests {
@Mock CqlSession session;
CodecRegistry codecRegistry = new DefaultCodecRegistry("foo");
@Mock DriverContext driverContext;
@Mock AsyncResultSet resultSet;
@Mock Row row;
@Mock ColumnDefinition columnDefinition;
@Mock ColumnDefinitions columnDefinitions;
@Captor ArgumentCaptor<SimpleStatement> statementCaptor;
private AsyncCassandraTemplate template;
private Object beforeSave;
private Object beforeConvert;
@BeforeEach
void setUp() {
when(driverContext.getCodecRegistry()).thenReturn(codecRegistry);
when(session.getContext()).thenReturn(driverContext);
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
EntityCallbacks callbacks = EntityCallbacks.create();
callbacks.addEntityCallback((BeforeSaveCallback<Object>) (entity, tableName, statement) -> {
assertThat(tableName).isNotNull();
assertThat(statement).isNotNull();
beforeSave = entity;
return entity;
});
callbacks.addEntityCallback((BeforeConvertCallback<Object>) (entity, tableName) -> {
assertThat(tableName).isNotNull();
beforeConvert = entity;
return entity;
});
template = new AsyncCassandraTemplate(session);
template.setUsePreparedStatements(false);
template.setEntityCallbacks(callbacks);
}
@Test // gh-1133
void shouldConfigureConverterFromSession() {
assertThat(template.getConverter().getCodecRegistry()).isEqualTo(session.getContext().getCodecRegistry());
assertThat(template.getConverter()).extracting("userTypeResolver").isNotNull();
}
@Test // DATACASS-292
void selectUsingCqlShouldReturnMappedResults() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(columnDefinitions.contains(any(CqlIdentifier.class))).thenReturn(true);
when(columnDefinitions.get(anyInt())).thenReturn(columnDefinition);
when(columnDefinitions.firstIndexOf("id")).thenReturn(0);
when(columnDefinitions.firstIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.firstIndexOf("lastname")).thenReturn(2);
when(columnDefinition.getType()).thenReturn(DataTypes.TEXT);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
assertThat(getUninterruptibly(list)).hasSize(1).contains(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users");
}
@Test // DATACASS-292
void selectUsingCqlShouldInvokeCallbackWithMappedResults() {
when(resultSet.currentPage()).thenReturn(Collections.singletonList(row));
when(columnDefinitions.contains(any(CqlIdentifier.class))).thenReturn(true);
when(columnDefinitions.get(anyInt())).thenReturn(columnDefinition);
when(columnDefinitions.firstIndexOf("id")).thenReturn(0);
when(columnDefinitions.firstIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.firstIndexOf("lastname")).thenReturn(2);
when(columnDefinition.getType()).thenReturn(DataTypes.TEXT);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
List<User> list = new ArrayList<>();
ListenableFuture<Void> result = template.select("SELECT * FROM users", list::add, User.class);
assertThat(getUninterruptibly(result)).isNull();
assertThat(list).hasSize(1).contains(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users");
}
@Test // DATACASS-292
void selectShouldTranslateException() throws Exception {
when(resultSet.currentPage()).thenThrow(new NoNodeAvailableException());
ListenableFuture<List<User>> list = template.select("SELECT * FROM users", User.class);
try {
list.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoNodeAvailableException.class);
}
}
@Test // DATACASS-292
void selectOneShouldReturnMappedResults() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(columnDefinitions.contains(any(CqlIdentifier.class))).thenReturn(true);
when(columnDefinitions.get(anyInt())).thenReturn(columnDefinition);
when(columnDefinitions.firstIndexOf("id")).thenReturn(0);
when(columnDefinitions.firstIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.firstIndexOf("lastname")).thenReturn(2);
when(columnDefinition.getType()).thenReturn(DataTypes.TEXT);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<User> future = template.selectOne("SELECT * FROM users WHERE id='myid'", User.class);
assertThat(getUninterruptibly(future)).isEqualTo(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users WHERE id='myid'");
}
@Test // DATACASS-292
void selectOneByIdShouldReturnMappedResults() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(columnDefinitions.contains(any(CqlIdentifier.class))).thenReturn(true);
when(columnDefinitions.get(anyInt())).thenReturn(columnDefinition);
when(columnDefinitions.firstIndexOf("id")).thenReturn(0);
when(columnDefinitions.firstIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.firstIndexOf("lastname")).thenReturn(2);
when(columnDefinition.getType()).thenReturn(DataTypes.ASCII);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<User> future = template.selectOneById("myid", User.class);
assertThat(getUninterruptibly(future)).isEqualTo(new User("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users WHERE id='myid' LIMIT 1");
}
@Test // DATACASS-696
void selectOneShouldNull() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
ListenableFuture<String> future = template.selectOne("SELECT id FROM users WHERE id='myid'", String.class);
assertThat(getUninterruptibly(future)).isNull();
}
@Test // DATACASS-292
void existsShouldReturnExistingElement() {
when(resultSet.one()).thenReturn(row);
ListenableFuture<Boolean> future = template.exists("myid", User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users WHERE id='myid' LIMIT 1");
}
@Test // DATACASS-292
void existsShouldReturnNonExistingElement() {
ListenableFuture<Boolean> future = template.exists("myid", User.class);
assertThat(getUninterruptibly(future)).isFalse();
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users WHERE id='myid' LIMIT 1");
}
@Test // DATACASS-512
void existsByQueryShouldReturnExistingElement() {
when(resultSet.one()).thenReturn(row);
ListenableFuture<Boolean> future = template.exists(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT * FROM users LIMIT 1");
}
@Test // DATACASS-292
void countShouldExecuteCountQueryElement() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(User.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT count(1) FROM users");
}
@Test // DATACASS-292
void countByQueryShouldExecuteCountQueryElement() {
when(resultSet.currentPage()).thenReturn(Collections.singleton(row));
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(Query.empty(), User.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("SELECT count(1) FROM users");
}
@Test // DATACASS-292, DATACASS-618
void insertShouldInsertEntity() {
when(resultSet.wasApplied()).thenReturn(true);
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.insert(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("INSERT INTO users (firstname,id,lastname) VALUES ('Walter','heisenberg','White')");
assertThat(beforeConvert).isSameAs(user);
assertThat(beforeSave).isSameAs(user);
}
@Test // DATACASS-618
void insertShouldInsertVersionedEntity() {
when(resultSet.wasApplied()).thenReturn(true);
VersionedUser user = new VersionedUser("heisenberg", "Walter", "White");
ListenableFuture<VersionedUser> future = template.insert(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo(
"INSERT INTO vusers (firstname,id,lastname,version) VALUES ('Walter','heisenberg','White',0) IF NOT EXISTS");
assertThat(beforeConvert).isSameAs(user);
assertThat(beforeSave).isSameAs(user);
}
@Test // DATACASS-292
void insertShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.insert(new User("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoNodeAvailableException.class);
}
}
@Test // DATACASS-292, DATACASS-618
void updateShouldUpdateEntity() {
when(resultSet.wasApplied()).thenReturn(true);
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.update(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("UPDATE users SET firstname='Walter', lastname='White' WHERE id='heisenberg'");
assertThat(beforeConvert).isSameAs(user);
assertThat(beforeSave).isSameAs(user);
}
@Test // DATACASS-618
void updateShouldUpdateVersionedEntity() {
when(resultSet.wasApplied()).thenReturn(true);
VersionedUser user = new VersionedUser("heisenberg", "Walter", "White");
user.setVersion(0L);
ListenableFuture<VersionedUser> future = template.update(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
SimpleStatement value = statementCaptor.getValue();
assertThat(render(value)).isEqualTo(
"UPDATE vusers SET firstname='Walter', lastname='White', version=1 WHERE id='heisenberg' IF version=0");
assertThat(beforeConvert).isSameAs(user);
assertThat(beforeSave).isSameAs(user);
}
@Test // DATACASS-575
void updateShouldUpdateEntityWithOptions() {
UpdateOptions updateOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
template.update(user, updateOptions);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("UPDATE users SET firstname='Walter', lastname='White' WHERE id='heisenberg' IF EXISTS");
}
@Test // DATACASS-575
void updateShouldUpdateEntityWithLwt() {
UpdateOptions options = UpdateOptions.builder().ifCondition(where("firstname").is("Walter")).build();
User user = new User("heisenberg", "Walter", "White");
template.update(user, options);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("UPDATE users SET firstname='Walter', lastname='White' WHERE id='heisenberg' IF firstname='Walter'");
}
@Test // DATACASS-575
void updateShouldApplyUpdateQuery() {
Query query = Query.query(where("id").is("heisenberg"));
Update update = Update.update("firstname", "Walter");
template.update(query, update, User.class);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("UPDATE users SET firstname='Walter' WHERE id='heisenberg'");
}
@Test // DATACASS-575
void updateShouldApplyUpdateQueryWitLwt() {
Filter ifCondition = Filter.from(where("firstname").is("Walter"), where("lastname").is("White"));
Query query = Query.query(where("id").is("heisenberg"))
.queryOptions(UpdateOptions.builder().ifCondition(ifCondition).build());
Update update = Update.update("firstname", "Walter");
template.update(query, update, User.class);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo(
"UPDATE users SET firstname='Walter' WHERE id='heisenberg' IF firstname='Walter' AND lastname='White'");
}
@Test // DATACASS-292
void updateShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.update(new User("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoNodeAvailableException.class);
}
}
@Test // DATACASS-292
void deleteByIdShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<Boolean> future = template.deleteById(user.getId(), User.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("DELETE FROM users WHERE id='heisenberg'");
}
@Test // DATACASS-292
void deleteShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> future = template.delete(user);
assertThat(getUninterruptibly(future)).isEqualTo(user);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("DELETE FROM users WHERE id='heisenberg'");
}
@Test // DATACASS-575
void deleteShouldRemoveEntityWithLwt() {
User user = new User("heisenberg", "Walter", "White");
DeleteOptions options = DeleteOptions.builder().ifCondition(where("firstname").is("Walter")).build();
template.delete(user, options);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("DELETE FROM users WHERE id='heisenberg' IF firstname='Walter'");
}
@Test // DATACASS-575
void deleteShouldRemoveByQueryWithLwt() {
DeleteOptions options = DeleteOptions.builder().ifCondition(where("firstname").is("Walter")).build();
Query query = Query.query(where("id").is("heisenberg")).queryOptions(options);
template.delete(query, User.class);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue()))
.isEqualTo("DELETE FROM users WHERE id='heisenberg' IF firstname='Walter'");
}
@Test // DATACASS-292
void deleteShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoNodeAvailableException()));
ListenableFuture<User> future = template.delete(new User("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoNodeAvailableException.class);
}
}
@Test // DATACASS-292
void truncateShouldRemoveEntities() {
template.truncate(User.class);
verify(session).executeAsync(statementCaptor.capture());
assertThat(render(statementCaptor.getValue())).isEqualTo("TRUNCATE users");
}
private static String render(SimpleStatement statement) {
String query = statement.getQuery();
List<Object> positionalValues = statement.getPositionalValues();
for (Object positionalValue : positionalValues) {
query = query.replaceFirst("\\?",
positionalValue != null
? CodecRegistry.DEFAULT.codecFor((Class) positionalValue.getClass()).format(positionalValue)
: "NULL");
}
return query;
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static class TestResultSetFuture extends CompletableFuture<AsyncResultSet> {
private TestResultSetFuture() {}
private TestResultSetFuture(AsyncResultSet resultSet) {
complete(resultSet);
}
/**
* Create a completed future that reports a failure given {@link Throwable}.
*
* @param throwable must not be {@literal null}.
* @return the completed/failed {@link TestResultSetFuture}.
*/
private static TestResultSetFuture failed(Throwable throwable) {
TestResultSetFuture future = new TestResultSetFuture();
future.completeExceptionally(throwable);
return future;
}
}
}

View File

@@ -21,14 +21,14 @@ import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Test
import org.springframework.data.cassandra.domain.Person
import org.springframework.scheduling.annotation.AsyncResult
import java.util.concurrent.CompletableFuture
/**
* Unit tests for [AsyncCqlOperationsExtensions].
*
* @author Mark Paluch
*/
class AsyncCqlOperationsExtensionsUnitTests {
class LegacyAsyncCqlOperationsExtensionsUnitTests {
val operations = mockk<AsyncCqlOperations>(relaxed = true)
@@ -120,7 +120,14 @@ class AsyncCqlOperationsExtensionsUnitTests {
@Test // DATACASS-484
fun `query(String, ResultSetExtractor, array) extension should call its Java counterpart`() {
operations.query("", 3) { rs -> AsyncResult(Person("Walter", rs.one()!!.getString(0))) }
operations.query("", 3) { rs ->
CompletableFuture.completedFuture(
Person(
"Walter",
rs.one()!!.getString(0)
)
)
}
verify { operations.query(eq(""), any<AsyncResultSetExtractor<Person>>(), eq(3)) }
}

View File

@@ -0,0 +1,10 @@
[[cassandra.migration.3.x-to-4.x]]
= Migration Guide from Spring Data Cassandra 3.x to 4.x
Spring Data for Apache Cassandra 4.0 introduces a set of breaking changes when upgrading from earlier versions.
== Asynchronous Template API
With the deprecation of `ListenableFuture`, `AsyncCqlOperations` and `AsyncCassandraOperations` and their dependant classes were migrated to `CompletableFuture`.
If your application heavily depends on `ListenableFuture` and you cannot easily migrate to `CompletableFuture` then we suggest switching to the legacy `Async…Operations` types in the `legacy` subpackage.
That is `org.springframework.data.cassandra.core.cql.legacy` for `AsyncCqlOperations` and `org.springframework.data.cassandra.core.legacy` for `AsyncCassandraOperations`.

View File

@@ -4,3 +4,5 @@
include::migration-guide-1.5-to-2.0.adoc[leveloffset=+1]
include::migration-guide-2.2-to-3.0.adoc[leveloffset=+1]
include::migration-guide-3.0-to-4.0.adoc[leveloffset=+1]