From 8af23880e97fbf17d68a605771d7d5cae9bd82e9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 19 Oct 2016 14:56:11 +0200 Subject: [PATCH] DATACASS-292 - Provide revised synchronous and asynchronous CQL and Cassandra templates. We now provide revised CQL and Cassandra templates as central classes to interact with CQL and Cassandra with object mapping. Previously, synchronous and asynchronous methods were exposed inside the same interfaces that made it hard to chose the right method. The revised Template API consists of: * CqlTemplate * AsyncCqlTemplate * CassandraTemplate * AsyncCassandraTemplate CassandraTemplate and AsyncCassandraTemplate reuse CqlTemplate and AsyncCqlTemplate instead of extending from these. This is, to not mix methods using conversion/object mapping with lower level CQL execution methods. AsyncCqlTemplate and AsyncCassandraTemplate are all new and benefit from ListenableFuture as synchronization aid. They no longer rely on various callback-interfaces. CassandraTemplate and AsyncCassandraTemplate no longer provide insert/update/delete methods accepting a collection of items. Use CassandraBatchOperations for atomic batches to group operations. --- .../cassandra/core/AsyncCqlOperations.java | 722 +++++ .../cassandra/core/AsyncCqlTemplate.java | 973 +++++++ .../core/AsyncPreparedStatementCreator.java | 53 + .../cassandra/core/AsyncSessionCallback.java | 57 + .../cassandra/core/CqlOperations.java | 1892 +++++-------- .../cassandra/core/CqlTemplate.java | 2365 ++++++----------- ...ionTranslatingListenableFutureAdapter.java | 155 ++ .../core/GuavaListenableFutureAdapter.java | 158 ++ .../cassandra/core/HostMapper.java | 23 +- .../core/PreparedStatementCallback.java | 29 + .../core/PreparedStatementCreator.java | 22 +- .../cassandra/core/QueryOptionsUtil.java | 151 ++ .../cassandra/core/ResultSetExtractor.java | 29 + .../cassandra/core/RingMemberHostMapper.java | 33 +- .../cassandra/core/RowCallbackHandler.java | 30 +- .../cassandra/core/RowMapper.java | 26 +- .../core/RowMapperResultSetExtractor.java | 87 + .../cassandra/core/SessionCallback.java | 33 +- .../cassandra/support/CassandraAccessor.java | 48 + .../AsyncCqlTemplateIntegrationTests.java | 214 ++ .../core/AsyncCqlTemplateUnitTests.java | 1032 +++++++ .../core/CqlTemplateIntegrationTests.java | 210 ++ .../cassandra/core/CqlTemplateUnitTests.java | 1202 ++++++--- .../core/QueryOptionsUtilUnitTests.java | 194 ++ ...tractEmbeddedCassandraIntegrationTest.java | 4 +- ...stractKeyspaceCreatingIntegrationTest.java | 6 +- .../test/integration/CassandraRule.java | 8 +- .../test/integration/KeyspaceRule.java | 2 +- .../core/CqlOperationsIntegrationTests.java | 1231 --------- ...chronousCqlOperationsIntegrationTests.java | 422 --- .../test/integration/core/async/Book.java | 42 - .../integration/support/ListListener.java | 66 - .../support/ListOfMapListener.java | 69 - .../test/integration/support/MapListener.java | 66 - .../integration/support/ObjectListener.java | 65 - .../integration/support/QueryListener.java | 55 - .../core/AsyncCassandraOperations.java | 236 ++ .../core/AsyncCassandraTemplate.java | 467 ++++ .../core/CassandraAdminOperations.java | 25 +- .../core/CassandraAdminTemplate.java | 97 +- .../core/CassandraBatchTemplate.java | 33 +- .../cassandra/core/CassandraOperations.java | 616 +---- ...assandraPersistentEntitySchemaCreator.java | 4 +- .../cassandra/core/CassandraTemplate.java | 1388 +++------- .../data/cassandra/core/QueryUtils.java | 113 + .../core/ReactiveCassandraTemplate.java | 10 +- .../query/CassandraQueryExecution.java | 11 +- .../query/StringBasedCassandraQuery.java | 6 +- .../support/SimpleCassandraRepository.java | 26 +- .../CassandraSessionFactoryBeanUnitTests.java | 14 +- .../convert/ColumnReaderUnitTests.java | 4 +- .../MappingCassandraConverterUnitTests.java | 4 +- ...syncCassandraTemplateIntegrationTests.java | 135 + .../core/AsyncCassandraTemplateUnitTests.java | 497 ++++ ...assandraAdminTemplateIntegrationTests.java | 6 +- ...assandraBatchTemplateIntegrationTests.java | 16 +- ...ersistentEntitySchemaCreatorUnitTests.java | 8 +- .../CassandraTemplateIntegrationTests.java | 242 ++ .../core/CassandraTemplateUnitTests.java | 426 ++- ...PersistentPropertyComparatorUnitTests.java | 4 +- .../ParameterConversionTestSupport.java | 8 +- .../ConvertingParameterAccessorUnitTests.java | 4 +- .../StringBasedCassandraQueryUnitTests.java | 9 +- .../CassandraRepositoryFactoryUnitTests.java | 8 +- ...nousCassandraTemplateIntegrationTests.java | 288 -- .../CassandraOperationsIntegrationTests.java | 815 ------ .../CompositeKeyCrudIntegrationTests.java | 12 +- ...tedCompositePrimaryKeyRepositoryTests.java | 5 +- .../config/ForceQuotedRepositoryTests.java | 4 +- .../CustomConversionTests.java | 10 +- ...ateMapIdProxyDelegateIntegrationTests.java | 12 +- ...CassandraTemplateMapIdIntegrationTest.java | 12 +- .../CassandraTypeMappingIntegrationTest.java | 93 +- .../cdi/CassandraOperationsProducer.java | 15 +- .../QueryDerivationIntegrationTests.java | 24 +- .../UserRepositoryIntegrationTests.java | 5 +- ...gDataEmbeddedCassandraIntegrationTest.java | 3 +- .../integration/support/SchemaTestUtils.java | 28 +- 78 files changed, 9074 insertions(+), 8443 deletions(-) create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlOperations.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlTemplate.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/AsyncPreparedStatementCreator.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/AsyncSessionCallback.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/ExceptionTranslatingListenableFutureAdapter.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/GuavaListenableFutureAdapter.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/QueryOptionsUtil.java create mode 100644 spring-cql/src/main/java/org/springframework/cassandra/core/RowMapperResultSetExtractor.java create mode 100644 spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateIntegrationTests.java create mode 100644 spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateUnitTests.java create mode 100644 spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateIntegrationTests.java mode change 100755 => 100644 spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java create mode 100755 spring-cql/src/test/java/org/springframework/cassandra/core/QueryOptionsUtilUnitTests.java delete mode 100755 spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/CqlOperationsIntegrationTests.java delete mode 100755 spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/AsynchronousCqlOperationsIntegrationTests.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/Book.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListListener.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListOfMapListener.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/MapListener.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ObjectListener.java delete mode 100644 spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/QueryListener.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryUtils.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateUnitTests.java rename spring-data-cassandra/src/test/java/org/springframework/data/cassandra/{test/integration => }/core/CassandraAdminTemplateIntegrationTests.java (91%) create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java delete mode 100755 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java delete mode 100755 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlOperations.java b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlOperations.java new file mode 100644 index 000000000..3984586f8 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlOperations.java @@ -0,0 +1,722 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import java.util.List; +import java.util.Map; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.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 + * @since 2.0 + * @see AsyncCqlTemplate + */ +public interface AsyncCqlOperations { + + // ------------------------------------------------------------------------- + // Methods dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + + /** + * Execute a CQL data access operation, implemented as callback action working on a + * {@link com.datastax.driver.core.Session}. This allows for implementing arbitrary data access operations, within + * Spring's managed CQL environment: that is, converting CQL + * {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * 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. + */ + ListenableFuture execute(AsyncSessionCallback 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 empty or {@literal null}. + * @return boolean value whether the statement was applied. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture execute(String cql) throws DataAccessException; + + /** + * Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + *

+ * 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 empty or {@literal null}. + * @param rse object that will extract all rows of results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the ResultSetExtractor. + * @throws DataAccessException if there is any problem executing the query. + * @see #query(String, ResultSetExtractor, Object...) + */ + ListenableFuture query(String cql, ResultSetExtractor rse) throws DataAccessException; + + /** + * Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. + *

+ * 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 {@code null} as argument array. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rch 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 query(String cql, RowCallbackHandler rch) throws DataAccessException; + + /** + * Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}. + *

+ * 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 empty or {@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[]) + */ + ListenableFuture> query(String cql, RowMapper rowMapper) throws DataAccessException; + + /** + * Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}. + *

+ * 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 empty or {@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[]) + */ + ListenableFuture queryForObject(String cql, RowMapper rowMapper) throws DataAccessException; + + /** + * Execute a query for a result object, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@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 {@link Mono#empty()} 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[]) + */ + ListenableFuture queryForObject(String cql, Class requiredType) throws DataAccessException; + + /** + * Execute a query for a result Map, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@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> queryForMap(String cql) throws DataAccessException; + + /** + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@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 + */ + ListenableFuture> queryForList(String cql, Class elementType) throws DataAccessException; + + /** + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@literal null}. + * @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>> queryForList(String cql) throws DataAccessException; + + /** + * Execute a query for a ResultSet, given static CQL. + *

+ * 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. + *

+ * The results will be mapped to an {@link ResultSet}. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @return a {@link ResultSet} representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String, Object[]) + */ + ListenableFuture queryForResultSet(String cql) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.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 execute(Statement statement) throws DataAccessException; + + /** + * Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + *

+ * 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 rse object that will extract all rows of results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the ResultSetExtractor. + * @throws DataAccessException if there is any problem executing the query. + * @see #query(String, ResultSetExtractor, Object...) + */ + ListenableFuture query(Statement statement, ResultSetExtractor rse) throws DataAccessException; + + /** + * Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. + *

+ * 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 {@code null} as argument array. + * + * @param statement static CQL {@link Statement}, must not be {@literal null}. + * @param rch 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 query(Statement statement, RowCallbackHandler rch) throws DataAccessException; + + /** + * Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}. + *

+ * 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[]) + */ + ListenableFuture> query(Statement statement, RowMapper rowMapper) throws DataAccessException; + + /** + * Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}. + *

+ * 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[]) + */ + ListenableFuture queryForObject(Statement statement, RowMapper rowMapper) throws DataAccessException; + + /** + * Execute a query for a result object, given static CQL. + *

+ * 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. + *

+ * 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 {@link Mono#empty()} 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[]) + */ + ListenableFuture queryForObject(Statement statement, Class requiredType) throws DataAccessException; + + /** + * Execute a query for a result Map, given static CQL. + *

+ * 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. + *

+ * 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> queryForMap(Statement statement) throws DataAccessException; + + /** + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 + */ + ListenableFuture> queryForList(Statement statement, Class elementType) throws DataAccessException; + + /** + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@literal null}. + * @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>> queryForList(Statement statement) throws DataAccessException; + + /** + * Execute a query for a ResultSet, given static CQL. + *

+ * 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. + *

+ * 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 queryForResultSet(Statement statement) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /** + * 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.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * The callback action can return a result object, for example a domain object or a collection of domain objects. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 + */ + ListenableFuture execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback action) + 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.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * 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 empty or {@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 TODO: Lambda-usage clashes with execute(cql, + * PreparedStatementBinder) + */ + ListenableFuture execute(String cql, PreparedStatementCallback action) throws DataAccessException; + + /** + * Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * must not be {@literal null}. + * @param rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor} + * @throws DataAccessException if there is any problem + */ + ListenableFuture query(AsyncPreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException; + + /** + * Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + * + * @param cql static CQL to execute, must not be empty or {@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 rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor}. + * @throws DataAccessException if there is any problem + */ + ListenableFuture query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) + 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 ResultSetExtractor}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor}. + * @throws DataAccessException if there is any problem + */ + ListenableFuture query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor rse) + 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 ResultSetExtractor}. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rse 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 ResultSetExtractor} + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture query(String cql, ResultSetExtractor rse, Object... args) throws DataAccessException; + + /** + * Query using a prepared statement, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * must not be {@literal null}. + * @param rch 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 query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch) 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 empty or {@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 rch 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 query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) + 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 psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 rch 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 query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch) + 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 empty or {@literal null}. + * @param rch 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 query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException; + + /** + * Query using a prepared statement, mapping each row to a Java object via a {@link RowMapper}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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. + */ + ListenableFuture> query(AsyncPreparedStatementCreator psc, RowMapper rowMapper) 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 empty or {@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. + */ + ListenableFuture> query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) + 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 psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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. + */ + ListenableFuture> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper rowMapper) + 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 empty or {@literal null}. + * @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. + */ + ListenableFuture> query(String cql, RowMapper rowMapper, 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 a + * single result row to a Java object via a {@link RowMapper}. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @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. + */ + ListenableFuture queryForObject(String cql, RowMapper rowMapper, Object... args) 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. + *

+ * 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 empty or {@literal null}. + * @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 {@link Mono#empty()} 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) + */ + ListenableFuture queryForObject(String cql, Class requiredType, Object... args) 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. + *

+ * 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 empty or {@literal null}. + * @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> queryForMap(String cql, Object... args) 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}. + *

+ * 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 empty or {@literal null}. + * @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 + */ + ListenableFuture> queryForList(String cql, Class elementType, Object... args) + 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}. + *

+ * 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 empty or {@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} that contains a {@link Map} per row + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForList(String) + */ + ListenableFuture>> queryForList(String cql, Object... args) 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. + *

+ * The results will be mapped to an {@link ResultSet}. + * + * @param cql static CQL to execute, must not be empty or {@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 ResultSet} representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String) + */ + ListenableFuture queryForResultSet(String cql, Object... args) throws DataAccessException; + + /** + * 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 psc 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 issuing the execution. + */ + // TODO: Interferes with execute(session callback lambda) + ListenableFuture execute(AsyncPreparedStatementCreator psc) 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 empty or {@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. + * @return boolean value whether the statement was applied. + * @throws DataAccessException if there is any problem issuing the execution. + */ + ListenableFuture execute(String cql, PreparedStatementBinder psb) 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 empty or {@literal null}. + * @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 issuing the execution. + */ + ListenableFuture execute(String cql, Object... args) throws DataAccessException; + +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlTemplate.java b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlTemplate.java new file mode 100644 index 000000000..e36340f89 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncCqlTemplate.java @@ -0,0 +1,973 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; +import java.util.stream.StreamSupport; + +import org.springframework.cassandra.support.CassandraAccessor; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.util.Assert; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.SettableListenableFuture; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.ConsistencyLevel; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SimpleStatement; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.exceptions.DriverException; +import com.datastax.driver.core.policies.RetryPolicy; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; + +/** + * This is the central class in the CQL core package for asynchronous Cassandra data access. 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. + *

+ * 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 ResultSetExtractor} interface extracts values from a {@link ResultSet}. See + * also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces. + *

+ * Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get + * prepared in an application context and given to services as bean reference. Note: The {@link Session} 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. + *

+ * Because this class is parameterizable by the callback interfaces and the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} interface, there should be no need to subclass + * it. + *

+ * All CQL operations performed by this class are logged at debug level, using + * "org.springframework.cassandra.core.CqlTemplate" as log category. + *

+ * NOTE: An instance of this class is thread-safe once configured. + * + * @author Mark Paluch + * @see ListenableFuture + * @see PreparedStatementCreator + * @see PreparedStatementBinder + * @see PreparedStatementCallback + * @see ResultSetExtractor + * @see RowCallbackHandler + * @see RowMapper + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ +public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOperations { + + /** + * Placeholder for default values. + */ + private final static Statement DEFAULTS = QueryBuilder.select().from("DEFAULT"); + + /** + * If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on + * statements used for query processing. + */ + private int fetchSize = -1; + + /** + * If this variable is set to a value, it will be used for setting the {@code retryPolicy} property on statements used + * for query processing. + */ + private com.datastax.driver.core.policies.RetryPolicy retryPolicy; + + /** + * If this variable is set to a value, it will be used for setting the {@code consistencyLevel} property on statements + * used for query processing. + */ + private com.datastax.driver.core.ConsistencyLevel consistencyLevel; + + /** + * Construct a new {@link AsyncCqlTemplate}. Note: The {@link Session} has to be set before using the instance. + * + * @see #setSession(Session) + */ + public AsyncCqlTemplate() {} + + /** + * Construct a new {@link AsyncCqlTemplate}, given a {@link Session}. + * + * @param session the active Cassandra {@link Session}. + */ + public AsyncCqlTemplate(Session session) { + + Assert.notNull(session, "Session must not be null"); + + setSession(session); + } + + /** + * Set the fetch size for this {@link AsyncCqlTemplate}. This is important for processing large result sets: Setting + * this higher than the default value will increase processing speed at the cost of memory consumption; setting this + * lower can avoid transferring row data that will never be read by the application. Default is -1, indicating to use + * the CQL driver's default configuration (i.e. to not pass a specific fetch size setting on to the driver). + * + * @see Statement#setFetchSize(int) + */ + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + /** + * @return the fetch size specified for this {@link AsyncCqlTemplate}. + */ + public int getFetchSize() { + return this.fetchSize; + } + + /** + * Set the retry policy for this {@link AsyncCqlTemplate}. This is important for defining behavior when a request + * fails. + * + * @see Statement#setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy) + * @see com.datastax.driver.core.policies.RetryPolicy + */ + public void setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + /** + * @return the {@link com.datastax.driver.core.policies.RetryPolicy} specified for this {@link AsyncCqlTemplate}. + */ + public com.datastax.driver.core.policies.RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + /** + * Set the consistency level for this {@link AsyncCqlTemplate}. Consistency level defines the number of nodes involved + * into query processing. Relaxed consistency level settings use fewer nodes but eventual consistency is more likely + * to occur while a higher consistency level involves more nodes to obtain results with a higher consistency + * guarantee. + * + * @see Statement#setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel) + * @see com.datastax.driver.core.policies.RetryPolicy + */ + public void setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel consistencyLevel) { + this.consistencyLevel = consistencyLevel; + } + + /** + * @return the {@link com.datastax.driver.core.ConsistencyLevel} specified for this {@link AsyncCqlTemplate}. + */ + public com.datastax.driver.core.ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + // ------------------------------------------------------------------------- + // Methods dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncSessionCallback) + */ + @Override + public ListenableFuture execute(AsyncSessionCallback action) throws DataAccessException { + + Assert.notNull(action, "Callback object must not be null"); + + try { + return action.doInSession(getSession()); + } catch (DriverException e) { + throw translateException("SessionCallback", getCql(action), e); + } + } + + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String) + */ + @Override + public ListenableFuture execute(String cql) throws DataAccessException { + + Assert.hasText(cql, "CQL must not be empty"); + + return new MappingListenableFutureAdapter<>(queryForResultSet(cql), ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public ListenableFuture query(String cql, ResultSetExtractor rse) throws DataAccessException { + + Assert.hasText(cql, "CQL must not be empty"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Executing CQL Statement [{}]", cql); + } + + SimpleStatement simpleStatement = new SimpleStatement(cql); + + applyStatementSettings(simpleStatement); + + return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>( + new GuavaListenableFutureAdapter<>(getSession().executeAsync(simpleStatement), + ex -> translateExceptionIfPossible("Query", cql, ex)), + rse::extractData), getExceptionTranslator()); + } catch (DriverException e) { + throw translateException("Query", cql, e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public ListenableFuture query(String cql, RowCallbackHandler rch) throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(cql, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture> query(String cql, RowMapper rowMapper) throws DataAccessException { + return query(cql, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture queryForObject(String cql, RowMapper rowMapper) throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(cql, rowMapper), DataAccessUtils::requiredSingleResult), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class) + */ + @Override + public ListenableFuture queryForObject(String cql, Class requiredType) throws DataAccessException { + return queryForObject(cql, getSingleColumnRowMapper(requiredType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String) + */ + @Override + public ListenableFuture> queryForMap(String cql) throws DataAccessException { + return queryForObject(cql, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class) + */ + @Override + public ListenableFuture> queryForList(String cql, Class elementType) throws DataAccessException { + return query(cql, getSingleColumnRowMapper(elementType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String) + */ + @Override + public ListenableFuture>> queryForList(String cql) throws DataAccessException { + return query(cql, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String) + */ + @Override + public ListenableFuture queryForResultSet(String cql) throws DataAccessException { + return query(cql, rs -> rs); + } + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(com.datastax.driver.core.Statement) + */ + @Override + public ListenableFuture execute(Statement statement) throws DataAccessException { + + Assert.notNull(statement, "CQL Statement must not be null"); + + return new MappingListenableFutureAdapter<>(queryForResultSet(statement), ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public ListenableFuture query(Statement statement, ResultSetExtractor rse) throws DataAccessException { + + Assert.notNull(statement, "CQL Statement must not be null"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Executing CQL Statement [{}]", statement); + } + + applyStatementSettings(statement); + + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(new GuavaListenableFutureAdapter<>(getSession().executeAsync(statement), + ex -> translateExceptionIfPossible("Query", statement.toString(), ex)), rse::extractData), + getExceptionTranslator()); + } catch (DriverException e) { + throw translateException("Query", statement.toString(), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public ListenableFuture query(Statement statement, RowCallbackHandler rch) throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>( + query(statement, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture> query(Statement statement, RowMapper rowMapper) throws DataAccessException { + return query(statement, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture queryForObject(Statement statement, RowMapper rowMapper) + throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(statement, rowMapper), DataAccessUtils::requiredSingleResult), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public ListenableFuture queryForObject(Statement statement, Class requiredType) throws DataAccessException { + return queryForObject(statement, getSingleColumnRowMapper(requiredType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(com.datastax.driver.core.Statement) + */ + @Override + public ListenableFuture> queryForMap(Statement statement) throws DataAccessException { + return queryForObject(statement, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public ListenableFuture> queryForList(Statement statement, Class elementType) + throws DataAccessException { + return query(statement, getSingleColumnRowMapper(elementType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement) + */ + @Override + public ListenableFuture>> queryForList(Statement statement) throws DataAccessException { + return query(statement, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(com.datastax.driver.core.Statement) + */ + @Override + public ListenableFuture queryForResultSet(Statement statement) throws DataAccessException { + return query(statement, rs -> rs); + } + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public ListenableFuture execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback action) + throws DataAccessException { + + Assert.notNull(psc, "PreparedStatementCreator must not be null"); + Assert.notNull(action, "PreparedStatementCallback object must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Preparing statement [{}] using {}", getCql(psc), psc); + } + + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(psc.createPreparedStatement(getSession()), preparedStatement -> { + + try { + applyStatementSettings(preparedStatement); + return action.doInPreparedStatement(preparedStatement); + } catch (DriverException e) { + throw translateException("PreparedStatementCallback", preparedStatement.toString(), e); + } + }), getExceptionTranslator()); + + } catch (DriverException e) { + throw translateException("PreparedStatementCallback", getCql(psc), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public ListenableFuture query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, + ResultSetExtractor rse) throws DataAccessException { + + Assert.notNull(psc, "AsyncPreparedStatementCreator must not be null"); + Assert.notNull(rse, "ResultSetExtractor object must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Preparing statement [{}] using {}", getCql(psc), psc); + } + + Session session = getSession(); + + PersistenceExceptionTranslator exceptionTranslator = ex -> translateExceptionIfPossible("Query", getCql(psc), ex); + + ListenableFuture psFuture = new MappingListenableFutureAdapter<>( + psc.createPreparedStatement(session), ps -> { + + if (logger.isDebugEnabled()) { + logger.debug("Executing prepared statement [{}]", ps); + } + + BoundStatement boundStatement = psb != null ? psb.bindValues(ps) : ps.bind(); + + applyStatementSettings(boundStatement); + + return boundStatement; + }); + + SettableListenableFuture settableListenableFuture = new SettableListenableFuture(); + psFuture.addCallback(boundStatement -> { + + Futures.addCallback(session.executeAsync(boundStatement), new FutureCallback() { + @Override + public void onSuccess(ResultSet result) { + try { + settableListenableFuture.set(rse.extractData(result)); + } catch (DriverException e) { + settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible(e)); + } + } + + @Override + public void onFailure(Throwable ex) { + + if (ex instanceof DriverException) { + settableListenableFuture + .setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex)); + } else { + settableListenableFuture.setException(ex); + } + } + }); + + }, ex -> { + if (ex instanceof DriverException) { + settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex)); + } else { + settableListenableFuture.setException(ex); + } + }); + + return settableListenableFuture; + + } catch (DriverException e) { + throw translateException("Query", getCql(psc), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public ListenableFuture execute(String cql, PreparedStatementCallback action) throws DataAccessException { + return execute(newAsyncPreparedStatementCreator(cql), action); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public ListenableFuture query(AsyncPreparedStatementCreator psc, ResultSetExtractor rse) + throws DataAccessException { + return query(psc, null, rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public ListenableFuture query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) + throws DataAccessException { + return query(newAsyncPreparedStatementCreator(cql), psb, rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor, java.lang.Object[]) + */ + @Override + public ListenableFuture query(String cql, ResultSetExtractor rse, Object... args) + throws DataAccessException { + return query(cql, newArgPreparedStatementBinder(args), rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public ListenableFuture query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch) + throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(psc, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public ListenableFuture query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) + throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(cql, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public ListenableFuture query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, + RowCallbackHandler rch) throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(psc, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler, java.lang.Object[]) + */ + @Override + public ListenableFuture query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>( + new MappingListenableFutureAdapter<>(query(newAsyncPreparedStatementCreator(cql), + newArgPreparedStatementBinder(args), new RowCallbackHandlerResultSetExtractor(rch)), o -> null), + getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture> query(AsyncPreparedStatementCreator psc, RowMapper rowMapper) + throws DataAccessException { + return query(psc, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture> query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) + throws DataAccessException { + return query(cql, psb, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) + */ + @Override + public ListenableFuture> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, + RowMapper rowMapper) throws DataAccessException { + return query(psc, psb, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) + */ + @Override + public ListenableFuture> query(String cql, RowMapper rowMapper, Object... args) + throws DataAccessException { + return query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) + */ + @Override + public ListenableFuture queryForObject(String cql, RowMapper rowMapper, Object... args) + throws DataAccessException { + return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>( + query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper, 1)), + DataAccessUtils::requiredSingleResult), getExceptionTranslator()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class, java.lang.Object[]) + */ + @Override + public ListenableFuture queryForObject(String cql, Class requiredType, Object... args) + throws DataAccessException { + return queryForObject(cql, getSingleColumnRowMapper(requiredType), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String, java.lang.Object[]) + */ + @Override + public ListenableFuture> queryForMap(String cql, Object... args) throws DataAccessException { + return queryForObject(cql, getColumnMapRowMapper(), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class, java.lang.Object[]) + */ + @Override + public ListenableFuture> queryForList(String cql, Class elementType, Object... args) + throws DataAccessException { + return query(cql, getSingleColumnRowMapper(elementType), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Object[]) + */ + @Override + public ListenableFuture>> queryForList(String cql, Object... args) + throws DataAccessException { + return query(cql, getColumnMapRowMapper(), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String, java.lang.Object[]) + */ + @Override + public ListenableFuture queryForResultSet(String cql, Object... args) throws DataAccessException { + return query(cql, rs -> rs, args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator) + */ + @Override + public ListenableFuture execute(AsyncPreparedStatementCreator psc) throws DataAccessException { + return query(psc, ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder) + */ + @Override + public ListenableFuture execute(String cql, PreparedStatementBinder psb) throws DataAccessException { + return query(newAsyncPreparedStatementCreator(cql), psb, ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, java.lang.Object[]) + */ + @Override + public ListenableFuture execute(String cql, Object... args) throws DataAccessException { + return execute(cql, newArgPreparedStatementBinder(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 {@code null}) + * @param ex the offending {@code RuntimeException}. + * @return the translated {@link DataAccessException} or {@literal null} if translation not possible. + * @see CqlProvider + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + protected DataAccessException translateExceptionIfPossible(String task, String cql, RuntimeException ex) { + + if (ex instanceof DriverException) { + return translate(task, cql, (DriverException) ex); + } + + return null; + } + + /** + * 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 {@code null}) + * @param ex the offending {@code RuntimeException}. + * @return the exception translation {@link Function} + * @see CqlProvider + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + protected DataAccessException translateException(String task, String cql, DriverException ex) { + return translate(task, cql, ex); + } + + /** + * Create a new RowMapper for reading columns as key-value pairs. + * + * @return the RowMapper to use + * @see ColumnMapRowMapper + */ + protected RowMapper> getColumnMapRowMapper() { + return new ColumnMapRowMapper(); + } + + /** + * Create a new RowMapper for reading result objects from a single column. + * + * @param requiredType the type that each result object is expected to match + * @return the RowMapper to use + * @see SingleColumnRowMapper + */ + protected RowMapper getSingleColumnRowMapper(Class requiredType) { + return SingleColumnRowMapper.newInstance(requiredType); + } + + /** + * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement + * settings such as fetch size, retry policy, and consistency level. + * + * @param stmt the CQL Statement to prepare + * @see #setFetchSize(int) + * @see #setRetryPolicy(RetryPolicy) + * @see #setConsistencyLevel(ConsistencyLevel) + */ + protected void applyStatementSettings(Statement stmt) { + + int fetchSize = getFetchSize(); + if (fetchSize != -1 && stmt.getFetchSize() == DEFAULTS.getFetchSize()) { + stmt.setFetchSize(fetchSize); + } + + RetryPolicy retryPolicy = getRetryPolicy(); + if (retryPolicy != null && stmt.getRetryPolicy() == DEFAULTS.getRetryPolicy()) { + stmt.setRetryPolicy(retryPolicy); + } + + ConsistencyLevel consistencyLevel = getConsistencyLevel(); + if (consistencyLevel != null && stmt.getConsistencyLevel() == DEFAULTS.getConsistencyLevel()) { + stmt.setConsistencyLevel(consistencyLevel); + } + } + + /** + * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement + * settings such as retry policy and consistency level. + * + * @param stmt the CQL Statement to prepare + * @see #setRetryPolicy(RetryPolicy) + * @see #setConsistencyLevel(ConsistencyLevel) + */ + protected void applyStatementSettings(PreparedStatement stmt) { + + RetryPolicy retryPolicy = getRetryPolicy(); + if (retryPolicy != null) { + stmt.setRetryPolicy(retryPolicy); + } + + ConsistencyLevel consistencyLevel = getConsistencyLevel(); + if (consistencyLevel != null) { + stmt.setConsistencyLevel(consistencyLevel); + } + } + + /** + * Create a new arg-based PreparedStatementSetter using the args passed in. By default, we'll create an + * {@link ArgumentPreparedStatementBinder}. This method allows for the creation to be overridden by subclasses. + * + * @param args object array with arguments + * @return the new {@link PreparedStatementBinder} to use + */ + protected PreparedStatementBinder newArgPreparedStatementBinder(Object[] args) { + return new ArgumentPreparedStatementBinder(args); + } + + /** + * Create a new CQL-based 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(cql, + ex -> translateExceptionIfPossible("PrepareStatement", cql, ex)); + } + + /** + * Determine CQL from potential provider object. + * + * @param cqlProvider object that's potentially a {@link CqlProvider} + * @return the CQL string, or {@code null} + * @see CqlProvider + */ + private static String getCql(Object cqlProvider) { + + if (cqlProvider instanceof CqlProvider) { + return ((CqlProvider) cqlProvider).getCql(); + } else { + return null; + } + } + + private static class SimpleAsyncPreparedStatementCreator implements AsyncPreparedStatementCreator, CqlProvider { + + private final String cql; + private final PersistenceExceptionTranslator persistenceExceptionTranslator; + + SimpleAsyncPreparedStatementCreator(String cql, PersistenceExceptionTranslator persistenceExceptionTranslator) { + + Assert.notNull(cql, "CQL must not be null"); + + this.cql = cql; + this.persistenceExceptionTranslator = persistenceExceptionTranslator; + } + + @Override + public ListenableFuture createPreparedStatement(Session session) throws DriverException { + + return new GuavaListenableFutureAdapter<>(session.prepareAsync(cql), persistenceExceptionTranslator); + } + + @Override + public String getCql() { + return cql; + } + } + + private static class MappingListenableFutureAdapter + extends org.springframework.util.concurrent.ListenableFutureAdapter { + + private final Function mapper; + + public MappingListenableFutureAdapter(ListenableFuture adaptee, Function mapper) { + super(adaptee); + this.mapper = mapper; + } + + @Override + protected T adapt(S adapteeResult) throws ExecutionException { + return mapper.apply(adapteeResult); + } + } + + /** + * Adapter to enable use of a {@link RowCallbackHandler} inside a {@link ResultSetExtractor}. + */ + private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor { + + private final RowCallbackHandler rch; + + public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { + this.rch = rch; + } + + @Override + public Object extractData(ResultSet rs) { + + StreamSupport.stream(rs.spliterator(), false).forEach(rch::processRow); + return null; + } + } +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncPreparedStatementCreator.java b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncPreparedStatementCreator.java new file mode 100644 index 000000000..b799c9466 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncPreparedStatementCreator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * 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 Session}, provided + * by the {@link CqlTemplate} class. + *

+ * 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 AsyncCqlTemplate} class will catch and handle {@link DriverException}s appropriately. + *

+ * A {@link AsyncPreparedStatementCreator} 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 2.0 + */ +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 CqlTemplate} class will handle them. + */ + ListenableFuture createPreparedStatement(Session session) throws DriverException; +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncSessionCallback.java b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncSessionCallback.java new file mode 100644 index 000000000..ea9a1281b --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/AsyncSessionCallback.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import org.springframework.dao.DataAccessException; + +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; +import org.springframework.util.concurrent.ListenableFuture; + +/** + * Generic callback interface for code that operates asynchronously on a Cassandra {@link Session}. Allows to execute any number of + * operations on a single session, using any type and number of statements. + *

+ * This is particularly useful for delegating to existing data access code that expects a {@link Session} 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. + * + * @author David Webb + * @author Mark Paluch + * @see AsyncCqlTemplate#execute(AsyncSessionCallback) + * @see AsyncCqlTemplate#query + */ +public interface AsyncSessionCallback { + + /** + * Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about + * activating or closing the {@link Session}. + *

+ * 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 + * template. + * + * @param session active Cassandra Session, must not be {@literal null}. + * @return a result object, or {@code null} 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 doInSession(Session session) throws DriverException, DataAccessException; + +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java index 8d2c3c67b..cbb4a832f 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java @@ -1,12 +1,12 @@ /* - * Copyright 2013-2016 the original author or authors. - * + * Copyright 2016 the original author or authors. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,1239 +16,759 @@ package org.springframework.cassandra.core; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import org.springframework.cassandra.core.cql.CqlIdentifier; -import org.springframework.cassandra.core.keyspace.AlterKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.AlterTableSpecification; -import org.springframework.cassandra.core.keyspace.CreateIndexSpecification; -import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.CreateTableSpecification; -import org.springframework.cassandra.core.keyspace.DropIndexSpecification; -import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.dao.DataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Session; +import com.datastax.driver.core.Row; import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.Batch; -import com.datastax.driver.core.querybuilder.Delete; -import com.datastax.driver.core.querybuilder.Insert; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.querybuilder.Truncate; -import com.datastax.driver.core.querybuilder.Update; /** - * Operations for interacting with Cassandra at the lowest level. This interface provides Exception Translation. - * - * @author David Webb - * @author Matthew Adams - * @author John Blum + * Interface specifying a basic set of CQL operations. Implemented by {@link CqlTemplate}. Not often used directly, but + * a useful option to enhance testability, as it can easily be mocked or stubbed. + * + * @author Mark Paluch + * @since 2.0 + * @see CqlTemplate */ public interface CqlOperations { + // ------------------------------------------------------------------------- + // Methods dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + /** - * Convenient method that delegates to {@link ResultSetFuture#getUninterruptibly()} but translates exceptions if any - * are thrown. + * Execute a CQL data access operation, implemented as callback action working on a + * {@link com.datastax.driver.core.Session}. This allows for implementing arbitrary data access operations, within + * Spring's managed CQL environment: that is, converting CQL + * {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * The callback action can return a result object, for example a domain object or a collection of domain objects. * - * @param rsf The {@link ResultSetFuture} from which to get the {@link ResultSet}. - * @return The {@link ResultSet} + * @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. */ - ResultSet getResultSetUninterruptibly(ResultSetFuture rsf); + T execute(SessionCallback action) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- /** - * Convenient method that delegates to {@link ResultSetFuture#getUninterruptibly()} but translates exceptions if any - * are thrown. + * Issue a single CQL execute, typically a DDL statement, insert, update or delete statement. * - * @param rsf The {@link ResultSetFuture} from which to get the {@link ResultSet}. - * @param timeout The timeout to wait in milliseconds. A nonpositive value means wait indefinitely. - * @return The {@link ResultSet} + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @return boolean value whether the statement was applied. + * @throws DataAccessException if there is any problem executing the query. */ - ResultSet getResultSetUninterruptibly(ResultSetFuture rsf, long millis); + boolean execute(String cql) throws DataAccessException; /** - * Convenient method that delegates to {@link ResultSetFuture#getUninterruptibly()} but translates exceptions if any - * are thrown. + * Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + *

+ * 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 rsf The {@link ResultSetFuture} from which to get the {@link ResultSet}. - * @param timeout The timeout to wait. A nonpositive value means wait indefinitely. - * @param unit The {@link TimeUnit} of the timeout. - * @return The {@link ResultSet} - */ - ResultSet getResultSetUninterruptibly(ResultSetFuture rsf, long timeout, TimeUnit unit); - - /** - * Executes the supplied {@link SessionCallback} in the current Template Session. The implementation of - * SessionCallback can decide whether or not to execute() or executeAsync() the operation. - * - * @param sessionCallback - * @return Type defined in the SessionCallback - */ - T execute(SessionCallback sessionCallback) throws DataAccessException; - - /** - * Executes the supplied CQL Query and returns nothing. - * - * @param cql - */ - void execute(String cql) throws DataAccessException; - - /** - * Executes the supplied CQL Query and returns nothing. - * - * @param cql - * @param options may be null - */ - void execute(String cql, QueryOptions options) throws DataAccessException; - - /** - * Executes the supplied Query and returns nothing. - * - * @param query The {@link Statement} to execute - */ - void execute(Statement query) throws DataAccessException; - - /** - * Executes the supplied Delete Query and returns nothing. - * - * @param query The {@link Delete} to execute - */ - void execute(Delete delete) throws DataAccessException; - - /** - * Executes the supplied Insert Query and returns nothing. - * - * @param query The {@link Insert} to execute - */ - void execute(Insert insert) throws DataAccessException; - - /** - * Executes the supplied Update Query and returns nothing. - * - * @param query The {@link Update} to execute - */ - void execute(Update update) throws DataAccessException; - - /** - * Executes the supplied Batch Query and returns nothing. - * - * @param query The {@link Batch} to execute - */ - void execute(Batch batch) throws DataAccessException; - - /** - * Executes the supplied Truncate Query and returns nothing. - * - * @param query The {@link Truncate} to execute - */ - void execute(Truncate truncate) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(String cql) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @param options The {@link QueryOptions} to use. Only applies to cql statements that can use QueryOptions. - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(String cql, QueryOptions options) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @param listener The {@link Runnable} to register with the {@link ResultSetFuture} - * @return A {@link Cancellable} that can be used to cancel the query. - * @see queryAsyncronously for Reads - */ - Cancellable executeAsynchronously(String cql, Runnable listener) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @param listener The {@link Runnable} to register with the {@link ResultSetFuture} - * @param executor The {@link Executor} to regsiter with the {@link ResultSetFuture} - * @return A {@link Cancellable} that can be used to cancel the query. - * @see queryAsyncronously for Reads - */ - Cancellable executeAsynchronously(String cql, Runnable listener, Executor executor) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @param listener The {@link AsynchronousQueryListener} to register with the {@link ResultSetFuture} - * @return A {@link Cancellable} that can be used to cancel the query. - * @see queryAsyncronously for Reads - */ - Cancellable executeAsynchronously(String cql, AsynchronousQueryListener listener) throws DataAccessException; - - /** - * Executes the supplied Query Asynchronously and returns nothing. - * - * @param cql The CQL String to execute - * @param listener The {@link AsynchronousQueryListener} to register with the {@link ResultSetFuture} - * @param executor The {@link Executor} to regsiter with the {@link ResultSetFuture} - * @return A {@link Cancellable} that can be used to cancel the query. - * @see queryAsyncronously for Reads - */ - Cancellable executeAsynchronously(String cql, AsynchronousQueryListener listener, Executor executor) - throws DataAccessException; - - /** - * Executes the supplied CQL Truncate Asynchronously and returns nothing. - * - * @param query The {@link Truncate} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Truncate truncate) throws DataAccessException; - - /** - * Executes the supplied CQL Delete Asynchronously and returns nothing. - * - * @param query The {@link Delete} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Delete delete) throws DataAccessException; - - /** - * Executes the supplied CQL Insert Asynchronously and returns nothing. - * - * @param query The {@link Insert} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Insert insert) throws DataAccessException; - - /** - * Executes the supplied CQL Update Asynchronously and returns nothing. - * - * @param query The {@link Update} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Update update) throws DataAccessException; - - /** - * Executes the supplied CQL Batch Asynchronously and returns nothing. - * - * @param query The {@link Batch} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Batch batch) throws DataAccessException; - - Cancellable executeAsynchronously(Truncate truncate, AsynchronousQueryListener listener) throws DataAccessException; - - Cancellable executeAsynchronously(Delete delete, AsynchronousQueryListener listener) throws DataAccessException; - - Cancellable executeAsynchronously(Insert insert, AsynchronousQueryListener listener) throws DataAccessException; - - Cancellable executeAsynchronously(Update update, AsynchronousQueryListener listener) throws DataAccessException; - - Cancellable executeAsynchronously(Batch batch, AsynchronousQueryListener listener) throws DataAccessException; - - /** - * Executes the supplied CQL Query Asynchronously and returns nothing. - * - * @param query The {@link Statement} to execute - * @return A {@link ResultSetFuture} that can be used to cancel the query. - */ - ResultSetFuture executeAsynchronously(Statement query) throws DataAccessException; - - /** - * Executes the supplied CQL Query Asynchronously and returns nothing. - * - * @param query The {@link Statement} to execute - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable executeAsynchronously(Statement query, Runnable runnable) throws DataAccessException; - - /** - * Executes the supplied CQL Query Asynchronously and returns nothing. - * - * @param query The {@link Statement} to execute - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable executeAsynchronously(Statement query, AsynchronousQueryListener listener) throws DataAccessException; - - /** - * Executes the supplied CQL Query Asynchronously and returns nothing. - * - * @param query The {@link Statement} to execute - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable executeAsynchronously(Statement query, Runnable runnable, Executor executor) throws DataAccessException; - - /** - * Executes the supplied CQL Query Asynchronously and returns nothing. - * - * @param query The {@link Statement} to execute - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable executeAsynchronously(Statement query, AsynchronousQueryListener listener, Executor executor) - throws DataAccessException; - - /** - * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. This uses default Query - * Options when extracting the ResultSet. - * - * @param cql The Query - * @param rse The implementation for extracting the ResultSet - * @param timeout Time to wait for results - * @param timeUnit Time unit to wait for results - * @return - */ - T queryAsynchronously(String cql, ResultSetExtractor rse, Long timeout, TimeUnit timeUnit); - - /** - * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. - * - * @param cql The Query - * @param rse The implementation for extracting the ResultSet - * @param timeout Time to wait for results - * @param timeUnit Time unit to wait for results - * @param options Query Options - * @return - */ - T queryAsynchronously(String cql, ResultSetExtractor rse, Long timeout, TimeUnit timeUnit, - QueryOptions options); - - /** - * Executes the provided CQL Query and returns the ResultSetFuture for user processing. - * - * @param cql The Query - * @return - */ - ResultSetFuture queryAsynchronously(String cql); - - /** - * Executes the provided CQL Select and returns the ResultSetFuture for user processing. - * - * @param cql The {@link Select} - * @return - */ - ResultSetFuture queryAsynchronously(Select select); - - /** - * Executes the provided CQL Query and returns the ResultSetFuture for user processing. - * - * @param cql The Query - * @param options Query Options - * @return - */ - ResultSetFuture queryAsynchronously(String cql, QueryOptions options); - - /** - * Executes the provided CQL Query with the provided {@link Runnable}, which is started after the query has completed. - *

- * A more useful method than this one is {@link #queryAsynchronously(String, AsynchronousQueryListener)}, where you're - * given the {@link ResultSetFuture} after the query has been executed. - * - * @param cql The Query - * @param listener {@link Runnable} listener for handling the query in a separate thread - * @return A {@link Cancellable} that can be used to cancel the query. - * @see #queryAsynchronously(String, AsynchronousQueryListener) - */ - Cancellable queryAsynchronously(String cql, Runnable listener); - - /** - * Executes the provided CQL Select with the provided {@link Runnable}, which is started after the query has - * completed. - *

- * A more useful method than this one is {@link #queryAsynchronously(Select, AsynchronousQueryListener)}, where you're - * given the {@link ResultSetFuture} after the query has been executed. - * - * @param select The Select Query - * @param listener {@link Runnable} listener for handling the query in a separate thread - * @return A {@link Cancellable} that can be used to cancel the query. - * @see #queryAsynchronously(Select, AsynchronousQueryListener) - */ - Cancellable queryAsynchronously(Select select, Runnable listener); - - /** - * Executes the provided CQL Query with the provided listener. This is preferred over the same method that takes a - * {@link Runnable}. The {@link AsynchronousQueryListener} gives you access to the {@link ResultSetFuture} once the - * query is completed for optimal flexibility. - * - * @param cql The Query - * @param listener {@link AsynchronousQueryListener} for handling the query's {@link ResultSetFuture} in a separate - * thread - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener); - - /** - * Executes the provided CQL Select with the provided listener. This is preferred over the same method that takes a - * {@link Runnable}. The {@link AsynchronousQueryListener} gives you access to the {@link ResultSetFuture} once the - * query is completed for optimal flexibility. - * - * @param select The Select - * @param listener {@link AsynchronousQueryListener} for handling the query's {@link ResultSetFuture} in a separate - * thread - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(Select select, AsynchronousQueryListener listener); - - /** - * Executes the provided CQL Query with the Runnable implementations using the Query Options. - * - * @param cql The Query - * @param options Query Option - * @param listener Runnable Listener for handling the query in a separate thread - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, Runnable listener, QueryOptions options); - - /** - * Executes the provided CQL Query with the provided Listener and Query Options. This is preferred over the same - * method that takes a plain Runnable. The {@link AsynchronousQueryListener} gives you access to the - * {@link ResultSetFuture} once the query is completed for optimal flexibility. - * - * @param cql The Query - * @param options Query Option - * @param listener Runnable Listener for handling the query in a separate thread - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener, QueryOptions options); - - /** - * Executes the provided CQL Query with the provided Executor and Runnable implementations. - * - * @param cql The Query - * @param listener Runnable Listener for handling the query in a separate thread - * @param executor To execute the Runnable Listener - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, Runnable listener, Executor executor); - - /** - * Executes the provided CQL Select with the provided Executor and Runnable implementations. - * - * @param select The Select Query - * @param listener Runnable Listener for handling the query in a separate thread - * @param executor To execute the Runnable Listener - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(Select select, Runnable listener, Executor executor); - - /** - * Executes the provided CQL Query with the provided listener and executor. This is preferred over the same method - * that takes a plain Runnable. The {@link AsynchronousQueryListener} gives you access to the {@link ResultSetFuture} - * once the query is completed for optimal flexibility. - * - * @param cql The Query - * @param options Query Option - * @param listener Runnable Listener for handling the query in a separate thread - * @param executor To execute the Runnable Listener - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener, Executor executor); - - /** - * Executes the provided Select Query with the provided listener and executor. This is preferred over the same method - * that takes a plain Runnable. The {@link AsynchronousQueryListener} gives you access to the {@link ResultSetFuture} - * once the query is completed for optimal flexibility. - * - * @param select The Select Query - * @param listener Runnable Listener for handling the query in a separate thread - * @param executor To execute the Runnable Listener - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(Select select, AsynchronousQueryListener listener, Executor executor); - - /** - * Executes the provided CQL Query with the provided Executor and Runnable implementations. - * - * @param cql The Query - * @param options Query Option - * @param listener Runnable Listener for handling the query in a separate thread - * @param executor To execute the Runnable Listener - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, Runnable listener, QueryOptions options, Executor executor); - - /** - * Executes the provided CQL Query with the provided Listener, Executor and Query Options. This is preferred over the - * same method that takes a plain Runnable. The {@link AsynchronousQueryListener} gives you access to the - * {@link ResultSetFuture} once the query is completed for optimal flexibility. - * - * @param cql - * @param listener - * @param options - * @param executor - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener, QueryOptions options, - Executor executor); - - /** - * Executes the provided CQL query and returns the {@link ResultSet}. - * - * @param cql The query - * @return The {@link ResultSet} - */ - ResultSet query(String cql); - - /** - * Executes the provided Select query and returns the {@link ResultSet}. - * - * @param select The Select Query - * @return The {@link ResultSet} - */ - ResultSet query(Select select); - - /** - * Executes the provided CQL query with the given {@link QueryOptions} and returns the {@link ResultSet}. - * - * @param cql The query - * @param options The {@link QueryOptions}; may be null. - * @return The {@link ResultSet} - */ - ResultSet query(String cql, QueryOptions options); - - /** - * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. - * - * @param cql The Query - * @param rse The implementation for extracting the ResultSet - * @return Type specified in the ResultSetExtractor - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rse object that will extract all rows of results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the ResultSetExtractor. + * @throws DataAccessException if there is any problem executing the query. + * @see #query(String, ResultSetExtractor, Object...) */ T query(String cql, ResultSetExtractor rse) throws DataAccessException; /** - * Executes the provided Select Query, and extracts the results with the ResultSetExtractor. + * Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. + *

+ * 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 {@code null} as argument array. * - * @param select The SelectQuery - * @param rse The implementation for extracting the ResultSet - * @return Type specified in the ResultSetExtractor - * @throws DataAccessException - */ - T query(Select select, ResultSetExtractor rse) throws DataAccessException; - - /** - * Executes the provided CQL Query, and extracts the results with the ResultSetExtractor. - * - * @param cql The Query - * @param rse The implementation for extracting the ResultSet - * @param options Query Options - * @return - * @throws DataAccessException - */ - T query(String cql, ResultSetExtractor rse, QueryOptions options) throws DataAccessException; - - /** - * Executes the provided CQL Query, and then processes the results with the RowCallbackHandler. - * - * @param cql The Query - * @param rch The implementation for processing the rows returned. - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rch 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[]) */ void query(String cql, RowCallbackHandler rch) throws DataAccessException; /** - * Executes the provided Select Query, and then processes the results with the RowCallbackHandler. + * Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}. + *

+ * 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 select The Select Query - * @param rch The implementation for processing the rows returned. - * @throws DataAccessException - */ - void query(Select select, RowCallbackHandler rch) throws DataAccessException; - - /** - * Executes the provided CQL Query, and then processes the results with the RowCallbackHandler. - * - * @param cql The Query - * @param rch The implementation for processing the rows returned. - * @param options Query Options Object - * @throws DataAccessException - */ - void query(String cql, RowCallbackHandler rch, QueryOptions options) throws DataAccessException; - - /** - * Processes the ResultSet through the RowCallbackHandler and return nothing. This is used internal to the Template - * for core operations, but is made available through Operations in the event you have a ResultSet to process. The - * ResultsSet could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet Results to process - * @param rch RowCallbackHandler with the processing implementation - * @throws DataAccessException - */ - void process(ResultSet resultSet, RowCallbackHandler rch) throws DataAccessException; - - /** - * Executes the provided CQL Query, and maps all Rows returned with the supplied RowMapper. - * - * @param cql The Query - * @param rowMapper The implementation for mapping all rows - * @return List of processed by the RowMapper - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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[]) */ List query(String cql, RowMapper rowMapper) throws DataAccessException; /** - * Executes the provided Select Query, and maps all Rows returned with the supplied RowMapper. - * - * @param select The Select Query - * @param rowMapper The implementation for mapping all rows - * @return List of processed by the RowMapper - * @throws DataAccessException - */ - List query(Select select, RowMapper rowMapper) throws DataAccessException; - - /** - * Executes the provided CQL Query, and maps all Rows returned with the supplied RowMapper. - * - * @param cql The Query - * @param rowMapper The implementation for mapping all rows - * @param options Query Options Object - * @return List of processed by the RowMapper - * @throws DataAccessException - */ - List query(String cql, RowMapper rowMapper, QueryOptions options) throws DataAccessException; - - /** - * Processes the ResultSet through the RowMapper and returns the List of mapped Rows. This is used internal to the - * Template for core operations, but is made available through Operations in the event you have a ResultSet to - * process. The ResultsSet could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet Results to process - * @param rowMapper RowMapper with the processing implementation - * @return List of generated by the RowMapper - * @throws DataAccessException - */ - List process(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException; - - /** - * Executes the provided string CQL query, and maps the first row returned with the supplied {@link RowMapper}. - * - * @param cql The string query CQL. - * @param rowMapper The {@link RowMapper} to convert the row into an object of type T. - * @param listener The listener that receives the results upon completion. - * @return A {@link Cancellable} that can be used to cancel the query. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(String cql, RowMapper rowMapper, QueryForObjectListener listener) - throws DataAccessException; - - /** - * Executes the provided string CQL query, and maps the first row returned with the supplied {@link RowMapper}. - * - * @param cql The string query CQL. - * @param rowMapper The {@link RowMapper} to convert the row into an object of type T. - * @param listener The listener that receives the results upon completion. - * @param options The {@link QueryOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(String cql, RowMapper rowMapper, QueryForObjectListener listener, - QueryOptions options) throws DataAccessException; - - /** - * Executes the provided {@link Select} query, and maps the first row returned with the supplied {@link RowMapper}. - * - * @param select The {@link Select} query to execute. - * @param rowMapper The {@link RowMapper} to convert the row into an object of type T. - * @param listener The listener that receives the results upon completion. - * @return A {@link Cancellable} that can be used to cancel the query. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(Select select, RowMapper rowMapper, - QueryForObjectListener listener) throws DataAccessException; - - /** - * Executes the provided CQL Query, and maps ONE Row returned with the supplied RowMapper. + * Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}. *

- * This expects only ONE row to be returned. More than one Row will cause an Exception to be thrown. - *

+ * 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 The Query - * @param rowMapper The implementation for convert the Row to - * @return Object - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 queryForObject(String cql, RowMapper rowMapper) throws DataAccessException; /** - * Executes the provided Select Query, and maps ONE Row returned with the supplied RowMapper. + * Execute a query for a result object, given static CQL. *

- * This expects only ONE row to be returned. More than one Row will cause an Exception to be thrown. - *

+ * 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. + *

+ * 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 select The Select Query - * @param rowMapper The implementation for convert the Row to - * @return Object - * @throws DataAccessException - */ - T queryForObject(Select select, RowMapper rowMapper) throws DataAccessException; - - /** - * Process {@link ResultSet} with {@link RowMapper}. This method is used internally to the template for core - * operations, but is made available through this interface in the event you have a {@link ResultSet} to process. The - * {@link ResultSet} could come from a {@link ResultSetFuture} after an asynchronous query. - * - * @param resultSet {@link ResultSet} to process, must not be {@literal null}. - * @param rowMapper {@link RowMapper} used to process the single row of the result set, must not be {@literal null}. - * @throws IncorrectResultSizeDataAccessException if no rows are found, or more than 1 row is found. - * @throws DataAccessException if a Cassandra driver error occurs. - */ - T processOne(ResultSet resultSet, RowMapper rowMapper) throws DataAccessException; - - /** - * Executes the provided query and tries to return the first column of the first Row as a Class. - * - * @param cql The Query - * @param requiredType Valid Class that Cassandra Data Types can be converted to. - * @return The Object - item [0,0] in the result table of the query. - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 {@link Mono#empty()} 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 queryForObject(String cql, Class requiredType) throws DataAccessException; /** - * Executes the provided {@link Select} query and returns the first column of the first Row as an object of type - * T. + * Execute a query for a result Map, given static CQL. + *

+ * 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. + *

+ * 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 select The {@link Select} query - * @param requiredType Type that Cassandra data types can be converted to. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(Select select, Class requiredType, QueryForObjectListener listener) - throws DataAccessException; - - /** - * Executes the provided select CQL query and returns the first column of the first Row as an object of type - * T. - * - * @param cql The select query CQL. Must not be null or blank. - * @param requiredType The type to convert the first column of the first row to. Must not be null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. Must not be null. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(String cql, Class requiredType, QueryForObjectListener listener) - throws DataAccessException; - - /** - * Executes the provided select CQL query and returns the first column of the first Row as an object of type - * T. - * - * @param cql The select query CQL. Must not be null or blank. - * @param requiredType The type to convert the first column of the first row to. Must not be null. - * @param options The {@link QueryOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. Must not be null. - * @throws DataAccessException - */ - Cancellable queryForObjectAsynchronously(String cql, Class requiredType, QueryForObjectListener listener, - QueryOptions options) throws DataAccessException; - - /** - * Executes the provided Select query and tries to return the first column of the first Row as a Class. - * - * @param select The Select Query - * @param requiredType Valid Class that Cassandra Data Types can be converted to. - * @return The Object - item [0,0] in the result table of the query. - * @throws DataAccessException - */ - T queryForObject(Select select, Class requiredType) throws DataAccessException; - - /** - * Process a ResultSet, trying to convert the first columns of the first Row to Class. This is used internal to the - * Template for core operations, but is made available through Operations in the event you have a ResultSet to - * process. The ResultsSet could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet - * @param requiredType - * @return - * @throws DataAccessException - */ - T processOne(ResultSet resultSet, Class requiredType) throws DataAccessException; - - /** - * Executes the provided CQL Query and maps ONE Row to a basic Map of Strings and Objects. If more than one Row - * is returned from the Query, an exception will be thrown. - * - * @param cql The Query - * @return Map representing the results of the Query - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 */ Map queryForMap(String cql) throws DataAccessException; /** - * Executes the provided Select Query and maps ONE Row to a basic Map of Strings and Objects. If more than one - * Row is returned from the Query, an exception will be thrown. + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 select The Select Query - * @return Map representing the results of the Query - * @throws DataAccessException - */ - Map queryForMap(Select select) throws DataAccessException; - - /** - * Executes the provided CQL query asynchronously and maps the first row to a {@link Map}<String,Object>. - * Additional rows are ignored. - * - * @param cql The select query CQL. Must not be null or blank. - * @param listener The {@link QueryForMapListener} that will recieve the results upon query completion. Must not be - * null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. Must not be null. - * @throws DataAccessException - */ - Cancellable queryForMapAsynchronously(String cql, QueryForMapListener listener) throws DataAccessException; - - /** - * Executes the provided CQL query asynchronously and maps the first row to a {@link Map}<String,Object>. - * Additional rows are ignored. - * - * @param cql The select query CQL. Must not be null or blank. - * @param listener The {@link QueryForMapListener} that will recieve the results upon query completion. Must not be - * null. - * @param options The {@link QueryOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. Must not be null. - * @throws DataAccessException - */ - Cancellable queryForMapAsynchronously(String cql, QueryForMapListener listener, QueryOptions options) - throws DataAccessException; - - /** - * Executes the provided {@link Select} query asynchronously and maps the first row to a {@link Map} - * <String,Object>. Additional rows are ignored. - * - * @param cql The select query CQL. Must not be null or blank. - * @param listener The {@link QueryForMapListener} that will recieve the results upon query completion. Must not be - * null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. Must not be null. - * @throws DataAccessException - */ - Cancellable queryForMapAsynchronously(Select select, QueryForMapListener listener) throws DataAccessException; - - /** - * Process a ResultSet with ONE Row and convert to a Map. This is used internal to the Template for core - * operations, but is made available through Operations in the event you have a ResultSet to process. The ResultsSet - * could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet - * @return - * @throws DataAccessException - */ - Map processMap(ResultSet resultSet) throws DataAccessException; - - /** - * Executes the provided CQL and returns all values in the first column of the Results as a List of the Type in the - * second argument. - * - * @param cql The Query - * @param elementType Type to cast the data values to - * @return List of elementType - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 */ List queryForList(String cql, Class elementType) throws DataAccessException; /** - * Executes the provided Select Query and returns all values in the first column of the Results as a List of the Type - * in the second argument. + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 select The Select Query - * @param elementType Type to cast the data values to - * @return List of elementType - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @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[]) */ - List queryForList(Select select, Class elementType) throws DataAccessException; + List> queryForList(String cql) throws DataAccessException; /** - * Executes the provided {@link Select} query asynchronously and returns all values in the first column of the results - * as a {@link List} of the type in the second argument. - * - * @param select The {@link Select} query - * @param elementType The type to cast the data values to - * @param listener The listener to receive the results asynchronously. Must not be null. - * @return {@link Cancellable} to cancel the query if necessary - * @throws DataAccessException + * Execute a query for a ResultSet, given static CQL. + *

+ * 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. + *

+ * The results will be mapped to an {@link ResultSet}. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @return a {@link ResultSet} representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String, Object[]) */ - Cancellable queryForListAsynchronously(Select select, Class elementType, QueryForListListener listener) - throws DataAccessException; + ResultSet queryForResultSet(String cql) throws DataAccessException; /** - * Executes the provided {@link Select} query asynchronously and returns all values in the first column of the results - * as a {@link List} of the type in the second argument. - * - * @param select The select query CQL - * @param elementType The type to cast the data values to - * @param listener The listener to receive the results asynchronously. Must not be null. - * @return {@link Cancellable} to cancel the query if necessary - * @throws DataAccessException + * Execute a query for Rows, given static CQL. + *

+ * 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. + *

+ * The results will be mapped to {@link Row}s. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @return a Row representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String, Object[]) */ - Cancellable queryForListAsynchronously(String select, Class elementType, QueryForListListener listener) - throws DataAccessException; + Iterator queryForRows(String cql) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- /** - * Process a ResultSet and convert the first column of the results to a List. This is used internal to the Template - * for core operations, but is made available through Operations in the event you have a ResultSet to process. The - * ResultsSet could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet - * @param elementType - * @return - * @throws DataAccessException + * 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. */ - List processList(ResultSet resultSet, Class elementType) throws DataAccessException; + boolean execute(Statement statement) throws DataAccessException; /** - * Executes the provided CQL and converts the results to a basic List of Maps. Each element in the List represents a - * Row returned from the Query. Each Row's columns are put into the map as column/value. - * - * @param cql The Query - * @return List of Maps with the query results - * @throws DataAccessException + * Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}. + *

+ * 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 rse object that will extract all rows of results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the ResultSetExtractor. + * @throws DataAccessException if there is any problem executing the query. + * @see #query(String, ResultSetExtractor, Object...) */ - List> queryForListOfMap(String cql) throws DataAccessException; + T query(Statement statement, ResultSetExtractor rse) throws DataAccessException; /** - * Executes the provided {@link Select} query and converts the results to a {@link List} of {@link Map}s. Each element - * in the {@link List} represents a row returned from the query. Each row's column(s) are put into a {@link Map} as - * values keyed by column name. - * - * @param select The {@link Select} query. Must not be null. - * @param listener The listener that will receive the results upon query completion. Must not be null. - * @return A {@link Cancellable} that can be used to cancel the query. Must not be null. - * @throws DataAccessException + * Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. + *

+ * 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 {@code null} as argument array. + * + * @param statement static CQL {@link Statement}, must not be {@literal null}. + * @param rch 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[]) */ - Cancellable queryForListOfMapAsynchronously(Select select, QueryForListListener> listener) - throws DataAccessException; + void query(Statement statement, RowCallbackHandler rch) throws DataAccessException; /** - * Executes the provided select CQL query and converts the results to a {@link List} of {@link Map}s. Each element in - * the {@link List} represents a row returned from the query. Each row's column(s) are put into a {@link Map} as - * values keyed by column name. - * - * @param select The select query CQL. Must not be null or blank. - * @param listener The listener that will receive the results upon query completion. Must not be null. - * @return A {@link Cancellable} that can be used to cancel the query. Must not be null. - * @throws DataAccessException + * Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}. + *

+ * 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[]) */ - Cancellable queryForListOfMapAsynchronously(String cql, QueryForListListener> listener) - throws DataAccessException; + List query(Statement statement, RowMapper rowMapper) throws DataAccessException; /** - * Executes the provided select CQL query and converts the results to a {@link List} of {@link Map}s. Each element in - * the {@link List} represents a row returned from the query. Each row's column(s) are put into a {@link Map} as - * values keyed by column name. - * - * @param select The select query CQL. Must not be null or blank. - * @param listener The listener that will receive the results upon query completion. Must not be null. - * @param options The {@link QueryOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query. Must not be null. - * @throws DataAccessException + * Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}. + *

+ * 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[]) */ - Cancellable queryForListOfMapAsynchronously(String cql, QueryForListListener> listener, - QueryOptions options) throws DataAccessException; + T queryForObject(Statement statement, RowMapper rowMapper) throws DataAccessException; /** - * Executes the provided Select Query and converts the results to a basic List of Maps. Each element in the List - * represents a Row returned from the Query. Each Row's columns are put into the map as column/value. - * - * @param select The Select Query - * @return List of Maps with the query results - * @throws DataAccessException + * Execute a query for a result object, given static CQL. + *

+ * 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. + *

+ * 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 {@link Mono#empty()} 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[]) */ - List> queryForListOfMap(Select select) throws DataAccessException; + T queryForObject(Statement statement, Class requiredType) throws DataAccessException; /** - * Process a ResultSet and convert it to a List of Maps with column/value. This is used internal to the Template for - * core operations, but is made available through Operations in the event you have a ResultSet to process. The - * ResultsSet could come from a ResultSetFuture after an asynchronous query. - * - * @param resultSet - * @return - * @throws DataAccessException + * Execute a query for a result Map, given static CQL. + *

+ * 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. + *

+ * 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 */ - List> processListOfMap(ResultSet resultSet) throws DataAccessException; + Map queryForMap(Statement statement) throws DataAccessException; /** - * Creates and caches a {@link PreparedStatement} from the given CQL, invokes the {@link PreparedStatementCallback} - * with that {@link PreparedStatement}, then returns the value returned by the {@link PreparedStatementCallback}. - * - * @param cql The CQL statement from which to create and cache a {@link PreparedStatement} - * @param action The callback that is given the {@link PreparedStatement} - * @return The value returned by the given {@link PreparedStatementCallback} - * @throws DataAccessException + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 execute(String cql, PreparedStatementCallback action) throws DataAccessException; + List queryForList(Statement statement, Class elementType) throws DataAccessException; /** - * Uses the provided {@link PreparedStatementCreator} to create a {@link PreparedStatement} in the current - * {@link Session}, then passes that {@link PreparedStatement} to the given {@link PreparedStatementCallback}. + * Execute a query for a result {@link List}, given static CQL. + *

+ * 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. + *

+ * 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 empty or {@literal null}. + * @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[]) + */ + List> queryForList(Statement statement) throws DataAccessException; + + /** + * Execute a query for a ResultSet, given static CQL. + *

+ * 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. + *

+ * 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[]) + */ + ResultSet queryForResultSet(Statement statement) throws DataAccessException; + + /** + * Execute a query for Rows, given static CQL. + *

+ * 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. + *

+ * The results will be mapped to {@link Row}s. + * + * @param statement static CQL {@link Statement}, must not be {@literal null}. + * @return a Row representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String, Object[]) + */ + Iterator queryForRows(Statement statement) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /** + * 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.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * The callback action can return a result object, for example a domain object or a collection of domain objects. * - * @param psc The {@link PreparedStatementCreator} - * @param action The callback that receives the {@link PreparedStatement} - * @return The value returned by the given {@link PreparedStatementCallback} - * @throws DataAccessException + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 */ T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException; /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the ResultSetExtractor implementation provided by the Application Code. The can return any object, - * including a List of Objects to support the ResultSet processing. + * 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.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy. + *

+ * The callback action can return a result object, for example a domain object or a collection of domain objects. * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rse The implementation for extracting the results of the query. - * @return Type generated by the ResultSetExtractor - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 */ - T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException; + T execute(String cql, PreparedStatementCallback action) throws DataAccessException; /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the ResultSetExtractor implementation provided by the Application Code. The can return any object, - * including a List of Objects to support the ResultSet processing. + * Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}. * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rse The implementation for extracting the results of the query. - * @param options The Query Options to apply to the PreparedStatement - * @return Type generated by the ResultSetExtractor - * @throws DataAccessException - */ - T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse, QueryOptions options) - throws DataAccessException; - - /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the RowCallbackHandler implementation provided and nothing is returned. - * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rch The RowCallbackHandler for processing the ResultSet - * @throws DataAccessException - */ - void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException; - - /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the RowCallbackHandler implementation provided and nothing is returned. - * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rch The RowCallbackHandler for processing the ResultSet - * @param options The Query Options Object - * @throws DataAccessException - */ - void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch, QueryOptions options) - throws DataAccessException; - - /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the RowMapper implementation provided and a List is returned with elements of Type for each Row - * returned. - * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rowMapper The implementation for Mapping a Row to Type - * @return List of for each Row returned from the Query. - * @throws DataAccessException - */ - List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) throws DataAccessException; - - /** - * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. Then, the PreparedStatementBinder will - * bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are - * processed with the RowMapper implementation provided and a List is returned with elements of Type for each Row - * returned. - * - * @param cql The Query to Prepare - * @param psb The Binding implementation - * @param rowMapper The implementation for Mapping a Row to Type - * @param options The Query Options Object - * @return List of for each Row returned from the Query. - * @throws DataAccessException - */ - List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper, QueryOptions options) - throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with - * ResultSetExtractor implementation provided by the Application Code. - * - * @param psc The implementation to create the PreparedStatement - * @param rse Implementation for extracting from the ResultSet - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * must not be {@literal null}. + * @param rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor} + * @throws DataAccessException if there is any problem */ T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with - * ResultSetExtractor implementation provided by the Application Code. + * Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}. * - * @param psc The implementation to create the PreparedStatement - * @param rse Implementation for extracting from the ResultSet - * @param options The Query Options Object - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@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 rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor}. + * @throws DataAccessException if there is any problem */ - T query(PreparedStatementCreator psc, ResultSetExtractor rse, QueryOptions options) throws DataAccessException; + T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with - * RowCallbackHandler and nothing is returned. - * - * @param psc The implementation to create the PreparedStatement - * @param rch The implementation to process Results - * @throws DataAccessException - */ - void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with - * RowCallbackHandler and nothing is returned. - * - * @param psc The implementation to create the PreparedStatement - * @param rch The implementation to process Results - * @param options The Query Options Object - * @throws DataAccessException - */ - void query(PreparedStatementCreator psc, RowCallbackHandler rch, QueryOptions options) throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with RowMapper - * implementation provided and a List is returned with elements of Type for each Row returned. - * - * @param psc The implementation to create the PreparedStatement - * @param rowMapper The implementation for mapping each Row returned. - * @return List of Type mapped from each Row in the Results - * @throws DataAccessException - */ - List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. This can only be used for CQL - * Statements that do not have data binding. The results of the PreparedStatement are processed with RowMapper - * implementation provided and a List is returned with elements of Type for each Row returned. - * - * @param psc The implementation to create the PreparedStatement - * @param rowMapper The implementation for mapping each Row returned. - * @param options The Query Options Object - * @return List of Type mapped from each Row in the Results - * @throws DataAccessException - */ - List query(PreparedStatementCreator psc, RowMapper rowMapper, QueryOptions options) - throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * ResultSetExtractor implementation provided by the Application Code. - * - * @param psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rse Implementation for extracting from the ResultSet - * @param options The Query Options Object - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException - */ - T query(PreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor rse, QueryOptions options) - throws DataAccessException; - - /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * ResultSetExtractor implementation provided by the Application Code. - * - * @param psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rse Implementation for extracting from the ResultSet - * @return Type which is the output of the 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} with a {@link ResultSetExtractor}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 rse object that will extract results, must not be {@literal null}. + * @return an arbitrary result object, as returned by the {@link ResultSetExtractor}. + * @throws DataAccessException if there is any problem */ T query(PreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * RowCallbackHandler and nothing is returned. + * 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 ResultSetExtractor}. * - * @param psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rch The implementation to process Results - * @param options The Query Options Object - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rse 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 ResultSetExtractor} + * @throws DataAccessException if there is any problem executing the query. */ - void query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch, QueryOptions options) - throws DataAccessException; + T query(String cql, ResultSetExtractor rse, Object... args) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * RowCallbackHandler and nothing is returned. + * Query using a prepared statement, reading the {@link ResultSet} on a per-row basis with a + * {@link RowCallbackHandler}. * - * @param psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rch The implementation to process Results - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * must not be {@literal null}. + * @param rch 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. + */ + void query(PreparedStatementCreator psc, RowCallbackHandler rch) 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 empty or {@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 rch 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. + */ + void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) 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 psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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 rch 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. */ void query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * RowMapper implementation provided and a List is returned with elements of Type for each Row returned. + * 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 psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rowMapper The implementation for mapping each Row returned. - * @param options The Query Options Object - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @param rch 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. */ - List query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper rowMapper, - QueryOptions options) throws DataAccessException; + void query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException; /** - * Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the - * PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with - * RowMapper implementation provided and a List is returned with elements of Type for each Row returned. - * - * @param psc The implementation to create the PreparedStatement - * @param psb The implementation to bind variables to values - * @param rowMapper The implementation for mapping each Row returned. - * @return Type which is the output of the ResultSetExtractor - * @throws DataAccessException + * Query using a prepared statement, mapping each row to a Java object via a {@link RowMapper}. + * + * @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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. + */ + List query(PreparedStatementCreator psc, RowMapper rowMapper) 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 empty or {@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. + */ + List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) 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 psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session}, + * 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. */ List query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper rowMapper) 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 empty or {@literal null}. + * @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. + */ + List query(String cql, RowMapper rowMapper, 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 a + * single result row to a Java object via a {@link RowMapper}. + * + * @param cql static CQL to execute, must not be empty or {@literal null}. + * @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 queryForObject(String cql, RowMapper rowMapper, Object... args) 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. + *

+ * 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 empty or {@literal null}. + * @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 {@link Mono#empty()} 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 queryForObject(String cql, Class requiredType, Object... args) 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. + *

+ * 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 empty or {@literal null}. + * @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 + */ + Map queryForMap(String cql, Object... args) 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}. + *

+ * 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 empty or {@literal null}. + * @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 + */ + List queryForList(String cql, Class elementType, Object... args) 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}. + *

+ * 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 empty or {@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} that contains a {@link Map} per row + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForList(String) + */ + List> queryForList(String cql, Object... args) 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. + *

+ * The results will be mapped to an {@link ResultSet}. + * + * @param cql static CQL to execute, must not be empty or {@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 ResultSet} representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String) + */ + ResultSet queryForResultSet(String cql, Object... args) throws DataAccessException; + + /** + * Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting + * Rows. + *

+ * The results will be mapped to {@link Row}s. + * + * @param cql static CQL to execute, must not be empty or {@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 Row} representation. + * @throws DataAccessException if there is any problem executing the query. + * @see #queryForResultSet(String) + */ + Iterator queryForRows(String cql, Object... args) throws DataAccessException; + + /** + * Issue a single CQL execute operation (such as an insert, update or delete statement) using a + * {@link PreparedStatementCreator} to provide CQL and any required parameters. + * + * @param psc 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 issuing the execution. + */ + // TODO: Interferes with execute(session callback lambda) + boolean execute(PreparedStatementCreator psc) throws DataAccessException; + + /** + * Issue an statement using a {@link PreparedStatementBinder} to set bind parameters, with given CQL. Simpler than + * using a {@link PreparedStatementCreator} 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 empty or {@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. + * @return boolean value whether the statement was applied. + * @throws DataAccessException if there is any problem issuing the execution. + */ + boolean execute(String cql, PreparedStatementBinder psb) 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 empty or {@literal null}. + * @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 issuing the execution. + */ + boolean execute(String cql, Object... args) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with cluster metadata + // ------------------------------------------------------------------------- + /** * Describe the current Ring. This uses the provided {@link RingMemberHostMapper} to provide the basics of the * Cassandra Ring topology. - * + * * @return The list of ring tokens that are active in the cluster */ List describeRing() throws DataAccessException; @@ -1256,180 +776,10 @@ public interface CqlOperations { /** * Describe the current Ring. Application code must provide its own {@link HostMapper} implementation to process the * lists of hosts returned by the Cassandra Cluster Metadata. - * + * * @param hostMapper The implementation to use for host mapping. * @return Collection generated by the provided HostMapper. * @throws DataAccessException */ Collection describeRing(HostMapper hostMapper) throws DataAccessException; - - /** - * Get the current Session used for operations in the implementing class. - * - * @return The DataStax Driver Session Object - */ - Session getSession(); - - /** - * This is an operation designed for high performance writes. The cql is used to create a PreparedStatement once, then - * all row values are bound to the single PreparedStatement and executed against the Session. - *

- * This is used internally by the other ingest() methods, but can be used if you want to write your own RowIterator. - * The Object[] length returned by the next() implementation must match the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rowIterator Implementation to provide the Object[] to be bound to the CQL. - * @param options The Query Options Object - */ - void ingest(String cql, RowIterator rowIterator, WriteOptions options); - - /** - * This is an operation designed for high performance writes. The CQL is used to create a PreparedStatement once, then - * all row values are bound to the single PreparedStatement and executed against the Session. - *

- * This is used internally by the other ingest() methods, but can be used if you want to write your own RowIterator. - * The Object[] length returned by the next() implementation must match the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rowIterator Implementation to provide the Object[] to be bound to the CQL. - */ - void ingest(String cql, RowIterator rowIterator); - - /** - * This is an operation designed for high performance writes. The CQL is used to create a PreparedStatement once, then - * all row values are bound to the single PreparedStatement and executed against the Session. - *

- * The List length must match the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rows List of List with data to bind to the CQL. - * @param options The Query Options Object - */ - void ingest(String cql, List> rows, WriteOptions options); - - /** - * This is an operation designed for high performance writes. The CQL is used to create a {@link PreparedStatement} - * once, then all row values are bound to that {@link PreparedStatement} and executed against the {@link Session}. - *

- * The lengths of the nested {@link List}s must not be less than the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rows The data to bind to the CQL statement - */ - void ingest(String cql, List> rows); - - /** - * This is an operation designed for high performance writes. The CQL is used to create a {@link PreparedStatement} - * once, then all row values are bound to that {@link PreparedStatement} and executed against the {@link Session}. - *

- * The lengths of the nested object arrays must not be less than the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rows The data to bind to the CQL statement - */ - void ingest(String cql, Object[][] rows); - - /** - * This is an operation designed for high performance writes. The CQL is used to create a {@link PreparedStatement} - * once, then all row values are bound to that {@link PreparedStatement} and executed against the {@link Session}. - *

- * The lengths of the nested object arrays must not be less than the number of bind variables in the CQL. - *

- * - * @param cql The CQL - * @param rows The data to bind to the CQL statement - * @param options The Query Options Object - */ - void ingest(String cql, Object[][] rows, WriteOptions options); - - /** - * Delete all rows in the table - * - * @param tableName - */ - void truncate(CqlIdentifier tableName); - - /** - * Delete all rows in the table - * - * @param tableName - */ - void truncate(String tableName); - - /** - * Counts all rows for given table - * - * @param tableName - * @return - */ - long count(CqlIdentifier tableName); - - /** - * Counts all rows for given table - * - * @param tableName - * @return - */ - long count(String tableName); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(DropTableSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(CreateTableSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(AlterTableSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(DropKeyspaceSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(CreateKeyspaceSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(AlterKeyspaceSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(DropIndexSpecification specification); - - /** - * Convenience method to convert the given specification to CQL and execute it. - * - * @param specification The specification to execute; must not be null. - */ - ResultSet execute(CreateIndexSpecification specification); } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java index ad10d72b1..b0cfa902a 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java @@ -1,11 +1,11 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,71 +19,57 @@ import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.stream.StreamSupport; -import org.springframework.cassandra.core.cql.CqlIdentifier; -import org.springframework.cassandra.core.cql.generator.AlterKeyspaceCqlGenerator; -import org.springframework.cassandra.core.cql.generator.AlterTableCqlGenerator; -import org.springframework.cassandra.core.cql.generator.CreateIndexCqlGenerator; -import org.springframework.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator; -import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; -import org.springframework.cassandra.core.cql.generator.DropIndexCqlGenerator; -import org.springframework.cassandra.core.cql.generator.DropKeyspaceCqlGenerator; -import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; -import org.springframework.cassandra.core.keyspace.AlterKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.AlterTableSpecification; -import org.springframework.cassandra.core.keyspace.CreateIndexSpecification; -import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.CreateTableSpecification; -import org.springframework.cassandra.core.keyspace.DropIndexSpecification; -import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.DropTableSpecification; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.Update; import org.springframework.cassandra.support.CassandraAccessor; import org.springframework.dao.DataAccessException; -import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.QueryTimeoutException; -import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.dao.support.DataAccessUtils; import org.springframework.util.Assert; import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.ColumnDefinitions; -import com.datastax.driver.core.ColumnDefinitions.Definition; +import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.Host; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; import com.datastax.driver.core.Statement; import com.datastax.driver.core.exceptions.DriverException; -import com.datastax.driver.core.querybuilder.Batch; -import com.datastax.driver.core.querybuilder.Delete; -import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.policies.RetryPolicy; import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.querybuilder.Truncate; -import com.datastax.driver.core.querybuilder.Update; /** - * This is the central class in the Cassandra core package. {@link CqlTemplate} simplifies the use of Cassandra - * and helps to avoid common errors. The template executes the core Cassandra workflow, leaving application code to - * provide CQL and result handling. The template executes CQL queries, provides different ways to extract and map - * results, and provides Exception translation to the generic, more informative exception hierarchy defined in the - * org.springframework.dao package. + * This is the central class in the CQL core package. 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. *

- * For working with POJOs, use the CassandraTemplate. - *

+ * 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 ResultSetExtractor} interface extracts values from a {@link ResultSet}. See + * also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces. + *

+ * Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get + * prepared in an application context and given to services as bean reference. Note: The {@link Session} 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. + *

+ * Because this class is parameterizable by the callback interfaces and the + * {@link org.springframework.dao.support.PersistenceExceptionTranslator} interface, there should be no need to subclass + * it. + *

+ * All CQL operations performed by this class are logged at debug level, using + * "org.springframework.cassandra.core.CqlTemplate" as log category. + *

+ * NOTE: An instance of this class is thread-safe once configured. * * @author David Webb * @author Matthew Adams @@ -91,1675 +77,826 @@ import com.datastax.driver.core.querybuilder.Update; * @author Antoine Toulme * @author John Blum * @author Mark Paluch - * @see org.springframework.cassandra.core.CqlOperations - * @see org.springframework.cassandra.support.CassandraAccessor + * @see PreparedStatementCreator + * @see PreparedStatementBinder + * @see PreparedStatementCallback + * @see ResultSetExtractor + * @see RowCallbackHandler + * @see RowMapper + * @see org.springframework.dao.support.PersistenceExceptionTranslator */ public class CqlTemplate extends CassandraAccessor implements CqlOperations { - protected static final Executor RUN_RUNNABLE_EXECUTOR = new Executor() { - - @Override - @SuppressWarnings("all") - public void execute(Runnable command) { - command.run(); - } - }; - - protected static final ResultSetExtractor RESULT_SET_RETURNING_EXTRACTOR = new ResultSetExtractor() { - - @Override - public ResultSet extractData(ResultSet resultSet) { - return resultSet; - } - }; - - protected String logCql(String cql) { - return logCql("executing CQL [{}]", cql); - } - - protected String logCql(String message, String cql) { - logDebug(message, cql); - return cql; - } - - protected T logStatement(T statement) { - logDebug("executing statement [{}]", statement); - return statement; - } - /** - * Add common {@link QueryOptions} to Cassandra {@link PreparedStatement}s. - * - * @param preparedStatement the Cassandra {@link PreparedStatement} to execute. - * @param queryOptions query options (e.g. consistency level) to add to the Cassandra {@link PreparedStatement}. + * Placeholder for default values. */ - public static PreparedStatement addPreparedStatementOptions(PreparedStatement preparedStatement, - QueryOptions queryOptions) { - - if (queryOptions != null) { - if (queryOptions.getDriverConsistencyLevel() != null) { - preparedStatement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel()); - } else if (queryOptions.getConsistencyLevel() != null) { - preparedStatement.setConsistencyLevel( - ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel())); - } - - if (queryOptions.getDriverRetryPolicy() != null) { - preparedStatement.setRetryPolicy(queryOptions.getDriverRetryPolicy()); - } else if (queryOptions.getRetryPolicy() != null) { - preparedStatement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy())); - } - } - - return preparedStatement; - } + private final static Statement DEFAULTS = QueryBuilder.select().from("DEFAULT"); /** - * Add common {@link QueryOptions} to all types of queries. - * - * @param statement CQL {@link Statement} to execute. - * @param queryOptions query options (e.g. consistency level) to add to the CQL statement. - * @return the given {@link Statement}. + * If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on + * statements used for query processing. */ - public static T addQueryOptions(T statement, QueryOptions queryOptions) { - - if (queryOptions != null) { - if (queryOptions.getDriverConsistencyLevel() != null) { - statement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel()); - } else if (queryOptions.getConsistencyLevel() != null) { - statement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel())); - } - - if (queryOptions.getDriverRetryPolicy() != null) { - statement.setRetryPolicy(queryOptions.getDriverRetryPolicy()); - } else if (queryOptions.getRetryPolicy() != null) { - statement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy())); - } - - if (queryOptions.getFetchSize() != null) { - statement.setFetchSize(queryOptions.getFetchSize()); - } - - if (queryOptions.getReadTimeout() != null) { - statement.setReadTimeoutMillis(queryOptions.getReadTimeout().intValue()); - } - - if (queryOptions.getTracing() != null) { - if (queryOptions.getTracing()) { - statement.enableTracing(); - } else { - statement.disableTracing(); - } - } - } - - return statement; - } + private int fetchSize = -1; /** - * Add common {@link WriteOptions} options to {@link Insert} CQL statements. - * - * @param insert {@link Insert} CQL statement to execute. - * @param writeOptions write options (e.g. consistency level) to add to the CQL statement. - * @return the given {@link Insert}. + * If this variable is set to a value, it will be used for setting the {@code retryPolicy} property on statements used + * for query processing. */ - public static Insert addWriteOptions(Insert insert, WriteOptions writeOptions) { - - if (writeOptions != null) { - - addQueryOptions(insert, writeOptions); - - if (writeOptions.getTtl() != null) { - insert.using(QueryBuilder.ttl(writeOptions.getTtl())); - } - } - - return insert; - } + private RetryPolicy retryPolicy; /** - * Add common {@link WriteOptions} options to {@link Update} CQL statements. - * - * @param update {@link Update} CQL statement to execute. - * @param writeOptions write options (e.g. consistency level) to add to the CQL statement. - * @return the given {@link Update}. + * If this variable is set to a value, it will be used for setting the {@code consistencyLevel} property on statements + * used for query processing. */ - public static Update addWriteOptions(Update update, WriteOptions writeOptions) { - - if (writeOptions != null) { - - addQueryOptions(update, writeOptions); - - if (writeOptions.getTtl() != null) { - update.using(QueryBuilder.ttl(writeOptions.getTtl())); - } - } - - return update; - } + private com.datastax.driver.core.ConsistencyLevel consistencyLevel; /** - * Constructs an uninitialized instance of {@link CqlTemplate}. A Cassandra {@link Session} is required before use. + * Construct a new {@link CqlTemplate}. Note: The {@link Session} has to be set before using the instance. * - * @see #CqlTemplate(Session) + * @see #setSession(Session) */ public CqlTemplate() {} /** - * Constructs an instance of {@link CqlTemplate} initialized with the given {@link Session}. + * Construct a new {@link CqlTemplate}, given a {@link Session}. * - * @param session Cassandra {@link Session} used by this template to perform CQL operations. Must not be - * {@literal null}. - * @see com.datastax.driver.core.Session - * @see #setSession(Session) + * @param session the active Cassandra {@link Session}. */ - // TODO: should not call setSession(..) in constructor for initialization safety; - // only really matters if CqlTemplate makes Thread-safety guarantees, which currently it does not. public CqlTemplate(Session session) { + + Assert.notNull(session, "Session must not be null"); + setSession(session); } /** - * Executes the given command in a Cassandra {@link Session}. + * Set the fetch size for this {@link CqlTemplate}. This is important for processing large result sets: Setting this + * higher than the default value will increase processing speed at the cost of memory consumption; setting this lower + * can avoid transferring row data that will never be read by the application. Default is -1, indicating to use the + * CQL driver's default configuration (i.e. to not pass a specific fetch size setting on to the driver). * - * @param Class type of the callback return value. - * @param callback {@link SessionCallback} to execute in the context of a Cassandra {@link Session}. - * @return the result of the callback. + * @see Statement#setFetchSize(int) */ - protected T doExecute(SessionCallback callback) { + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } - Assert.notNull(callback, "SessionCallback must not be null"); + /** + * @return the fetch size specified for this {@link CqlTemplate}. + */ + public int getFetchSize() { + return this.fetchSize; + } + + /** + * Set the retry policy for this {@link CqlTemplate}. This is important for defining behavior when a request + * fails. + * + * @see Statement#setRetryPolicy(RetryPolicy) + * @see RetryPolicy + */ + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + /** + * @return the {@link RetryPolicy} specified for this {@link CqlTemplate}. + */ + public RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + /** + * Set the consistency level for this {@link CqlTemplate}. Consistency level defines the number of nodes + * involved into query processing. Relaxed consistency level settings use fewer nodes but eventual consistency is more + * likely to occur while a higher consistency level involves more nodes to obtain results with a higher consistency + * guarantee. + * + * @see Statement#setConsistencyLevel(ConsistencyLevel) + * @see RetryPolicy + */ + public void setConsistencyLevel(ConsistencyLevel consistencyLevel) { + this.consistencyLevel = consistencyLevel; + } + + /** + * @return the {@link ConsistencyLevel} specified for this {@link CqlTemplate}. + */ + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + // ------------------------------------------------------------------------- + // Methods dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(org.springframework.cassandra.core.SessionCallback) + */ + @Override + public T execute(SessionCallback action) throws DataAccessException { + + Assert.notNull(action, "Callback object must not be null"); try { - return callback.doInSession(getSession()); - } catch (Throwable t) { - throw translateExceptionIfPossible(t); + return action.doInSession(getSession()); + } catch (DriverException e) { + throw translateException("SessionCallback", getCql(action), e); } } - protected ResultSet doExecuteQueryReturnResultSet(final String query) { - return doExecute(new SessionCallback() { - @Override - public ResultSet doInSession(Session session) throws DataAccessException { - return session.execute(logCql(query)); - } - }); - } + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- - protected ResultSet doExecuteQueryReturnResultSet(final Select select) { - return doExecute(new SessionCallback() { - @Override public ResultSet doInSession(Session session) throws DataAccessException { - return session.execute(logStatement(select)); - } - }); - } - - @Override - public T execute(SessionCallback sessionCallback) { - return doExecute(sessionCallback); - } - - @Override - public void execute(String cql) { - execute(cql, (QueryOptions) null); - } - - @Override - public void execute(String cql, QueryOptions options) { - doExecute(cql, options); - } - - @Override - public void execute(Statement statement) { - doExecute(statement); - } - - @Override - public ResultSetFuture queryAsynchronously(final String cql) { - - return execute(new SessionCallback() { - - @Override - public ResultSetFuture doInSession(Session session) { - return session.executeAsync(logCql("async execute CQL [{}]", cql)); - } - }); - } - - @Override - public T queryAsynchronously(String cql, ResultSetExtractor resultSetExtractor, Long timeout, - TimeUnit timeUnit) { - - return queryAsynchronously(cql, resultSetExtractor, timeout, timeUnit, null); - } - - @Override - public T queryAsynchronously(final String cql, final ResultSetExtractor resultSetExtractor, final Long timeout, - final TimeUnit timeUnit, final QueryOptions options) { - - return resultSetExtractor.extractData(execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - - Statement statement = addQueryOptions(new SimpleStatement(logCql(cql)), options); - - ResultSetFuture resultSetFuture = session.executeAsync(statement); - - try { - return resultSetFuture.get(timeout, timeUnit); - } catch (TimeoutException e) { - throw new QueryTimeoutException(String.format( - "timeout occurred in [%1$d %2$s] while asynchronously executing CQL [%3$s]", timeout, timeUnit, cql), e); - } catch (InterruptedException e) { - throw translateExceptionIfPossible(e); - } catch (ExecutionException e) { - - if (e.getCause() instanceof Exception) { - throw translateExceptionIfPossible(e.getCause()); - } - throw new CassandraUncategorizedDataAccessException("Unknown Throwable", e.getCause()); - } - } - })); - } - - @Override - public ResultSetFuture queryAsynchronously(final String cql, final QueryOptions queryOptions) { - - return execute(new SessionCallback() { - - @Override - public ResultSetFuture doInSession(Session session) { - return session.executeAsync(addQueryOptions(new SimpleStatement(logCql(cql)), queryOptions)); - } - }); - } - - @Override - public Cancellable queryAsynchronously(String cql, Runnable listener) { - return queryAsynchronously(cql, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener) { - return queryAsynchronously(cql, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(String cql, Runnable listener, QueryOptions queryOptions) { - return queryAsynchronously(cql, listener, queryOptions, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener, QueryOptions queryOptions) { - return queryAsynchronously(cql, listener, queryOptions, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(String cql, Runnable listener, Executor executor) { - return queryAsynchronously(cql, listener, null, executor); - } - - @Override - public Cancellable queryAsynchronously(String cql, AsynchronousQueryListener listener, Executor executor) { - return queryAsynchronously(cql, listener, null, executor); - } - - @Override - public Cancellable queryAsynchronously(final String cql, final Runnable listener, final QueryOptions queryOptions, - final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - Statement statement = addQueryOptions(new SimpleStatement(logCql("async execute CQL [{}]", cql)), queryOptions); - - ResultSetFuture resultSetFuture = session.executeAsync(statement); - resultSetFuture.addListener(listener, executor); - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public Cancellable queryAsynchronously(final String cql, final AsynchronousQueryListener listener, - final QueryOptions queryOptions, final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - Statement statement = addQueryOptions(new SimpleStatement(logCql("async execute CQL [{}]", cql)), - queryOptions); - - final ResultSetFuture resultSetFuture = session.executeAsync(statement); - - Runnable runnable = new Runnable() { - @Override - public void run() { - listener.onQueryComplete(resultSetFuture); - } - }; - - resultSetFuture.addListener(runnable, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @SuppressWarnings("unused") - public T queryAsynchronously(String cql, ResultSetFutureExtractor resultSetFutureExtractor) { - return queryAsynchronously(cql, resultSetFutureExtractor, null); - } - - public T queryAsynchronously(final String cql, ResultSetFutureExtractor resultSetFutureExtractor, - final QueryOptions queryOptions) { - - return resultSetFutureExtractor.extractData(execute(new SessionCallback() { - - @Override - public ResultSetFuture doInSession(Session session) { - return session - .executeAsync( - addQueryOptions(new SimpleStatement(logCql("async execute CQL [{}]", cql)), queryOptions)); - } - })); - } - - @Override - public T query(String cql, ResultSetExtractor resultSetExtractor) { - return query(cql, resultSetExtractor, null); - } - - @Override - public T query(String cql, ResultSetExtractor resultSetExtractor, QueryOptions queryOptions) { - - Assert.notNull(cql, "CQL must not be null"); - - return resultSetExtractor.extractData(doExecute(cql, queryOptions)); - } - - @Override - public void query(String cql, RowCallbackHandler rowCallbackHandler) { - query(cql, rowCallbackHandler, null); - } - - @Override - public void query(String cql, RowCallbackHandler rowCallbackHandler, QueryOptions queryOptions) { - process(doExecute(cql, queryOptions), rowCallbackHandler); - } - - @Override - public List query(String cql, RowMapper rowMapper, QueryOptions queryOptions) { - return process(doExecute(cql, queryOptions), rowMapper); - } - - @Override - public ResultSet query(String cql) { - return query(cql, (QueryOptions) null); - } - - @Override - public ResultSet query(String cql, QueryOptions queryOptions) { - return query(cql, RESULT_SET_RETURNING_EXTRACTOR, queryOptions); - } - - @Override - public List query(String cql, RowMapper rowMapper) { - return query(cql, rowMapper, null); - } - - @Override - public List> queryForListOfMap(String cql) { - return processListOfMap(doExecute(cql, null)); - } - - @Override - public List queryForList(String cql, Class elementType) { - return processList(doExecute(cql, null), elementType); - } - - @Override - public Map queryForMap(String cql) { - return processMap(doExecute(cql, null)); - } - - @Override - public T queryForObject(String cql, Class requiredType) { - return processOne(doExecute(cql, null), requiredType); - } - - @Override - public T queryForObject(String cql, RowMapper rowMapper) { - return processOne(doExecute(cql, null), rowMapper); - } - - @SuppressWarnings("unused") - protected ResultSet doExecute(String cql) { - return doExecute(cql, null); - } - - protected ResultSet doExecute(String cql, QueryOptions queryOptions) { - return doExecute(addQueryOptions(new SimpleStatement(logCql(cql)), queryOptions)); - } - - /** - * Execute a command at the Session Level with optional options - * - * @param statement The query to execute. + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(java.lang.String) */ - protected ResultSet doExecute(final Statement statement) { + @Override + public boolean execute(String cql) throws DataAccessException { - return doExecute(new SessionCallback() { + Assert.hasText(cql, "CQL must not be empty"); - @Override - public ResultSet doInSession(Session session) { + return queryForResultSet(cql).wasApplied(); + } - logDebug("execute [{}]", statement); - return session.execute(statement); + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(String cql, ResultSetExtractor rse) throws DataAccessException { + + Assert.hasText(cql, "CQL must not be empty"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Executing CQL Statement [{}]", cql); } - }); - } - protected ResultSetFuture doExecuteAsync(final Statement statement) { + SimpleStatement simpleStatement = new SimpleStatement(cql); - return doExecute(new SessionCallback() { + applyStatementSettings(simpleStatement); - @Override - public ResultSetFuture doInSession(Session session) { - - logDebug("async execute [{}]", statement); - return session.executeAsync(statement); - } - }); - } - - protected Cancellable doExecuteAsync(final Statement statement, final AsynchronousQueryListener listener) { - return doExecuteAsync(statement, listener, null); - } - - protected Cancellable doExecuteAsync(final Statement statement, final AsynchronousQueryListener listener, - final QueryOptions queryOptions) { - - return doExecute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - logDebug("async execute [{}]", statement); - - final ResultSetFuture resultSetFuture = session.executeAsync(addQueryOptions(statement, queryOptions)); - - if (listener != null) { - resultSetFuture.addListener(new Runnable() { - @Override - public void run() { - listener.onQueryComplete(resultSetFuture); - } - }, RUN_RUNNABLE_EXECUTOR); - } - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - protected Object firstColumnToObject(Row row) { - - Iterator columnDefinitions = row.getColumnDefinitions().iterator(); - return (columnDefinitions.hasNext() ? columnToObject(row, columnDefinitions.next()) : null); - } - - /* (non-Javadoc) */ - T columnToObject(Row row, Definition columnDefinition) { - return (T) row.getObject(columnDefinition.getName()); - } - - protected Map toMap(Row row) { - - Map map = null; - - if (row != null) { - ColumnDefinitions columns = row.getColumnDefinitions(); - map = new HashMap(columns.size()); - - for (Definition columnDefinition : columns.asList()) { - map.put(columnDefinition.getName(), columnToObject(row, columnDefinition)); - } + return rse.extractData(getSession().execute(simpleStatement)); + } catch (DriverException e) { + throw translateException("Query", cql, e); } - - return map; } - @Override - public List describeRing() { - return new ArrayList(describeRing(new RingMemberHostMapper())); - } - - /** - * Requests the set of hosts in the Cassandra cluster from the current {@link Session}. + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler) */ - protected Set getHosts() { - - return doExecute(new SessionCallback>() { - - @Override - public Set doInSession(Session session) { - return session.getCluster().getMetadata().getAllHosts(); - } - }); + @Override + public void query(String cql, RowCallbackHandler rch) throws DataAccessException { + query(cql, new RowCallbackHandlerResultSetExtractor(rch)); } + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ @Override - public Collection describeRing(HostMapper hostMapper) { + public List query(String cql, RowMapper rowMapper) throws DataAccessException { + return query(cql, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper) + */ + @Override + public T queryForObject(String cql, RowMapper rowMapper) throws DataAccessException { + + List results = query(cql, rowMapper); + return DataAccessUtils.requiredSingleResult(results); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(java.lang.String, java.lang.Class) + */ + @Override + public T queryForObject(String cql, Class requiredType) throws DataAccessException { + return queryForObject(cql, getSingleColumnRowMapper(requiredType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForMap(java.lang.String) + */ + @Override + public Map queryForMap(String cql) throws DataAccessException { + return queryForObject(cql, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(java.lang.String, java.lang.Class) + */ + @Override + public List queryForList(String cql, Class elementType) throws DataAccessException { + return query(cql, getSingleColumnRowMapper(elementType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(java.lang.String) + */ + @Override + public List> queryForList(String cql) throws DataAccessException { + return query(cql, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForResultSet(java.lang.String) + */ + @Override + public ResultSet queryForResultSet(String cql) throws DataAccessException { + return query(cql, rs -> rs); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForRows(java.lang.String) + */ + @Override + public Iterator queryForRows(String cql) throws DataAccessException { + return queryForResultSet(cql).iterator(); + } + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(com.datastax.driver.core.Statement) + */ + @Override + public boolean execute(Statement statement) throws DataAccessException { + + Assert.notNull(statement, "CQL Statement must not be null"); + + return queryForResultSet(statement).wasApplied(); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(Statement statement, ResultSetExtractor rse) throws DataAccessException { + + Assert.notNull(statement, "CQL Statement must not be null"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Executing CQL Statement [{}]", statement); + } + + applyStatementSettings(statement); + + return rse.extractData(getSession().execute(statement)); + } catch (DriverException e) { + throw translateException("Query", statement.toString(), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(Statement statement, RowCallbackHandler rch) throws DataAccessException { + query(statement, new RowCallbackHandlerResultSetExtractor(rch)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(Statement statement, RowMapper rowMapper) throws DataAccessException { + return query(statement, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) + */ + @Override + public T queryForObject(Statement statement, RowMapper rowMapper) throws DataAccessException { + + List results = query(statement, rowMapper); + return DataAccessUtils.requiredSingleResult(results); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public T queryForObject(Statement statement, Class requiredType) throws DataAccessException { + return queryForObject(statement, getSingleColumnRowMapper(requiredType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForMap(com.datastax.driver.core.Statement) + */ + @Override + public Map queryForMap(Statement statement) throws DataAccessException { + return queryForObject(statement, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public List queryForList(Statement statement, Class elementType) throws DataAccessException { + return query(statement, getSingleColumnRowMapper(elementType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(com.datastax.driver.core.Statement) + */ + @Override + public List> queryForList(Statement statement) throws DataAccessException { + return query(statement, getColumnMapRowMapper()); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForResultSet(com.datastax.driver.core.Statement) + */ + @Override + public ResultSet queryForResultSet(Statement statement) throws DataAccessException { + return query(statement, rs -> rs); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForRows(com.datastax.driver.core.Statement) + */ + @Override + public Iterator queryForRows(Statement statement) throws DataAccessException { + return queryForResultSet(statement).iterator(); + } + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { + + Assert.notNull(psc, "PreparedStatementCreator must not be null"); + Assert.notNull(action, "PreparedStatementCallback object must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Preparing statement [{}] using {}", getCql(psc), psc); + } + + PreparedStatement preparedStatement = psc.createPreparedStatement(getSession()); + applyStatementSettings(preparedStatement); + + return action.doInPreparedStatement(preparedStatement); + + } catch (DriverException e) { + throw translateException("PreparedStatementCallback", getCql(psc), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(PreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor rse) + throws DataAccessException { + + Assert.notNull(psc, "PreparedStatementCreator must not be null"); + Assert.notNull(rse, "ResultSetExtractor object must not be null"); + + try { + + if (logger.isDebugEnabled()) { + logger.debug("Preparing statement [{}] using {}", getCql(psc), psc); + } + + Session session = getSession(); + PreparedStatement ps = psc.createPreparedStatement(session); + + if (logger.isDebugEnabled()) { + logger.debug("Executing prepared statement [{}]", ps); + } + + BoundStatement boundStatement = psb != null ? psb.bindValues(ps) : ps.bind(); + + applyStatementSettings(boundStatement); + return rse.extractData(session.execute(boundStatement)); + + } catch (DriverException e) { + throw translateException("Query", getCql(psc), e); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementCallback) + */ + @Override + public T execute(String cql, PreparedStatementCallback action) throws DataAccessException { + return execute(new SimplePreparedStatementCreator(cql), action); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException { + return query(psc, null, rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor) + */ + @Override + public T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException { + return query(new SimplePreparedStatementCreator(cql), psb, rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor, java.lang.Object[]) + */ + @Override + public T query(String cql, ResultSetExtractor rse, Object... args) throws DataAccessException { + return query(cql, newArgPreparedStatementBinder(args), rse); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException { + query(psc, new RowCallbackHandlerResultSetExtractor(rch)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException { + query(cql, psb, new RowCallbackHandlerResultSetExtractor(rch)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler) + */ + @Override + public void query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch) + throws DataAccessException { + query(psc, psb, new RowCallbackHandlerResultSetExtractor(rch)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler, java.lang.Object[]) + */ + @Override + public void query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException { + query(cql, newArgPreparedStatementBinder(args), new RowCallbackHandlerResultSetExtractor(rch)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { + return query(psc, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) throws DataAccessException { + return query(cql, psb, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) + */ + @Override + public List query(PreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper rowMapper) + throws DataAccessException { + return query(psc, psb, new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#query(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) + */ + @Override + public List query(String cql, RowMapper rowMapper, Object... args) throws DataAccessException { + return query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) + */ + @Override + public T queryForObject(String cql, RowMapper rowMapper, Object... args) throws DataAccessException { + + List results = query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper, 1)); + return DataAccessUtils.requiredSingleResult(results); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForObject(java.lang.String, java.lang.Class, java.lang.Object[]) + */ + @Override + public T queryForObject(String cql, Class requiredType, Object... args) throws DataAccessException { + return queryForObject(cql, getSingleColumnRowMapper(requiredType), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForMap(java.lang.String, java.lang.Object[]) + */ + @Override + public Map queryForMap(String cql, Object... args) throws DataAccessException { + return queryForObject(cql, getColumnMapRowMapper(), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(java.lang.String, java.lang.Class, java.lang.Object[]) + */ + @Override + public List queryForList(String cql, Class elementType, Object... args) throws DataAccessException { + return query(cql, getSingleColumnRowMapper(elementType), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForList(java.lang.String, java.lang.Object[]) + */ + @Override + public List> queryForList(String cql, Object... args) throws DataAccessException { + return query(cql, getColumnMapRowMapper(), args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForResultSet(java.lang.String, java.lang.Object[]) + */ + @Override + public ResultSet queryForResultSet(String cql, Object... args) throws DataAccessException { + return query(cql, rs -> rs, args); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#queryForRows(java.lang.String, java.lang.Object[]) + */ + @Override + public Iterator queryForRows(String cql, Object... args) throws DataAccessException { + return queryForResultSet(cql, args).iterator(); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(org.springframework.cassandra.core.PreparedStatementCreator) + */ + @Override + public boolean execute(PreparedStatementCreator psc) throws DataAccessException { + return query(psc, ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder) + */ + @Override + public boolean execute(String cql, PreparedStatementBinder psb) throws DataAccessException { + return query(new SimplePreparedStatementCreator(cql), psb, ResultSet::wasApplied); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#execute(java.lang.String, java.lang.Object[]) + */ + @Override + public boolean execute(String cql, Object... args) throws DataAccessException { + return execute(cql, newArgPreparedStatementBinder(args)); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#describeRing() + */ + @Override + public List describeRing() throws DataAccessException { + return (List) describeRing(RingMemberHostMapper.INSTANCE); + } + + /* + * (non-Javadoc) + * @see org.springframework.cassandra.core.CqlOperationsNG#describeRing(org.springframework.cassandra.core.HostMapper) + */ + @Override + public Collection describeRing(HostMapper hostMapper) throws DataAccessException { + + Assert.notNull(hostMapper, "HostMapper must not be null"); + return hostMapper.mapHosts(getHosts()); } - @Override - public ResultSetFuture executeAsynchronously(String cql) { - return executeAsynchronously(cql, (QueryOptions) null); + private Set getHosts() { + return getSession().getCluster().getMetadata().getAllHosts(); } - @Override - public ResultSetFuture executeAsynchronously(String cql, QueryOptions queryOptions) { - return doExecuteAsync(addQueryOptions(new SimpleStatement(logCql(cql)), queryOptions)); - } - - @Override - public Cancellable executeAsynchronously(String cql, Runnable listener) { - return executeAsynchronously(cql, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable executeAsynchronously(final String cql, final Runnable listener, final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - Statement statement = new SimpleStatement(logCql("async execute CQL [{}]", cql)); - - ResultSetFuture resultSetFuture = session.executeAsync(statement); - resultSetFuture.addListener(listener, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public Cancellable executeAsynchronously(String cql, AsynchronousQueryListener listener) { - - return executeAsynchronously(cql, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable executeAsynchronously(final String cql, final AsynchronousQueryListener listener, - final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - Statement statement = new SimpleStatement(logCql("async execute CQL [{}]", cql)); - - final ResultSetFuture resultSetFuture = session.executeAsync(statement); - - Runnable runnable = new Runnable() { - @Override - public void run() { - listener.onQueryComplete(resultSetFuture); - } - }; - - resultSetFuture.addListener(runnable, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public ResultSetFuture executeAsynchronously(Statement statement) { - return doExecuteAsync(statement); - } - - @Override - public Cancellable executeAsynchronously(Statement statement, Runnable listener) { - return executeAsynchronously(statement, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable executeAsynchronously(Statement statement, AsynchronousQueryListener listener) { - - return executeAsynchronously(statement, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable executeAsynchronously(final Statement statement, final Runnable listener, - final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - logDebug("executing [{}]", statement); - - final ResultSetFuture resultSetFuture = session.executeAsync(statement); - resultSetFuture.addListener(listener, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public Cancellable executeAsynchronously(final Statement statement, final AsynchronousQueryListener listener, - final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - logDebug("executing [{}]", statement); - - final ResultSetFuture resultSetFuture = session.executeAsync(statement); - - Runnable runnable = new Runnable() { - @Override - public void run() { - listener.onQueryComplete(resultSetFuture); - } - }; - - resultSetFuture.addListener(runnable, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public void process(ResultSet resultSet, RowCallbackHandler rowCallbackHandler) { - - try { - for (Row row : resultSet.all()) { - rowCallbackHandler.processRow(row); - } - } catch (DriverException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public List process(ResultSet resultSet, RowMapper rowMapper) { - - try { - - List rows = resultSet.all(); - List mappedRows = new ArrayList(rows.size()); - - int rowIndex = 0; - - for (Row row : rows) { - mappedRows.add(rowMapper.mapRow(row, rowIndex++)); - } - - return mappedRows; - } catch (DriverException dx) { - throw translateExceptionIfPossible(dx); - } - } - - @Override - public T processOne(ResultSet resultSet, RowMapper rowMapper) { - - Assert.notNull(resultSet, "ResultSet must not be null"); - Assert.notNull(rowMapper, "RowMapper must not be null"); - - try { - - Row row = resultSet.one(); - - if (row == null) { - throw new IncorrectResultSizeDataAccessException(1, 0); - } - - if (!resultSet.isExhausted()) { - throw new IncorrectResultSizeDataAccessException("ResultSet size exceeds 1", 1); - } - - return rowMapper.mapRow(row, 0); - } catch (DriverException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - @SuppressWarnings("unchecked") - public T processOne(ResultSet resultSet, Class requiredType) { - - Assert.notNull(resultSet, "ResultSet must not be null"); - - try { - - Row row = resultSet.one(); - - if (row == null) { - throw new IncorrectResultSizeDataAccessException(1, 0); - } - - if (!resultSet.isExhausted()) { - throw new IncorrectResultSizeDataAccessException("ResultSet size exceeds 1", 1); - } - - return requiredType.cast(firstColumnToObject(row)); - } catch (DriverException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public Map processMap(ResultSet resultSet) { - return (resultSet != null ? toMap(resultSet.one()) : null); - } - - @Override - @SuppressWarnings("unchecked") - public List processList(ResultSet resultSet, Class elementType) { - - List rows = resultSet.all(); - List list = new ArrayList(rows.size()); - - for (Row row : rows) { - list.add(elementType.cast(firstColumnToObject(row))); - } - - return list; - } - - @Override - public List> processListOfMap(ResultSet resultSet) { - - List rows = resultSet.all(); - List> list = new ArrayList>(rows.size()); - - for (Row row : rows) { - list.add(toMap(row)); - } - - return list; + // ------------------------------------------------------------------------- + // 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 {@code null}) + * @param ex the offending {@code RuntimeException}. + * @return the exception translation {@link Function} + * @see CqlProvider + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + protected DataAccessException translateException(String task, String cql, DriverException driverException) { + return translate(task, cql, driverException); } /** - * Attempts to translate the {@link Exception} into a Spring Data {@link Exception}. + * Create a new RowMapper for reading columns as key-value pairs. * - * @param e the {@link Exception} to translate. - * @return the translated {@link RuntimeException}. - * @see Consistent exception hierarchy + * @return the RowMapper to use + * @see ColumnMapRowMapper */ - @SuppressWarnings("all") - protected RuntimeException translateExceptionIfPossible(Throwable t) { - return translateExceptionIfPossible(t, getExceptionTranslator()); + protected RowMapper> getColumnMapRowMapper() { + return new ColumnMapRowMapper(); } /** - * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original - * exception if the conversation failed. Thus allows safe re-throwing of the return value. + * Create a new RowMapper for reading result objects from a single column. * - * @param e the exception to translate - * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation - * @return + * @param requiredType the type that each result object is expected to match + * @return the RowMapper to use + * @see SingleColumnRowMapper */ - @SuppressWarnings("all") - protected static RuntimeException translateExceptionIfPossible(Throwable t, - PersistenceExceptionTranslator exceptionTranslator) { - - Assert.notNull(t, "Throwble must not be null"); - Assert.notNull(exceptionTranslator, "PersistenceExceptionTranslator must not be null"); - - return (t instanceof RuntimeException) ? potentiallyConvertRuntimeException((RuntimeException) t, exceptionTranslator) - : new CassandraUncategorizedDataAccessException("Caught Uncategorized Exception", t); + protected RowMapper getSingleColumnRowMapper(Class requiredType) { + return SingleColumnRowMapper.newInstance(requiredType); } /** - * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original - * exception if the conversation failed. Thus allows safe re-throwing of the return value. + * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement + * settings such as fetch size, retry policy, and consistency level. * - * @param e the exception to translate - * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation - * @return + * @param stmt the CQL Statement to prepare + * @see #setFetchSize(int) + * @see #setRetryPolicy(RetryPolicy) + * @see #setConsistencyLevel(ConsistencyLevel) */ - @SuppressWarnings("all") - private static RuntimeException potentiallyConvertRuntimeException(RuntimeException e, - PersistenceExceptionTranslator exceptionTranslator) { + protected void applyStatementSettings(Statement stmt) { - RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(e); + int fetchSize = getFetchSize(); + if (fetchSize != -1 && stmt.getFetchSize() == DEFAULTS.getFetchSize()) { + stmt.setFetchSize(fetchSize); + } - return (resolved != null ? resolved : e); - } + RetryPolicy retryPolicy = getRetryPolicy(); + if (retryPolicy != null && stmt.getRetryPolicy() == DEFAULTS.getRetryPolicy()) { + stmt.setRetryPolicy(retryPolicy); + } - @Override - public T execute(PreparedStatementCreator preparedStatementCreator, - PreparedStatementCallback preparedStatementCallback) { - - try { - - PreparedStatement preparedStatement = preparedStatementCreator.createPreparedStatement(getSession()); - logDebug("executing [{}]", preparedStatement); - - return preparedStatementCallback.doInPreparedStatement(preparedStatement); - } catch (DriverException dx) { - throw translateExceptionIfPossible(dx); + ConsistencyLevel consistencyLevel = getConsistencyLevel(); + if (consistencyLevel != null && stmt.getConsistencyLevel() == DEFAULTS.getConsistencyLevel()) { + stmt.setConsistencyLevel(consistencyLevel); } } - @Override - public T execute(String cql, PreparedStatementCallback callback) { - return execute(new CachedPreparedStatementCreator(logCql(cql)), callback); - } + /** + * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement + * settings such as retry policy and consistency level. + * + * @param stmt the CQL Statement to prepare + * @see #setRetryPolicy(RetryPolicy) + * @see #setConsistencyLevel(ConsistencyLevel) + */ + protected void applyStatementSettings(PreparedStatement stmt) { - @Override - public T query(PreparedStatementCreator preparedStatementCreator, ResultSetExtractor resultSetExtractor) { - return query(preparedStatementCreator, resultSetExtractor, null); - } + RetryPolicy retryPolicy = getRetryPolicy(); + if (retryPolicy != null) { + stmt.setRetryPolicy(retryPolicy); + } - @Override - public T query(PreparedStatementCreator preparedStatementCreator, ResultSetExtractor resultSetExtractor, - QueryOptions queryOptions) { - return query(preparedStatementCreator, null, resultSetExtractor, queryOptions); - } - - @Override - public void query(PreparedStatementCreator preparedStatementCreator, RowCallbackHandler rowCallbackHandler) { - query(preparedStatementCreator, rowCallbackHandler, null); - } - - @Override - public void query(PreparedStatementCreator preparedStatementCreator, RowCallbackHandler rowCallbackHandler, - QueryOptions queryOptions) { - query(preparedStatementCreator, null, rowCallbackHandler, queryOptions); - } - - @Override - public List query(PreparedStatementCreator preparedStatementCreator, RowMapper rowMapper) { - return query(preparedStatementCreator, rowMapper, null); - } - - @Override - public List query(PreparedStatementCreator preparedStatementCreator, RowMapper rowMapper, - QueryOptions queryOptions) { - return query(preparedStatementCreator, null, rowMapper, queryOptions); - } - - @Override - public T query(String cql, PreparedStatementBinder preparedStatementBinder, - ResultSetExtractor resultSetExtractor) { - - return query(cql, preparedStatementBinder, resultSetExtractor, null); - } - - @Override - public T query(String cql, PreparedStatementBinder preparedStatementBinder, - ResultSetExtractor resultSetExtractor, QueryOptions queryOptions) { - - return query(new CachedPreparedStatementCreator(logCql(cql)), preparedStatementBinder, resultSetExtractor, - queryOptions); - } - - @Override - public void query(String cql, PreparedStatementBinder preparedStatementBinder, - RowCallbackHandler rowCallbackHandler) { - - query(cql, preparedStatementBinder, rowCallbackHandler, null); - } - - @Override - public void query(String cql, PreparedStatementBinder preparedStatementBinder, RowCallbackHandler rowCallbackHandler, - QueryOptions queryOptions) { - - query(new CachedPreparedStatementCreator(logCql(cql)), preparedStatementBinder, rowCallbackHandler, queryOptions); - } - - @Override - public List query(String cql, PreparedStatementBinder preparedStatementBinder, RowMapper rowMapper) { - return query(cql, preparedStatementBinder, rowMapper, null); - } - - @Override - public List query(String cql, PreparedStatementBinder preparedStatementBinder, RowMapper rowMapper, - QueryOptions queryOptions) { - - return query(new CachedPreparedStatementCreator(logCql(cql)), preparedStatementBinder, rowMapper, queryOptions); - } - - @Override - public void ingest(String cql, RowIterator rowIterator, WriteOptions options) { - - CachedPreparedStatementCreator cachedPreparedStatementCreator = - new CachedPreparedStatementCreator(logCql(cql)); - - PreparedStatement preparedStatement = addPreparedStatementOptions( - cachedPreparedStatementCreator.createPreparedStatement(getSession()), options); - - Session session = getSession(); - - while (rowIterator.hasNext()) { - session.executeAsync(preparedStatement.bind(rowIterator.next())); + ConsistencyLevel consistencyLevel = getConsistencyLevel(); + if (consistencyLevel != null) { + stmt.setConsistencyLevel(consistencyLevel); } } - @Override - public void ingest(String cql, RowIterator rowIterator) { - ingest(cql, rowIterator, null); + /** + * Create a new arg-based PreparedStatementSetter using the args passed in. By default, we'll create an + * {@link ArgumentPreparedStatementBinder}. This method allows for the creation to be overridden by subclasses. + * + * @param args object array with arguments + * @return the new {@link PreparedStatementBinder} to use + */ + protected PreparedStatementBinder newArgPreparedStatementBinder(Object[] args) { + return new ArgumentPreparedStatementBinder(args); } - @Override - public void ingest(String cql, List> rows) { - ingest(cql, rows, null); - } - - @Override - public void ingest(String cql, final List> rows, WriteOptions writeOptions) { - - Assert.notNull(rows, "Rows must not be null"); - Assert.notEmpty(rows, "Rows must not be empty"); - - ingest(cql, new RowIterator() { + /** + * Determine CQL from potential provider object. + * + * @param cqlProvider object that's potentially a {@link CqlProvider} + * @return the CQL string, or {@code null} + * @see CqlProvider + */ + private static String getCql(Object cqlProvider) { - Iterator> rowIterator = rows.iterator(); - - @Override - public Object[] next() { - return rowIterator.next().toArray(); - } - - @Override - public boolean hasNext() { - return rowIterator.hasNext(); - } - - }, writeOptions); + if (cqlProvider instanceof CqlProvider) { + return ((CqlProvider) cqlProvider).getCql(); + } else { + return null; + } } - @Override - public void ingest(String cql, Object[][] rows) { - ingest(cql, rows, null); - } + private class SimplePreparedStatementCreator implements PreparedStatementCreator, CqlProvider { - @Override - public void ingest(String cql, final Object[][] rows, WriteOptions writeOptions) { + private final String cql; - ingest(cql, new RowIterator() { + SimplePreparedStatementCreator(String cql) { - int index = 0; + Assert.notNull(cql, "CQL must not be null"); - @Override - public boolean hasNext() { - return (index < rows.length); - } - - @Override - public Object[] next() { - if (!hasNext()) { - throw new NoSuchElementException("No more elements"); - } - - return rows[index++]; - } - }, writeOptions); - } - - @Override - public void truncate(String tableName) { - truncate(cqlId(tableName)); - } - - @Override - public void truncate(CqlIdentifier tableName) { - doExecute(QueryBuilder.truncate(logCql(tableName.toCql()))); - } + this.cql = cql; + } - @Override - public T query(PreparedStatementCreator preparedStatementCreator, PreparedStatementBinder preparedStatementBinder, - ResultSetExtractor resultSetExtractor) { + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + return session.prepare(cql); + } - return query(preparedStatementCreator, preparedStatementBinder, resultSetExtractor, null); + @Override + public String getCql() { + return cql; + } } - @Override - public T query(PreparedStatementCreator preparedStatementCreator, - final PreparedStatementBinder preparedStatementBinder, final ResultSetExtractor resultSetExtractor, - final QueryOptions queryOptions) { - - Assert.notNull(resultSetExtractor, "ResultSetExtractor must not be null"); - - return execute(preparedStatementCreator, new PreparedStatementCallback() { - - @Override - public T doInPreparedStatement(PreparedStatement preparedStatement) { - - BoundStatement boundStatement = (preparedStatementBinder != null - ? preparedStatementBinder.bindValues(preparedStatement) : preparedStatement.bind()); - - return resultSetExtractor.extractData(doExecute(addQueryOptions(boundStatement, queryOptions))); - } - }); - } - - @Override - public void query(PreparedStatementCreator preparedStatementCreator, - final PreparedStatementBinder preparedStatementBinder, final RowCallbackHandler rowCallbackHandler, - final QueryOptions queryOptions) { - - Assert.notNull(rowCallbackHandler, "RowCallbackHandler must not be null"); - - execute(preparedStatementCreator, new PreparedStatementCallback() { - - @Override - public Object doInPreparedStatement(PreparedStatement preparedStatement) { - - BoundStatement boundStatement = (preparedStatementBinder != null - ? preparedStatementBinder.bindValues(preparedStatement) : preparedStatement.bind()); - - process(doExecute(addQueryOptions(boundStatement, queryOptions)), rowCallbackHandler); - - return null; - } - }); - } + /** + * Adapter to enable use of a {@link RowCallbackHandler} inside a {@link ResultSetExtractor}. + */ + private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor { - @Override - public void query(PreparedStatementCreator preparedStatementCreator, PreparedStatementBinder preparedStatementBinder, - RowCallbackHandler rowCallbackHandler) { - - query(preparedStatementCreator, preparedStatementBinder, rowCallbackHandler, null); - } - - @Override - public List query(PreparedStatementCreator preparedStatementCreator, - final PreparedStatementBinder preparedStatementBinder, final RowMapper rowMapper, - final QueryOptions queryOptions) { - - Assert.notNull(rowMapper, "RowMapper must not be null"); - - return execute(preparedStatementCreator, new PreparedStatementCallback>() { - - @Override - public List doInPreparedStatement(PreparedStatement preparedStatement) { - - BoundStatement boundStatement = (preparedStatementBinder != null - ? preparedStatementBinder.bindValues(preparedStatement) : preparedStatement.bind()); - - return process(doExecute(addQueryOptions(boundStatement, queryOptions)), rowMapper); - } - }); - } - - @Override - public List query(PreparedStatementCreator preparedStatementCreator, - PreparedStatementBinder preparedStatementBinder, RowMapper rowMapper) { - - return query(preparedStatementCreator, preparedStatementBinder, rowMapper, null); - } - - @Override - public ResultSet execute(final AlterKeyspaceSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(AlterKeyspaceCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final CreateKeyspaceSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(CreateKeyspaceCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final DropKeyspaceSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(DropKeyspaceCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final AlterTableSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(AlterTableCqlGenerator.toCql(specification))); - } - }); - } + private final RowCallbackHandler rch; - @Override - public ResultSet execute(final CreateTableSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(CreateTableCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final DropTableSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(DropTableCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final CreateIndexSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(CreateIndexCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public ResultSet execute(final DropIndexSpecification specification) { - - return execute(new SessionCallback() { - - @Override - public ResultSet doInSession(Session session) { - return session.execute(logCql(DropIndexCqlGenerator.toCql(specification))); - } - }); - } - - @Override - public void execute(Batch batch) { - doExecute(batch); - } - - @Override - public void execute(Delete delete) { - doExecute(delete); - } - - @Override - public void execute(Insert insert) { - doExecute(insert); - } - - @Override - public void execute(Truncate truncate) { - doExecute(truncate); - } - - @Override - public void execute(Update update) { - doExecute(update); - } - - @Override - public long count(String tableName) { - return count(cqlId(tableName)); - } - - @Override - public long count(CqlIdentifier tableName) { - return selectCount(QueryBuilder.select().countAll().from(tableName.toCql())); - } - - protected long selectCount(final Select select) { - - return query(select, new ResultSetExtractor() { - - @Override - public Long extractData(ResultSet resultSet) { - - Row row = resultSet.one(); - - if (row == null) { - throw new InvalidDataAccessApiUsageException( - String.format("count query [%1$s] did not return any results", select)); - } - - return row.getLong(0); - } - }); - } - - @Override - public ResultSetFuture executeAsynchronously(Batch batch) { - return doExecuteAsync(batch); - } - - @Override - public ResultSetFuture executeAsynchronously(Delete delete) { - return doExecuteAsync(delete); - } - - @Override - public ResultSetFuture executeAsynchronously(Insert insert) { - return doExecuteAsync(insert); - } - - @Override - public ResultSetFuture executeAsynchronously(Truncate truncate) { - return doExecuteAsync(truncate); - } - - @Override - public ResultSetFuture executeAsynchronously(Update update) { - return doExecuteAsync(update); - } - - @Override - public Cancellable executeAsynchronously(Batch batch, AsynchronousQueryListener listener) { - return doExecuteAsync(batch, listener); - } - - @Override - public Cancellable executeAsynchronously(Delete delete, AsynchronousQueryListener listener) { - return doExecuteAsync(delete, listener); - } - - @Override - public Cancellable executeAsynchronously(Insert insert, AsynchronousQueryListener listener) { - return doExecuteAsync(insert, listener); - } - - @Override - public Cancellable executeAsynchronously(Truncate truncate, AsynchronousQueryListener listener) { - return doExecuteAsync(truncate, listener); - } - - @Override - public Cancellable executeAsynchronously(Update update, AsynchronousQueryListener listener) { - return doExecuteAsync(update, listener); - } - - @Override - public ResultSetFuture queryAsynchronously(final Select select) { - - return execute(new SessionCallback() { - @Override - public ResultSetFuture doInSession(Session session) { - - logDebug("async query [{}]", select); - return session.executeAsync(select); - } - }); - } - - @Override - public Cancellable queryAsynchronously(Select select, AsynchronousQueryListener listener) { - return queryAsynchronously(select, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(final Select select, final AsynchronousQueryListener listener, - final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - logDebug("async query [{}]", select); - - final ResultSetFuture resultSetFuture = session.executeAsync(select); - - Runnable wrapper = new Runnable() { - - @Override - public void run() { - listener.onQueryComplete(resultSetFuture); - } - }; - - resultSetFuture.addListener(wrapper, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public Cancellable queryAsynchronously(Select select, Runnable listener) { - return queryAsynchronously(select, listener, RUN_RUNNABLE_EXECUTOR); - } - - @Override - public Cancellable queryAsynchronously(final Select select, final Runnable listener, final Executor executor) { - - return execute(new SessionCallback() { - - @Override - public Cancellable doInSession(Session session) { - - logDebug("async query [{}]", select); - - ResultSetFuture resultSetFuture = session.executeAsync(select); - resultSetFuture.addListener(listener, executor); - - return new ResultSetFutureCancellable(resultSetFuture); - } - }); - } - - @Override - public ResultSet query(Select select) { - return query(select, RESULT_SET_RETURNING_EXTRACTOR); - } - - @Override - public T query(Select select, ResultSetExtractor resultSetExtractor) { - - Assert.notNull(select); - - return resultSetExtractor.extractData(doExecute(select)); - } - - @Override - public void query(Select select, RowCallbackHandler rowCallbackHandler) { - process(doExecute(select), rowCallbackHandler); - } - - @Override - public List query(Select select, RowMapper rowMapper) { - return process(doExecute(select), rowMapper); - } - - @Override - public T queryForObject(Select select, RowMapper rowMapper) { - return processOne(doExecute(select), rowMapper); - } - - @Override - public T queryForObject(Select select, Class requiredType) { - return processOne(doExecute(select), requiredType); - } - - @Override - public Map queryForMap(Select select) { - return processMap(doExecute(select)); - } - - @Override - public List queryForList(Select select, Class elementType) { - return processList(doExecute(select), elementType); - } - - @Override - public List> queryForListOfMap(Select select) { - return processListOfMap(doExecute(select)); - } - - @Override - public Cancellable queryForListAsynchronously(Select select, final Class requiredType, - final QueryForListListener listener) { - - Assert.notNull(select, "Select must not be null"); - Assert.notNull(requiredType, "Required type must not be null"); - Assert.notNull(listener, "Listener must not be null"); - - return doExecuteAsync(select, new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processList(resultSetFuture.getUninterruptibly(), requiredType)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public Cancellable queryForListAsynchronously(String select, final Class requiredType, - final QueryForListListener listener) { - - Assert.hasText(select, "Select must not be null"); - Assert.notNull(requiredType, "Required type must not be null"); - Assert.notNull(listener, "Listener must not be null"); - - return doExecuteAsync(new SimpleStatement(logCql(select)), new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processList(resultSetFuture.getUninterruptibly(), requiredType)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public Cancellable queryForListOfMapAsynchronously(Select select, - final QueryForListListener> listener) { - - return doExecuteAsync(select, new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processListOfMap(resultSetFuture.getUninterruptibly())); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public Cancellable queryForListOfMapAsynchronously(String cql, - final QueryForListListener> listener) { - - return queryForListOfMapAsynchronously(cql, listener, null); - } - - @Override - public Cancellable queryForListOfMapAsynchronously(String cql, - final QueryForListListener> listener, QueryOptions queryOptions) { - - return doExecuteAsync(new SimpleStatement(logCql(cql)), new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture rsf) { - - try { - listener.onQueryComplete(processListOfMap(rsf.getUninterruptibly())); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }, queryOptions); - } - - @Override - public Cancellable queryForMapAsynchronously(String cql, QueryForMapListener listener) { - return queryForMapAsynchronously(cql, listener, null); - } - - @Override - public Cancellable queryForMapAsynchronously(String cql, final QueryForMapListener listener, - final QueryOptions queryOptions) { - - return doExecuteAsync(new SimpleStatement(logCql(cql)), new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processMap(resultSetFuture.getUninterruptibly())); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }, queryOptions); - } - - @Override - public Cancellable queryForMapAsynchronously(Select select, final QueryForMapListener listener) { - - return doExecuteAsync(select, new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processMap(resultSetFuture.getUninterruptibly())); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public Cancellable queryForObjectAsynchronously(Select select, final Class requiredType, - final QueryForObjectListener listener) { - - return doExecuteAsync(select, new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processOne(resultSetFuture.getUninterruptibly(), requiredType)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public Cancellable queryForObjectAsynchronously(String cql, Class requiredType, - QueryForObjectListener listener) { - - return queryForObjectAsynchronously(cql, requiredType, listener, null); - } - - @Override - public Cancellable queryForObjectAsynchronously(String cql, final Class requiredType, - final QueryForObjectListener listener, QueryOptions options) { - - return doExecuteAsync(new SimpleStatement(logCql(cql)), new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processOne(resultSetFuture.getUninterruptibly(), requiredType)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }, options); - } - - @Override - public Cancellable queryForObjectAsynchronously(String cql, RowMapper rowMapper, - QueryForObjectListener listener) { - - return queryForObjectAsynchronously(cql, rowMapper, listener, null); - } - - @Override - public Cancellable queryForObjectAsynchronously(String cql, final RowMapper rowMapper, - final QueryForObjectListener listener, QueryOptions options) { - - return doExecuteAsync(new SimpleStatement(logCql(cql)), new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processOne(resultSetFuture.getUninterruptibly(), rowMapper)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }, options); - } - - @Override - public Cancellable queryForObjectAsynchronously(Select select, final RowMapper rowMapper, - final QueryForObjectListener listener) { - - return doExecuteAsync(select, new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - try { - listener.onQueryComplete(processOne(resultSetFuture.getUninterruptibly(), rowMapper)); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - } - - @Override - public ResultSet getResultSetUninterruptibly(ResultSetFuture resultSetFuture) { - return getResultSetUninterruptibly(resultSetFuture, 0, null); - } - - @Override - public ResultSet getResultSetUninterruptibly(ResultSetFuture resultSetFuture, long milliseconds) { - return getResultSetUninterruptibly(resultSetFuture, milliseconds, TimeUnit.MILLISECONDS); - } + public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { + this.rch = rch; + } - @Override - public ResultSet getResultSetUninterruptibly(ResultSetFuture resultSetFuture, long timeout, TimeUnit timeUnit) { - try { - timeUnit = (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS); + @Override + public Object extractData(ResultSet rs) { - return (timeout > 0 ? resultSetFuture.getUninterruptibly(timeout, timeUnit) - : resultSetFuture.getUninterruptibly()); - } catch (Exception e) { - throw translateExceptionIfPossible(e); + StreamSupport.stream(rs.spliterator(), false).forEach(rch::processRow); + return null; } } } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/ExceptionTranslatingListenableFutureAdapter.java b/spring-cql/src/main/java/org/springframework/cassandra/core/ExceptionTranslatingListenableFutureAdapter.java new file mode 100644 index 000000000..1cd1221ae --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/ExceptionTranslatingListenableFutureAdapter.java @@ -0,0 +1,155 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +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.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 2.0 + */ +class ExceptionTranslatingListenableFutureAdapter implements ListenableFuture { + + private final ListenableFuture adaptee; + private final ListenableFuture 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}. + */ + public ExceptionTranslatingListenableFutureAdapter(ListenableFuture 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 ListenableFuture adaptListenableFuture(ListenableFuture listenableFuture, + PersistenceExceptionTranslator exceptionTranslator) { + + SettableListenableFuture settableFuture = new SettableListenableFuture(); + + listenableFuture.addCallback(new ListenableFutureCallback() { + + @Override + public void onSuccess(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; + + } + + /* + * (non-Javadoc) + * @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback) + */ + @Override + public void addCallback(ListenableFutureCallback callback) { + future.addCallback(callback); + } + + /* + * (non-Javadoc) + * @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback) + */ + @Override + public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { + future.addCallback(successCallback, failureCallback); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#cancel(boolean) + */ + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return adaptee.cancel(mayInterruptIfRunning); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#isCancelled() + */ + @Override + public boolean isCancelled() { + return adaptee.isCancelled(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#isDone() + */ + @Override + public boolean isDone() { + return future.isDone(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#get() + */ + @Override + public T get() throws InterruptedException, ExecutionException { + return future.get(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit) + */ + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/GuavaListenableFutureAdapter.java b/spring-cql/src/main/java/org/springframework/cassandra/core/GuavaListenableFutureAdapter.java new file mode 100644 index 000000000..583e70077 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/GuavaListenableFutureAdapter.java @@ -0,0 +1,158 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +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.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; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; + +/** + * Adapter class to adapt Guava's {@link com.google.common.util.concurrent.ListenableFuture} into a Spring + * {@link ListenableFuture}. + * + * @author Mark Paluch + * @since 2.0 + */ +public class GuavaListenableFutureAdapter implements ListenableFuture { + + private final com.google.common.util.concurrent.ListenableFuture adaptee; + private final ListenableFuture future; + + /** + * Create a new {@link GuavaListenableFutureAdapter} given a Guava + * {@link com.google.common.util.concurrent.ListenableFuture} and a {@link PersistenceExceptionTranslator}. + * + * @param adaptee must not be {@literal null}. + * @param persistenceExceptionTranslator must not be {@literal null}. + */ + public GuavaListenableFutureAdapter(com.google.common.util.concurrent.ListenableFuture 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 ListenableFuture adaptListenableFuture( + com.google.common.util.concurrent.ListenableFuture guavaFuture, + PersistenceExceptionTranslator exceptionTranslator) { + + SettableListenableFuture settableFuture = new SettableListenableFuture(); + + Futures.addCallback(guavaFuture, new FutureCallback() { + @Override + public void onSuccess(T result) { + settableFuture.set(result); + } + + @Override + public void onFailure(Throwable t) { + + if (t instanceof RuntimeException) { + + DataAccessException dataAccessException = exceptionTranslator + .translateExceptionIfPossible((RuntimeException) t); + if (dataAccessException != null) { + settableFuture.setException(dataAccessException); + return; + } + } + + settableFuture.setException(t); + } + }); + + return settableFuture; + + } + + /* + * (non-Javadoc) + * @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback) + */ + @Override + public void addCallback(ListenableFutureCallback callback) { + future.addCallback(callback); + } + + /* + * (non-Javadoc) + * @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback) + */ + @Override + public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { + future.addCallback(successCallback, failureCallback); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#cancel(boolean) + */ + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return adaptee.cancel(mayInterruptIfRunning); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#isCancelled() + */ + @Override + public boolean isCancelled() { + return adaptee.isCancelled(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#isDone() + */ + @Override + public boolean isDone() { + return future.isDone(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#get() + */ + @Override + public T get() throws InterruptedException, ExecutionException { + return future.get(); + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit) + */ + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/HostMapper.java b/spring-cql/src/main/java/org/springframework/cassandra/core/HostMapper.java index 5aee66093..4d094515f 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/HostMapper.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/HostMapper.java @@ -16,13 +16,30 @@ package org.springframework.cassandra.core; import java.util.Collection; -import java.util.Set; import com.datastax.driver.core.Host; import com.datastax.driver.core.exceptions.DriverException; +/** + * An interface used by {@link CqlTemplate} for mapping {@link Host}s of a {@link com.datastax.driver.core.Metadata} on + * a per-item basis.. Implementations of this interface perform the actual work of mapping each host to a result object, + * but don't need to worry about exception handling. {@link DriverException} will be caught and handled by the calling + * {@link CqlTemplate}. + * + * @author Matthew T. Adams + * @author Mark Paluch + * @see CqlTemplate + */ public interface HostMapper { - Collection mapHosts(Set host) throws DriverException; - + /** + * Implementations must implement this method to map each {@link Host} in the + * {@link com.datastax.driver.core.Metadata}. + * + * @param hosts the {@link Iterable} of {@link Host}s to map, must not be {@literal null}. + * @return the result objects for the given hosts. + * @throws DriverException if a {@link DriverException} is encountered mapping values (that is, there's no need to + * catch {@link DriverException}). + */ + Collection mapHosts(Iterable hosts) throws DriverException; } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java b/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java index da855c45e..e9cca7560 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCallback.java @@ -18,13 +18,42 @@ package org.springframework.cassandra.core; import org.springframework.dao.DataAccessException; import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; import com.datastax.driver.core.exceptions.DriverException; /** + * Generic callback interface for code that operates on a {@link PreparedStatement}. Allows to execute any number of + * operations on a single {@link PreparedStatement}, for example a single {@link Session#execute(Statement). + *

+ * Used internally by {@link CqlTemplate}, but also useful for application code. Note that the passed-in + * {@link PreparedStatement} can have been created by the framework or by a custom {@link PreparedStatementCreator}. + * However, the latter is hardly ever necessary, as most custom callback actions will perform updates in which case a + * standard {@link PreparedStatement is fine. Custom actions will always set parameter values themselves, so that + * {@link PreparedStatementCreator} capability is not needed either. + * * @author David Webb + * @author Mark Paluch + * @see CqlTemplate#execute(String, PreparedStatementCallback) + * @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback) */ public interface PreparedStatementCallback { + /** + * Gets called by {@link CqlTemplate#execute(String, PreparedStatementCallback)} with a {@link PreparedStatement}. + *

+ * 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(String, Class, Object...)} etc. A thrown RuntimeException is treated as + * application exception, it gets propagated to the caller of the template. + * + * @param ps the {@link PreparedStatement}, must not be {@literal null}. + * @return a result object publisher. + * @throws DriverException if thrown by a session method, to be auto-converted to a DataAccessException. + * @throws DataAccessException in case of custom exceptions. + * @see CqlTemplate#queryForObject(String, Class, Object...) + * @see CqlTemplate#queryForList(String, Object...) + */ T doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException; } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java b/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java index c46544fda..d6740d854 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreator.java @@ -20,21 +20,27 @@ import com.datastax.driver.core.Session; import com.datastax.driver.core.exceptions.DriverException; /** - * Creates a PreparedStatement for the usage with the DataStax Java Driver - * + * One of the two central callback interfaces used by the {@link CqlTemplate} class. This interface creates a + * {@link PreparedStatement} given a session, provided by the {@link CqlTemplate} class. Implementations are responsible + * for providing CQL and any necessary parameters. + *

+ * Implementations do not need to concern themselves with {@link DriverException}s that may be thrown from + * operations they attempt. The {@link CqlTemplate} class will catch and handle {@link DriverException}s appropriately. + * * @author David Webb + * @author Mark Paluch + * @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback) + * @see CqlTemplate#query(PreparedStatementCreator, RowCallbackHandler) */ public interface PreparedStatementCreator { /** - * Create a statement in this session. Allows implementations to use PreparedStatements. The CassandraTemlate will - * attempt to cache the PreparedStatement for future use without the overhead of re-preparing on the entire cluster. + * Create a statement in this session. Allows implementations to use {@link PreparedStatement}. * - * @param session Session to use to create statement + * @param session {@link Session} to use to create statement * @return a prepared statement - * @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this - * method. The CassandraTemlate class will handle them. + * @throws DriverException there is no need to catch {@link DriverException} that may be thrown in the implementation + * of this method. The {@link CqlTemplate} class will handle them. */ PreparedStatement createPreparedStatement(Session session) throws DriverException; - } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/QueryOptionsUtil.java b/spring-cql/src/main/java/org/springframework/cassandra/core/QueryOptionsUtil.java new file mode 100644 index 000000000..345af6f61 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/QueryOptionsUtil.java @@ -0,0 +1,151 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Update; + +/** + * Utility class to associate {@link QueryOptions} and {@link WriteOptions} with QueryBuilder {@link Statement}s. + * + * @author Mark Paluch + * @since 2.0 + */ +public abstract class QueryOptionsUtil { + + /** + * Add common {@link QueryOptions} to Cassandra {@link PreparedStatement}s. + * + * @param preparedStatement the Cassandra {@link PreparedStatement}, must not be {@literal null}. + * @param queryOptions query options (e.g. consistency level) to add to the Cassandra {@link PreparedStatement}. + */ + public static PreparedStatement addPreparedStatementOptions(PreparedStatement preparedStatement, + QueryOptions queryOptions) { + + Assert.notNull(preparedStatement, "PreparedStatement must not be null"); + + if (queryOptions != null) { + if (queryOptions.getDriverConsistencyLevel() != null) { + preparedStatement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel()); + } else if (queryOptions.getConsistencyLevel() != null) { + preparedStatement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel())); + } + + if (queryOptions.getDriverRetryPolicy() != null) { + preparedStatement.setRetryPolicy(queryOptions.getDriverRetryPolicy()); + } else if (queryOptions.getRetryPolicy() != null) { + preparedStatement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy())); + } + } + + return preparedStatement; + } + + /** + * Add common {@link QueryOptions} to all types of queries. + * + * @param statement CQL {@link Statement}, must not be {@literal null}. + * @param queryOptions query options (e.g. consistency level) to add to the CQL statement. + * @return the given {@link Statement}. + */ + public static T addQueryOptions(T statement, QueryOptions queryOptions) { + + Assert.notNull(statement, "Statement must not be null"); + + if (queryOptions != null) { + if (queryOptions.getDriverConsistencyLevel() != null) { + statement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel()); + } else if (queryOptions.getConsistencyLevel() != null) { + statement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel())); + } + + if (queryOptions.getDriverRetryPolicy() != null) { + statement.setRetryPolicy(queryOptions.getDriverRetryPolicy()); + } else if (queryOptions.getRetryPolicy() != null) { + statement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy())); + } + + if (queryOptions.getFetchSize() != null) { + statement.setFetchSize(queryOptions.getFetchSize()); + } + + if (queryOptions.getReadTimeout() != null) { + statement.setReadTimeoutMillis(queryOptions.getReadTimeout().intValue()); + } + + if (queryOptions.getTracing() != null) { + if (queryOptions.getTracing()) { + statement.enableTracing(); + } else { + statement.disableTracing(); + } + } + } + + return statement; + } + + /** + * Add common {@link WriteOptions} options to {@link Insert} CQL statements. + * + * @param insert {@link Insert} CQL statement, must not be {@literal null}. + * @param writeOptions write options (e.g. consistency level) to add to the CQL statement. + * @return the given {@link Insert}. + */ + public static Insert addWriteOptions(Insert insert, WriteOptions writeOptions) { + + Assert.notNull(insert, "Insert must not be null"); + + if (writeOptions != null) { + + addQueryOptions(insert, writeOptions); + + if (writeOptions.getTtl() != null) { + insert.using(QueryBuilder.ttl(writeOptions.getTtl())); + } + } + + return insert; + } + + /** + * Add common {@link WriteOptions} options to {@link Update} CQL statements. + * + * @param update {@link Update} CQL statement, must not be {@literal null}. + * @param writeOptions write options (e.g. consistency level) to add to the CQL statement. + * @return the given {@link Update}. + */ + public static Update addWriteOptions(Update update, WriteOptions writeOptions) { + + Assert.notNull(update, "Update must not be null"); + + if (writeOptions != null) { + + addQueryOptions(update, writeOptions); + + if (writeOptions.getTtl() != null) { + update.using(QueryBuilder.ttl(writeOptions.getTtl())); + } + } + + return update; + } +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java b/spring-cql/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java index 2dc1679c9..10ea122d1 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/ResultSetExtractor.java @@ -20,7 +20,36 @@ import org.springframework.dao.DataAccessException; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.exceptions.DriverException; +/** + * Callback interface used by {@link CqlTemplate}'s query methods. Implementations of this interface perform the actual + * work of extracting results from a {@link ResultSet}, but don't need to worry about exception handling. + * {@link DriverException}s will be caught and handled by the calling {@link CqlTemplate}. + *

+ * This interface is mainly used within the CQL framework itself. A {@link RowMapper} is usually a simpler choice for + * {@link ResultSet} processing, mapping one result object per row instead of one result object for the entire + * {@link ResultSet}. + *

+ * Note: In contrast to a {@link RowCallbackHandler}, a {@link ResultSetExtractor} object is typically stateless and + * thus reusable, as long as it doesn't access stateful resources or keep result state within the object. + * + * @author Matthew T. Adams + * @author Mark Paluch + * @since April 24, 2003 + * @see CqlTemplate + * @see RowCallbackHandler + * @see RowMapper + */ public interface ResultSetExtractor { + /** + * Implementations must implement this method to process the entire {@link ResultSet}. + * + * @param rs {@link ResultSet} to extract data from. + * @return an arbitrary result object, or {@code 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 + */ T extractData(ResultSet rs) throws DriverException, DataAccessException; } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java b/spring-cql/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java index e9c474801..1f79c4bc1 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/RingMemberHostMapper.java @@ -15,9 +15,9 @@ */ package org.springframework.cassandra.core; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.springframework.util.Assert; @@ -25,29 +25,26 @@ import com.datastax.driver.core.Host; import com.datastax.driver.core.exceptions.DriverException; /** + * {@link HostMapper} to to map hosts into {@link RingMember} objects. + * * @author David Webb + * @author Mark Paluch * @param */ -public class RingMemberHostMapper implements HostMapper { +public enum RingMemberHostMapper implements HostMapper { + + INSTANCE; /* (non-Javadoc) - * @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Set) + * @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Iterable) */ @Override - public List mapHosts(Set hosts) throws DriverException { + public Collection mapHosts(Iterable hosts) throws DriverException { - List members = new ArrayList(); - - Assert.notNull(hosts); - Assert.notEmpty(hosts); - - RingMember r = null; - for (Host host : hosts) { - r = new RingMember(host); - members.add(r); - } - - return members; + Assert.notNull(hosts, "Hosts must not be null"); + return StreamSupport.stream(hosts.spliterator(), false) // + .map(RingMember::new) // + .collect(Collectors.toList()); } } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java b/spring-cql/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java index deef9a451..6d8e2f12c 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/RowCallbackHandler.java @@ -18,8 +18,36 @@ package org.springframework.cassandra.core; import com.datastax.driver.core.Row; import com.datastax.driver.core.exceptions.DriverException; +/** + * An interface used by {@link CqlTemplate} for processing rows of a {@link com.datastax.driver.core.ResultSet} on a + * per-row basis. Implementations of this interface perform the actual work of processing each row but don't need to + * worry about exception handling. {@link DriverException}s will be caught and handled by the calling + * {@link CqlTemplate}. + *

+ * In contrast to a {@link ResultSetExtractor}, a {@link RowCallbackHandler} object is typically stateful: It keeps the + * result state within the object, to be available for later inspection. + *

+ * Consider using a {@link RowMapper} instead if you need to map exactly one result object per row, assembling them into + * a List. + * + * @author Mark Paluch + * @see CqlTemplate + * @see RowMapper + * @see ResultSetExtractor + */ +@FunctionalInterface public interface RowCallbackHandler { + /** + * Implementations must implement this method to process each row of data in the {@link ResultSet}. This method is only + * supposed to extract values of the current row. + *

+ * Exactly what the implementation chooses to do is up to it: A trivial implementation might simply count rows, while + * another implementation might build an XML document. + * + * @param row the {@link Row} to process (pre-initialized for the current row) + * @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need + * to catch {@link DriverException}) + */ void processRow(Row row) throws DriverException; - } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapper.java b/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapper.java index 3dab710f2..98e208d79 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapper.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapper.java @@ -18,8 +18,32 @@ package org.springframework.cassandra.core; import com.datastax.driver.core.Row; import com.datastax.driver.core.exceptions.DriverException; +/** + * An interface used by {@link CqlTemplate} for mapping rows of a {@link com.datastax.driver.core.ResultSet} on a + * per-row basis. Implementations of this interface perform the actual work of mapping each row to a result object, but + * don't need to worry about exception handling. {@link DriverException}s will be caught and handled by the calling + * {@link CqlTemplate}. + *

+ * Typically used either for {@link CqlTemplate}'s query methods or for out parameters of stored procedures. + * {@link RowMapper} objects are typically stateless and thus reusable; they are an ideal choice for implementing + * row-mapping logic in a single place. + * + * @author Matthew T. Adams + * @author Mark Paluch + * @see RowCallbackHandler + * @see ResultSetExtractor + */ public interface RowMapper { + /** + * Implementations must implement this method to map each row of data in the + * {@link com.datastax.driver.core.ResultSet}. + * + * @param row the {@link Row} to map, must not be {@literal null}. + * @param rowNum the number of the current row. + * @return the result object for the current row. + * @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need + * to catch {@link DriverException}) + */ T mapRow(Row row, int rowNum) throws DriverException; - } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapperResultSetExtractor.java b/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapperResultSetExtractor.java new file mode 100644 index 000000000..6e9e39834 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/RowMapperResultSetExtractor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.util.Assert; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.exceptions.DriverException; + +/** + * 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}. + *

+ * 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. + *

+ * Note that a {@link RowMapper} object is typically stateless and thus reusable. + * + * @author Mark Paluch + * @since 2.0 + * @see RowMapper + * @see CqlTemplate + */ +public class RowMapperResultSetExtractor implements ResultSetExtractor> { + + private final RowMapper rowMapper; + + private final int rowsExpected; + + /** + * Create a new {@link RowMapperResultSetExtractor}. + * + * @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}. + */ + public RowMapperResultSetExtractor(RowMapper rowMapper) { + this(rowMapper, 0); + } + + /** + * Create a new {@link RowMapperResultSetExtractor}. + * + * @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}. + * @param rowsExpected the number of expected rows (just used for optimized collection handling). + */ + public RowMapperResultSetExtractor(RowMapper rowMapper, int rowsExpected) { + + Assert.notNull(rowMapper, "RowMapper is must not be null"); + + this.rowMapper = rowMapper; + this.rowsExpected = rowsExpected; + } + + /* (non-Javadoc) + * @see org.springframework.cassandra.core.ResultSetExtractor#extractData(com.datastax.driver.core.ResultSet) + */ + @Override + public List extractData(ResultSet resultSet) throws DriverException, DataAccessException { + + List results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList()); + + int rowNum = 0; + for (Row row : resultSet) { + results.add(this.rowMapper.mapRow(row, rowNum++)); + } + + return results; + } +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/SessionCallback.java b/spring-cql/src/main/java/org/springframework/cassandra/core/SessionCallback.java index 130088e95..2e2b1c4cf 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/SessionCallback.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/SessionCallback.java @@ -18,22 +18,39 @@ package org.springframework.cassandra.core; import org.springframework.dao.DataAccessException; import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.DriverException; /** - * Interface for operations on a Cassandra Session. - * + * Generic callback interface for code that operates on a Cassandra {@link Session}. Allows to execute any number of + * operations on a single session, using any type and number of statements. + *

+ * This is particularly useful for delegating to existing data access code that expects a {@link Session} 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. + * * @author David Webb - * @param + * @author Mark Paluch + * @see CqlTemplate#execute(SessionCallback) + * @see CqlTemplate#query */ public interface SessionCallback { /** - * Perform the operation in the given Session + * Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about + * activating or closing the {@link Session}. + *

+ * 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 + * template. * - * @param s - * @return - * @throws DataAccessException + * @param session active Cassandra Session, must not be {@literal null}. + * @return a result object, or {@code null} 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 CqlTemplate#queryForObject(String, Class) + * @see CqlTemplate#queryForResultSet(String) */ - T doInSession(Session s) throws DataAccessException; + T doInSession(Session session) throws DriverException, DataAccessException; } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java index d245437b1..e468df583 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java @@ -15,9 +15,11 @@ */ package org.springframework.cassandra.support; +import com.datastax.driver.core.exceptions.DriverException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; import org.springframework.util.Assert; import com.datastax.driver.core.Session; @@ -103,4 +105,50 @@ public class CassandraAccessor implements InitializingBean { Assert.state(this.session != null, "Session was not properly initialized"); return this.session; } + + /** + * Translate the given {@link DriverException} into a generic {@link DataAccessException}. + *

+ * The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause. + * However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by + * other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and + * subsequent cast) is considered reliable when expecting Cassandra-based access to have happened. + * + * @param ex the offending {@link DriverException} + * @return the DataAccessException, wrapping the {@code DriverException} + * @see Consistent + * exception hierarchy + * @see DataAccessException + */ + protected DataAccessException translateExceptionIfPossible(DriverException ex) { + + Assert.notNull(ex, "DriverException must not be null"); + + return getExceptionTranslator().translateExceptionIfPossible(ex); + } + + /** + * Translate the given {@link DriverException} into a generic {@link DataAccessException}. + *

+ * The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause. + * However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by + * other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and + * subsequent cast) is considered reliable when expecting Cassandra-based access to have happened. + * + * @param task readable text describing the task being attempted + * @param cql CQL query or update that caused the problem (may be {@code null}) + * @param ex the offending {@link DriverException} + * @return the DataAccessException, wrapping the {@code DriverException} + * @see org.springframework.dao.DataAccessException#getRootCause() + * @see Consistent + * exception hierarchy + */ + protected DataAccessException translate(String task, String cql, DriverException ex) { + + Assert.notNull(ex, "DriverException must not be null"); + + return getExceptionTranslator().translate(task, cql, ex); + } } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateIntegrationTests.java b/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateIntegrationTests.java new file mode 100644 index 000000000..460e428ce --- /dev/null +++ b/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateIntegrationTests.java @@ -0,0 +1,214 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.querybuilder.QueryBuilder; + +/** + * Integration tests for {@link AsyncCqlTemplate}. + * + * @author Mark Paluch + */ +public class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { + + private static final AtomicBoolean initialized = new AtomicBoolean(); + private AsyncCqlTemplate template; + + @Before + public void before() throws Exception { + + if (initialized.compareAndSet(false, true)) { + getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);"); + } else { + session.execute("TRUNCATE user;"); + } + + session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');"); + + template = new AsyncCqlTemplate(); + template.setSession(getSession()); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeShouldRemoveRecords() throws Exception { + + template.execute("DELETE FROM user WHERE id = 'WHITE'").get(); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query("SELECT id FROM user;", row -> { + result.add(row.getString(0)); + }).get(); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject("SELECT id FROM user;", String.class).get(); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectShouldReturnMap() throws Exception { + + Map map = template.queryForMap("SELECT * FROM user;").get(); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldRemoveRecords() throws Exception { + + template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE"))).get(); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryStatementShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query(QueryBuilder.select("id").from("user"), row -> { + result.add(row.getString(0)); + }).get(); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class).get(); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnMap() throws Exception { + + Map map = template.queryForMap(QueryBuilder.select().from("user")).get(); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeWithArgsShouldRemoveRecords() throws Exception { + + template.execute("DELETE FROM user WHERE id = ?", "WHITE").get(); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query("SELECT id FROM user WHERE id = ?;", row -> { + result.add(row.getString(0)); + }, "WHITE").get(); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query( + session -> new GuavaListenableFutureAdapter( + session.prepareAsync("SELECT id FROM user WHERE id = ?;"), template.getExceptionTranslator()), + ps -> ps.bind("WHITE"), row -> { + result.add(row.getString(0)); + }).get(); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE").get(); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectWithArgsShouldReturnMap() throws Exception { + + Map map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE").get(); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } +} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateUnitTests.java b/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateUnitTests.java new file mode 100644 index 000000000..a33b73ffd --- /dev/null +++ b/spring-cql/src/test/java/org/springframework/cassandra/core/AsyncCqlTemplateUnitTests.java @@ -0,0 +1,1032 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; +import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.*; +import com.datastax.driver.core.ConsistencyLevel; +import com.datastax.driver.core.exceptions.InvalidQueryException; +import com.datastax.driver.core.exceptions.NoHostAvailableException; +import com.datastax.driver.core.policies.DowngradingConsistencyRetryPolicy; +import com.google.common.util.concurrent.AbstractFuture; +import com.google.common.util.concurrent.SettableFuture; + +/** + * Unit tests for {@link AsyncCqlTemplate}. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +public class AsyncCqlTemplateUnitTests { + + @Mock private Session session; + @Mock private ResultSet resultSet; + @Mock private Row row; + @Mock private PreparedStatement preparedStatement; + @Mock private BoundStatement boundStatement; + @Mock private ColumnDefinitions columnDefinitions; + + private AsyncCqlTemplate template; + + @Before + public void setup() throws Exception { + + this.template = new AsyncCqlTemplate(); + this.template.setSession(session); + } + + // ------------------------------------------------------------------------- + // Tests dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void executeCallbackShouldTranslateExceptions() throws Exception { + + try { + template.execute((AsyncSessionCallback) session -> { + throw new InvalidQueryException("wrong query"); + }); + + fail("Missing CassandraInvalidQueryException"); + } catch (CassandraInvalidQueryException e) { + assertThat(e).hasMessageContaining("wrong query"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlShouldTranslateExceptions() throws Exception { + + TestResultSetFuture resultSetFuture = TestResultSetFuture + .failed(new NoHostAvailableException(Collections.emptyMap())); + when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture); + + ListenableFuture future = template.execute("UPDATE user SET a = 'b';"); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("tried for query failed"); + } + } + + // ------------------------------------------------------------------------- + // Tests dealing with static CQL + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + asyncCqlTemplate.execute("SELECT * from USERS"); + + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, asyncCqlTemplate -> { + + asyncCqlTemplate.execute("SELECT * from USERS"); + + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForResultSetShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + ResultSet resultSet = getUninterruptibly(asyncCqlTemplate.queryForResultSet("SELECT * from USERS")); + + assertThat(resultSet).hasSize(3); + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetExtractorShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + List 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)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetExtractorWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, asyncCqlTemplate -> { + + List 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)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryCqlShouldTranslateExceptions() throws Exception { + + TestResultSetFuture resultSetFuture = TestResultSetFuture + .failed(new NoHostAvailableException(Collections.emptyMap())); + when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture); + + ListenableFuture future = template.query("UPDATE user SET a = 'b';", ResultSet::wasApplied); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("tried for query failed"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldBeEmpty() throws Exception { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + ListenableFuture 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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK"); + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldReturnNullValue() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.queryForObject("SELECT * FROM user", (row, rowNum) -> null); + assertThat(getUninterruptibly(future)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldFailReturningManyRecords() throws Exception { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + + ListenableFuture 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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlWithTypeShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); + + ListenableFuture future = template.queryForObject("SELECT * FROM user", String.class); + + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForListCqlWithTypeShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); + + ListenableFuture> future = template.queryForList("SELECT * FROM user", String.class); + + assertThat(getUninterruptibly(future)).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlShouldReturnWasApplied() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.wasApplied()).thenReturn(true); + + ListenableFuture future = template.execute("UPDATE user SET a = 'b';"); + + assertThat(getUninterruptibly(future)).isTrue(); + } + + // ------------------------------------------------------------------------- + // Tests dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + asyncCqlTemplate.execute(new SimpleStatement("SELECT * from USERS")); + + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, asyncCqlTemplate -> { + + asyncCqlTemplate.execute(new SimpleStatement("SELECT * from USERS")); + + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForResultStatementSetShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + ListenableFuture future = asyncCqlTemplate + .queryForResultSet(new SimpleStatement("SELECT * from USERS")); + + assertThat(getUninterruptibly(future)).hasSize(3); + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetStatementExtractorShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + ListenableFuture> future = asyncCqlTemplate.query(new SimpleStatement("SELECT * from USERS"), + (row, index) -> row.getString(0)); + + assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetStatementExtractorWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, asyncCqlTemplate -> { + + ListenableFuture> future = asyncCqlTemplate.query(new SimpleStatement("SELECT * from USERS"), + (row, index) -> row.getString(0)); + + assertThat(getUninterruptibly(future)).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).executeAsync(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryStatementShouldTranslateExceptions() throws Exception { + + TestResultSetFuture resultSetFuture = TestResultSetFuture + .failed(new NoHostAvailableException(Collections.emptyMap())); + when(session.executeAsync(any(Statement.class))).thenReturn(resultSetFuture); + + ListenableFuture future = template.query(new SimpleStatement("UPDATE user SET a = 'b';"), + ResultSet::wasApplied); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("tried for query failed"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldBeEmpty() throws Exception { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + ListenableFuture future = template.queryForObject(new SimpleStatement("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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.queryForObject(new SimpleStatement("SELECT * FROM user"), + (row, rowNum) -> "OK"); + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnNullValue() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.queryForObject(new SimpleStatement("SELECT * FROM user"), + (row, rowNum) -> null); + assertThat(getUninterruptibly(future)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldFailReturningManyRecords() throws Exception { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + + ListenableFuture future = template.queryForObject(new SimpleStatement("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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementWithTypeShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); + + ListenableFuture future = template.queryForObject(new SimpleStatement("SELECT * FROM user"), String.class); + + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForListStatementWithTypeShouldReturnRecord() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); + + ListenableFuture> future = template.queryForList(new SimpleStatement("SELECT * FROM user"), + String.class); + + assertThat(getUninterruptibly(future)).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldReturnWasApplied() { + + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.wasApplied()).thenReturn(true); + + ListenableFuture future = template.execute(new SimpleStatement("UPDATE user SET a = 'b';")); + + assertThat(getUninterruptibly(future)).isTrue(); + } + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementWithCallbackShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + ListenableFuture futureOfFuture = asyncCqlTemplate.execute("SELECT * from USERS", + (PreparedStatementCallback) (ps) -> asyncCqlTemplate.getSession() + .executeAsync(ps.bind("A"))); + + try { + assertThat(getUninterruptibly(futureOfFuture).get()).hasSize(3); + } catch (Exception e) { + fail(e.getMessage(), e); + } + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executePreparedStatementWithCallbackShouldCallExecution() { + + doTestStrings(null, null, null, asyncCqlTemplate -> { + + when(this.preparedStatement.bind("White")).thenReturn(this.boundStatement); + when(this.resultSet.wasApplied()).thenReturn(true); + + ListenableFuture applied = asyncCqlTemplate.execute("UPDATE users SET name = ?", "White"); + + assertThat(getUninterruptibly(applied)).isTrue(); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executePreparedStatementCreatorShouldTranslateStatementCreationExceptions() throws Exception { + + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.wasApplied()).thenReturn(true); + + try { + template.execute(session -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }, (ps) -> session.executeAsync(boundStatement)); + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query"); + } + + ListenableFuture future = template.execute( + session -> AsyncResult.forExecutionException(new NoHostAvailableException(Collections.emptyMap())), + (ps) -> session.executeAsync(boundStatement)); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("tried for query"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void executePreparedStatementCreatorShouldTranslateStatementCallbackExceptions() throws Exception { + + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.wasApplied()).thenReturn(true); + + ListenableFuture future = template.execute(session -> new AsyncResult<>(preparedStatement), + (ps) -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("tried for query"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorShouldReturnResult() { + + when(session.prepareAsync(anyString())).thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture> future = template.query(session -> new AsyncResult<>(preparedStatement), + ResultSet::iterator); + + assertThat(getUninterruptibly(future)).hasSize(1).contains(row); + verify(preparedStatement).bind(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldReturnResult() { + + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + assertThat(getUninterruptibly(future)).contains(row); + verify(preparedStatement).bind("a", "b"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldTranslatePrepareStatementExceptions() throws Exception { + + when(preparedStatement.bind()).thenReturn(boundStatement); + + ListenableFuture future = template.query( + session -> AsyncResult.forExecutionException(new NoHostAvailableException(Collections.emptyMap())), ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldTranslateBindExceptions() throws Exception { + + when(preparedStatement.bind()).thenReturn(boundStatement); + + ListenableFuture future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }, rs -> rs); + + try { + future.get(); + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldTranslateExecutionExceptions() throws Exception { + + when(preparedStatement.bind()).thenReturn(boundStatement); + + TestResultSetFuture resultSetFuture = TestResultSetFuture + .failed(new NoHostAvailableException(Collections.emptyMap())); + + when(session.executeAsync(boundStatement)).thenReturn(resultSetFuture); + + ListenableFuture future = template.query(session -> new AsyncResult<>(preparedStatement), ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + try { + future.get(); + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderAndMapperShouldReturnResult() { + + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture> 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"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementShouldBeEmpty() throws Exception { + + when(session.prepareAsync("SELECT * FROM user WHERE username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + ListenableFuture 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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementShouldReturnRecord() { + + when(session.prepareAsync("SELECT * FROM user WHERE username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ListenableFuture future = template.queryForObject("SELECT * FROM user WHERE username = ?", + (row, rowNum) -> "OK", "Walter"); + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementShouldFailReturningManyRecords() throws Exception { + + when(session.prepareAsync("SELECT * FROM user WHERE username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + + ListenableFuture 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"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementWithTypeShouldReturnRecord() { + + when(session.prepareAsync("SELECT * FROM user WHERE username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); + + Future future = template.queryForObject("SELECT * FROM user WHERE username = ?", String.class, "Walter"); + + assertThat(getUninterruptibly(future)).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForListPreparedStatementWithTypeShouldReturnRecord() { + + when(session.prepareAsync("SELECT * FROM user WHERE username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); + + ListenableFuture> future = template.queryForList("SELECT * FROM user WHERE username = ?", String.class, + "Walter"); + + assertThat(getUninterruptibly(future)).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updatePreparedStatementShouldReturnApplied() { + + when(session.prepareAsync("UPDATE user SET username = ?")) + .thenReturn(new TestPreparedStatementFuture(preparedStatement)); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.executeAsync(boundStatement)).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.wasApplied()).thenReturn(true); + + ListenableFuture future = template.execute("UPDATE user SET username = ?", "Walter"); + + assertThat(getUninterruptibly(future)).isTrue(); + } + + private void doTestStrings(Integer fetchSize, ConsistencyLevel consistencyLevel, + com.datastax.driver.core.policies.RetryPolicy retryPolicy, Consumer cqlTemplateConsumer) { + + String[] results = { "Walter", "Hank", " Jesse" }; + + when(this.session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(this.resultSet.iterator()).thenReturn(Arrays.asList(row, row, row).iterator()); + + when(this.row.getString(0)).thenReturn(results[0], results[1], results[2]); + + SettableFuture settableFuture = SettableFuture.create(); + settableFuture.set(this.preparedStatement); + + when(this.session.prepareAsync(anyString())).thenReturn(settableFuture); + + AsyncCqlTemplate template = new AsyncCqlTemplate(); + template.setSession(this.session); + + if (fetchSize != null) { + template.setFetchSize(fetchSize); + } + if (retryPolicy != null) { + template.setRetryPolicy(retryPolicy); + } + if (consistencyLevel != null) { + template.setConsistencyLevel(consistencyLevel); + } + + cqlTemplateConsumer.accept(template); + + ArgumentCaptor statementArgumentCaptor = ArgumentCaptor.forClass(Statement.class); + verify(this.session).executeAsync(statementArgumentCaptor.capture()); + + Statement statement = statementArgumentCaptor.getValue(); + + if (statement instanceof PreparedStatement || statement instanceof BoundStatement) { + + if (fetchSize != null) { + verify(statement).setFetchSize(fetchSize.intValue()); + } + + if (retryPolicy != null) { + verify(statement).setRetryPolicy(retryPolicy); + } + + if (consistencyLevel != null) { + verify(statement).setConsistencyLevel(consistencyLevel); + } + } else { + + if (fetchSize != null) { + assertThat(statement.getFetchSize()).isEqualTo(fetchSize.intValue()); + } + + if (retryPolicy != null) { + assertThat(statement.getRetryPolicy()).isEqualTo(retryPolicy); + } + + if (consistencyLevel != null) { + assertThat(statement.getConsistencyLevel()).isEqualTo(consistencyLevel); + } + } + } + + private static T getUninterruptibly(Future future) { + + try { + return future.get(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static class TestResultSetFuture extends AbstractFuture implements ResultSetFuture { + + public TestResultSetFuture() {} + + public TestResultSetFuture(ResultSet resultSet) { + set(resultSet); + } + + @Override + public boolean set(ResultSet value) { + return super.set(value); + } + + @Override + public ResultSet getUninterruptibly() { + return null; + } + + @Override + public ResultSet getUninterruptibly(long l, TimeUnit timeUnit) throws TimeoutException { + return null; + } + + @Override + protected boolean setException(Throwable throwable) { + return super.setException(throwable); + } + + /** + * Create a completed future that reports a failure given {@link Throwable}. + * + * @param throwable must not be {@literal null}. + * @return the completed/failed {@link TestResultSetFuture}. + */ + public static TestResultSetFuture failed(Throwable throwable) { + + TestResultSetFuture future = new TestResultSetFuture(); + future.setException(throwable); + return future; + } + } + + private static class TestPreparedStatementFuture extends AbstractFuture { + + public TestPreparedStatementFuture() {} + + public TestPreparedStatementFuture(PreparedStatement resultSet) { + set(resultSet); + } + + @Override + public boolean set(PreparedStatement value) { + return super.set(value); + } + } +} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateIntegrationTests.java b/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateIntegrationTests.java new file mode 100644 index 000000000..01ca49adb --- /dev/null +++ b/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateIntegrationTests.java @@ -0,0 +1,210 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; + +import com.datastax.driver.core.querybuilder.QueryBuilder; + +/** + * Integration tests for {@link CqlTemplate}. + * + * @author Mark Paluch + */ +public class CqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { + + static final AtomicBoolean initialized = new AtomicBoolean(); + CqlTemplate template; + + @Before + public void before() throws Exception { + + if (initialized.compareAndSet(false, true)) { + getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);"); + } else { + session.execute("TRUNCATE user;"); + } + + session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');"); + + template = new CqlTemplate(); + template.setSession(getSession()); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeShouldRemoveRecords() throws Exception { + + template.execute("DELETE FROM user WHERE id = 'WHITE'"); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query("SELECT id FROM user;", row -> { + result.add(row.getString(0)); + }); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject("SELECT id FROM user;", String.class); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectShouldReturnMap() throws Exception { + + Map map = template.queryForMap("SELECT * FROM user;"); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldRemoveRecords() throws Exception { + + template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE"))); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryStatementShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query(QueryBuilder.select("id").from("user"), row -> { + result.add(row.getString(0)); + }); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnMap() throws Exception { + + Map map = template.queryForMap(QueryBuilder.select().from("user")); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeWithArgsShouldRemoveRecords() throws Exception { + + template.execute("DELETE FROM user WHERE id = ?", "WHITE"); + + assertThat(session.execute("SELECT * FROM user").one()).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query("SELECT id FROM user WHERE id = ?;", row -> { + result.add(row.getString(0)); + }, "WHITE"); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception { + + List result = new ArrayList<>(); + template.query(session -> session.prepare("SELECT id FROM user WHERE id = ?;"), ps -> ps.bind("WHITE"), row -> { + result.add(row.getString(0)); + }); + + assertThat(result).contains("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception { + + String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE"); + + assertThat(id).isEqualTo("WHITE"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectWithArgsShouldReturnMap() throws Exception { + + Map map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE"); + + assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter"); + } +} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java b/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java old mode 100755 new mode 100644 index 250bd455d..0fbe254f6 --- a/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/core/CqlTemplateUnitTests.java @@ -1,561 +1,885 @@ /* - * Copyright 2016 the original author or authors + * Copyright 2016 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.cassandra.core; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; -import java.util.concurrent.TimeUnit; +import java.util.List; +import java.util.function.Consumer; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.cassandra.support.CassandraExceptionTranslator; -import org.springframework.cassandra.support.exception.CassandraReadTimeoutException; -import org.springframework.cassandra.support.exception.CassandraUncategorizedException; -import org.springframework.dao.DataAccessException; +import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; +import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.ColumnDefinitions; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; +import com.datastax.driver.core.SimpleStatement; import com.datastax.driver.core.Statement; -import com.datastax.driver.core.exceptions.DriverException; -import com.datastax.driver.core.exceptions.ReadTimeoutException; -import com.datastax.driver.core.policies.FallthroughRetryPolicy; -import com.datastax.driver.core.querybuilder.Insert; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.querybuilder.Update; -import com.datastax.driver.core.querybuilder.Using; +import com.datastax.driver.core.exceptions.InvalidQueryException; +import com.datastax.driver.core.exceptions.NoHostAvailableException; +import com.datastax.driver.core.policies.DowngradingConsistencyRetryPolicy; /** - * The CqlTemplateUnitTests class is a test suite of test cases testing the contract and functionality of the - * {@link CqlTemplate} class. - * - * @author John Blum + * Unit tests for {@link CqlTemplate}. + * * @author Mark Paluch */ -// TODO: add many more unit tests until SUT test coverage is 100%! @RunWith(MockitoJUnitRunner.class) -@SuppressWarnings("unchecked") public class CqlTemplateUnitTests { - @Rule public ExpectedException exception = ExpectedException.none(); + @Mock Session session; + @Mock ResultSet resultSet; + @Mock Row row; + @Mock PreparedStatement preparedStatement; + @Mock BoundStatement boundStatement; + @Mock ColumnDefinitions columnDefinitions; - private CqlTemplate template; - - @Mock private Insert mockInsert; - - @Mock private PreparedStatement mockPreparedStatement; - - @Mock private Session mockSession; - - @Mock private Statement mockStatement; - - @Mock private Update mockUpdate; + CqlTemplate template; @Before public void setup() { - template = new CqlTemplate(mockSession); - template.setExceptionTranslator(new CassandraExceptionTranslator()); + + this.template = new CqlTemplate(); + this.template.setSession(session); } + // ------------------------------------------------------------------------- + // Tests dealing with a plain com.datastax.driver.core.Session + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ @Test - public void doExecuteInSessionCallbackIsCalled() { - String result = template.doExecute(new SessionCallback() { - @Override - public String doInSession(Session session) throws DataAccessException { - session.execute("test"); - return "test"; - } - }); + public void executeCallbackShouldTranslateExceptions() { - assertThat(result).isEqualTo("test"); + try { + template.execute((SessionCallback) session -> { + throw new InvalidQueryException("wrong query"); + }); - verify(mockSession, times(1)).execute(eq("test")); + fail("Missing CassandraInvalidQueryException"); + } catch (CassandraInvalidQueryException e) { + assertThat(e).hasMessageContaining("wrong query"); + } } /** - * @see DATACASS-304 + * @see DATACASS-292 */ @Test - public void doExecuteInSessionCallbackTranslatesToCassandraException() { - exception.expect(CassandraReadTimeoutException.class); - exception.expectCause(org.hamcrest.Matchers.isA(ReadTimeoutException.class)); + public void executeCqlShouldTranslateExceptions() { - template.doExecute(new SessionCallback() { - @Override - public String doInSession(Session session) throws DataAccessException { - throw new ReadTimeoutException(ConsistencyLevel.ALL, 0, 1, true); + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.execute("UPDATE user SET a = 'b';"); + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query failed"); + } + } + + // ------------------------------------------------------------------------- + // Tests dealing with static CQL + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + cqlTemplate.execute("SELECT * from USERS"); + + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, cqlTemplate -> { + + cqlTemplate.execute("SELECT * from USERS"); + + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForResultSetShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + ResultSet resultSet = cqlTemplate.queryForResultSet("SELECT * from USERS"); + + assertThat(resultSet).hasSize(3); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetExtractorShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + List rows = cqlTemplate.query("SELECT * from USERS", (row, index) -> row.getString(0)); + + assertThat(rows).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetExtractorWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, cqlTemplate -> { + + List rows = cqlTemplate.query("SELECT * from USERS", (row, index) -> row.getString(0)); + + assertThat(rows).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryCqlShouldTranslateExceptions() { + + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.query("UPDATE user SET a = 'b';", ResultSet::wasApplied); + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query failed"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldBeEmpty() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + try { + template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK"); + fail("Missing IncorrectResultSizeDataAccessException"); + } catch (EmptyResultDataAccessException e) { + assertThat(e).hasMessageContaining("expected 1, actual 0"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + String result = template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK"); + assertThat(result).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldReturnNullValue() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + String result = template.queryForObject("SELECT * FROM user", (row, rowNum) -> null); + assertThat(result).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlShouldFailReturningManyRecords() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + + try { + template.queryForObject("SELECT * FROM user", (row, rowNum) -> "OK"); + fail("Missing IncorrectResultSizeDataAccessException"); + } catch (IncorrectResultSizeDataAccessException e) { + assertThat(e).hasMessageContaining("expected 1, actual 2"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectCqlWithTypeShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); + + String result = template.queryForObject("SELECT * FROM user", String.class); + + assertThat(result).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForListCqlWithTypeShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); + + List result = template.queryForList("SELECT * FROM user", String.class); + + assertThat(result).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeCqlShouldReturnWasApplied() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.wasApplied()).thenReturn(true); + + boolean applied = template.execute("UPDATE user SET a = 'b';"); + + assertThat(applied).isTrue(); + } + + // ------------------------------------------------------------------------- + // Tests dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + cqlTemplate.execute(new SimpleStatement("SELECT * from USERS")); + + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, cqlTemplate -> { + + cqlTemplate.execute(new SimpleStatement("SELECT * from USERS")); + + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForResultStatementSetShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + ResultSet resultSet = cqlTemplate.queryForResultSet(new SimpleStatement("SELECT * from USERS")); + + assertThat(resultSet).hasSize(3); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetStatementExtractorShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + List result = cqlTemplate.query(new SimpleStatement("SELECT * from USERS"), + (row, index) -> row.getString(0)); + + assertThat(result).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryWithResultSetStatementExtractorWithArgumentsShouldCallExecution() { + + doTestStrings(5, ConsistencyLevel.ONE, DowngradingConsistencyRetryPolicy.INSTANCE, cqlTemplate -> { + + List result = cqlTemplate.query(new SimpleStatement("SELECT * from USERS"), + (row, index) -> row.getString(0)); + + assertThat(result).hasSize(3).contains("Walter", "Hank", " Jesse"); + verify(session).execute(any(Statement.class)); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryStatementShouldTranslateExceptions() { + + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.query(new SimpleStatement("UPDATE user SET a = 'b';"), ResultSet::wasApplied); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query failed"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldBeEmpty() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + try { + template.queryForObject(new SimpleStatement("SELECT * FROM user"), (row, rowNum) -> "OK"); + + fail("Missing IncorrectResultSizeDataAccessException"); + } catch (IncorrectResultSizeDataAccessException e) { + assertThat(e).hasMessageContaining("expected 1, actual 0"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + String result = template.queryForObject(new SimpleStatement("SELECT * FROM user"), (row, rowNum) -> "OK"); + assertThat(result).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldReturnNullValue() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + String result = template.queryForObject(new SimpleStatement("SELECT * FROM user"), (row, rowNum) -> null); + assertThat(result).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementShouldFailReturningManyRecords() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + + try { + template.queryForObject(new SimpleStatement("SELECT * FROM user"), (row, rowNum) -> "OK"); + + fail("Missing IncorrectResultSizeDataAccessException"); + } catch (IncorrectResultSizeDataAccessException e) { + assertThat(e).hasMessageContaining("expected 1, actual 2"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectStatementWithTypeShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); + + String result = template.queryForObject(new SimpleStatement("SELECT * FROM user"), String.class); + + assertThat(result).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForListStatementWithTypeShouldReturnRecord() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); + + List result = template.queryForList(new SimpleStatement("SELECT * FROM user"), String.class); + + assertThat(result).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executeStatementShouldReturnWasApplied() { + + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.wasApplied()).thenReturn(true); + + boolean applied = template.execute(new SimpleStatement("UPDATE user SET a = 'b';")); + + assertThat(applied).isTrue(); + } + + // ------------------------------------------------------------------------- + // Methods dealing with prepared statements + // ------------------------------------------------------------------------- + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementWithCallbackShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + ResultSet resultSet = cqlTemplate.execute("SELECT * from USERS", + (PreparedStatementCallback) (ps) -> cqlTemplate.getSession().execute(ps.bind("A"))); + + try { + assertThat(resultSet).hasSize(3); + } catch (Exception e) { + fail(e.getMessage(), e); } }); } /** - * @see DATACASS-304 + * @see DATACASS-292 */ @Test - public void doExecuteInSessionCallbackTranslatesToCassandraUncategorizedException() { + public void executePreparedStatementWithCallbackShouldCallExecution() { + + doTestStrings(null, null, null, cqlTemplate -> { + + when(this.preparedStatement.bind("White")).thenReturn(this.boundStatement); + when(this.resultSet.wasApplied()).thenReturn(true); + + boolean applied = cqlTemplate.execute("UPDATE users SET name = ?", "White"); + + assertThat(applied).isTrue(); + }); + } + + /** + * @see DATACASS-292 + */ + @Test + public void executePreparedStatementCreatorShouldTranslateStatementCreationExceptions() { + + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.wasApplied()).thenReturn(true); try { - template.doExecute(new SessionCallback() { - @Override - public String doInSession(Session session) throws DataAccessException { - throw new DriverException("test"); - } + template.execute(session -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }, (ps) -> session.execute(boundStatement)); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query"); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void executePreparedStatementCreatorShouldTranslateStatementCallbackExceptions() { + + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.wasApplied()).thenReturn(true); + + try { + template.execute(session -> preparedStatement, (ps) -> { + throw new NoHostAvailableException(Collections.emptyMap()); }); - fail("Missing CassandraUncategorizedException"); - } catch (CassandraUncategorizedException e) { - assertThat(e).hasMessageContaining("test").hasRootCauseInstanceOf(DriverException.class); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasMessageContaining("tried for query"); } } /** - * @see DATACASS-304 + * @see DATACASS-292 */ @Test - public void doExecuteInSessionCallbackTranslatesToCassandraUncategorizedDataAccessException() { + public void queryPreparedStatementCreatorShouldReturnResult() { + + when(session.prepare(anyString())).thenReturn(preparedStatement); + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + Iterator iterator = template.query(session -> preparedStatement, ResultSet::iterator); + + assertThat(iterator).hasSize(1).contains(row); + verify(preparedStatement).bind(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldReturnResult() { + + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + ResultSet resultSet = template.query(session -> preparedStatement, ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + assertThat(resultSet).contains(row); + verify(preparedStatement).bind("a", "b"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderShouldTranslatePrepareStatementExceptions() { + + when(preparedStatement.bind()).thenReturn(boundStatement); try { - template.doExecute(new SessionCallback() { - @Override - public String doInSession(Session session) throws DataAccessException { - throw new Error("test"); - } - }); - fail("Missing CassandraUncategorizedException"); - } catch (CassandraUncategorizedDataAccessException e) { - assertThat(e).hasMessageContaining("test").hasCauseInstanceOf(Error.class); + template.query(session -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }, ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasCauseInstanceOf(NoHostAvailableException.class); } } + /** + * @see DATACASS-292 + */ @Test - public void doExecuteWithNullSessionCallbackThrowsIllegalArgumentException() { + public void queryPreparedStatementCreatorAndBinderShouldTranslateBindExceptions() { + + when(preparedStatement.bind()).thenReturn(boundStatement); try { - template.doExecute((SessionCallback) null); - fail("Missing IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageContaining("SessionCallback must not be null"); + template.query(session -> preparedStatement, ps -> { + throw new NoHostAvailableException(Collections.emptyMap()); + }, rs -> rs); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasCauseInstanceOf(NoHostAvailableException.class); } } - @Test - public void doExecuteQueryReturnsResultSetForOqlQueryString() { - ResultSet mockResultSet = mock(ResultSet.class); - - when(mockSession.execute(eq("SELECT * FROM Customers"))).thenReturn(mockResultSet); - - ResultSet resultSet = template.doExecuteQueryReturnResultSet("SELECT * FROM Customers"); - - assertThat(resultSet).isEqualTo(mockResultSet); - - verify(mockSession, times(1)).execute(eq("SELECT * FROM Customers")); - verifyZeroInteractions(mockResultSet); - } - - @Test - public void doExecuteSelectReturnsResultSetForOqlQueryString() { - Select mockSelect = mock(Select.class); - ResultSet mockResultSet = mock(ResultSet.class); - - when(mockSession.execute(eq(mockSelect))).thenReturn(mockResultSet); - - ResultSet resultSet = template.doExecuteQueryReturnResultSet(mockSelect); - - assertThat(resultSet).isEqualTo(mockResultSet); - - verify(mockSession, times(1)).execute(eq(mockSelect)); - verifyZeroInteractions(mockResultSet); - } - /** - * @see DATACASS-286 + * @see DATACASS-292 */ @Test - public void firstColumnToObjectReturnsColumnValue() { + public void queryPreparedStatementCreatorAndBinderShouldTranslateExecutionExceptions() { - final Row mockRow = mock(Row.class); - ColumnDefinitions mockColumnDefinitions = mock(ColumnDefinitions.class); - Iterator mockIterator = mock(Iterator.class); - final ColumnDefinitions.Definition mockColumnDefinition = mock(ColumnDefinitions.Definition.class); - - when(mockRow.getColumnDefinitions()).thenReturn(mockColumnDefinitions); - when(mockColumnDefinitions.iterator()).thenReturn(mockIterator); - when(mockIterator.hasNext()).thenReturn(true); - when(mockIterator.next()).thenReturn(mockColumnDefinition); - - template = new CqlTemplate() { - @Override - T columnToObject(Row row, ColumnDefinitions.Definition columnDefinition) { - - assertThat(row).isSameAs(mockRow); - assertThat(columnDefinition).isSameAs(mockColumnDefinition); - return (T) "test"; - } - }; - - assertThat(String.valueOf(template.firstColumnToObject(mockRow))).isEqualTo("test"); - - verify(mockRow, times(1)).getColumnDefinitions(); - verify(mockColumnDefinitions, times(1)).iterator(); - verify(mockIterator, times(1)).hasNext(); - verify(mockIterator, times(1)).next(); - verifyZeroInteractions(mockColumnDefinition); - } - - /** - * @see DATACASS-286 - */ - @Test - public void firstColumnToObjectReturnsNull() { - - Row mockRow = mock(Row.class); - ColumnDefinitions mockColumnDefinitions = mock(ColumnDefinitions.class); - Iterator mockIterator = mock(Iterator.class); - - when(mockRow.getColumnDefinitions()).thenReturn(mockColumnDefinitions); - when(mockColumnDefinitions.iterator()).thenReturn(mockIterator); - when(mockIterator.hasNext()).thenReturn(false); - - assertThat(template.firstColumnToObject(mockRow)).isNull(); - - verify(mockRow, times(1)).getColumnDefinitions(); - verify(mockColumnDefinitions, times(1)).iterator(); - verify(mockIterator, times(1)).hasNext(); - verify(mockIterator, never()).next(); - } - - /** - * @see DATACASS-286 - */ - @Test - public void processOneIsSuccessful() { - - ResultSet mockResultSet = mock(ResultSet.class); - Row mockRow = mock(Row.class); - RowMapper mockRowMapper = mock(RowMapper.class); - - when(mockResultSet.one()).thenReturn(mockRow); - when(mockResultSet.isExhausted()).thenReturn(true); - when(mockRowMapper.mapRow(eq(mockRow), eq(0))).thenReturn("test"); - - assertThat(template.processOne(mockResultSet, mockRowMapper)).isEqualTo("test"); - - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, times(1)).isExhausted(); - verify(mockRowMapper, times(1)).mapRow(eq(mockRow), eq(0)); - verifyZeroInteractions(mockRow); - } - - /** - * @see DATACASS-286 - */ - @Test - public void processOneThrowsIncorrectResultSetSizeDataAccessExceptionWhenNoRowsFound() { - - ResultSet mockResultSet = mock(ResultSet.class); - RowMapper mockRowMapper = mock(RowMapper.class); - - when(mockResultSet.one()).thenReturn(null); + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenThrow(new NoHostAvailableException(Collections.emptyMap())); try { + template.query(session -> preparedStatement, ps -> { + ps.bind("a", "b"); + return boundStatement; + }, rs -> rs); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryPreparedStatementCreatorAndBinderAndMapperShouldReturnResult() { + + when(preparedStatement.bind()).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + + List rows = template.query(session -> preparedStatement, ps -> { + ps.bind("a", "b"); + return boundStatement; + }, (row, rowNum) -> row); + + assertThat(rows).hasSize(1).contains(row); + verify(preparedStatement).bind("a", "b"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementShouldBeEmpty() { + + when(session.prepare("SELECT * FROM user WHERE username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + try { + template.queryForObject("SELECT * FROM user WHERE username = ?", (row, rowNum) -> "OK", "Walter"); - template.processOne(mockResultSet, mockRowMapper); fail("Missing IncorrectResultSizeDataAccessException"); } catch (IncorrectResultSizeDataAccessException e) { assertThat(e).hasMessageContaining("expected 1, actual 0"); - } finally { - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, never()).isExhausted(); - verifyZeroInteractions(mockRowMapper); } } /** - * @see DATACASS-286 + * @see DATACASS-292 */ @Test - public void processOneThrowsIncorrectResultSetSizeDataAccessExceptionWhenTooManyRowsFound() { + public void queryForObjectPreparedStatementShouldReturnRecord() { - ResultSet mockResultSet = mock(ResultSet.class); - Row mockRow = mock(Row.class); - RowMapper mockRowMapper = mock(RowMapper.class); + when(session.prepare("SELECT * FROM user WHERE username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); - when(mockResultSet.one()).thenReturn(mockRow); - when(mockResultSet.isExhausted()).thenReturn(false); + String result = template.queryForObject("SELECT * FROM user WHERE username = ?", (row, rowNum) -> "OK", "Walter"); + assertThat(result).isEqualTo("OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void queryForObjectPreparedStatementShouldFailReturningManyRecords() { + + when(session.prepare("SELECT * FROM user WHERE username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); try { - template.processOne(mockResultSet, mockRowMapper); + template.queryForObject("SELECT * FROM user WHERE username = ?", (row, rowNum) -> "OK", "Walter"); + fail("Missing IncorrectResultSizeDataAccessException"); } catch (IncorrectResultSizeDataAccessException e) { - assertThat(e).hasMessage("ResultSet size exceeds 1"); - } finally { - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, times(1)).isExhausted(); - verifyZeroInteractions(mockRowMapper); - verifyZeroInteractions(mockRow); + assertThat(e).hasMessageContaining("expected 1, actual 2"); } } /** - * @see DATACASS-286 + * @see DATACASS-292 */ @Test - public void processOnePassingNullResultSetThrowsIllegalArgumentException() { + public void queryForObjectPreparedStatementWithTypeShouldReturnRecord() { - RowMapper mockRowMapper = mock(RowMapper.class); + when(session.prepare("SELECT * FROM user WHERE username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK"); - try { - exception.expect(IllegalArgumentException.class); - template.processOne(null, mockRowMapper); - } finally { - verifyZeroInteractions(mockRowMapper); - } + String result = template.queryForObject("SELECT * FROM user WHERE username = ?", String.class, "Walter"); + + assertThat(result).isEqualTo("OK"); } /** - * @see DATACASS-286 + * @see DATACASS-292 */ @Test - public void processOneWithRequiredTypeIsSuccessful() { + public void queryForListPreparedStatementWithTypeShouldReturnRecord() { - ResultSet mockResultSet = mock(ResultSet.class); - final Row mockRow = mock(Row.class); + when(session.prepare("SELECT * FROM user WHERE username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.iterator()).thenReturn(Arrays.asList(row, row).iterator()); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + when(columnDefinitions.size()).thenReturn(1); + when(row.getString(0)).thenReturn("OK", "NOT OK"); - when(mockResultSet.one()).thenReturn(mockRow); - when(mockResultSet.isExhausted()).thenReturn(true); + List result = template.queryForList("SELECT * FROM user WHERE username = ?", String.class, "Walter"); - template = new CqlTemplate() { - @Override - protected Object firstColumnToObject(Row row) { - assertThat(row).isEqualTo(mockRow); - return 1L; + assertThat(result).contains("OK", "NOT OK"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updatePreparedStatementShouldReturnApplied() { + + when(session.prepare("UPDATE user SET username = ?")).thenReturn(preparedStatement); + when(preparedStatement.bind("Walter")).thenReturn(boundStatement); + when(session.execute(boundStatement)).thenReturn(resultSet); + when(resultSet.wasApplied()).thenReturn(true); + + boolean applied = template.execute("UPDATE user SET username = ?", "Walter"); + + assertThat(applied).isTrue(); + } + + + private void doTestStrings(Integer fetchSize, ConsistencyLevel consistencyLevel, + com.datastax.driver.core.policies.RetryPolicy retryPolicy, Consumer cqlTemplateConsumer) { + + String[] results = { "Walter", "Hank", " Jesse" }; + + when(this.session.execute(any(Statement.class))).thenReturn(resultSet); + when(this.resultSet.iterator()).thenReturn(Arrays.asList(row, row, row).iterator()); + + when(this.row.getString(0)).thenReturn(results[0], results[1], results[2]); + when(this.session.prepare(anyString())).thenReturn(preparedStatement); + + CqlTemplate template = new CqlTemplate(); + template.setSession(this.session); + + if (fetchSize != null) { + template.setFetchSize(fetchSize); + } + if (retryPolicy != null) { + template.setRetryPolicy(retryPolicy); + } + if (consistencyLevel != null) { + template.setConsistencyLevel(consistencyLevel); + } + + cqlTemplateConsumer.accept(template); + + ArgumentCaptor statementArgumentCaptor = ArgumentCaptor.forClass(Statement.class); + verify(this.session).execute(statementArgumentCaptor.capture()); + + Statement statement = statementArgumentCaptor.getValue(); + + if (statement instanceof PreparedStatement || statement instanceof BoundStatement) { + + if (fetchSize != null) { + verify(statement).setFetchSize(fetchSize.intValue()); } - }; - Number value = template.processOne(mockResultSet, Long.class); + if (retryPolicy != null) { + verify(statement).setRetryPolicy(retryPolicy); + } - assertThat(value).isInstanceOf(Long.class).isEqualTo(1L); + if (consistencyLevel != null) { + verify(statement).setConsistencyLevel(consistencyLevel); + } + } else { - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, times(1)).isExhausted(); - verifyZeroInteractions(mockRow); - } + if (fetchSize != null) { + assertThat(statement.getFetchSize()).isEqualTo(fetchSize.intValue()); + } - /** - * @see DATACASS-286 - */ - @Test - public void processOneWithRequiredTypeThrowsIncorrectResultSetSizeDataAccessExceptionWhenNoRowsFound() { + if (retryPolicy != null) { + assertThat(statement.getRetryPolicy()).isEqualTo(retryPolicy); + } - ResultSet mockResultSet = mock(ResultSet.class); - - when(mockResultSet.one()).thenReturn(null); - - try { - template.processOne(mockResultSet, Integer.class); - fail("Missing IncorrectResultSizeDataAccessException"); - } catch (IncorrectResultSizeDataAccessException e) { - assertThat(e).hasMessageContaining("expected 1, actual 0"); - } finally { - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, never()).isExhausted(); + if (consistencyLevel != null) { + assertThat(statement.getConsistencyLevel()).isEqualTo(consistencyLevel); + } } } - - /** - * @see DATACASS-286 - */ - @Test - public void processOneWithRequiredTypeThrowsIncorrectResultSetSizeDataAccessExceptionWhenTooManyRowsFound() { - - ResultSet mockResultSet = mock(ResultSet.class); - Row mockRow = mock(Row.class); - - when(mockResultSet.one()).thenReturn(mockRow); - when(mockResultSet.isExhausted()).thenReturn(false); - - try { - template.processOne(mockResultSet, Double.class); - fail("Missing IncorrectResultSizeDataAccessException"); - - } catch (IncorrectResultSizeDataAccessException e) { - assertThat(e).hasMessageContaining("ResultSet size exceeds 1"); - } finally { - verify(mockResultSet, times(1)).one(); - verify(mockResultSet, times(1)).isExhausted(); - verifyZeroInteractions(mockRow); - } - } - - /** - * @see DATACASS-286 - */ - @Test - public void processOneWithRequiredTypePassingNullResultSetThrowsIllegalArgumentException() { - exception.expect(IllegalArgumentException.class); - - template.processOne(null, String.class); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addPreparedStatementOptionsShouldAddDriverQueryOptions() { - - QueryOptions queryOptions = QueryOptions.builder() // - .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // - .retryPolicy(FallthroughRetryPolicy.INSTANCE) // - .build(); - - template.addPreparedStatementOptions(mockPreparedStatement, queryOptions); - - verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); - verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addPreparedStatementOptionsShouldAddOurQueryOptions() { - - QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build(); - - queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM); - - template.addPreparedStatementOptions(mockPreparedStatement, queryOptions); - - verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addStatementQueryOptionsShouldAddDriverQueryOptions() { - - QueryOptions queryOptions = QueryOptions.builder().consistencyLevel(ConsistencyLevel.EACH_QUORUM) // - .retryPolicy(FallthroughRetryPolicy.INSTANCE) // - .build(); - - template.addQueryOptions(mockStatement, queryOptions); - - verify(mockStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); - verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addStatementQueryOptionsShouldAddOurQueryOptions() { - - QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build(); - - queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM); - - template.addQueryOptions(mockStatement, queryOptions); - - verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - verify(mockStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addStatementQueryOptionsShouldNotAddOptions() { - - QueryOptions queryOptions = QueryOptions.builder().build(); - - template.addQueryOptions(mockStatement, queryOptions); - - verifyZeroInteractions(mockStatement); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addStatementQueryOptionsShouldAddGenericQueryOptions() { - - QueryOptions queryOptions = QueryOptions.builder() // - .fetchSize(10) // - .readTimeout(1, TimeUnit.MINUTES) // - .withTracing() // - .build(); - - template.addQueryOptions(mockStatement, queryOptions); - - verify(mockStatement).setReadTimeoutMillis(60 * 1000); - verify(mockStatement).setFetchSize(10); - verify(mockStatement).enableTracing(); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addInsertWriteOptionsShouldAddDriverQueryOptions() { - - WriteOptions writeOptions = WriteOptions.builder() // - .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // - .retryPolicy(FallthroughRetryPolicy.INSTANCE) // - .readTimeout(10) // - .ttl(10) // - .build(); - - template.addWriteOptions(mockInsert, writeOptions); - - verify(mockInsert).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); - verify(mockInsert).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - verify(mockInsert).setReadTimeoutMillis(10); - verify(mockInsert).using(Mockito.any(Using.class)); - } - - /** - * @see DATACASS-202 - */ - @Test - public void addUpdateWriteOptionsShouldAddDriverQueryOptions() { - - WriteOptions writeOptions = WriteOptions.builder() // - .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // - .retryPolicy(FallthroughRetryPolicy.INSTANCE) // - .ttl(10) // - .tracing(false).build(); - - template.addWriteOptions(mockUpdate, writeOptions); - - verify(mockUpdate).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); - verify(mockUpdate).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); - verify(mockUpdate).using(Mockito.any(Using.class)); - verify(mockUpdate).disableTracing(); - } } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/core/QueryOptionsUtilUnitTests.java b/spring-cql/src/test/java/org/springframework/cassandra/core/QueryOptionsUtilUnitTests.java new file mode 100755 index 000000000..e84b2963d --- /dev/null +++ b/spring-cql/src/test/java/org/springframework/cassandra/core/QueryOptionsUtilUnitTests.java @@ -0,0 +1,194 @@ +/* + * Copyright 2016 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.core; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import com.datastax.driver.core.ConsistencyLevel; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.policies.FallthroughRetryPolicy; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.Update; +import com.datastax.driver.core.querybuilder.Using; + +/** + * Unit tests for {@link QueryOptionsUtil}. + * + * @author John Blum + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("unchecked") +public class QueryOptionsUtilUnitTests { + + @Rule public ExpectedException exception = ExpectedException.none(); + + @Mock Insert mockInsert; + @Mock PreparedStatement mockPreparedStatement; + @Mock Session mockSession; + @Mock Statement mockStatement; + @Mock Update mockUpdate; + + /** + * @see DATACASS-202 + */ + @Test + public void addPreparedStatementOptionsShouldAddDriverQueryOptions() { + + QueryOptions queryOptions = QueryOptions.builder() // + .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // + .retryPolicy(FallthroughRetryPolicy.INSTANCE) // + .build(); + + QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions); + + verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); + verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addPreparedStatementOptionsShouldAddOurQueryOptions() { + + QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build(); + + queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM); + + QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions); + + verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addStatementQueryOptionsShouldAddDriverQueryOptions() { + + QueryOptions queryOptions = QueryOptions.builder().consistencyLevel(ConsistencyLevel.EACH_QUORUM) // + .retryPolicy(FallthroughRetryPolicy.INSTANCE) // + .build(); + + QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions); + + verify(mockStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); + verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addStatementQueryOptionsShouldAddOurQueryOptions() { + + QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build(); + + queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM); + + QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions); + + verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + verify(mockStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addStatementQueryOptionsShouldNotAddOptions() { + + QueryOptions queryOptions = QueryOptions.builder().build(); + + QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions); + + verifyZeroInteractions(mockStatement); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addStatementQueryOptionsShouldAddGenericQueryOptions() { + + QueryOptions queryOptions = QueryOptions.builder() // + .fetchSize(10) // + .readTimeout(1, TimeUnit.MINUTES) // + .withTracing() // + .build(); + + QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions); + + verify(mockStatement).setReadTimeoutMillis(60 * 1000); + verify(mockStatement).setFetchSize(10); + verify(mockStatement).enableTracing(); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addInsertWriteOptionsShouldAddDriverQueryOptions() { + + WriteOptions writeOptions = WriteOptions.builder() // + .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // + .retryPolicy(FallthroughRetryPolicy.INSTANCE) // + .readTimeout(10) // + .ttl(10) // + .build(); + + QueryOptionsUtil.addWriteOptions(mockInsert, writeOptions); + + verify(mockInsert).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); + verify(mockInsert).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + verify(mockInsert).setReadTimeoutMillis(10); + verify(mockInsert).using(Mockito.any(Using.class)); + } + + /** + * @see DATACASS-202 + */ + @Test + public void addUpdateWriteOptionsShouldAddDriverQueryOptions() { + + WriteOptions writeOptions = WriteOptions.builder() // + .consistencyLevel(ConsistencyLevel.EACH_QUORUM) // + .retryPolicy(FallthroughRetryPolicy.INSTANCE) // + .ttl(10) // + .tracing(false).build(); + + QueryOptionsUtil.addWriteOptions(mockUpdate, writeOptions); + + verify(mockUpdate).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM); + verify(mockUpdate).setRetryPolicy(FallthroughRetryPolicy.INSTANCE); + verify(mockUpdate).using(Mockito.any(Using.class)); + verify(mockUpdate).disableTracing(); + } +} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java index 67dda41fd..5a13a8833 100755 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractEmbeddedCassandraIntegrationTest.java @@ -49,8 +49,8 @@ public abstract class AbstractEmbeddedCassandraIntegrationTest { @Rule public final CassandraRule cassandraRule = cassandraEnvironment.testInstance() .before(new SessionCallback() { @Override - public Object doInSession(Session s) throws DataAccessException { - AbstractEmbeddedCassandraIntegrationTest.this.cluster = s.getCluster(); + public Object doInSession(Session session) throws DataAccessException { + AbstractEmbeddedCassandraIntegrationTest.this.cluster = session.getCluster(); return null; } }); diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractKeyspaceCreatingIntegrationTest.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractKeyspaceCreatingIntegrationTest.java index 9579b99df..763b2b463 100755 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractKeyspaceCreatingIntegrationTest.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/AbstractKeyspaceCreatingIntegrationTest.java @@ -71,10 +71,10 @@ public abstract class AbstractKeyspaceCreatingIntegrationTest extends AbstractEm cassandraRule.before(new SessionCallback() { @Override - public Object doInSession(Session s) throws DataAccessException { + public Object doInSession(Session session) throws DataAccessException { - if (!keyspace.equals(s.getLoggedKeyspace())) { - s.execute(String.format("USE %s;", keyspace)); + if (!keyspace.equals(session.getLoggedKeyspace())) { + session.execute(String.format("USE %s;", keyspace)); } return null; } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/CassandraRule.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/CassandraRule.java index 79b57d804..4bbd810ba 100644 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/CassandraRule.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/CassandraRule.java @@ -129,8 +129,8 @@ public class CassandraRule extends ExternalResource { SessionCallback sessionCallback = new SessionCallback() { @Override - public Void doInSession(Session s) throws DataAccessException { - load(s, cqlDataSet); + public Void doInSession(Session session) throws DataAccessException { + load(session, cqlDataSet); return null; } }; @@ -180,8 +180,8 @@ public class CassandraRule extends ExternalResource { after.add(new SessionCallback() { @Override - public Void doInSession(Session s) throws DataAccessException { - load(session, cqlDataSet); + public Void doInSession(Session session) throws DataAccessException { + load(CassandraRule.this.session, cqlDataSet); return null; } }); diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/KeyspaceRule.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/KeyspaceRule.java index 7c7984950..38956c736 100644 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/KeyspaceRule.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/KeyspaceRule.java @@ -78,7 +78,7 @@ public class KeyspaceRule extends ExternalResource { } else { cassandraRule.before(new SessionCallback() { @Override - public Object doInSession(Session s) throws DataAccessException { + public Object doInSession(Session session) throws DataAccessException { KeyspaceRule.this.session = cassandraRule.getSession(); return null; } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/CqlOperationsIntegrationTests.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/CqlOperationsIntegrationTests.java deleted file mode 100755 index df397f7c8..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/CqlOperationsIntegrationTests.java +++ /dev/null @@ -1,1231 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.core; - -import static org.assertj.core.api.Assertions.*; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -import org.junit.Before; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cassandra.core.*; -import org.springframework.cassandra.core.keyspace.CreateTableSpecification; -import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.util.CollectionUtils; - -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.DataType; -import com.datastax.driver.core.Host; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.exceptions.DriverException; -import com.datastax.driver.core.querybuilder.Insert; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Truncate; - -/** - * Integration tests for {@link CqlOperations}. - * - * @author David Webb - * @author Oliver Gierke - * @author Mark Paluch - */ -public class CqlOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { - - private static final String BOOK_INSERT = "insert into book (isbn, title, author, pages) values (?, ?, ?, ?)"; - - private static Logger log = LoggerFactory.getLogger(CqlOperationsIntegrationTests.class); - - private CqlOperations cqlTemplate; - - /* - * Objects used for test data - */ - final String ISBN_NINES = "999999999"; - final String TITLE_NINES = "Book of Nines"; - final Object[] o1 = new Object[] { "1234", "Moby Dick", "Herman Manville", new Integer(456) }; - final Object[] o2 = new Object[] { "2345", "War and Peace", "Russian Dude", new Integer(456) }; - final Object[] o3 = new Object[] { "3456", "Jane Ayre", "Charlotte", new Integer(456) }; - - @Before - public void setupTemplate() { - - execute("cassandraOperationsTest-cql-dataload.cql", this.keyspace); - this.cqlTemplate = new CqlTemplate(session); - } - - @Test - public void ringTest() { - - List ring = cqlTemplate.describeRing(); - - /* - * There must be 1 node in the cluster if the embedded server is - * running. - */ - assertThat(ring).isNotNull(); - } - - @Test - public void hostMapperTest() { - - List ring = (List) cqlTemplate.describeRing(new HostMapper() { - - @Override - public Collection mapHosts(Set host) throws DriverException { - - List list = new LinkedList(); - - for (Host h : host) { - MyHost mh = new MyHost(); - mh.someName = h.getAddress().getCanonicalHostName(); - list.add(mh); - } - - return list; - } - - }); - - assertThat(ring).isNotNull(); - assertThat(ring.size() > 0).isTrue(); - - for (MyHost h : ring) { - log.info("hostMapperTest Host -> " + h.someName); - } - - } - - @Test - @SuppressWarnings("unchecked") - public void ingestionTestListOfList() { - - WriteOptions options = new WriteOptions(); - options.setTtl(360); - - String cql = BOOK_INSERT; - - List> values = new LinkedList>(); - - values.add(new LinkedList(CollectionUtils.arrayToList(o1))); - values.add(new LinkedList(CollectionUtils.arrayToList(o2))); - values.add(new LinkedList(CollectionUtils.arrayToList(o3))); - - cqlTemplate.ingest(cql, values, options); - - // Assert that the rows were inserted into Cassandra - Book b1 = getBookWithRetry((String) o1[0]); - Book b2 = getBookWithRetry((String) o2[0]); - Book b3 = getBookWithRetry((String) o3[0]); - - assertBook(b1, objectToBook(o1)); - assertBook(b2, objectToBook(o2)); - assertBook(b3, objectToBook(o3)); - } - - /** - * Insert some Books needed to next test steps. - */ - private void insertTestObjectArray() { - - String cql = BOOK_INSERT; - - Object[][] values = new Object[3][]; - values[0] = o1; - values[1] = o2; - values[2] = o3; - - PreparedStatement pstmt = this.session.prepare(cql); - BoundStatement binder = null; - for (Object[] o : values) { - binder = pstmt.bind(o); - cqlTemplate.execute(binder); - } - - // Assert that the rows were inserted into Cassandra - Book b1 = getBook("1234"); - Book b2 = getBook("2345"); - Book b3 = getBook("3456"); - - assertBook(b1, objectToBook(o1)); - assertBook(b2, objectToBook(o2)); - assertBook(b3, objectToBook(o3)); - } - - @Test - public void ingestTestObjectArray() { - - String cql = BOOK_INSERT; - - Object[][] values = new Object[3][]; - values[0] = o1; - values[1] = o2; - values[2] = o3; - - cqlTemplate.ingest(cql, values); - - // Assert that the rows were inserted into Cassandra - Book b1 = getBookWithRetry((String) o1[0]); - Book b2 = getBookWithRetry((String) o2[0]); - Book b3 = getBookWithRetry((String) o3[0]); - - assertBook(b1, objectToBook(o1)); - assertBook(b2, objectToBook(o2)); - assertBook(b3, objectToBook(o3)); - - } - - /** - * This is an implementation of RowIterator for the purposes of testing passing your own Impl to CqlTemplate - * - * @author David Webb - */ - final class MyRowIterator implements RowIterator { - - private Object[][] values; - - public MyRowIterator(Object[][] values) { - this.values = values; - } - - int index = 0; - - /* (non-Javadoc) - * @see org.springframework.cassandra.core.RowIterator#next() - */ - @Override - public Object[] next() { - return values[index++]; - } - - /* (non-Javadoc) - * @see org.springframework.cassandra.core.RowIterator#hasNext() - */ - @Override - public boolean hasNext() { - return index < values.length; - } - - } - - @Test - public void ingestionTestRowIterator() { - - String cql = BOOK_INSERT; - - final Object[][] v = new Object[3][]; - v[0] = o1; - v[1] = o2; - v[2] = o3; - RowIterator ri = new MyRowIterator(v); - - cqlTemplate.ingest(cql, ri); - - // Assert that the rows were inserted into Cassandra - Book b1 = getBookWithRetry((String) o1[0]); - Book b2 = getBookWithRetry((String) o2[0]); - Book b3 = getBookWithRetry((String) o3[0]); - - assertBook(b1, objectToBook(o1)); - assertBook(b2, objectToBook(o2)); - assertBook(b3, objectToBook(o3)); - - } - - @Test - public void executeTestSessionCallback() { - - final String isbn = UUID.randomUUID().toString(); - final String title = "Spring Data Cassandra Cookbook"; - final String author = "David Webb"; - final Integer pages = 1; - - cqlTemplate.execute(new SessionCallback() { - - @Override - public Object doInSession(Session s) throws DataAccessException { - - String cql = BOOK_INSERT; - - PreparedStatement ps = s.prepare(cql); - BoundStatement bs = ps.bind(isbn, title, author, pages); - - s.execute(bs); - - return null; - - } - }); - - Book b = getBook(isbn); - - assertBook(b, isbn, title, author, pages); - - } - - @Test - public void executeTestCqlString() { - - final String isbn = UUID.randomUUID().toString(); - final String title = "Spring Data Cassandra Cookbook"; - final String author = "David Webb"; - final Integer pages = 1; - - cqlTemplate.execute("insert into book (isbn, title, author, pages) values ('" + isbn + "', '" + title + "', '" - + author + "', " + pages + ")"); - - Book b = getBook(isbn); - - assertBook(b, isbn, title, author, pages); - - } - - @Test - public void executeAsynchronouslyTestCqlString() { - - final String isbn = UUID.randomUUID().toString(); - final String title = "Spring Data Cassandra Cookbook"; - final String author = "David Webb"; - final Integer pages = 1; - - cqlTemplate.executeAsynchronously("insert into book (isbn, title, author, pages) values ('" + isbn + "', '" + title - + "', '" + author + "', " + pages + ")"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - Book b = getBook(isbn); - - assertBook(b, isbn, title, author, pages); - - } - - @Test - public void queryTestCqlStringResultSetExtractor() { - - final String isbn = "999999999"; - - Book b1 = cqlTemplate.query("select * from book where isbn='" + isbn + "'", new ResultSetExtractor() { - - @Override - public Book extractData(ResultSet rs) throws DriverException, DataAccessException { - Row r = rs.one(); - assertThat(r).isNotNull(); - - Book b = rowToBook(r); - - return b; - } - }); - - Book b2 = getBook(isbn); - - assertBook(b1, b2); - - } - - @Test - public void queryAsynchronouslyTestCqlStringResultSetExtractor() { - - final String isbn = "999999999"; - - Book b1 = cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", - - new ResultSetExtractor() { - - @Override - public Book extractData(ResultSet rs) throws DriverException, DataAccessException { - Row r = rs.one(); - assertThat(r).isNotNull(); - - Book b = rowToBook(r); - - return b; - } - }, 60l, TimeUnit.SECONDS); - - Book b2 = getBook(isbn); - - assertBook(b1, b2); - - } - - @Test - public void queryAsynchronouslyTestCqlStringResultSetExtractorWithOptions() { - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DEFAULT); - - final String isbn = "999999999"; - - Book b1 = cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", - - new ResultSetExtractor() { - - @Override - public Book extractData(ResultSet rs) throws DriverException, DataAccessException { - Row r = rs.one(); - assertThat(r).isNotNull(); - - Book b = rowToBook(r); - - return b; - } - }, 60l, TimeUnit.SECONDS, options); - - Book b2 = getBook(isbn); - - assertBook(b1, b2); - - } - - @Test - public void queryAsynchronouslyWithListener() throws InterruptedException { - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DEFAULT); - - final String isbn = "999999999"; - - BookListener listener = new BookListener(); - cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", listener); - listener.await(); - - Book book2 = getBook(isbn); - assertBook(listener.getBook(), book2); - } - - @Test - public void queryAsynchronouslyWithListenerAndExecutor() throws InterruptedException { - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DEFAULT); - - final String isbn = "999999999"; - - BookListener listener = new BookListener(); - - cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", listener, new Executor() { - - @Override - public void execute(Runnable command) { - command.run(); - } - }); - listener.await(); - - Book book2 = getBook(isbn); - assertBook(listener.getBook(), book2); - } - - @Test - public void queryAsynchronouslyWithListenerAndExecutorAndOptions() throws InterruptedException { - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DEFAULT); - - final String isbn = "999999999"; - - BookListener listener = new BookListener(); - - cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", listener, options, new Executor() { - - @Override - public void execute(Runnable command) { - command.run(); - } - }); - listener.await(); - - Book book2 = getBook(isbn); - assertBook(listener.getBook(), book2); - } - - @Test - public void queryTestCqlStringRowCallbackHandler() { - - final String isbn = "999999999"; - - final Book b1 = getBook(isbn); - - cqlTemplate.query("select * from book where isbn='" + isbn + "'", new RowCallbackHandler() { - - @Override - public void processRow(Row row) throws DriverException { - - assertThat(row).isNotNull(); - Book b = rowToBook(row); - assertBook(b1, b); - - } - }); - - } - - @Test - public void processTestResultSetRowCallbackHandlerWithAsyncOptions() { - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DEFAULT); - - final String isbn = "999999999"; - - final Book b1 = getBook(isbn); - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn='" + isbn + "'", options); - ResultSet rs = rsf.getUninterruptibly(); - - assertThat(rs).isNotNull(); - - cqlTemplate.process(rs, new RowCallbackHandler() { - - @Override - public void processRow(Row row) throws DriverException { - - assertThat(row).isNotNull(); - Book b = rowToBook(row); - assertBook(b1, b); - } - - }); - } - - @Test - public void queryTestCqlStringRowMapper() { - - // Insert our 3 test books. - insertTestObjectArray(); - - List books = cqlTemplate.query("select * from book where isbn in ('1234','2345','3456')", - new RowMapper() { - - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - Book b = rowToBook(row); - return b; - } - }); - - assertThat(3).isEqualTo(books.size()); - assertBook(books.get(0), getBook(books.get(0).getIsbn())); - assertBook(books.get(1), getBook(books.get(1).getIsbn())); - assertBook(books.get(2), getBook(books.get(2).getIsbn())); - } - - @Test - public void processTestResultSetRowMapper() { - - // Insert our 3 test books. - insertTestObjectArray(); - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn in ('1234','2345','3456')"); - ResultSet rs = rsf.getUninterruptibly(); - - assertThat(rs).isNotNull(); - - List books = cqlTemplate.process(rs, new RowMapper() { - - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - Book b = rowToBook(row); - return b; - } - }); - - assertThat(3).isEqualTo(books.size()); - assertBook(books.get(0), getBook(books.get(0).getIsbn())); - assertBook(books.get(1), getBook(books.get(1).getIsbn())); - assertBook(books.get(2), getBook(books.get(2).getIsbn())); - - } - - @Test - public void queryForObjectTestCqlStringRowMapper() { - - Book book = cqlTemplate.queryForObject("select * from book where isbn in ('" + ISBN_NINES + "')", - new RowMapper() { - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - Book b = rowToBook(row); - return b; - } - }); - - assertThat(book).isNotNull(); - assertBook(book, getBook(ISBN_NINES)); - } - - /** - * Test that CQL for QueryForObject must only return 1 row or an IllegalArgumentException is thrown. - */ - @Test(expected = IncorrectResultSizeDataAccessException.class) - public void queryForObjectTestCqlStringRowMapperNotOneRowReturned() { - - // Insert our 3 test books. - insertTestObjectArray(); - - @SuppressWarnings("unused") - Book book = cqlTemplate.queryForObject("SELECT * FROM book WHERE isbn IN('1234','2345','3456')", - new RowMapper() { - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - return rowToBook(row); - } - }); - } - - @Test - public void processOneTestResultSetRowMapper() { - - // Insert our 3 test books. - insertTestObjectArray(); - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn in ('" + ISBN_NINES + "')"); - - ResultSet rs = rsf.getUninterruptibly(); - assertThat(rs).isNotNull(); - - Book book = cqlTemplate.processOne(rs, new RowMapper() { - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - Book b = rowToBook(row); - return b; - } - }); - - assertThat(book).isNotNull(); - assertBook(book, getBook(ISBN_NINES)); - } - - @Test - public void quertForObjectTestCqlStringRequiredType() { - - String title = cqlTemplate.queryForObject("select title from book where isbn in ('" + ISBN_NINES + "')", - String.class); - - assertThat(TITLE_NINES).isEqualTo(title); - - } - - @Test(expected = ClassCastException.class) - public void queryForObjectTestCqlStringRequiredTypeInvalid() { - - @SuppressWarnings("unused") - Float title = cqlTemplate.queryForObject("select title from book where isbn in ('" + ISBN_NINES + "')", - Float.class); - - } - - @Test - public void processOneTestResultSetType() { - - ResultSetFuture rsf = cqlTemplate - .queryAsynchronously("select title from book where isbn in ('" + ISBN_NINES + "')"); - - ResultSet rs = rsf.getUninterruptibly(); - assertThat(rs).isNotNull(); - - String title = cqlTemplate.processOne(rs, String.class); - - assertThat(title).isNotNull(); - assertThat(TITLE_NINES).isEqualTo(title); - } - - @Test - public void queryForMapTestCqlString() { - - Map rsMap = cqlTemplate.queryForMap("select * from book where isbn in ('" + ISBN_NINES + "')"); - - Book b1 = objectToBook(rsMap.get("isbn"), rsMap.get("title"), rsMap.get("author"), rsMap.get("pages")); - Book b2 = getBook(ISBN_NINES); - - assertBook(b1, b2); - } - - @Test - public void processMapTestResultSet() { - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn in ('" + ISBN_NINES + "')"); - - ResultSet rs = rsf.getUninterruptibly(); - assertThat(rs).isNotNull(); - - Map rsMap = cqlTemplate.processMap(rs); - - Book b1 = objectToBook(rsMap.get("isbn"), rsMap.get("title"), rsMap.get("author"), rsMap.get("pages")); - Book b2 = getBook(ISBN_NINES); - - assertBook(b1, b2); - } - - @Test - public void queryForListTestCqlStringType() { - - // Insert our 3 test books. - insertTestObjectArray(); - - List titles = cqlTemplate.queryForList("select title from book where isbn in ('1234','2345','3456')", - String.class); - - assertThat(titles).isNotNull(); - assertThat(3).isEqualTo(titles.size()); - } - - @Test - public void processListTestResultSetType() { - - // Insert our 3 test books. - insertTestObjectArray(); - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn in ('1234','2345','3456')"); - ResultSet rs = rsf.getUninterruptibly(); - - assertThat(rs).isNotNull(); - - List titles = cqlTemplate.processList(rs, String.class); - assertThat(titles).isNotNull(); - assertThat(3).isEqualTo(titles.size()); - } - - @Test - public void queryForListOfMapCqlString() { - - // Insert our 3 test books. - insertTestObjectArray(); - - List> results = cqlTemplate - .queryForListOfMap("select * from book where isbn in ('1234','2345','3456')"); - - assertThat(3).isEqualTo(results.size()); - - } - - @Test - public void processListOfMapTestResultSet() { - - // Insert our 3 test books. - insertTestObjectArray(); - - ResultSetFuture rsf = cqlTemplate.queryAsynchronously("select * from book where isbn in ('1234','2345','3456')"); - - ResultSet rs = rsf.getUninterruptibly(); - - assertThat(rs).isNotNull(); - - List> results = cqlTemplate.processListOfMap(rs); - - assertThat(3).isEqualTo(results.size()); - - } - - @Test - public void executeTestCqlStringPreparedStatementCallback() { - - String cql = BOOK_INSERT; - - BoundStatement statement = cqlTemplate.execute(cql, new PreparedStatementCallback() { - - @Override - public BoundStatement doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException { - BoundStatement bs = ps.bind(); - return bs; - } - }); - - assertThat(statement).isNotNull(); - - } - - @Test - public void executeTestPreparedStatementCreatorPreparedStatementCallback() { - - final String cql = BOOK_INSERT; - - BoundStatement statement = cqlTemplate.execute(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new PreparedStatementCallback() { - - @Override - public BoundStatement doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException { - BoundStatement bs = ps.bind(); - return bs; - } - }); - - assertThat(statement).isNotNull(); - - } - - @Test - public void queryTestCqlStringPreparedStatementBinderResultSetExtractor() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - Book b1 = cqlTemplate.query(cql, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new ResultSetExtractor() { - - @Override - public Book extractData(ResultSet rs) throws DriverException, DataAccessException { - Row r = rs.one(); - assertThat(r).isNotNull(); - - Book b = rowToBook(r); - - return b; - } - }); - - Book b2 = getBook(isbn); - - assertBook(b1, b2); - } - - @Test - public void queryTestCqlStringPreparedStatementBinderRowCallbackHandler() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - cqlTemplate.query(cql, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new RowCallbackHandler() { - - @Override - public void processRow(Row row) throws DriverException { - - Book b = rowToBook(row); - - Book b2 = getBook(isbn); - - assertBook(b, b2); - - } - }); - - } - - @Test - public void queryTestCqlStringPreparedStatementBinderRowMapper() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - List books = cqlTemplate.query(cql, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new RowMapper() { - - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - return rowToBook(row); - } - }); - - Book b2 = getBook(isbn); - - assertThat(1).isEqualTo(books.size()); - assertBook(books.get(0), b2); - } - - @Test - public void queryTestPreparedStatementCreatorResultSetExtractor() { - - insertTestObjectArray(); - - final String cql = "select * from book"; - - List books = cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new ResultSetExtractor>() { - - @Override - public List extractData(ResultSet rs) throws DriverException, DataAccessException { - - List books = new LinkedList(); - - for (Row row : rs.all()) { - books.add(rowToBook(row)); - } - - return books; - } - }); - - assertThat(books.size() > 0).isTrue(); - } - - @Test - public void queryTestPreparedStatementCreatorRowCallbackHandler() { - - insertTestObjectArray(); - - final String cql = "select * from book"; - - cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new RowCallbackHandler() { - - @Override - public void processRow(Row row) throws DriverException { - - rowToBook(row); - } - }); - - } - - @Test - public void queryTestPreparedStatementCreatorRowMapper() { - - insertTestObjectArray(); - - final String cql = "select * from book"; - - List books = cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new RowMapper() { - - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - return rowToBook(row); - } - }); - - assertThat(books.size() > 0).isTrue(); - } - - @Test - public void queryTestPreparedStatementCreatorPreparedStatementBinderResultSetExtractor() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - List books = cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new ResultSetExtractor>() { - - @Override - public List extractData(ResultSet rs) throws DriverException, DataAccessException { - List books = new LinkedList(); - - for (Row row : rs.all()) { - books.add(rowToBook(row)); - } - - return books; - } - }); - - Book b2 = getBook(isbn); - - assertThat(1).isEqualTo(books.size()); - assertBook(books.get(0), b2); - } - - @Test - public void queryTestPreparedStatementCreatorPreparedStatementBinderRowCallbackHandler() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new RowCallbackHandler() { - - @Override - public void processRow(Row row) throws DriverException { - Book b = rowToBook(row); - Book b2 = getBook(isbn); - assertBook(b, b2); - } - }); - - } - - @Test - public void queryTestPreparedStatementCreatorPreparedStatementBinderRowMapper() { - - final String cql = "select * from book where isbn = ?"; - final String isbn = "999999999"; - - List books = cqlTemplate.query(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(Session session) throws DriverException { - return session.prepare(cql); - } - }, new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new RowMapper() { - - @Override - public Book mapRow(Row row, int rowNum) throws DriverException { - return rowToBook(row); - } - }); - - Book b2 = getBook(isbn); - - assertThat(1).isEqualTo(books.size()); - assertBook(books.get(0), b2); - } - - @Test - public void insertAndTruncateQueryObjectTest() { - - String tableName = "truncate_test"; - - CreateTableSpecification createTableSpec = new CreateTableSpecification(); - createTableSpec.name(tableName).partitionKeyColumn("id", DataType.text()).column("foo", DataType.text()); - cqlTemplate.execute(createTableSpec); - - Insert insert = QueryBuilder.insertInto(tableName).value("id", uuid()).value("foo", "bar"); - cqlTemplate.execute(insert); - - Truncate truncate = QueryBuilder.truncate(tableName); - cqlTemplate.execute(truncate); - } - - /** - * @see DATACASS-202 - */ - @Test - public void queryShouldApplyFetchSize() { - - insertTestObjectArray(); - - final String cql = "select * from book"; - - ResultSet oneByOneResultSet = cqlTemplate.query(cql, QueryOptions.builder().fetchSize(1).build()); - - assertThat(oneByOneResultSet.isFullyFetched()).isFalse(); - assertThat(oneByOneResultSet.getAvailableWithoutFetching()).isEqualTo(1); - - ResultSet fullResultSet = cqlTemplate.query(cql, QueryOptions.builder().fetchSize(10).build()); - - assertThat(fullResultSet.isFullyFetched()).isTrue(); - assertThat(fullResultSet.getAvailableWithoutFetching()).isEqualTo(4); - } - - /** - * Assert that a Book matches the arguments expected - * - * @param b - * @param orderedElements - */ - private void assertBook(Book b, Object... orderedElements) { - - assertThat(orderedElements[0]).isEqualTo(b.getIsbn()); - assertThat(orderedElements[1]).isEqualTo(b.getTitle()); - assertThat(orderedElements[2]).isEqualTo(b.getAuthor()); - assertThat(orderedElements[3]).isEqualTo(b.getPages()); - - } - - private Book rowToBook(Row row) { - Book b = new Book(); - b.setIsbn(row.getString("isbn")); - b.setTitle(row.getString("title")); - b.setAuthor(row.getString("author")); - b.setPages(row.getInt("pages")); - return b; - } - - /** - * Convert Object[] to a Book - * - * @param bookElements - * @return - */ - private Book objectToBook(Object... bookElements) { - Book b = new Book(); - b.setIsbn((String) bookElements[0]); - b.setTitle((String) bookElements[1]); - b.setAuthor((String) bookElements[2]); - b.setPages((Integer) bookElements[3]); - return b; - } - - /** - * Assert that 2 Book objects are the same - * - * @param b1 - * @param b2 - */ - public static void assertBook(Book b1, Book b2) { - - assertThat(b2.getIsbn()).isEqualTo(b1.getIsbn()); - assertThat(b2.getTitle()).isEqualTo(b1.getTitle()); - assertThat(b2.getAuthor()).isEqualTo(b1.getAuthor()); - assertThat(b2.getPages()).isEqualTo(b1.getPages()); - - } - - /** - * Get a Book from Cassandra for assertions. - * - * @param isbn - * @return - */ - private Book getBook(final String isbn) { - - Book b = cqlTemplate.query("select * from book where isbn = ?", new PreparedStatementBinder() { - - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - return ps.bind(isbn); - } - }, new ResultSetExtractor() { - - @Override - public Book extractData(ResultSet rs) throws DriverException, DataAccessException { - Book b = new Book(); - Row r = rs.one(); - if (r == null) { - return null; - } - b.setIsbn(r.getString("isbn")); - b.setTitle(r.getString("title")); - b.setAuthor(r.getString("author")); - b.setPages(r.getInt("pages")); - return b; - } - }); - - return b; - - } - - /** - * Get a Book from Cassandra for assertions, if the Book is not retruned then retry as needed. This is used for - * assertions after asynchronous insert/ingest to give the datastore time to catch up with the tests. - * - * @param isbn - * @param retryMillis - * @param numRetries - * @return - */ - private Book getBookWithRetry(final String isbn, final long retryMillis, final int numRetries) { - - Book b = getBook(isbn); - - for (int i = 1; i <= numRetries && b == null; i++) { - log.info(String.format("SLEEP - Trying to get Book after Async Call Waiting [%s]ms, Retry [%s]", retryMillis, i)); - try { - Thread.sleep(retryMillis); - } catch (InterruptedException e) { - throw new IllegalStateException("Failed to sleep for query retry", e); - } - b = getBook(isbn); - } - - return b; - } - - /** - * Get a Book from Cassandra for assertions, if the Book is not retruned then retry as needed. This is used for - * assertions after asynchronous insert/ingest to give the datastore time to catch up with the tests. Defaults to 5 - * retries @ 200ms intervals - * - * @param isbn - * @return - */ - private Book getBookWithRetry(final String isbn) { - return getBookWithRetry(isbn, 200, 5); - } - - /** - * For testing a HostMapper Implementation - */ - public class MyHost { - public String someName; - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/AsynchronousCqlOperationsIntegrationTests.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/AsynchronousCqlOperationsIntegrationTests.java deleted file mode 100755 index d7f6ca45b..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/AsynchronousCqlOperationsIntegrationTests.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.core.async; - -import static org.assertj.core.api.Assertions.*; -import static org.springframework.cassandra.core.keyspace.CreateTableSpecification.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CancellationException; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.cassandra.core.*; -import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; -import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; -import org.springframework.cassandra.test.integration.support.ListOfMapListener; -import org.springframework.cassandra.test.integration.support.MapListener; -import org.springframework.cassandra.test.integration.support.ObjectListener; -import org.springframework.cassandra.test.integration.support.QueryListener; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import com.datastax.driver.core.DataType; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; - -/** - * @author Mark Paluch - */ -public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { - - public static final String TABLE = "book"; - CqlOperations cqlOperations; - - @Before - public void setUp() { - cqlOperations = new CqlTemplate(session); - ensureTableExists(); - cqlOperations.truncate(TABLE); - } - - public static String cql(Book book, String... columns) { - if (columns == null || columns.length == 0) { - columns = new String[] { "title", "isbn" }; - } - return String.format("select %s from %s where title = '%s' and isbn = '%s'", - StringUtils.arrayToCommaDelimitedString(columns), TABLE, book.title, book.isbn); - } - - public static String cql(String[] titles) { - String[] quoted = new String[titles.length]; - System.arraycopy(titles, 0, quoted, 0, titles.length); - for (int i = 0; i < quoted.length; i++) { - quoted[i] = "'" + quoted[i] + "'"; - } - - return String.format("select * from %s where title in (%s)", TABLE, - StringUtils.arrayToCommaDelimitedString(quoted)); - } - - public static Select select(String isbn) { - Select select = QueryBuilder.select("isbn", "title").from(TABLE); - select.where(QueryBuilder.eq("isbn", isbn)); - return select; - } - - public static final Comparator BOOK_COMPARATOR = new Comparator() { - @Override - public int compare(Book l, Book r) { - return l.isbn.compareTo(r.isbn); - } - }; - - public static final Comparator> MAP_WITH_ISBN_COMPARATOR = new Comparator>() { - @Override - public int compare(Map o1, Map o2) { - Assert.isInstanceOf(Comparable.class, o1.get("isbn"), - "Map o1 must contain a key 'isbn' and a Comparable value to compare the maps"); - Assert.isInstanceOf(Comparable.class, o2.get("isbn"), - "Map o2 must contain a key 'isbn' and a Comparable value to compare the maps"); - return ((Comparable) o1.get("isbn")).compareTo(o2.get("isbn")); - } - }; - - public static void assertMapEquals(Map expected, Map actual) { - for (Object key : expected.keySet()) { - assertThat(actual.containsKey(key)).isTrue(); - assertThat(actual.get(key)).isEqualTo(expected.get(key)); - } - } - - void ensureTableExists() { - cqlOperations.execute(createTable(TABLE).ifNotExists().partitionKeyColumn("title", DataType.ascii()) - .clusteredKeyColumn("isbn", DataType.ascii())); - } - - Book[] insert(int n) { - Book[] books = new Book[n]; - for (int i = 0; i < n; i++) { - Book b = books[i] = Book.random(); - cqlOperations.execute(String.format("insert into %s (isbn, title) values ('%s', '%s')", TABLE, b.isbn, b.title)); - } - return books; - } - - void assertBook(Book expected, Book actual) { - assertThat(actual.isbn).isEqualTo(expected.isbn); - assertThat(actual.title).isEqualTo(expected.title); - } - - /** - * Tests that test {@link AsynchronousQueryListener} should create an anonymous subclass of this class then call - * {@link #test()}. - */ - abstract class AsynchronousQueryListenerTestTemplate { - - /** - * Subclass must perform the asynchronous query using the given data and listener and set this.expected - * to the appropriate value before returning. - */ - abstract void doAsyncQuery(Book b, QueryListener listener); - - void test() throws InterruptedException { - Book expected = insert(1)[0]; - QueryListener listener = QueryListener.create(); - doAsyncQuery(expected, listener); - listener.await(); - Row r = cqlOperations.getResultSetUninterruptibly(listener.getResultSetFuture()).one(); - Book actual = new Book(r.getString(0), r.getString(1)); - assertBook(expected, actual); - } - } - - /** - * Tests that test {@link QueryForObjectListener} should create an anonymous subclass of this class then call - * {@link #test()} - */ - abstract class QueryForObjectListenerTestTemplate { - - /** - * Subclass must perform the asynchronous query using the given data and listener and set this.expected - * to the appropriate value before returning. - */ - abstract void doAsyncQuery(Book b, QueryForObjectListener listener); - - T expected; // subclass should set this value in doAsyncQuery - - void test() throws Exception { - Book book = insert(1)[0]; - ObjectListener listener = ObjectListener.create(); - doAsyncQuery(book, listener); - listener.await(); - if (listener.getException() != null) { - throw listener.getException(); - } - assertThat(listener.getResult()).isEqualTo(expected); - } - } - - /** - * Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call - * {@link #test()} - */ - abstract class QueryForMapListenerTestTemplate { - - /** - * Subclass must perform the asynchronous query using the given data and listener and set this.expected - * to the appropriate value before returning. - */ - abstract void doAsyncQuery(Book b, QueryForMapListener listener); - - Map expected; // subclass should set this value in doAsyncQuery - - void test() throws Exception { - Book book = insert(1)[0]; - MapListener listener = MapListener.create(); - doAsyncQuery(book, listener); - listener.await(); - if (listener.getException() != null) { - throw listener.getException(); - } - assertMapEquals(expected, listener.getResult()); - } - } - - /** - * Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call or - * {@link #test(int)} - */ - abstract class QueryForListListenerTestTemplate { - - /** - * Subclass must perform the asynchronous query using the given data and listener and set this.expected - * to the appropriate value before returning. - */ - abstract void doAsyncQuery(Book[] books, QueryForListOfMapListener listener); - - List> expected; // subclass should set this value in doAsyncQuery - - void test(int n) throws Exception { - Book[] books = insert(n); - ListOfMapListener listener = ListOfMapListener.create(); - Arrays.sort(books, BOOK_COMPARATOR); - doAsyncQuery(books, listener); - listener.await(); - if (listener.getException() != null) { - throw listener.getException(); - } - - // sort results the same way as the books array above - Collections.sort(listener.getResult(), MAP_WITH_ISBN_COMPARATOR); - - for (int i = 0; i < expected.size(); i++) { - assertMapEquals(expected.get(i), listener.getResult().get(i)); - } - } - } - - @Test(expected = CancellationException.class) - public void testString_AsynchronousQueryListener_Cancelled() throws InterruptedException { - new AsynchronousQueryListenerTestTemplate() { - @Override - void doAsyncQuery(Book b, QueryListener listener) { - Cancellable qc = cqlOperations.queryAsynchronously(cql(b), listener); - qc.cancel(); - } - }.test(); - } - - @Test - public void testString_AsynchronousQueryListener() throws InterruptedException { - new AsynchronousQueryListenerTestTemplate() { - @Override - void doAsyncQuery(Book b, QueryListener listener) { - cqlOperations.queryAsynchronously(cql(b), listener); - } - }.test(); - } - - public void testString_AsynchronousQueryListener_QueryOptions(final ConsistencyLevel cl) throws InterruptedException { - new AsynchronousQueryListenerTestTemplate() { - @Override - void doAsyncQuery(Book b, QueryListener listener) { - cqlOperations.queryAsynchronously(cql(b), listener, new QueryOptions(cl, RetryPolicy.DEFAULT)); - } - }.test(); - } - - @Test - public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel1() throws InterruptedException { - testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel2() throws InterruptedException { - testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.TWO); - } - - @Test - public void testSelect_AsynchronousQueryListener() throws InterruptedException { - new AsynchronousQueryListenerTestTemplate() { - @Override - void doAsyncQuery(Book b, QueryListener listener) { - cqlOperations.queryAsynchronously(cql(b), listener); - } - }.test(); - } - - @Test - public void testString_QueryForObjectListener() throws Exception { - new QueryForObjectListenerTestTemplate() { - - @Override - void doAsyncQuery(Book b, QueryForObjectListener listener) { - cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener); - expected = b.title; - } - - }.test(); - } - - public void testString_QueryForObjectListener_QueryOptions(final ConsistencyLevel cl) throws Exception { - new QueryForObjectListenerTestTemplate() { - - @Override - void doAsyncQuery(Book b, QueryForObjectListener listener) { - QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING); - cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener, opts); - expected = b.title; - } - - }.test(); - } - - @Test - public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel() throws Exception { - testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel2() throws Exception { - testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.TWO); - } - - @Test - public void testString_QueryForMapListener() throws Exception { - new QueryForMapListenerTestTemplate() { - - @Override - void doAsyncQuery(Book b, QueryForMapListener listener) { - cqlOperations.queryForMapAsynchronously(cql(b), listener); - expected = new HashMap(); - expected.put("isbn", b.isbn); - expected.put("title", b.title); - } - - }.test(); - } - - public void testString_QueryForMapListener_QueryOptions(final ConsistencyLevel cl) throws Exception { - new QueryForMapListenerTestTemplate() { - - @Override - void doAsyncQuery(Book b, QueryForMapListener listener) { - QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING); - cqlOperations.queryForMapAsynchronously(cql(b), listener, opts); - expected = new HashMap(); - expected.put("isbn", b.isbn); - expected.put("title", b.title); - } - - }.test(); - } - - @Test - public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel1() throws Exception { - testString_QueryForMapListener_QueryOptions(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel2() throws Exception { - testString_QueryForMapListener_QueryOptions(ConsistencyLevel.TWO); - } - - @Test - public void testString_QueryForListListener() throws Exception { - new QueryForListListenerTestTemplate() { - - @Override - void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) { - - String[] titles = new String[books.length]; - expected = new ArrayList>(books.length); - for (int i = 0; i < books.length; i++) { - Book b = books[i]; - titles[i] = b.title; - Map row = new HashMap(2); - row.put("title", b.title); - row.put("isbn", b.isbn); - expected.add(row); - } - - cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener); - } - - }.test(2); - } - - public void testString_QueryForListListener_QueryOptions(final ConsistencyLevel cl) throws Exception { - new QueryForListListenerTestTemplate() { - - @Override - void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) { - - String[] titles = new String[books.length]; - expected = new ArrayList>(books.length); - for (int i = 0; i < books.length; i++) { - Book b = books[i]; - titles[i] = b.title; - Map row = new HashMap(2); - row.put("title", b.title); - row.put("isbn", b.isbn); - expected.add(row); - } - - cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener, new QueryOptions(cl, RetryPolicy.LOGGING)); - - } - - }.test(2); - } - - @Test - public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel1() throws Exception { - testString_QueryForListListener_QueryOptions(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel2() throws Exception { - testString_QueryForListListener_QueryOptions(ConsistencyLevel.TWO); - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/Book.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/Book.java deleted file mode 100644 index 36652226a..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/core/async/Book.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.core.async; - -import java.util.UUID; - -/** - * @author Matthew T. Adams - */ -public class Book { - - public static final String uuid() { - return UUID.randomUUID().toString(); - } - - public static Book random() { - return new Book("title-" + uuid(), "isbn-" + uuid()); - } - - public Book() {} - - public Book(String title, String isbn) { - this.isbn = isbn; - this.title = title; - } - - public String isbn; - public String title; -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListListener.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListListener.java deleted file mode 100644 index 5f79c7917..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListListener.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.support; - -import java.util.List; - -import org.springframework.cassandra.core.QueryForListListener; - -/** - * {@link QueryForListListener} suitable for tests. - * - * @author Matthew T. Adams - * @author David Webb - */ -public class ListListener extends CallbackSynchronizationSupport implements QueryForListListener { - - private volatile Exception exception; - private volatile List result; - - /** - * Allow instances only using {@link #create()} - */ - private ListListener() {} - - /** - * @return a new {@link QueryForListListener}. - */ - public static ListListener create() { - return new ListListener(); - } - - @Override - public void onQueryComplete(List results) { - - this.result = results; - countDown(); - } - - @Override - public void onException(Exception x) { - - this.exception = x; - countDown(); - } - - public Exception getException() { - return exception; - } - - public List getResult() { - return result; - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListOfMapListener.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListOfMapListener.java deleted file mode 100644 index 41142980f..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ListOfMapListener.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.support; - -import java.util.List; -import java.util.Map; - -import org.springframework.cassandra.core.QueryForListListener; -import org.springframework.cassandra.core.QueryForListOfMapListener; - -/** - * {@link QueryForListListener} suitable for tests. - * - * @author Matthew T. Adams - * @author David Webb - * @author Mark Paluch - */ -public class ListOfMapListener extends CallbackSynchronizationSupport implements QueryForListOfMapListener { - - private volatile Exception exception; - private volatile List> result; - - /** - * Allow instances only using {@link #create()} - */ - private ListOfMapListener() {} - - /** - * @return a new {@link QueryForListListener}. - */ - public static ListOfMapListener create() { - return new ListOfMapListener(); - } - - @Override - public void onQueryComplete(List> results) { - - this.result = results; - countDown(); - } - - @Override - public void onException(Exception x) { - - this.exception = x; - countDown(); - } - - public Exception getException() { - return exception; - } - - public List> getResult() { - return result; - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/MapListener.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/MapListener.java deleted file mode 100644 index 5d0cbcfab..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/MapListener.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.support; - -import java.util.Map; - -import org.springframework.cassandra.core.QueryForMapListener; - -/** - * {@link QueryForMapListener} suitable for tests. - * - * @author Matthew T. Adams - * @author Mark Paluch - */ -public class MapListener extends CallbackSynchronizationSupport implements QueryForMapListener { - - private volatile Map result; - private volatile Exception exception; - - /** - * Allow instances only using {@link #create()} - */ - private MapListener() {} - - /** - * @return a new {@link MapListener}. - */ - public static MapListener create() { - return new MapListener(); - } - - @Override - public void onQueryComplete(Map results) { - - this.result = results; - countDown(); - } - - @Override - public void onException(Exception x) { - - this.exception = x; - countDown(); - } - - public Map getResult() { - return result; - } - - public Exception getException() { - return exception; - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ObjectListener.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ObjectListener.java deleted file mode 100644 index 64649c6ac..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/ObjectListener.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.support; - -import org.springframework.cassandra.core.QueryForObjectListener; - -/** - * {@link QueryForObjectListener} suitable for tests. - * - * @author Matthew T. Adams - * @author David Webb - * @author Mark Paluch - */ -public class ObjectListener extends CallbackSynchronizationSupport implements QueryForObjectListener { - - private volatile T result; - private volatile Exception exception; - - /** - * Allow instances only using {@link #create()} - */ - private ObjectListener() {} - - /** - * @return a new {@link ObjectListener}. - */ - public static ObjectListener create() { - return new ObjectListener(); - } - - @Override - public void onQueryComplete(T result) { - - this.result = result; - countDown(); - } - - @Override - public void onException(Exception x) { - - this.exception = x; - countDown(); - } - - public T getResult() { - return result; - } - - public Exception getException() { - return exception; - } -} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/QueryListener.java b/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/QueryListener.java deleted file mode 100644 index f9749a1a7..000000000 --- a/spring-cql/src/test/java/org/springframework/cassandra/test/integration/support/QueryListener.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cassandra.test.integration.support; - -import org.springframework.cassandra.core.AsynchronousQueryListener; - -import com.datastax.driver.core.ResultSetFuture; - -/** - * {@link AsynchronousQueryListener} suitable for usage in tests. - * - * @author Matthew T. Adams - * @author David Webb - * @author Mark Paluch - */ -public class QueryListener extends CallbackSynchronizationSupport implements AsynchronousQueryListener { - - private volatile ResultSetFuture resultSetFuture; - - /** - * Allow instances only using {@link #create()} - */ - private QueryListener() {} - - /** - * @return a new {@link QueryListener}. - */ - public static QueryListener create() { - return new QueryListener(); - } - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - - this.resultSetFuture = resultSetFuture; - countDown(); - } - - public ResultSetFuture getResultSetFuture() { - return resultSetFuture; - } -} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java new file mode 100644 index 000000000..0cfdcfe11 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraOperations.java @@ -0,0 +1,236 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import java.util.List; +import java.util.function.Consumer; + +import org.springframework.cassandra.core.AsyncCqlOperations; +import org.springframework.cassandra.core.QueryOptions; +import org.springframework.cassandra.core.WriteOptions; +import org.springframework.dao.DataAccessException; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.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 + * @since 2.0 + * @see AsyncCassandraTemplate + */ +public interface AsyncCassandraOperations { + + // ------------------------------------------------------------------------- + // 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. + */ + ListenableFuture> select(String cql, Class 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. + */ + ListenableFuture select(String cql, Consumer entityConsumer, Class 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. + */ + ListenableFuture selectOne(String cql, Class entityClass) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /** + * 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. + */ + ListenableFuture> select(Statement statement, Class 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. + */ + ListenableFuture select(Statement statement, Consumer entityConsumer, Class 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. + */ + ListenableFuture selectOne(Statement statement, Class entityClass) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with entities + // ------------------------------------------------------------------------- + + /** + * Execute the Select by {@code id} for the given {@code entityClass}. + * + * @param id 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. + */ + ListenableFuture selectOneById(Object id, Class entityClass) throws DataAccessException; + + /** + * Determine whether the row {@code entityClass} with the given {@code id} exists. + * + * @param id must not be {@literal null}. + * @param entityClass must not be {@literal null}. + * @return {@literal true}, if the object exists. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture exists(Object id, Class entityClass) throws DataAccessException; + + /** + * Returns the number of rows for the given entity class. + * + * @param entityClass must not be {@literal null}. + * @return the number of existing entities. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture count(Class 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. + */ + ListenableFuture 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 may be {@literal null}. + * @return the inserted entity. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture insert(T entity, WriteOptions 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. + */ + ListenableFuture 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 may be {@literal null}. + * @return the updated entity. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture update(T entity, WriteOptions options) throws DataAccessException; + + /** + * Remove the given object from the table by id. + * + * @param id 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 deleteById(Object id, Class entityClass) throws DataAccessException; + + /** + * Delete the given entity and return the entity if the delete was applied. + * + * @param entity must not be {@literal null}. + * @return the deleted entity. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture delete(T entity) throws DataAccessException; + + /** + * Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied. + * + * @param entity must not be {@literal null}. + * @param options may be {@literal null}. + * @return the deleted entity. + * @throws DataAccessException if there is any problem executing the query. + */ + ListenableFuture delete(T entity, QueryOptions options) 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 truncate(Class entityClass) throws DataAccessException; + + /** + * Returns the underlying {@link CassandraConverter}. + * + * @return the underlying {@link CassandraConverter}. + */ + CassandraConverter getConverter(); + + /** + * Expose the underlying {@link AsyncCqlOperationsOperations} to allow asynchronous CQL operations. + * + * @return the underlying {@link AsyncCqlOperations}. + * @see AsyncCqlOperations + */ + AsyncCqlOperations getAsyncCqlOperations(); +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java new file mode 100644 index 000000000..3e9641283 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/AsyncCassandraTemplate.java @@ -0,0 +1,467 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.cassandra.core.AsyncCqlOperations; +import org.springframework.cassandra.core.AsyncCqlTemplate; +import org.springframework.cassandra.core.AsyncSessionCallback; +import org.springframework.cassandra.core.CqlProvider; +import org.springframework.cassandra.core.GuavaListenableFutureAdapter; +import org.springframework.cassandra.core.QueryOptions; +import org.springframework.cassandra.core.WriteOptions; +import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.cassandra.core.support.CQLExceptionTranslator; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.cassandra.convert.CassandraConverter; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SimpleStatement; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.exceptions.DriverException; +import com.datastax.driver.core.querybuilder.Delete; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.querybuilder.Truncate; +import com.datastax.driver.core.querybuilder.Update; + +/** + * Primary implementation of {@link AsyncCassandraOperations}. It simplifies the use of asynchronous Cassandra usage and + * helps to avoid common errors. It executes core Cassandra workflow. This class executes CQL queries or updates, + * initiating iteration over {@link ResultSet} and catching Cassandra exceptions and translating them to the generic, + * more informative exception hierarchy defined in the {@code org.springframework.dao} package. + *

+ * Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get + * prepared in an application context and given to services as bean reference. + *

+ * Note: The {@link Session} 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. + * + * @author Mark Paluch + * @since 2.0 + */ +public class AsyncCassandraTemplate implements AsyncCassandraOperations { + + private final CQLExceptionTranslator exceptionTranslator; + private final CassandraConverter converter; + private final CassandraMappingContext mappingContext; + private final AsyncCqlOperations cqlOperations; + + /** + * Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link Session} and a default + * {@link MappingCassandraConverter}. + * + * @param session {@link Session} used to interact with Cassandra; must not be {@literal null}. + * @see CassandraConverter + * @see Session + */ + public AsyncCassandraTemplate(Session session) { + this(session, newConverter()); + } + + /** + * Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link Session} and + * {@link CassandraConverter}. + * + * @param session {@link Session} used to interact with Cassandra; must not be {@literal null}. + * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be + * {@literal null}. + * @see CassandraConverter + * @see Session + */ + public AsyncCassandraTemplate(Session session, CassandraConverter converter) { + + Assert.notNull(session, "Session must not be null"); + Assert.notNull(converter, "CassandraConverter must not be null"); + + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + + AsyncCqlTemplate asyncCqlTemplate = new AsyncCqlTemplate(session); + this.exceptionTranslator = asyncCqlTemplate.getExceptionTranslator(); + this.cqlOperations = asyncCqlTemplate; + } + + /** + * Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link AsyncCqlTemplate} and + * {@link CassandraConverter}. + * + * @param asyncCqlTemplate {@link AsyncCqlTemplate} used to interact with Cassandra; must not be {@literal null}. + * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be + * {@literal null}. + * @see CassandraConverter + * @see Session + */ + public AsyncCassandraTemplate(AsyncCqlTemplate asyncCqlTemplate, CassandraConverter converter) { + + Assert.notNull(asyncCqlTemplate, "AsyncCqlTemplate must not be null"); + Assert.notNull(converter, "CassandraConverter must not be null"); + + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + this.cqlOperations = asyncCqlTemplate; + this.exceptionTranslator = asyncCqlTemplate.getExceptionTranslator(); + } + + private static MappingCassandraConverter newConverter() { + + MappingCassandraConverter converter = new MappingCassandraConverter(); + converter.afterPropertiesSet(); + + return converter; + } + + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#select(java.lang.String, java.lang.Class) + */ + @Override + public ListenableFuture> select(String cql, Class entityClass) { + + Assert.hasText(cql, "Statement must not be empty"); + + return select(new SimpleStatement(cql), entityClass); + } + + @Override + public ListenableFuture select(String cql, Consumer entityConsumer, Class entityClass) + throws DataAccessException { + + Assert.hasText(cql, "Statement must not be empty"); + Assert.notNull(entityConsumer, "Entity Consumer must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return select(new SimpleStatement(cql), entityConsumer, entityClass); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOne(java.lang.String, java.lang.Class) + */ + @Override + public ListenableFuture selectOne(String cql, Class entityClass) { + + Assert.hasText(cql, "Statement must not be empty"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return selectOne(new SimpleStatement(cql), entityClass); + } + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#select(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public ListenableFuture> select(Statement statement, Class entityClass) { + + Assert.notNull(statement, "Statement must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return cqlOperations.query(statement, (row, rowNum) -> converter.read(entityClass, row)); + } + + @Override + public ListenableFuture select(Statement statement, Consumer entityConsumer, Class entityClass) + throws DataAccessException { + + Assert.notNull(statement, "Statement must not be null"); + Assert.notNull(entityConsumer, "Entity Consumer must not be empty"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return cqlOperations.query(statement, (row) -> { + entityConsumer.accept(converter.read(entityClass, row)); + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOne(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public ListenableFuture selectOne(Statement statement, Class entityClass) { + + return new MappingListenableFutureAdapter<>(select(statement, entityClass), list -> { + + if (list.isEmpty()) { + return null; + } + return list.get(0); + + }); + } + + // ------------------------------------------------------------------------- + // Methods dealing with entities + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOneById(java.lang.Object, java.lang.Class) + */ + @Override + public ListenableFuture selectOneById(Object id, Class entityClass) { + + Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + Select select = QueryBuilder.select().all().from(entity.getTableName().toCql()); + + converter.write(id, select.where(), entity); + + return selectOne(select, entityClass); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#exists(java.lang.Object, java.lang.Class) + */ + @Override + public ListenableFuture exists(Object id, Class entityClass) { + + Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + Select select = QueryBuilder.select().from(entity.getTableName().toCql()); + converter.write(id, select.where(), entity); + + return new MappingListenableFutureAdapter<>(cqlOperations.queryForResultSet(select), + resultSet -> resultSet.iterator().hasNext()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#count(java.lang.Class) + */ + @Override + public ListenableFuture count(Class entityClass) { + + Assert.notNull(entityClass, "Entity type must not be null"); + + Select select = QueryBuilder.select().countAll().from(getPersistentEntity(entityClass).getTableName().toCql()); + + return cqlOperations.queryForObject(select, Long.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object) + */ + @Override + public ListenableFuture insert(T entity) { + return insert(entity, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object, org.springframework.cassandra.core.WriteOptions) + */ + @Override + public ListenableFuture insert(T entity, WriteOptions options) { + + Assert.notNull(entity, "Entity must not be null"); + + CqlIdentifier tableName = getTableName(entity); + + Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, converter); + + return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(insert)), + resultSet -> resultSet.wasApplied() ? entity : null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object) + */ + @Override + public ListenableFuture update(T entity) { + return update(entity, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object, org.springframework.cassandra.core.WriteOptions) + */ + @Override + public ListenableFuture update(T entity, WriteOptions options) { + + Assert.notNull(entity, "Entity must not be null"); + + CqlIdentifier tableName = getTableName(entity); + + Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, converter); + + return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(update)), + resultSet -> resultSet.wasApplied() ? entity : null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#deleteById(java.lang.Object, java.lang.Class) + */ + @Override + public ListenableFuture deleteById(Object id, Class entityClass) { + + Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + Delete delete = QueryBuilder.delete().from(entity.getTableName().toCql()); + + converter.write(id, delete.where(), entity); + + return cqlOperations.execute(delete); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#delete(java.lang.Object) + */ + @Override + public ListenableFuture delete(T entity) { + return delete(entity, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#delete(java.lang.Object, org.springframework.cassandra.core.QueryOptions) + */ + @Override + public ListenableFuture delete(T entity, QueryOptions options) { + + Assert.notNull(entity, "Entity must not be null"); + + CqlIdentifier tableName = getTableName(entity); + + Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, converter); + + return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(delete)), + resultSet -> resultSet.wasApplied() ? entity : null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#truncate(java.lang.Class) + */ + @Override + public ListenableFuture truncate(Class entityClass) { + + Assert.notNull(entityClass, "Entity type must not be null"); + Truncate truncate = QueryBuilder.truncate(getPersistentEntity(entityClass).getTableName().toCql()); + + return new MappingListenableFutureAdapter<>(cqlOperations.execute(truncate), aBoolean -> null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#getConverter() + */ + @Override + public CassandraConverter getConverter() { + return converter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.AsyncCassandraOperations#getAsyncCqlOperations() + */ + @Override + public AsyncCqlOperations getAsyncCqlOperations() { + return cqlOperations; + } + + private CassandraPersistentEntity getPersistentEntity(Class entityClass) { + + Assert.notNull(entityClass, "Entity type must not be null"); + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + if (entity == null) { + throw new InvalidDataAccessApiUsageException( + String.format("No Persistent Entity information found for the class [%s]", entityClass.getName())); + } + + return entity; + } + + private CqlIdentifier getTableName(Object entity) { + return getPersistentEntity(ClassUtils.getUserClass(entity)).getTableName(); + } + + private static class MappingListenableFutureAdapter + extends org.springframework.util.concurrent.ListenableFutureAdapter { + + private final Function mapper; + + public MappingListenableFutureAdapter(ListenableFuture adaptee, Function mapper) { + super(adaptee); + this.mapper = mapper; + } + + @Override + protected T adapt(S adapteeResult) throws ExecutionException { + return mapper.apply(adapteeResult); + } + } + + private class AsyncStatementCallback implements AsyncSessionCallback, CqlProvider { + + private final Statement statement; + + AsyncStatementCallback(Statement statement) { + this.statement = statement; + } + + @Override + public ListenableFuture doInSession(Session session) throws DriverException, DataAccessException { + return new GuavaListenableFutureAdapter<>(session.executeAsync(statement), e -> { + + if (e instanceof DriverException) { + return exceptionTranslator.translate("AsyncStatementCallback", getCql(), (DriverException) e); + } + + return exceptionTranslator.translateExceptionIfPossible(e); + }); + } + + @Override + public String getCql() { + return statement.toString(); + } + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java index 4115c0311..346fa8aaa 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java @@ -47,27 +47,6 @@ public interface CassandraAdminOperations extends CassandraOperations { void createTable(boolean ifNotExists, CqlIdentifier tableName, Class entityClass, Map optionsByName); - /** - * Add columns to the given table from the given class. If parameter dropRemovedAttributColumns is true, then this - * effectively becomes a synchronization operation between the class's fields and the existing table's columns. - * - * @param tableName The name of the existing table. - * @param entityClass The class whose fields determine the columns added. - * @param dropRemovedAttributeColumns Whether to drop columns that exist on the table but that don't have - * corresponding fields in the class. If true, this effectively becomes a synchronziation operation. - */ - void alterTable(CqlIdentifier tableName, Class entityClass, boolean dropRemovedAttributeColumns); - - /** - * Drops the existing table with the given name and creates a new one; basically a {@link #dropTable(String)} followed - * by a {@link #createTable(boolean, String, Class, Map)}. - * - * @param tableName The name of the table. - * @param entityClass The class whose fields determine the new table's columns. - * @param optionsByName Table options, given by the string option name and the appropriate option value. - */ - void replaceTable(CqlIdentifier tableName, Class entityClass, Map optionsByName); - /** * Drops the named table. * @@ -86,7 +65,7 @@ public interface CassandraAdminOperations extends CassandraOperations { /** * Returns {@link KeyspaceMetadata} for the current keyspace. - * + * * @return {@link KeyspaceMetadata} for the current keyspace. * @since 1.5 */ @@ -94,7 +73,7 @@ public interface CassandraAdminOperations extends CassandraOperations { /** * Drops a user type. - * + * * @param typeName must not be {@literal null}. * @since 1.5 */ diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java index bd5d155fd..655ff5073 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java @@ -15,7 +15,6 @@ */ package org.springframework.data.cassandra.core; -import java.util.List; import java.util.Map; import org.slf4j.Logger; @@ -23,19 +22,19 @@ import org.slf4j.LoggerFactory; import org.springframework.cassandra.core.SessionCallback; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; import org.springframework.cassandra.core.cql.generator.DropUserTypeCqlGenerator; +import org.springframework.cassandra.core.keyspace.CreateTableSpecification; import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.cassandra.core.keyspace.DropUserTypeSpecification; import org.springframework.dao.DataAccessException; import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; -import org.springframework.data.cassandra.util.CqlUtils; import org.springframework.util.Assert; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.Session; import com.datastax.driver.core.TableMetadata; -import com.datastax.driver.core.UserType; /** * Default implementation of {@link CassandraAdminOperations}. @@ -65,71 +64,11 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand public void createTable(final boolean ifNotExists, final CqlIdentifier tableName, Class entityClass, Map optionsByName) { - final CassandraPersistentEntity entity = getCassandraMappingContext().getPersistentEntity(entityClass); + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + CreateTableSpecification createTableSpecification = getConverter().getMappingContext() + .getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists); - execute(new SessionCallback() { - @Override - public Object doInSession(Session s) throws DataAccessException { - - String cql = new CreateTableCqlGenerator( - getCassandraMappingContext().getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists)).toCql(); - - log.debug(cql); - - s.execute(cql); - return null; - } - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraAdminOperations#alterTable(org.springframework.cassandra.core.cql.CqlIdentifier, java.lang.Class, boolean) - */ - @Override - public void alterTable(CqlIdentifier tableName, Class entityClass, boolean dropRemovedAttributeColumns) { - throw new UnsupportedOperationException("not yet implemented"); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraAdminOperations#replaceTable(org.springframework.cassandra.core.cql.CqlIdentifier, java.lang.Class, java.util.Map) - */ - @Override - public void replaceTable(CqlIdentifier tableName, Class entityClass, Map optionsByName) { - - dropTable(tableName); - createTable(false, tableName, entityClass, optionsByName); - } - - /** - * Create a list of query operations to alter the table for the given entity - * - * @param entityClass - * @param tableName - */ - protected void doAlterTable(Class entityClass, String keyspace, CqlIdentifier tableName) { - - CassandraPersistentEntity entity = getCassandraMappingContext().getPersistentEntity(entityClass); - - Assert.notNull(entity); - - final TableMetadata tableMetadata = getTableMetadata(keyspace, tableName); - final List queryList = CqlUtils.alterTable(tableName.toCql(), entity, tableMetadata); - - execute(new SessionCallback() { - - @Override - public Object doInSession(Session s) throws DataAccessException { - - for (String q : queryList) { - log.info(q); - s.execute(q); - } - - return null; - } - }); + getCqlOperations().execute(CreateTableCqlGenerator.toCql(createTableSpecification)); } public void dropTable(Class entityClass) { @@ -142,12 +81,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand */ @Override public void dropTable(CqlIdentifier tableName) { - - Assert.notNull(tableName, "Table name must not be null"); - - log.info("Dropping table => " + tableName); - - execute(DropTableSpecification.dropTable(tableName)); + getCqlOperations().execute(DropTableCqlGenerator.toCql(DropTableSpecification.dropTable(tableName))); } /* @@ -158,10 +92,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand public void dropUserType(CqlIdentifier typeName) { Assert.notNull(typeName, "Type name must not be null"); - - log.info("Dropping user type => {}", typeName); - - execute(DropUserTypeCqlGenerator.toCql(DropUserTypeSpecification.dropType(typeName))); + getCqlOperations().execute(DropUserTypeCqlGenerator.toCql(DropUserTypeSpecification.dropType(typeName))); } /* @@ -169,17 +100,13 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand * @see org.springframework.data.cassandra.core.CassandraAdminOperations#getTableMetadata(java.lang.String, org.springframework.cassandra.core.cql.CqlIdentifier) */ @Override - public TableMetadata getTableMetadata(final String keyspace, final CqlIdentifier tableName) { + public TableMetadata getTableMetadata(String keyspace, CqlIdentifier tableName) { Assert.hasText(keyspace, "Keyspace name must not be empty"); Assert.notNull(tableName, "Table name must not be null"); - return execute(new SessionCallback() { - @Override - public TableMetadata doInSession(Session s) { - return s.getCluster().getMetadata().getKeyspace(keyspace).getTable(tableName.toCql()); - } - }); + return getCqlOperations().execute((SessionCallback) session -> session.getCluster().getMetadata() + .getKeyspace(keyspace).getTable(tableName.toCql())); } /* @@ -189,7 +116,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand @Override public KeyspaceMetadata getKeyspaceMetadata() { - return execute(new SessionCallback() { + return getCqlOperations().execute(new SessionCallback() { @Override public KeyspaceMetadata doInSession(Session s) throws DataAccessException { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraBatchTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraBatchTemplate.java index 55c759f50..369b1282e 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraBatchTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraBatchTemplate.java @@ -36,16 +36,19 @@ class CassandraBatchTemplate implements CassandraBatchOperations { static final Object[] EMPTY_ARRAY = new Object[0]; private AtomicBoolean executed = new AtomicBoolean(); - private final Batch batch; + private final CassandraOperations operations; - private final CassandraTemplate cassandraTemplate; + /** + * Creates a new {@link CassandraBatchTemplate} given {@link CassandraOperations}. + * + * @param operations must not be {@literal null}. + */ + public CassandraBatchTemplate(CassandraOperations operations) { - public CassandraBatchTemplate(CassandraTemplate cassandraTemplate) { + Assert.notNull(operations, "CassandraOperations must not be null"); - Assert.notNull(cassandraTemplate, "CassandraTemplate must not be null"); - - this.cassandraTemplate = cassandraTemplate; + this.operations = operations; this.batch = QueryBuilder.batch(); } @@ -57,7 +60,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations { public void execute() { if (executed.compareAndSet(false, true)) { - cassandraTemplate.execute(batch); + operations.getCqlOperations().execute(batch); return; } @@ -98,7 +101,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations { for (Object entity : nullSafeIterable(entities)) { Assert.notNull(entity, "Entity must not be null"); - batch.add(cassandraTemplate.createInsertQuery(entity, null)); + batch.add(QueryUtils.createInsertQuery(getTableName(entity), entity, null, operations.getConverter())); } return this; @@ -124,7 +127,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations { for (Object entity : nullSafeIterable(entities)) { Assert.notNull(entity, "Entity must not be null"); - batch.add(cassandraTemplate.createUpdateQuery(entity, null)); + batch.add(QueryUtils.createUpdateQuery(getTableName(entity), entity, null, operations.getConverter())); } return this; @@ -150,7 +153,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations { for (Object entity : nullSafeIterable(entities)) { Assert.notNull(entity, "Entity must not be null"); - batch.add(cassandraTemplate.createDeleteQuery(entity, null)); + batch.add(QueryUtils.createDeleteQuery(getTableName(entity), entity, null, operations.getConverter())); } return this; @@ -160,11 +163,17 @@ class CassandraBatchTemplate implements CassandraBatchOperations { Assert.state(!executed.get(), "This Cassandra Batch was already executed"); } + private String getTableName(Object entity) { + + Assert.notNull(entity, "Entity must not be null"); + return operations.getTableName(entity.getClass()).toCql(); + } + private Iterable nullSafeIterable(T... array) { - return (array == null ? Collections.emptyList() : Arrays.asList(array)); + return (array == null ? Collections. emptyList() : Arrays.asList(array)); } private Iterable nullSafeIterable(Iterable iterable) { - return (iterable != null ? iterable : Collections.emptyList()); + return (iterable != null ? iterable : Collections. emptyList()); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java index 9d4b99a80..fd1638738 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraOperations.java @@ -1,11 +1,11 @@ /* - * Copyright 2013-2016 the original author or authors + * Copyright 2016 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,43 +17,30 @@ package org.springframework.data.cassandra.core; import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; -import org.springframework.cassandra.core.Cancellable; import org.springframework.cassandra.core.CqlOperations; -import org.springframework.cassandra.core.QueryForObjectListener; import org.springframework.cassandra.core.QueryOptions; import org.springframework.cassandra.core.WriteOptions; import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.dao.DataAccessException; import org.springframework.data.cassandra.convert.CassandraConverter; -import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.Statement; /** - * Operations for interacting with Cassandra. These operations are used by the Repository implementation, but can also - * be used directly when that is desired by the developer. - *

Deprecation note

- *

- * Methods accepting a {@link List} of entities perform batching operations (insert/update/delete). This can be fine for - * entities sharing a partition key but leads in most cases to distributed batches across a Cassandra cluster which is - * an anti-pattern. Please use {@link #batchOps()} if your intention is batching. As of Version 1.5, all methods - * accepting a {@link List} of entities are deprecated because there is no alternative of inserting multiple rows in an - * atomic way that guarantees not to harm Cassandra performance. These methods will be removed in Version 2.0. Please - * issue multiple calls to the corresponding single-entity method. - *

- * {@link CassandraOperations} mixes synchronous and asynchronous methods so asynchronous methods are subject to be - * moved into an asynchronous Cassandra template. - * + * Interface specifying a basic set of Cassandra operations. Implemented by {@link CassandraTemplate}. Not often used + * directly, but a useful option to enhance testability, as it can easily be mocked or stubbed. + * * @author Alex Shvid * @author David Webb * @author Matthew Adams * @author Mark Paluch + * @see CassandraTemplate * @see CqlOperations - * @see Select - * @see WriteListener - * @see DeletionListener - * @see QueryForObjectListener + * @see Statement */ -public interface CassandraOperations extends CqlOperations { +public interface CassandraOperations { /** * The table name used for the specified class by this template. @@ -63,552 +50,198 @@ public interface CassandraOperations extends CqlOperations { */ CqlIdentifier getTableName(Class entityClass); - /** - * Executes the given select {@code query} on the entity table of the specified {@code type} backed by a Cassandra - * {@link com.datastax.driver.core.ResultSet}. - *

- * Returns a {@link java.util.Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}. - * - * @param element return type. - * @param query query to execute. Must not be empty or {@literal null}. - * @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}. - * @return an {@link Iterator} (stream) over the elements in the query result set. - * @since 1.5 - */ - Iterator stream(String query, Class entityClass); + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- /** - * Execute query and convert ResultSet to the list of entities. + * 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. */ - List select(String cql, Class entityClass); + List select(String cql, Class entityClass) throws DataAccessException; /** - * Execute the Select Query and convert to the list of entities. + * Execute a {@code SELECT} query and convert the resulting items to a {@link Iterator} of entities. + *

+ * Returns a {@link Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}. * - * @param select must not be {@literal null}. + * @param element return type. + * @param cql query to execute. Must not be empty or {@literal null}. + * @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}. + * @return an {@link Iterator} (stream) over the elements in the query result set. + * @throws DataAccessException if there is any problem executing the query. + * @since 1.5 + */ + Stream stream(String cql, Class 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 selectOne(String cql, Class entityClass) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /** + * 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. */ - List select(Select select, Class entityClass); + List select(Statement statement, Class entityClass) throws DataAccessException; /** - * Select objects for the given {@code entityClass} and {@code ids}. + * Execute a {@code SELECT} query and convert the resulting items to a {@link Iterator} of entities. + *

+ * Returns a {@link Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}. * - * @param entityClass The entity type must not be {@literal null}. - * @param ids must not be {@literal null}. - * @return the converted results + * @param element return type. + * @param statement query to execute. Must not be empty or {@literal null}. + * @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}. + * @return an {@link Iterator} (stream) over the elements in the query result set. + * @throws DataAccessException if there is any problem executing the query. + * @since 1.5 */ - List selectBySimpleIds(Class entityClass, Iterable ids); + Stream stream(Statement statement, Class entityClass) throws DataAccessException; /** - * @deprecated Calling this method could result in {@link OutOfMemoryError}, as this is a brute force selection. + * 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 A list of all entities of type T. + * @return the converted object or {@literal null}. + * @throws DataAccessException if there is any problem executing the query. */ - @Deprecated - List selectAll(Class entityClass); + T selectOne(Statement statement, Class entityClass) throws DataAccessException; + + // ------------------------------------------------------------------------- + // Methods dealing with entities + // ------------------------------------------------------------------------- /** * Execute the Select by {@code id} for the given {@code entityClass}. * - * @param entityClass The entity type must not be {@literal null}. * @param id must not be {@literal null}. - * @return the converted object or {@literal null}. - */ - T selectOneById(Class entityClass, Object id); - - /** - * Execute CQL and convert ResultSet to the 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 selectOne(String cql, Class entityClass); + T selectOneById(Object id, Class entityClass) throws DataAccessException; /** - * Execute Select query and convert ResultSet to the entity + * Select objects for the given {@code entityClass} and {@code ids}. * - * @param select must not be {@literal null}. + * @param ids must not be {@literal null}. * @param entityClass The entity type must not be {@literal null}. - * @return the converted object or {@literal null}. + * @return the converted results + * @throws DataAccessException if there is any problem executing the query. */ - T selectOne(Select select, Class entityClass); - - /** - * Executes the {@link Select} query asynchronously. - * - * @param select The {@link Select} query to execute. - * @param entityClass The entity type must not be {@literal null}. - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable selectOneAsynchronously(Select select, Class entityClass, QueryForObjectListener listener); - - /** - * Executes the string CQL query asynchronously. - * - * @param cql The string query CQL to execute. - * @param entityClass The entity type must not be {@literal null}. - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable selectOneAsynchronously(String cql, Class entityClass, QueryForObjectListener listener); - - /** - * Executes the {@link Select} query asynchronously. - * - * @param select The {@link Select} query to execute. - * @param entityClass The entity type must not be {@literal null}. - * @param options The {@link QueryOptions} to use. - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable selectOneAsynchronously(Select select, Class entityClass, QueryForObjectListener listener, - QueryOptions options); - - /** - * Executes the string CQL query asynchronously. - * - * @param cql The string query CQL to execute. - * @param entityClass The entity type must not be {@literal null}. - * @param options The {@link QueryOptions} to use. - * @return A {@link Cancellable} that can be used to cancel the query. - */ - Cancellable selectOneAsynchronously(String cql, Class entityClass, QueryForObjectListener listener, - QueryOptions options); + List selectBySimpleIds(Iterable ids, Class entityClass) throws DataAccessException; /** * Determine whether the row {@code entityClass} with the given {@code id} exists. * - * @param entityClass The entity type must not be {@literal null}. * @param id must not be {@literal null}. - * @return true, if the object exists - */ - boolean exists(Class entityClass, Object id); - - /** - * Returns the number of rows for the given {@code entityClass} by querying the table of the given entity class. - * * @param entityClass The entity type must not be {@literal null}. - * @return number of rows + * @return true, if the object exists. + * @throws DataAccessException if there is any problem executing the query. */ - long count(Class entityClass); + boolean exists(Object id, Class entityClass) throws DataAccessException; /** - * Insert the given entity. + * Returns the number of rows for the given entity class. * - * @param entity The entity to insert - * @return The entity given + * @param entityClass must not be {@literal null}. + * @return the number of existing entities. + * @throws DataAccessException if there is any problem executing the query. */ - T insert(T entity); + long count(Class entityClass) throws DataAccessException; /** - * Insert the given entity. + * Insert the given entity and return the entity if the insert was applied. * - * @param entity The entity to insert - * @param options The {@link WriteOptions} to use. - * @return The entity given + * @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 insert(T entity, WriteOptions options); + T insert(T entity) throws DataAccessException; /** - * Insert the given list of entities. + * Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied. * - * @param entities The entities to insert. - * @return The entities given. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and inserts all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #insert(Object)} - * as that's the preferred approach. This method will be removed in Version 2.0. + * @param entity The entity to insert, must not be {@literal null}. + * @param options may be {@literal null}. + * @return the inserted entity. + * @throws DataAccessException if there is any problem executing the query. */ - @Deprecated - List insert(List entities); + T insert(T entity, WriteOptions options) throws DataAccessException; /** - * Insert the given list of entities. + * Update the given entity and return the entity if the update was applied. * - * @param entities The entities to insert. - * @param options The {@link WriteOptions} to use. - * @return The entities given. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and inserts all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to - * {@link #insert(Object, WriteOptions)} as that's the preferred approach. This method will be removed in - * Version 2.0. + * @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. */ - @Deprecated - List insert(List entities, WriteOptions options); + T update(T entity) throws DataAccessException; /** - * Inserts the given entity asynchronously. + * Update the given entity applying {@link WriteOptions} and return the entity if the update was applied. * - * @param entity The entity to insert - * @return The entity given - * @see #insertAsynchronously(Object, WriteListener) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #insertAsynchronously(Object, WriteListener)}. + * @param entity The entity to update, must not be {@literal null}. + * @param options may be {@literal null}. + * @return the updated entity. + * @throws DataAccessException if there is any problem executing the query. */ - @Deprecated - T insertAsynchronously(T entity); - - /** - * Inserts the given entity asynchronously. - * - * @param entity The entity to insert - * @return The entity given - * @see #insertAsynchronously(Object, WriteOptions) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #insertAsynchronously(Object, WriteListener, WriteOptions)}. - */ - @Deprecated - T insertAsynchronously(T entity, WriteOptions options); - - /** - * Inserts the given entity asynchronously. - * - * @param entity The entity to insert - * @param listener The listener to receive notification of completion - * @return A {@link Cancellable} enabling the cancellation of the operation - */ - Cancellable insertAsynchronously(T entity, WriteListener listener); - - /** - * Inserts the given entity asynchronously. - * - * @param entity The entity to insert - * @param listener The listener to receive notification of completion - * @param options The {@link WriteOptions} to use - * @return A {@link Cancellable} enabling the cancellation of the operation - */ - Cancellable insertAsynchronously(T entity, WriteListener listener, WriteOptions options); - - /** - * Inserts the given entities asynchronously in a batch. - * - * @param entities The entities to insert - * @return The entities given - * @see #insertAsynchronously(List, WriteListener) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #insertAsynchronously(List, WriteListener)}. - */ - @Deprecated - List insertAsynchronously(List entities); - - /** - * Inserts the given entities asynchronously in a batch. - * - * @param entities The entities to insert - * @return The entities given - * @see #insertAsynchronously(List, WriteListener, WriteOptions) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #insertAsynchronously(List, WriteListener, WriteOptions)}. - */ - @Deprecated - List insertAsynchronously(List entities, WriteOptions options); - - /** - * Inserts the given entities asynchronously in a batch. - * - * @param entities The entities to insert - * @param listener The listener to receive notification of completion - * @return A {@link Cancellable} enabling the cancellation of the operation - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable insertAsynchronously(List entities, WriteListener listener); - - /** - * Inserts the given entities asynchronously in a batch. - * - * @param entities The entities to insert - * @param listener The listener to receive notification of completion - * @param options The {@link WriteOptions} to use - * @return A {@link Cancellable} enabling the cancellation of the operation - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable insertAsynchronously(List entities, WriteListener listener, WriteOptions options); - - /** - * Update the given entity. - * - * @param entity The entity to update - * @return The entity given - */ - T update(T entity); - - /** - * Update the given entity. - * - * @param entity The entity to update - * @param options The {@link WriteOptions} to use. - * @return The entity given - */ - T update(T entity, WriteOptions options); - - /** - * Update the given list of entities. - * - * @param entities The entities to update. - * @return The entities given. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and updates all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #update(Object)} - * as that's the preferred approach. This method will be removed in Version 2.0. - */ - @Deprecated - List update(List entities); - - /** - * Update the given list of entities. - * - * @param entities The entities to update. - * @param options The {@link WriteOptions} to use. - * @return The entities given. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and updates all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to - * {@link #update(Object, WriteOptions)} as that's the preferred approach. This method will be removed in - * Version 2.0. - */ - @Deprecated - List update(List entities, WriteOptions options); - - /** - * Updates the given entity asynchronously. - * - * @param entity The entity to update - * @return The entity given - * @see #updateAsynchronously(Object, WriteListener) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #updateAsynchronously(Object, WriteListener)}. - */ - @Deprecated - T updateAsynchronously(T entity); - - /** - * Updates the given entity asynchronously. - * - * @param entity The entity to update - * @return The entity given - * @see #updateAsynchronously(Object, WriteOptions) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #updateAsynchronously(Object, WriteListener, WriteOptions)}. - */ - @Deprecated - T updateAsynchronously(T entity, WriteOptions options); - - /** - * Updates the given entity asynchronously. - * - * @param entity The entity to update - * @param listener The listener to receive notification of completion - * @return A {@link Cancellable} enabling the cancellation of the operation - */ - Cancellable updateAsynchronously(T entity, WriteListener listener); - - /** - * Updates the given entity asynchronously. - * - * @param entity The entity to update - * @param listener The listener to receive notification of completion - * @param options The {@link WriteOptions} to use - * @return A {@link Cancellable} enabling the cancellation of the operation - */ - Cancellable updateAsynchronously(T entity, WriteListener listener, WriteOptions options); - - /** - * Updates the given entities asynchronously in a batch. - * - * @param entities The entities to update - * @return The entities given - * @see #updateAsynchronously(List, WriteListener) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #updateAsynchronously(List, WriteListener)}. - */ - @Deprecated - List updateAsynchronously(List entities); - - /** - * Updates the given entities asynchronously in a batch. - * - * @param entities The entities to update - * @return The entities given - * @see #updateAsynchronously(List, WriteListener, WriteOptions) - * @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor - * {@link #updateAsynchronously(List, WriteListener, WriteOptions)}. - */ - @Deprecated - List updateAsynchronously(List entities, WriteOptions options); - - /** - * Updates the given entities asynchronously in a batch. - * - * @param entities The entities to update - * @param listener The listener to receive notification of completion - * @return A {@link Cancellable} enabling the cancellation of the operation - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable updateAsynchronously(List entities, WriteListener listener); - - /** - * Updates the given entities asynchronously in a batch. - * - * @param entities The entities to update - * @param listener The listener to receive notification of completion - * @param options The {@link WriteOptions} to use - * @return A {@link Cancellable} enabling the cancellation of the operation - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable updateAsynchronously(List entities, WriteListener listener, WriteOptions options); + T update(T entity, WriteOptions options) throws DataAccessException; /** * Remove the given object from the table by id. * - * @param entityClass The entity type must not be {@literal null}. * @param id 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. */ - void deleteById(Class entityClass, Object id); + boolean deleteById(Object id, Class entityClass) throws DataAccessException; /** - * Remove the given object from the table by id. + * Delete the given entity and return the entity if the delete was applied. * * @param entity must not be {@literal null}. + * @return the deleted entity. + * @throws DataAccessException if there is any problem executing the query. */ - void delete(T entity); + T delete(T entity) throws DataAccessException; /** - * Remove the given object from the table by id. + * Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied. * * @param entity must not be {@literal null}. * @param options may be {@literal null}. + * @return the deleted entity. + * @throws DataAccessException if there is any problem executing the query. */ - void delete(T entity, QueryOptions options); + T delete(T entity, QueryOptions options) throws DataAccessException; /** - * Remove the given objects from the table by id. - * - * @param entities must not be {@literal null}. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #delete(Object)} - * as that's the preferred approach. This method will be removed in Version 2.0. - */ - @Deprecated - void delete(List entities); - - /** - * Remove the given objects from the table by id. - * - * @param entities must not be {@literal null}. - * @param options may be {@literal null}. - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use - * {@link #batchOps()} for if your intent is batching or issue multiple calls to - * {@link #delete(Object, WriteOptions)} as that's the preferred approach. This method will be removed in - * Version 2.0. - */ - @Deprecated - void delete(List entities, QueryOptions options); - - /** - * Deletes all entities of a given class. + * 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. */ - void deleteAll(Class entityClass); - - /** - * Remove the given object from the table by id. - * - * @param entity The object to delete - */ - Cancellable deleteAsynchronously(T entity); - - /** - * Remove the given object from the table by id. - * - * @param entity The object to delete - * @param options The {@link QueryOptions} to use - */ - Cancellable deleteAsynchronously(T entity, QueryOptions options); - - /** - * Remove the given object from the table by id. - * - * @param entity The object to delete - * @param listener The {@link DeletionListener} to receive notification upon completion - */ - Cancellable deleteAsynchronously(T entity, DeletionListener listener); - - /** - * Remove the given object from the table by id. - * - * @param entity The object to delete - * @param listener The {@link DeletionListener} to receive notification upon completion - * @param options The {@link QueryOptions} to use - */ - Cancellable deleteAsynchronously(T entity, DeletionListener listener, QueryOptions options); - - /** - * Remove the given objects from the table by id. - * - * @param entities The objects to delete - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable deleteAsynchronously(List entities); - - /** - * Remove the given objects from the table by id. - * - * @param entities The objects to delete - * @param listener The {@link DeletionListener} to receive notification upon completion - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable deleteAsynchronously(List entities, DeletionListener listener); - - /** - * Remove the given objects from the table by id. - * - * @param entities The objects to delete - * @param options The {@link QueryOptions} to use - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable deleteAsynchronously(List entities, QueryOptions options); - - /** - * Remove the given objects from the table by id. - * - * @param entities The objects to delete - * @param listener The {@link DeletionListener} to receive notification upon completion - * @param options The {@link QueryOptions} to use - * @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's - * not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method - * will be removed in Version 2.0. - */ - @Deprecated - Cancellable deleteAsynchronously(List entities, DeletionListener listener, QueryOptions options); + void truncate(Class entityClass) throws DataAccessException; /** * Returns a new {@link CassandraBatchOperations}. Each {@link CassandraBatchOperations} instance can be executed only @@ -625,4 +258,11 @@ public interface CassandraOperations extends CqlOperations { */ CassandraConverter getConverter(); + /** + * Expose the underlying {@link CqlOperations} to allow CQL operations. + * + * @return the underlying {@link CqlOperations}. + * @see CqlOperations + */ + CqlOperations getCqlOperations(); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java index c57c69f0c..e9aee1cb0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java @@ -90,7 +90,7 @@ public class CassandraPersistentEntitySchemaCreator { List specifications = createUserTypeSpecifications(ifNotExists); for (CreateUserTypeSpecification specification : specifications) { - cassandraAdminOperations.execute(CreateUserTypeCqlGenerator.toCql(specification)); + cassandraAdminOperations.getCqlOperations().execute(CreateUserTypeCqlGenerator.toCql(specification)); } } @@ -111,7 +111,7 @@ public class CassandraPersistentEntitySchemaCreator { List specifications = createTableSpecifications(ifNotExists); for (CreateTableSpecification specification : specifications) { - cassandraAdminOperations.execute(CreateTableCqlGenerator.toCql(specification)); + cassandraAdminOperations.getCqlOperations().execute(CreateTableCqlGenerator.toCql(specification)); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java index b46027c5b..38656d5c2 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -1,11 +1,11 @@ /* - * Copyright 2013-2016 the original author or authors + * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,391 +15,244 @@ */ package org.springframework.data.cassandra.core; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Map; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import org.springframework.cassandra.core.AsynchronousQueryListener; -import org.springframework.cassandra.core.Cancellable; +import org.springframework.cassandra.core.CqlOperations; +import org.springframework.cassandra.core.CqlProvider; import org.springframework.cassandra.core.CqlTemplate; -import org.springframework.cassandra.core.QueryForObjectListener; import org.springframework.cassandra.core.QueryOptions; -import org.springframework.cassandra.core.RowCallback; +import org.springframework.cassandra.core.SessionCallback; import org.springframework.cassandra.core.WriteOptions; import org.springframework.cassandra.core.cql.CqlIdentifier; -import org.springframework.cassandra.core.support.EmptyResultSet; import org.springframework.cassandra.core.util.CollectionUtils; -import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.convert.MappingCassandraConverter; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; -import org.springframework.data.cassandra.mapping.CassandraPersistentProperty; -import org.springframework.data.convert.EntityWriter; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.PropertyHandler; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; -import com.datastax.driver.core.querybuilder.Batch; -import com.datastax.driver.core.querybuilder.Clause; +import com.datastax.driver.core.SimpleStatement; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.exceptions.DriverException; import com.datastax.driver.core.querybuilder.Delete; -import com.datastax.driver.core.querybuilder.Delete.Where; import com.datastax.driver.core.querybuilder.Insert; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.querybuilder.Truncate; import com.datastax.driver.core.querybuilder.Update; /** - * The CassandraTemplate is a convenient API for all Cassandra operations using POJOs with their Spring Data Cassandra - * mapping information. For low-level Cassandra operation, see {@link CqlTemplate}. + * Primary implementation of {@link CassandraOperations}. It simplifies the use of Cassandra usage and helps to avoid + * common errors. It executes core Cassandra workflow. This class executes CQL queries or updates, initiating iteration + * over {@link ResultSet} and catching Cassandra exceptions and translating them to the generic, more informative + * exception hierarchy defined in the {@code org.springframework.dao} package. + *

+ * Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get + * prepared in an application context and given to services as bean reference. + *

+ * Note: The {@link Session} 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. * - * @author Alex Shvid - * @author David Webb - * @author Matthew T. Adams - * @author Oliver Gierke * @author Mark Paluch - * @author John Blum - * @see CqlTemplate - * @see CassandraOperations + * @since 2.0 */ -public class CassandraTemplate extends CqlTemplate implements CassandraOperations { +public class CassandraTemplate implements CassandraOperations { - protected CassandraConverter cassandraConverter; - protected CassandraMappingContext mappingContext; + private final CassandraConverter converter; + private final CassandraMappingContext mappingContext; + private final CqlOperations cqlOperations; /** - * Default constructor used to wire in the required components later. - */ - public CassandraTemplate() {} - - /** - * Creates a new {@link CassandraTemplate} for the given {@link Session}. - * - * @param session Cassandra {@link Session} connected to the Cassandra cluster instance; - * must not be {@literal null}. - * @see com.datastax.driver.core.Session - */ - public CassandraTemplate(Session session) { - this(session, null); - } - - /** - * Creates an instance of {@link CassandraTemplate} initialized with the given {@link Session} - * and {@link CassandraConverter}. + * Creates an instance of {@link CassandraTemplate} initialized with the given {@link Session} and a default + * {@link MappingCassandraConverter}. * * @param session {@link Session} used to interact with Cassandra; must not be {@literal null}. - * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; - * must not be {@literal null}. - * @see org.springframework.data.cassandra.convert.CassandraConverter - * @see com.datastax.driver.core.Session + * @see CassandraConverter + * @see Session + */ + public CassandraTemplate(Session session) { + this(session, newConverter()); + } + + /** + * Creates an instance of {@link CassandraTemplate} initialized with the given {@link Session} and + * {@link CassandraConverter}. + * + * @param session {@link Session} used to interact with Cassandra; must not be {@literal null}. + * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be + * {@literal null}. + * @see CassandraConverter + * @see Session */ public CassandraTemplate(Session session, CassandraConverter converter) { - setSession(session); - setConverter(resolveConverter(converter)); - } - private static CassandraConverter resolveConverter(CassandraConverter cassandraConverter) { - return (cassandraConverter != null ? cassandraConverter : getDefaultCassandraConverter()); - } + Assert.notNull(session, "Session must not be null"); + Assert.notNull(converter, "CassandraConverter must not be null"); - private static CassandraConverter getDefaultCassandraConverter() { - - MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter(); - mappingCassandraConverter.afterPropertiesSet(); - return mappingCassandraConverter; + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + this.cqlOperations = new CqlTemplate(session); } /** - * Set the {@link CassandraConverter} used by this template to perform conversions. + * Creates an instance of {@link CassandraTemplate} initialized with the given {@link CqlOperations} and + * {@link CassandraConverter}. * - * @param cassandraConverter Converter used to perform conversion of Cassandra data types to entity types. - * Must not be {@literal null}. - * @throws IllegalArgumentException if {@code cassandraConverter} is null. + * @param cqlOperations {@link CqlOperations} used to interact with Cassandra; must not be {@literal null}. + * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be + * {@literal null}. + * @see CassandraConverter + * @see Session */ - public void setConverter(CassandraConverter cassandraConverter) { + public CassandraTemplate(CqlOperations cqlOperations, CassandraConverter converter) { - Assert.notNull(cassandraConverter, "CassandraConverter must not be null"); + Assert.notNull(cqlOperations, "CqlOperations must not be null"); + Assert.notNull(converter, "CassandraConverter must not be null"); - this.cassandraConverter = cassandraConverter; - this.mappingContext = cassandraConverter.getMappingContext(); + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + this.cqlOperations = cqlOperations; } - /* (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraOperations#getConverter() + private static MappingCassandraConverter newConverter() { + + MappingCassandraConverter converter = new MappingCassandraConverter(); + converter.afterPropertiesSet(); + + return converter; + } + + // ------------------------------------------------------------------------- + // Methods dealing with static CQL + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#select(java.lang.String, java.lang.Class) */ - @Override - public CassandraConverter getConverter() { - return cassandraConverter; - } - - /** - * @deprecated as of 1.5, see {@link #getMappingContext()}. - */ - @Deprecated - public CassandraMappingContext getCassandraMappingContext() { - return mappingContext; - } - - /** - * Returns the {@link CassandraMappingContext}. - * - * @return the {@link CassandraMappingContext}. - */ - public CassandraMappingContext getMappingContext() { - return mappingContext; - } - - /* (non-Javadoc) - * @see org.springframework.cassandra.support.CassandraAccessor#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() { - - super.afterPropertiesSet(); - - Assert.notNull(cassandraConverter, "CassandraConverter must not be null"); - Assert.notNull(mappingContext, "CassandraMappingContext must not be null"); - } - - @Override - public boolean exists(Class entityClass, Object id) { - - Assert.notNull(entityClass, "EntityClass must not be null"); - Assert.notNull(id, "Id must not be null"); - - CassandraPersistentEntity entity = getPersistentEntity(entityClass); - Select select = QueryBuilder.select().countAll().from(entity.getTableName().toCql()); - - cassandraConverter.write(id, select.where(), entity); - - Long count = queryForObject(select, Long.class); - - return count != 0; - } - - @Override - public long count(Class type) { - return count(getTableName(type).toCql()); - } - - @Override - public void delete(List entities) { - delete(entities, null); - } - - @Override - public void delete(List entities, QueryOptions options) { - doBatchDelete(entities, options); - } - - @Override - public void deleteById(Class entityClass, Object id) { - - Assert.notNull(entityClass, "EntityClass must not be null"); - Assert.notNull(id, "Id must not be null"); - - CassandraPersistentEntity entity = getPersistentEntity(entityClass); - Delete delete = QueryBuilder.delete().from(entity.getTableName().toCql()); - - cassandraConverter.write(id, delete.where(), entity); - - execute(delete); - } - - @Override - public void delete(T entity) { - delete(entity, null); - } - - @Override - public void delete(T entity, QueryOptions options) { - doDelete(entity, options); - } - - @Override - public Cancellable deleteAsynchronously(List entities) { - return doBatchDeleteAsync(entities, null, null); - } - - @Override - public Cancellable deleteAsynchronously(List entities, QueryOptions options) { - return doBatchDeleteAsync(entities, null, options); - } - - @Override - public Cancellable deleteAsynchronously(List entities, DeletionListener listener) { - return doBatchDeleteAsync(entities, listener, null); - } - - @Override - public Cancellable deleteAsynchronously(List entities, DeletionListener listener, QueryOptions options) { - return doBatchDeleteAsync(entities, listener, options); - } - - @Override - public Cancellable deleteAsynchronously(T entity) { - return doDeleteAsync(entity, null, null); - } - - @Override - public Cancellable deleteAsynchronously(T entity, QueryOptions options) { - return doDeleteAsync(entity, null, options); - } - - @Override - public Cancellable deleteAsynchronously(T entity, DeletionListener listener) { - return doDeleteAsync(entity, listener, null); - } - - @Override - public Cancellable deleteAsynchronously(T entity, DeletionListener listener, QueryOptions options) { - return doDeleteAsync(entity, listener, options); - } - - @Override - public CqlIdentifier getTableName(Class entityClass) { - return getPersistentEntity(entityClass).getTableName(); - } - - @Override - public List insert(List entities) { - return insert(entities, null); - } - - @Override - public List insert(List entities, WriteOptions options) { - return doBatchInsert(entities, options); - } - - @Override - public T insert(T entity) { - return insert(entity, null); - } - - @Override - public T insert(T entity, WriteOptions options) { - return doInsert(entity, options); - } - - /** - * @deprecated as of 1.2, see {@link #insertAsynchronously(Object, WriteListener)} - */ - @Deprecated - @Override - public List insertAsynchronously(List entities) { - doInsertAsynchronously(entities, null, null); - return entities; - } - - /** - * @deprecated as of 1.2, see {@link #insertAsynchronously(List, WriteListener, WriteOptions)} - */ - @Deprecated - @Override - public List insertAsynchronously(List entities, WriteOptions options) { - doInsertAsynchronously(entities, null, options); - return entities; - } - - @Override - public Cancellable insertAsynchronously(List entities, WriteListener listener) { - return doInsertAsynchronously(entities, listener, null); - } - - @Override - public Cancellable insertAsynchronously(List entities, WriteListener listener, WriteOptions options) { - return doInsertAsynchronously(entities, listener, options); - } - - /** - * This method resolves ambiguity the compiler sees as a result of type erasure between - * {@link #insertAsynchronously(Object, WriteListener, WriteOptions)} - * and {@link #insertAsynchronously(List, WriteListener, WriteOptions)}. - */ - protected Cancellable doInsertAsynchronously(List entities, WriteListener listener, WriteOptions options) { - return doBatchInsertAsync(entities, listener, options); - } - - /** - * @deprecated as of 1.2, see {@link #insertAsynchronously(List, WriteListener, WriteOptions)}. - */ - @Deprecated - @Override - public T insertAsynchronously(T entity) { - insertAsynchronously(entity, null, null); - return entity; - } - - /** - * @deprecated as of 1.2, see {@link #insertAsynchronously(List, WriteListener, WriteOptions)}. - */ - @Deprecated - @Override - public T insertAsynchronously(T entity, WriteOptions options) { - insertAsynchronously(entity, null, options); - return entity; - } - - @Override - public Cancellable insertAsynchronously(T entity, WriteListener listener) { - return insertAsynchronously(entity, listener, null); - } - - @Override - public Cancellable insertAsynchronously(T entity, WriteListener listener, WriteOptions options) { - return doInsertAsync(entity, listener, options); - } - - @Override - public List selectAll(Class entityClass) { - - Assert.notNull(entityClass, "EntityClass must not be null"); - - return select(QueryBuilder.select().all().from(getTableName(entityClass).toCql()), entityClass); - } - @Override public List select(String cql, Class entityClass) { - Assert.hasText(cql, "CQL must not be empty"); - Assert.notNull(entityClass, "EntityClass must not be null"); + Assert.hasText(cql, "Statement must not be empty"); - return select(cql, new CassandraConverterRowCallback(cassandraConverter, entityClass)); + return select(new SimpleStatement(cql), entityClass); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperationsNG#stream(java.lang.String, java.lang.Class) + */ + @Override + public Stream stream(String cql, Class entityClass) throws DataAccessException { + + Assert.hasText(cql, "Statement must not be empty"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return stream(new SimpleStatement(cql), entityClass); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(java.lang.String, java.lang.Class) + */ + @Override + public T selectOne(String cql, Class entityClass) { + + Assert.hasText(cql, "Statement must not be empty"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return selectOne(new SimpleStatement(cql), entityClass); + } + + // ------------------------------------------------------------------------- + // Methods dealing with com.datastax.driver.core.Statement + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#select(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public List select(Statement statement, Class entityClass) { + + Assert.notNull(statement, "Statement must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return cqlOperations.query(statement, (row, rowNum) -> converter.read(entityClass, row)); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperationsNG#stream(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public Stream stream(Statement statement, Class entityClass) throws DataAccessException { + + Assert.notNull(statement, "Statement must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + return StreamSupport.stream(cqlOperations.queryForResultSet(statement).spliterator(), false) + .map(row -> converter.read(entityClass, row)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(com.datastax.driver.core.Statement, java.lang.Class) + */ + @Override + public T selectOne(Statement statement, Class entityClass) { + + List result = select(statement, entityClass); + + if (result.isEmpty()) { + return null; + } + + return result.get(0); + } + + // ------------------------------------------------------------------------- + // Methods dealing with entities + // ------------------------------------------------------------------------- + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#selectOneById(java.lang.Object, java.lang.Class) + */ + @Override + public T selectOneById(Object id, Class entityClass) { + + Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); + + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + Select select = QueryBuilder.select().all().from(entity.getTableName().toCql()); + + converter.write(id, select.where(), entity); + + return selectOne(select, entityClass); } @Override - public List select(Select select, Class entityClass) { + public List selectBySimpleIds(Iterable ids, Class entityClass) throws DataAccessException { - Assert.notNull(select, "Select must not be null"); - Assert.notNull(entityClass, "EntityClass must not be null"); - - return select(select, new CassandraConverterRowCallback(cassandraConverter, entityClass)); - } - - @Override - public List selectBySimpleIds(Class entityClass, Iterable ids) { - - Assert.notNull(entityClass, "EntityClass must not be null"); Assert.notNull(ids, "Ids must not be null"); + Assert.notNull(entityClass, "EntityClass must not be null"); CassandraPersistentEntity entity = getPersistentEntity(entityClass); if (entity.getIdProperty() == null || entity.getIdProperty().isCompositePrimaryKey()) { String typeName = (entity.getIdProperty() == null ? "Unknown" - : entity.getIdProperty().getCompositePrimaryKeyEntity().getType().getName()); + : entity.getIdProperty().getCompositePrimaryKeyEntity().getType().getName()); - throw new IllegalArgumentException(String.format( - "Entity class [%s] uses a composite primary key class [%s] which this method can't support", - entityClass.getName(), typeName)); + throw new IllegalArgumentException( + String.format("Entity class [%s] uses a composite primary key class [%s] which this method can't support", + entityClass.getName(), typeName)); } Select select = QueryBuilder.select().all().from(entity.getTableName().toCql()); @@ -409,824 +262,213 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation return select(select, entityClass); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#exists(java.lang.Object, java.lang.Class) + */ @Override - public T selectOneById(Class entityClass, Object id) { + public boolean exists(Object id, Class entityClass) { - Assert.notNull(entityClass, "EntityClass must not be null"); Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); CassandraPersistentEntity entity = getPersistentEntity(entityClass); - Select select = QueryBuilder.select().all().from(entity.getTableName().toCql()); + Select select = QueryBuilder.select().from(entity.getTableName().toCql()); + converter.write(id, select.where(), entity); - cassandraConverter.write(id, select.where(), entity); - - return selectOne(select, entityClass); - } - - @Deprecated - protected void appendIdCriteria(ClauseCallback clauseCallback, CassandraPersistentEntity entity, Map id) { - - for (Map.Entry entry : id.entrySet()) { - - CassandraPersistentProperty property = entity.getPersistentProperty(entry.getKey().toString()); - - if (property == null) { - throw new IllegalArgumentException(String.format( - "Entity class [%s] has no persistent property named [%s]", - entity.getType().getName(), entry.getKey())); - } - - clauseCallback.doWithClause(QueryBuilder.eq(property.getColumnName().toCql(), entry.getValue())); - } - } - - @Deprecated - @SuppressWarnings("deprecation") - protected void appendIdCriteria(final ClauseCallback clauseCallback, CassandraPersistentEntity entity, Object id) { - - if (id instanceof Map) { - - appendIdCriteria(clauseCallback, entity, (Map) id); - return; - } - - CassandraPersistentProperty idProperty = entity.getIdProperty(); - - if (idProperty.isCompositePrimaryKey()) { - - CassandraPersistentEntity idEntity = idProperty.getCompositePrimaryKeyEntity(); - PersistentPropertyAccessor idAccessor = idEntity.getPropertyAccessor(id); - - final ConvertingPropertyAccessor covertingIdAccessor = new ConvertingPropertyAccessor(idAccessor, - cassandraConverter.getConversionService()); - - idEntity.doWithProperties(new PropertyHandler() { - - @Override - public void doWithPersistentProperty(CassandraPersistentProperty property) { - clauseCallback.doWithClause(QueryBuilder.eq(property.getColumnName().toCql(), - covertingIdAccessor.getProperty(property, property.getActualType()))); - } - }); - - return; - } - - clauseCallback.doWithClause(QueryBuilder.eq(idProperty.getColumnName().toCql(), id)); - } - - @Deprecated - @SuppressWarnings("deprecation") - protected void appendIdCriteria(final Select.Where where, CassandraPersistentEntity entity, Object id) { - - appendIdCriteria(new ClauseCallback() { - - @Override - public void doWithClause(Clause clause) { - where.and(clause); - } - }, entity, id); - } - - @Deprecated - @SuppressWarnings("deprecation") - protected void appendIdCriteria(final Delete.Where where, CassandraPersistentEntity entity, Object id) { - - appendIdCriteria(new ClauseCallback() { - - @Override - public void doWithClause(Clause clause) { - where.and(clause); - } - }, entity, id); + return cqlOperations.queryForResultSet(select).iterator().hasNext(); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#count(java.lang.Class) + */ @Override - public T selectOne(String cql, Class entityClass) { + public long count(Class entityClass) { - Assert.notNull(entityClass, "EntityClass must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); - return selectOne(cql, new CassandraConverterRowCallback(cassandraConverter, entityClass)); + Select select = QueryBuilder.select().countAll().from(getPersistentEntity(entityClass).getTableName().toCql()); + + return cqlOperations.queryForObject(select, Long.class); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object) + */ @Override - public T selectOne(Select select, Class entityClass) { - - Assert.notNull(entityClass, "EntityClass must not be null"); - - return selectOne(select, new CassandraConverterRowCallback(cassandraConverter, entityClass)); + public T insert(T entity) { + return insert(entity, null); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.cassandra.core.WriteOptions) + */ @Override - public List update(List entities) { - return update(entities, null); - } - - @Override - public List update(List entities, WriteOptions options) { - return doBatchUpdate(entities, options); + public T insert(T entity, WriteOptions options) { + + Assert.notNull(entity, "Entity must not be null"); + + CqlIdentifier tableName = getTableName(entity.getClass()); + + Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, converter); + + return cqlOperations.execute(new StatementCallback<>(insert, entity)); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object) + */ @Override public T update(T entity) { return update(entity, null); } + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.cassandra.core.WriteOptions) + */ @Override public T update(T entity, WriteOptions options) { - return doUpdate(entity, options); - } - @Override - public List updateAsynchronously(List entities) { - doUpdateAsynchronously(entities, null, null); - return entities; - } + Assert.notNull(entity, "Entity must not be null"); - @Override - public List updateAsynchronously(List entities, WriteOptions options) { - doUpdateAsynchronously(entities, null, options); - return entities; - } + CqlIdentifier tableName = getTableName(entity.getClass()); - @Override - public Cancellable updateAsynchronously(List entities, WriteListener listener) { - return doUpdateAsynchronously(entities, listener, null); - } + Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, converter); - @Override - public Cancellable updateAsynchronously(List entities, WriteListener listener, WriteOptions options) { - return doUpdateAsynchronously(entities, listener, options); - } - - /** - * This method resolves ambiguity the compiler sees as a result of type erasure between - * {@link #updateAsynchronously(Object, WriteListener, WriteOptions)} - * and {@link #updateAsynchronously(List, WriteListener, WriteOptions)}. - */ - protected Cancellable doUpdateAsynchronously(List entities, WriteListener listener, WriteOptions options) { - return doBatchUpdateAsync(entities, listener, options); - } - - @Override - public T updateAsynchronously(T entity) { - updateAsynchronously(entity, null, null); - return entity; - } - - @Override - public T updateAsynchronously(T entity, WriteOptions options) { - updateAsynchronously(entity, null, options); - return entity; - } - - @Override - public Cancellable updateAsynchronously(T entity, WriteListener listener) { - return updateAsynchronously(entity, listener, null); - } - - @Override - public Cancellable updateAsynchronously(T entity, WriteListener listener, WriteOptions options) { - return doUpdateAsync(entity, listener, options); - } - - protected List select(String query, CassandraConverterRowCallback rowCallback) { - return processResultSet(doExecuteQueryReturnResultSet(query), rowCallback); - } - - protected List select(Select query, CassandraConverterRowCallback rowCallback) { - return processResultSet(doExecuteQueryReturnResultSet(query), rowCallback); - } - - private List processResultSet(ResultSet resultSet, RowCallback rowCallback) { - List result = new ArrayList(); - - for (Row row : EmptyResultSet.nullSafeResultSet(resultSet)) { - result.add(rowCallback.doWith(row)); - } - - return result; + return cqlOperations.execute(new StatementCallback<>(update, entity)); } /* * (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraOperations#stream(java.lang.String, java.lang.Class) + * @see org.springframework.data.cassandra.core.CassandraOperations#deleteById(java.lang.Object, java.lang.Class) */ - public Iterator stream(String query, Class entityClass) { + @Override + public boolean deleteById(Object id, Class entityClass) { - Assert.hasText(query, "Query must not be empty"); - Assert.notNull(entityClass, "EntityClass must not be null"); + Assert.notNull(id, "Id must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); - ResultSet resultSet = doExecuteQueryReturnResultSet(query); + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + Delete delete = QueryBuilder.delete().from(entity.getTableName().toCql()); - return (resultSet != null ? toIterator(resultSet, entityClass) : Collections.emptyIterator()); + converter.write(id, delete.where(), entity); + + return cqlOperations.execute(delete); } /* * (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraTemplate.ResultSetIteratorAdapter + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object) */ - @SuppressWarnings("unchecked") - private Iterator toIterator(ResultSet resultSet, Class entityClass) { - - return new ResultSetIteratorAdapter(resultSet.iterator(), getExceptionTranslator(), - new CassandraConverterRowCallback(cassandraConverter, entityClass)); - } - - protected T selectOne(String query, CassandraConverterRowCallback rowCallback) { - - Iterator iterator = query(logCql(query)).iterator(); - T result = null; - - if (iterator.hasNext()) { - Row row = iterator.next(); - - result = rowCallback.doWith(row); - - if (iterator.hasNext()) { - // TODO: this is not necessarily a duplicate key depending on the query predicate! - // TODO: should probably be IncorrectResultSizeDataAccessException - throw new DuplicateKeyException(String.format("found two or more results in query [%s]", query)); - } - } - - return result; - } - - protected T selectOne(Select query, CassandraConverterRowCallback rowCallback) { - - Iterator iterator = query(query).iterator(); - T result = null; - - if (iterator.hasNext()) { - Row row = iterator.next(); - - result = rowCallback.doWith(row); - - if (iterator.hasNext()) { - // TODO: this is not necessarily a duplicate key depending on the query predicate! - // TODO: should probably be IncorrectResultSizeDataAccessException - throw new DuplicateKeyException(String.format("found two or more results in query [%s]", query)); - } - } - - return result; - } - - // TODO: handle possible IndexOutOfBoundsException if the List of entities is empty - protected void doBatchDelete(List entities, QueryOptions options) { - execute(createDeleteBatchQuery(getTableName(entities.get(0).getClass()).toCql(), entities, options, - cassandraConverter)); - } - - // TODO: handle possible IndexOutOfBoundsException if the List of entities is empty - protected Cancellable doBatchDeleteAsync(final List entities, final DeletionListener listener, - QueryOptions options) { - - AsynchronousQueryListener queryListener = (listener == null ? null : new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - resultSetFuture.getUninterruptibly(); - listener.onDeletionComplete(entities); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - - return executeAsynchronously(createDeleteBatchQuery( - getTableName(entities.get(0).getClass()).toCql(), entities, options, cassandraConverter), - queryListener); - } - - protected T doInsert(T entity, WriteOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - execute(createInsertQuery(entity, options)); - - return entity; - } - - Insert createInsertQuery(T entity, WriteOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - return createInsertQuery(getTableName(entity.getClass()).toCql(), entity, options, cassandraConverter); - } - - protected Cancellable doInsertAsync(final T entity, final WriteListener listener, WriteOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - Insert insert = createInsertQuery(entity, options); - - AsynchronousQueryListener queryListener = (listener == null ? null : new AsynchronousQueryListener() { - - @Override - @SuppressWarnings("unchecked") - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - resultSetFuture.getUninterruptibly(); - listener.onWriteComplete(Collections.singletonList(entity)); - } catch (Exception x) { - listener.onException(translateExceptionIfPossible(x)); - } - } - }); - - return executeAsynchronously(insert, queryListener); - } - - protected List doBatchInsert(List entities, WriteOptions options) { - return doBatchWrite(entities, options, true); - } - - protected List doBatchUpdate(List entities, WriteOptions options) { - return doBatchWrite(entities, options, false); - } - - protected List doBatchWrite(List entities, WriteOptions options, boolean insert) { - - if (CollectionUtils.isEmpty(entities)) { - if (logger.isWarnEnabled()) { - logger.warn("no-op due to given null or empty List"); - } - - return entities; - } - - String tableName = getTableName(entities.get(0).getClass()).toCql(); - - Batch batch = (insert ? createInsertBatchQuery(tableName, entities, options, cassandraConverter) - : createUpdateBatchQuery(tableName, entities, options, cassandraConverter)); - - execute(batch); - - return entities; - } - - /** - * Asynchronously performs a batch insert or update. - * - * @param entities The entities to insert or update. - * @param listener The listener that will receive notification of the completion of the batch insert or update. May be - * null. - * @param options The {@link WriteOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. - */ - protected Cancellable doBatchInsertAsync(final List entities, final WriteListener listener, - WriteOptions options) { - - return doBatchWriteAsync(entities, listener, options, true); - } - - /** - * Asynchronously performs a batch insert or update. - * - * @param entities The entities to insert or update. - * @param listener The listener that will receive notification of the completion of the batch insert or update. May be - * null. - * @param options The {@link WriteOptions} to use. May be null. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. - */ - protected Cancellable doBatchUpdateAsync(final List entities, final WriteListener listener, - WriteOptions options) { - - return doBatchWriteAsync(entities, listener, options, false); - } - - /** - * Asynchronously performs a batch insert or update. - * - * @param entities The entities to insert or update. - * @param listener The listener that will receive notification of the completion of the batch insert or update. May be - * null. - * @param options The {@link WriteOptions} to use. May be null. - * @param insert If true, then an insert is performed, else an update is performed. - * @return A {@link Cancellable} that can be used to cancel the query if necessary. - */ - protected Cancellable doBatchWriteAsync(final List entities, final WriteListener listener, - WriteOptions options, boolean insert) { - - if (CollectionUtils.isEmpty(entities)) { - if (logger.isWarnEnabled()) { - logger.warn("no-op due to given null or empty list"); - } - - return new Cancellable() { - - @Override - public void cancel() { - if (logger.isWarnEnabled()) { - logger.warn("no-op query cancellation due to given null or empty list"); - } - } - }; - } - - String tableName = getTableName(entities.get(0).getClass()).toCql(); - - Batch batch = (insert ? createInsertBatchQuery(tableName, entities, options, cassandraConverter) - : createUpdateBatchQuery(tableName, entities, options, cassandraConverter)); - - AsynchronousQueryListener queryListener = (listener == null ? null : new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - resultSetFuture.getUninterruptibly(); - listener.onWriteComplete(entities); - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }); - - return executeAsynchronously(batch, queryListener); - } - - Delete createDeleteQuery(T entity, QueryOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - return createDeleteQuery(getTableName(entity.getClass()).toCql(), entity, options, cassandraConverter); - } - - protected void doDelete(T entity, QueryOptions options) { - Assert.notNull(entity, "Entity must not be null"); - - execute(createDeleteQuery(entity, options)); - } - - protected Cancellable doDeleteAsync(final T entity, final DeletionListener listener, QueryOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - Delete delete = createDeleteQuery(entity, options); - - AsynchronousQueryListener queryListener = (listener == null ? null : new AsynchronousQueryListener() { - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - resultSetFuture.getUninterruptibly(); - listener.onDeletionComplete(Collections.singletonList(entity)); - } catch (Exception x) { - listener.onException(translateExceptionIfPossible(x)); - } - } - }); - - return executeAsynchronously(delete, queryListener); - } - - Update createUpdateQuery(T entity, WriteOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - return createUpdateQuery(getTableName(entity.getClass()).toCql(), entity, options, cassandraConverter); - } - - protected T doUpdate(T entity, WriteOptions options) { - Assert.notNull(entity, "Entity must not be null"); - - execute(createUpdateQuery(entity, options)); - - return entity; - } - - protected Cancellable doUpdateAsync(final T entity, final WriteListener listener, WriteOptions options) { - - Assert.notNull(entity, "Entity must not be null"); - - AsynchronousQueryListener queryListener = (listener == null ? null : new AsynchronousQueryListener() { - - @Override - @SuppressWarnings("unchecked") - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - resultSetFuture.getUninterruptibly(); - listener.onWriteComplete(Collections.singletonList(entity)); - } catch (Exception x) { - listener.onException(translateExceptionIfPossible(x)); - } - } - }); - - return executeAsynchronously(createUpdateQuery(entity, options), queryListener); + @Override + public T delete(T entity) { + return delete(entity, null); } /* * (non-Javadoc) - * @see org.springframework.data.cassandra.core.CassandraOperations#batchOps(java.lang.Class) + * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, org.springframework.cassandra.core.QueryOptions) + */ + @Override + public T delete(T entity, QueryOptions options) { + + Assert.notNull(entity, "Entity must not be null"); + + CqlIdentifier tableName = getTableName(entity.getClass()); + + Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, converter); + + return cqlOperations.execute(new StatementCallback<>(delete, entity)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#truncate(java.lang.Class) + */ + @Override + public void truncate(Class entityClass) { + + Assert.notNull(entityClass, "Entity type must not be null"); + Truncate truncate = QueryBuilder.truncate(getPersistentEntity(entityClass).getTableName().toCql()); + + cqlOperations.execute(truncate); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#getConverter() + */ + @Override + public CassandraConverter getConverter() { + return converter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperations#CqlOperations() + */ + @Override + public CqlOperations getCqlOperations() { + return cqlOperations; + } + + // ------------------------------------------------------------------------- + // Implementation hooks and helper methods + // ------------------------------------------------------------------------- + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperationsNG#getTableName(java.lang.Class) + */ + @Override + public CqlIdentifier getTableName(Class entityClass) { + return getPersistentEntity(ClassUtils.getUserClass(entityClass)).getTableName(); + } + + /* (non-Javadoc) + * @see org.springframework.data.cassandra.core.CassandraOperationsNG#batchOps() */ @Override public CassandraBatchOperations batchOps() { return new CassandraBatchTemplate(this); } - /** - * Generates a Query Object for an insert. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectToUpdate the object to save, must not be {@literal null}. - * @param options optional {@link WriteOptions} to apply to the {@link Insert} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write insert values. - * @return The Query object to run with session.execute(); - */ - public static Insert createInsertQuery(String tableName, Object objectToUpdate, WriteOptions options, - EntityWriter entityWriter) { + protected CassandraPersistentEntity getPersistentEntity(Class entityClass) { - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectToUpdate, "Object to insert must not be null"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Insert insert = addWriteOptions(QueryBuilder.insertInto(tableName), options); - - entityWriter.write(objectToUpdate, insert); - - return insert; - } - - /** - * Generates a Batch Object for multiple inserts. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectsToInsert the object to save, must not be empty and not {@literal null}. - * @param options optional {@link WriteOptions} to apply to the {@link Insert} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write insert values. - * @return The Query object to run with session.execute(); - */ - public static Batch createInsertBatchQuery(String tableName, List objectsToInsert, WriteOptions options, - EntityWriter entityWriter) { - - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectsToInsert, "Objects to insert must not be null"); - Assert.notEmpty(objectsToInsert, "Objects to insert must not be empty"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Batch batch = addQueryOptions(QueryBuilder.batch(), options); - - for (T entity : objectsToInsert) { - batch.add(createInsertQuery(tableName, entity, options, entityWriter)); - } - - return batch; - } - - /** - * Generates a Query Object for an Update. The {@link Update} uses the identity and values from the given - * {@code objectsToUpdate}. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectToUpdate the object to update, must not be {@literal null}. - * @param options optional {@link WriteOptions} to apply to the {@link Update} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write update assignments and where clauses. - * @return The Query object to run with session.execute(); - */ - public static Update createUpdateQuery(String tableName, Object objectToUpdate, WriteOptions options, - EntityWriter entityWriter) { - - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectToUpdate, "Object to update must not be null"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Update update = addWriteOptions(QueryBuilder.update(tableName), options); - - entityWriter.write(objectToUpdate, update); - - return update; - } - - /** - * Generates a Batch Object for multiple Updates. The {@link Update} uses the identity and values from the given - * {@code objectsToUpdate}. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectsToUpdate the object to update, must not be empty and not {@literal null}. - * @param options optional {@link WriteOptions} to apply to the {@link Update} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write update assignments and where clauses. - * @return The Query object to run with session.execute(); - */ - public static Batch createUpdateBatchQuery(String tableName, List objectsToUpdate, WriteOptions options, - EntityWriter entityWriter) { - - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectsToUpdate, "Objects to update must not be null"); - Assert.notEmpty(objectsToUpdate, "Objects to update must not be empty"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Batch batch = addQueryOptions(QueryBuilder.batch(), options); - - for (T objectToSave : objectsToUpdate) { - batch.add(createUpdateQuery(tableName, objectToSave, options, entityWriter)); - } - - return batch; - } - - /** - * @deprecated as of 1.2, method renamed. Use {@link #createUpdateBatchQuery(String, List, WriteOptions, EntityWriter)} - * @see #createUpdateBatchQuery(String, List, WriteOptions, EntityWriter) - */ - @Deprecated - public static Batch toUpdateBatchQuery(String tableName, List objectsToUpdate, WriteOptions options, - EntityWriter entityWriter) { - - return createUpdateBatchQuery(tableName, objectsToUpdate, options, entityWriter); - } - - /** - * @deprecated as of 1.2, method renamed. Use {@link #createUpdateQuery(String, Object, WriteOptions, EntityWriter)} - * @see #createUpdateQuery(String, Object, WriteOptions, EntityWriter) - */ - @Deprecated - public static Update toUpdateQueryX(String tableName, Object objectToUpdate, WriteOptions options, - EntityWriter entityWriter) { - - return createUpdateQuery(tableName, objectToUpdate, options, entityWriter); - } - - /** - * Create a Delete Query Object from an annotated POJO. The {@link Delete} uses the identity from the given - * {@code objectToDelete}. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectToDelete the object to delete, must not be {@literal null}. - * @param options optional {@link QueryOptions} to apply to the {@link Delete} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write delete where clauses. - * @return The Query object to run with session.execute(); - */ - public static Delete createDeleteQuery(String tableName, Object objectToDelete, QueryOptions options, - EntityWriter entityWriter) { - - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectToDelete, "Object to delete must not be null"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Delete.Selection deleteSelection = QueryBuilder.delete(); - Delete delete = deleteSelection.from(tableName); - Where where = addQueryOptions(delete.where(), options); - - entityWriter.write(objectToDelete, where); - - return delete; - } - - /** - * Create a Batch Query object for multiple deletes. - * - * @param tableName the table name, must not be empty and not {@literal null}. - * @param objectsToDelete the object to delete, must not be empty and not {@literal null}. - * @param options optional {@link QueryOptions} to apply to the {@link Delete} statement, may be {@literal null}. - * @param entityWriter the {@link EntityWriter} to write delete where clauses. - * @return The Query object to run with session.execute(); - */ - public static Batch createDeleteBatchQuery(String tableName, List objectsToDelete, QueryOptions options, - EntityWriter entityWriter) { - - Assert.hasText(tableName, "TableName must not be empty"); - Assert.notNull(objectsToDelete, "Objects to delete must not be null"); - Assert.notEmpty(objectsToDelete, "Objects to delete must not be empty"); - Assert.notNull(entityWriter, "EntityWriter must not be null"); - - Batch batch = addQueryOptions(QueryBuilder.batch(), options); - - for (T entity : objectsToDelete) { - batch.add(createDeleteQuery(tableName, entity, options, entityWriter)); - } - - return batch; - } - - @Override - public void deleteAll(Class entityClass) { - truncate(getPersistentEntity(entityClass).getTableName()); - } - - @Override - public Cancellable selectOneAsynchronously(Select select, Class type, QueryForObjectListener listener) { - return selectOneAsynchronously(select, type, listener, null); - } - - @Override - public Cancellable selectOneAsynchronously(String cql, Class entityClass, QueryForObjectListener listener) { - return selectOneAsynchronously(cql, entityClass, listener, null); - } - - @Override - public Cancellable selectOneAsynchronously(Select select, Class entityClass, QueryForObjectListener listener, - QueryOptions options) { - - return doSelectOneAsync(select, entityClass, listener, options); - } - - @Override - public Cancellable selectOneAsynchronously(String cql, Class entityClass, QueryForObjectListener listener, - QueryOptions options) { - - return doSelectOneAsync(cql, entityClass, listener, options); - } - - private CassandraPersistentEntity getPersistentEntity(Class entityClass) { - - Assert.notNull(entityClass, "EntityClass must not be null"); + Assert.notNull(entityClass, "Entity type must not be null"); CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); if (entity == null) { throw new InvalidDataAccessApiUsageException( - String.format("No Persistent Entity information found for the class [%s]", entityClass.getName())); + String.format("No Persistent Entity information found for the class [%s]", entityClass.getName())); } return entity; } - protected Cancellable doSelectOneAsync(final Object query, final Class entityClass, - final QueryForObjectListener listener, QueryOptions options) { + private static class StatementCallback implements SessionCallback, CqlProvider { - Assert.notNull(entityClass, "EntityClass must not be null"); + private final Statement statement; + private final T entity; - AsynchronousQueryListener queryListener = new AsynchronousQueryListener() { - - @Override - public void onQueryComplete(ResultSetFuture resultSetFuture) { - try { - ResultSet resultSet = resultSetFuture.getUninterruptibly(); - - Iterator iterator = resultSet.iterator(); - - if (iterator.hasNext()) { - Row row = iterator.next(); - - T result = new CassandraConverterRowCallback(cassandraConverter, entityClass).doWith(row); - - if (iterator.hasNext()) { - // TODO: throw IncorrectResultSetSizeDataAccessException instead - throw new DuplicateKeyException(String.format( - "found two or more results in query [%s]", query)); - } - - listener.onQueryComplete(result); - } else { - listener.onQueryComplete(null); - } - } catch (Exception e) { - listener.onException(translateExceptionIfPossible(e)); - } - } - }; - - if (query instanceof String) { - return queryAsynchronously((String) query, queryListener, options); - } - - if (query instanceof Select) { - return queryAsynchronously((Select) query, queryListener); - } - - throw new IllegalArgumentException(String.format( - "Expected type String or Select; got type [%1$s] with value [%2$s]", query.getClass(), query)); - } - - protected interface ClauseCallback { - void doWithClause(Clause clause); - } - - private static class ResultSetIteratorAdapter implements Iterator{ - - private final CassandraConverterRowCallback rowCallback; - private final Iterator iterator; - private final PersistenceExceptionTranslator exceptionTranslator; - - public ResultSetIteratorAdapter(Iterator iterator, PersistenceExceptionTranslator exceptionTranslator, - CassandraConverterRowCallback rowCallback) { - - this.iterator = iterator; - this.exceptionTranslator = exceptionTranslator; - this.rowCallback = rowCallback; + StatementCallback(Statement statement, T entity) { + this.statement = statement; + this.entity = entity; } @Override - public boolean hasNext() { - - try { - return iterator.hasNext(); - } catch (Exception e) { - throw translateExceptionIfPossible(e, exceptionTranslator); - } + public T doInSession(Session session) throws DriverException, DataAccessException { + return session.execute(statement).wasApplied() ? entity : null; } @Override - public T next() { - - try { - return rowCallback.doWith(iterator.next()); - } catch (Exception e) { - throw translateExceptionIfPossible(e, exceptionTranslator); - } + public String getCql() { + return statement.toString(); } } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryUtils.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryUtils.java new file mode 100644 index 000000000..c11a12448 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/QueryUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import org.springframework.cassandra.core.CqlTemplate; +import org.springframework.cassandra.core.QueryOptions; +import org.springframework.cassandra.core.QueryOptionsUtil; +import org.springframework.cassandra.core.WriteOptions; +import org.springframework.data.convert.EntityWriter; +import org.springframework.util.Assert; + +import com.datastax.driver.core.querybuilder.Delete; +import com.datastax.driver.core.querybuilder.Delete.Where; +import com.datastax.driver.core.querybuilder.Insert; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Update; + +/** + * Simple utility class for working with the QueryBuilder API. + *

+ * Only intended for internal use. + * + * @author Mark Paluch + * @since 2.0 + */ +class QueryUtils { + + /** + * Creates a Query Object for an insert. + * + * @param tableName the table name, must not be empty and not {@literal null}. + * @param objectToUpdate the object to save, must not be {@literal null}. + * @param options optional {@link WriteOptions} to apply to the {@link Insert} statement, may be {@literal null}. + * @param entityWriter the {@link EntityWriter} to write insert values. + * @return The Query object to run with session.execute(); + */ + public static Insert createInsertQuery(String tableName, Object objectToUpdate, WriteOptions options, + EntityWriter entityWriter) { + + Assert.hasText(tableName, "TableName must not be empty"); + Assert.notNull(objectToUpdate, "Object to insert must not be null"); + Assert.notNull(entityWriter, "EntityWriter must not be null"); + + Insert insert = QueryOptionsUtil.addWriteOptions(QueryBuilder.insertInto(tableName), options); + + entityWriter.write(objectToUpdate, insert); + + return insert; + } + + /** + * Creates a Query Object for an Update. The {@link Update} uses the identity and values from the given + * {@code objectsToUpdate}. + * + * @param tableName the table name, must not be empty and not {@literal null}. + * @param objectToUpdate the object to update, must not be {@literal null}. + * @param options optional {@link WriteOptions} to apply to the {@link Update} statement, may be {@literal null}. + * @param entityWriter the {@link EntityWriter} to write update assignments and where clauses. + * @return The Query object to run with session.execute(); + */ + public static Update createUpdateQuery(String tableName, Object objectToUpdate, WriteOptions options, + EntityWriter entityWriter) { + + Assert.hasText(tableName, "TableName must not be empty"); + Assert.notNull(objectToUpdate, "Object to update must not be null"); + Assert.notNull(entityWriter, "EntityWriter must not be null"); + + Update update = QueryOptionsUtil.addWriteOptions(QueryBuilder.update(tableName), options); + + entityWriter.write(objectToUpdate, update); + + return update; + } + + /** + * Creates a Delete Query Object from an annotated POJO. The {@link Delete} uses the identity from the given + * {@code objectToDelete}. + * + * @param tableName the table name, must not be empty and not {@literal null}. + * @param objectToDelete the object to delete, must not be {@literal null}. + * @param options optional {@link QueryOptions} to apply to the {@link Delete} statement, may be {@literal null}. + * @param entityWriter the {@link EntityWriter} to write delete where clauses. + * @return The Query object to run with session.execute(); + */ + public static Delete createDeleteQuery(String tableName, Object objectToDelete, QueryOptions options, + EntityWriter entityWriter) { + + Assert.hasText(tableName, "TableName must not be empty"); + Assert.notNull(objectToDelete, "Object to delete must not be null"); + Assert.notNull(entityWriter, "EntityWriter must not be null"); + + Delete.Selection deleteSelection = QueryBuilder.delete(); + Delete delete = deleteSelection.from(tableName); + Where where = QueryOptionsUtil.addQueryOptions(delete.where(), options); + + entityWriter.write(objectToDelete, where); + + return delete; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java index 89372bc66..d75181936 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/ReactiveCassandraTemplate.java @@ -15,10 +15,6 @@ */ package org.springframework.data.cassandra.core; -import static org.springframework.data.cassandra.core.CassandraTemplate.createDeleteQuery; -import static org.springframework.data.cassandra.core.CassandraTemplate.createInsertQuery; -import static org.springframework.data.cassandra.core.CassandraTemplate.createUpdateQuery; - import org.reactivestreams.Publisher; import org.springframework.cassandra.core.CqlProvider; import org.springframework.cassandra.core.DefaultReactiveSessionFactory; @@ -274,7 +270,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations { CqlIdentifier tableName = getTableName(entity); - Insert insert = createInsertQuery(tableName.toCql(), entity, options, converter); + Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, converter); class InsertCallback implements ReactiveSessionCallback, CqlProvider { @@ -334,7 +330,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations { CqlIdentifier tableName = getTableName(entity); - Update update = createUpdateQuery(tableName.toCql(), entity, options, converter); + Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, converter); class UpdateCallback implements ReactiveSessionCallback, CqlProvider { @@ -412,7 +408,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations { CqlIdentifier tableName = getTableName(entity); - Delete delete = createDeleteQuery(tableName.toCql(), entity, options, converter); + Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, converter); class DeleteCallback implements ReactiveSessionCallback, CqlProvider { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryExecution.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryExecution.java index fe6ec3c43..1ec844a27 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryExecution.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryExecution.java @@ -54,14 +54,7 @@ interface CassandraQueryExecution { */ @Override public Object execute(String query, Class type) { - - return StreamUtils.createStreamFromIterator(operations.stream(query, type)).map(new Function() { - - @Override - public Object apply(Object t) { - return resultProcessing.convert(t); - } - }); + return operations.stream(query, type).map(resultProcessing::convert); } } @@ -118,7 +111,7 @@ interface CassandraQueryExecution { */ @Override public Object execute(String query, Class type) { - return operations.query(query); + return operations.getCqlOperations().queryForResultSet(query); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java index 7d2e82405..a86c8e7cd 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java @@ -15,6 +15,8 @@ */ package org.springframework.data.cassandra.repository.query; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.cassandra.core.CassandraOperations; @@ -73,7 +75,9 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery { super(queryMethod, operations); - CodecRegistry codecRegistry = operations.getSession().getCluster().getConfiguration().getCodecRegistry(); + Cluster cluster = operations.getCqlOperations().execute(Session::getCluster); + + CodecRegistry codecRegistry = cluster.getConfiguration().getCodecRegistry(); this.stringBasedQuery = new StringBasedQuery(query, new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider), codecRegistry); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java index ec7eeb0a9..01b5d9d9c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/SimpleCassandraRepository.java @@ -25,6 +25,7 @@ import org.springframework.data.cassandra.repository.TypedIdCassandraRepository; import org.springframework.data.cassandra.repository.query.CassandraEntityInformation; import org.springframework.util.Assert; +import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; /** @@ -36,8 +37,8 @@ import com.datastax.driver.core.querybuilder.Select; */ public class SimpleCassandraRepository implements TypedIdCassandraRepository { - protected CassandraOperations operations; - protected CassandraEntityInformation entityInformation; + private CassandraOperations operations; + private CassandraEntityInformation entityInformation; /** * Creates a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and @@ -67,22 +68,22 @@ public class SimpleCassandraRepository implements Ty @Override public T findOne(ID id) { - return operations.selectOneById(entityInformation.getJavaType(), id); + return operations.selectOneById(id, entityInformation.getJavaType()); } @Override public boolean exists(ID id) { - return operations.exists(entityInformation.getJavaType(), id); + return operations.exists(id, entityInformation.getJavaType()); } @Override public long count() { - return operations.count(entityInformation.getTableName()); + return operations.count(entityInformation.getJavaType()); } @Override public void delete(ID id) { - operations.deleteById(entityInformation.getJavaType(), id); + operations.deleteById(id, entityInformation.getJavaType()); } @Override @@ -97,20 +98,19 @@ public class SimpleCassandraRepository implements Ty @Override public void deleteAll() { - operations.truncate(entityInformation.getTableName()); + operations.truncate(entityInformation.getJavaType()); } @Override public List findAll() { - return operations.selectAll(entityInformation.getJavaType()); + + Select select = QueryBuilder.select().all().from(entityInformation.getTableName().toCql()); + + return operations.select(select, entityInformation.getJavaType()); } @Override public Iterable findAll(Iterable ids) { - return operations.selectBySimpleIds(entityInformation.getJavaType(), ids); - } - - protected List findAll(Select query) { - return operations.select(query, entityInformation.getJavaType()); + return operations.selectBySimpleIds(ids, entityInformation.getJavaType()); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBeanUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBeanUnitTests.java index 904068548..c55b3b958 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBeanUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CassandraSessionFactoryBeanUnitTests.java @@ -70,7 +70,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Before public void setup() { - + when(mockCluster.connect()).thenReturn(mockSession); when(mockSession.getCluster()).thenReturn(mockCluster); @@ -84,7 +84,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void afterPropertiesSetPerformsSchemaAction() throws Exception { - + doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { @@ -109,7 +109,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void afterPropertiesSetThrowsIllegalStateExceptionWhenConverterIsNull() throws Exception { - + exception.expect(IllegalStateException.class); exception.expectMessage("Converter was not properly initialized"); @@ -165,7 +165,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void performsSchemaActionDoesNotCallCreateTablesWhenSchemaActionIsNone() { - + doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { @@ -185,7 +185,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void setAndGetConverter() { - + assertThat(factoryBean.getConverter()).isNull(); factoryBean.setConverter(mockConverter); assertThat(factoryBean.getConverter()).isEqualTo(mockConverter); @@ -194,7 +194,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void setConverterToNull() { - + exception.expect(IllegalArgumentException.class); exception.expectMessage("CassandraConverter must not be null"); @@ -203,7 +203,7 @@ public class CassandraSessionFactoryBeanUnitTests { @Test public void setAndGetSchemaAction() { - + assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.NONE); factoryBean.setSchemaAction(SchemaAction.CREATE); assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.CREATE); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/ColumnReaderUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/ColumnReaderUnitTests.java index 815a0a065..faffb7eb5 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/ColumnReaderUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/ColumnReaderUnitTests.java @@ -39,9 +39,9 @@ public class ColumnReaderUnitTests { public static final String NON_EXISTENT_COLUMN = "column_name"; - @Mock private Row row; + @Mock Row row; - @Mock private ColumnDefinitions columnDefinitions; + @Mock ColumnDefinitions columnDefinitions; private ColumnReader underTest; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java index 10a68e086..61b3a5af7 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/convert/MappingCassandraConverterUnitTests.java @@ -97,9 +97,9 @@ public class MappingCassandraConverterUnitTests { @Rule public final ExpectedException expectedException = ExpectedException.none(); - @Mock private ColumnDefinitions columnDefinitionsMock; + @Mock ColumnDefinitions columnDefinitionsMock; - @Mock private Row rowMock; + @Mock Row rowMock; private CassandraMappingContext mappingContext; private MappingCassandraConverter mappingCassandraConverter; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java new file mode 100644 index 000000000..c9f6e1914 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateIntegrationTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.concurrent.Future; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cassandra.core.AsyncCqlTemplate; +import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.domain.Person; +import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils; +import org.springframework.util.concurrent.ListenableFuture; + +/** + * Integration tests for {@link AsyncCassandraTemplate}. + * + * @author Mark Paluch + */ +public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { + + private AsyncCassandraTemplate template; + + @Before + public void setUp() throws Exception { + + MappingCassandraConverter converter = new MappingCassandraConverter(); + CassandraTemplate cassandraTemplate = new CassandraTemplate(session, converter); + template = new AsyncCassandraTemplate(new AsyncCqlTemplate(session), converter); + + SchemaTestUtils.potentiallyCreateTableFor(Person.class, cassandraTemplate); + SchemaTestUtils.truncate(Person.class, cassandraTemplate); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldInsertEntity() { + + Person person = new Person("heisenberg", "Walter", "White"); + + assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull(); + + ListenableFuture insert = template.insert(person); + + assertThat(getUninterruptibly(insert)).isNotNull().isEqualTo(person); + assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isEqualTo(person); + } + + /** + * @see DATACASS-292 + */ + @Test + public void shouldInsertAndCountEntities() throws Exception { + + Person person = new Person("heisenberg", "Walter", "White"); + + template.insert(person).get(); + + ListenableFuture count = template.count(Person.class); + assertThat(getUninterruptibly(count)).isEqualTo(1L); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldUpdateEntity() throws Exception { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person).get(); + + person.setFirstname("Walter Hartwell"); + Person updated = template.update(person).get(); + assertThat(updated).isNotNull(); + + assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isEqualTo(person); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldRemoveEntity() throws Exception { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person).get(); + + Person deleted = template.delete(person).get(); + assertThat(deleted).isNotNull(); + + assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteByIdShouldRemoveEntity() throws Exception { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person).get(); + + Boolean deleted = template.deleteById(person.getId(), Person.class).get(); + assertThat(deleted).isTrue(); + + assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull(); + } + + private static T getUninterruptibly(Future future) { + + try { + return future.get(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateUnitTests.java new file mode 100644 index 000000000..12976b332 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/AsyncCassandraTemplateUnitTests.java @@ -0,0 +1,497 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyInt; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.domain.Person; +import org.springframework.util.concurrent.ListenableFuture; + +import com.datastax.driver.core.ColumnDefinitions; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.exceptions.NoHostAvailableException; +import com.google.common.util.concurrent.AbstractFuture; + +/** + * Unit tests for {@link AsyncCassandraTemplate}. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +public class AsyncCassandraTemplateUnitTests { + + @Mock Session session; + @Mock ResultSet resultSet; + @Mock Row row; + @Mock ColumnDefinitions columnDefinitions; + @Captor ArgumentCaptor statementCaptor; + + private AsyncCassandraTemplate template; + + @Before + public void setUp() { + + template = new AsyncCassandraTemplate(session); + when(session.executeAsync(anyString())).thenReturn(new TestResultSetFuture(resultSet)); + when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet)); + when(resultSet.getColumnDefinitions()).thenReturn(columnDefinitions); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); + } + + /** + * @see DATACASS-292 + */ + @Test + public void selectUsingCqlShouldReturnMappedResults() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); + + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); + + ListenableFuture> list = template.select("SELECT * FROM person", Person.class); + + assertThat(getUninterruptibly(list)).hasSize(1).contains(new Person("myid", "Walter", "White")); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void selectUsingCqlShouldInvokeCallbackWithMappedResults() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(resultSet.spliterator()).thenReturn(Arrays.asList(row).spliterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); + + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); + + List list = new ArrayList<>(); + + ListenableFuture result = template.select("SELECT * FROM person", list::add, Person.class); + + assertThat(getUninterruptibly(result)).isNull(); + assertThat(list).hasSize(1).contains(new Person("myid", "Walter", "White")); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void selectShouldTranslateException() throws Exception { + + when(resultSet.iterator()).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + ListenableFuture> list = template.select("SELECT * FROM person", Person.class); + + try { + list.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void selectOneShouldReturnMappedResults() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); + + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); + + ListenableFuture future = template.selectOne("SELECT * FROM person WHERE id='myid';", Person.class); + + assertThat(getUninterruptibly(future)).isEqualTo(new Person("myid", "Walter", "White")); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void selectOneByIdShouldReturnMappedResults() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); + + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); + + ListenableFuture future = template.selectOneById("myid", Person.class); + + assertThat(getUninterruptibly(future)).isEqualTo(new Person("myid", "Walter", "White")); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void existsShouldReturnExistingElement() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + ListenableFuture future = template.exists("myid", Person.class); + + assertThat(getUninterruptibly(future)).isTrue(); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void existsShouldReturnNonExistingElement() { + + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + ListenableFuture future = template.exists("myid", Person.class); + + assertThat(getUninterruptibly(future)).isFalse(); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void countShouldExecuteCountQueryElement() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getLong(0)).thenReturn(42L); + when(columnDefinitions.size()).thenReturn(1); + + ListenableFuture future = template.count(Person.class); + + assertThat(getUninterruptibly(future)).isEqualTo(42L); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM person;"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldInsertEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.insert(person); + + assertThat(getUninterruptibly(future)).isEqualTo(person); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()) + .isEqualTo("INSERT INTO person (firstname,id,lastname) VALUES ('Walter','heisenberg','White');"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldTranslateException() throws Exception { + + reset(session); + when(session.executeAsync(any(Statement.class))) + .thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap()))); + + ListenableFuture future = template.insert(new Person("heisenberg", "Walter", "White")); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldNotApplyInsert() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.insert(person); + + assertThat(getUninterruptibly(future)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldUpdateEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.update(person); + + assertThat(getUninterruptibly(future)).isEqualTo(person); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()) + .isEqualTo("UPDATE person SET firstname='Walter',lastname='White' WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldTranslateException() throws Exception { + + reset(session); + when(session.executeAsync(any(Statement.class))) + .thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap()))); + + ListenableFuture future = template.update(new Person("heisenberg", "Walter", "White")); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldNotApplyUpdate() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.update(person); + + assertThat(getUninterruptibly(future)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteByIdShouldRemoveEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.deleteById(person.getId(), Person.class); + + assertThat(getUninterruptibly(future)).isTrue(); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldRemoveEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.delete(person); + + assertThat(getUninterruptibly(future)).isEqualTo(person); + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldTranslateException() throws Exception { + + reset(session); + when(session.executeAsync(any(Statement.class))) + .thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap()))); + + ListenableFuture future = template.delete(new Person("heisenberg", "Walter", "White")); + + try { + future.get(); + + fail("Missing CassandraConnectionFailureException"); + } catch (ExecutionException e) { + assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class) + .hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldNotApplyRemoval() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + ListenableFuture future = template.delete(person); + + assertThat(getUninterruptibly(future)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void truncateShouldRemoveEntities() { + + template.truncate(Person.class); + + verify(session).executeAsync(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("TRUNCATE person;"); + } + + private static T getUninterruptibly(Future future) { + + try { + return future.get(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static class TestResultSetFuture extends AbstractFuture implements ResultSetFuture { + + public TestResultSetFuture() {} + + public TestResultSetFuture(ResultSet resultSet) { + set(resultSet); + } + + @Override + public boolean set(ResultSet value) { + return super.set(value); + } + + @Override + public ResultSet getUninterruptibly() { + return null; + } + + @Override + public ResultSet getUninterruptibly(long l, TimeUnit timeUnit) throws TimeoutException { + return null; + } + + @Override + protected boolean setException(Throwable throwable) { + return super.setException(throwable); + } + + /** + * Create a completed future that reports a failure given {@link Throwable}. + * + * @param throwable must not be {@literal null}. + * @return the completed/failed {@link TestResultSetFuture}. + */ + public static TestResultSetFuture failed(Throwable throwable) { + + TestResultSetFuture future = new TestResultSetFuture(); + future.setException(throwable); + return future; + } + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraAdminTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraAdminTemplateIntegrationTests.java similarity index 91% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraAdminTemplateIntegrationTests.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraAdminTemplateIntegrationTests.java index 90b1951a8..7d0938a92 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraAdminTemplateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraAdminTemplateIntegrationTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.test.integration.core; +package org.springframework.data.cassandra.core; import static org.assertj.core.api.Assertions.*; @@ -22,6 +22,7 @@ import java.util.Collection; import org.junit.Before; import org.junit.Test; import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator; import org.springframework.cassandra.core.keyspace.DropTableSpecification; import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; import org.springframework.data.cassandra.convert.MappingCassandraConverter; @@ -49,7 +50,8 @@ public class CassandraAdminTemplateIntegrationTests extends AbstractKeyspaceCrea KeyspaceMetadata keyspace = getKeyspaceMetadata(); Collection tables = keyspace.getTables(); for (TableMetadata table : tables) { - cassandraAdminTemplate.execute(DropTableSpecification.dropTable(table.getName())); + cassandraAdminTemplate.getCqlOperations() + .execute(DropTableCqlGenerator.toCql(DropTableSpecification.dropTable(table.getName()))); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraBatchTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraBatchTemplateIntegrationTests.java index 62fe11219..cc493d804 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraBatchTemplateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraBatchTemplateIntegrationTests.java @@ -65,7 +65,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.insert(walter).insert(mike).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded.getId().getUsername()).isEqualTo(walter.getId().getUsername()); } @@ -82,7 +82,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.insert(Arrays.asList(walter, mike)).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded.getId().getUsername()).isEqualTo(walter.getId().getUsername()); } @@ -102,7 +102,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.update(walter).update(mike).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded.getEmail()).isEqualTo(walter.getEmail()); } @@ -122,7 +122,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.update(Arrays.asList(walter, mike)).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded.getEmail()).isEqualTo(walter.getEmail()); } @@ -142,7 +142,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.update(Arrays.asList(walter, mike)).execute(); - FlatGroup loaded = template.selectOneById(FlatGroup.class, walter); + FlatGroup loaded = template.selectOneById(walter, FlatGroup.class); assertThat(loaded.getEmail()).isEqualTo(walter.getEmail()); } @@ -160,7 +160,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea batchOperations.delete(walter).delete(mike).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded).isNull(); } @@ -178,7 +178,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea batchOperations.delete(Arrays.asList(walter, mike)).execute(); - Group loaded = template.selectOneById(Group.class, walter.getId()); + Group loaded = template.selectOneById(walter.getId(), Group.class); assertThat(loaded).isNull(); } @@ -200,7 +200,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template); batchOperations.insert(walter).insert(mike).withTimestamp(timestamp).execute(); - ResultSet resultSet = template.query("SELECT writetime(email) FROM group;"); + ResultSet resultSet = template.getCqlOperations().queryForResultSet("SELECT writetime(email) FROM group;"); assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java index f2f93b045..6cd27898e 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java @@ -26,6 +26,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cassandra.core.CqlOperations; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; import org.springframework.data.cassandra.mapping.UserDefinedType; @@ -44,7 +45,8 @@ import lombok.Data; @RunWith(MockitoJUnitRunner.class) public class CassandraPersistentEntitySchemaCreatorUnitTests { - @Mock CassandraAdminOperations operations; + @Mock CassandraAdminOperations adminOperations; + @Mock CqlOperations operations; @Mock KeyspaceMetadata metadata; @Mock UserType universetype; @Mock UserType moontype; @@ -63,6 +65,8 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests { return metadata.getUserType(typeName.toCql()); } }); + + when(adminOperations.getCqlOperations()).thenReturn(operations); } @Test @@ -76,7 +80,7 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests { when(metadata.getUserType("moontype")).thenReturn(moontype); CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context, - operations); + adminOperations); schemaCreator.createUserTypes(false, false, false); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java new file mode 100644 index 000000000..3425ca3a2 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateIntegrationTests.java @@ -0,0 +1,242 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.cassandra.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cassandra.core.CqlTemplate; +import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.domain.Person; +import org.springframework.data.cassandra.domain.UserToken; +import org.springframework.data.cassandra.repository.support.BasicMapId; +import org.springframework.data.cassandra.test.integration.simpletons.BookReference; +import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils; + +import com.datastax.driver.core.utils.UUIDs; + +/** + * Integration tests for {@link CassandraTemplate}. + * + * @author Mark Paluch + */ +public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { + + private CassandraTemplate template; + + @Before + public void setUp() { + + MappingCassandraConverter converter = new MappingCassandraConverter(); + converter.afterPropertiesSet(); + + template = new CassandraTemplate(new CqlTemplate(session), converter); + + SchemaTestUtils.potentiallyCreateTableFor(Person.class, template); + SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, template); + SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template); + SchemaTestUtils.truncate(Person.class, template); + SchemaTestUtils.truncate(UserToken.class, template); + SchemaTestUtils.truncate(BookReference.class, template); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldInsertEntity() { + + Person person = new Person("heisenberg", "Walter", "White"); + + assertThat(template.selectOneById(person.getId(), Person.class)).isNull(); + + Person inserted = template.insert(person); + + assertThat(inserted).isNotNull().isEqualTo(person); + assertThat(template.selectOneById(person.getId(), Person.class)).isEqualTo(person); + } + + /** + * @see DATACASS-292 + */ + @Test + public void shouldInsertAndCountEntities() { + + Person person = new Person("heisenberg", "Walter", "White"); + + template.insert(person); + + long count = template.count(Person.class); + assertThat(count).isEqualTo(1L); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldUpdateEntity() { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person); + + person.setFirstname("Walter Hartwell"); + Person updated = template.update(person); + assertThat(updated).isNotNull(); + + assertThat(template.selectOneById(person.getId(), Person.class)).isEqualTo(person); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldRemoveEntity() { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person); + + Person deleted = template.delete(person); + assertThat(deleted).isNotNull(); + + assertThat(template.selectOneById(person.getId(), Person.class)).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteByIdShouldRemoveEntity() { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person); + + Boolean deleted = template.deleteById(person.getId(), Person.class); + assertThat(deleted).isTrue(); + + assertThat(template.selectOneById(person.getId(), Person.class)).isNull(); + } + + /** + * @see DATACASS-182 + */ + @Test + public void stream() { + + Person person = new Person("heisenberg", "Walter", "White"); + template.insert(person); + + Stream stream = template.stream("SELECT * FROM person", Person.class); + + assertThat(stream.collect(Collectors.toList())).hasSize(1).contains(person); + } + + /** + * @see DATACASS-182 + */ + @Test + public void updateShouldRemoveFields() { + + Person person = new Person("heisenberg", "Walter", "White"); + + template.insert(person); + + person.setFirstname(null); + template.update(person); + + Person loaded = template.selectOneById(person.getId(), Person.class); + + assertThat(loaded.getFirstname()).isNull(); + assertThat(loaded.getId()).isEqualTo("heisenberg"); + } + + /** + * @see DATACASS-182 + */ + @Test + public void insertShouldRemoveFields() { + + Person person = new Person("heisenberg", "Walter", "White"); + + template.insert(person); + + person.setFirstname(null); + template.insert(person); + + Person loaded = template.selectOneById(person.getId(), Person.class); + + assertThat(loaded.getFirstname()).isNull(); + assertThat(loaded.getId()).isEqualTo("heisenberg"); + } + + /** + * @see DATACASS-182 + */ + @Test + public void insertAndUpdateToEmptyCollection() { + + BookReference bookReference = new BookReference(); + + bookReference.setIsbn("isbn"); + bookReference.setBookmarks(Arrays.asList(1, 2, 3, 4)); + + template.insert(bookReference); + + bookReference.setBookmarks(Collections. emptyList()); + + template.update(bookReference); + + BookReference loaded = template.selectOneById(bookReference.getIsbn(), BookReference.class); + + assertThat(loaded.getTitle()).isNull(); + assertThat(loaded.getBookmarks()).isNull(); + } + + /** + * @see DATACASS-206 + */ + @Test + public void shouldUseSpecifiedColumnNamesForSingleEntityModifyingOperations() { + + UserToken userToken = new UserToken(); + userToken.setToken(UUIDs.startOf(System.currentTimeMillis())); + userToken.setUserId(UUIDs.endOf(System.currentTimeMillis())); + + template.insert(userToken); + + userToken.setUserComment("comment"); + template.update(userToken); + + UserToken loaded = template.selectOneById( + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()), UserToken.class); + + assertThat(loaded).isNotNull(); + assertThat(loaded.getUserComment()).isEqualTo("comment"); + + template.delete(userToken); + + UserToken loadAfterDelete = template.selectOneById( + BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()), UserToken.class); + + assertThat(loadAfterDelete).isNull(); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateUnitTests.java index 266de47ce..f1615c1f7 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraTemplateUnitTests.java @@ -1,175 +1,409 @@ /* - * Copyright 2013-2016 the original author or authors + * Copyright 2016 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package org.springframework.data.cassandra.core; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyInt; -import java.util.Arrays; import java.util.Collections; -import java.util.Iterator; import java.util.List; +import com.datastax.driver.core.querybuilder.Batch; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.data.cassandra.convert.CassandraConverter; -import org.springframework.data.cassandra.test.integration.simpletons.Book; +import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.domain.Person; +import com.datastax.driver.core.ColumnDefinitions; +import com.datastax.driver.core.DataType; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; -import com.datastax.driver.core.querybuilder.Batch; -import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.Statement; +import com.datastax.driver.core.exceptions.NoHostAvailableException; +import org.springframework.data.cassandra.test.integration.simpletons.Book; /** - * Test suite of test cases testing the contract and functionality of the {@link CassandraTemplate} class. - * - * @author John Blum - * @see org.springframework.data.cassandra.core.CassandraTemplate - * @since 1.5.0 + * Unit tests for {@link CassandraTemplate}. + * + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class CassandraTemplateUnitTests { + @Mock Session session; + @Mock ResultSet resultSet; + @Mock Row row; + @Mock ColumnDefinitions columnDefinitions; + @Captor ArgumentCaptor statementCaptor; + private CassandraTemplate template; - @Mock private Session mockSession; - @Before - public void setup() { - template = new CassandraTemplate(mockSession); - } + public void setUp() { - protected Iterator iterator(T... elements) { - return Collections.unmodifiableList(Arrays.asList(elements)).iterator(); - } - - protected Row mockRow(String name) { - return mock(Row.class, name); - } - - protected CassandraConverterRowCallback newRollCallback(CassandraConverter converter, Class type) { - return new CassandraConverterRowCallback(converter, type); + template = new CassandraTemplate(session, new MappingCassandraConverter()); + when(session.execute(anyString())).thenReturn(resultSet); + when(session.execute(any(Statement.class))).thenReturn(resultSet); + when(resultSet.getColumnDefinitions()).thenReturn(columnDefinitions); + when(row.getColumnDefinitions()).thenReturn(columnDefinitions); } /** - * @see DATACASS-310 + * @see DATACASS-292 */ @Test - public void processResultSetHandlesResultSetRows() { - ResultSet mockResultSet = mock(ResultSet.class); + public void selectUsingCqlShouldReturnMappedResults() { - Row mockRowOne = mockRow("MockRowOne"); - Row mockRowTwo = mockRow("MockRowTwo"); - Row mockRowThree = mockRow("MockRowThree"); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); - CassandraConverter mockCassandraConverter = mock(CassandraConverter.class); + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); - when(mockSession.execute(eq("SELECT * FROM Test"))).thenReturn(mockResultSet); - when(mockResultSet.iterator()).thenReturn(iterator(mockRowOne, mockRowTwo, mockRowThree)); - when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowOne))).thenReturn(1); - when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowTwo))).thenReturn(2); - when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowThree))).thenReturn(3); + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); - List results = template.select("SELECT * FROM Test", - newRollCallback(mockCassandraConverter, Integer.class)); + List list = template.select("SELECT * FROM person", Person.class); - assertThat(results).isNotNull().hasSize(3).contains(1, 2, 3); - - verify(mockSession, times(1)).execute(eq("SELECT * FROM Test")); - verify(mockResultSet, times(1)).iterator(); - verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowOne)); - verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowTwo)); - verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowThree)); + assertThat(list).hasSize(1).contains(new Person("myid", "Walter", "White")); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person"); } /** - * @see DATACASS-310 + * @see DATACASS-292 */ @Test - public void processResultSetHandlesSingleElementResultSet() { - Select mockSelect = mock(Select.class); - ResultSet mockResultSet = mock(ResultSet.class); - Row mockRow = mock(Row.class); - CassandraConverter mockCassandraConverter = mock(CassandraConverter.class); + public void selectShouldTranslateException() throws Exception { - when(mockSession.execute(eq(mockSelect))).thenReturn(mockResultSet); - when(mockResultSet.iterator()).thenReturn(iterator(mockRow)); - when(mockCassandraConverter.read(eq(String.class), eq(mockRow))).thenReturn("test"); + when(resultSet.iterator()).thenThrow(new NoHostAvailableException(Collections.emptyMap())); - List results = template.select(mockSelect, newRollCallback(mockCassandraConverter, String.class)); + try { + template.select("SELECT * FROM person", Person.class); - assertThat(results).hasSize(1).contains("test"); - - verify(mockSession, times(1)).execute(eq(mockSelect)); - verify(mockResultSet, times(1)).iterator(); - verify(mockCassandraConverter, times(1)).read(eq(String.class), eq(mockRow)); + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class); + } } /** - * @see DATACASS-310 + * @see DATACASS-292 */ @Test - public void processResultSetHandlesEmptyResultSet() { - CassandraConverter mockCassandraConverter = mock(CassandraConverter.class); - ResultSet mockResultSet = mock(ResultSet.class); + public void selectOneShouldReturnMappedResults() { - when(mockSession.execute(eq("SELECT * FROM Test"))).thenReturn(mockResultSet); - when(mockResultSet.iterator()).thenReturn(this. iterator()); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); - List results = template.select("SELECT * FROM Test", newRollCallback(mockCassandraConverter, Object.class)); + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); - assertThat(results).isNotNull(); - assertThat(results.isEmpty()).isTrue(); + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); - verify(mockSession, times(1)).execute(eq("SELECT * FROM Test")); - verify(mockResultSet, times(1)).iterator(); - verifyZeroInteractions(mockCassandraConverter); + Person person = template.selectOne("SELECT * FROM person WHERE id='myid';", Person.class); + + assertThat(person).isEqualTo(new Person("myid", "Walter", "White")); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); } /** - * @see DATACASS-310 + * @see DATACASS-292 */ @Test - public void processResultSetHandlesNullResultSet() { - CassandraConverter mockCassandraConverter = mock(CassandraConverter.class); + public void selectOneByIdShouldReturnMappedResults() { - when(mockSession.execute(anyString())).thenReturn(null); + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); - List results = template.select("SELECT * FROM Test", newRollCallback(mockCassandraConverter, Object.class)); + when(columnDefinitions.getIndexOf("id")).thenReturn(0); + when(columnDefinitions.getIndexOf("firstname")).thenReturn(1); + when(columnDefinitions.getIndexOf("lastname")).thenReturn(2); - assertThat(results).isNotNull(); - assertThat(results.isEmpty()).isTrue(); + when(row.getObject(0)).thenReturn("myid"); + when(row.getObject(1)).thenReturn("Walter"); + when(row.getObject(2)).thenReturn("White"); - verify(mockSession, times(1)).execute(eq("SELECT * FROM Test")); - verifyZeroInteractions(mockCassandraConverter); + Person person = template.selectOneById("myid", Person.class); + + assertThat(person).isEqualTo(new Person("myid", "Walter", "White")); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); } /** - * @see DATACASS-288 + * @see DATACASS-292 */ @Test + public void existsShouldReturnExistingElement() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(columnDefinitions.contains(anyString())).thenReturn(true); + when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii()); + + boolean exists = template.exists("myid", Person.class); + + assertThat(exists).isTrue(); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void existsShouldReturnNonExistingElement() { + + when(resultSet.iterator()).thenReturn(Collections.emptyIterator()); + + boolean exists = template.exists("myid", Person.class); + + assertThat(exists).isFalse(); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void countShouldExecuteCountQueryElement() { + + when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator()); + when(row.getLong(0)).thenReturn(42L); + when(columnDefinitions.size()).thenReturn(1); + + long count = template.count(Person.class); + + assertThat(count).isEqualTo(42L); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM person;"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldInsertEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person inserted = template.insert(person); + + assertThat(inserted).isEqualTo(person); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()) + .isEqualTo("INSERT INTO person (firstname,id,lastname) VALUES ('Walter','heisenberg','White');"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldTranslateException() throws Exception { + + reset(session); + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.insert(new Person("heisenberg", "Walter", "White")); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void insertShouldNotApplyInsert() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person inserted = template.insert(person); + + assertThat(inserted).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldUpdateEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person updated = template.update(person); + + assertThat(updated).isEqualTo(person); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()) + .isEqualTo("UPDATE person SET firstname='Walter',lastname='White' WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldTranslateException() throws Exception { + + reset(session); + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.update(new Person("heisenberg", "Walter", "White")); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void updateShouldNotApplyUpdate() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person updated = template.update(person); + + assertThat(updated).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteByIdShouldRemoveEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + boolean deleted = template.deleteById(person.getId(), Person.class); + + assertThat(deleted).isTrue(); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldRemoveEntity() { + + when(resultSet.wasApplied()).thenReturn(true); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person deleted = template.delete(person); + + assertThat(deleted).isEqualTo(person); + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';"); + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldTranslateException() throws Exception { + + reset(session); + when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap())); + + try { + template.delete(new Person("heisenberg", "Walter", "White")); + + fail("Missing CassandraConnectionFailureException"); + } catch (CassandraConnectionFailureException e) { + assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class); + } + } + + /** + * @see DATACASS-292 + */ + @Test + public void deleteShouldNotApplyRemoval() { + + when(resultSet.wasApplied()).thenReturn(false); + + Person person = new Person("heisenberg", "Walter", "White"); + + Person deleted = template.delete(person); + + assertThat(deleted).isNull(); + } + + /** + * @see DATACASS-292 + */ + @Test + public void truncateShouldRemoveEntities() { + + template.truncate(Person.class); + + verify(session).execute(statementCaptor.capture()); + assertThat(statementCaptor.getValue().toString()).isEqualTo("TRUNCATE person;"); + } + + /** + * @see DATACASS-292 + */ + @Test + @Ignore public void batchOperationsShouldCallSession() { + template.batchOps().insert(new Book()).execute(); - verify(mockSession).execute(Mockito.any(Batch.class)); + verify(session).execute(Mockito.any(Batch.class)); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparatorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparatorUnitTests.java index 563e2813b..2aa5c37ee 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparatorUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/mapping/CassandraPersistentPropertyComparatorUnitTests.java @@ -35,9 +35,9 @@ import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class CassandraPersistentPropertyComparatorUnitTests { - @Mock private CassandraPersistentProperty left; + @Mock CassandraPersistentProperty left; - @Mock private CassandraPersistentProperty right; + @Mock CassandraPersistentProperty right; @Test public void leftAndRightAreNullReturnsZero() { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/conversion/ParameterConversionTestSupport.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/conversion/ParameterConversionTestSupport.java index 6f98077ed..a9bed3f62 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/conversion/ParameterConversionTestSupport.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/conversion/ParameterConversionTestSupport.java @@ -92,11 +92,11 @@ abstract class ParameterConversionTestSupport extends AbstractSpringDataEmbedded deleteAllEntities(); - template.execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);"); - template.execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);"); - template.execute("CREATE INDEX IF NOT EXISTS contact_main_phones ON contact (mainphone);"); - template.execute("CREATE INDEX IF NOT EXISTS contact_alternative_phones ON contact (alternativephones);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_main_phones ON contact (mainphone);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_alternative_phones ON contact (alternativephones);"); walter = new Contact("Walter"); walter.setAddress(new Address("Albuquerque", "USA")); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java index b61d61838..4a1d34345 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java @@ -45,9 +45,9 @@ import com.datastax.driver.core.DataType; @RunWith(MockitoJUnitRunner.class) public class ConvertingParameterAccessorUnitTests { - @Mock private CassandraParameterAccessor mockParameterAccessor; + @Mock CassandraParameterAccessor mockParameterAccessor; - @Mock private CassandraPersistentProperty mockProperty; + @Mock CassandraPersistentProperty mockProperty; ConvertingParameterAccessor convertingParameterAccessor; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java index 8ab67e5b3..3559c2f2a 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java @@ -31,6 +31,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cassandra.core.CqlOperations; +import org.springframework.cassandra.core.ReactiveSessionCallback; +import org.springframework.cassandra.core.SessionCallback; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.data.cassandra.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.CassandraOperations; @@ -75,6 +78,7 @@ public class StringBasedCassandraQueryUnitTests { SpelExpressionParser PARSER = new SpelExpressionParser(); @Mock CassandraOperations operations; + @Mock CqlOperations cqlOperations; @Mock Session session; @Mock Cluster cluster; @Mock Configuration configuration; @@ -92,8 +96,9 @@ public class StringBasedCassandraQueryUnitTests { mappingContext.setUserTypeResolver(userTypeResolver); when(operations.getConverter()).thenReturn(converter); - when(operations.getSession()).thenReturn(session); - when(operations.getConverter()).thenReturn(converter); + when(operations.getCqlOperations()).thenReturn(cqlOperations); + when(cqlOperations.execute(any(SessionCallback.class))) + .thenAnswer(invocation -> ((SessionCallback) invocation.getArguments()[0]).doInSession(session)); when(session.getCluster()).thenReturn(cluster); when(cluster.getConfiguration()).thenReturn(configuration); when(configuration.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT_INSTANCE); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java index e21e8357a..bad0b2989 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java @@ -42,13 +42,13 @@ import org.springframework.data.repository.Repository; @SuppressWarnings({ "rawtypes", "unchecked" }) public class CassandraRepositoryFactoryUnitTests { - @Mock private CassandraConverter converter; + @Mock CassandraConverter converter; - @Mock private CassandraMappingContext mappingContext; + @Mock CassandraMappingContext mappingContext; - @Mock private CassandraPersistentEntity entity; + @Mock CassandraPersistentEntity entity; - @Mock private CassandraTemplate template; + @Mock CassandraTemplate template; @Before public void setUp() { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java deleted file mode 100755 index 48c682c05..000000000 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.cassandra.test.integration.core; - -import static org.assertj.core.api.Assertions.*; -import static org.junit.Assume.*; -import static org.springframework.data.cassandra.repository.support.BasicMapId.*; - -import java.util.Collection; -import java.util.UUID; -import java.util.concurrent.CancellationException; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.cassandra.core.Cancellable; -import org.springframework.cassandra.core.ConsistencyLevel; -import org.springframework.cassandra.core.PrimaryKeyType; -import org.springframework.cassandra.core.RetryPolicy; -import org.springframework.cassandra.core.WriteOptions; -import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; -import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; -import org.springframework.cassandra.test.integration.support.ObjectListener; -import org.springframework.data.cassandra.core.CassandraOperations; -import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.core.DeletionListener; -import org.springframework.data.cassandra.core.WriteListener; -import org.springframework.data.cassandra.mapping.Column; -import org.springframework.data.cassandra.mapping.PrimaryKeyColumn; -import org.springframework.data.cassandra.mapping.Table; -import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils; -import org.springframework.data.cassandra.test.integration.support.TestListener; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Integration tests for asynchronous {@link CassandraTemplate} operations. - * - * @author Matthew T. Adams - * @author Mark Paluch - */ -public class AsynchronousCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { - - CassandraOperations operations; - - @Before - public void before() { - - operations = new CassandraTemplate(session); - - SchemaTestUtils.potentiallyCreateTableFor(Person.class, operations); - SchemaTestUtils.truncate(Person.class, operations); - } - - @Test - public void insertAsynchronously() throws Exception { - insertAsynchronously(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void insertAsynchronouslyThrows() throws Exception { - insertAsynchronously(ConsistencyLevel.TWO); - } - - public void insertAsynchronously(ConsistencyLevel cl) throws Exception { - - Person person = Person.random(); - PersonListener listener = new PersonListener(); - - operations.insertAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING)); - listener.await(); - - if (listener.exception != null) { - throw listener.exception; - } - - assertThat(listener.entities.iterator().next()).isEqualTo(person); - } - - @Test(expected = CancellationException.class) - public void insertAsynchronouslyCancelled() throws Exception { - insertOrUpdateAsynchronouslyCancelled(true); - } - - @Test(expected = CancellationException.class) - public void updateAsynchronouslyCancelled() throws Exception { - insertOrUpdateAsynchronouslyCancelled(false); - } - - public void insertOrUpdateAsynchronouslyCancelled(boolean insert) throws Exception { - - Person person = Person.random(); - PersonListener listener = new PersonListener(); - - Cancellable cancellable; - - if (insert) { - cancellable = operations.insertAsynchronously(person, listener, null); - } else { - cancellable = operations.updateAsynchronously(person, listener, null); - } - cancellable.cancel(); - listener.await(); - - // if listener.success is true then the - // async operations was faster than it could be cancelled so we cannot - // verify that a CancellationException was thrown. - assumeFalse(listener.success); - - if (listener.exception != null) { - throw listener.exception; - } - - fail("should've thrown CancellationException"); - } - - @Test - public void updateAsynchronously() throws Exception { - updateAsynchronously(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void updateAsynchronouslyThrows() throws Exception { - updateAsynchronously(ConsistencyLevel.TWO); - } - - public void updateAsynchronously(ConsistencyLevel cl) throws Exception { - - Person person = Person.random(); - person.setFirstname("Homer"); - operations.insert(person); - - PersonListener listener = new PersonListener(); - operations.updateAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING)); - - listener.await(); - if (listener.exception != null) { - throw listener.exception; - } - - assertThat(listener.entities.iterator().next()).isEqualTo(person); - } - - @Test - public void deleteAsynchronously() throws Exception { - deleteAsynchronously(ConsistencyLevel.ONE); - } - - @Test(expected = CassandraConnectionFailureException.class) - public void deleteAsynchronouslyThrows() throws Exception { - deleteAsynchronously(ConsistencyLevel.TWO); - } - - public void deleteAsynchronously(ConsistencyLevel cl) throws Exception { - - Person person = Person.random(); - - operations.insert(person); - - PersonListener listener = new PersonListener(); - operations.deleteAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING)); - - listener.await(); - if (listener.exception != null) { - throw listener.exception; - } - assertThat(operations.exists(Person.class, id("id", person.id))).isFalse(); - } - - @Test(expected = CancellationException.class) - public void deleteAsynchronouslyCancelled() throws Exception { - - Person person = Person.random(); - PersonListener listener = new PersonListener(); - operations.deleteAsynchronously(person, listener, null).cancel(); - listener.await(); - - // if listener.success is true then the - // async operations was faster than it could be cancelled so we cannot - // verify that a CancellationException was thrown. - assumeFalse(listener.success); - - if (listener.exception != null) { - throw listener.exception; - } - - fail("should've thrown CancellationException"); - } - - /** - * @see DATACASS-287 - */ - @Test(timeout = 10000) - public void shouldSelectOneAsynchronously() throws Exception { - - Person person = Person.random(); - operations.insert(person); - - ObjectListener objectListener = ObjectListener.create(); - String cql = String.format("SELECT * from person where id = '%s'", person.id); - - operations.selectOneAsynchronously(cql, Person.class, objectListener); - objectListener.await(); - - assertThat(objectListener.getResult()).isNotNull(); - assertThat(objectListener.getResult().id).isEqualTo(person.id); - } - - /** - * @see DATACASS-287 - */ - @Test(timeout = 10000) - public void shouldSelectOneAsynchronouslyIfObjectIsAbsent() throws Exception { - - ObjectListener objectListener = ObjectListener.create(); - String cql = String.format("SELECT * from person where id = '%s'", "unknown"); - - operations.selectOneAsynchronously(cql, Person.class, objectListener); - objectListener.await(); - - assertThat(objectListener.getResult()).isNull(); - } - - @Table - @Data - @AllArgsConstructor - @NoArgsConstructor - @SuppressWarnings("unused") - static class Person { - - @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String id; - @Column String firstname; - - public static String uuid() { - return UUID.randomUUID().toString(); - } - - public static Person random() { - return new Person(uuid(), null); - } - - } - - public static class PersonListener extends TestListener implements WriteListener, DeletionListener { - - public volatile Exception exception; - public volatile Collection entities; - public volatile boolean success; - - @Override - public void onWriteComplete(Collection entities) { - - this.entities = entities; - this.success = true; - countDown(); - } - - @Override - public void onDeletionComplete(Collection entities) { - - this.entities = entities; - this.success = true; - countDown(); - } - - @Override - public void onException(Exception x) { - - this.exception = x; - this.success = false; - countDown(); - } - } -} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java deleted file mode 100755 index 4ad83a0da..000000000 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java +++ /dev/null @@ -1,815 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.cassandra.test.integration.core; - -import static org.assertj.core.api.Assertions.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.cassandra.core.ConsistencyLevel; -import org.springframework.cassandra.core.QueryOptions; -import org.springframework.cassandra.core.RetryPolicy; -import org.springframework.cassandra.core.WriteOptions; -import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; -import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.domain.UserToken; -import org.springframework.data.cassandra.repository.support.BasicMapId; -import org.springframework.data.cassandra.test.integration.simpletons.Book; -import org.springframework.data.cassandra.test.integration.simpletons.BookCondition; -import org.springframework.data.cassandra.test.integration.simpletons.BookReference; -import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils; - -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.utils.UUIDs; - -/** - * Integration tests for {@link CassandraTemplate}. - * - * @author David Webb - * @author Mark Paluch - * @author John Blum - */ -public class CassandraOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { - - CassandraTemplate template; - - @Before - public void before() { - - template = new CassandraTemplate(session); - - SchemaTestUtils.potentiallyCreateTableFor(Book.class, template); - SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template); - SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, template); - - SchemaTestUtils.truncate(Book.class, template); - SchemaTestUtils.truncate(BookReference.class, template); - SchemaTestUtils.truncate(UserToken.class, template); - } - - @Test - public void insertTest() { - - Book b1 = new Book(); - b1.setIsbn("123456-1"); - b1.setTitle("Spring Data Cassandra Guide"); - b1.setAuthor("Cassandra Guru"); - b1.setPages(521); - b1.setSaleDate(new Date()); - b1.setInStock(true); - b1.setCondition(BookCondition.NEW); - - template.insert(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - b2.setTitle("Spring Data Cassandra Guide"); - b2.setAuthor("Cassandra Guru"); - b2.setPages(521); - b2.setCondition(BookCondition.NEW); - - template.insert(b2); - - Book b3 = new Book(); - b3.setIsbn("123456-3"); - b3.setTitle("Spring Data Cassandra Guide"); - b3.setAuthor("Cassandra Guru"); - b3.setPages(265); - b3.setCondition(BookCondition.USED); - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - template.insert(b3, options); - - Book b5 = new Book(); - b5.setIsbn("123456-5"); - b5.setTitle("Spring Data Cassandra Guide"); - b5.setAuthor("Cassandra Guru"); - b5.setPages(265); - b5.setCondition(BookCondition.USED); - - template.insert(b5, options); - } - - @Test - @SuppressWarnings("deprecation") - public void insertAsynchronouslyTest() { - - Book b1 = new Book(); - b1.setIsbn("123456-1"); - b1.setTitle("Spring Data Cassandra Guide"); - b1.setAuthor("Cassandra Guru"); - b1.setPages(521); - b1.setCondition(BookCondition.NEW); - - template.insertAsynchronously(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - b2.setTitle("Spring Data Cassandra Guide"); - b2.setAuthor("Cassandra Guru"); - b2.setPages(521); - b2.setCondition(BookCondition.NEW); - - template.insertAsynchronously(b2); - - /* - * Test Single Insert with entity - */ - Book b3 = new Book(); - b3.setIsbn("123456-3"); - b3.setTitle("Spring Data Cassandra Guide"); - b3.setAuthor("Cassandra Guru"); - b3.setPages(265); - b3.setCondition(BookCondition.USED); - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - template.insertAsynchronously(b3, options); - - /* - * Test Single Insert with entity - */ - Book b4 = new Book(); - b4.setIsbn("123456-4"); - b4.setTitle("Spring Data Cassandra Guide"); - b4.setAuthor("Cassandra Guru"); - b4.setPages(465); - b4.setCondition(BookCondition.USED); - - /* - * Test Single Insert with entity - */ - Book b5 = new Book(); - b5.setIsbn("123456-5"); - b5.setTitle("Spring Data Cassandra Guide"); - b5.setAuthor("Cassandra Guru"); - b5.setPages(265); - b5.setCondition(BookCondition.USED); - - template.insertAsynchronously(b5, options); - } - - @Test - public void insertEmptyList() { - List list = template.insert(new ArrayList()); - - assertThat(list.isEmpty()).isTrue(); - } - - @Test - public void insertNullList() { - List list = template.insert((List) null); - - assertThat(list).isNull(); - } - - @Test - public void insertBatchTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insert(books); - - books = getBookList(20); - - template.insert(books); - - books = getBookList(20); - - template.insert(books, options); - - books = getBookList(20); - - template.insert(books, options); - - assertThat(template.count(Book.class)).isEqualTo(80l); - } - - @Test - @SuppressWarnings("deprecation") - public void insertBatchAsynchronouslyTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insertAsynchronously(books); - - books = getBookList(20); - - template.insertAsynchronously(books); - - books = getBookList(20); - - template.insertAsynchronously(books, options); - - books = getBookList(20); - - template.insertAsynchronously(books, options); - } - - private List getBookList(long numBooks) { - - List books = new ArrayList(); - Book book; - - for (int index = 0; index < numBooks; index++) { - book = new Book(); - book.setIsbn(UUID.randomUUID().toString()); - book.setTitle("Spring Data Cassandra Guide"); - book.setAuthor("Cassandra Guru"); - book.setPages(index * 10 + 5); - book.setInStock(true); - book.setSaleDate(new Date()); - book.setCondition(BookCondition.NEW); - books.add(book); - } - - return books; - } - - @Test - public void updateTest() { - - insertTest(); - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - /* - * Test Single Insert with entity - */ - Book b1 = new Book(); - b1.setIsbn("123456-1"); - b1.setTitle("Spring Data Cassandra Book"); - b1.setAuthor("Cassandra Guru"); - b1.setPages(521); - - template.update(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - b2.setTitle("Spring Data Cassandra Book"); - b2.setAuthor("Cassandra Guru"); - b2.setPages(521); - - template.update(b2); - - /* - * Test Single Insert with entity - */ - Book b3 = new Book(); - b3.setIsbn("123456-3"); - b3.setTitle("Spring Data Cassandra Book"); - b3.setAuthor("Cassandra Guru"); - b3.setPages(265); - - template.update(b3, options); - - /* - * Test Single Insert with entity - */ - Book b5 = new Book(); - b5.setIsbn("123456-5"); - b5.setTitle("Spring Data Cassandra Book"); - b5.setAuthor("Cassandra Guru"); - b5.setPages(265); - - template.update(b5, options); - } - - @Test - @SuppressWarnings("deprecation") - public void updateAsynchronouslyTest() { - - insertTest(); - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - /* - * Test Single Insert with entity - */ - Book b1 = new Book(); - b1.setIsbn("123456-1"); - b1.setTitle("Spring Data Cassandra Book"); - b1.setAuthor("Cassandra Guru"); - b1.setPages(521); - - template.updateAsynchronously(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - b2.setTitle("Spring Data Cassandra Book"); - b2.setAuthor("Cassandra Guru"); - b2.setPages(521); - - template.updateAsynchronously(b2); - - /* - * Test Single Insert with entity - */ - Book b3 = new Book(); - b3.setIsbn("123456-3"); - b3.setTitle("Spring Data Cassandra Book"); - b3.setAuthor("Cassandra Guru"); - b3.setPages(265); - - template.updateAsynchronously(b3, options); - - /* - * Test Single Insert with entity - */ - Book b5 = new Book(); - b5.setIsbn("123456-5"); - b5.setTitle("Spring Data Cassandra Book"); - b5.setAuthor("Cassandra Guru"); - b5.setPages(265); - - template.updateAsynchronously(b5, options); - } - - @Test - public void updateBatchTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insert(books); - - alterBooks(books); - - template.update(books); - - books = getBookList(20); - - template.insert(books); - - alterBooks(books); - - template.update(books); - - books = getBookList(20); - - template.insert(books, options); - - alterBooks(books); - - template.update(books, options); - - books = getBookList(20); - - template.insert(books, options); - - alterBooks(books); - - template.update(books, options); - } - - @Test - @SuppressWarnings("deprecation") - public void updateBatchAsynchronouslyTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insert(books); - - alterBooks(books); - - template.updateAsynchronously(books); - - books = getBookList(20); - - template.insert(books); - - alterBooks(books); - - template.updateAsynchronously(books); - - books = getBookList(20); - - template.insert(books, options); - - alterBooks(books); - - template.updateAsynchronously(books, options); - - books = getBookList(20); - - template.insert(books, options); - - alterBooks(books); - - template.updateAsynchronously(books, options); - } - - private void alterBooks(List books) { - - for (Book book : books) { - book.setAuthor("Ernest Hemmingway"); - book.setTitle("The Old Man and the Sea"); - book.setPages(115); - } - } - - @Test - public void deleteTest() { - - insertTest(); - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); - - // Test Single Insert with entity - Book b1 = new Book(); - b1.setIsbn("123456-1"); - - template.delete(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - - template.delete(b2); - - // Test Single Insert with entity - Book b3 = new Book(); - b3.setIsbn("123456-3"); - - template.delete(b3, options); - - // Test Single Insert with entity - Book b5 = new Book(); - b5.setIsbn("123456-5"); - - template.delete(b5, options); - } - - @Test - public void deleteAsynchronouslyTest() { - - insertTest(); - - QueryOptions options = new QueryOptions(); - options.setConsistencyLevel(ConsistencyLevel.ONE); - options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY); - - /* - * Test Single Insert with entity - */ - Book b1 = new Book(); - b1.setIsbn("123456-1"); - - template.deleteAsynchronously(b1); - - Book b2 = new Book(); - b2.setIsbn("123456-2"); - - template.deleteAsynchronously(b2); - - /* - * Test Single Insert with entity - */ - Book b3 = new Book(); - b3.setIsbn("123456-3"); - - template.deleteAsynchronously(b3, options); - - /* - * Test Single Insert with entity - */ - Book b5 = new Book(); - b5.setIsbn("123456-5"); - - template.deleteAsynchronously(b5, options); - } - - @Test - public void deleteBatchTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insert(books); - template.delete(books); - - books = getBookList(20); - - template.insert(books); - template.delete(books); - - books = getBookList(20); - - template.insert(books, options); - template.delete(books, options); - - books = getBookList(20); - - template.insert(books, options); - template.delete(books, options); - } - - @Test - public void deleteBatchAsynchronouslyTest() { - - WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60); - - List books = getBookList(20); - - template.insert(books); - template.deleteAsynchronously(books); - - books = getBookList(20); - - template.insert(books); - template.deleteAsynchronously(books); - - books = getBookList(20); - - template.insert(books, options); - template.deleteAsynchronously(books, options); - - books = getBookList(20); - - template.insert(books, options); - template.deleteAsynchronously(books, options); - } - - @Test - public void selectOneTest() { - - /* - * Test Single Insert with entity - */ - Book b1 = new Book(); - b1.setIsbn("123456-1"); - b1.setTitle("Spring Data Cassandra Guide"); - b1.setAuthor("Cassandra Guru"); - b1.setPages(521); - - template.insert(b1); - - Select select = QueryBuilder.select().all().from("book"); - select.where(QueryBuilder.eq("isbn", "123456-1")); - - Book book = template.selectOne(select, Book.class); - - assertThat(book.getTitle()).isEqualTo("Spring Data Cassandra Guide"); - assertThat(book.getAuthor()).isEqualTo("Cassandra Guru"); - - } - - @Test - public void selectTest() { - - List books = getBookList(20); - - template.insert(books); - - Select select = QueryBuilder.select().all().from("book"); - - List selectedBooks = template.select(select, Book.class); - - assertThat(selectedBooks).hasSize(20); - - for (Book book : selectedBooks) { - assertThat(book.isInStock()).isTrue(); - assertThat(book.getCondition()).isEqualTo(BookCondition.NEW); - } - } - - @Test - public void selectCountTest() { - - long count = 20; - List books = getBookList(count); - - template.insert(books); - - assertThat(template.count(Book.class)).isEqualTo(count); - } - - @Test - public void insertAndSelect() { - - long count = 20; - List books = getBookList(count); - - template.insert(books); - - assertThat(template.count(Book.class)).isEqualTo(count); - } - - /** - * @see DATACASS-182 - */ - @Test - public void updateShouldRemoveFields() { - - Book book = new Book(); - book.setIsbn("isbn"); - book.setTitle("title"); - book.setAuthor("author"); - - template.insert(book); - - book.setTitle(null); - template.update(book); - - Book loaded = template.selectOneById(Book.class, book.getIsbn()); - - assertThat(loaded.getTitle()).isNull(); - assertThat(loaded.getAuthor()).isEqualTo("author"); - } - - /** - * @see DATACASS-182 - */ - @Test - public void insertShouldRemoveFields() { - - Book book = new Book(); - book.setIsbn("isbn"); - book.setTitle("title"); - book.setAuthor("author"); - - template.insert(book); - - book.setTitle(null); - - template.insert(book); - - Book loaded = template.selectOneById(Book.class, book.getIsbn()); - - assertThat(loaded.getTitle()).isNull(); - assertThat(loaded.getAuthor()).isEqualTo("author"); - } - - /** - * @see DATACASS-182 - */ - @Test - public void updateShouldInsertEntity() { - - Book book = new Book(); - book.setIsbn("isbn"); - book.setTitle("title"); - book.setAuthor("author"); - - template.update(book); - - Book loaded = template.selectOneById(Book.class, book.getIsbn()); - - assertThat(loaded).isNotNull(); - assertThat(loaded.getAuthor()).isEqualTo("author"); - assertThat(loaded.getTitle()).isEqualTo("title"); - } - - /** - * @see DATACASS-182 - */ - @Test - public void insertAndUpdateToEmptyCollection() { - - BookReference bookReference = new BookReference(); - - bookReference.setIsbn("isbn"); - bookReference.setBookmarks(Arrays.asList(1, 2, 3, 4)); - - template.insert(bookReference); - - bookReference.setBookmarks(Collections. emptyList()); - - template.update(bookReference); - - BookReference loaded = template.selectOneById(BookReference.class, bookReference.getIsbn()); - - assertThat(loaded.getTitle()).isNull(); - assertThat(loaded.getBookmarks()).isNull(); - } - - /** - * @see DATACASS-182 - */ - @Test - public void stream() throws InterruptedException { - - while (template.select("SELECT * FROM book", Book.class).size() != 0) { - template.truncate("book"); - Thread.sleep(10); - } - - template.insert(getBookList(20)); - - Iterator iterator = template.stream("SELECT * FROM book", Book.class); - - assertThat(iterator).isNotNull(); - - List selectedBooks = new ArrayList(); - - for (Book book : toIterable(iterator)) { - selectedBooks.add(book); - } - - assertThat(selectedBooks).hasSize(20); - assertThat(selectedBooks.get(0)).isInstanceOf(Book.class); - } - - /** - * @see DATACASS-206 - */ - @Test - public void shouldUseSpecifiedColumnNamesForSingleEntityModifyingOperations() { - - UserToken userToken = new UserToken(); - userToken.setToken(UUIDs.startOf(System.currentTimeMillis())); - userToken.setUserId(UUIDs.endOf(System.currentTimeMillis())); - - template.insert(userToken); - - userToken.setUserComment("comment"); - template.update(userToken); - - UserToken loaded = template.selectOneById(UserToken.class, - BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); - - assertThat(loaded).isNotNull(); - assertThat(loaded.getUserComment()).isEqualTo("comment"); - - template.delete(userToken); - - UserToken loadAfterDelete = template.selectOneById(UserToken.class, - BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); - - assertThat(loadAfterDelete).isNull(); - } - - /** - * @see DATACASS-206 - */ - @Test - public void shouldUseSpecifiedColumnNamesForMultiEntityModifyingOperations() { - - UserToken userToken = new UserToken(); - userToken.setToken(UUIDs.startOf(System.currentTimeMillis())); - userToken.setUserId(UUIDs.endOf(System.currentTimeMillis())); - - template.insert(Collections.singletonList(userToken)); - - userToken.setUserComment("comment"); - template.update(Collections.singletonList(userToken)); - - UserToken loaded = template.selectOneById(UserToken.class, - BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); - - assertThat(loaded).isNotNull(); - assertThat(loaded.getUserComment()).isEqualTo("comment"); - - template.delete(Collections.singletonList(userToken)); - - UserToken loadAfterDelete = template.selectOneById(UserToken.class, - BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken())); - - assertThat(loadAfterDelete).isNull(); - } - - WriteOptions newWriteOptions(ConsistencyLevel consistencyLevel, RetryPolicy retryPolicy, int timeToLive) { - return new WriteOptions(consistencyLevel, retryPolicy, timeToLive); - } - - Iterable toIterable(final Iterator iterator) { - return new Iterable() { - @Override - public Iterator iterator() { - return iterator; - } - }; - } -} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/CompositeKeyCrudIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/CompositeKeyCrudIntegrationTests.java index e89d0803c..4949b6f57 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/CompositeKeyCrudIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/CompositeKeyCrudIntegrationTests.java @@ -79,16 +79,14 @@ public class CompositeKeyCrudIntegrationTests extends AbstractKeyspaceCreatingIn assertThat(correlationEntities).hasSize(2); - QueryOptions qo = new QueryOptions(); - qo.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.ONE); - ArrayList entities = new ArrayList(); - entities.add(correlationEntity1); - entities.add(correlationEntity2); - operations.delete(entities, qo); + QueryOptions queryOptions = new QueryOptions(); + queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.ONE); + + operations.delete(correlationEntity1, queryOptions); + operations.delete(correlationEntity2, queryOptions); correlationEntities = operations.select(select, CorrelationEntity.class); assertThat(correlationEntities).isEmpty(); } - } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ForceQuotedCompositePrimaryKeyRepositoryTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ForceQuotedCompositePrimaryKeyRepositoryTests.java index df8c46f3b..4c7963902 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ForceQuotedCompositePrimaryKeyRepositoryTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/compositeprimarykey/ForceQuotedCompositePrimaryKeyRepositoryTests.java @@ -31,13 +31,14 @@ public class ForceQuotedCompositePrimaryKeyRepositoryTests { CassandraTemplate cassandraTemplate; public void before() { - cassandraTemplate.deleteAll(Implicit.class); + cassandraTemplate.truncate(Implicit.class); } public String query(String columnName, String tableName, String keyZeroColumnName, String keyZero, String keyOneColumnName, String keyOne) { - return cassandraTemplate.queryForObject(String.format("select %s from %s where %s = '%s' and %s = '%s'", columnName, + return cassandraTemplate.getCqlOperations() + .queryForObject(String.format("select %s from %s where %s = '%s' and %s = '%s'", columnName, tableName, keyZeroColumnName, keyZero, keyOneColumnName, keyOne), String.class); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ForceQuotedRepositoryTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ForceQuotedRepositoryTests.java index d834ec472..d65e8c591 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ForceQuotedRepositoryTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/forcequote/config/ForceQuotedRepositoryTests.java @@ -33,11 +33,11 @@ public class ForceQuotedRepositoryTests { CassandraOperations cassandraTemplate; public void before() { - cassandraTemplate.deleteAll(Implicit.class); + cassandraTemplate.truncate(Implicit.class); } public String query(String columnName, String tableName, String keyColumnName, String key) { - return cassandraTemplate.queryForObject( + return cassandraTemplate.getCqlOperations().queryForObject( String.format("select %s from %s where %s = '%s'", columnName, tableName, keyColumnName, key), String.class); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/customconversion/CustomConversionTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/customconversion/CustomConversionTests.java index 15592ff12..75e48f4ad 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/customconversion/CustomConversionTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/customconversion/CustomConversionTests.java @@ -168,7 +168,8 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe @Test public void shouldLoadCustomConvertedObject() { - cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id").value("person", + cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id") + .value("person", "{\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}")); Employee employee = cassandraOperations.selectOne(QueryBuilder.select("id", "person").from("employee"), @@ -186,7 +187,8 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe @Test public void shouldLoadCustomConvertedWithCollectionsObject() { - cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id").value("people", + cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id") + .value("people", Collections.singleton("{\"firstname\":\"Apu\",\"lastname\":\"Nahasapeemapetilon\"}"))); Employee employee = cassandraOperations.selectOne(QueryBuilder.select("id", "people").from("employee"), @@ -205,9 +207,9 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe @Test public void dummy() { - cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id")); + cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id")); - cassandraOperations + cassandraOperations.getCqlOperations() .execute(QueryBuilder.update("employee").where(QueryBuilder.eq("id", "employee-id")).with(QueryBuilder .set("people", Collections.singleton("{\"firstname\":\"Apu\",\"lastname\":\"Nahasapeemapetilon\"}")))); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTests.java index e3bce187f..503745868 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTests.java @@ -63,7 +63,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac // select SinglePkcId id = id(SinglePkcId.class).key(saved.getKey()); - SinglePkc selected = operations.selectOneById(SinglePkc.class, id); + SinglePkc selected = operations.selectOneById(id, SinglePkc.class); assertThat(saved).isNotSameAs(selected); assertThat(selected.getKey()).isEqualTo(saved.getKey()); assertThat(selected.getValue()).isEqualTo(saved.getValue()); @@ -73,13 +73,13 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac SinglePkc updated = operations.update(selected); assertThat(selected).isSameAs(updated); - selected = operations.selectOneById(SinglePkc.class, id); + selected = operations.selectOneById(id, SinglePkc.class); assertThat(updated).isNotSameAs(selected); assertThat(selected.getValue()).isEqualTo(updated.getValue()); // delete operations.delete(selected); - assertThat(operations.selectOneById(SinglePkc.class, id)).isNull(); + assertThat(operations.selectOneById(id, SinglePkc.class)).isNull(); } public interface SinglePkcId { @@ -127,7 +127,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac // select MultiPkcId id = id(MultiPkcId.class).key0(saved.getKey0()).key1(saved.getKey1()); - MultiPkc selected = operations.selectOneById(MultiPkc.class, id); + MultiPkc selected = operations.selectOneById(id, MultiPkc.class); assertThat(saved).isNotSameAs(selected); assertThat(selected.getKey0()).isEqualTo(saved.getKey0()); assertThat(selected.getKey1()).isEqualTo(saved.getKey1()); @@ -138,13 +138,13 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac MultiPkc updated = operations.update(selected); assertThat(selected).isSameAs(updated); - selected = operations.selectOneById(MultiPkc.class, id); + selected = operations.selectOneById(id, MultiPkc.class); assertThat(updated).isNotSameAs(selected); assertThat(selected.getValue()).isEqualTo(updated.getValue()); // delete operations.delete(selected); - assertThat(operations.selectOneById(MultiPkc.class, id)).isNull(); + assertThat(operations.selectOneById(id, MultiPkc.class)).isNull(); } public interface MultiPkcId { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java index 3e1790197..a9849a197 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/template/CassandraTemplateMapIdIntegrationTest.java @@ -63,7 +63,7 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat // select MapId id = id("key", saved.getKey()); - SinglePkc selected = operations.selectOneById(SinglePkc.class, id); + SinglePkc selected = operations.selectOneById(id, SinglePkc.class); assertThat(saved).isNotSameAs(selected); assertThat(selected.getKey()).isEqualTo(saved.getKey()); assertThat(selected.getValue()).isEqualTo(saved.getValue()); @@ -73,13 +73,13 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat SinglePkc updated = operations.update(selected); assertThat(selected).isSameAs(updated); - selected = operations.selectOneById(SinglePkc.class, id); + selected = operations.selectOneById(id, SinglePkc.class); assertThat(updated).isNotSameAs(selected); assertThat(selected.getValue()).isEqualTo(updated.getValue()); // delete operations.delete(selected); - assertThat(operations.selectOneById(SinglePkc.class, id)).isNull(); + assertThat(operations.selectOneById(id, SinglePkc.class)).isNull(); } @Table @@ -121,7 +121,7 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat // select MapId id = id("key0", saved.getKey0()).with("key1", saved.getKey1()); - MultiPkc selected = operations.selectOneById(MultiPkc.class, id); + MultiPkc selected = operations.selectOneById(id, MultiPkc.class); assertThat(saved).isNotSameAs(selected); assertThat(selected.getKey0()).isEqualTo(saved.getKey0()); assertThat(selected.getKey1()).isEqualTo(saved.getKey1()); @@ -132,13 +132,13 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat MultiPkc updated = operations.update(selected); assertThat(selected).isSameAs(updated); - selected = operations.selectOneById(MultiPkc.class, id); + selected = operations.selectOneById(id, MultiPkc.class); assertThat(updated).isNotSameAs(selected); assertThat(selected.getValue()).isEqualTo(updated.getValue()); // delete operations.delete(selected); - assertThat(operations.selectOneById(MultiPkc.class, id)).isNull(); + assertThat(operations.selectOneById(id, MultiPkc.class)).isNull(); } @Table diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/types/CassandraTypeMappingIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/types/CassandraTypeMappingIntegrationTest.java index 3012ab5e2..4249a76ef 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/types/CassandraTypeMappingIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/types/CassandraTypeMappingIntegrationTest.java @@ -28,10 +28,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.UUID; +import com.datastax.driver.core.SimpleStatement; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.domain.AllPossibleTypes; @@ -74,7 +76,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setInet(InetAddress.getByName("127.0.0.1")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getInet()).isEqualTo(entity.getInet()); } @@ -89,7 +91,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setUuid(UUID.randomUUID()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getUuid()).isEqualTo(entity.getUuid()); } @@ -104,7 +106,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedShort(Short.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedShort()).isEqualTo(entity.getBoxedShort()); } @@ -119,7 +121,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveShort(Short.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveShort()).isEqualTo(entity.getPrimitiveShort()); } @@ -134,7 +136,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedByte(Byte.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedByte()).isEqualTo(entity.getBoxedByte()); } @@ -149,7 +151,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveByte(Byte.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveByte()).isEqualTo(entity.getPrimitiveByte()); } @@ -164,7 +166,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedLong(Long.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedLong()).isEqualTo(entity.getBoxedLong()); } @@ -179,7 +181,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveLong(Long.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveLong()).isEqualTo(entity.getPrimitiveLong()); } @@ -194,7 +196,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedInteger(Integer.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedInteger()).isEqualTo(entity.getBoxedInteger()); } @@ -209,7 +211,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveInteger(Integer.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveInteger()).isEqualTo(entity.getPrimitiveInteger()); } @@ -224,7 +226,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedFloat(Float.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedFloat()).isEqualTo(entity.getBoxedFloat()); } @@ -239,7 +241,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveFloat(Float.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveFloat()).isEqualTo(entity.getPrimitiveFloat()); } @@ -254,7 +256,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedDouble(Double.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedDouble()).isEqualTo(entity.getBoxedDouble()); } @@ -269,7 +271,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveDouble(Double.MAX_VALUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getPrimitiveDouble()).isEqualTo(entity.getPrimitiveDouble()); } @@ -284,7 +286,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBoxedBoolean(Boolean.TRUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBoxedBoolean()).isEqualTo(entity.getBoxedBoolean()); } @@ -299,7 +301,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setPrimitiveBoolean(Boolean.TRUE); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.isPrimitiveBoolean()).isEqualTo(entity.isPrimitiveBoolean()); } @@ -315,7 +317,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setTimestamp(new Date(1)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getTimestamp()).isEqualTo(entity.getTimestamp()); } @@ -330,7 +332,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setDate(LocalDate.fromDaysSinceEpoch(1)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getDate()).isEqualTo(entity.getDate()); } @@ -345,7 +347,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBigInteger(new BigInteger("123456")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBigInteger()).isEqualTo(entity.getBigInteger()); } @@ -360,7 +362,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBigDecimal(new BigDecimal("123456.7890123")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBigDecimal()).isEqualTo(entity.getBigDecimal()); } @@ -375,7 +377,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBlob(ByteBuffer.wrap("Hello".getBytes())); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); ByteBuffer blob = loaded.getBlob(); byte[] bytes = new byte[blob.remaining()]; @@ -393,7 +395,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setSetOfString(Collections.singleton("hello")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getSetOfString()).isEqualTo(entity.getSetOfString()); } @@ -408,7 +410,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setSetOfString(new HashSet()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getSetOfString()).isNull(); } @@ -423,7 +425,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setListOfString(Collections.singletonList("hello")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getListOfString()).isEqualTo(entity.getListOfString()); } @@ -438,7 +440,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setListOfString(new ArrayList()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getListOfString()).isNull(); } @@ -453,7 +455,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setMapOfString(Collections.singletonMap("hello", "world")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getMapOfString()).isEqualTo(entity.getMapOfString()); } @@ -468,7 +470,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setMapOfString(new HashMap()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getMapOfString()).isNull(); } @@ -483,7 +485,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setAnEnum(Condition.MINT); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getAnEnum()).isEqualTo(entity.getAnEnum()); } @@ -499,11 +501,10 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin String id = "1"; long time = 21312214L; - PreparedStatement prepare = operations.getSession().prepare("INSERT INTO timeentity (id, time) values(?,?)"); - BoundStatement boundStatement = prepare.bind(id, time); - operations.execute(boundStatement); + operations.getCqlOperations() + .execute(new SimpleStatement("INSERT INTO timeentity (id, time) values(?,?)", id, time)); - TimeEntity loaded = operations.selectOneById(TimeEntity.class, id); + TimeEntity loaded = operations.selectOneById(id, TimeEntity.class); assertThat(loaded.getTime()).isEqualTo(time); } @@ -518,7 +519,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setLocalDate(java.time.LocalDate.of(2010, 7, 4)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getLocalDate()).isEqualTo(entity.getLocalDate()); } @@ -533,7 +534,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setLocalDateTime(java.time.LocalDateTime.of(2010, 7, 4, 1, 2, 3)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getLocalDateTime()).isEqualTo(entity.getLocalDateTime()); } @@ -548,7 +549,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setLocalTime(java.time.LocalTime.of(1, 2, 3)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getLocalTime()).isEqualTo(entity.getLocalTime()); } @@ -563,7 +564,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setInstant(java.time.Instant.now()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getInstant()).isEqualTo(entity.getInstant()); } @@ -578,7 +579,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setZoneId(java.time.ZoneId.of("Europe/Paris")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getZoneId()).isEqualTo(entity.getZoneId()); } @@ -593,7 +594,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setJodaLocalDate(new org.joda.time.LocalDate(2010, 7, 4)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getJodaLocalDate()).isEqualTo(entity.getJodaLocalDate()); } @@ -608,7 +609,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setJodaDateMidnight(new org.joda.time.DateMidnight(2010, 7, 4)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getJodaDateMidnight()).isEqualTo(entity.getJodaDateMidnight()); } @@ -623,7 +624,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setJodaDateTime(new org.joda.time.DateTime(2010, 7, 4, 1, 2, 3)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getJodaDateTime()).isEqualTo(entity.getJodaDateTime()); } @@ -638,7 +639,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBpLocalDate(org.threeten.bp.LocalDate.of(2010, 7, 4)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBpLocalDate()).isEqualTo(entity.getBpLocalDate()); } @@ -653,7 +654,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBpLocalDateTime(org.threeten.bp.LocalDateTime.of(2010, 7, 4, 1, 2, 3)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBpLocalDateTime()).isEqualTo(entity.getBpLocalDateTime()); } @@ -668,7 +669,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBpLocalTime(org.threeten.bp.LocalTime.of(1, 2, 3)); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBpLocalTime()).isEqualTo(entity.getBpLocalTime()); } @@ -683,7 +684,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBpInstant(org.threeten.bp.Instant.now()); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBpZoneId()).isEqualTo(entity.getBpZoneId()); } @@ -698,7 +699,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setBpZoneId(org.threeten.bp.ZoneId.of("Europe/Paris")); operations.insert(entity); - AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId()); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getBpZoneId()).isEqualTo(entity.getBpZoneId()); } @@ -714,7 +715,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin entity.setCount(1); operations.update(entity); - CounterEntity loaded = operations.selectOneById(CounterEntity.class, entity.getId()); + CounterEntity loaded = operations.selectOneById(entity.getId(), CounterEntity.class); assertThat(loaded.getCount()).isEqualTo(entity.getCount()); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java index 793a82e8d..39db3c21e 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/cdi/CassandraOperationsProducer.java @@ -23,6 +23,9 @@ import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.inject.Singleton; +import org.springframework.cassandra.core.cql.CqlIdentifier; +import org.springframework.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator; +import org.springframework.cassandra.core.cql.generator.DropKeyspaceCqlGenerator; import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; import org.springframework.cassandra.support.RandomKeySpaceName; @@ -72,16 +75,16 @@ class CassandraOperationsProducer { CreateKeyspaceSpecification createKeyspaceSpecification = new CreateKeyspaceSpecification(KEYSPACE_NAME) .ifNotExists(); - cassandraTemplate.execute(createKeyspaceSpecification); - cassandraTemplate.execute("USE " + KEYSPACE_NAME); + cassandraTemplate.getCqlOperations().execute(CreateKeyspaceCqlGenerator.toCql(createKeyspaceSpecification)); + cassandraTemplate.getCqlOperations().execute("USE " + KEYSPACE_NAME); CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(mappingContext, cassandraTemplate); schemaCreator.createUserTypes(false, false, true); schemaCreator.createTables(false, false, true); for (CassandraPersistentEntity entity : cassandraTemplate.getConverter().getMappingContext() - .getNonPrimaryKeyEntities()) { - cassandraTemplate.truncate(entity.getTableName()); + .getPersistentEntities()) { + cassandraTemplate.truncate(entity.getType()); } return cassandraTemplate; @@ -97,8 +100,8 @@ class CassandraOperationsProducer { public void close(@Disposes CassandraOperations cassandraOperations) { - cassandraOperations.execute(DropKeyspaceSpecification.dropKeyspace(KEYSPACE_NAME)); - cassandraOperations.getSession().close(); + cassandraOperations.getCqlOperations() + .execute(DropKeyspaceCqlGenerator.toCql(DropKeyspaceSpecification.dropKeyspace(KEYSPACE_NAME))); } public void close(@Disposes Cluster cluster) { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java index 51fdfbc95..da9026970 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import com.datastax.driver.core.Session; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -70,9 +71,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC } - @Autowired private CassandraOperations template; - - @Autowired private PersonRepository personRepository; + @Autowired CassandraOperations template; + @Autowired Session session; + @Autowired PersonRepository personRepository; private Person walter; private Person skyler; @@ -144,7 +145,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC @Test public void shouldFindByMappedUdt() throws InterruptedException { - template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);"); // Give Cassandra some time to build the index Thread.sleep(500); @@ -160,7 +161,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC @Test public void shouldFindByMappedUdtStringQuery() throws InterruptedException { - template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);"); // Give Cassandra some time to build the index Thread.sleep(500); @@ -189,7 +190,8 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC assumeTrue(Version.parse(SpringVersion.getVersion()).isGreaterThanOrEqualTo(Version.parse("4.3"))); - template.execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);"); + template.getCqlOperations() + .execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);"); // Give Cassandra some time to build the index Thread.sleep(500); @@ -205,7 +207,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC @Test public void shouldFindByLocalDate() throws InterruptedException { - template.execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);"); // Give Cassandra some time to build the index Thread.sleep(500); @@ -239,9 +241,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC @Test public void shouldUseStartsWithQuery() throws InterruptedException { - assumeTrue(CassandraVersion.get(template.getSession()).isGreaterThanOrEqualTo(Version.parse("3.4"))); + assumeTrue(CassandraVersion.get(session).isGreaterThanOrEqualTo(Version.parse("3.4"))); - template.execute( + template.getCqlOperations().execute( "CREATE CUSTOM INDEX IF NOT EXISTS fn_starts_with ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex';"); // Give Cassandra some time to build the index @@ -259,9 +261,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC @Test public void shouldUseContainsQuery() throws InterruptedException { - assumeTrue(CassandraVersion.get(template.getSession()).isGreaterThanOrEqualTo(Version.parse("3.4"))); + assumeTrue(CassandraVersion.get(session).isGreaterThanOrEqualTo(Version.parse("3.4"))); - template.execute( + template.getCqlOperations().execute( "CREATE CUSTOM INDEX IF NOT EXISTS fn_contains ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex'\n" + "WITH OPTIONS = { 'mode': 'CONTAINS' };"); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryIntegrationTests.java index a89e757ef..c4751ba80 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryIntegrationTests.java @@ -51,7 +51,7 @@ public class UserRepositoryIntegrationTests { public void setUp() { - template.execute("CREATE INDEX IF NOT EXISTS users_address ON users (address);"); + template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS users_address ON users (address);"); repository.deleteAll(); @@ -89,7 +89,8 @@ public class UserRepositoryIntegrationTests { scott.setPassword("444"); scott.setPlace("Boston"); - all = template.insert(Arrays.asList(tom, bob, alice, scott)); + all = Arrays.asList(tom, bob, alice, scott); + template.batchOps().insert(all).execute(); } public void before() { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java index d6501486d..75ef14853 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java @@ -38,13 +38,14 @@ public abstract class AbstractSpringDataEmbeddedCassandraIntegrationTest * Truncate table for all known {@link org.springframework.data.mapping.PersistentEntity entities}. */ public void deleteAllEntities() { + for (CassandraPersistentEntity entity : template.getConverter().getMappingContext().getPersistentEntities()) { if (entity.getType().isInterface()) { continue; } - template.truncate(entity.getTableName()); + template.truncate(entity.getType()); } } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/SchemaTestUtils.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/SchemaTestUtils.java index 536ccb073..11f901578 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/SchemaTestUtils.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/SchemaTestUtils.java @@ -15,15 +15,17 @@ */ package org.springframework.data.cassandra.test.integration.support; +import org.springframework.cassandra.core.SessionCallback; import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; import org.springframework.cassandra.core.keyspace.CreateTableSpecification; +import org.springframework.dao.DataAccessException; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.Session; -import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.exceptions.DriverException; /** * {@link SchemaTestUtils} is a collection of reflection-based utility methods for use in unit and integration testing @@ -43,13 +45,19 @@ public class SchemaTestUtils { CassandraMappingContext mappingContext = operations.getConverter().getMappingContext(); CassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); - Session session = operations.getSession(); - KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace()); - if (keyspace.getTable(persistentEntity.getTableName().toCql()) == null) { - CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); - operations.execute(new CreateTableCqlGenerator(tableSpecification).toCql()); - } + operations.getCqlOperations().execute(new SessionCallback() { + @Override + public Object doInSession(Session session) throws DriverException, DataAccessException { + + KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace()); + if (keyspace.getTable(persistentEntity.getTableName().toCql()) == null) { + CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); + operations.getCqlOperations().execute(new CreateTableCqlGenerator(tableSpecification).toCql()); + } + return null; + } + }); } /** @@ -59,10 +67,6 @@ public class SchemaTestUtils { * @param operations must not be {@literal null}. */ public static void truncate(Class entityClass, CassandraOperations operations) { - - CassandraMappingContext mappingContext = operations.getConverter().getMappingContext(); - CassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); - - operations.execute(QueryBuilder.truncate(persistentEntity.getTableName().toCql())); + operations.truncate(entityClass); } }