Remove ListenableFuture support.

Closes #1548
This commit is contained in:
Mark Paluch
2025-01-08 10:41:27 +01:00
parent 3adf6a0d9b
commit 5255cd07db
28 changed files with 9 additions and 6794 deletions

View File

@@ -22,7 +22,6 @@ import java.util.function.Consumer;
import java.util.stream.Collector;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
@@ -75,7 +74,7 @@ class AsyncResultStream<T> {
/**
* Performs a mutable reduction operation on the elements of this stream using a {@link Collector} resulting in a
* {@link ListenableFuture}.
* {@link CompletableFuture}.
* <p>
* This is a terminal operation.
*
@@ -97,7 +96,7 @@ class AsyncResultStream<T> {
}
/**
* Performs an action for each element of this stream. This method returns a {@link ListenableFuture} that completes
* Performs an action for each element of this stream. This method returns a {@link CompletableFuture} that completes
* without a value ({@code null}) once all elements have been processed.
* <p>
* This is a terminal operation.
@@ -177,7 +176,7 @@ class AsyncResultStream<T> {
class CollectState<A, R> {
private final AtomicInteger rowNumber = new AtomicInteger();
private volatile A intermediate;
private final A intermediate;
private final Collector<? super T, A, R> collector;
CollectState(Collector<? super T, A, R> collector) {

View File

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

View File

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

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.CqlProvider;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.cql.PreparedStatementCallback;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
/**
* One of the two central callback interfaces used by the
* {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate} class. This interface prepares a CQL
* statement returning a {@link ListenableFuture} given a {@link CqlSession}, provided by the {@link CqlTemplate} class.
* <p>
* Implementations may either create new prepared statements or reuse cached instances. Implementations do not need to
* concern themselves with {@link DriverException}s that may be thrown from operations they attempt. The
* {@link org.springframework.data.cassandra.core.cql.AsyncCqlTemplate} class will catch and handle
* {@link DriverException}s appropriately.
* <p>
* Classes implementing this interface should also implement the {@link CqlProvider} interface if it is able to provide
* the CQL it uses for {@link PreparedStatement} creation. This allows for better contextual information in case of
* exceptions.
*
* @author Mark Paluch
* @since 4.0
* @see org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate#execute(AsyncPreparedStatementCreator,
* PreparedStatementCallback)
* @see CqlProvider
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
@SuppressWarnings("removal")
public interface AsyncPreparedStatementCreator {
/**
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}s. The
* {@link CqlTemplate} will attempt to cache the {@link PreparedStatement}s for future use without the overhead of
* re-preparing on the entire cluster.
*
* @param session Session to use to create statement, must not be {@literal null}.
* @return a prepared statement.
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
* method. The {@link AsyncCqlTemplate} class will handle them.
*/
ListenableFuture<PreparedStatement> createPreparedStatement(CqlSession session) throws DriverException;
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.RowCallbackHandler;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
/**
* Callback interface used by {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate}'s query
* methods. Implementations of this interface perform the actual work of extracting results from a
* {@link AsyncResultSet}, but don't need to worry about exception handling. {@link DriverException}s will be caught and
* handled by the calling {@link org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate}.
* <p>
* This interface is mainly used within the CQL framework itself. A {@link RowMapper} is usually a simpler choice for
* {@link AsyncResultSet} processing, mapping one result object per row instead of one result object for the entire
* {@link AsyncResultSet}.
* <p>
* Note: In contrast to a {@link RowCallbackHandler}, a {@link AsyncResultSetExtractor} object is typically stateless
* and thus reusable, as long as it doesn't access stateful resources or keep result state within the object.
*
* @author Mark Paluch
* @since 4.0
* @see AsyncCqlTemplate
* @see RowMapper
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
@SuppressWarnings("removal")
public interface AsyncResultSetExtractor<T> {
/**
* Implementations must implement this method to process the entire {@link AsyncResultSet}.
*
* @param resultSet {@link AsyncResultSet} to extract data from.
* @return an arbitrary result object, or {@literal null} if none (the extractor will typically be stateful in the
* latter case).
* @throws DriverException if a {@link DriverException} is encountered getting column values or navigating (that is,
* there's no need to catch {@link DriverException})
* @throws DataAccessException in case of custom exceptions
*/
@Nullable
ListenableFuture<T> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException;
}

View File

