/* * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cassandra.core; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Map; import java.util.Optional; import java.util.function.Function; import org.reactivestreams.Publisher; import org.springframework.cassandra.core.session.DefaultReactiveSessionFactory; import org.springframework.cassandra.core.session.ReactiveResultSet; import org.springframework.cassandra.core.session.ReactiveSession; import org.springframework.cassandra.core.session.ReactiveSessionFactory; import org.springframework.cassandra.support.ReactiveCassandraAccessor; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.DataAccessUtils; import org.springframework.util.Assert; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Row; import com.datastax.driver.core.SimpleStatement; import com.datastax.driver.core.Statement; import com.datastax.driver.core.exceptions.DriverException; import com.datastax.driver.core.policies.RetryPolicy; import com.datastax.driver.core.querybuilder.QueryBuilder; /** * <b>This is the central class in the CQL core package for reactive 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 ReactiveResultSet}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 ResultSetExtractor} interface extracts values from a * {@link ReactiveResultSet}. 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 ReactiveSessionFactory} reference, * or get prepared in an application context and given to services as bean reference. Note: The * {@link ReactiveSessionFactory} 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 org.springframework.dao.support.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.cassandra.core.ReactiveCqlTemplate" as log category. * <p> * <b>NOTE: An instance of this class is thread-safe once configured.</b> * * @author Mark Paluch * @since 2.0 * @see PreparedStatementCreator * @see PreparedStatementBinder * @see PreparedStatementCallback * @see ResultSetExtractor * @see RowCallbackHandler * @see RowMapper * @see org.springframework.dao.support.PersistenceExceptionTranslator */ public class ReactiveCqlTemplate extends ReactiveCassandraAccessor implements ReactiveCqlOperations { /** * Placeholder for default values. */ private static final Statement DEFAULTS = QueryBuilder.select().from("DEFAULT"); /** * If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on * statements used for query processing. */ private int fetchSize = -1; /** * If this variable is set to a value, it will be used for setting the {@code retryPolicy} property on statements used * for query processing. */ private RetryPolicy retryPolicy; /** * If this variable is set to a value, it will be used for setting the {@code consistencyLevel} property on statements * used for query processing. */ private com.datastax.driver.core.ConsistencyLevel consistencyLevel; /** * Construct a new {@link ReactiveCqlTemplate Note: The {@link ReactiveSessionFactory} has to be set before using the * instance. * * @see #setSessionFactory */ public ReactiveCqlTemplate() {} /** * Construct a new {@link ReactiveCqlTemplate}, given a {@link ReactiveSession}. * * @param reactiveSession the {@link ReactiveSession}, must not be {@literal null}. */ public ReactiveCqlTemplate(ReactiveSession reactiveSession) { Assert.notNull(reactiveSession, "ReactiveSession must not be null"); setSessionFactory(new DefaultReactiveSessionFactory(reactiveSession)); afterPropertiesSet(); } /** * Construct a new {@link ReactiveCqlTemplate}, given a {@link ReactiveSessionFactory} to obtain * {@link ReactiveSession}s from. * * @param reactiveSessionFactory the {@link ReactiveSessionFactory} to obtain {@link ReactiveSession}s from, must not * be {@literal null}. */ public ReactiveCqlTemplate(ReactiveSessionFactory reactiveSessionFactory) { setSessionFactory(reactiveSessionFactory); afterPropertiesSet(); } /** * Set the consistency level for this {@link ReactiveCqlTemplate}. Consistency level defines the number of nodes * involved into query processing. Relaxed consistency level settings use fewer nodes but eventual consistency is more * likely to occur while a higher consistency level involves more nodes to obtain results with a higher consistency * guarantee. * * @see Statement#setConsistencyLevel(ConsistencyLevel) * @see RetryPolicy */ public void setConsistencyLevel(ConsistencyLevel consistencyLevel) { this.consistencyLevel = consistencyLevel; } /** * @return the {@link ConsistencyLevel} specified for this {@link ReactiveCqlTemplate}. */ public ConsistencyLevel getConsistencyLevel() { return consistencyLevel; } /** * Set the fetch size for this {@link ReactiveCqlTemplate}. This is important for processing large result sets: * Setting this higher than the default value will increase processing speed at the cost of memory consumption; * setting this lower can avoid transferring row data that will never be read by the application. Default is -1, * indicating to use the CQL driver's default configuration (i.e. to not pass a specific fetch size setting on to the * driver). * * @see Statement#setFetchSize(int) */ public void setFetchSize(int fetchSize) { this.fetchSize = fetchSize; } /** * @return the fetch size specified for this {@link ReactiveCqlTemplate}. */ public int getFetchSize() { return this.fetchSize; } /** * Set the retry policy for this {@link ReactiveCqlTemplate}. This is important for defining behavior when a request * fails. * * @see Statement#setRetryPolicy(RetryPolicy) * @see RetryPolicy */ public void setRetryPolicy(RetryPolicy retryPolicy) { this.retryPolicy = retryPolicy; } /** * @return the {@link RetryPolicy} specified for this {@link ReactiveCqlTemplate}. */ public RetryPolicy getRetryPolicy() { return retryPolicy; } // ------------------------------------------------------------------------- // Methods dealing with a plain org.springframework.cassandra.core.ReactiveSession // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(org.springframework.cassandra.core.ReactiveSessionCallback) */ @Override public <T> Flux<T> execute(ReactiveSessionCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); return createFlux(action).onErrorMap(translateException("ReactiveSessionCallback", getCql(action))); } // ------------------------------------------------------------------------- // Methods dealing with static CQL // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(java.lang.String) */ @Override public Mono<Boolean> execute(String cql) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); return queryForResultSet(cql).map(ReactiveResultSet::wasApplied); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ReactiveResultSetExtractor) */ @Override public <T> Flux<T> query(String cql, ReactiveResultSetExtractor<T> resultSetExtractor) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); Assert.notNull(resultSetExtractor, "ReactiveResultSetExtractor must not be null"); return createFlux(new SimpleStatement(cql), (session, stmt) -> { if (logger.isDebugEnabled()) { logger.debug("Executing CQL Statement [{}]", cql); } return session.execute(stmt).flatMapMany(resultSetExtractor::extractData); }).onErrorMap(translateException("Query", cql)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Flux<T> query(String cql, RowMapper<T> rowMapper) throws DataAccessException { return query(cql, new ReactiveRowMapperResultSetExtractor<>(rowMapper)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Mono<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException { return query(cql, rowMapper).buffer(2).flatMap(list -> Mono.just(DataAccessUtils.requiredSingleResult(list))) .next(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(java.lang.String, java.lang.Class) */ @Override public <T> Mono<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException { return queryForObject(cql, getSingleColumnRowMapper(requiredType)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForMap(java.lang.String) */ @Override public Mono<Map<String, Object>> queryForMap(String cql) throws DataAccessException { return queryForObject(cql, getColumnMapRowMapper()); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(java.lang.String, java.lang.Class) */ @Override public <T> Flux<T> queryForFlux(String cql, Class<T> elementType) throws DataAccessException { return query(cql, getSingleColumnRowMapper(elementType)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(java.lang.String) */ @Override public Flux<Map<String, Object>> queryForFlux(String cql) throws DataAccessException { return query(cql, getColumnMapRowMapper()); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForResultSet(java.lang.String) */ @Override public Mono<ReactiveResultSet> queryForResultSet(String cql) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); return createMono(new SimpleStatement(cql), (session, statement) -> { if (logger.isDebugEnabled()) { logger.debug("Executing CQL [{}]", cql); } return session.execute(statement); }).onErrorMap(translateException("QueryForResultSet", cql)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForRows(java.lang.String) */ @Override public Flux<Row> queryForRows(String cql) throws DataAccessException { return queryForResultSet(cql).flatMapMany(ReactiveResultSet::rows) .onErrorMap(translateException("QueryForRows", cql)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(org.reactivestreams.Publisher) */ @Override public Flux<Boolean> execute(Publisher<String> statementPublisher) throws DataAccessException { Assert.notNull(statementPublisher, "CQL Publisher must not be null"); return Flux.from(statementPublisher).flatMap(this::execute); } // ------------------------------------------------------------------------- // Methods dealing with com.datastax.driver.core.Statement // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(com.datastax.driver.core.Statement) */ @Override public Mono<Boolean> execute(Statement statement) throws DataAccessException { Assert.notNull(statement, "CQL Statement must not be null"); return queryForResultSet(statement).map(ReactiveResultSet::wasApplied); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.ReactiveResultSetExtractor) */ @Override public <T> Flux<T> query(Statement statement, ReactiveResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(statement, "CQL Statement must not be null"); Assert.notNull(rse, "ReactiveResultSetExtractor must not be null"); return createFlux(statement, (session, stmt) -> { if (logger.isDebugEnabled()) { logger.debug("Executing CQL Statement [{}]", statement); } return session.execute(stmt).flatMapMany(rse::extractData); }).onErrorMap(translateException("Query", statement.toString())); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Flux<T> query(Statement statement, RowMapper<T> rowMapper) throws DataAccessException { return query(statement, new ReactiveRowMapperResultSetExtractor<>(rowMapper)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Mono<T> queryForObject(Statement statement, RowMapper<T> rowMapper) throws DataAccessException { return query(statement, rowMapper).buffer(2).flatMap(list -> Mono.just(DataAccessUtils.requiredSingleResult(list))) .next(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> Mono<T> queryForObject(Statement statement, Class<T> requiredType) throws DataAccessException { return queryForObject(statement, getSingleColumnRowMapper(requiredType)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForMap(com.datastax.driver.core.Statement) */ @Override public Mono<Map<String, Object>> queryForMap(Statement statement) throws DataAccessException { return queryForObject(statement, getColumnMapRowMapper()); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> Flux<T> queryForFlux(Statement statement, Class<T> elementType) throws DataAccessException { return query(statement, getSingleColumnRowMapper(elementType)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(com.datastax.driver.core.Statement) */ @Override public Flux<Map<String, Object>> queryForFlux(Statement statement) throws DataAccessException { return query(statement, getColumnMapRowMapper()); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForResultSet(com.datastax.driver.core.Statement) */ @Override public Mono<ReactiveResultSet> queryForResultSet(Statement statement) throws DataAccessException { Assert.notNull(statement, "CQL Statement must not be null"); return createMono(statement, (session, executedStatement) -> { if (logger.isDebugEnabled()) { logger.debug("Executing CQL [{}]", executedStatement); } return session.execute(executedStatement); }).onErrorMap(translateException("QueryForResultSet", statement.toString())); } @Override public Flux<Row> queryForRows(Statement statement) throws DataAccessException { return queryForResultSet(statement).flatMapMany(ReactiveResultSet::rows) .onErrorMap(translateException("QueryForRows", statement.toString())); } // ------------------------------------------------------------------------- // Methods dealing with prepared statements // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(org.springframework.cassandra.core.ReactivePreparedStatementCreator, org.springframework.cassandra.core.ReactivePreparedStatementCallback) */ @Override public <T> Flux<T> execute(ReactivePreparedStatementCreator psc, ReactivePreparedStatementCallback<T> action) throws DataAccessException { Assert.notNull(psc, "ReactivePreparedStatementCreator must not be null"); Assert.notNull(action, "ReactivePreparedStatementCallback object must not be null"); return createFlux(session -> { logger.debug("Preparing statement [{}] using {}", getCql(psc), psc); return psc.createPreparedStatement(session).doOnNext(this::applyStatementSettings) .flatMapMany(ps -> action.doInPreparedStatement(session, ps)); }).onErrorMap(translateException("ReactivePreparedStatementCallback", getCql(psc))); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.ReactivePreparedStatementCallback) */ @Override public <T> Flux<T> execute(String cql, ReactivePreparedStatementCallback<T> action) throws DataAccessException { return execute(new SimpleReactivePreparedStatementCreator(cql), action); } /** * Query using a prepared statement, reading the {@link ReactiveResultSet} with a {@link ReactiveResultSetExtractor}. * * @param psc object that can create a {@link PreparedStatement} given a {@link ReactiveSession} * @param preparedStatementBinder 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. * @param rse object that will extract results * @return an arbitrary result object, as returned by the {@link ReactiveResultSetExtractor} * @throws DataAccessException if there is any problem */ public <T> Flux<T> query(ReactivePreparedStatementCreator psc, PreparedStatementBinder preparedStatementBinder, ReactiveResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(psc, "ReactivePreparedStatementCreator must not be null"); Assert.notNull(rse, "ReactiveResultSetExtractor object must not be null"); return execute(psc, (session, ps) -> Mono.just(ps).flatMapMany(pps -> { if (logger.isDebugEnabled()) { logger.debug("Executing Prepared CQL Statement [{}]", ps.getQueryString()); } BoundStatement boundStatement = (preparedStatementBinder != null ? preparedStatementBinder.bindValues(ps) : ps.bind()); applyStatementSettings(boundStatement); return session.execute(boundStatement); }).flatMap(rse::extractData)).onErrorMap(translateException("Query", getCql(psc))); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(org.springframework.cassandra.core.ReactivePreparedStatementCreator, org.springframework.cassandra.core.ReactiveResultSetExtractor) */ @Override public <T> Flux<T> query(ReactivePreparedStatementCreator psc, ReactiveResultSetExtractor<T> rse) throws DataAccessException { return query(psc, null, rse); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ReactiveResultSetExtractor) */ @Override public <T> Flux<T> query(String cql, PreparedStatementBinder psb, ReactiveResultSetExtractor<T> rse) throws DataAccessException { return query(new SimpleReactivePreparedStatementCreator(cql), psb, rse); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ReactiveResultSetExtractor, java.lang.Object[]) */ @Override public <T> Flux<T> query(String cql, ReactiveResultSetExtractor<T> rse, Object... args) throws DataAccessException { return query(new SimpleReactivePreparedStatementCreator(cql), newArgPreparedStatementBinder(args), rse); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(org.springframework.cassandra.core.ReactivePreparedStatementCreator, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Flux<T> query(ReactivePreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException { return query(psc, null, new ReactiveRowMapperResultSetExtractor<>(rowMapper)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Flux<T> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException { return query(cql, psb, new ReactiveRowMapperResultSetExtractor<>(rowMapper)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(org.springframework.cassandra.core.ReactivePreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper) */ @Override public <T> Flux<T> query(ReactivePreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException { return query(psc, psb, new ReactiveRowMapperResultSetExtractor<>(rowMapper)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) */ @Override public <T> Flux<T> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException { return query(cql, newArgPreparedStatementBinder(args), rowMapper); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[]) */ @Override public <T> Mono<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException { return query(cql, rowMapper, args).buffer(2).flatMap(list -> Mono.just(DataAccessUtils.requiredSingleResult(list))) .next(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForObject(java.lang.String, java.lang.Class, java.lang.Object[]) */ @Override public <T> Mono<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException { return queryForObject(cql, getSingleColumnRowMapper(requiredType), args); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForMap(java.lang.String, java.lang.Object[]) */ @Override public Mono<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException { return queryForObject(cql, getColumnMapRowMapper(), args); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(java.lang.String, java.lang.Class, java.lang.Object[]) */ @Override public <T> Flux<T> queryForFlux(String cql, Class<T> elementType, Object... args) throws DataAccessException { return query(cql, getSingleColumnRowMapper(elementType), args); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForFlux(java.lang.String, java.lang.Object[]) */ @Override public Flux<Map<String, Object>> queryForFlux(String cql, Object... args) throws DataAccessException { return query(cql, getColumnMapRowMapper(), args); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForResultSet(java.lang.String, java.lang.Object[]) */ @Override public Mono<ReactiveResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); return query(new SimpleReactivePreparedStatementCreator(cql), newArgPreparedStatementBinder(args), Mono::just) .next(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#queryForRows(java.lang.String, java.lang.Object[]) */ @Override public Flux<Row> queryForRows(String cql, Object... args) throws DataAccessException { return queryForResultSet(cql, args).flatMapMany(ReactiveResultSet::rows) .onErrorMap(translateException("QueryForRows", cql)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(org.springframework.cassandra.core.ReactivePreparedStatementCreator) */ @Override public Mono<Boolean> execute(ReactivePreparedStatementCreator psc) throws DataAccessException { return query(psc, resultSet -> Mono.just(resultSet.wasApplied())).last(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder) */ @Override public Mono<Boolean> execute(String cql, PreparedStatementBinder psb) throws DataAccessException { return query(new SimpleReactivePreparedStatementCreator(cql), psb, resultSet -> Mono.just(resultSet.wasApplied())) .next(); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(java.lang.String, java.lang.Object[]) */ @Override public Mono<Boolean> execute(String cql, Object... args) throws DataAccessException { return execute(cql, newArgPreparedStatementBinder(args)); } /* (non-Javadoc) * @see org.springframework.cassandra.core.ReactiveCqlOperations#execute(java.lang.String, org.reactivestreams.Publisher) */ @Override public Flux<Boolean> execute(String cql, Publisher<Object[]> args) throws DataAccessException { Assert.notNull(args, "Args Publisher must not be null"); SimpleReactivePreparedStatementCreator psc = new SimpleReactivePreparedStatementCreator(cql); return execute(psc, (session, ps) -> Flux.from(args).flatMap(objects -> { if (logger.isDebugEnabled()) { logger.debug("Executing Prepared CQL Statement [{}]", cql); } BoundStatement boundStatement = newArgPreparedStatementBinder(objects).bindValues(ps); applyStatementSettings(boundStatement); return session.execute(boundStatement); }).map(ReactiveResultSet::wasApplied)); } // ------------------------------------------------------------------------- // Implementation hooks and helper methods // ------------------------------------------------------------------------- /** * Create a reusable {@link Flux} given a {@link ReactiveStatementCallback} without exception translation. * * @param callback must not be {@literal null}. * @return a reusable {@link Flux} wrapping the {@link ReactiveStatementCallback}. */ protected <T> Flux<T> createFlux(Statement statement, ReactiveStatementCallback<T> callback) { Assert.notNull(callback, "ReactiveStatementCallback must not be null"); applyStatementSettings(statement); ReactiveSession session = getSession(); return Flux.defer(() -> callback.doInStatement(session, statement)); } /** * Create a reusable {@link Mono} given a {@link ReactiveStatementCallback} without exception translation. * * @param callback must not be {@literal null}. * @return a reusable {@link Mono} wrapping the {@link ReactiveStatementCallback }. */ protected <T> Mono<T> createMono(Statement statement, ReactiveStatementCallback<T> callback) { Assert.notNull(callback, "ReactiveStatementCallback must not be null"); applyStatementSettings(statement); ReactiveSession session = getSession(); return Mono.defer(() -> Mono.from(callback.doInStatement(session, statement))); } /** * Create a reusable {@link Flux} given a {@link ReactiveSessionCallback} without exception translation. * * @param callback must not be {@literal null}. * @return a reusable {@link Flux} wrapping the {@link ReactiveSessionCallback}. */ protected <T> Flux<T> createFlux(ReactiveSessionCallback<T> callback) { Assert.notNull(callback, "ReactiveStatementCallback must not be null"); ReactiveSession session = getSession(); return Flux.defer(() -> callback.doInSession(session)); } /** * Exception translation {@link Function} intended for {@link Mono#otherwise(Function)} usage. * * @param task readable text describing the task being attempted * @param cql CQL query or update that caused the problem (may be {@code null}) * @return the exception translation {@link Function} * @see CqlProvider */ protected Function<Throwable, Throwable> translateException(String task, String cql) { return throwable -> throwable instanceof DriverException ? translate(task, cql, (DriverException) throwable) : throwable; } /** * Create a new RowMapper for reading columns as key-value pairs. * * @return the RowMapper to use * @see ColumnMapRowMapper */ protected RowMapper<Map<String, Object>> getColumnMapRowMapper() { return new ColumnMapRowMapper(); } /** * Create a new RowMapper for reading result objects from a single column. * * @param requiredType the type that each result object is expected to match * @return the RowMapper to use * @see SingleColumnRowMapper */ protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) { return SingleColumnRowMapper.newInstance(requiredType); } /** * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement * settings such as fetch size, retry policy, and consistency level. * * @param stmt the CQL Statement to prepare * @see #setFetchSize(int) * @see #setRetryPolicy(RetryPolicy) * @see #setConsistencyLevel(ConsistencyLevel) */ protected void applyStatementSettings(Statement stmt) { ConsistencyLevel consistencyLevel = getConsistencyLevel(); if (consistencyLevel != null && stmt.getConsistencyLevel() == DEFAULTS.getConsistencyLevel()) { stmt.setConsistencyLevel(consistencyLevel); } int fetchSize = getFetchSize(); if (fetchSize != -1 && stmt.getFetchSize() == DEFAULTS.getFetchSize()) { stmt.setFetchSize(fetchSize); } RetryPolicy retryPolicy = getRetryPolicy(); if (retryPolicy != null && stmt.getRetryPolicy() == DEFAULTS.getRetryPolicy()) { stmt.setRetryPolicy(retryPolicy); } } /** * Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement * settings such as retry policy and consistency level. * * @param stmt the CQL Statement to prepare * @see #setRetryPolicy(RetryPolicy) * @see #setConsistencyLevel(ConsistencyLevel) */ protected void applyStatementSettings(PreparedStatement stmt) { ConsistencyLevel consistencyLevel = getConsistencyLevel(); if (consistencyLevel != null) { stmt.setConsistencyLevel(consistencyLevel); } RetryPolicy retryPolicy = getRetryPolicy(); if (retryPolicy != null) { stmt.setRetryPolicy(retryPolicy); } } /** * Create a new arg-based PreparedStatementSetter using the args passed in. * <p> * By default, we'll create an {@link ArgumentPreparedStatementBinder}. This method allows for the creation to be * overridden by subclasses. * * @param args object array with arguments * @return the new {@link PreparedStatementBinder} to use */ protected PreparedStatementBinder newArgPreparedStatementBinder(Object[] args) { return new ArgumentPreparedStatementBinder(args); } private ReactiveSession getSession() { return getSessionFactory().getSession(); } /** * Determine CQL from potential provider object. * * @param cqlProvider object that's potentially a {@link CqlProvider} * @return the CQL string, or {@code null} * @see CqlProvider */ private static String getCql(Object cqlProvider) { return Optional.ofNullable(cqlProvider) // .filter(o -> o instanceof CqlProvider) // .map(o -> (CqlProvider) o) // .map(CqlProvider::getCql) // .orElse(null); } private class SimpleReactivePreparedStatementCreator implements ReactivePreparedStatementCreator, CqlProvider { private final String cql; SimpleReactivePreparedStatementCreator(String cql) { Assert.notNull(cql, "CQL must not be null"); this.cql = cql; } @Override public Mono<PreparedStatement> createPreparedStatement(ReactiveSession session) throws DriverException { return session.prepare(cql); } @Override public String getCql() { return cql; } } }