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); } }