@@ -1,238 +0,0 @@
/*
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collector;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Asynchronous supplied sequence of elements supporting sequential operations over a {@link AsyncResultSet a result
* set}. An asynchronous stream represents a pipeline of operations to process a {@link AsyncResultSet}.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
@SuppressWarnings("removal")
class AsyncResultStream<T> {
private final AsyncResultSet resultSet;
private final RowMapper<T> mapper;
private AsyncResultStream(AsyncResultSet resultSet, RowMapper<T> mapper) {
this.resultSet = resultSet;
this.mapper = mapper;
}
/**
* Creates a {@link AsyncResultStream} given {@link AsyncResultSet}.
*
* @param resultSet the result set to process.
* @return a new {@link AsyncResultStream} instance.
*/
static AsyncResultStream<Row> from(AsyncResultSet resultSet) {
Assert.notNull(resultSet, "AsyncResultSet must not be null");
return new AsyncResultStream<>(resultSet, (row, rowNum) -> row);
}
/**
* Returns a stream consisting of the results of applying the given function to the elements of this stream.
* <p>
* This is an intermediate operation.
*
* @param <R> The element type of the new stream
* @param mapper a non-interfering, stateless {@link RowMapper}.
*/
<R> AsyncResultStream<R> map(RowMapper<R> mapper) {
Assert.notNull(mapper, "RowMapper must not be null");
return new AsyncResultStream<>(resultSet, mapper);
}
/**
* Performs a mutable reduction operation on the elements of this stream using a {@link Collector} resulting in a
* {@link ListenableFuture}.
* <p>
* This is a terminal operation.
*
* @param <R> the type of the result
* @param <A> the intermediate accumulation type of the {@link Collector}
* @param collector the {@link Collector} describing the reduction
* @return the result of the reduction
*/
<R, A> ListenableFuture<R> collect(Collector<? super T, A, R> collector) {
Assert.notNull(collector, "Collector must not be null");
SettableListenableFuture<R> future = new SettableListenableFuture<>();
CollectState<A, R> collectState = new CollectState<>(collector);
collectState.collectAsync(future, this.resultSet);
return future;
}
/**
* Performs an action for each element of this stream. This method returns a {@link ListenableFuture} that completes
* without a value ({@code null}) once all elements have been processed.
* <p>
* This is a terminal operation.
* <p>
* If the action accesses shared state, it is responsible for providing the required synchronization.
*
* @param action a non-interfering action to perform on the elements.
*/
ListenableFuture<Void> forEach(Consumer<T> action) {
Assert.notNull(action, "Action must not be null");
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
ForwardLoopState loopState = new ForwardLoopState(action);
loopState.forEachAsync(future, this.resultSet);
return future;
}
/**
* State object for forward-looping using {@code forEach}.
*/
class ForwardLoopState {
private final AtomicInteger rowNumber = new AtomicInteger();
private final Consumer<T> consumer;
ForwardLoopState(Consumer<T> consumer) {
this.consumer = consumer;
}
void peekRow(Iterable<Row> rows) {
rows.forEach(row -> consumer.accept(mapper.mapRow(row, rowNumber.incrementAndGet())));
}
/**
* Recursive async iteration.
*
* @param target
* @param resultSet
*/
void forEachAsync(SettableListenableFuture<Void> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
}
try {
peekRow(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(null);
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
} else {
forEachAsync(target, nextResultSet);
}
});
}
}
}
/**
* State object for collecting rows using {@code collect}.
*/
class CollectState<A, R> {
private final AtomicInteger rowNumber = new AtomicInteger();
private volatile A intermediate;
private final Collector<? super T, A, R> collector;
CollectState(Collector<? super T, A, R> collector) {
this.collector = collector;
this.intermediate = collector.supplier().get();
}
void collectPage(Iterable<Row> rows) {
for (Row row : rows) {
collector.accumulator().accept(intermediate, mapper.mapRow(row, rowNumber.incrementAndGet()));
}
}
R finish() {
return collector.finisher().apply(intermediate);
}
/**
* Recursive collection.
*
* @param target
* @param resultSet
*/
void collectAsync(SettableListenableFuture<R> target, AsyncResultSet resultSet) {
if (target.isCancelled()) {
return;
}
try {
collectPage(resultSet.currentPage());
} catch (RuntimeException e) {
target.setException(e);
return;
}
if (!resultSet.hasMorePages()) {
target.set(finish());
} else {
CompletionStage<AsyncResultSet> nextPage = resultSet.fetchNextPage();
nextPage.whenComplete((nextResultSet, throwable) -> {
if (throwable != null) {
target.setException(throwable);
} else {
collectAsync(target, nextResultSet);
}
});
}
}
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.cql.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.cql.ResultSetExtractor;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
/**
* Adapter implementation of the {@link ResultSetExtractor} interface that delegates to a {@link RowMapper} which is
* supposed to create an object for each row. Each object is added to the results List of this
* {@link ResultSetExtractor}.
* <p>
* Useful for the typical case of one object per row in the database table. The number of entries in the results will
* match the number of rows.
* <p>
* Note that a {@link RowMapper} object is typically stateless and thus reusable.
*
* @author Mark Paluch
* @since 4.0
* @see RowMapper
* @see AsyncCqlTemplate
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@SuppressWarnings("removal")
public class AsyncRowMapperResultSetExtractor<T> implements AsyncResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
/**
* Create a new {@link AsyncRowMapperResultSetExtractor}.
*
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
*/
public AsyncRowMapperResultSetExtractor(RowMapper<T> rowMapper) {
Assert.notNull(rowMapper, "RowMapper is must not be null");
this.rowMapper = rowMapper;
}
@Override
public ListenableFuture<List<T>> extractData(AsyncResultSet resultSet) throws DriverException, DataAccessException {
return AsyncResultStream.from(resultSet).map(rowMapper).collect(Collectors.toList());
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import org.springframework.dao.DataAccessException;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
/**
* Generic callback interface for code that operates asynchronously on a Cassandra {@link CqlSession}. Allows to execute
* any number of operations on a single session, using any type and number of statements.
* <p>
* This is particularly useful for delegating to existing data access code that expects a {@link CqlSession} to work on
* and throws {@link DriverException}. For newly written code, it is strongly recommended to use
* {@link AsyncCqlTemplate}'s more specific operations, for example a {@code query} or {@code update} variant.
*
* @author David Webb
* @author Mark Paluch
* @since 4.0
* @see AsyncCqlTemplate#execute(AsyncSessionCallback)
* @see AsyncCqlTemplate#query
* @see AsyncCqlTemplate#execute(String)
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant.
*/
@Deprecated(since = "4.0", forRemoval = true)
@FunctionalInterface
@SuppressWarnings("removal")
public interface AsyncSessionCallback<T> {
/**
* Gets called by {@link AsyncCqlTemplate#execute} with an active Cassandra {@link CqlSession}. Does not need to care
* about activating or closing the {@link CqlSession}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see {@link AsyncCqlTemplate#queryForObject}
* etc. A thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* template.
*
* @param session active Cassandra Session, must not be {@literal null}.
* @return a result object, or {@code ListenableFuture<Void>} if none.
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
* @throws DataAccessException in case of custom exceptions.
* @see AsyncCqlTemplate#queryForObject(String, Class)
* @see AsyncCqlTemplate#queryForResultSet(String)
*/
ListenableFuture<T> doInSession(CqlSession session) throws DriverException, DataAccessException;
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.util.concurrent.SuccessCallback;
/**
* Adapter class to {@link ListenableFuture} {@link ExecutionException} by applying a
* {@link PersistenceExceptionTranslator}.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
@SuppressWarnings("removal")
class ExceptionTranslatingListenableFutureAdapter<T> implements ListenableFuture<T> {
private final ListenableFuture<T> adaptee;
private final ListenableFuture<T> future;
/**
* Create a new {@link ExceptionTranslatingListenableFutureAdapter} given a {@link ListenableFuture} and a
* {@link PersistenceExceptionTranslator}.
*
* @param adaptee must not be {@literal null}.
* @param persistenceExceptionTranslator must not be {@literal null}.
*/
ExceptionTranslatingListenableFutureAdapter(ListenableFuture<T> adaptee,
PersistenceExceptionTranslator persistenceExceptionTranslator) {
Assert.notNull(adaptee, "ListenableFuture must not be null");
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
this.adaptee = adaptee;
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
}
private static <T> ListenableFuture<T> adaptListenableFuture(ListenableFuture<T> listenableFuture,
PersistenceExceptionTranslator exceptionTranslator) {
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<>();
listenableFuture.addCallback(new ListenableFutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {
settableFuture.set(result);
}
@Override
public void onFailure(Throwable ex) {
if (ex instanceof RuntimeException) {
DataAccessException dataAccessException = exceptionTranslator
.translateExceptionIfPossible((RuntimeException) ex);
if (dataAccessException != null) {
settableFuture.setException(dataAccessException);
return;
}
}
settableFuture.setException(ex);
}
});
return settableFuture;
}
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
future.addCallback(callback);
}
@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
future.addCallback(successCallback, failureCallback);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return adaptee.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return adaptee.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
}

