@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]]
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user