View File

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

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.util;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.ListenableFutureCallbackRegistry;
import org.springframework.util.concurrent.SuccessCallback;
/**
* Adapts a {@link CompletableFuture} or {@link CompletionStage} into a Spring {@link ListenableFuture} applying
* {@link PersistenceExceptionTranslator}.
*
* @since 3.0
* @param <T> the result type returned by this Future's {@code get} method
* @deprecated since 3.0, use {@link CompletableFuture} directly.
*/
@Deprecated
public class CassandraFutureAdapter<T> implements ListenableFuture<T> {
private final CompletableFuture<T> completableFuture;
private final CompletableFuture<T> translated;
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<>();
/**
* Create a new adapter for the given {@link CompletionStage}.
*/
public CassandraFutureAdapter(CompletionStage<T> completionStage, PersistenceExceptionTranslator exceptionMapper) {
this(completionStage.toCompletableFuture(), exceptionMapper);
}
/**
* Create a new adapter for the given {@link CompletableFuture}.
*/
public CassandraFutureAdapter(CompletableFuture<T> completableFuture,
PersistenceExceptionTranslator exceptionMapper) {
this.completableFuture = completableFuture;
this.translated = new CompletableFuture<>();
this.completableFuture.whenComplete((result, ex) -> {
if (ex != null) {
Throwable exceptionToUse = ex;
if (exceptionToUse instanceof CompletionException) {
exceptionToUse = exceptionToUse.getCause();
}
if (exceptionToUse instanceof RuntimeException) {
RuntimeException translated = exceptionMapper.translateExceptionIfPossible((RuntimeException) exceptionToUse);
this.callbacks.failure(translated != null ? translated : ex);
this.translated.completeExceptionally(translated != null ? translated : ex);
} else {
this.callbacks.failure(ex);
this.translated.completeExceptionally(ex);
}
} else {
this.callbacks.success(result);
this.translated.complete(result);
}
});
}
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
this.callbacks.addCallback(callback);
}
@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
this.callbacks.addSuccessCallback(successCallback);
this.callbacks.addFailureCallback(failureCallback);
}
@Override
public CompletableFuture<T> completable() {
return this.completableFuture;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return this.completableFuture.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return this.completableFuture.isCancelled();
}
@Override
public boolean isDone() {
return this.completableFuture.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return this.translated.get();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return this.translated.get(timeout, unit);
}
}

View File

@@ -1,394 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.DeleteOptions;
import org.springframework.data.cassandra.core.EntityWriteResult;
import org.springframework.data.cassandra.core.InsertOptions;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.WriteResult;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.WriteOptions;
import org.springframework.data.cassandra.core.cql.legacy.AsyncCqlOperations;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.domain.Slice;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* Interface specifying a basic set of asynchronous Cassandra operations. Implemented by {@link AsyncCassandraTemplate}.
* Not often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Mark Paluch
* @author John Blum
* @since 4.0
* @see AsyncCassandraTemplate
* @see AsyncCqlOperations
* @see Statement
* @see InsertOptions
* @see UpdateOptions
* @deprecated since 4.0, use the {@link java.util.concurrent.CompletableFuture}-based variant
* {@link org.springframework.data.cassandra.core.AsyncCassandraTemplate}.
*/
@Deprecated(since = "4.0", forRemoval = true)
@SuppressWarnings("removal")
public interface AsyncCassandraOperations {
/**
* Expose the underlying {@link AsyncCqlOperations} to allow asynchronous CQL operations.
*
* @return the underlying {@link AsyncCqlOperations}.
* @see AsyncCqlOperations
*/
AsyncCqlOperations getAsyncCqlOperations();
/**
* Returns the underlying {@link CassandraConverter}.
*
* @return the underlying {@link CassandraConverter}.
*/
CassandraConverter getConverter();
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param cql must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.oss.driver.api.core.cql.Statement
// -------------------------------------------------------------------------
/**
* Execute the given Cassandra {@link Statement}. Any errors that result from executing this command will be converted
* into Spring's DAO exception hierarchy.
*
* @param statement a Cassandra {@link Statement}, must not be {@literal null}.
* @return the {@link AsyncResultSet}.
* @throws DataAccessException if there is any problem executing the query.
* @since 3.2
*/
ListenableFuture<AsyncResultSet> execute(Statement<?> statement) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities. A sliced
* query translates the effective {@link Statement#getFetchSize() fetch size} to the page size.
*
* @param statement the CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param statement must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Statement<?> statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Statement<?> statement, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.data.cassandra.core.query.Query
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query with paging and convert the result set to a {@link Slice} of entities.
*
* @param query the query object used to create a CQL statement, must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
* @see CassandraPageRequest
*/
<T> ListenableFuture<Slice<T>> slice(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param query must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Query query, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException;
/**
* Update the queried entities and return {@literal true} if the update was applied.
*
* @param query must not be {@literal null}.
* @param update must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> update(Query query, Update update, Class<?> entityClass) throws DataAccessException;
/**
* Remove entities (rows)/columns from the table by {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> delete(Query query, Class<?> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
/**
* Returns the number of rows for the given entity class.
*
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if any problem occurs while executing the query.
*/
ListenableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
* to further refine the selection criteria using a {@link Query} predicate to determine how many entities of the
* given {@link Class type} match the criteria.
*
* @param query user-provided count {@link Query} to execute; must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if any problem occurs while executing the query.
* @since 2.1
*/
ListenableFuture<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether a row of {@code entityClass} with the given {@code id} exists.
*
* @param id Id value. For single primary keys it's the plain value. For composite primary keys either, it's an
* instance of either {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
*/
ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
*
* @param query user-provided exists {@link Query} to execute; must not be {@literal null}.
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
* @return {@literal true} if the object exists.
* @throws DataAccessException if any problem occurs while executing the query.
* @since 2.1
*/
ListenableFuture<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
/**
* Insert the given entity and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity) throws DataAccessException;
/**
* Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link EntityWriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see InsertOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity) throws DataAccessException;
/**
* Update the given entity applying {@link WriteOptions} and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link EntityWriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see UpdateOptions#empty()
*/
<T> ListenableFuture<EntityWriteResult<T>> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> delete(T entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link WriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see QueryOptions#empty()
*/
ListenableFuture<WriteResult> delete(Object entity, QueryOptions options) throws DataAccessException;
/**
* Delete the given entity applying {@link DeleteOptions} and return the entity if the delete statement was applied.
*
* @param entity must not be {@literal null}.
* @param options must not be {@literal null}.
* @return the {@link WriteResult} for this operation.
* @throws DataAccessException if there is any problem executing the query.
* @see DeleteOptions#empty()
* @since 2.2
*/
default ListenableFuture<WriteResult> delete(Object entity, DeleteOptions options) throws DataAccessException {
return delete(entity, (QueryOptions) options);
}
/**
* Remove the given object from the table by id.
*
* @param id the Id value. For single primary keys it's the plain value. For composite primary keys either the
* {@link org.springframework.data.cassandra.core.mapping.PrimaryKeyClass} or
* {@link org.springframework.data.cassandra.core.mapping.MapId}. Must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Execute a {@code TRUNCATE} query to remove all entities of a given class.
*
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> truncate(Class<?> entityClass) throws DataAccessException;
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.Nullable;
/**
* Delegate class to encapsulate lifecycle event configuration and publishing. Event creation is deferred within an
* event {@link Supplier} to delay the actual event object creation.
*
* @author Mark Paluch
* @since 4.0
* @see ApplicationEventPublisher
*/
@Deprecated
class EntityLifecycleEventDelegate {
private @Nullable ApplicationEventPublisher publisher;
private boolean eventsEnabled = true;
public void setPublisher(@Nullable ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public boolean isEventsEnabled() {
return eventsEnabled;
}
public void setEventsEnabled(boolean eventsEnabled) {
this.eventsEnabled = eventsEnabled;
}
/**
* Publish an application event if event publishing is enabled.
*
* @param eventSupplier the supplier for application events.
*/
public void publishEvent(Supplier<?> eventSupplier) {
if (canPublishEvent()) {
publisher.publishEvent(eventSupplier.get());
}
}
private boolean canPublishEvent() {
return publisher != null && eventsEnabled;
}
}

View File

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

View File

@@ -1,184 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.lang.Nullable;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
/**
* Simple utility class for working with the QueryBuilder API using mapped entities.
* <p>
* Only intended for internal use.
*
* @author Mark Paluch
* @since 4.0
*/
@Deprecated(since = "4.0", forRemoval = true)
class EntityQueryUtils {
private static final Pattern FROM_REGEX = Pattern.compile(" FROM ([\"]?[\\w]*[\\\\.]?[\\w]*[\"]?)[\\s]?",
Pattern.CASE_INSENSITIVE);
/**
* Read a {@link Slice} of data from the {@link ResultSet} for a {@link Pageable}.
*
* @param resultSet must not be {@literal null}.
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
*/
static <T> Slice<T> readSlice(ResultSet resultSet, RowMapper<T> mapper, int page, int pageSize) {
int toRead = resultSet.getAvailableWithoutFetching();
return readSlice(() -> limit(resultSet.iterator(), toRead), resultSet.getExecutionInfo().getPagingState(), mapper,
page, pageSize);
}
/**
* Read a {@link Slice} of data from the {@link ResultSet} for a {@link Pageable}.
*
* @param resultSet must not be {@literal null}.
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
* @since 3.0
*/
static <T> Slice<T> readSlice(AsyncResultSet resultSet, RowMapper<T> mapper, int page, int pageSize) {
return readSlice(() -> limit(resultSet.currentPage().iterator(), resultSet.remaining()),
resultSet.getExecutionInfo().getPagingState(), mapper, page, pageSize);
}
/**
* Read a {@link Slice} of data from the {@link Iterable} of {@link Row}s for a {@link Pageable}.
*
* @param rows must not be {@literal null}.
* @param pagingState
* @param mapper must not be {@literal null}.
* @param page
* @param pageSize
* @return the resulting {@link Slice}.
* @since 2.1
*/
static <T> Slice<T> readSlice(Iterable<Row> rows, @Nullable ByteBuffer pagingState, RowMapper<T> mapper, int page,
int pageSize) {
List<T> result = new ArrayList<>(pageSize);
Iterator<Row> iterator = rows.iterator();
int index = 0;
while (iterator.hasNext()) {
T element = mapper.mapRow(iterator.next(), index++);
result.add(element);
}
CassandraPageRequest pageRequest = CassandraPageRequest.of(PageRequest.of(page, pageSize), pagingState);
return new SliceImpl<>(result, pageRequest, pagingState != null);
}
/**
* Extract the table name from a {@link Statement}.
*
* @param statement
* @return
* @since 2.1
*/
static CqlIdentifier getTableName(Statement<?> statement) {
String cql = statement instanceof SimpleStatement ? ((SimpleStatement) statement).getQuery() : statement.toString();
Matcher matcher = FROM_REGEX.matcher(cql);
if (matcher.find()) {
String cqlTableName = matcher.group(1);
int separator = cqlTableName.indexOf('.');
if (separator != -1) {
cqlTableName = cqlTableName.substring(separator + 1);
}
if (cqlTableName.startsWith("\"") || cqlTableName.endsWith("\"")) {
return CqlIdentifier.fromCql(cqlTableName.substring(separator + 1));
}
return CqlIdentifier.fromInternal(cqlTableName);
}
return CqlIdentifier.fromCql("unknown");
}
/**
* Returns a view containing the first {@code limitSize} elements of {@code iterator}. If {@code
* iterator} contains fewer than {@code limitSize} elements, the returned view contains all of its elements. The
* returned iterator supports {@code remove()} if {@code iterator} does.
*
* @param iterator the iterator to limit
* @param limitSize the maximum number of elements in the returned iterator
* @throws IllegalArgumentException if {@code limitSize} is negative
* @since 3.0
*/
private static <T> Iterator<T> limit(Iterator<T> iterator, int limitSize) {
return new Iterator<T>() {
private int count;
@Override
public boolean hasNext() {
return count < limitSize && iterator.hasNext();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
count++;
return iterator.next();
}
@Override
public void remove() {
iterator.remove();
}
};
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import java.util.List;
import org.springframework.data.cassandra.core.WriteResult;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* The result of a write operation for an entity.
*
* @author Mark Paluch
* @see WriteResult
*/
@Deprecated(since = "4.0", forRemoval = true)
class EntityWriteResult<T> extends org.springframework.data.cassandra.core.EntityWriteResult<T> {
public EntityWriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows, T entity) {
super(executionInfo, wasApplied, rows, entity);
}
}

View File

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

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import java.util.List;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
@Deprecated(since = "4.0", forRemoval = true)
class WriteResult extends org.springframework.data.cassandra.core.WriteResult {
WriteResult(List<ExecutionInfo> executionInfo, boolean wasApplied, List<Row> rows) {
super(executionInfo, wasApplied, rows);
}
}

View File

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

View File

@@ -35,9 +35,9 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.data.cassandra.ReactiveResultSet;
import org.springframework.data.cassandra.core.cql.session.DefaultBridgedReactiveSession;
import org.springframework.scheduling.annotation.AsyncResult;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
@@ -57,8 +57,8 @@ class DefaultBridgedReactiveSessionUnitTests {
@Mock CqlSession sessionMock;
private CompletableFuture<AsyncResultSet> future = new CompletableFuture<>();
private CompletableFuture<PreparedStatement> preparedStatementFuture = new CompletableFuture<>();
private final CompletableFuture<AsyncResultSet> future = new CompletableFuture<>();
private final CompletableFuture<PreparedStatement> preparedStatementFuture = new CompletableFuture<>();
private DefaultBridgedReactiveSession reactiveSession;
@@ -183,8 +183,6 @@ class DefaultBridgedReactiveSessionUnitTests {
@Test // DATACASS-509
void shouldFetchMore() {
Iterator<Row> rows = mockIterator();
AsyncResultSet resultSet = mock(AsyncResultSet.class);
when(resultSet.remaining()).thenReturn(10);
@@ -198,7 +196,7 @@ class DefaultBridgedReactiveSessionUnitTests {
future.complete(resultSet);
when(resultSet.hasMorePages()).thenReturn(true, false);
when(resultSet.fetchNextPage()).thenReturn(new AsyncResult<>(emptyResultSet).completable());
when(resultSet.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(emptyResultSet));
Flux<Row> flux = reactiveSession.execute(SimpleStatement.newInstance("")).flatMapMany(ReactiveResultSet::rows);

View File

@@ -1,185 +0,0 @@
/*
* Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
/**
* Integration tests for {@link AsyncCqlTemplate}.
*
* @author Mark Paluch
*/
@SuppressWarnings("removal")
class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
private static final AtomicBoolean initialized = new AtomicBoolean();
private AsyncCqlTemplate template;
@BeforeEach
void before() {
if (initialized.compareAndSet(false, true)) {
session.execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
}
session.execute("TRUNCATE user;");
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
template = new AsyncCqlTemplate();
template.setSession(getSession());
}
@Test // DATACASS-292
void executeShouldRemoveRecords() {
getUninterruptibly(template.execute("DELETE FROM user WHERE id = 'WHITE'"));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query("SELECT id FROM user;", row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectShouldReturnFirstColumn() {
String id = getUninterruptibly(template.queryForObject("SELECT id FROM user;", String.class));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(template.queryForMap("SELECT * FROM user;"));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
@Test // DATACASS-292
void executeStatementShouldRemoveRecords() {
getUninterruptibly(template.execute(SimpleStatement.newInstance("DELETE FROM user WHERE id = 'WHITE'")));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryStatementShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query(SimpleStatement.newInstance("SELECT id FROM user"), row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnFirstColumn() {
String id = getUninterruptibly(
template.queryForObject(SimpleStatement.newInstance("SELECT id FROM user"), String.class));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectStatementShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(
template.queryForMap(SimpleStatement.newInstance("SELECT * FROM user")));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
@Test // DATACASS-292
void executeWithArgsShouldRemoveRecords() {
getUninterruptibly(template.execute("DELETE FROM user WHERE id = ?", "WHITE"));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
@Test // DATACASS-292
void queryPreparedStatementShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query("SELECT id FROM user WHERE id = ?;", row -> {
result.add(row.getString(0));
}, "WHITE"));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryPreparedStatementCreatorShouldInvokeCallback() {
List<String> result = new ArrayList<>();
getUninterruptibly(template.query(session -> new CompletableToListenableFutureAdapter<>(
session.prepareAsync("SELECT id FROM user WHERE id = ?;")), ps -> ps.bind("WHITE"), row -> {
result.add(row.getString(0));
}));
assertThat(result).contains("WHITE");
}
@Test // DATACASS-292
void queryForObjectWithArgsShouldReturnFirstColumn() {
String id = getUninterruptibly(template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE"));
assertThat(id).isEqualTo("WHITE");
}
@Test // DATACASS-292
void queryForObjectWithArgsShouldReturnMap() {
Map<String, Object> map = getUninterruptibly(template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE"));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

View File

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

View File

@@ -1,142 +0,0 @@
/*
* Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.cql.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Unit tests for {@link AsyncResultStream}.
*
* @author Mark Paluch
*/
@SuppressWarnings("removal")
class AsyncResultStreamUnitTests {
private AsyncResultSet first = mock(AsyncResultSet.class);
private AsyncResultSet last = mock(AsyncResultSet.class);
private Row row1 = mock(Row.class);
private Row row2 = mock(Row.class);
@Test // DATACASS-656
void shouldIterateFirstPage() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
List<Row> rows = new ArrayList<>();
AsyncResultStream.from(first).forEach(rows::add);
assertThat(rows).containsOnly(row1);
}
@Test // DATACASS-656
void shouldIterateMappedFirstPage() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
List<String> rows = new ArrayList<>();
AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum).forEach(rows::add);
assertThat(rows).containsOnly("row-1");
}
@Test // DATACASS-656
void shouldIterateMappedPages() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
when(last.currentPage()).thenReturn(Collections.singletonList(row2));
when(first.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(last));
when(first.hasMorePages()).thenReturn(true);
List<String> rows = new ArrayList<>();
AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum).forEach(rows::add);
assertThat(rows).containsOnly("row-1", "row-2");
}
@Test // DATACASS-656
void shouldPropagateExceptionOnIterate() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
CompletableFuture<AsyncResultSet> failed = new CompletableFuture<>();
failed.completeExceptionally(new RuntimeException("boo"));
when(first.fetchNextPage()).thenReturn(failed);
when(first.hasMorePages()).thenReturn(true);
List<String> rows = new ArrayList<>();
ListenableFuture<Void> completion = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.forEach(rows::add);
assertThatThrownBy(completion::get).hasRootCauseInstanceOf(RuntimeException.class);
}
@Test // DATACASS-656
void shouldCollectFirstPage() throws ExecutionException, InterruptedException {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThat(collect.get()).containsOnly(row1);
}
@Test // DATACASS-656
void shouldCollectMappedPages() throws ExecutionException, InterruptedException {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
when(last.currentPage()).thenReturn(Collections.singletonList(row2));
when(first.fetchNextPage()).thenReturn(CompletableFuture.completedFuture(last));
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<String>> rows = AsyncResultStream.from(first).map((row, rowNum) -> "row-" + rowNum)
.collect(Collectors.toList());
assertThat(rows.get()).containsOnly("row-1", "row-2");
}
@Test // DATACASS-656
void shouldPropagateExceptionOnCollect() {
when(first.currentPage()).thenReturn(Collections.singletonList(row1));
CompletableFuture<AsyncResultSet> failed = new CompletableFuture<>();
failed.completeExceptionally(new RuntimeException("boo"));
when(first.fetchNextPage()).thenReturn(failed);
when(first.hasMorePages()).thenReturn(true);
ListenableFuture<List<Row>> collect = AsyncResultStream.from(first).collect(Collectors.toList());
assertThatThrownBy(collect::get).hasRootCauseInstanceOf(RuntimeException.class);
}
}

View File

@@ -1,380 +0,0 @@
/*
* Copyright 2022-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.legacy;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.cassandra.core.query.Criteria.*;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Future;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.DeleteOptions;
import org.springframework.data.cassandra.core.InsertOptions;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.legacy.AsyncCqlTemplate;
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;
import org.springframework.data.cassandra.domain.User;
import org.springframework.data.cassandra.domain.UserToken;
import org.springframework.data.cassandra.repository.support.SchemaTestUtils;
import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTests;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.uuid.Uuids;
/**
* Integration tests for {@link AsyncCassandraTemplate}.
*
* @author Mark Paluch
*/
@SuppressWarnings("removal")
class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTests {
private AsyncCassandraTemplate template;
@BeforeEach
void setUp() {
MappingCassandraConverter converter = new MappingCassandraConverter();
CassandraTemplate cassandraTemplate = new CassandraTemplate(session, converter);
template = new AsyncCassandraTemplate(new AsyncCqlTemplate(session), converter);
prepareTemplate(template);
SchemaTestUtils.potentiallyCreateTableFor(User.class, cassandraTemplate);
SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, cassandraTemplate);
SchemaTestUtils.truncate(User.class, cassandraTemplate);
SchemaTestUtils.truncate(UserToken.class, cassandraTemplate);
}
/**
* Post-process the {@link AsyncCassandraTemplate} before running the tests.
*
* @param template
*/
void prepareTemplate(AsyncCassandraTemplate template) {
template.setUsePreparedStatements(false);
}
@Test // DATACASS-343
void shouldSelectByQueryWithSorting() {
UserToken token1 = new UserToken();
token1.setUserId(Uuids.endOf(System.currentTimeMillis()));
token1.setToken(Uuids.startOf(System.currentTimeMillis()));
token1.setUserComment("foo");
UserToken token2 = new UserToken();
token2.setUserId(token1.getUserId());
token2.setToken(Uuids.endOf(System.currentTimeMillis() + 100));
token2.setUserComment("bar");
getUninterruptibly(template.insert(token1));
getUninterruptibly(template.insert(token2));
Query query = Query.query(where("userId").is(token1.getUserId())).sort(Sort.by("token"));
assertThat(getUninterruptibly(template.select(query, UserToken.class))).containsSequence(token1, token2);
}
@Test // DATACASS-343
void shouldSelectOneByQuery() {
UserToken token1 = new UserToken();
token1.setUserId(Uuids.endOf(System.currentTimeMillis()));
token1.setToken(Uuids.startOf(System.currentTimeMillis()));
token1.setUserComment("foo");
getUninterruptibly(template.insert(token1));
Query query = Query.query(where("userId").is(token1.getUserId()));
assertThat(getUninterruptibly(template.selectOne(query, UserToken.class))).isEqualTo(token1);
}
@Test // DATACASS-292
void insertShouldInsertEntity() {
User user = new User("heisenberg", "Walter", "White");
assertThat(getUser(user.getId())).isNull();
ListenableFuture<User> insert = template.insert(user);
assertThat(getUninterruptibly(insert)).isEqualTo(user);
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-250
void insertShouldCreateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> inserted = template.insert(user,
lwtOptions);
assertThat(getUninterruptibly(inserted).wasApplied()).isTrue();
}
@Test // DATACASS-250
void insertShouldNotUpdateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user, lwtOptions));
user.setFirstname("Walter Hartwell");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> lwt = template.insert(user,
lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter");
}
@Test // DATACASS-292
void shouldInsertAndCountEntities() {
User user = new User("heisenberg", "Walter", "White");
User result = getUninterruptibly(template.insert(user));
ListenableFuture<Long> count = template.count(User.class);
assertThat(result).isSameAs(user);
assertThat(getUninterruptibly(count)).isEqualTo(1L);
}
@Test // DATACASS-512
void shouldInsertEntityAndCountByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("heisenberg")), User.class))).isOne();
assertThat(getUninterruptibly(template.count(Query.query(where("id").is("foo")), User.class))).isZero();
}
@Test // DATACASS-512
void shouldInsertEntityAndExistsByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("heisenberg")), User.class))).isTrue();
assertThat(getUninterruptibly(template.exists(Query.query(where("id").is("foo")), User.class))).isFalse();
}
@Test // DATACASS-292
void updateShouldUpdateEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
User updated = getUninterruptibly(template.update(user));
assertThat(updated).isNotNull();
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-292
void updateShouldNotCreateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> lwt = template.update(user,
lwtOptions);
assertThat(getUninterruptibly(lwt).wasApplied()).isFalse();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
void updateShouldUpdateEntityWithLwt() throws InterruptedException {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
ListenableFuture<org.springframework.data.cassandra.core.EntityWriteResult<User>> updated = template.update(user,
lwtOptions);
assertThat(getUninterruptibly(updated).wasApplied()).isTrue();
assertThat(getUninterruptibly(updated).getEntity()).isSameAs(user);
}
@Test // DATACASS-343
void updateShouldUpdateEntityByQuery() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg"));
boolean result = getUninterruptibly(
template.update(query, Update.empty().set("firstname", "Walter Hartwell"), User.class));
assertThat(result).isTrue();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter Hartwell");
}
@Test // DATACASS-343
void deleteByQueryShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-343
void deleteColumnsByQueryShouldRemoveColumn() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg")).columns(Columns.from("lastname"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
User loaded = getUser(user.getId());
assertThat(loaded.getFirstname()).isEqualTo("Walter");
assertThat(loaded.getLastname()).isNull();
}
@Test // DATACASS-292
void deleteShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
User deleted = getUninterruptibly(template.delete(user));
assertThat(deleted).isNotNull();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
void deleteByIdShouldRemoveEntity() {
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Boolean deleted = getUninterruptibly(template.deleteById(user.getId(), User.class));
assertThat(deleted).isTrue();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-606
void deleteShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
assertThat(getUninterruptibly(template.delete(user, lwtOptions)).wasApplied()).isTrue();
}
@Test // DATACASS-606
void deleteByQueryShouldRemoveEntityWithLwt() {
DeleteOptions lwtOptions = DeleteOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
Query query = Query.query(where("id").is("heisenberg")).queryOptions(lwtOptions);
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUninterruptibly(template.delete(query, User.class))).isFalse();
}
@Test // DATACASS-56
void shouldPageRequests() {
Set<String> expectedIds = new LinkedHashSet<>();
for (int count = 0; count < 100; count++) {
User user = new User("heisenberg" + count, "Walter", "White");
expectedIds.add(user.getId());
getUninterruptibly(template.insert(user));
}
Set<String> ids = new HashSet<>();
Query query = Query.empty();
Slice<User> slice = getUninterruptibly(
template.slice(query.pageRequest(CassandraPageRequest.first(10)), User.class));
int iterations = 0;
do {
iterations++;
assertThat(slice).hasSize(10);
slice.stream().map(User::getId).forEach(ids::add);
if (slice.hasNext()) {
slice = getUninterruptibly(template.slice(query.pageRequest(slice.nextPageable()), User.class));
} else {
break;
}
} while (!slice.getContent().isEmpty());
assertThat(ids).containsAll(expectedIds);
assertThat(iterations).isEqualTo(10);
}
private User getUser(String id) {
return getUninterruptibly(template.selectOneById(id, User.class));
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception cause) {
throw new IllegalStateException(cause);
}
}
}

View File

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

View File

@@ -21,7 +21,7 @@ Defaults apply if the particular query option is not set.
NOTE: `CqlTemplate` comes in different execution model flavors.
The basic `CqlTemplate` uses a blocking execution model.
You can use `AsyncCqlTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or
You can use `AsyncCqlTemplate` for asynchronous execution and synchronization with `CompletableFuture` instances or
`ReactiveCqlTemplate` for reactive execution.
[[cassandracql-template.examples]]

View File

@@ -31,7 +31,7 @@ xref:cassandra/cql-template.adoc#exception-translation[exception translation] fo
NOTE: The Template API has different execution model flavors.
The basic `CassandraTemplate` uses a blocking (imperative-synchronous) execution model.
You can use `AsyncCassandraTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or `ReactiveCassandraTemplate` for reactive execution.
You can use `AsyncCassandraTemplate` for asynchronous execution and synchronization with `CompletableFuture` instances or `ReactiveCassandraTemplate` for reactive execution.
[[cassandra.template.instantiating]]
== Instantiating `CassandraTemplate`